Sample - WASAPI exclusive-mode event-driven playback app, including the HD Audio alignment dance

Sample - WASAPI exclusive-mode event-driven playback app, including the HD Audio alignment dance

  • Comments 19

Attached to this post is a sample WASAPI exclusive-mode event-driven playback app, including amd64 and x86 binaries, source, and a modification of the ac3.wav Dolby Digital test tone to include a "fact" chunk.

>play-exclusive.exe -?
play-exclusive.exe -?
play-exclusive.exe --list-devices
play-exclusive.exe [--device "Device long name"] --file "WAV file name"

    -? prints this message.
    --list-devices displays the long names of all active playback devices.

Plays the given file to the given device in WASAPI exclusive mode.
If no device is specified, plays to the default console device.

On the particular system I used to test this, these are the devices I have:

>play-exclusive.exe --list-devices
Active render endpoints found: 3
    Digital Audio (S/PDIF) (2- High Definition Audio Device)
    Speakers (2- High Definition Audio Device)
    Sceptre (High Definition Audio Device)

And this is the output I get when I play the attached ac3.wav test tones to the Sceptre HDMI output:


>play-exclusive --device "Sceptre (High Definition Audio Device)" --file ac3.wav
Opening .wav file "ac3.wav"...
The default period for this device is 30000 hundred-nanoseconds, or 144 frames.
Buffer size not aligned - doing the alignment dance.
Trying again with periodicity of 33333 hundred-nanoseconds, or 160 frames.
We ended up with a period of 33333 hns or 160 frames.

Successfully played all 460800 frames.

A word on the "alignment dance" highlighted above... first, this scene from The Pacifier.  (Vin Diesel is so coordinated.)

The Pacifier: The Peter Panda dance

Here's the source for the dance (in play.cpp in the attached.)

// call IAudioClient::Initialize the first time
// this may very well fail
// if the device period is unaligned
hr = pAudioClient->Initialize(
    AUDCLNT_SHAREMODE_EXCLUSIVE,
    AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
    hnsPeriod, hnsPeriod, pWfx, NULL
);
// if you get a compilation error on AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED,
// uncomment the #define below
//#define AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED AUDCLNT_ERR(0x019)
if (AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED == hr) {

    // if the buffer size was not aligned, need to do the alignment dance
    printf("Buffer size not aligned - doing the alignment dance.\n");
   
    // get the buffer size, which will be aligned
    hr = pAudioClient->GetBufferSize(&nFramesInBuffer);
    if (FAILED(hr)) {
        printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr);
        return hr;
    }
   
    // throw away this IAudioClient
    pAudioClient->Release();

    // calculate the new aligned periodicity
    hnsPeriod = // hns =
        (REFERENCE_TIME)(
            10000.0 * // (hns / ms) *
            1000 * // (ms / s) *
            nFramesInBuffer / // frames /
            pWfx->nSamplesPerSec  // (frames / s)
            + 0.5 // rounding
        );

    // activate a new IAudioClient
    hr = pMMDevice->Activate(
        __uuidof(IAudioClient),
        CLSCTX_ALL, NULL,
        (void**)&pAudioClient
    );
    if (FAILED(hr)) {
        printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x\n", hr);
        return hr;
    }

    // try initialize again
    printf("Trying again with periodicity of %I64u hundred-nanoseconds, or %u frames.\n", hnsPeriod, nFramesInBuffer);
    hr = pAudioClient->Initialize(
        AUDCLNT_SHAREMODE_EXCLUSIVE,
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
        hnsPeriod, hnsPeriod, pWfx, NULL
    );

    if (FAILED(hr)) {
        printf("IAudioClient::Initialize failed, even with an aligned buffer: hr = 0x%08x\n", hr);
        pAudioClient->Release();
        return hr;
    }
} else if (FAILED(hr)) {
    printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr);
    pAudioClient->Release();
    return hr;
}

// OK, IAudioClient::Initialize succeeded
// let's see what buffer size we actually ended up with
hr = pAudioClient->GetBufferSize(&nFramesInBuffer);
if (FAILED(hr)) {
    printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr);
    pAudioClient->Release();
    return hr;
}

// calculate the new period
hnsPeriod = // hns =
    (REFERENCE_TIME)(
        10000.0 * // (hns / ms) *
        1000 * // (ms / s) *
        nFramesInBuffer / // frames /
        pWfx->nSamplesPerSec  // (frames / s)
        + 0.5 // rounding
    );

 

 

