aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/backends/mmdevapi.c552
-rw-r--r--Alc/helpers.c1
2 files changed, 552 insertions, 1 deletions
diff --git a/Alc/backends/mmdevapi.c b/Alc/backends/mmdevapi.c
index d2a8596f..2e7f4e7b 100644
--- a/Alc/backends/mmdevapi.c
+++ b/Alc/backends/mmdevapi.c
@@ -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;
}
diff --git a/Alc/helpers.c b/Alc/helpers.c
index 9a8f4297..8811f3c3 100644
--- a/Alc/helpers.c
+++ b/Alc/helpers.c
@@ -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>