Last active
July 25, 2020 04:54
-
-
Save fukuroder/7843658 to your computer and use it in GitHub Desktop.
wave file player (WASAPI exclusive mode)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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