diff options
Diffstat (limited to 'Alc/backends/mmdevapi.c')
-rw-r--r-- | Alc/backends/mmdevapi.c | 805 |
1 files changed, 734 insertions, 71 deletions
diff --git a/Alc/backends/mmdevapi.c b/Alc/backends/mmdevapi.c index d732c3e1..e8563d33 100644 --- a/Alc/backends/mmdevapi.c +++ b/Alc/backends/mmdevapi.c @@ -13,8 +13,8 @@ * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ @@ -51,34 +51,36 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); +DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) -#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) -#define X5DOT1SIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) + +#define DEVNAME_TAIL " on OpenAL Soft" typedef struct { al_string name; WCHAR *devid; } DevMap; -DECL_VECTOR(DevMap) +TYPEDEF_VECTOR(DevMap, vector_DevMap) static void clear_devlist(vector_DevMap *list) { - DevMap *iter, *end; - - iter = VECTOR_ITER_BEGIN(*list); - end = VECTOR_ITER_END(*list); - for(;iter != end;iter++) - { - AL_STRING_DEINIT(iter->name); - free(iter->devid); - } +#define CLEAR_DEVMAP(i) do { \ + AL_STRING_DEINIT((i)->name); \ + free((i)->devid); \ + (i)->devid = NULL; \ +} while(0) + VECTOR_FOR_EACH(DevMap, *list, CLEAR_DEVMAP); VECTOR_RESIZE(*list, 0); +#undef CLEAR_DEVMAP } static vector_DevMap PlaybackDevices; @@ -134,39 +136,105 @@ static void get_device_name(IMMDevice *device, al_string *name) hr = IPropertyStore_GetValue(ps, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pvname); if(FAILED(hr)) - WARN("GetValue failed: 0x%08lx\n", hr); - else + WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); + else if(pvname.vt == VT_LPWSTR) al_string_copy_wcstr(name, pvname.pwszVal); + else + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvname.vt); PropVariantClear(&pvname); IPropertyStore_Release(ps); } -static void add_device(IMMDevice *device, vector_DevMap *list) +static void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) { - LPWSTR devid; + IPropertyStore *ps; + PROPVARIANT pvform; HRESULT hr; - hr = IMMDevice_GetId(device, &devid); - if(SUCCEEDED(hr)) + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps); + if(FAILED(hr)) { - DevMap entry; - AL_STRING_INIT(entry.name); + WARN("OpenPropertyStore failed: 0x%08lx\n", hr); + return; + } + + PropVariantInit(&pvform); + + hr = IPropertyStore_GetValue(ps, &PKEY_AudioEndpoint_FormFactor, &pvform); + if(FAILED(hr)) + WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); + else if(pvform.vt == VT_UI4) + *formfactor = pvform.ulVal; + else if(pvform.vt == VT_EMPTY) + *formfactor = UnknownFormFactor; + else + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.vt); + + PropVariantClear(&pvform); + IPropertyStore_Release(ps); +} + + +static void add_device(IMMDevice *device, LPCWSTR devid, vector_DevMap *list) +{ + int count = 0; + al_string tmpname; + DevMap entry; + + AL_STRING_INIT(tmpname); + AL_STRING_INIT(entry.name); + + entry.devid = strdupW(devid); + get_device_name(device, &tmpname); + + while(1) + { + const DevMap *iter; + + al_string_copy(&entry.name, tmpname); + if(count == 0) + al_string_append_cstr(&entry.name, DEVNAME_TAIL); + else + { + char str[64]; + snprintf(str, sizeof(str), " #%d"DEVNAME_TAIL, count+1); + al_string_append_cstr(&entry.name, str); + } + +#define MATCH_ENTRY(i) (al_string_cmp(entry.name, (i)->name) == 0) + VECTOR_FIND_IF(iter, const DevMap, *list, MATCH_ENTRY); + if(iter == VECTOR_ITER_END(*list)) break; +#undef MATCH_ENTRY + count++; + } - entry.devid = strdupW(devid); - get_device_name(device, &entry.name); + TRACE("Got device \"%s\", \"%ls\"\n", al_string_get_cstr(entry.name), entry.devid); + VECTOR_PUSH_BACK(*list, entry); - CoTaskMemFree(devid); + AL_STRING_DEINIT(tmpname); +} + +static LPWSTR get_device_id(IMMDevice *device) +{ + LPWSTR devid; + HRESULT hr; - TRACE("Got device \"%s\", \"%ls\"\n", al_string_get_cstr(entry.name), entry.devid); - VECTOR_PUSH_BACK(*list, entry); + hr = IMMDevice_GetId(device, &devid); + if(FAILED(hr)) + { + ERR("Failed to get device id: %lx\n", hr); + return NULL; } + + return devid; } static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, vector_DevMap *list) { IMMDeviceCollection *coll; IMMDevice *defdev = NULL; + LPWSTR defdevid = NULL; HRESULT hr; UINT count; UINT i; @@ -183,7 +251,7 @@ static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, ve if(SUCCEEDED(hr) && count > 0) { clear_devlist(list); - if(!VECTOR_RESERVE(*list, count+1)) + if(!VECTOR_RESERVE(*list, count)) { IMMDeviceCollection_Release(coll); return E_OUTOFMEMORY; @@ -193,22 +261,32 @@ static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, ve eMultimedia, &defdev); } if(SUCCEEDED(hr) && defdev != NULL) - add_device(defdev, list); + { + defdevid = get_device_id(defdev); + if(defdevid) + add_device(defdev, defdevid, list); + } for(i = 0;i < count;++i) { IMMDevice *device; + LPWSTR devid; - if(FAILED(IMMDeviceCollection_Item(coll, i, &device))) - continue; - - if(device != defdev) - add_device(device, list); + hr = IMMDeviceCollection_Item(coll, i, &device); + if(FAILED(hr)) continue; + devid = get_device_id(device); + if(devid) + { + if(wcscmp(devid, defdevid) != 0) + add_device(device, devid, list); + CoTaskMemFree(devid); + } IMMDevice_Release(device); } if(defdev) IMMDevice_Release(defdev); + if(defdevid) CoTaskMemFree(defdevid); IMMDeviceCollection_Release(coll); return S_OK; @@ -294,7 +372,7 @@ static DWORD CALLBACK ALCmmdevProxy_messageHandler(void *ptr) TRACE("Starting message loop\n"); while(GetMessage(&msg, NULL, WM_USER_First, WM_USER_Last)) { - TRACE("Got message %u\n", msg.message); + TRACE("Got message %u (lparam=%p, wparam=%p)\n", msg.message, (void*)msg.lParam, (void*)msg.wParam); switch(msg.message) { case WM_USER_OpenDevice: @@ -483,9 +561,9 @@ FORCE_ALIGN static int ALCmmdevPlayback_mixerProc(void *arg) if(FAILED(hr)) { ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr); - ALCdevice_Lock(device); + V0(device->Backend,lock)(); aluHandleDisconnect(device); - ALCdevice_Unlock(device); + V0(device->Backend,unlock)(); return 1; } @@ -500,9 +578,9 @@ FORCE_ALIGN static int ALCmmdevPlayback_mixerProc(void *arg) if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); - ALCdevice_Lock(device); + V0(device->Backend,lock)(); aluHandleDisconnect(device); - ALCdevice_Unlock(device); + V0(device->Backend,unlock)(); break; } self->Padding = written; @@ -521,18 +599,18 @@ FORCE_ALIGN static int ALCmmdevPlayback_mixerProc(void *arg) hr = IAudioRenderClient_GetBuffer(self->render, len, &buffer); if(SUCCEEDED(hr)) { - ALCdevice_Lock(device); + V0(device->Backend,lock)(); aluMixData(device, buffer, len); self->Padding = written + len; - ALCdevice_Unlock(device); + V0(device->Backend,unlock)(); hr = IAudioRenderClient_ReleaseBuffer(self->render, len, 0); } if(FAILED(hr)) { ERR("Failed to buffer data: 0x%08lx\n", hr); - ALCdevice_Lock(device); + V0(device->Backend,lock)(); aluHandleDisconnect(device); - ALCdevice_Unlock(device); + V0(device->Backend,unlock)(); break; } } @@ -599,7 +677,7 @@ static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *devi { if(deviceName) { - const DevMap *iter, *end; + const DevMap *iter; if(VECTOR_SIZE(PlaybackDevices) == 0) { @@ -609,19 +687,18 @@ static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *devi } hr = E_FAIL; - iter = VECTOR_ITER_BEGIN(PlaybackDevices); - end = VECTOR_ITER_END(PlaybackDevices); - for(;iter != end;iter++) +#define MATCH_NAME(i) (al_string_cmp_cstr((i)->name, deviceName) == 0) + VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME); + if(iter == VECTOR_ITER_END(PlaybackDevices)) + WARN("Failed to find device name matching \"%s\"\n", deviceName); + else { - if(al_string_cmp_cstr(iter->name, deviceName) == 0) - { - self->devid = strdupW(iter->devid); - hr = S_OK; - break; - } + ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice; + self->devid = strdupW(iter->devid); + al_string_copy(&device->DeviceName, iter->name); + hr = S_OK; } - if(FAILED(hr)) - WARN("Failed to find device name matching \"%s\"\n", deviceName); +#undef MATCH_NAME } } @@ -677,7 +754,11 @@ static HRESULT ALCmmdevPlayback_openProxy(ALCmmdevPlayback *self) if(SUCCEEDED(hr)) { self->client = ptr; - get_device_name(self->mmdev, &device->DeviceName); + if(al_string_empty(device->DeviceName)) + { + get_device_name(self->mmdev, &device->DeviceName); + al_string_append_cstr(&device->DeviceName, DEVNAME_TAIL); + } } if(FAILED(hr)) @@ -734,6 +815,7 @@ static ALCboolean ALCmmdevPlayback_reset(ALCmmdevPlayback *self) static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) { ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + EndpointFormFactor formfactor = UnknownFormFactor; WAVEFORMATEXTENSIBLE OutputType; WAVEFORMATEX *wfx = NULL; REFERENCE_TIME min_per, buf_time; @@ -783,11 +865,11 @@ static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) device->FmtChans = DevFmtQuad; else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) device->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1SIDE) - device->FmtChans = DevFmtX51Side; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) + device->FmtChans = DevFmtX51Rear; else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) device->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1) + else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) device->FmtChans = DevFmtX71; else ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); @@ -799,6 +881,9 @@ static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) OutputType.Format.nChannels = 1; OutputType.dwChannelMask = MONO; break; + case DevFmtBFormat3D: + device->FmtChans = DevFmtStereo; + /*fall-through*/ case DevFmtStereo: OutputType.Format.nChannels = 2; OutputType.dwChannelMask = STEREO; @@ -811,9 +896,9 @@ static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) OutputType.Format.nChannels = 6; OutputType.dwChannelMask = X5DOT1; break; - case DevFmtX51Side: + case DevFmtX51Rear: OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1SIDE; + OutputType.dwChannelMask = X5DOT1REAR; break; case DevFmtX61: OutputType.Format.nChannels = 7; @@ -894,11 +979,11 @@ static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) device->FmtChans = DevFmtQuad; else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) device->FmtChans = DevFmtX51; - else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1SIDE) - device->FmtChans = DevFmtX51Side; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) + device->FmtChans = DevFmtX51Rear; else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) device->FmtChans = DevFmtX61; - else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1) + else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) device->FmtChans = DevFmtX71; else { @@ -936,6 +1021,8 @@ static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) } OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; } + get_device_formfactor(self->mmdev, &formfactor); + device->IsHeadphones = (device->FmtChans == DevFmtStereo && formfactor == Headphones); SetDefaultWFXChannelOrder(device); @@ -1055,6 +1142,575 @@ 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; + + ll_ringbuffer_t *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) +{ + ll_ringbuffer_free(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(), RECORD_THREAD_NAME); + + 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 numsamples; + DWORD flags; + 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; + } + + ll_ringbuffer_write(self->Ring, (char*)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 + { + ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice; + self->devid = strdupW(iter->devid); + al_string_copy(&device->DeviceName, iter->name); + 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; + if(al_string_empty(device->DeviceName)) + { + get_device_name(self->mmdev, &device->DeviceName); + al_string_append_cstr(&device->DeviceName, DEVNAME_TAIL); + } + } + + 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); + + ll_ringbuffer_free(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; + WAVEFORMATEX *wfx = NULL; + 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; + OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format); + + hr = IAudioClient_IsFormatSupported(self->client, + AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx + ); + if(FAILED(hr)) + { + ERR("Failed to check format support: 0x%08lx\n", hr); + return hr; + } + + /* FIXME: We should do conversion/resampling if we didn't get a matching format. */ + if(wfx->nSamplesPerSec != OutputType.Format.nSamplesPerSec || + wfx->wBitsPerSample != OutputType.Format.wBitsPerSample || + wfx->nChannels != OutputType.Format.nChannels || + wfx->nBlockAlign != OutputType.Format.nBlockAlign) + { + ERR("Did not get matching format, wanted: %s %s %uhz, got: %d channel(s) %d-bit %luhz\n", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->Frequency, + wfx->nChannels, wfx->wBitsPerSample, wfx->nSamplesPerSec); + CoTaskMemFree(wfx); + return E_FAIL; + } + + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = NULL; + + 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 + 1, buffer_len); + ll_ringbuffer_free(self->Ring); + self->Ring = ll_ringbuffer_create(buffer_len, OutputType.Format.nBlockAlign); + 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 (ALuint)ll_ringbuffer_read_space(self->Ring); +} + +ALCenum ALCmmdevCapture_captureSamples(ALCmmdevCapture *self, ALCvoid *buffer, ALCuint samples) +{ + if(ALCmmdevCapture_availableSamples(self) < samples) + return ALC_INVALID_VALUE; + ll_ringbuffer_read(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) @@ -1125,7 +1781,12 @@ static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory* UNUSED(self)) static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory* UNUSED(self), ALCbackend_Type type) { - if(type == ALCbackend_Playback) + /* TODO: Disable capture with mmdevapi for now, since it doesn't do any + * rechanneling or resampling; if the device is configured for 48000hz + * stereo input, for example, and the app asks for 22050hz mono, + * initialization will fail. + */ + if(type == ALCbackend_Playback /*|| type == ALCbackend_Capture*/) return ALC_TRUE; return ALC_FALSE; } @@ -1162,13 +1823,15 @@ static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory* if(type == ALCbackend_Playback) { ALCmmdevPlayback *backend; - - backend = ALCmmdevPlayback_New(sizeof(*backend)); + NEW_OBJ(backend, ALCmmdevPlayback)(device); + if(!backend) return NULL; + return STATIC_CAST(ALCbackend, backend); + } + if(type == ALCbackend_Capture) + { + ALCmmdevCapture *backend; + NEW_OBJ(backend, ALCmmdevCapture)(device); if(!backend) return NULL; - memset(backend, 0, sizeof(*backend)); - - ALCmmdevPlayback_Construct(backend, device); - return STATIC_CAST(ALCbackend, backend); } |