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.
Browse source
Download play-exclusive.exe
>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%08xn", 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%08xn", 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%08xn", hr);
pAudioClient->Release();
return hr;
}
} else if (FAILED(hr)) {
printf("IAudioClient::Initialize failed: hr = 0x%08xn", 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%08xn", 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.
EDIT September 28 2015: moved source to https://github.com/mvaneerde/blog/tree/master/play-exclusive