Skip to content

Instantly share code, notes, and snippets.

@fukuroder
Last active July 25, 2020 04:54
Show Gist options
  • Save fukuroder/7843658 to your computer and use it in GitHub Desktop.
Save fukuroder/7843658 to your computer and use it in GitHub Desktop.
wave file player (WASAPI exclusive mode)
/*
* wave_play_wasapi_exclusive.cpp
*
* Created by fukuroda (https://github.com/fukuroder)
*/
// windows API
#include <windows.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <avrt.h>
#pragma comment(lib, "avrt.lib")
// libsndfile -> http://www.mega-nerd.com/libsndfile/
#include "sndfile.h"
#pragma comment(lib, "libsndfile-1.lib")
// STL
#include <iostream>
int main()
{
SNDFILE* snd_file = nullptr;
IMMDevice* pDevice = nullptr;
IMMDeviceEnumerator* pDeviceEnumerator = nullptr;
IAudioClient* pAudioClient = nullptr;
IAudioRenderClient* pAudioRenderClient = nullptr;
HANDLE hEvent = nullptr;
HANDLE hTask = nullptr;
try
{
// open sound file (wav/ogg/flac...)
SF_INFO sf_info = {};
snd_file = sf_open("test.wav", SFM_READ, &sf_info);
if (snd_file == nullptr) throw std::runtime_error("sf_open error");
if (sf_info.frames == 0 ||
sf_info.channels != 2 ||
sf_info.samplerate != 44100) throw std::runtime_error("44.1kHz/stereo only");
// 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;
hr = pDevice->Activate(
__uuidof(IAudioClient),
CLSCTX_ALL,
nullptr,
(void**)&pAudioClient);
if (FAILED(hr)) throw std::runtime_error("IMMDevice.Activate error");
std::cout << "IMMDevice.Activate()->OK" << std::endl;
REFERENCE_TIME MinimumDevicePeriod = 0;
hr = pAudioClient->GetDevicePeriod(nullptr, &MinimumDevicePeriod);
if (FAILED(hr)) throw std::runtime_error("IAudioClient.GetDevicePeriod error");
std::cout << "minimum device period=" << MinimumDevicePeriod * 100 << "[nano seconds]" << std::endl;
WAVEFORMATEX wave_format = {};
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = 2;
wave_format.nSamplesPerSec = 44100;
wave_format.nAvgBytesPerSec = 44100 * 2 * 16 / 8;
wave_format.nBlockAlign = 2 * 16 / 8;
wave_format.wBitsPerSample = 16;
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE, // exclusive mode
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
MinimumDevicePeriod,
MinimumDevicePeriod,
&wave_format,
nullptr);
if (FAILED(hr)) throw std::runtime_error("IAudioClient.Initialize error");
std::cout << "IAudioClient.Initialize()->OK" << 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("IAudioClient.SetEventHandle error");
UINT32 NumBufferFrames = 0;
hr = pAudioClient->GetBufferSize(&NumBufferFrames);
if (FAILED(hr)) throw std::runtime_error("IAudioClient.GetBufferSize error");
std::cout << "buffer frame size=" << NumBufferFrames << "[frames]" << std::endl;
hr = pAudioClient->GetService(
__uuidof(IAudioRenderClient),
(void**)&pAudioRenderClient);
if (FAILED(hr)) throw std::runtime_error("IAudioClient.GetService error");
// 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;
bool playing = true;
BYTE* pData = nullptr;
hr = pAudioRenderClient->GetBuffer(NumBufferFrames, &pData);
if (FAILED(hr)) throw std::runtime_error("IAudioRenderClient.GetBuffer error");
sf_count_t read_count = sf_readf_short(snd_file, (short*)pData, NumBufferFrames);
if (read_count < NumBufferFrames)
{
playing = false;
// zero padding
memset(&pData[read_count * 2 * 2], 0, (NumBufferFrames - (UINT32)read_count) * 2 * 2);
}
hr = pAudioRenderClient->ReleaseBuffer(NumBufferFrames, 0);
if (FAILED(hr)) throw std::runtime_error("IAudioRenderClient.ReleaseBuffer error");
hr = pAudioClient->Start();
if (FAILED(hr)) throw std::runtime_error("IAudioClient.Start error");
std::cout << "IAudioClient.Start()->OK" << std::endl;
while (playing)
{
WaitForSingleObject(hEvent, INFINITE);
hr = pAudioRenderClient->GetBuffer(NumBufferFrames, &pData);
if (FAILED(hr)) throw std::runtime_error("IAudioRenderClient.GetBuffer error");
read_count = sf_readf_short(snd_file, (short*)pData, NumBufferFrames);
if (read_count < NumBufferFrames)
{
playing = false;
// zero padding
memset(&pData[read_count * 2 * 2], 0, (NumBufferFrames - (UINT32)read_count) * 2 * 2);
}
hr = pAudioRenderClient->ReleaseBuffer(NumBufferFrames, 0);
if (FAILED(hr)) throw std::runtime_error("IAudioRenderClient.ReleaseBuffer error");
}
do
{
// wait for buffer to be empty
WaitForSingleObject(hEvent, INFINITE);
UINT32 NumPaddingFrames = 0;
hr = pAudioClient->GetCurrentPadding(&NumPaddingFrames);
if (FAILED(hr)) throw std::runtime_error("IAudioClient.GetCurrentPadding error");
if (NumPaddingFrames == 0)
{
std::cout << "current buffer padding=0[frames]" << std::endl;
break;
}
} while (true);
hr = pAudioClient->Stop();
if (FAILED(hr)) throw std::runtime_error("IAudioClient.Stop error");
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();
sf_close(snd_file);
return 0;
}
@g40
Copy link

g40 commented Sep 16, 2015

One suggestion: The call to AudioClient->Initialize() may fail with an AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED error, especially if you choose to use the minimum buffering periods. In this case you need to renegotiate as suggested by the docs here:

https://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx

HTH

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment