[wdmaudiodev] WASAPI update ...

  • From: "Jerry Evans" <jerry@xxxxxxxxxxx>
  • To: <wdmaudiodev@xxxxxxxxxxxxx>
  • Date: Wed, 16 Sep 2015 14:20:03 +0100

Apologies for banging on about this one but a bit more data. Code example
updated to include buffer size renegotiation, reporting of HRESULT error
codes, plus some indication that the render thread is actually running.
Delta and Realtek are Win7, Cirrus is Win10, both Pro|Ultimate, both x64.






Mode\Soundcard

Delta AP PCI

Asus/Realtek HDA

Dell/Cirrus HDA


WASAPI exclusive def period

Glitches

works

works


WASAPI exclusive min period

Silence

works

error 0x888900*


WASAPI exclusive w/ GetCurrentPadding

Glitches

silence

Silence



*when initializing IAudioclient. What does this one signify?



Jerry.

/*

Minimal WASAPI exclusive mode glitch demonstration

This is an example and is *not* production code.

It picks the default audio render device and opens in
exclusive mode. Thread priority is set up and
the thread loops rendering a stereo sine tone at 440Hz

N.B. Hardware is expected to be configured for 44K1, 16 bit stereo

Win7+ VS2013 Pro.
*/

#include <windows.h>
#include <stdint.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <avrt.h>
#include <conio.h>
#pragma comment(lib, "avrt.lib")
#pragma comment(lib, "ole32.lib")

//-----------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <thread>

//-----------------------------------------------------------------------------
// Generate sine tones at 440HZ
class SineGen
{

//-----------------------------------------------------------------------------
inline int16_t toInt16(float arg)
{
return static_cast<int16_t>(arg * 32767.0f);
}

float m_frequency;
float m_amplitude;
float m_dc;
float m_phase;

int m_samplerate;
int m_channels;
int m_bitsPerSample;
//
float m_phaseInc;
float m_twoPI;

public:

//
static double pi() { return 3.14159265358979323846; }

SineGen(float frequency = 440, int sameplerate = 44100, int channels =
2, int bitsPerSample = 16)
: m_frequency(frequency), m_amplitude(0.5), m_dc(0),
m_phase(0.0),
m_samplerate(sameplerate), m_channels(channels),
m_bitsPerSample(bitsPerSample), m_twoPI(pi() * 2)
{
m_phaseInc = 2 * pi()* m_frequency / m_samplerate;
}

//
void assign(float frequency = 440, int sameplerate = 44100, int
channels = 2, int bitsPerSample = 16)
{
m_frequency = frequency;
m_amplitude = 0.5;
m_dc = 0;
m_phase = 0.0;
m_samplerate = (sameplerate);
m_channels = (channels);
m_bitsPerSample = (bitsPerSample);
m_twoPI = (pi() * 2);
m_phaseInc = 2 * pi()* m_frequency / m_samplerate;
}
// generate a sine wave on the fly ...
virtual uint32_t Render(uint8_t* pdata, uint32_t frames)
{
uint32_t ret = 0;
float value = 0;
if (m_bitsPerSample == 16)
{
int16_t* data = reinterpret_cast<int16_t*>(pdata);
for (int frame = 0; frame < frames; frame++)
{
// not something we'd do in the real world
value = m_amplitude * sinf(m_phase);
for (uint32_t channel = 0; channel <
m_channels; channel++)
{
*data++ = toInt16(value);
}
m_phase += m_phaseInc;
}
// ditto
m_phase = fmodf(m_phase, m_twoPI);
ret = frames;
}
return ret;
}

};

//-----------------------------------------------------------------------------
std::string toHex(void* p, size_t bytes)
{
static char hexChars[] = "0123456789ABCDEF";
std::string ret = "0x";
const char* ps = reinterpret_cast<const char*>(p);
if (p && bytes)
{
for (size_t s = bytes-1; s > 0; s--)
{
ret += hexChars[((ps[s] >> 4) & 0x0F)];
ret += hexChars[(ps[s] & 0x0F)];
}
}
else
{
ret += "00";
}
return ret;
}

//-----------------------------------------------------------------------------
std::string toHex(HRESULT hr)
{
return toHex(&hr, sizeof(hr));
}

std::string makeError(const char* p,HRESULT hr)
{
std::string err = p;
err += ": ";
err += toHex(hr);
return err;
}

//-----------------------------------------------------------------------------
// simplify things here. when set to false thread cleans up and exits ...
static bool run = true;

