Last active
March 9, 2024 02:14
-
-
Save fukuroder/e280cb9a2e2f06c83d12804fd2480f31 to your computer and use it in GitHub Desktop.
WASAPIでcaptureしてそのままrenderするサンプル
This file contains hidden or 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
/* | |
* wasapi_capture_and_render.cpp | |
* | |
* Created by fukuroda (https://github.com/fukuroder) | |
*/ | |
// STL | |
#include <iostream> | |
#include <vector> | |
// windows API | |
#include <windows.h> | |
#include <audioclient.h> | |
#include <mmdeviceapi.h> | |
#include <avrt.h> | |
#pragma comment(lib, "avrt.lib") | |
static bool Playing = true; | |
std::vector<SHORT> Process(const std::vector<SHORT>& Input) | |
{ | |
// do something | |
return Input; | |
} | |
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) | |
{ | |
if (dwCtrlType == CTRL_C_EVENT) { | |
Playing = false; | |
return TRUE; | |
} | |
else { | |
return FALSE; | |
} | |
} | |
int main() | |
{ | |
// | |
IMMDeviceEnumerator* pDeviceEnumerator = nullptr; | |
// | |
IMMDevice* pRenderDevice = nullptr; | |
// | |
IMMDevice* pCaptureDevice = nullptr; | |
// | |
IAudioClient* pRenderClient = nullptr; | |
// | |
IAudioClient* pCaptureClient = nullptr; | |
// | |
IAudioRenderClient* pRenderService = nullptr; | |
// | |
IAudioCaptureClient* pCaptureService = nullptr; | |
// | |
HANDLE hRenderEvent = nullptr; | |
// | |
HANDLE hCaptureEvent = nullptr; | |
// | |
HANDLE hTask = nullptr; | |
SetConsoleCtrlHandler(HandlerRoutine, TRUE); | |
try | |
{ | |
// 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, | |
&pRenderDevice); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pDeviceEnumerator->GetDefaultAudioEndpoint() error"); | |
} | |
std::cout << "pDeviceEnumerator->GetDefaultAudioEndpoint() OK" << std::endl; | |
hr = pDeviceEnumerator->GetDefaultAudioEndpoint( | |
eCapture, | |
eConsole, | |
&pCaptureDevice); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pDeviceEnumerator->GetDefaultAudioEndpoint() error"); | |
} | |
std::cout << "pDeviceEnumerator->GetDefaultAudioEndpoint() OK" << std::endl; | |
hr = pRenderDevice->Activate( | |
__uuidof(IAudioClient), | |
CLSCTX_ALL, | |
nullptr, | |
(void**)&pRenderClient); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderDevice->Activate() error"); | |
} | |
std::cout << "pRenderDevice->Activate() OK" << std::endl; | |
hr = pCaptureDevice->Activate( | |
__uuidof(IAudioClient), | |
CLSCTX_ALL, | |
nullptr, | |
(void**)&pCaptureClient); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureDevice->Activate() error"); | |
} | |
std::cout << "pCaptureDevice->Activate() OK" << std::endl; | |
REFERENCE_TIME DefaultDevicePeriod = 0; | |
REFERENCE_TIME MinimumDevicePeriod = 0; | |
hr = pRenderClient->GetDevicePeriod(&DefaultDevicePeriod, &MinimumDevicePeriod); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->GetDevicePeriod() error"); | |
} | |
std::cout << "pRenderClient->GetDevicePeriod() OK" << std::endl; | |
std::cout << "default render device period=" << DefaultDevicePeriod * 100 << "[nano seconds]" << std::endl; | |
std::cout << "minimum render device period=" << MinimumDevicePeriod * 100 << "[nano seconds]" << std::endl; | |
REFERENCE_TIME DefaultDevicePeriod_ = 0; | |
REFERENCE_TIME MinimumDevicePeriod_ = 0; | |
hr = pCaptureClient->GetDevicePeriod(&DefaultDevicePeriod_, &MinimumDevicePeriod_); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->GetDevicePeriod() error"); | |
} | |
std::cout << "pCaptureClient->GetDevicePeriod() OK" << std::endl; | |
std::cout << "default capture device period=" << DefaultDevicePeriod_ * 100 << "[nano seconds]" << std::endl; | |
std::cout << "minimum capture device period=" << MinimumDevicePeriod_ * 100 << "[nano seconds]" << std::endl; | |
WAVEFORMATEX WaveFormat = {}; | |
WaveFormat.wFormatTag = WAVE_FORMAT_PCM; | |
WaveFormat.nChannels = 2; | |
WaveFormat.nSamplesPerSec = 44100; | |
WaveFormat.wBitsPerSample = 16; | |
WaveFormat.nAvgBytesPerSec = WaveFormat.nSamplesPerSec * WaveFormat.nChannels * WaveFormat.wBitsPerSample / 8; | |
WaveFormat.nBlockAlign = WaveFormat.nChannels * WaveFormat.wBitsPerSample / 8; | |
hr = pRenderClient->Initialize( | |
AUDCLNT_SHAREMODE_EXCLUSIVE, // exclusive mode | |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, | |
DefaultDevicePeriod, | |
DefaultDevicePeriod, | |
&WaveFormat, | |
nullptr); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->Initialize() error"); | |
} | |
std::cout << "pRenderClient->Initialize() OK" << std::endl; | |
hr = pCaptureClient->Initialize( | |
AUDCLNT_SHAREMODE_EXCLUSIVE, // exclusive mode | |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, | |
DefaultDevicePeriod, | |
DefaultDevicePeriod, | |
&WaveFormat, | |
nullptr); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->Initialize() error"); | |
} | |
std::cout << "pCaptureClient->Initialize() OK" << std::endl; | |
// event | |
hRenderEvent = CreateEvent(nullptr, false, false, nullptr); | |
if (hRenderEvent == nullptr) { | |
throw std::runtime_error("CreateEvent() error"); | |
} | |
hCaptureEvent = CreateEvent(nullptr, false, false, nullptr); | |
if (hCaptureEvent == nullptr) { | |
throw std::runtime_error("CreateEvent() error"); | |
} | |
hr = pRenderClient->SetEventHandle(hRenderEvent); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->SetEventHandle() error"); | |
} | |
std::cout << "pRenderClient->SetEventHandle() OK" << std::endl; | |
hr = pCaptureClient->SetEventHandle(hCaptureEvent); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->SetEventHandle() error"); | |
} | |
std::cout << "pCaptureClient->SetEventHandle() OK" << std::endl; | |
UINT32 NumRenderBufferFrames = 0; | |
hr = pRenderClient->GetBufferSize(&NumRenderBufferFrames); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->GetBufferSize() error"); | |
} | |
std::cout << "pRenderClient->GetBufferSize() OK" << std::endl; | |
std::cout << "render buffer frame size=" << NumRenderBufferFrames << "[frames]" << std::endl; | |
UINT32 NumCaptureBufferFrames = 0; | |
hr = pCaptureClient->GetBufferSize(&NumCaptureBufferFrames); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->GetBufferSize() error"); | |
} | |
std::cout << "pCaptureClient->GetBufferSize() OK" << std::endl; | |
std::cout << "capture buffer frame size=" << NumCaptureBufferFrames << "[frames]" << std::endl; | |
hr = pRenderClient->GetService( | |
__uuidof(IAudioRenderClient), | |
(void**)&pRenderService); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->GetService() error"); | |
} | |
std::cout << "pRenderClient->GetService() OK" << std::endl; | |
hr = pCaptureClient->GetService( | |
__uuidof(IAudioCaptureClient), | |
(void**)&pCaptureService); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->GetService() error"); | |
} | |
std::cout << "pCaptureClient->GetService() OK" << std::endl; | |
// 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; | |
hr = pRenderClient->Start(); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->Start() error"); | |
} | |
std::cout << "pRenderClient->Start() OK" << std::endl; | |
hr = pCaptureClient->Start(); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->Start() error"); | |
} | |
std::cout << "pCaptureClient->Start() OK" << std::endl; | |
while (Playing) | |
{ | |
WaitForSingleObject(hCaptureEvent, INFINITE); | |
SHORT* pCaptureData = nullptr; | |
UINT32 NumFramesToRead = 0; | |
DWORD flags = 0; | |
hr = pCaptureService->GetBuffer((BYTE**)&pCaptureData, &NumFramesToRead, &flags, nullptr, nullptr); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureService->GetBuffer() error"); | |
} | |
std::vector<SHORT> Input(pCaptureData, &pCaptureData[NumFramesToRead * 2]); | |
hr = pCaptureService->ReleaseBuffer(NumFramesToRead); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureService->ReleaseBuffer() error"); | |
} | |
///////////////////////////////////////////////// | |
std::vector<SHORT> Output = Process(Input); | |
///////////////////////////////////////////////// | |
SHORT* pRenderData = nullptr; | |
hr = pRenderService->GetBuffer(NumFramesToRead, (BYTE**)&pRenderData); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderService->GetBuffer() error"); | |
} | |
memcpy(pRenderData, Output.data(), Output.size() * sizeof(SHORT)); | |
hr = pRenderService->ReleaseBuffer(NumFramesToRead, 0); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderService->ReleaseBuffer() error"); | |
} | |
WaitForSingleObject(hRenderEvent, INFINITE); | |
} | |
do | |
{ | |
// wait for buffer to be empty | |
UINT32 NumPaddingFrames = 0; | |
hr = pRenderClient->GetCurrentPadding(&NumPaddingFrames); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->GetCurrentPadding() error"); | |
} | |
std::cout << "pRenderClient->GetCurrentPadding() OK" << std::endl; | |
if (NumPaddingFrames == 0) | |
{ | |
std::cout << "current buffer padding=0[frames]" << std::endl; | |
break; | |
} | |
WaitForSingleObject(hRenderEvent, INFINITE); | |
} while (true); | |
hr = pRenderClient->Stop(); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pRenderClient->Stop() error"); | |
} | |
std::cout << "pRenderClient->Stop() OK" << std::endl; | |
hr = pCaptureClient->Stop(); | |
if (FAILED(hr)) { | |
throw std::runtime_error("pCaptureClient->Stop() error"); | |
} | |
std::cout << "pCaptureClient->Stop() OK" << std::endl; | |
} | |
catch (std::exception & ex) | |
{ | |
std::cout << "error:" << ex.what() << std::endl; | |
} | |
if (pDeviceEnumerator) { | |
pDeviceEnumerator->Release(); | |
pDeviceEnumerator = nullptr; | |
} | |
if (pRenderDevice) { | |
pRenderDevice->Release(); | |
pRenderDevice = nullptr; | |
} | |
if (pCaptureDevice) { | |
pCaptureDevice->Release(); | |
pCaptureDevice = nullptr; | |
} | |
if (pRenderClient) { | |
pRenderClient->Release(); | |
pRenderClient = nullptr; | |
} | |
if (pCaptureClient) { | |
pCaptureClient->Release(); | |
pCaptureClient = nullptr; | |
} | |
if (pRenderService) { | |
pRenderService->Release(); | |
pRenderService = nullptr; | |
} | |
if (pCaptureService) { | |
pCaptureService->Release(); | |
pCaptureService = nullptr; | |
} | |
if (hRenderEvent) { | |
CloseHandle(hRenderEvent); | |
hRenderEvent = nullptr; | |
} | |
if (hCaptureEvent) { | |
CloseHandle(hCaptureEvent); | |
hCaptureEvent = nullptr; | |
} | |
if (hTask) { | |
AvRevertMmThreadCharacteristics(hTask); | |
hTask = nullptr; | |
} | |
CoUninitialize(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,for exclusive mode this code isn't working.
error:pRenderClient->Initialize() error