Note the new HRESULT. 

HD Audio works on a 128-byte aligned buffer size.  This dance ensures that the HD Audio driver is being fed data in chunks of 128 bytes.  It is somewhat complicated by the fact that IAudioClient::Initialize takes a parameter of hundred-nano-seconds, but IAudioClient::GetBufferSize sets a parameter of frames.

Attachment: play-exclusive.zip
Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post
  • Your comment on Raymond's blog is rather odd.

    http://blogs.msdn.com/oldnewthing/archive/2009/04/03/9529929.aspx#9530732

    How do you expect computers to work without virtual memory?

  • > How do you expect computers to work without virtual memory?

    Quickly* or not at all**.

    *If everything fits in physical memory

    **If it doesn't

  • I do not get it: You initialize it at 3 ms. It returns a buffer size of 160 bytes. You tear it down and re-initialize it with a time that corresponds to 160 bytes buffer. Woudn't ignoring "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED" give the same end-result, or did I miss something?

  • If you ignore the AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED error, then further calls you make on that IAudioClient object will fail (well, calls other than IAudioClient::GetBufferSize.)

    In particular, there's no way to get an IAudioRenderClient or an IAudioCaptureClient on a needs-alignment endpoint without doing the dance (or guessing right the first time) because IAudioClient::GetService(...) will fail.

  • Thaks for your reply. My current implementation does guess right the first time (as long as the rule is to stick with 128 byte alignment).

  • So I assume the purpose of this was just an example demo to show how to us exclusive mode "right" or "well"?

  • be careful !!!

    hnsPeriod = // hns =

           (REFERENCE_TIME)(

               10000.0 * // (hns / ms) *

               1000 * // (ms / s) *

               nFramesInBuffer / // frames /

               pWfx->nSamplesPerSec  // (frames / s)

               + 0.5 // rounding

           );

    doesn't work as expected.

    (10000.0 * 1000 / 44100 * 160) = 36281.179138321997

    (10000.0 * 1000 / 44100 * 160) + 0.5 = 36281.679138321997

    when assigning to hnsPeriod which is supposedly "int" or smth

    we get

    hnsPeriod = 36281

    then doing reverse calculations of nFramesInBuffer

    nFramesInBuffer=36281*44100/(1000*10000)=159

    Not 160 as expected. another one from MS :(

  • The going-back-to-frames code looks like this:

       // need to know how many frames that is

       UINT32 nFramesInBuffer = (UINT32)( // frames =

           1.0 * hnsPeriod * // hns *

           pWfx->nSamplesPerSec / // (frames / s) /

           1000 / // (ms / s) /

           10000 // (hns / s) /

           + 0.5 // rounding

       );

    Notice the 1.0 to force a conversion to double, and then the +0.5 at the end.

    36281*44100/1000/10000 is 159.9992...; adding 0.5 and then converting to int snaps this to the nearest frame, 160.

  • where does code snippet coming from? Is it really code that works in the OS or you think it should be like this?

  • And what is priority in your calculations duration or number of frames? Why is it so confusing?

  • Code snippets come from the attached play-exclusive.zip, play.cpp.  This code is not in the OS; this is an example WASAPI client.

  • On this forum post:

    social.msdn.microsoft.com/.../f4ec50d0-01cc-4217-8647-a2d7ec693c6d

    you mention that capture would look similar.  I tried to create a simple exclusive capture program, but the program hangs when I unplug the microphone.  Forum post:

    social.msdn.microsoft.com/.../83f5978d-e809-46ab-95bd-e41502901cdd

    Have you tried creating an exclusive capture example program?  Have you ever run into this problem before?  I saw that there's a problem calling Release from a different thread than the one that called GetService(), but that does not appear to be my problem.

    Thanks!  Great post, BTW.

    Bill

  • You're probably hanging on this line:

           // in a production app there would be a timeout here

           WaitForSingleObject(hNeedDataEvent, INFINITE);

    Add a timeout value of, let's say, five times the buffer duration in milliseconds.  If WaitForSingleObject returns anything but WAIT_OBJECT_0, log a failure and bubble it up.

  • what codes do I need to add in order to play files other than 16bit 44.1KHz format? Sorry, maybe off topic!

  • If the audio driver supports the wave format in the .wav file, this will play it.

    If the audio driver does not support the wave format in the .wav file, then you will need a format converter.  WASAPI does not provide a format converter, so you will need to use a higher-level API like Media Foundation to play the content.

Page 1 of 2 (19 items) 12