//-----------------------------------------------------------------------------
// complete audio task
void DoAudio(bool padTest,bool minTest)
{
IMMDevice *pDevice = nullptr;
IMMDeviceEnumerator *pDeviceEnumerator = nullptr;
IAudioClient *pAudioClient = nullptr;
IAudioRenderClient *pAudioRenderClient = nullptr;
HANDLE hEvent = nullptr;
HANDLE hTask = nullptr;
try
{
const int frequency = 880;
const int channels = 2;
const int samplerate = 44100;
const int bitspersample = 16;

// COM result
HRESULT hr = S_OK;

hr = CoInitialize(nullptr);
if (FAILED(hr)) throw std::runtime_error("CoInitialize error");

hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
nullptr,
CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pDeviceEnumerator);
if (FAILED(hr)) throw std::runtime_error("CoCreateInstance
error");

hr = pDeviceEnumerator->GetDefaultAudioEndpoint(
eRender,
eConsole,
&pDevice);
if (FAILED(hr)) throw
std::runtime_error("IMMDeviceEnumerator.GetDefaultAudioEndpoint error");
std::cout <<
"IMMDeviceEnumerator.GetDefaultAudioEndpoint()->OK" << std::endl;

IAudioClient *pAudioClient = nullptr;
hr = pDevice->Activate(
__uuidof(IAudioClient),
CLSCTX_ALL,
nullptr,
(void**)&pAudioClient);
if (FAILED(hr)) throw
std::runtime_error(makeError("IMMDevice.Activate error:",hr));
std::cout << "IMMDevice.Activate()->OK" << std::endl;

REFERENCE_TIME DefaultDevicePeriod = 0;
REFERENCE_TIME MinimumDevicePeriod = 0;
hr = pAudioClient->GetDevicePeriod(&DefaultDevicePeriod,
&MinimumDevicePeriod);
if (FAILED(hr)) throw
std::runtime_error("IAudioClient.GetDevicePeriod error");
std::cout << "default device period=" << DefaultDevicePeriod <<
"[nano seconds]" << std::endl;
std::cout << "minimum device period=" << MinimumDevicePeriod <<
"[nano seconds]" << std::endl;

WAVEFORMATEX wave_format = {};
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = channels;
wave_format.nSamplesPerSec = samplerate;
wave_format.nAvgBytesPerSec = samplerate * channels *
bitspersample / 8;
wave_format.nBlockAlign = channels * bitspersample / 8;
wave_format.wBitsPerSample = bitspersample;

UINT32 NumBufferFrames = 0;
REFERENCE_TIME rt = (minTest ? MinimumDevicePeriod :
DefaultDevicePeriod);
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE, // exclusive mode
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
rt,
rt,
&wave_format,
nullptr);
// If the requested buffer size is not aligned...
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
{
// Get the next aligned frame.
hr = pAudioClient->GetBufferSize(&NumBufferFrames);

//
std::cout << "Realigning. New buffersize is " <<
NumBufferFrames << "frames\n";

rt = (REFERENCE_TIME)
((10000.0 * 1000 / wave_format.nSamplesPerSec *
NumBufferFrames) + 0.5);

// Release the previous allocations.
pAudioClient->Release();

// Create a new audio client.
hr = pDevice->Activate(
__uuidof(IAudioClient),
CLSCTX_ALL,
NULL,
(void**)&pAudioClient);

// this is redundant I think but we'll keep here for
WAVEFORMATEX* pwfx = 0;
// Get the device format.
hr = pAudioClient->GetMixFormat(&pwfx);
if (FAILED(hr))
std::runtime_error(makeError("IAudioClient.GetMixFormat", hr));
// Open the stream and associate it with an audio
session.
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
rt,
rt,
&wave_format,
NULL);
CoTaskMemFree(pwfx);
if (FAILED(hr))
std::runtime_error(makeError("IAudioClient.Initialize reinitialize ", hr));
}
else if (FAILED(hr))
{
throw std::runtime_error(makeError("Initial
IAudioClient.Initialize failed ", hr));
}
std::cout << "IAudioClient.Initialize()->OK Period is " << rt
<< "ns" << std::endl;

// event
hEvent = CreateEvent(nullptr, false, false, nullptr);
if (FAILED(hr)) throw std::runtime_error("CreateEvent error");

hr = pAudioClient->SetEventHandle(hEvent);
if (FAILED(hr)) throw
std::runtime_error(makeError("SetEventHandle", hr));

hr = pAudioClient->GetBufferSize(&NumBufferFrames);
if (FAILED(hr)) throw
std::runtime_error(makeError("GetBufferSize", hr));
std::cout << "buffer frame size=" << NumBufferFrames <<
"[frames]" << std::endl;

hr = pAudioClient->GetService(
__uuidof(IAudioRenderClient),
(void**)&pAudioRenderClient);
if (FAILED(hr)) throw
std::runtime_error(makeError("GetService", hr));

// exclusive mode
DWORD taskIndex = 0;
hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"),
&taskIndex);
if (hTask == nullptr) throw
std::runtime_error("AvSetMmThreadCharacteristics error");
std::cout << "exclusive mode->OK" << std::endl;

// zero any garbage
BYTE *pData = nullptr;
hr = pAudioRenderClient->GetBuffer(NumBufferFrames, &pData);
if (FAILED(hr)) throw std::runtime_error(makeError("GetBuffer",
hr));

// zero padding
memset(&pData[0], 0, (NumBufferFrames)* channels *
(bitspersample / 8));

hr = pAudioRenderClient->ReleaseBuffer(NumBufferFrames, 0);
if (FAILED(hr)) throw
std::runtime_error(makeError("ReleaseBuffer", hr));

hr = pAudioClient->Start();
if (FAILED(hr)) throw
std::runtime_error(makeError("IAudioClient.Start", hr));

std::cout << "IAudioClient.Start()->OK" << std::endl;

// sine wave generator
SineGen sg(440, samplerate, channels, bitspersample);

//
UINT32 framesToProcess = 0;
UINT32 NumPaddingFrames = 0;
//
UINT32 timeout = 1000;
// run forever ...
DWORD count = 0;
while (run)
{
DWORD dw = WaitForSingleObject(hEvent, timeout);
if (dw == WAIT_TIMEOUT)
{
throw std::runtime_error("WaitForSingleObject
timed out!");
}
if (dw != WAIT_OBJECT_0)
{
throw
std::runtime_error(makeError("WaitForSingleObject error",dw));
}
count++;
if (count % 1024 == 0)
{
std::cout << "Looping " << count << std::endl;
}
// do this and it'll glitch on USB devices too.
if (padTest)
{
hr =
pAudioClient->GetCurrentPadding(&NumPaddingFrames);
if (FAILED(hr)) throw
std::runtime_error(makeError("IAudioClient.GetCurrentPadding", hr));
}

framesToProcess = NumBufferFrames - NumPaddingFrames;
if (framesToProcess > 0)
{
hr =
pAudioRenderClient->GetBuffer(NumBufferFrames, &pData);
if (FAILED(hr)) throw
std::runtime_error(makeError("GetBuffer", hr));

// do the sine thing
sg.Render(pData, framesToProcess);

hr =
pAudioRenderClient->ReleaseBuffer(framesToProcess, 0);
if (FAILED(hr)) throw
std::runtime_error(makeError("ReleaseBuffer", hr));
}
}

hr = pAudioClient->Stop();
if (FAILED(hr)) throw std::runtime_error(makeError("Stop", hr));
std::cout << "IAudioClient.Stop()->OK" << std::endl;

}
catch (std::exception& ex)
{
std::cout << "error:" << ex.what() << std::endl;
}

if (hEvent)
{
CloseHandle(hEvent);
}
if (hTask)
{
AvRevertMmThreadCharacteristics(hTask);
}
if (pDeviceEnumerator) pDeviceEnumerator->Release();
if (pDevice) pDevice->Release();
if (pAudioClient) pAudioClient->Release();
if (pAudioRenderClient) pAudioRenderClient->Release();
CoUninitialize();
}

//-----------------------------------------------------------------------------
int main(int argc,char* argv[])
{
// call GetPadding() before we GetBuffer() ?
bool padTest = false;
// use minimum period or the default?
bool minTest = false;
//
for (int i = 1; argv[i] != 0; i++)
{
switch (tolower(argv[i][0]))
{
//
case 'p':
padTest = true;
break;
case 'm':
minTest = true;
break;
}
}
// any command line arg will switch on GetPadding test
std::thread audioThread(DoAudio,padTest,minTest);

// wait for a key
_getch();

// halt thread ...
run = false;

// wait for shutdown
audioThread.join();

return 0;
}

Other related posts: