I am a Software Development Engineer in Test working for the Windows Sound team. You can contact me via email: mateer at microsoft dot com
Friend key: 28904932216450_59cd9d55374be03d8167d37c8ff4196b
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-devicesplay-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.
>play-exclusive.exe -?play-exclusive.exe -?play-exclusive.exe --list-devicesplay-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-devicesActive 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.wavOpening .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 unalignedhr = 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 withhr = pAudioClient->GetBufferSize(&nFramesInBuffer);if (FAILED(hr)) { printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr;} // calculate the new periodhnsPeriod = // hns = (REFERENCE_TIME)( 10000.0 * // (hns / ms) * 1000 * // (ms / s) * nFramesInBuffer / // frames / pWfx->nSamplesPerSec // (frames / s) + 0.5 // rounding );
// call IAudioClient::Initialize the first time// this may very well fail// if the device period is unalignedhr = 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 withhr = pAudioClient->GetBufferSize(&nFramesInBuffer);if (FAILED(hr)) { printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr;}
// calculate the new periodhnsPeriod = // 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.
I was asked one day why this full-scale sine wave was being measured by our signal analysis tools as -3 dB FS, even though it hits the maximum and the minimum sample values:
The answer is "because it's a sine wave, not a square wave." The intensity of a signal can be calculated from the following formula:
The inner integral does not depend on t - it's just the average sample value - so it's usually precalculated:
The importance of taking dc into account during analysis can be appreciated if you try to calculate the intensity of a signal with a high dc.
Exercise: calculate the intensity of x(t) ≡ -0.5 using the formulas above; calculate the "naïve intensity" by using the last formula above and omitting dc. Note the difference.
Now that we have the necessary formulas, let's analyze our full-scale sine wave signal. Plugging in a full-scale sine wave and analyzing over a full period we get:
As expected, the average value of the signal over a single period is 0.
Evaluating the intensity requires finding the antiderivative of (sin(t))2. This can be ascertained most easily by plotting a few values and realizing that (sin(t))2 = (1 - cos(2t))/2:
This is a dimensionless number that ranges from 0 (signal is a flat line) to 1 (... we'll get to that later.)
We can convert this into a dB FS measurement using the formula IdB FS = 20 log10 IRMS:
Et voilà - that's where the -3 dB comes from.
In contrast to a sine wave, a full-scale square wave centered at 0 has an intensity of 0 dB FS:
To finish up, a couple of advanced exercises:
Advanced Exercise: prove that, provided t1 and t2 are sufficiently far apart, the intensity of a general sine wave x(t) = a sin(ωt + φ) + c depends only on a and not on ω, φ, or c.Advanced Exercise: completely characterize the set of digital signals that achieve 0 dB FS intensity. If you have, say, 1000 samples of mono 16-bit integer audio to play with, how many distinct signals achieve 0 dB FS intensity?
Raymond Chen explains some common terms for blood relatives of varying distance across cultures in his blog post "What kind of uncle am I?"
He links to a diagram on genealogy.com that I felt was lacking something... so here's my version, with consanguinary colors.
Red means "marriage is almost certainly legally prohibited."
Yellow means "marriage may be legally prohibited - check your region's laws."
Green means "marriage is amost certainly legal."
In his post "A chess problem begging for a solution", Michael Kaplan quotes Barbara Hambly's Star Trek novel Ishmael. In the quoted scene, Spock (AKA Ishmael) plays a couple of chess games against a stranger - rather unusual chess games. The problem alluded to is to determine the moves of the games given certain information.
Let's take the second game first. There are two effective possibilities that meet the "Reverse Fool's mate" and "three moves" criteria:
Reverse Fool's mate, even material: $200 in 2½ moves
... and...
Reverse Fool's mate plus a pawn: $220 in 2½ moves
The task for the first game, then, is to win either $400 or $380 in seven moves. Assuming the game ends in mate (this is reasonable) gives us a difference of either $200 or $180. The first move can not be a capture, so we really only have six moves to capture $200 worth of material - going after the queen is obvious, but the rooks are quite well tucked away, and it is hard to go after them and simultaneously set up the ending mate.
I believe that the solution that Barbara Hambly had in mind is the following variation of the "other" mate that every chess student learns (Scholar's Mate). This particular setup is gated by White's need to move his Bishop out of the way, and this nicely satisfies the common convention that players alternate colors in successive games. Naturally Spock, being a gentleman, would let the stranger take White first.
Mate, a queen, and four pawns: $380 in 7 moves
However, from a "chess problem" point of view, there's a cook. It is, in fact, possible to get mate plus a queen plus four pawns in a mere five and a half moves, rather than the seven full moves above:
Mate, a queen, and four pawns: $380 in 5½ moves
Even worse, there is a way to get $400 in a mere four and a half moves.
Mate, a queen, a bishop, and a knight: $400 in 4½ moves
The problem, as it stands, is therefore underdetermined... Spock and the stranger had a full two and a half moves to play around with in the first game, either to exchange material or perhaps to allow the stranger to pick up a free pawn (and return it in the second game.)
There is a general pattern in professional people. We start out idealistic and adventurous, and as disasters inevitably accumulate we become more circumspect and pessimistic.
(Pre-emptive snarky response: "you say that like it's a bad thing.")
While pessimism is invaluable to a tester, it should be tempered with a just sense of hubris. Larry Wall's saw about the three virtues of any great programmer has a historical antecedent from one of the canonical American authors:
We should be careful to get out of an experience only the wisdom that is in it -- and stop there; lest we be like the cat that sits down on a hot stove-lid. She will never sit down on a hot stove-lid again -- and that is well; but also she will never sit down on a cold one any more. -- Mark Twain, Following the Equator
Keep your childlike optimism.