I am writing a WASAPI app. I have an external audio interface and the onboard Realtek audio card. I seem to have a problem running render devices in exclusive mode.
No matter what I do and what the user chooses, the app crashes on initialization. The culprit is IAudioClient->Initialize()
that fails with the following code.
0x80070016 - HRESULT_FROM_WIN32(ERROR_BAD_COMMAND) : The device does not recognize the command
Here is the code.
#include <iostream>
#include <Windows.h>
#include <mmdeviceapi.h>
#include <Functiondiscoverykeys_devpkey.h>
#include <vector>
#include <propvarutil.h>
#include <Audioclient.h>
#pragma comment(lib, "Propsys.lib")
using namespace std;
// A device entry that the user can select from a menu.
struct device {
WCHAR friendlyName[128];
IMMDevice* pDevice;
};
int main()
{
// Used for error codes.
HRESULT hr;
// Initialize COM.
hr = CoInitialize(0);
// The enumerator is used to list devices.
IMMDeviceEnumerator* pEnumerator;
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pEnumerator);
// We only care about render devices.
vector<device> renderDevices;
// We enumerate them.
IMMDeviceCollection* pRenderCollection;
hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pRenderCollection);
UINT cRenderDevices;
hr = pRenderCollection->GetCount(&cRenderDevices);
for (UINT i = 0; i < cRenderDevices; i++) {
// For each device, we create a device entry.
device dev;
// We store the pointer to the device in dev.pDevice.
hr = pRenderCollection->Item(i, &dev.pDevice);
// We store the friendly name in dev.friendlyName;
IPropertyStore* pProperties;
hr = dev.pDevice->OpenPropertyStore(STGM_READ, &pProperties);
PROPVARIANT friendlyName;
hr = pProperties->GetValue(PKEY_Device_FriendlyName, &friendlyName);
hr = PropVariantToString(friendlyName, dev.friendlyName, 128);
renderDevices.push_back(dev);
pProperties->Release();
}
pRenderCollection->Release();
// We don't need the enumerator anymore.
pEnumerator->Release();
// We let the user pick a device.
int renderIndex;
cout << "Select render device" << endl;
for (int i = 0; i < renderDevices.size(); i++) {
wcout << " " << i << ") " << renderDevices[i].friendlyName << endl;
}
cin >> renderIndex;
// And that's what we are going to use.
IMMDevice* pRenderDevice = renderDevices[renderIndex].pDevice;
// We activate the device to get an audio client.
IAudioClient* pAudioRenderClientTemp;
hr = pRenderDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioRenderClientTemp);
// We want to get a suitable format for each mode of operation, exclusive and shared.
WAVEFORMATEX* pExclusiveRenderFormat = NULL;
WAVEFORMATEX* pSharedRenderFormat = NULL;
// This is the target format that we are interested in.
WAVEFORMATEX format;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 2;
format.cbSize = 0;
format.wBitsPerSample = 16;
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
format.nSamplesPerSec = 44100;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
// This variable will store the closest match.
WAVEFORMATEX* pClosestMatch;
// We try to get a shared mode format.
pClosestMatch = NULL;
if (S_OK == pAudioRenderClientTemp->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &format, &pClosestMatch)) {
cout << "Exact format match for shared mode." << endl;
pSharedRenderFormat = &format;
}
else if (pClosestMatch) {
cout << "Close match for shared mode." << endl;
pSharedRenderFormat = pClosestMatch;
}
// We try to get an exclusive mode format.
pClosestMatch = NULL;
if (S_OK == pAudioRenderClientTemp->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &format, &pClosestMatch)) {
cout << "Exact format match for exclusive mode." << endl;
pExclusiveRenderFormat = &format;
}
else if (pClosestMatch) {
cout << "Close format match for exclusive mode." << endl;
pExclusiveRenderFormat = pClosestMatch;
}
// We give precedence to exclusive mode if available.
auto renderMode = pExclusiveRenderFormat ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
auto renderFormat = pExclusiveRenderFormat ? pExclusiveRenderFormat : pSharedRenderFormat;
// We try to initialize the device; no matter what the user chooses, this fails with
// 0x80070016 - HRESULT_FROM_WIN32(ERROR_BAD_COMMAND) : The device does not recognize the command.
hr = pAudioRenderClientTemp->Initialize(
renderMode,
0,
1000,
0,
renderFormat,
NULL);
return hr;
}
Here is the console output when picking the integrated soundcard.
Select render device
0) Speakers (Realtek(R) Audio)
1) Speakers (USB Audio CODEC )
0
Close match for shared mode.
Exact format match for exclusive mode.
And here is the console output when picking the external audio interface.
Select render device
0) Speakers (Realtek(R) Audio)
1) Speakers (USB Audio CODEC )
1
Exact format match for shared mode.
Exact format match for exclusive mode.
What am I doing wrong?
Edit #1
I tried this app and on my system it fails in the same way as mine. What's wrong with my system? Maybe Windows 11 does not support WASAPI in console apps? What's going on?
Edit #2
I tried to copy my code in a WinMain and the same thing happens; all S_OK, one S_FALSE when checking for format support, which still results into a valid closest format match and then the same error 0x80070016 - HRESULT_FROM_WIN32(ERROR_BAD_COMMAND)
when I call Initialize.
Edit #3 (I rebooted the system and it works)
Okay, so I noticed that even the Test Sounds button in Windows in Chrome and YouTube were not working, even though I was not getting any error. Then I remembered that on this PC I frequently have audio issues. So, I rebooted and of course now everything seems to be working. It may not be a StackOverflow question after all; maybe it's more for SuperUser. Why is audio so finicky?