-
-
Save Liastre/ff201f37bc62f6dc0b7f5541923565ab to your computer and use it in GitHub Desktop.
WASAPI capture shared event based
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
#pragma once | |
#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. | |
# define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. | |
#endif | |
#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 1 | |
#include <new> | |
#include <stdexcept> | |
#include <windows.h> | |
#include <strsafe.h> | |
#include <objbase.h> | |
#pragma warning(push) | |
#pragma warning(disable : 4201) | |
#include <mmdeviceapi.h> | |
#include <audiopolicy.h> | |
#pragma warning(pop) | |
template <class T> void SafeRelease(T **ppT) | |
{ | |
if (*ppT) | |
{ | |
(*ppT)->Release(); | |
*ppT = NULL; | |
} | |
} |
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
#include "DeviceManager.h" | |
#include <functiondiscoverykeys.h> | |
LPWSTR GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex) | |
{ | |
IMMDevice *device; | |
LPWSTR deviceId; | |
HRESULT hr; | |
hr = DeviceCollection->Item(DeviceIndex, &device); | |
if (hr != S_OK) throw std::runtime_error(""); | |
hr = device->GetId(&deviceId); | |
if (hr != S_OK) throw std::runtime_error(""); | |
IPropertyStore *propertyStore; | |
hr = device->OpenPropertyStore(STGM_READ, &propertyStore); | |
SafeRelease(&device); | |
if (hr != S_OK) throw std::runtime_error(""); | |
PROPVARIANT friendlyName; | |
PropVariantInit(&friendlyName); | |
hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName); | |
if (hr != S_OK) throw std::runtime_error(""); | |
SafeRelease(&propertyStore); | |
wchar_t deviceName[128]; | |
hr = StringCbPrintf(deviceName, sizeof(deviceName), L"%s (%s)", friendlyName.vt != VT_LPWSTR ? L"Unknown" : friendlyName.pwszVal, deviceId); | |
if (hr != S_OK) throw std::runtime_error(""); | |
PropVariantClear(&friendlyName); | |
CoTaskMemFree(deviceId); | |
wchar_t *returnValue = _wcsdup(deviceName); | |
if (returnValue == NULL) | |
{ | |
printf("Unable to allocate buffer for return\n"); | |
return NULL; | |
} | |
return returnValue; | |
} | |
// Based on the input switches, pick the specified device to use. | |
bool PickDevice(IMMDevice **DeviceToUse, ERole *DefaultDeviceRole) | |
{ | |
HRESULT hr; | |
bool retValue = true; | |
bool UseConsoleDevice; | |
bool UseCommunicationsDevice; | |
bool UseMultimediaDevice; | |
IMMDeviceEnumerator *deviceEnumerator = NULL; | |
IMMDeviceCollection *deviceCollection = NULL; | |
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)); | |
if (hr != S_OK) throw std::runtime_error(""); | |
IMMDevice *device = NULL; | |
// The user didn't specify an output device, prompt the user for a device and use that. | |
hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection); | |
if (hr != S_OK) throw std::runtime_error(""); | |
printf("Select an output device:\n"); | |
printf(" 0: Default Console Device\n"); | |
printf(" 1: Default Communications Device\n"); | |
printf(" 2: Default Multimedia Device\n"); | |
UINT deviceCount; | |
hr = deviceCollection->GetCount(&deviceCount); | |
if (hr != S_OK) throw std::runtime_error(""); | |
for (UINT i = 0; i < deviceCount; i += 1) | |
{ | |
LPWSTR deviceName; | |
deviceName = GetDeviceName(deviceCollection, i); | |
if (deviceName == NULL) throw std::runtime_error(""); | |
printf(" %d: %S\n", i + 3, deviceName); | |
free(deviceName); | |
} | |
long deviceIndex; | |
scanf("%d", &deviceIndex); | |
switch (deviceIndex) | |
{ | |
case 0: | |
UseConsoleDevice = 1; | |
break; | |
case 1: | |
UseCommunicationsDevice = 1; | |
break; | |
case 2: | |
UseMultimediaDevice = 1; | |
break; | |
default: | |
hr = deviceCollection->Item(deviceIndex - 3, &device); | |
if (hr != S_OK) throw std::runtime_error(""); | |
break; | |
} | |
if (device == NULL) throw std::runtime_error(""); | |
*DeviceToUse = device; | |
retValue = true; | |
SafeRelease(&deviceCollection); | |
SafeRelease(&deviceEnumerator); | |
return retValue; | |
} |
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
#pragma once | |
#include "config.h" | |
LPWSTR GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex); | |
bool PickDevice(IMMDevice **DeviceToUse, ERole *DefaultDeviceRole); |
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
#include "config.h" | |
#include <thread> | |
#include "WASAPICapture.h" | |
// A simple WASAPI Capture client. | |
CWASAPICapture::CWASAPICapture(IMMDevice *Endpoint, ERole EndpointRole) : | |
_RefCount(1), | |
_Endpoint(Endpoint), | |
_AudioClient(NULL), | |
_CaptureClient(NULL), | |
_CaptureThread(NULL), | |
_MixFormat(NULL), | |
_AudioSamplesReadyEvent(NULL), | |
_CurrentCaptureIndex(0), | |
_EndpointRole(EndpointRole), | |
_AudioSessionControl(NULL), | |
_DeviceEnumerator(NULL) | |
{ | |
_Endpoint->AddRef(); // Since we're holding a copy of the endpoint, take a reference to it. It'll be released in Shutdown(); | |
} | |
// Empty destructor - everything should be released in the Shutdown() call. | |
CWASAPICapture::~CWASAPICapture(void) | |
{ | |
} | |
// Initialize WASAPI in event driven mode, associate the audio client with our samples ready event handle, retrieve | |
// a capture client for the transport, create the capture thread and start the audio engine. | |
bool CWASAPICapture::InitializeAudioEngine() | |
{ | |
HRESULT hr = _AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, _EngineLatencyInMS*10000, 0, _MixFormat, NULL); | |
if (hr != S_OK) throw std::runtime_error("Unable to initialize audio client. \n"); | |
hr = _AudioClient->GetBufferSize(&_BufferSize); | |
if (hr != S_OK) throw std::runtime_error("Unable to get audio client buffer. \n"); | |
hr = _AudioClient->SetEventHandle(_AudioSamplesReadyEvent); | |
if (hr != S_OK) throw std::runtime_error("Unable to set ready event. \n"); | |
hr = _AudioClient->GetService(IID_PPV_ARGS(&_CaptureClient)); | |
if (hr != S_OK) throw std::runtime_error("Unable to get new capture client. \n"); | |
return true; | |
} | |
// Retrieve the format we'll use to capture samples. | |
// We use the Mix format since we're capturing in shared mode. | |
bool CWASAPICapture::LoadFormat() | |
{ | |
HRESULT hr = _AudioClient->GetMixFormat(&_MixFormat); | |
if (hr != S_OK) throw std::runtime_error("Unable to get mix format on audio client.\n"); | |
_FrameSize = (_MixFormat->wBitsPerSample / 8) * _MixFormat->nChannels; | |
return true; | |
} | |
// Initialize the capturer. | |
bool CWASAPICapture::Initialize(UINT32 EngineLatency) | |
{ | |
_AudioSamplesReadyEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); | |
if (_AudioSamplesReadyEvent == NULL) | |
{ | |
printf("Unable to create samples ready event: %d.\n", GetLastError()); | |
return false; | |
} | |
// Now activate an IAudioClient object on our preferred endpoint and retrieve the mix format for that endpoint. | |
HRESULT hr = _Endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_AudioClient)); | |
if (hr != S_OK) throw std::runtime_error("Unable to activate audio client.\n"); | |
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_DeviceEnumerator)); | |
if (hr != S_OK) throw std::runtime_error("Unable to instantiate device enumerator.\n"); | |
// Load the MixFormat. This may differ depending on the shared mode used | |
if (!LoadFormat()) throw std::runtime_error("Failed to load the mix format.\n"); | |
// Remember our configured latency in case we'll need it for a stream switch later. | |
_EngineLatencyInMS = EngineLatency; | |
if (!InitializeAudioEngine()) throw std::runtime_error(""); | |
return true; | |
} | |
// Shut down the capture code and free all the resources. | |
void CWASAPICapture::Shutdown() | |
{ | |
if (_CaptureThread) | |
{ | |
WaitForSingleObject(_CaptureThread, INFINITE); | |
CloseHandle(_CaptureThread); | |
_CaptureThread = NULL; | |
} | |
if (_AudioSamplesReadyEvent) | |
{ | |
CloseHandle(_AudioSamplesReadyEvent); | |
_AudioSamplesReadyEvent = NULL; | |
} | |
SafeRelease(&_Endpoint); | |
SafeRelease(&_AudioClient); | |
SafeRelease(&_CaptureClient); | |
if (_MixFormat) | |
{ | |
CoTaskMemFree(_MixFormat); | |
_MixFormat = NULL; | |
} | |
} | |
// Start capturing... | |
bool CWASAPICapture::Start(BYTE *CaptureBuffer, size_t CaptureBufferSize) | |
{ | |
HRESULT hr; | |
_CaptureBuffer = CaptureBuffer; | |
_CaptureBufferSize = CaptureBufferSize; | |
// Now create the thread which is going to drive the capture. | |
_CaptureThread = CreateThread(NULL, 0, WASAPICaptureThread, this, 0, NULL); | |
if (_CaptureThread == NULL) throw std::runtime_error("Unable to create transport thread.\n"); | |
// We're ready to go, start capturing! | |
hr = _AudioClient->Start(); | |
if (hr != S_OK) throw std::runtime_error("Unable to start capture client.\n"); | |
return true; | |
} | |
// Stop the capturer. | |
void CWASAPICapture::Stop() | |
{ | |
HRESULT hr; | |
// Tell the capture thread to shut down, wait for the thread to complete then clean up all the stuff we | |
// allocated in Start(). | |
hr = _AudioClient->Stop(); | |
if (hr != S_OK) throw std::runtime_error("Unable to stop audio client.\n"); | |
if (_CaptureThread) | |
{ | |
WaitForSingleObject(_CaptureThread, INFINITE); | |
CloseHandle(_CaptureThread); | |
_CaptureThread = NULL; | |
} | |
} | |
// Capture thread - processes samples from the audio engine | |
DWORD CWASAPICapture::WASAPICaptureThread(LPVOID Context) | |
{ | |
CWASAPICapture *capturer = static_cast<CWASAPICapture *>(Context); | |
return capturer->DoCaptureThread(); | |
} | |
DWORD CWASAPICapture::DoCaptureThread() | |
{ | |
BYTE *pData; | |
UINT32 framesAvailable = 1; | |
DWORD flags, state = 0; | |
bool stillPlaying = true; | |
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); | |
if (hr != S_OK) throw std::runtime_error("Unable to initialize COM in render thread. \n"); | |
// Each loop fills about half of the shared buffer. | |
//Sleep(_EngineLatencyInMS/2); | |
state = WaitForSingleObject(_AudioSamplesReadyEvent, INFINITE); | |
if (state != 0) throw std::runtime_error(""); | |
while (framesAvailable != 0) { | |
// get the available data in the shared buffer. | |
hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL); | |
if (hr != S_OK) throw std::runtime_error(""); | |
UINT32 framesToCopy = min(framesAvailable, static_cast<UINT32>((_CaptureBufferSize - _CurrentCaptureIndex) / _FrameSize)); | |
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) | |
{ | |
ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], framesToCopy*_FrameSize); | |
} else { | |
CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, framesToCopy*_FrameSize); | |
} | |
_CurrentCaptureIndex += framesToCopy*_FrameSize; | |
// release data | |
hr = _CaptureClient->ReleaseBuffer(framesAvailable); | |
if (hr != S_OK) throw std::runtime_error(""); | |
// start next capture | |
state = WaitForSingleObject(_AudioSamplesReadyEvent, INFINITE); | |
if (state != 0) throw std::runtime_error(""); | |
hr = _CaptureClient->GetNextPacketSize(&framesAvailable); | |
if (hr != S_OK) throw std::runtime_error(""); | |
} | |
// TODO: my break point, check _CurrentCaptureIndex | |
if (_CurrentCaptureIndex != _CaptureBufferSize) throw std::runtime_error("Number of read frames not equal to requested"); | |
CoUninitialize(); | |
return 0; | |
} | |
ULONG CWASAPICapture::AddRef() | |
{ | |
return InterlockedIncrement(&_RefCount); | |
} | |
ULONG CWASAPICapture::Release() | |
{ | |
ULONG returnValue = InterlockedDecrement(&_RefCount); | |
if (returnValue == 0) | |
{ | |
delete this; | |
} | |
return returnValue; | |
} |
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
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF | |
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO | |
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A | |
// PARTICULAR PURPOSE. | |
// | |
// Copyright (c) Microsoft Corporation. All rights reserved | |
// | |
#pragma once | |
#include <MMDeviceAPI.h> | |
#include <AudioClient.h> | |
#include <AudioPolicy.h> | |
// WASAPI Capture class. | |
class CWASAPICapture : public IAudioSessionEvents, IMMNotificationClient | |
{ | |
public: | |
// Public interface to CWASAPICapture. | |
CWASAPICapture(IMMDevice *Endpoint, ERole EndpointRole); | |
bool Initialize(UINT32 EngineLatency); | |
void Shutdown(); | |
bool Start(BYTE *CaptureBuffer, size_t BufferSize); | |
void Stop(); | |
WORD ChannelCount() { return _MixFormat->nChannels; } | |
UINT32 SamplesPerSecond() { return _MixFormat->nSamplesPerSec; } | |
UINT32 BytesPerSample() { return _MixFormat->wBitsPerSample / 8; } | |
size_t FrameSize() { return _FrameSize; } | |
WAVEFORMATEX *MixFormat() { return _MixFormat; } | |
UINT64 BytesCaptured() { return _CurrentCaptureIndex; } | |
STDMETHOD_(ULONG, AddRef)(); | |
STDMETHOD_(ULONG, Release)(); | |
private: | |
~CWASAPICapture(void); // Destructor is private to prevent accidental deletion. | |
LONG _RefCount; | |
// Core Audio Capture member variables. | |
IMMDevice * _Endpoint; | |
IAudioClient * _AudioClient; | |
IAudioCaptureClient *_CaptureClient; | |
HANDLE _CaptureThread; | |
HANDLE _AudioSamplesReadyEvent; | |
WAVEFORMATEX * _MixFormat; | |
size_t _FrameSize; | |
UINT32 _BufferSize; | |
// Capture buffer management. | |
BYTE *_CaptureBuffer; | |
size_t _CaptureBufferSize; | |
UINT64 _CurrentCaptureIndex; | |
static DWORD __stdcall WASAPICaptureThread(LPVOID Context); | |
DWORD CWASAPICapture::DoCaptureThread(); | |
// Stream switch related members and methods. | |
bool _EnableStreamSwitch; | |
ERole _EndpointRole; | |
IAudioSessionControl * _AudioSessionControl; | |
IMMDeviceEnumerator * _DeviceEnumerator; | |
LONG _EngineLatencyInMS; | |
// Overrides for pure virtual methods. | |
STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; }; | |
STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; }; | |
STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/) { return S_OK; }; | |
STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; }; | |
STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; }; | |
STDMETHOD(OnStateChanged) (AudioSessionState /*NewState*/) { return S_OK; }; | |
STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason) { return S_OK; }; | |
STDMETHOD(OnDeviceStateChanged) (LPCWSTR /*DeviceId*/, DWORD /*NewState*/) { return S_OK; } | |
STDMETHOD(OnDeviceAdded) (LPCWSTR /*DeviceId*/) { return S_OK; }; | |
STDMETHOD(OnDeviceRemoved) (LPCWSTR /*DeviceId(*/) { return S_OK; }; | |
STDMETHOD(OnDefaultDeviceChanged) (EDataFlow Flow, ERole Role, LPCWSTR NewDefaultDeviceId) { return S_OK; }; | |
STDMETHOD(OnPropertyValueChanged) (LPCWSTR /*DeviceId*/, const PROPERTYKEY /*Key*/) { return S_OK; }; | |
STDMETHOD(QueryInterface)(REFIID iid, void **pvObject) { return S_OK; }; | |
// Utility functions. | |
bool InitializeAudioEngine(); | |
bool LoadFormat(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment