/* * Copyright 2011-2012 Maarten Lankhorst * Copyright 2010-2011 Maarten Lankhorst for CodeWeavers * Copyright 2011 Andrew Eikum for CodeWeavers * Copyright 2022 Huw Davies * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define COBJMACROS #include #include #include #include #include #include #include "mmdevapi_private.h" WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); typedef struct tagLANGANDCODEPAGE { WORD wLanguage; WORD wCodePage; } LANGANDCODEPAGE; extern void sessions_lock(void); extern void sessions_unlock(void); extern HRESULT get_audio_session(const GUID *sessionguid, IMMDevice *device, UINT channels, struct audio_session **out); extern struct audio_session_wrapper *session_wrapper_create(struct audio_client *client); static HANDLE main_loop_thread; void main_loop_stop(void) { if (main_loop_thread) { WaitForSingleObject(main_loop_thread, INFINITE); CloseHandle(main_loop_thread); } } void set_stream_volumes(struct audio_client *This) { struct set_volumes_params params; params.stream = This->stream; params.master_volume = (This->session->mute ? 0.0f : This->session->master_vol); params.volumes = This->vols; params.session_volumes = This->session->channel_vols; wine_unix_call(set_volumes, ¶ms); } static inline struct audio_client *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioCaptureClient_iface); } static inline struct audio_client *impl_from_IAudioClient3(IAudioClient3 *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioClient3_iface); } static inline struct audio_client *impl_from_IAudioClock(IAudioClock *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioClock_iface); } static inline struct audio_client *impl_from_IAudioClock2(IAudioClock2 *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioClock2_iface); } static inline struct audio_client *impl_from_IAudioRenderClient(IAudioRenderClient *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioRenderClient_iface); } static inline struct audio_client *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) { return CONTAINING_RECORD(iface, struct audio_client, IAudioStreamVolume_iface); } static HRESULT get_periods(struct audio_client *client, REFERENCE_TIME *def_period, REFERENCE_TIME *min_period) { static const REFERENCE_TIME min_def_period = 100000; /* 10 ms */ struct get_device_period_params params; params.device = client->device_name; params.flow = client->dataflow; params.def_period = def_period; params.min_period = min_period; wine_unix_call(get_device_period, ¶ms); if (def_period) *def_period = max(*def_period, min_def_period); return params.result; } static HRESULT adjust_timing(struct audio_client *client, const BOOLEAN force_def_period, REFERENCE_TIME *duration, REFERENCE_TIME *period, const AUDCLNT_SHAREMODE mode, const DWORD flags, const WAVEFORMATEX *fmt) { REFERENCE_TIME def_period, min_period; HRESULT hr; TRACE("Requested duration %lu and period %lu\n", (ULONG)*duration, (ULONG)*period); if (FAILED(hr = get_periods(client, &def_period, &min_period))) return hr; TRACE("Device periods: %lu default and %lu minimum\n", (ULONG)def_period, (ULONG)min_period); if (mode == AUDCLNT_SHAREMODE_SHARED) { if (*period == 0 || force_def_period) *period = def_period; else if (*period < min_period) return AUDCLNT_E_INVALID_DEVICE_PERIOD; if (*duration < 3 * *period) *duration = 3 * *period; } else { const WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE *)fmt; if (fmtex->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && (fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED)) return AUDCLNT_E_UNSUPPORTED_FORMAT; else { if (*period == 0) *period = def_period; if (*period < min_period || *period > 5000000) return AUDCLNT_E_INVALID_DEVICE_PERIOD; else if (*duration > 20000000) /* The smaller the period, the lower this limit. */ return AUDCLNT_E_BUFFER_SIZE_ERROR; else if (flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) { if (*duration != *period) return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL; FIXME("EXCLUSIVE mode with EVENTCALLBACK\n"); return AUDCLNT_E_DEVICE_IN_USE; } else if (*duration < 8 * *period) *duration = 8 * *period; /* May grow above 2s. */ } } TRACE("Adjusted duration %lu and period %lu\n", (ULONG)*duration, (ULONG)*period); return hr; } static void dump_fmt(const WAVEFORMATEX *fmt) { TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); switch (fmt->wFormatTag) { case WAVE_FORMAT_PCM: TRACE("WAVE_FORMAT_PCM"); break; case WAVE_FORMAT_IEEE_FLOAT: TRACE("WAVE_FORMAT_IEEE_FLOAT"); break; case WAVE_FORMAT_EXTENSIBLE: TRACE("WAVE_FORMAT_EXTENSIBLE"); break; default: TRACE("Unknown"); break; } TRACE(")\n"); TRACE("nChannels: %u\n", fmt->nChannels); TRACE("nSamplesPerSec: %lu\n", fmt->nSamplesPerSec); TRACE("nAvgBytesPerSec: %lu\n", fmt->nAvgBytesPerSec); TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); TRACE("cbSize: %u\n", fmt->cbSize); if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { WAVEFORMATEXTENSIBLE *fmtex = (void *)fmt; TRACE("dwChannelMask: %08lx\n", fmtex->dwChannelMask); TRACE("Samples: %04x\n", fmtex->Samples.wReserved); TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); } } static DWORD CALLBACK main_loop_func(void *event) { struct main_loop_params params; SetThreadDescription(GetCurrentThread(), L"audio_client_main"); params.event = event; wine_unix_call(main_loop, ¶ms); return 0; } HRESULT main_loop_start(void) { if (!main_loop_thread) { HANDLE event = CreateEventW(NULL, TRUE, FALSE, NULL); if (!(main_loop_thread = CreateThread(NULL, 0, main_loop_func, event, 0, NULL))) { ERR("Failed to create main loop thread\n"); CloseHandle(event); return E_FAIL; } SetThreadPriority(main_loop_thread, THREAD_PRIORITY_TIME_CRITICAL); WaitForSingleObject(event, INFINITE); CloseHandle(event); } return S_OK; } static DWORD CALLBACK timer_loop_func(void *user) { struct timer_loop_params params; struct audio_client *This = user; SetThreadDescription(GetCurrentThread(), L"audio_client_timer"); params.stream = This->stream; wine_unix_call(timer_loop, ¶ms); return 0; } HRESULT stream_release(stream_handle stream, HANDLE timer_thread) { struct release_stream_params params; params.stream = stream; params.timer_thread = timer_thread; wine_unix_call(release_stream, ¶ms); return params.result; } static BOOL query_productname(void *data, LANGANDCODEPAGE *lang, LPVOID *buffer, UINT *len) { WCHAR pn[37]; swprintf(pn, ARRAY_SIZE(pn), L"\\StringFileInfo\\%04x%04x\\ProductName", lang->wLanguage, lang->wCodePage); return VerQueryValueW(data, pn, buffer, len) && *len; } WCHAR *get_application_name(void) { WCHAR path[MAX_PATH], *name; UINT translate_size, productname_size; LANGANDCODEPAGE *translate; LPVOID productname; BOOL found = FALSE; void *data = NULL; unsigned int i; LCID locale; DWORD size; GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); size = GetFileVersionInfoSizeW(path, NULL); if (!size) goto skip; data = malloc(size); if (!data) goto skip; if (!GetFileVersionInfoW(path, 0, size, data)) goto skip; if (!VerQueryValueW(data, L"\\VarFileInfo\\Translation", (LPVOID *)&translate, &translate_size)) goto skip; /* No translations found. */ if (translate_size < sizeof(LANGANDCODEPAGE)) goto skip; /* The following code will try to find the best translation. We first search for an * exact match of the language, then a match of the language PRIMARYLANGID, then we * search for a LANG_NEUTRAL match, and if that still doesn't work we pick the * first entry which contains a proper productname. */ locale = GetThreadLocale(); for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { if (translate[i].wLanguage == locale && query_productname(data, &translate[i], &productname, &productname_size)) { found = TRUE; break; } } if (!found) { for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { if (PRIMARYLANGID(translate[i].wLanguage) == PRIMARYLANGID(locale) && query_productname(data, &translate[i], &productname, &productname_size)) { found = TRUE; break; } } } if (!found) { for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { if (PRIMARYLANGID(translate[i].wLanguage) == LANG_NEUTRAL && query_productname(data, &translate[i], &productname, &productname_size)) { found = TRUE; break; } } } if (!found) { for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { if (query_productname(data, &translate[i], &productname, &productname_size)) { found = TRUE; break; } } } skip: if (found) { name = wcsdup(productname); free(data); return name; } free(data); name = wcsrchr(path, '\\'); if (!name) name = path; else name++; return wcsdup(name); } static HRESULT stream_init(struct audio_client *client, const BOOLEAN force_def_period, const AUDCLNT_SHAREMODE mode, const DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, const WAVEFORMATEX *fmt, const GUID *sessionguid) { struct create_stream_params params; UINT32 i, channel_count; stream_handle stream; WCHAR *name; if (!fmt) return E_POINTER; dump_fmt(fmt); if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) return E_INVALIDARG; if (flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_RATEADJUST | AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)) { FIXME("Unknown flags: %08lx\n", flags); return E_INVALIDARG; } if (FAILED(params.result = adjust_timing(client, force_def_period, &duration, &period, mode, flags, fmt))) return params.result; sessions_lock(); if (client->stream) { sessions_unlock(); return AUDCLNT_E_ALREADY_INITIALIZED; } if (FAILED(params.result = main_loop_start())) { sessions_unlock(); return params.result; } params.name = name = get_application_name(); params.device = client->device_name; params.flow = client->dataflow; params.share = mode; params.flags = flags; params.duration = duration; params.period = period; params.fmt = fmt; params.channel_count = &channel_count; params.stream = &stream; wine_unix_call(create_stream, ¶ms); free(name); if (FAILED(params.result)) { sessions_unlock(); return params.result; } if (!(client->vols = malloc(channel_count * sizeof(*client->vols)))) { params.result = E_OUTOFMEMORY; goto exit; } for (i = 0; i < channel_count; i++) client->vols[i] = 1.f; params.result = get_audio_session(sessionguid, client->parent, channel_count, &client->session); exit: if (FAILED(params.result)) { stream_release(stream, NULL); free(client->vols); client->vols = NULL; } else { list_add_tail(&client->session->clients, &client->entry); client->stream = stream; client->channel_count = channel_count; set_stream_volumes(client); } sessions_unlock(); return params.result; } static HRESULT WINAPI capture_QueryInterface(IAudioCaptureClient *iface, REFIID riid, void **ppv) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioCaptureClient)) *ppv = iface; else if (IsEqualIID(riid, &IID_IMarshal)) { return IUnknown_QueryInterface(This->marshal, riid, ppv); } else { *ppv = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI capture_AddRef(IAudioCaptureClient *iface) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); return IAudioClient3_AddRef(&This->IAudioClient3_iface); } static ULONG WINAPI capture_Release(IAudioCaptureClient *iface) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); return IAudioClient3_Release(&This->IAudioClient3_iface); } static HRESULT WINAPI capture_GetBuffer(IAudioCaptureClient *iface, BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, UINT64 *qpcpos) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); struct get_capture_buffer_params params; TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, devpos, qpcpos); if (!data) return E_POINTER; *data = NULL; if (!frames || !flags) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.data = data; params.frames = frames; params.flags = (UINT *)flags; params.devpos = devpos; params.qpcpos = qpcpos; wine_unix_call(get_capture_buffer, ¶ms); return params.result; } static HRESULT WINAPI capture_ReleaseBuffer(IAudioCaptureClient *iface, UINT32 done) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); struct release_capture_buffer_params params; TRACE("(%p)->(%u)\n", This, done); if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.done = done; wine_unix_call(release_capture_buffer, ¶ms); return params.result; } static HRESULT WINAPI capture_GetNextPacketSize(IAudioCaptureClient *iface, UINT32 *frames) { struct audio_client *This = impl_from_IAudioCaptureClient(iface); struct get_next_packet_size_params params; TRACE("(%p)->(%p)\n", This, frames); if (!frames) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.frames = frames; wine_unix_call(get_next_packet_size, ¶ms); return params.result; } const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = { capture_QueryInterface, capture_AddRef, capture_Release, capture_GetBuffer, capture_ReleaseBuffer, capture_GetNextPacketSize }; static HRESULT WINAPI client_QueryInterface(IAudioClient3 *iface, REFIID riid, void **ppv) { TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClient) || IsEqualIID(riid, &IID_IAudioClient2) || IsEqualIID(riid, &IID_IAudioClient3)) *ppv = iface; else if(IsEqualIID(riid, &IID_IMarshal)) { struct audio_client *This = impl_from_IAudioClient3(iface); return IUnknown_QueryInterface(This->marshal, riid, ppv); } else { *ppv = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI client_AddRef(IAudioClient3 *iface) { struct audio_client *This = impl_from_IAudioClient3(iface); ULONG ref = InterlockedIncrement(&This->ref); TRACE("(%p) Refcount now %lu\n", This, ref); return ref; } static ULONG WINAPI client_Release(IAudioClient3 *iface) { struct audio_client *This = impl_from_IAudioClient3(iface); ULONG ref = InterlockedDecrement(&This->ref); TRACE("(%p) Refcount now %lu\n", This, ref); if (!ref) { IAudioClient3_Stop(iface); IMMDevice_Release(This->parent); IUnknown_Release(This->marshal); if (This->session) { sessions_lock(); list_remove(&This->entry); sessions_unlock(); } free(This->vols); if (This->stream) stream_release(This->stream, This->timer_thread); free(This); } return ref; } static HRESULT WINAPI client_Initialize(IAudioClient3 *iface, AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, REFERENCE_TIME period, const WAVEFORMATEX *fmt, const GUID *sessionguid) { struct audio_client *This = impl_from_IAudioClient3(iface); TRACE("(%p)->(%x, %lx, %s, %s, %p, %s)\n", This, mode, flags, wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); return stream_init(This, TRUE, mode, flags, duration, period, fmt, sessionguid); } static HRESULT WINAPI client_GetBufferSize(IAudioClient3 *iface, UINT32 *out) { struct audio_client *This = impl_from_IAudioClient3(iface); struct get_buffer_size_params params; TRACE("(%p)->(%p)\n", This, out); if (!out) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.frames = out; wine_unix_call(get_buffer_size, ¶ms); return params.result; } static HRESULT WINAPI client_GetStreamLatency(IAudioClient3 *iface, REFERENCE_TIME *latency) { struct audio_client *This = impl_from_IAudioClient3(iface); struct get_latency_params params; TRACE("(%p)->(%p)\n", This, latency); if (!latency) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.latency = latency; wine_unix_call(get_latency, ¶ms); return params.result; } static HRESULT WINAPI client_GetCurrentPadding(IAudioClient3 *iface, UINT32 *out) { struct audio_client *This = impl_from_IAudioClient3(iface); struct get_current_padding_params params; TRACE("(%p)->(%p)\n", This, out); if (!out) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.padding = out; wine_unix_call(get_current_padding, ¶ms); return params.result; } static HRESULT WINAPI client_IsFormatSupported(IAudioClient3 *iface, AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, WAVEFORMATEX **out) { struct audio_client *This = impl_from_IAudioClient3(iface); struct is_format_supported_params params; TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); if (fmt) dump_fmt(fmt); params.device = This->device_name; params.flow = This->dataflow; params.share = mode; params.fmt_in = fmt; params.fmt_out = NULL; if (out) { *out = NULL; if (mode == AUDCLNT_SHAREMODE_SHARED) params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); } wine_unix_call(is_format_supported, ¶ms); if (params.result == S_FALSE) *out = ¶ms.fmt_out->Format; else CoTaskMemFree(params.fmt_out); return params.result; } static HRESULT WINAPI client_GetMixFormat(IAudioClient3 *iface, WAVEFORMATEX **pwfx) { struct audio_client *This = impl_from_IAudioClient3(iface); struct get_mix_format_params params; TRACE("(%p)->(%p)\n", This, pwfx); if (!pwfx) return E_POINTER; *pwfx = NULL; params.device = This->device_name; params.flow = This->dataflow; params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); if (!params.fmt) return E_OUTOFMEMORY; wine_unix_call(get_mix_format, ¶ms); if (SUCCEEDED(params.result)) { *pwfx = ¶ms.fmt->Format; dump_fmt(*pwfx); } else CoTaskMemFree(params.fmt); return params.result; } static HRESULT WINAPI client_GetDevicePeriod(IAudioClient3 *iface, REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) { struct audio_client *This = impl_from_IAudioClient3(iface); TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); if (!defperiod && !minperiod) return E_POINTER; return get_periods(This, defperiod, minperiod); } static HRESULT WINAPI client_Start(IAudioClient3 *iface) { struct audio_client *This = impl_from_IAudioClient3(iface); struct start_params params; TRACE("(%p)\n", This); sessions_lock(); if (!This->stream) { sessions_unlock(); return AUDCLNT_E_NOT_INITIALIZED; } params.stream = This->stream; wine_unix_call(start, ¶ms); if (SUCCEEDED(params.result) && !This->timer_thread) { if ((This->timer_thread = CreateThread(NULL, 0, timer_loop_func, This, 0, NULL))) SetThreadPriority(This->timer_thread, THREAD_PRIORITY_TIME_CRITICAL); else { IAudioClient3_Stop(&This->IAudioClient3_iface); params.result = E_FAIL; } } sessions_unlock(); return params.result; } static HRESULT WINAPI client_Stop(IAudioClient3 *iface) { struct audio_client *This = impl_from_IAudioClient3(iface); struct stop_params params; TRACE("(%p)\n", This); if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; wine_unix_call(stop, ¶ms); return params.result; } static HRESULT WINAPI client_Reset(IAudioClient3 *iface) { struct audio_client *This = impl_from_IAudioClient3(iface); struct reset_params params; TRACE("(%p)\n", This); if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; wine_unix_call(reset, ¶ms); return params.result; } static HRESULT WINAPI client_SetEventHandle(IAudioClient3 *iface, HANDLE event) { struct audio_client *This = impl_from_IAudioClient3(iface); struct set_event_handle_params params; TRACE("(%p)->(%p)\n", This, event); if (!event) return E_INVALIDARG; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.event = event; wine_unix_call(set_event_handle, ¶ms); return params.result; } static HRESULT WINAPI client_GetService(IAudioClient3 *iface, REFIID riid, void **ppv) { struct audio_client *This = impl_from_IAudioClient3(iface); HRESULT hr; TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; *ppv = NULL; sessions_lock(); if (!This->stream) { hr = AUDCLNT_E_NOT_INITIALIZED; goto exit; } if (IsEqualIID(riid, &IID_IAudioRenderClient)) { if (This->dataflow != eRender) { hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE; goto exit; } IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface); *ppv = &This->IAudioRenderClient_iface; } else if (IsEqualIID(riid, &IID_IAudioCaptureClient)) { if (This->dataflow != eCapture) { hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE; goto exit; } IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface); *ppv = &This->IAudioCaptureClient_iface; } else if (IsEqualIID(riid, &IID_IAudioClock)) { IAudioClock_AddRef(&This->IAudioClock_iface); *ppv = &This->IAudioClock_iface; } else if (IsEqualIID(riid, &IID_IAudioStreamVolume)) { IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface); *ppv = &This->IAudioStreamVolume_iface; } else if (IsEqualIID(riid, &IID_IAudioSessionControl) || IsEqualIID(riid, &IID_IChannelAudioVolume) || IsEqualIID(riid, &IID_ISimpleAudioVolume)) { const BOOLEAN new_session = !This->session_wrapper; if (new_session) { This->session_wrapper = session_wrapper_create(This); if (!This->session_wrapper) { hr = E_OUTOFMEMORY; goto exit; } } if (IsEqualIID(riid, &IID_IAudioSessionControl)) *ppv = &This->session_wrapper->IAudioSessionControl2_iface; else if (IsEqualIID(riid, &IID_IChannelAudioVolume)) *ppv = &This->session_wrapper->IChannelAudioVolume_iface; else if (IsEqualIID(riid, &IID_ISimpleAudioVolume)) *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; if (!new_session) IUnknown_AddRef((IUnknown *)*ppv); } else { FIXME("stub %s\n", debugstr_guid(riid)); hr = E_NOINTERFACE; goto exit; } hr = S_OK; exit: sessions_unlock(); return hr; } static HRESULT WINAPI client_IsOffloadCapable(IAudioClient3 *iface, AUDIO_STREAM_CATEGORY category, BOOL *offload_capable) { struct audio_client *This = impl_from_IAudioClient3(iface); TRACE("(%p)->(0x%x, %p)\n", This, category, offload_capable); if (!offload_capable) return E_INVALIDARG; *offload_capable = FALSE; return S_OK; } static HRESULT WINAPI client_SetClientProperties(IAudioClient3 *iface, const AudioClientProperties *prop) { struct audio_client *This = impl_from_IAudioClient3(iface); const Win8AudioClientProperties *legacy_prop = (const Win8AudioClientProperties *)prop; TRACE("(%p)->(%p)\n", This, prop); if (!legacy_prop) return E_POINTER; if (legacy_prop->cbSize == sizeof(AudioClientProperties)) { TRACE("{ bIsOffload: %u, eCategory: 0x%x, Options: 0x%x }\n", legacy_prop->bIsOffload, legacy_prop->eCategory, prop->Options); } else if(legacy_prop->cbSize == sizeof(Win8AudioClientProperties)) { TRACE("{ bIsOffload: %u, eCategory: 0x%x }\n", legacy_prop->bIsOffload, legacy_prop->eCategory); } else { WARN("Unsupported Size = %d\n", legacy_prop->cbSize); return E_INVALIDARG; } if (legacy_prop->bIsOffload) return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE; return S_OK; } static HRESULT WINAPI client_GetBufferSizeLimits(IAudioClient3 *iface, const WAVEFORMATEX *format, BOOL event_driven, REFERENCE_TIME *min_duration, REFERENCE_TIME *max_duration) { struct audio_client *This = impl_from_IAudioClient3(iface); FIXME("(%p)->(%p, %u, %p, %p) - stub\n", This, format, event_driven, min_duration, max_duration); return E_NOTIMPL; } static HRESULT WINAPI client_GetSharedModeEnginePeriod(IAudioClient3 *iface, const WAVEFORMATEX *format, UINT32 *default_period_frames, UINT32 *unit_period_frames, UINT32 *min_period_frames, UINT32 *max_period_frames) { struct audio_client *This = impl_from_IAudioClient3(iface); REFERENCE_TIME def_period, min_period; HRESULT hr; TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, format, default_period_frames, unit_period_frames, min_period_frames, max_period_frames); if (FAILED(hr = get_periods(This, &def_period, &min_period))) return hr; *default_period_frames = def_period * format->nSamplesPerSec / (REFERENCE_TIME)10000000; *min_period_frames = min_period * format->nSamplesPerSec / (REFERENCE_TIME)10000000; *max_period_frames = *default_period_frames; *unit_period_frames = 1; return hr; } static HRESULT WINAPI client_GetCurrentSharedModeEnginePeriod(IAudioClient3 *iface, WAVEFORMATEX **cur_format, UINT32 *cur_period_frames) { struct audio_client *This = impl_from_IAudioClient3(iface); UINT32 dummy; HRESULT hr; TRACE("(%p)->(%p, %p)\n", This, cur_format, cur_period_frames); if (!cur_format || !cur_period_frames) return E_POINTER; if (FAILED(hr = client_GetMixFormat(iface, cur_format))) return hr; return client_GetSharedModeEnginePeriod(iface, *cur_format, cur_period_frames, &dummy, &dummy, &dummy); } static HRESULT WINAPI client_InitializeSharedAudioStream(IAudioClient3 *iface, DWORD flags, UINT32 period_frames, const WAVEFORMATEX *format, const GUID *session_guid) { struct audio_client *This = impl_from_IAudioClient3(iface); REFERENCE_TIME period; TRACE("(%p)->(0x%lx, %u, %p, %s)\n", This, flags, period_frames, format, debugstr_guid(session_guid)); if (!format) return E_POINTER; period = period_frames * (REFERENCE_TIME)10000000 / format->nSamplesPerSec; return stream_init(This, FALSE, AUDCLNT_SHAREMODE_SHARED, flags, 0, period, format, session_guid); } const IAudioClient3Vtbl AudioClient3_Vtbl = { client_QueryInterface, client_AddRef, client_Release, client_Initialize, client_GetBufferSize, client_GetStreamLatency, client_GetCurrentPadding, client_IsFormatSupported, client_GetMixFormat, client_GetDevicePeriod, client_Start, client_Stop, client_Reset, client_SetEventHandle, client_GetService, client_IsOffloadCapable, client_SetClientProperties, client_GetBufferSizeLimits, client_GetSharedModeEnginePeriod, client_GetCurrentSharedModeEnginePeriod, client_InitializeSharedAudioStream, }; static HRESULT WINAPI clock_QueryInterface(IAudioClock *iface, REFIID riid, void **ppv) { struct audio_client *This = impl_from_IAudioClock(iface); TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) *ppv = iface; else if (IsEqualIID(riid, &IID_IAudioClock2)) *ppv = &This->IAudioClock2_iface; else if (IsEqualIID(riid, &IID_IMarshal)) { return IUnknown_QueryInterface(This->marshal, riid, ppv); } else { *ppv = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI clock_AddRef(IAudioClock *iface) { struct audio_client *This = impl_from_IAudioClock(iface); return IAudioClient3_AddRef(&This->IAudioClient3_iface); } static ULONG WINAPI clock_Release(IAudioClock *iface) { struct audio_client *This = impl_from_IAudioClock(iface); return IAudioClient3_Release(&This->IAudioClient3_iface); } static HRESULT WINAPI clock_GetFrequency(IAudioClock *iface, UINT64 *freq) { struct audio_client *This = impl_from_IAudioClock(iface); struct get_frequency_params params; TRACE("(%p)->(%p)\n", This, freq); if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.freq = freq; wine_unix_call(get_frequency, ¶ms); return params.result; } static HRESULT WINAPI clock_GetPosition(IAudioClock *iface, UINT64 *pos, UINT64 *qpctime) { struct audio_client *This = impl_from_IAudioClock(iface); struct get_position_params params; TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); if (!pos) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.device = FALSE; params.pos = pos; params.qpctime = qpctime; wine_unix_call(get_position, ¶ms); return params.result; } static HRESULT WINAPI clock_GetCharacteristics(IAudioClock *iface, DWORD *chars) { struct audio_client *This = impl_from_IAudioClock(iface); TRACE("(%p)->(%p)\n", This, chars); if (!chars) return E_POINTER; *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; return S_OK; } const IAudioClockVtbl AudioClock_Vtbl = { clock_QueryInterface, clock_AddRef, clock_Release, clock_GetFrequency, clock_GetPosition, clock_GetCharacteristics }; static HRESULT WINAPI clock2_QueryInterface(IAudioClock2 *iface, REFIID riid, void **ppv) { struct audio_client *This = impl_from_IAudioClock2(iface); return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); } static ULONG WINAPI clock2_AddRef(IAudioClock2 *iface) { struct audio_client *This = impl_from_IAudioClock2(iface); return IAudioClient3_AddRef(&This->IAudioClient3_iface); } static ULONG WINAPI clock2_Release(IAudioClock2 *iface) { struct audio_client *This = impl_from_IAudioClock2(iface); return IAudioClient3_Release(&This->IAudioClient3_iface); } static HRESULT WINAPI clock2_GetDevicePosition(IAudioClock2 *iface, UINT64 *pos, UINT64 *qpctime) { struct audio_client *This = impl_from_IAudioClock2(iface); struct get_position_params params; TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); if (!pos) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.device = TRUE; params.pos = pos; params.qpctime = qpctime; wine_unix_call(get_position, ¶ms); return params.result; } const IAudioClock2Vtbl AudioClock2_Vtbl = { clock2_QueryInterface, clock2_AddRef, clock2_Release, clock2_GetDevicePosition }; static HRESULT WINAPI render_QueryInterface(IAudioRenderClient *iface, REFIID riid, void **ppv) { struct audio_client *This = impl_from_IAudioRenderClient(iface); TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioRenderClient)) *ppv = iface; else if (IsEqualIID(riid, &IID_IMarshal)) { return IUnknown_QueryInterface(This->marshal, riid, ppv); } else { *ppv = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI render_AddRef(IAudioRenderClient *iface) { struct audio_client *This = impl_from_IAudioRenderClient(iface); return IAudioClient3_AddRef(&This->IAudioClient3_iface); } static ULONG WINAPI render_Release(IAudioRenderClient *iface) { struct audio_client *This = impl_from_IAudioRenderClient(iface); return IAudioClient3_Release(&This->IAudioClient3_iface); } static HRESULT WINAPI render_GetBuffer(IAudioRenderClient *iface, UINT32 frames, BYTE **data) { struct audio_client *This = impl_from_IAudioRenderClient(iface); struct get_render_buffer_params params; TRACE("(%p)->(%u, %p)\n", This, frames, data); if (!data) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; *data = NULL; params.stream = This->stream; params.frames = frames; params.data = data; wine_unix_call(get_render_buffer, ¶ms); return params.result; } static HRESULT WINAPI render_ReleaseBuffer(IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) { struct audio_client *This = impl_from_IAudioRenderClient(iface); struct release_render_buffer_params params; TRACE("(%p)->(%u, %lx)\n", This, written_frames, flags); if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; params.stream = This->stream; params.written_frames = written_frames; params.flags = flags; wine_unix_call(release_render_buffer, ¶ms); return params.result; } const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { render_QueryInterface, render_AddRef, render_Release, render_GetBuffer, render_ReleaseBuffer }; static HRESULT WINAPI streamvolume_QueryInterface(IAudioStreamVolume *iface, REFIID riid, void **ppv) { TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); if (!ppv) return E_POINTER; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioStreamVolume)) *ppv = iface; else if (IsEqualIID(riid, &IID_IMarshal)) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); return IUnknown_QueryInterface(This->marshal, riid, ppv); } else { *ppv = NULL; return E_NOINTERFACE; } IUnknown_AddRef((IUnknown *)*ppv); return S_OK; } static ULONG WINAPI streamvolume_AddRef(IAudioStreamVolume *iface) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); return IAudioClient3_AddRef(&This->IAudioClient3_iface); } static ULONG WINAPI streamvolume_Release(IAudioStreamVolume *iface) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); return IAudioClient3_Release(&This->IAudioClient3_iface); } static HRESULT WINAPI streamvolume_GetChannelCount(IAudioStreamVolume *iface, UINT32 *out) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); TRACE("(%p)->(%p)\n", This, out); if (!out) return E_POINTER; *out = This->channel_count; return S_OK; } static HRESULT WINAPI streamvolume_SetChannelVolume(IAudioStreamVolume *iface, UINT32 index, float level) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); TRACE("(%p)->(%d, %f)\n", This, index, level); if (level < 0.f || level > 1.f) return E_INVALIDARG; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; if (index >= This->channel_count) return E_INVALIDARG; sessions_lock(); This->vols[index] = level; set_stream_volumes(This); sessions_unlock(); return S_OK; } static HRESULT WINAPI streamvolume_GetChannelVolume(IAudioStreamVolume *iface, UINT32 index, float *level) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); TRACE("(%p)->(%d, %p)\n", This, index, level); if (!level) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; if (index >= This->channel_count) return E_INVALIDARG; *level = This->vols[index]; return S_OK; } static HRESULT WINAPI streamvolume_SetAllVolumes(IAudioStreamVolume *iface, UINT32 count, const float *levels) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); unsigned int i; TRACE("(%p)->(%d, %p)\n", This, count, levels); if (!levels) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; if (count != This->channel_count) return E_INVALIDARG; sessions_lock(); for (i = 0; i < count; ++i) This->vols[i] = levels[i]; set_stream_volumes(This); sessions_unlock(); return S_OK; } static HRESULT WINAPI streamvolume_GetAllVolumes(IAudioStreamVolume *iface, UINT32 count, float *levels) { struct audio_client *This = impl_from_IAudioStreamVolume(iface); unsigned int i; TRACE("(%p)->(%d, %p)\n", This, count, levels); if (!levels) return E_POINTER; if (!This->stream) return AUDCLNT_E_NOT_INITIALIZED; if (count != This->channel_count) return E_INVALIDARG; sessions_lock(); for (i = 0; i < count; ++i) levels[i] = This->vols[i]; sessions_unlock(); return S_OK; } const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = { streamvolume_QueryInterface, streamvolume_AddRef, streamvolume_Release, streamvolume_GetChannelCount, streamvolume_SetChannelVolume, streamvolume_GetChannelVolume, streamvolume_SetAllVolumes, streamvolume_GetAllVolumes }; HRESULT AudioClient_Create(GUID *guid, IMMDevice *device, IAudioClient **out) { struct audio_client *This; char *name; EDataFlow dataflow; size_t size; HRESULT hr; TRACE("%s %p %p\n", debugstr_guid(guid), device, out); *out = NULL; if (!drvs.pget_device_name_from_guid(guid, &name, &dataflow)) return AUDCLNT_E_DEVICE_INVALIDATED; if (dataflow != eRender && dataflow != eCapture) { free(name); return E_UNEXPECTED; } size = strlen(name) + 1; This = calloc(1, FIELD_OFFSET(struct audio_client, device_name[size])); if (!This) { free(name); return E_OUTOFMEMORY; } memcpy(This->device_name, name, size); free(name); This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; This->dataflow = dataflow; This->parent = device; hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->marshal); if (FAILED(hr)) { free(This); return hr; } IMMDevice_AddRef(This->parent); *out = (IAudioClient *)&This->IAudioClient3_iface; IAudioClient3_AddRef(&This->IAudioClient3_iface); return S_OK; }