4

I'm trying to play a simple sineous waveform using the Windows Audio Session API (WASAPI) in exclusive mode, but encountering sound glitches no matter what I do. I've been using the MSDN Exclusive-Mode Streams example as a reference point, and here's how the slightly adapted code currently looks like.

Setup code:

-- <variable declarations, incl. "HRESULT hr; BYTE *pData;" > --

// also, hr is checked for errors every step of the way

hr = CoCreateInstance(
    CLSID_MMDeviceEnumerator, NULL,
    CLSCTX_ALL, IID_IMMDeviceEnumerator,
    (void**)&pEnumerator);

hr = pEnumerator->GetDefaultAudioEndpoint(
    eRender, eConsole, &pDevice);

hr = pDevice->Activate(
    IID_IAudioClient, CLSCTX_ALL,
    NULL, (void**)&pAudioClient);


REFERENCE_TIME DefaultDevicePeriod = 0, MinimumDevicePeriod = 0;
hr = pAudioClient->GetDevicePeriod(&DefaultDevicePeriod, &MinimumDevicePeriod);

WAVEFORMATEX wave_format = {};
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = 2;
wave_format.nSamplesPerSec = 44100;
wave_format.nAvgBytesPerSec = 44100 * 2 * 16 / 8;
wave_format.nBlockAlign = 2 * 16 / 8;
wave_format.wBitsPerSample = 16;

hr = pAudioClient->IsFormatSupported(
    AUDCLNT_SHAREMODE_EXCLUSIVE,
    &wave_format,
    NULL // can't suggest a "closest match" in exclusive mode
    );

hr = pAudioClient->Initialize(
    AUDCLNT_SHAREMODE_EXCLUSIVE,
    AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
    MinimumDevicePeriod,
    MinimumDevicePeriod,
    &wave_format,
    NULL);


// Get the actual size of the allocated buffer.
hr = pAudioClient->GetBufferSize(&bufferFrameCount);

INT32 FrameSize_bytes = bufferFrameCount * wave_format.nChannels * wave_format.wBitsPerSample / 8;

hr = pAudioClient->GetService(
    IID_IAudioRenderClient,
    (void**)&pRenderClient);

hEvent = CreateEvent(nullptr, false, false, nullptr);
if (hEvent == INVALID_HANDLE_VALUE) { printf("CreateEvent failed\n");  return -1; }

hr = pAudioClient->SetEventHandle(hEvent);

Buffer setup:

const size_t num_samples = FrameSize_bytes / sizeof(unsigned short);

unsigned short *samples = new unsigned short[num_samples];

float min = (float)(std::numeric_limits<unsigned short>::min)();
float max = (float)(std::numeric_limits<unsigned short>::max)();
float halfmax = max / 2.0;
float dt = 1.0 / (float)wave_format.nSamplesPerSec;

float freq = (float)wave_format.nSamplesPerSec / (float)bufferFrameCount;

for (int i = 0; i < num_samples/2; ++i) {
    float t = (float)i * dt;
    samples[2*i] = sin_minmax_Hz(min, max, freq, t);
    samples[2*i + 1] = sin_minmax_Hz(min, max, freq, t);
}

hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);

memcpy(pData, samples, FrameSize_bytes);

hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);

DWORD taskIndex = 0;
hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);

if (hTask == NULL) {
    hr = E_FAIL;
    IF_ERROR_EXIT(hr);
}

The function sin_minmax_Hz is defined as follows:

#define TWO_PI (3.14159265359*2)

static inline float sin01(float alpha) {
    return 0.5*sin(alpha) + 0.5;
}

static inline float sin_minmax_Hz(float min, float max, float freq_Hz, float t) {
    return (max - min) / 2.0 * sin01(t * freq_Hz * TWO_PI);
}

Playback:

hr = pAudioClient->Start();  // Start playing.
IF_ERROR_EXIT(hr);

// just play indefinitely

while (true) {
    WaitForSingleObject(hEvent, INFINITE);

    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    memcpy(pData, samples, FrameSize_bytes);
    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, 0);
}

The problem is that at the minimum latency, the sine wave usually plays smoothly for about 2 seconds, and then starts glitching out with massive aliasing, sounding almost like a sawtooth wave. Am I missing something here?

(The whole working example can be found here.)

ehoopz
  • 139
  • 3
  • 10

2 Answers2

3

Just tried your full sample, IAudioClient::Initialize fails with AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED. This error was introduced in windows 7, are you running vista? After correcting the period size for alignment i get a perfect sinewave. If your system doesnt generate this error, try manually aligning the buffer on a 128-byte (not bit) boundary. Otherwise here's the alignment code:

hr = pAudioClient->Initialize(
        AUDCLNT_SHAREMODE_EXCLUSIVE,
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
        MinimumDevicePeriod,
        MinimumDevicePeriod,
        &wave_format,
        NULL);

  if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
    UINT32 nFramesInBuffer;
    REFERENCE_TIME hnsPeriod;
    hr = pAudioClient->GetBufferSize(&nFramesInBuffer);
    IF_ERROR_EXIT(hr);
    hnsPeriod = (REFERENCE_TIME)(REFTIMES_PER_SEC * nFramesInBuffer / wave_format.nSamplesPerSec + 0.5);
    hr = pAudioClient->Release();
    IF_ERROR_EXIT(hr);
    hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient);
    IF_ERROR_EXIT(hr);
    hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsPeriod, hnsPeriod, &wave_format, NULL);
    IF_ERROR_EXIT(hr);
  }
Sjoerd van Kreel
  • 1,000
  • 6
  • 19
  • I'm actually on Windows 10, but my system doesn't seem to be giving that alignment error. Could it be a driver-specific thing? Anyway, manually aligning the buffer to be a (integral) multiple of 128 bytes seems to work, at least for sample rates of 44100 and 48000 Hz. at 96KHz the same glitching starts to occur again. – ehoopz Jun 11 '16 at 08:30
  • Also, on my system, the `hnsPeriod` calculated by the code you posted gives a period less than `MinimumDevicePeriod` on my machine (frame size of 128 @ 44,1 KHz corresponds to a period of 29024, vs minimum 132 -> ~30000). So the first working period value @ 44,1 KHz seems to be 58049, which gives a frame size of 256. – ehoopz Jun 11 '16 at 08:46
  • Seems like this system can't really handle periods of `< ~5.5 ms`, I'll try on another system. – ehoopz Jun 11 '16 at 09:01
  • Hmm, my MacBook Pro (also running W10) with the Cirrus Logic CS4208 (AB 93) sound card handles even its minimum period (25000 -> 2.5 ms) with ease at any sample rate. Could it really be a driver issue? :O The system in the original question has an ASUS Xonar Essence STX. – ehoopz Jun 11 '16 at 09:31
  • A quick google search suggests the ASUS Xonar Essence STX has native asio drivers, is this correct? If so it's entirely possible that it's shipped with crappy wdm drivers. I actually experienced the same thing with my e-mu asio card. Runs fine with asio drivers at ~1.5 ms buffer size, can't get reliably below 10ms using it's wdm drivers. Maybe you can try switching to the onboard soundcard? These usually use the stock windows drivers which are actually quite decent. Alternatively you can just use ASIO. – Sjoerd van Kreel Jun 11 '16 at 10:01
  • The system had two other sound cards to test with, namely the Blue Yeti mic, and "High-definition audio device" (= mobo integrated), and both of those handled their minimum (128-byte-aligned) periods without any glitching, at any supported sample rate. Leaning towards driver issue. – ehoopz Jun 11 '16 at 10:06
0

I was stuck at the same problem. Try inserting a pAudioClient->Reset() right before Start() command. Like this:

hr = pAudioClient->Reset();
if (hr) .....
hr = pAudioClient->Start();

It worked for me. Good luck.