|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
win32_wasapi: audio output for Windows wasapi exclusive mode audio
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
copyright ?-2013 by the mpg123 project - free software under the terms of the LGPL 2.1
|
|
Packit |
c32a2d |
see COPYING and AUTHORS files in distribution or http://mpg123.org
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
based on win32.c
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
#define _WIN32_WINNT 0x601
|
|
Packit |
c32a2d |
#define COBJMACROS 1
|
|
Packit |
c32a2d |
#include "out123_int.h"
|
|
Packit |
c32a2d |
#include <initguid.h>
|
|
Packit |
c32a2d |
#include <audioclient.h>
|
|
Packit |
c32a2d |
#include <mmdeviceapi.h>
|
|
Packit |
c32a2d |
#include <avrt.h>
|
|
Packit |
c32a2d |
#include "debug.h"
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#ifdef _MSC_VER
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* When compiling C code with MSVC it is only possible to declare, but not
|
|
Packit |
c32a2d |
define the WASAPI interface GUIDs using MS headers. So we define them
|
|
Packit |
c32a2d |
ourselves. */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#ifndef GUID_SECT
|
|
Packit |
c32a2d |
#define GUID_SECT
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#define __DEFINE_GUID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
|
Packit |
c32a2d |
#define __DEFINE_IID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const IID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
|
Packit |
c32a2d |
#define __DEFINE_CLSID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const CLSID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
|
Packit |
c32a2d |
#define MPG123_DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
|
Packit |
c32a2d |
__DEFINE_CLSID(mpg123_CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
|
Packit |
c32a2d |
#define MPG123_DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
|
Packit |
c32a2d |
__DEFINE_IID(mpg123_IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
// "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2"
|
|
Packit |
c32a2d |
MPG123_DEFINE_IID(IAudioClient, 1cb9ad4c, dbfa, 4c32, b1, 78, c2, f5, 68, a7, 03, b2);
|
|
Packit |
c32a2d |
// "A95664D2-9614-4F35-A746-DE8DB63617E6"
|
|
Packit |
c32a2d |
MPG123_DEFINE_IID(IMMDeviceEnumerator, a95664d2, 9614, 4f35, a7, 46, de, 8d, b6, 36, 17, e6);
|
|
Packit |
c32a2d |
// "BCDE0395-E52F-467C-8E3D-C4579291692E"
|
|
Packit |
c32a2d |
MPG123_DEFINE_CLSID(IMMDeviceEnumerator, bcde0395, e52f, 467c, 8e, 3d, c4, 57, 92, 91, 69, 2e);
|
|
Packit |
c32a2d |
// "F294ACFC-3146-4483-A7BF-ADDCA7C260E2"
|
|
Packit |
c32a2d |
MPG123_DEFINE_IID(IAudioRenderClient, f294acfc, 3146, 4483, a7, bf, ad, dc, a7, c2, 60, e2);
|
|
Packit |
c32a2d |
#else
|
|
Packit |
c32a2d |
#define mpg123_IID_IAudioClient IID_IAudioClient
|
|
Packit |
c32a2d |
#define mpg123_IID_IMMDeviceEnumerator IID_IMMDeviceEnumerator
|
|
Packit |
c32a2d |
#define mpg123_CLSID_IMMDeviceEnumerator CLSID_MMDeviceEnumerator
|
|
Packit |
c32a2d |
#define mpg123_IID_IAudioRenderClient IID_IAudioRenderClient
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Push mode does not work right yet, noisy audio, probably something to do with timing and buffers */
|
|
Packit |
c32a2d |
#define WASAPI_EVENT_MODE 1
|
|
Packit |
c32a2d |
#ifdef WASAPI_EVENT_MODE
|
|
Packit |
c32a2d |
#define Init_Flag AUDCLNT_STREAMFLAGS_EVENTCALLBACK
|
|
Packit |
c32a2d |
#define MOD_STRING "Experimental Audio output for Windows (wasapi event mode)."
|
|
Packit |
c32a2d |
#define BUFFER_TIME 20000000.0
|
|
Packit |
c32a2d |
#else
|
|
Packit |
c32a2d |
#define Init_Flag 0
|
|
Packit |
c32a2d |
#define MOD_STRING "Experimental Audio output for Windows (wasapi push mode)."
|
|
Packit |
c32a2d |
#define BUFFER_TIME 640000000.0
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int init_win32(out123_handle* ao);
|
|
Packit |
c32a2d |
static void flush_win32(out123_handle *ao);
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Module information data structure
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
mpg123_module_t mpg123_output_module_info = {
|
|
Packit |
c32a2d |
/* api_version */ MPG123_MODULE_API_VERSION,
|
|
Packit |
c32a2d |
/* name */ "win32_wasapi",
|
|
Packit |
c32a2d |
/* description */ MOD_STRING,
|
|
Packit |
c32a2d |
/* revision */ "$Rev:$",
|
|
Packit |
c32a2d |
/* handle */ NULL,
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* init_output */ init_win32,
|
|
Packit |
c32a2d |
};
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
// REFERENCE_TIME time units per second and per millisecond
|
|
Packit |
c32a2d |
#define REFTIMES_PER_SEC 10000000
|
|
Packit |
c32a2d |
#define REFTIMES_PER_MILLISEC 10000
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#define EXIT_ON_ERROR(hres) \
|
|
Packit |
c32a2d |
if (FAILED(hres)) { goto Exit; }
|
|
Packit |
c32a2d |
#define SAFE_RELEASE(punk) \
|
|
Packit |
c32a2d |
if ((punk) != NULL) \
|
|
Packit |
c32a2d |
{ (punk)->Release(); (punk) = NULL; }
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* todo: move into handle struct */
|
|
Packit |
c32a2d |
typedef struct _wasapi_state_struct {
|
|
Packit |
c32a2d |
IMMDeviceEnumerator *pEnumerator;
|
|
Packit |
c32a2d |
IMMDevice *pDevice;
|
|
Packit |
c32a2d |
IAudioClient *pAudioClient;
|
|
Packit |
c32a2d |
IAudioRenderClient *pRenderClient;
|
|
Packit |
c32a2d |
BYTE *pData;
|
|
Packit |
c32a2d |
UINT32 bufferFrameCount;
|
|
Packit |
c32a2d |
REFERENCE_TIME hnsRequestedDuration;
|
|
Packit |
c32a2d |
HANDLE hEvent;
|
|
Packit |
c32a2d |
HANDLE hTask;
|
|
Packit |
c32a2d |
size_t pData_off;
|
|
Packit |
c32a2d |
DWORD taskIndex;
|
|
Packit |
c32a2d |
char is_playing;
|
|
Packit |
c32a2d |
DWORD framesize;
|
|
Packit |
c32a2d |
} wasapi_state_struct;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* setup endpoints */
|
|
Packit |
c32a2d |
static int open_win32(out123_handle *ao){
|
|
Packit |
c32a2d |
HRESULT hr = 0;
|
|
Packit |
c32a2d |
wasapi_state_struct *state;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao || ao->userptr) return -1; /* userptr should really be null */
|
|
Packit |
c32a2d |
state = calloc(sizeof(*state),1);
|
|
Packit |
c32a2d |
if(!state) return -1;
|
|
Packit |
c32a2d |
state->hnsRequestedDuration = REFTIMES_PER_SEC;
|
|
Packit |
c32a2d |
ao->userptr = (void *)state;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
CoInitialize(NULL);
|
|
Packit |
c32a2d |
hr = CoCreateInstance(&mpg123_CLSID_IMMDeviceEnumerator,NULL,CLSCTX_ALL, &mpg123_IID_IMMDeviceEnumerator,(void**)&state->pEnumerator);
|
|
Packit |
c32a2d |
debug("CoCreateInstance");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(state->pEnumerator,eRender, eConsole, &state->pDevice);
|
|
Packit |
c32a2d |
debug("IMMDeviceEnumerator_GetDefaultAudioEndpoint");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
hr = IMMDeviceActivator_Activate(state->pDevice,
|
|
Packit |
c32a2d |
&mpg123_IID_IAudioClient, CLSCTX_ALL,
|
|
Packit |
c32a2d |
NULL, (void**)&state->pAudioClient);
|
|
Packit |
c32a2d |
debug("IMMDeviceActivator_Activate");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
Exit:
|
|
Packit |
c32a2d |
debug2("%s failed with %lx", __FUNCTION__, hr);
|
|
Packit |
c32a2d |
return 1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
typedef struct tWAVEFORMATEX {
|
|
Packit |
c32a2d |
WORD wFormatTag;
|
|
Packit |
c32a2d |
WORD nChannels;
|
|
Packit |
c32a2d |
DWORD nSamplesPerSec;
|
|
Packit |
c32a2d |
DWORD nAvgBytesPerSec;
|
|
Packit |
c32a2d |
WORD nBlockAlign;
|
|
Packit |
c32a2d |
WORD wBitsPerSample;
|
|
Packit |
c32a2d |
WORD cbSize;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
} WAVEFORMATEX;
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int formats_generator(const out123_handle * const ao, const int waveformat, WAVEFORMATEX *const format){
|
|
Packit |
c32a2d |
DWORD bytes_per_sample = 0;
|
|
Packit |
c32a2d |
WORD tag = WAVE_FORMAT_PCM;
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
int ret = waveformat;
|
|
Packit |
c32a2d |
switch(waveformat){
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_8:
|
|
Packit |
c32a2d |
bytes_per_sample = 1;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_FLOAT_32:
|
|
Packit |
c32a2d |
tag = WAVE_FORMAT_IEEE_FLOAT;
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_32:
|
|
Packit |
c32a2d |
bytes_per_sample = 4;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_16:
|
|
Packit |
c32a2d |
bytes_per_sample = 2;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_24:
|
|
Packit |
c32a2d |
bytes_per_sample = 3;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
default:
|
|
Packit |
c32a2d |
debug1("uh oh unknown %d",waveformat);
|
|
Packit |
c32a2d |
ret = 0;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
format->wFormatTag = tag;
|
|
Packit |
c32a2d |
format->nChannels = ao->channels;
|
|
Packit |
c32a2d |
format->nSamplesPerSec = ao->rate;
|
|
Packit |
c32a2d |
format->nAvgBytesPerSec = ao->channels * bytes_per_sample * ao->rate;
|
|
Packit |
c32a2d |
format->nBlockAlign = ao->channels * bytes_per_sample;
|
|
Packit |
c32a2d |
format->wBitsPerSample = bytes_per_sample * 8;
|
|
Packit |
c32a2d |
format->cbSize = 0;
|
|
Packit |
c32a2d |
return ret;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* check supported formats */
|
|
Packit |
c32a2d |
static int get_formats_win32(out123_handle *ao){
|
|
Packit |
c32a2d |
/* PLEASE check with write_init and write_win32 buffer size calculation in case it is able to support something other than 16bit */
|
|
Packit |
c32a2d |
HRESULT hr;
|
|
Packit |
c32a2d |
int ret = 0;
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return -1;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
debug2("channels %d, rate %ld",ao->channels, ao->rate);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
WAVEFORMATEX wf;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(ao->format & MPG123_ENC_SIGNED_8){
|
|
Packit |
c32a2d |
formats_generator(ao,MPG123_ENC_SIGNED_8,&wf);
|
|
Packit |
c32a2d |
if((hr = IAudioClient_IsFormatSupported(state->pAudioClient,AUDCLNT_SHAREMODE_EXCLUSIVE, &wf, NULL)) == S_OK)
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_8;
|
|
Packit |
c32a2d |
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT) debug1("MPG123_ENC_SIGNED_8 %ld not supported", ao->rate);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(ao->format & MPG123_ENC_SIGNED_16){
|
|
Packit |
c32a2d |
formats_generator(ao,MPG123_ENC_SIGNED_16,&wf);
|
|
Packit |
c32a2d |
if((hr = IAudioClient_IsFormatSupported(state->pAudioClient,AUDCLNT_SHAREMODE_EXCLUSIVE, &wf, NULL)) == S_OK)
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_16;
|
|
Packit |
c32a2d |
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT) debug1("MPG123_ENC_SIGNED_16 %ld not supported", ao->rate);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(ao->format & MPG123_ENC_SIGNED_32){
|
|
Packit |
c32a2d |
formats_generator(ao,MPG123_ENC_SIGNED_32,&wf);
|
|
Packit |
c32a2d |
if((hr = IAudioClient_IsFormatSupported(state->pAudioClient,AUDCLNT_SHAREMODE_EXCLUSIVE, &wf, NULL)) == S_OK)
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_32;
|
|
Packit |
c32a2d |
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT) debug1("MPG123_ENC_SIGNED_32 %ld not supported", ao->rate);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(ao->format & MPG123_ENC_FLOAT_32){
|
|
Packit |
c32a2d |
formats_generator(ao,MPG123_ENC_FLOAT_32,&wf);
|
|
Packit |
c32a2d |
if((hr = IAudioClient_IsFormatSupported(state->pAudioClient,AUDCLNT_SHAREMODE_EXCLUSIVE, &wf, NULL)) == S_OK)
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_FLOAT_32;
|
|
Packit |
c32a2d |
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT) debug1("MPG123_ENC_FLOAT_32 %ld not supported", ao->rate);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(ao->format & MPG123_ENC_SIGNED_24){
|
|
Packit |
c32a2d |
formats_generator(ao,MPG123_ENC_SIGNED_24,&wf);
|
|
Packit |
c32a2d |
if((hr = IAudioClient_IsFormatSupported(state->pAudioClient,AUDCLNT_SHAREMODE_EXCLUSIVE, &wf, NULL)) == S_OK)
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_24;
|
|
Packit |
c32a2d |
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT) debug1("MPG123_ENC_SIGNED_24 %ld not supported", ao->rate);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return ret; /* afaik only 16bit 44.1kHz/48kHz has been known to work */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* setup with agreed on format, for now only MPG123_ENC_SIGNED_16 */
|
|
Packit |
c32a2d |
static int write_init(out123_handle *ao){
|
|
Packit |
c32a2d |
HRESULT hr;
|
|
Packit |
c32a2d |
double offset = 0.5;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return -1;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
WAVEFORMATEX s16;
|
|
Packit |
c32a2d |
formats_generator(ao,ao->format,&s16);
|
|
Packit |
c32a2d |
state->framesize = s16.nBlockAlign;
|
|
Packit |
c32a2d |
debug1("block size %ld", state->framesize);
|
|
Packit |
c32a2d |
/* cargo cult code */
|
|
Packit |
c32a2d |
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,NULL, &state->hnsRequestedDuration);
|
|
Packit |
c32a2d |
debug("IAudioClient_GetDevicePeriod OK");
|
|
Packit |
c32a2d |
reinit:
|
|
Packit |
c32a2d |
hr = IAudioClient_Initialize(state->pAudioClient,
|
|
Packit |
c32a2d |
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
|
Packit |
c32a2d |
Init_Flag,
|
|
Packit |
c32a2d |
state->hnsRequestedDuration,
|
|
Packit |
c32a2d |
state->hnsRequestedDuration,
|
|
Packit |
c32a2d |
&s16,
|
|
Packit |
c32a2d |
NULL);
|
|
Packit |
c32a2d |
debug("IAudioClient_Initialize OK");
|
|
Packit |
c32a2d |
/* something about buffer sizes on Win7, fixme might loop forever */
|
|
Packit |
c32a2d |
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED){
|
|
Packit |
c32a2d |
if (offset > 10.0) goto Exit; /* is 10 enough to break out of the loop?*/
|
|
Packit |
c32a2d |
debug("AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED");
|
|
Packit |
c32a2d |
IAudioClient_GetBufferSize(state->pAudioClient,&state->bufferFrameCount);
|
|
Packit |
c32a2d |
/* double buffered */
|
|
Packit |
c32a2d |
state->hnsRequestedDuration = (REFERENCE_TIME)((BUFFER_TIME / s16.nSamplesPerSec * state->bufferFrameCount) + offset);
|
|
Packit |
c32a2d |
offset += 0.5;
|
|
Packit |
c32a2d |
IAudioClient_Release(state->pAudioClient);
|
|
Packit |
c32a2d |
state->pAudioClient = NULL;
|
|
Packit |
c32a2d |
hr = IMMDeviceActivator_Activate(state->pDevice,
|
|
Packit |
c32a2d |
&mpg123_IID_IAudioClient, CLSCTX_ALL,
|
|
Packit |
c32a2d |
NULL, (void**)&state->pAudioClient);
|
|
Packit |
c32a2d |
debug("IMMDeviceActivator_Activate");
|
|
Packit |
c32a2d |
goto reinit;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
hr = IAudioClient_GetService(state->pAudioClient,
|
|
Packit |
c32a2d |
&mpg123_IID_IAudioRenderClient,
|
|
Packit |
c32a2d |
(void**)&state->pRenderClient);
|
|
Packit |
c32a2d |
debug("IAudioClient_GetService OK");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
#ifdef WASAPI_EVENT_MODE
|
|
Packit |
c32a2d |
state->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
Packit |
c32a2d |
debug("CreateEvent OK");
|
|
Packit |
c32a2d |
if(!state->hEvent) goto Exit;
|
|
Packit |
c32a2d |
hr = IAudioClient_SetEventHandle(state->pAudioClient,state->hEvent);
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr);
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
hr = IAudioClient_GetBufferSize(state->pAudioClient,&state->bufferFrameCount);
|
|
Packit |
c32a2d |
debug("IAudioClient_GetBufferSize OK");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
Exit:
|
|
Packit |
c32a2d |
debug2("%s failed with %lx", __FUNCTION__, hr);
|
|
Packit |
c32a2d |
return 1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Set play mode if unset, also raise thread priority */
|
|
Packit |
c32a2d |
static HRESULT play_init(out123_handle *ao){
|
|
Packit |
c32a2d |
HRESULT hr = S_OK;
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return -1;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
if(!state->is_playing){
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
state->hTask = AvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
|
Packit |
c32a2d |
hr = IAudioClient_Start(state->pAudioClient);
|
|
Packit |
c32a2d |
state->is_playing = 1;
|
|
Packit |
c32a2d |
debug("IAudioClient_Start");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return hr;
|
|
Packit |
c32a2d |
Exit:
|
|
Packit |
c32a2d |
debug2("%s failed with %lx", __FUNCTION__, hr);
|
|
Packit |
c32a2d |
return hr;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* copy audio into IAudioRenderClient provided buffer */
|
|
Packit |
c32a2d |
static int write_win32(out123_handle *ao, unsigned char *buf, int len){
|
|
Packit |
c32a2d |
HRESULT hr;
|
|
Packit |
c32a2d |
size_t to_copy = 0;
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return -1;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
if(!len) return 0;
|
|
Packit |
c32a2d |
if(!state->pRenderClient) write_init(ao);
|
|
Packit |
c32a2d |
size_t frames_in = len/state->framesize; /* Frames in buf, is framesize even correct? */
|
|
Packit |
c32a2d |
debug("mode entered");
|
|
Packit |
c32a2d |
#ifdef WASAPI_EVENT_MODE
|
|
Packit |
c32a2d |
/* Event mode WASAPI */
|
|
Packit |
c32a2d |
DWORD retval = -1;
|
|
Packit |
c32a2d |
int flag = 0; /* Silence flag */
|
|
Packit |
c32a2d |
feed_again:
|
|
Packit |
c32a2d |
if(!state->pData){
|
|
Packit |
c32a2d |
/* Acquire buffer */
|
|
Packit |
c32a2d |
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,state->bufferFrameCount, &state->pData);
|
|
Packit |
c32a2d |
debug("IAudioRenderClient_GetBuffer");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(frames_in){ /* Did we get half a frame?? non-zero len smaller than framesize? */
|
|
Packit |
c32a2d |
/* We must put in exactly the amount of frames specified by IAudioRenderClient_GetBuffer */
|
|
Packit |
c32a2d |
while(state->pData_off < state->bufferFrameCount){
|
|
Packit |
c32a2d |
to_copy = state->bufferFrameCount - state->pData_off;
|
|
Packit |
c32a2d |
debug3("pData_off %I64d, bufferFrameCount %d, to_copy %I64d", state->pData_off, state->bufferFrameCount, to_copy);
|
|
Packit |
c32a2d |
if(to_copy > frames_in){
|
|
Packit |
c32a2d |
/* buf can fit in provided buffer space */
|
|
Packit |
c32a2d |
debug1("all buffers copied, %I64d", frames_in);
|
|
Packit |
c32a2d |
memcpy(state->pData+state->pData_off*state->framesize,buf,state->framesize*(frames_in));
|
|
Packit |
c32a2d |
state->pData_off += frames_in;
|
|
Packit |
c32a2d |
frames_in = 0;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
} else {
|
|
Packit |
c32a2d |
/* buf too big, needs spliting */
|
|
Packit |
c32a2d |
debug1("partial buffers %I64d", to_copy);
|
|
Packit |
c32a2d |
memcpy(state->pData+state->pData_off*state->framesize,buf,state->framesize*(to_copy));
|
|
Packit |
c32a2d |
state->pData_off += to_copy;
|
|
Packit |
c32a2d |
buf+=(to_copy*state->framesize);
|
|
Packit |
c32a2d |
frames_in -= to_copy;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
} else {
|
|
Packit |
c32a2d |
/* In case we ever get half a frame, is it possible? */
|
|
Packit |
c32a2d |
flag = AUDCLNT_BUFFERFLAGS_SILENT;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
debug2("Copied %I64d, left %I64d", state->pData_off, frames_in);
|
|
Packit |
c32a2d |
if(state->pData_off == state->bufferFrameCount) {
|
|
Packit |
c32a2d |
/* Tell IAudioRenderClient that buffer is filled and released */
|
|
Packit |
c32a2d |
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,state->pData_off, flag);
|
|
Packit |
c32a2d |
state->pData_off = 0;
|
|
Packit |
c32a2d |
state->pData = NULL;
|
|
Packit |
c32a2d |
debug("IAudioRenderClient_ReleaseBuffer");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
if(!state->is_playing){
|
|
Packit |
c32a2d |
hr = play_init(ao);
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* wait for next pull event */
|
|
Packit |
c32a2d |
retval = WaitForSingleObject(state->hEvent, 2000);
|
|
Packit |
c32a2d |
if (retval != WAIT_OBJECT_0){
|
|
Packit |
c32a2d |
/* Event handle timed out after a 2-second wait, something went very wrong */
|
|
Packit |
c32a2d |
IAudioClient_Stop(state->pAudioClient);
|
|
Packit |
c32a2d |
hr = ERROR_TIMEOUT;
|
|
Packit |
c32a2d |
goto Exit;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(frames_in > 0)
|
|
Packit |
c32a2d |
goto feed_again;
|
|
Packit |
c32a2d |
#else /* PUSH mode code */
|
|
Packit |
c32a2d |
UINT32 numFramesAvailable, numFramesPadding;
|
|
Packit |
c32a2d |
debug1("block size %ld", state->framesize);
|
|
Packit |
c32a2d |
feed_again:
|
|
Packit |
c32a2d |
/* How much buffer do we get to use? */
|
|
Packit |
c32a2d |
hr = IAudioClient_GetBufferSize(state->pAudioClient,&state->bufferFrameCount);
|
|
Packit |
c32a2d |
debug("IAudioRenderClient_GetBuffer");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
hr = IAudioClient_GetCurrentPadding(state->pAudioClient,&numFramesPadding);
|
|
Packit |
c32a2d |
debug("IAudioClient_GetCurrentPadding");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
/* How much buffer is writable at the moment? */
|
|
Packit |
c32a2d |
numFramesAvailable = state->bufferFrameCount - numFramesPadding;
|
|
Packit |
c32a2d |
debug3("numFramesAvailable %d, bufferFrameCount %d, numFramesPadding %d", numFramesAvailable, state->bufferFrameCount, numFramesPadding);
|
|
Packit |
c32a2d |
if(numFramesAvailable > frames_in){
|
|
Packit |
c32a2d |
/* can fit all frames now */
|
|
Packit |
c32a2d |
state->pData_off = 0;
|
|
Packit |
c32a2d |
to_copy = frames_in;
|
|
Packit |
c32a2d |
} else {
|
|
Packit |
c32a2d |
/* copy whatever that fits in the buffer */
|
|
Packit |
c32a2d |
state->pData_off = frames_in - numFramesAvailable;
|
|
Packit |
c32a2d |
to_copy = numFramesAvailable;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Acquire buffer */
|
|
Packit |
c32a2d |
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,to_copy,&state->pData);
|
|
Packit |
c32a2d |
debug("IAudioRenderClient_GetBuffer");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
memcpy(state->pData,buf+state->pData_off * state->framesize,to_copy*state->framesize);
|
|
Packit |
c32a2d |
/* Release buffer */
|
|
Packit |
c32a2d |
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,to_copy, 0);
|
|
Packit |
c32a2d |
debug("IAudioRenderClient_ReleaseBuffer");
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
if(!state->is_playing){
|
|
Packit |
c32a2d |
hr = play_init(ao);
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
frames_in -= to_copy;
|
|
Packit |
c32a2d |
/* Wait sometime for buffer to empty? */
|
|
Packit |
c32a2d |
DWORD sleeptime = (DWORD)(state->hnsRequestedDuration/REFTIMES_PER_MILLISEC/ao->rate);
|
|
Packit |
c32a2d |
debug1("Sleeping %ld msec", sleeptime);
|
|
Packit |
c32a2d |
Sleep(sleeptime);
|
|
Packit |
c32a2d |
if (frames_in)
|
|
Packit |
c32a2d |
goto feed_again;
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
return len;
|
|
Packit |
c32a2d |
Exit:
|
|
Packit |
c32a2d |
debug2("%s failed with %lx", __FUNCTION__, hr);
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void flush_win32(out123_handle *ao){
|
|
Packit |
c32a2d |
/* Wait for the last buffer to play before stopping. */
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
HRESULT hr;
|
|
Packit |
c32a2d |
if(!state->pAudioClient) return;
|
|
Packit |
c32a2d |
state->pData = NULL;
|
|
Packit |
c32a2d |
hr = IAudioClient_Stop(state->pAudioClient);
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
IAudioClient_Reset(state->pAudioClient);
|
|
Packit |
c32a2d |
EXIT_ON_ERROR(hr)
|
|
Packit |
c32a2d |
return;
|
|
Packit |
c32a2d |
Exit:
|
|
Packit |
c32a2d |
debug2("%s IAudioClient_Stop with %lx", __FUNCTION__, hr);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int close_win32(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao || !ao->userptr) return -1;
|
|
Packit |
c32a2d |
wasapi_state_struct *state = (wasapi_state_struct *) ao->userptr;
|
|
Packit |
c32a2d |
#ifdef WASAPI_EVENT_MODE
|
|
Packit |
c32a2d |
if(state->pData){
|
|
Packit |
c32a2d |
/* Play all in buffer before closing */
|
|
Packit |
c32a2d |
debug("Flushing remaining buffers");
|
|
Packit |
c32a2d |
IAudioRenderClient_ReleaseBuffer(state->pRenderClient,state->bufferFrameCount, 0);
|
|
Packit |
c32a2d |
WaitForSingleObject(state->hEvent, 2000);
|
|
Packit |
c32a2d |
state->pData = NULL;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
if(state->pAudioClient) IAudioClient_Stop(state->pAudioClient);
|
|
Packit |
c32a2d |
if(state->pRenderClient) IAudioRenderClient_Release(state->pRenderClient);
|
|
Packit |
c32a2d |
if(state->pAudioClient) IAudioClient_Release(state->pAudioClient);
|
|
Packit |
c32a2d |
if(state->hTask) AvRevertMmThreadCharacteristics(state->hTask);
|
|
Packit |
c32a2d |
if(state->pEnumerator) IMMDeviceEnumerator_Release(state->pEnumerator);
|
|
Packit |
c32a2d |
if(state->pDevice) IMMDevice_Release(state->pDevice);
|
|
Packit |
c32a2d |
CoUninitialize();
|
|
Packit |
c32a2d |
free(state);
|
|
Packit |
c32a2d |
ao->userptr = NULL;
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int init_win32(out123_handle* ao){
|
|
Packit |
c32a2d |
debug1("%s",__FUNCTION__);
|
|
Packit |
c32a2d |
if(!ao) return -1;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Set callbacks */
|
|
Packit |
c32a2d |
ao->open = open_win32;
|
|
Packit |
c32a2d |
ao->flush = flush_win32;
|
|
Packit |
c32a2d |
ao->write = write_win32;
|
|
Packit |
c32a2d |
ao->get_formats = get_formats_win32;
|
|
Packit |
c32a2d |
ao->close = close_win32;
|
|
Packit |
c32a2d |
ao->userptr = NULL;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Success */
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|