1
0
mirror of https://github.com/danog/libtgvoip.git synced 2024-12-11 16:49:52 +01:00
libtgvoip/os/windows/AudioOutputWASAPI.cpp
2017-05-05 00:44:23 +03:00

385 lines
11 KiB
C++

//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <assert.h>
#include "AudioOutputWASAPI.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include <functiondiscoverykeys.h>
#define BUFFER_SIZE 960
#define CHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); failed=true; return;}}
#define SCHECK_RES(res, msg) {if(FAILED(res)){LOGE("%s failed: HRESULT=0x%08X", msg, res); return;}}
template <class T> void SafeRelease(T **ppT)
{
if(*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
#ifdef TGVOIP_WINXP_COMPAT
#endif
using namespace tgvoip::audio;
AudioOutputWASAPI::AudioOutputWASAPI(std::string deviceID){
isPlaying=false;
remainingDataLen=0;
refCount=1;
HRESULT res;
res=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CHECK_RES(res, "CoInitializeEx");
#ifdef TGVOIP_WINXP_COMPAT
HANDLE (WINAPI *__CreateEventExA)(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags, DWORD dwDesiredAccess);
__CreateEventExA=(HANDLE (WINAPI *)(LPSECURITY_ATTRIBUTES, LPCSTR, DWORD, DWORD))GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateEventExA");
#undef CreateEventEx
#define CreateEventEx __CreateEventExA
#endif
shutdownEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
audioSamplesReadyEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
streamSwitchEvent=CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
ZeroMemory(&format, sizeof(format));
format.wFormatTag=WAVE_FORMAT_PCM;
format.nChannels=1;
format.nSamplesPerSec=48000;
format.nBlockAlign=2;
format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
format.wBitsPerSample=16;
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
CHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=enumerator->RegisterEndpointNotificationCallback(this);
CHECK_RES(res, "enumerator->RegisterEndpointNotificationCallback");
audioClient=NULL;
renderClient=NULL;
audioSessionControl=NULL;
device=NULL;
thread=NULL;
SetCurrentDevice(deviceID);
}
AudioOutputWASAPI::~AudioOutputWASAPI(){
if(audioClient && isPlaying){
audioClient->Stop();
}
if(audioSessionControl){
audioSessionControl->UnregisterAudioSessionNotification(this);
}
SafeRelease(&audioSessionControl);
SafeRelease(&renderClient);
SafeRelease(&audioClient);
SafeRelease(&device);
SetEvent(shutdownEvent);
if(thread){
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
}
CloseHandle(shutdownEvent);
CloseHandle(audioSamplesReadyEvent);
CloseHandle(streamSwitchEvent);
if(enumerator)
enumerator->UnregisterEndpointNotificationCallback(this);
SafeRelease(&enumerator);
}
void AudioOutputWASAPI::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
}
void AudioOutputWASAPI::Start(){
isPlaying=true;
if(!thread){
thread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)AudioOutputWASAPI::StartThread, this, 0, NULL);
}
if(audioClient)
audioClient->Start();
}
void AudioOutputWASAPI::Stop(){
isPlaying=false;
if(audioClient)
audioClient->Stop();
}
bool AudioOutputWASAPI::IsPlaying(){
return isPlaying;
}
void AudioOutputWASAPI::EnumerateDevices(std::vector<tgvoip::AudioOutputDevice>& devs){
HRESULT res;
res=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
SCHECK_RES(res, "CoInitializeEx");
IMMDeviceEnumerator *deviceEnumerator = NULL;
IMMDeviceCollection *deviceCollection = NULL;
res=CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
SCHECK_RES(res, "CoCreateInstance(MMDeviceEnumerator)");
res=deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
SCHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
SCHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
SCHECK_RES(res, "GetDeviceItem");
wchar_t* devID;
res=device->GetId(&devID);
SCHECK_RES(res, "get device id");
IPropertyStore* propStore;
res=device->OpenPropertyStore(STGM_READ, &propStore);
SafeRelease(&device);
SCHECK_RES(res, "OpenPropertyStore");
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
res=propStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propStore);
AudioOutputDevice dev;
wchar_t actualFriendlyName[128];
if(friendlyName.vt==VT_LPWSTR){
wcsncpy(actualFriendlyName, friendlyName.pwszVal, sizeof(actualFriendlyName)/sizeof(wchar_t));
}else{
wcscpy(actualFriendlyName, L"Unknown");
}
PropVariantClear(&friendlyName);
char buf[256];
WideCharToMultiByte(CP_UTF8, 0, devID, -1, buf, sizeof(buf), NULL, NULL);
dev.id=buf;
WideCharToMultiByte(CP_UTF8, 0, actualFriendlyName, -1, buf, sizeof(buf), NULL, NULL);
dev.displayName=buf;
devs.push_back(dev);
CoTaskMemFree(devID);
}
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
}
void AudioOutputWASAPI::SetCurrentDevice(std::string deviceID){
if(thread){
streamChangeToDevice=deviceID;
SetEvent(streamSwitchEvent);
}else{
ActuallySetCurrentDevice(deviceID);
}
}
void AudioOutputWASAPI::ActuallySetCurrentDevice(std::string deviceID){
currentDevice=deviceID;
HRESULT res;
if(audioClient && isPlaying){
res=audioClient->Stop();
CHECK_RES(res, "audioClient->Stop");
}
if(audioSessionControl){
res=audioSessionControl->UnregisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->UnregisterAudioSessionNotification");
}
SafeRelease(&audioSessionControl);
SafeRelease(&renderClient);
SafeRelease(&audioClient);
SafeRelease(&device);
IMMDeviceCollection *deviceCollection = NULL;
if(deviceID=="default"){
isDefaultDevice=true;
res=enumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &device);
CHECK_RES(res, "GetDefaultAudioEndpoint");
}else{
isDefaultDevice=false;
res=enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
CHECK_RES(res, "EnumAudioEndpoints");
UINT devCount;
res=deviceCollection->GetCount(&devCount);
CHECK_RES(res, "GetCount");
for(UINT i=0;i<devCount;i++){
IMMDevice* device;
res=deviceCollection->Item(i, &device);
CHECK_RES(res, "GetDeviceItem");
wchar_t* _devID;
res=device->GetId(&_devID);
CHECK_RES(res, "get device id");
char devID[128];
WideCharToMultiByte(CP_UTF8, 0, _devID, -1, devID, 128, NULL, NULL);
CoTaskMemFree(_devID);
if(deviceID==devID){
this->device=device;
break;
}
}
}
if(deviceCollection)
SafeRelease(&deviceCollection);
res=device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&audioClient);
CHECK_RES(res, "device->Activate");
res = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | 0x80000000/*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/, 60 * 10000, 0, &format, NULL);
CHECK_RES(res, "audioClient->Initialize");
uint32_t bufSize;
res = audioClient->GetBufferSize(&bufSize);
CHECK_RES(res, "audioClient->GetBufferSize");
LOGV("buffer size: %u", bufSize);
res = audioClient->SetEventHandle(audioSamplesReadyEvent);
CHECK_RES(res, "audioClient->SetEventHandle");
res = audioClient->GetService(IID_PPV_ARGS(&renderClient));
CHECK_RES(res, "audioClient->GetService");
BYTE* data;
res = renderClient->GetBuffer(bufSize, &data);
CHECK_RES(res, "renderClient->GetBuffer");
res = renderClient->ReleaseBuffer(bufSize, AUDCLNT_BUFFERFLAGS_SILENT);
CHECK_RES(res, "renderClient->ReleaseBuffer");
res=audioClient->GetService(IID_PPV_ARGS(&audioSessionControl));
CHECK_RES(res, "audioClient->GetService(IAudioSessionControl)");
res=audioSessionControl->RegisterAudioSessionNotification(this);
CHECK_RES(res, "audioSessionControl->RegisterAudioSessionNotification");
if(isPlaying)
audioClient->Start();
LOGV("set current output device done");
}
DWORD AudioOutputWASAPI::StartThread(void* arg) {
((AudioOutputWASAPI*)arg)->RunThread();
return 0;
}
void AudioOutputWASAPI::RunThread() {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
HANDLE waitArray[]={shutdownEvent, streamSwitchEvent, audioSamplesReadyEvent};
HRESULT res=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CHECK_RES(res, "CoInitializeEx in render thread");
uint32_t bufferSize;
res=audioClient->GetBufferSize(&bufferSize);
CHECK_RES(res, "audioClient->GetBufferSize");
uint32_t framesWritten=0;
bool running=true;
while(running){
DWORD waitResult=WaitForMultipleObjects(3, waitArray, false, INFINITE);
if(waitResult==WAIT_OBJECT_0){ // shutdownEvent
LOGV("render thread shutting down");
running=false;
}else if(waitResult==WAIT_OBJECT_0+1){ // streamSwitchEvent
LOGV("stream switch");
ActuallySetCurrentDevice(streamChangeToDevice);
ResetEvent(streamSwitchEvent);
LOGV("stream switch done");
}else if(waitResult==WAIT_OBJECT_0+2){ // audioSamplesReadyEvent
BYTE* data;
uint32_t padding;
uint32_t framesAvailable;
res=audioClient->GetCurrentPadding(&padding);
CHECK_RES(res, "audioClient->GetCurrentPadding");
framesAvailable=bufferSize-padding;
res=renderClient->GetBuffer(framesAvailable, &data);
CHECK_RES(res, "renderClient->GetBuffer");
size_t bufferSize=framesAvailable*2;
while(bufferSize>remainingDataLen){
InvokeCallback(remainingData+remainingDataLen, 960*2);
remainingDataLen+=960*2;
}
memcpy(data, remainingData, bufferSize);
if(remainingDataLen>bufferSize){
memmove(remainingData, remainingData+bufferSize, remainingDataLen-bufferSize);
}
remainingDataLen-=bufferSize;
res=renderClient->ReleaseBuffer(framesAvailable, 0);
CHECK_RES(res, "renderClient->ReleaseBuffer");
framesWritten+=framesAvailable;
}
}
}
HRESULT AudioOutputWASAPI::OnSessionDisconnected(AudioSessionDisconnectReason reason) {
if(!isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
HRESULT AudioOutputWASAPI::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR newDevID) {
if(flow==eRender && role==eCommunications && isDefaultDevice){
streamChangeToDevice="default";
SetEvent(streamSwitchEvent);
}
return S_OK;
}
ULONG AudioOutputWASAPI::AddRef(){
return InterlockedIncrement(&refCount);
}
ULONG AudioOutputWASAPI::Release(){
return InterlockedDecrement(&refCount);
}
HRESULT AudioOutputWASAPI::QueryInterface(REFIID iid, void** obj){
if(!obj){
return E_POINTER;
}
*obj=NULL;
if(iid==IID_IUnknown){
*obj=static_cast<IUnknown*>(static_cast<IAudioSessionEvents*>(this));
AddRef();
}else if(iid==__uuidof(IMMNotificationClient)){
*obj=static_cast<IMMNotificationClient*>(this);
AddRef();
}else if(iid==__uuidof(IAudioSessionEvents)){
*obj=static_cast<IAudioSessionEvents*>(this);
AddRef();
}else{
return E_NOINTERFACE;
}
return S_OK;
}