Support capture with mmdevapi

This commit is contained in:
Chris Robinson
2014-12-19 15:49:37 -08:00
parent f300be00ae
commit abf0bd13ca
2 changed files with 552 additions and 1 deletions
+551 -1
View File
@@ -1105,6 +1105,544 @@ static ALint64 ALCmmdevPlayback_getLatency(ALCmmdevPlayback *self)
}
typedef struct ALCmmdevCapture {
DERIVE_FROM_TYPE(ALCbackend);
DERIVE_FROM_TYPE(ALCmmdevProxy);
WCHAR *devid;
IMMDevice *mmdev;
IAudioClient *client;
IAudioCaptureClient *capture;
HANDLE NotifyEvent;
HANDLE MsgEvent;
RingBuffer *Ring;
volatile int killNow;
althrd_t thread;
} ALCmmdevCapture;
static int ALCmmdevCapture_recordProc(void *arg);
static void ALCmmdevCapture_Construct(ALCmmdevCapture *self, ALCdevice *device);
static void ALCmmdevCapture_Destruct(ALCmmdevCapture *self);
static ALCenum ALCmmdevCapture_open(ALCmmdevCapture *self, const ALCchar *name);
static HRESULT ALCmmdevCapture_openProxy(ALCmmdevCapture *self);
static void ALCmmdevCapture_close(ALCmmdevCapture *self);
static void ALCmmdevCapture_closeProxy(ALCmmdevCapture *self);
static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, ALCboolean, reset)
static HRESULT ALCmmdevCapture_resetProxy(ALCmmdevCapture *self);
static ALCboolean ALCmmdevCapture_start(ALCmmdevCapture *self);
static HRESULT ALCmmdevCapture_startProxy(ALCmmdevCapture *self);
static void ALCmmdevCapture_stop(ALCmmdevCapture *self);
static void ALCmmdevCapture_stopProxy(ALCmmdevCapture *self);
static ALCenum ALCmmdevCapture_captureSamples(ALCmmdevCapture *self, ALCvoid *buffer, ALCuint samples);
static ALuint ALCmmdevCapture_availableSamples(ALCmmdevCapture *self);
static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, ALint64, getLatency)
static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, void, lock)
static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, void, unlock)
DECLARE_DEFAULT_ALLOCATORS(ALCmmdevCapture)
DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevCapture);
DEFINE_ALCBACKEND_VTABLE(ALCmmdevCapture);
static void ALCmmdevCapture_Construct(ALCmmdevCapture *self, ALCdevice *device)
{
SET_VTABLE2(ALCmmdevCapture, ALCbackend, self);
SET_VTABLE2(ALCmmdevCapture, ALCmmdevProxy, self);
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy, self));
self->devid = NULL;
self->mmdev = NULL;
self->client = NULL;
self->capture = NULL;
self->NotifyEvent = NULL;
self->MsgEvent = NULL;
self->Ring = NULL;
self->killNow = 0;
}
static void ALCmmdevCapture_Destruct(ALCmmdevCapture *self)
{
DestroyRingBuffer(self->Ring);
self->Ring = NULL;
if(self->NotifyEvent != NULL)
CloseHandle(self->NotifyEvent);
self->NotifyEvent = NULL;
if(self->MsgEvent != NULL)
CloseHandle(self->MsgEvent);
self->MsgEvent = NULL;
free(self->devid);
self->devid = NULL;
ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy, self));
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
}
FORCE_ALIGN int ALCmmdevCapture_recordProc(void *arg)
{
ALCmmdevCapture *self = arg;
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
HRESULT hr;
hr = CoInitialize(NULL);
if(FAILED(hr))
{
ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr);
V0(device->Backend,lock)();
aluHandleDisconnect(device);
V0(device->Backend,unlock)();
return 1;
}
althrd_setname(althrd_current(), "alsoft-record");
while(!self->killNow)
{
UINT32 avail;
DWORD res;
hr = IAudioCaptureClient_GetNextPacketSize(self->capture, &avail);
if(FAILED(hr))
ERR("Failed to get next packet size: 0x%08lx\n", hr);
else while(avail > 0 && SUCCEEDED(hr))
{
UINT32 flags, numsamples;
BYTE *data;
hr = IAudioCaptureClient_GetBuffer(self->capture,
&data, &numsamples, &flags, NULL, NULL
);
if(FAILED(hr))
{
ERR("Failed to get capture buffer: 0x%08lx\n", hr);
break;
}
WriteRingBuffer(self->Ring, data, numsamples);
hr = IAudioCaptureClient_ReleaseBuffer(self->capture, numsamples);
if(FAILED(hr))
{
ERR("Failed to release capture buffer: 0x%08lx\n", hr);
break;
}
hr = IAudioCaptureClient_GetNextPacketSize(self->capture, &avail);
if(FAILED(hr))
ERR("Failed to get next packet size: 0x%08lx\n", hr);
}
if(FAILED(hr))
{
V0(device->Backend,lock)();
aluHandleDisconnect(device);
V0(device->Backend,unlock)();
break;
}
res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
if(res != WAIT_OBJECT_0)
ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
}
CoUninitialize();
return 0;
}
static ALCenum ALCmmdevCapture_open(ALCmmdevCapture *self, const ALCchar *deviceName)
{
HRESULT hr = S_OK;
self->NotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
self->MsgEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(self->NotifyEvent == NULL || self->MsgEvent == NULL)
{
ERR("Failed to create message events: %lu\n", GetLastError());
hr = E_FAIL;
}
if(SUCCEEDED(hr))
{
if(deviceName)
{
const DevMap *iter;
if(VECTOR_SIZE(CaptureDevices) == 0)
{
ThreadRequest req = { self->MsgEvent, 0 };
if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, CAPTURE_DEVICE_PROBE))
(void)WaitForResponse(&req);
}
hr = E_FAIL;
#define MATCH_NAME(i) (al_string_cmp_cstr((i)->name, deviceName) == 0)
VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
if(iter == VECTOR_ITER_END(CaptureDevices))
WARN("Failed to find device name matching \"%s\"\n", deviceName);
else
{
self->devid = strdupW(iter->devid);
hr = S_OK;
}
#undef MATCH_NAME
}
}
if(SUCCEEDED(hr))
{
ThreadRequest req = { self->MsgEvent, 0 };
hr = E_FAIL;
if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
hr = WaitForResponse(&req);
else
ERR("Failed to post thread message: %lu\n", GetLastError());
}
if(FAILED(hr))
{
if(self->NotifyEvent != NULL)
CloseHandle(self->NotifyEvent);
self->NotifyEvent = NULL;
if(self->MsgEvent != NULL)
CloseHandle(self->MsgEvent);
self->MsgEvent = NULL;
free(self->devid);
self->devid = NULL;
ERR("Device init failed: 0x%08lx\n", hr);
return ALC_INVALID_VALUE;
}
else
{
ThreadRequest req = { self->MsgEvent, 0 };
hr = E_FAIL;
if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
hr = WaitForResponse(&req);
else
ERR("Failed to post thread message: %lu\n", GetLastError());
if(FAILED(hr))
{
ALCmmdevCapture_close(self);
if(hr == E_OUTOFMEMORY)
return ALC_OUT_OF_MEMORY;
return ALC_INVALID_VALUE;
}
}
return ALC_NO_ERROR;
}
static HRESULT ALCmmdevCapture_openProxy(ALCmmdevCapture *self)
{
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
void *ptr;
HRESULT hr;
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
if(SUCCEEDED(hr))
{
IMMDeviceEnumerator *Enumerator = ptr;
if(!self->devid)
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eCapture, eMultimedia, &self->mmdev);
else
hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev);
IMMDeviceEnumerator_Release(Enumerator);
Enumerator = NULL;
}
if(SUCCEEDED(hr))
hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
if(SUCCEEDED(hr))
{
self->client = ptr;
get_device_name(self->mmdev, &device->DeviceName);
}
if(FAILED(hr))
{
if(self->mmdev)
IMMDevice_Release(self->mmdev);
self->mmdev = NULL;
}
return hr;
}
static void ALCmmdevCapture_close(ALCmmdevCapture *self)
{
ThreadRequest req = { self->MsgEvent, 0 };
if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
(void)WaitForResponse(&req);
DestroyRingBuffer(self->Ring);
self->Ring = NULL;
CloseHandle(self->MsgEvent);
self->MsgEvent = NULL;
CloseHandle(self->NotifyEvent);
self->NotifyEvent = NULL;
free(self->devid);
self->devid = NULL;
}
static void ALCmmdevCapture_closeProxy(ALCmmdevCapture *self)
{
if(self->client)
IAudioClient_Release(self->client);
self->client = NULL;
if(self->mmdev)
IMMDevice_Release(self->mmdev);
self->mmdev = NULL;
}
static HRESULT ALCmmdevCapture_resetProxy(ALCmmdevCapture *self)
{
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
WAVEFORMATEXTENSIBLE OutputType;
REFERENCE_TIME buf_time;
UINT32 buffer_len;
void *ptr = NULL;
HRESULT hr;
if(self->client)
IAudioClient_Release(self->client);
self->client = NULL;
hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
if(FAILED(hr))
{
ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
return hr;
}
self->client = ptr;
buf_time = ((REFERENCE_TIME)device->UpdateSize*device->NumUpdates*10000000 +
device->Frequency-1) / device->Frequency;
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(device->FmtChans)
{
case DevFmtMono:
OutputType.Format.nChannels = 1;
OutputType.dwChannelMask = MONO;
break;
case DevFmtStereo:
OutputType.Format.nChannels = 2;
OutputType.dwChannelMask = STEREO;
break;
case DevFmtQuad:
OutputType.Format.nChannels = 4;
OutputType.dwChannelMask = QUAD;
break;
case DevFmtX51:
OutputType.Format.nChannels = 6;
OutputType.dwChannelMask = X5DOT1;
break;
case DevFmtX51Rear:
OutputType.Format.nChannels = 6;
OutputType.dwChannelMask = X5DOT1REAR;
break;
case DevFmtX61:
OutputType.Format.nChannels = 7;
OutputType.dwChannelMask = X6DOT1;
break;
case DevFmtX71:
OutputType.Format.nChannels = 8;
OutputType.dwChannelMask = X7DOT1;
break;
case DevFmtBFormat3D:
return E_FAIL;
}
switch(device->FmtType)
{
case DevFmtUByte:
OutputType.Format.wBitsPerSample = 8;
OutputType.Samples.wValidBitsPerSample = 8;
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
break;
case DevFmtShort:
OutputType.Format.wBitsPerSample = 16;
OutputType.Samples.wValidBitsPerSample = 16;
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
break;
case DevFmtInt:
OutputType.Format.wBitsPerSample = 32;
OutputType.Samples.wValidBitsPerSample = 32;
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
break;
case DevFmtFloat:
OutputType.Format.wBitsPerSample = 32;
OutputType.Samples.wValidBitsPerSample = 32;
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
return E_FAIL;
}
OutputType.Format.nSamplesPerSec = device->Frequency;
OutputType.Format.nBlockAlign = OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
hr = IAudioClient_IsFormatSupported(self->client,
AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, NULL
);
if(FAILED(hr))
{
ERR("Failed to check format support: 0x%08lx\n", hr);
return hr;
}
hr = IAudioClient_Initialize(self->client,
AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buf_time, 0, &OutputType.Format, NULL
);
if(FAILED(hr))
{
ERR("Failed to initialize audio client: 0x%08lx\n", hr);
return hr;
}
hr = IAudioClient_GetBufferSize(self->client, &buffer_len);
if(FAILED(hr))
{
ERR("Failed to get buffer size: 0x%08lx\n", hr);
return hr;
}
buffer_len = maxu(device->UpdateSize*device->NumUpdates, buffer_len);
self->Ring = CreateRingBuffer(OutputType.Format.nBlockAlign, buffer_len);
if(!self->Ring)
{
ERR("Failed to allocate capture ring buffer\n");
return E_OUTOFMEMORY;
}
hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent);
if(FAILED(hr))
{
ERR("Failed to set event handle: 0x%08lx\n", hr);
return hr;
}
return hr;
}
static ALCboolean ALCmmdevCapture_start(ALCmmdevCapture *self)
{
ThreadRequest req = { self->MsgEvent, 0 };
HRESULT hr = E_FAIL;
if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
hr = WaitForResponse(&req);
return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
}
static HRESULT ALCmmdevCapture_startProxy(ALCmmdevCapture *self)
{
HRESULT hr;
void *ptr;
ResetEvent(self->NotifyEvent);
hr = IAudioClient_Start(self->client);
if(FAILED(hr))
{
ERR("Failed to start audio client: 0x%08lx\n", hr);
return hr;
}
hr = IAudioClient_GetService(self->client, &IID_IAudioCaptureClient, &ptr);
if(SUCCEEDED(hr))
{
self->capture = ptr;
self->killNow = 0;
if(althrd_create(&self->thread, ALCmmdevCapture_recordProc, self) != althrd_success)
{
ERR("Failed to start thread\n");
IAudioCaptureClient_Release(self->capture);
self->capture = NULL;
hr = E_FAIL;
}
}
if(FAILED(hr))
{
IAudioClient_Stop(self->client);
IAudioClient_Reset(self->client);
}
return hr;
}
static void ALCmmdevCapture_stop(ALCmmdevCapture *self)
{
ThreadRequest req = { self->MsgEvent, 0 };
if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self)))
(void)WaitForResponse(&req);
}
static void ALCmmdevCapture_stopProxy(ALCmmdevCapture *self)
{
int res;
if(!self->capture)
return;
self->killNow = 1;
althrd_join(self->thread, &res);
IAudioCaptureClient_Release(self->capture);
self->capture = NULL;
IAudioClient_Stop(self->client);
IAudioClient_Reset(self->client);
}
ALuint ALCmmdevCapture_availableSamples(ALCmmdevCapture *self)
{
return RingBufferSize(self->Ring);
}
ALCenum ALCmmdevCapture_captureSamples(ALCmmdevCapture *self, ALCvoid *buffer, ALCuint samples)
{
if(ALCmmdevCapture_availableSamples(self) < samples)
return ALC_INVALID_VALUE;
ReadRingBuffer(self->Ring, buffer, samples);
return ALC_NO_ERROR;
}
static inline void AppendAllDevicesList2(const DevMap *entry)
{ AppendAllDevicesList(al_string_get_cstr(entry->name)); }
static inline void AppendCaptureDeviceList2(const DevMap *entry)
@@ -1175,7 +1713,7 @@ static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory* UNUSED(self))
static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory* UNUSED(self), ALCbackend_Type type)
{
if(type == ALCbackend_Playback)
if(type == ALCbackend_Playback || type == ALCbackend_Capture)
return ALC_TRUE;
return ALC_FALSE;
}
@@ -1221,6 +1759,18 @@ static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory*
return STATIC_CAST(ALCbackend, backend);
}
if(type == ALCbackend_Capture)
{
ALCmmdevCapture *backend;
backend = ALCmmdevCapture_New(sizeof(*backend));
if(!backend) return NULL;
memset(backend, 0, sizeof(*backend));
ALCmmdevCapture_Construct(backend, device);
return STATIC_CAST(ALCbackend, backend);
}
return NULL;
}
+1
View File
@@ -55,6 +55,7 @@ DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
#ifdef HAVE_MMDEVAPI
#include <devpropdef.h>