diff options
Diffstat (limited to 'audio/out/ao_wasapi_utils.c')
-rw-r--r-- | audio/out/ao_wasapi_utils.c | 204 |
1 files changed, 145 insertions, 59 deletions
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index a696040dba..7d4c536e78 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -18,12 +18,23 @@ */ #include <math.h> -#include <wchar.h> + #include <windows.h> -#include <errors.h> +// Workaround for redefinition of some guids in mingw. ks.h has to be included +// before other ks*.h headers, but mingw fails then. +#ifdef __MINGW32__ +#include <mmreg.h> +#include <ksguid.h> +#else +#include <ks.h> #include <ksguid.h> +#include <mmreg.h> +#endif +#include <mmsystem.h> #include <ksmedia.h> #include <avrt.h> +#include <propsys.h> +#include <functiondiscoverykeys_devpkey.h> #include "audio/format.h" #include "osdep/timer.h" @@ -31,38 +42,81 @@ #include "osdep/strnlen.h" #include "ao_wasapi.h" -#define MIXER_DEFAULT_LABEL L"mpv - video player" - -DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName, - 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, - 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); -DEFINE_PROPERTYKEY(mp_PKEY_Device_DeviceDesc, - 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, - 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); -// CEA 861 subformats -// should work on vista -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS, - 0x00000008, 0x0000, 0x0010, 0x80, 0x00, +#ifdef _MSC_VER +// Define some GUIDs that are defined only in C++ interfaces. +DEFINE_GUID(KSDATAFORMAT_SPECIFIER_NONE, + 0xf6417d6, 0xc318, 0x11d0, 0xa4, 0x3f, + 0, 0xa0, 0xc9, 0x22, 0x31, 0x96); +DEFINE_GUID(CLSID_MMDeviceEnumerator, + 0xbcde0395, 0xe52f, 0x467c, 0x8e, 0x3d, + 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e); +DEFINE_GUID(IID_IAudioClient, + 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, + 0xf5, 0x68, 0xa7, 0x03, 0xb2); +DEFINE_GUID(IID_IAudioClock, + 0xcd63314f, 0x3fba, 0x4a1b, 0x81, 0x2c, 0xef, 0x96, + 0x35, 0x87, 0x28, 0xe7); +DEFINE_GUID(IID_IAudioEndpointVolume, + 0x5cdf2c82, 0x841e, 0x4546, 0x97, 0x22, + 0x0c, 0xf7, 0x40, 0x78, 0x22, 0x9a); +DEFINE_GUID(IID_IAudioRenderClient, + 0xf294acfc, 0x3146, 0x4483, 0xa7, 0xbf, + 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2); +DEFINE_GUID(IID_IAudioSessionControl, + 0xf4b1a599, 0x7266, 0x4319, 0xa8, 0xca, + 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd); +DEFINE_GUID(IID_IMMDeviceEnumerator, + 0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, + 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6); +DEFINE_GUID(IID_IMMNotificationClient, + 0x7991eec9, 0x7e89, 0x4d85, 0x83, 0x90, + 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0); +DEFINE_GUID(IID_ISimpleAudioVolume, + 0x87ce5498, 0x68d6, 0x44e5, 0x92, 0x15, + 0x6d, 0xa4, 0x7e, 0xf8, 0x83, 0xd8); +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS, + WAVE_FORMAT_DTS, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, - 0x00000092, 0x0000, 0x0010, 0x80, 0x00, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, + WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -// might require 7+ -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_AAC +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_AAC, 0x00000006, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3, - 0x00000004, 0x0cea, 0x0010, 0x80, 0x00, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3 +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3, + 0x00000005, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, 0x0000000a, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD, 0x0000000b, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); -DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP, +#endif + +#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP, 0x0000000c, 0x0cea, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +#endif struct wasapi_sample_fmt { int mp_format; // AF_FORMAT_* @@ -84,13 +138,13 @@ static const struct wasapi_sample_fmt wasapi_formats[] = { // aka S24 (with conversion on output) {AF_FORMAT_S32, 24, 24, &KSDATAFORMAT_SUBTYPE_PCM}, {AF_FORMAT_FLOAT, 32, 32, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT}, - {AF_FORMAT_S_AC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL}, - {AF_FORMAT_S_DTS, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS}, - {AF_FORMAT_S_AAC, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC}, - {AF_FORMAT_S_MP3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3}, - {AF_FORMAT_S_TRUEHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP}, - {AF_FORMAT_S_EAC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS}, - {AF_FORMAT_S_DTSHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD}, + {AF_FORMAT_S_AC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL}, + {AF_FORMAT_S_DTS, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS}, + {AF_FORMAT_S_AAC, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_AAC}, + {AF_FORMAT_S_MP3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3}, + {AF_FORMAT_S_TRUEHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP}, + {AF_FORMAT_S_EAC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS}, + {AF_FORMAT_S_DTSHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD}, {0}, }; @@ -145,7 +199,19 @@ static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat, wformat->SubFormat = *format_to_subtype(format->mp_format); wformat->Samples.wValidBitsPerSample = format->used_msb; - wformat->dwChannelMask = mp_chmap_to_waveext(channels); + + uint64_t chans = mp_chmap_to_waveext(channels); + wformat->dwChannelMask = chans; + + if (wformat->Format.nChannels > 8 || wformat->dwChannelMask != chans) { + // IAudioClient::IsFormatSupported tend to fallback to stereo for closest + // format match when there are more channels. Remix to standard layout. + // Also if input channel mask has channels outside 32-bits override it + // and hope for the best... + wformat->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + wformat->Format.nChannels = 8; + } + update_waveformat_datarate(wformat); } @@ -238,7 +304,8 @@ static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf) (unsigned) wf->nSamplesPerSec); return buf; } -#define waveformat_to_str(wf) waveformat_to_str_buf((char[64]){0}, 64, (wf)) +#define waveformat_to_str_(wf, sz) waveformat_to_str_buf((char[sz]){0}, sz, (wf)) +#define waveformat_to_str(wf) waveformat_to_str_(wf, MP_NUM_CHANNELS * 4 + 42) static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src) { @@ -374,7 +441,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat) "3.0", "3.0(back)", "quad", "quad(side)", "3.1", "5.0(side)", "4.1", - "5.1(side)", "6.0", "6.0(front)", "hexagonal" + "5.1(side)", "6.0", "6.0(front)", "hexagonal", "6.1(back)", "6.1(front)", "7.0", "7.0(front)", "7.1(wide)", "7.1(wide-side)", "7.1(rear)", "octagonal", NULL}; @@ -545,29 +612,36 @@ exit_label: return hr; } -static void init_session_display(struct wasapi_state *state) { +static void init_session_display(struct wasapi_state *state, const char *name) { HRESULT hr = IAudioClient_GetService(state->pAudioClient, &IID_IAudioSessionControl, (void **)&state->pSessionControl); EXIT_ON_ERROR(hr); - wchar_t path[MAX_PATH] = {0}; - GetModuleFileNameW(NULL, path, MAX_PATH); + wchar_t *path = talloc_array(NULL, wchar_t, MP_PATH_MAX); + GetModuleFileNameW(NULL, path, MP_PATH_MAX); hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL); + talloc_free(path); if (FAILED(hr)) { // don't goto exit_label here since SetDisplayName might still work MP_WARN(state, "Error setting audio session icon: %s\n", mp_HRESULT_to_str(hr)); } - hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, - MIXER_DEFAULT_LABEL, NULL); + assert(name); + if (!name) + return; + + wchar_t *title = mp_from_utf8(NULL, name); + hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL); + talloc_free(title); + EXIT_ON_ERROR(hr); return; exit_label: // if we got here then the session control is useless - release it SAFE_RELEASE(state->pSessionControl); - MP_WARN(state, "Error setting audio session display name: %s\n", + MP_WARN(state, "Error setting audio session name: %s\n", mp_HRESULT_to_str(hr)); return; } @@ -608,16 +682,28 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) struct wasapi_state *state = ao->priv; MP_DBG(state, "IAudioClient::GetDevicePeriod\n"); - REFERENCE_TIME devicePeriod; - HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, - NULL); - MP_VERBOSE(state, "Device period: %.2g ms\n", - (double) devicePeriod / 10000.0 ); - - REFERENCE_TIME bufferDuration = devicePeriod; + REFERENCE_TIME defaultPeriod, minPeriod; + HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&defaultPeriod, + &minPeriod); + MP_VERBOSE(state, "Device period: default %lld us, minimum %lld us\n", + defaultPeriod / 10, minPeriod / 10); + + // per Microsoft: + // * hnsBufferDuration = hnsPeriodicity = 0 for shared mode + // * hnsBufferDuration = hnsPeriodicity != 0 for exclusive mode + // https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize + REFERENCE_TIME bufferDuration; if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) { - // for shared mode, use integer multiple of device period close to 50ms - bufferDuration = devicePeriod * ceil(50.0 * 10000.0 / devicePeriod); + bufferDuration = 0; + } else if (state->opt_exclusive_buffer == 0) { + bufferDuration = defaultPeriod; + } else { + if (state->opt_exclusive_buffer > 0 && !align_hack) { + MP_VERBOSE(state, "Requested buffer duration: %d us\n", + state->opt_exclusive_buffer); + } + bufferDuration = MPMAX((REFERENCE_TIME) state->opt_exclusive_buffer * 10, + minPeriod); } // handle unsupported buffer size if AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED was @@ -630,15 +716,15 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) * state->bufferFrameCount)); } - REFERENCE_TIME bufferPeriod = - state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? bufferDuration : 0; + if (state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE) + MP_VERBOSE(state, "Trying buffer duration %lld us\n", bufferDuration / 10); MP_DBG(state, "IAudioClient::Initialize\n"); hr = IAudioClient_Initialize(state->pAudioClient, state->share_mode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, - bufferPeriod, + bufferDuration, &(state->format.Format), NULL); EXIT_ON_ERROR(hr); @@ -662,13 +748,13 @@ static HRESULT fix_format(struct ao *ao, bool align_hack) bufferDuration = (REFERENCE_TIME) (0.5 + (10000.0 * 1000 / state->format.Format.nSamplesPerSec * state->bufferFrameCount)); - MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n", - state->bufferFrameCount, (double) bufferDuration / 10000.0 ); + MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%lld us)\n", + state->bufferFrameCount, bufferDuration / 10); hr = init_clock(state); EXIT_ON_ERROR(hr); - init_session_display(state); + init_session_display(state, ao->client_name); init_volume_control(state); #if !HAVE_UWP @@ -701,7 +787,7 @@ static char* get_device_name(struct mp_log *l, void *talloc_ctx, IMMDevice *pDev HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); EXIT_ON_ERROR(hr); - hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName, + hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &devname); EXIT_ON_ERROR(hr); @@ -928,7 +1014,7 @@ bool wasapi_thread_init(struct ao *ao) { struct wasapi_state *state = ao->priv; MP_DBG(ao, "Init wasapi thread\n"); - int64_t retry_wait = 1; + int64_t retry_wait = MP_TIME_US_TO_NS(1); bool align_hack = false; HRESULT hr; @@ -1011,13 +1097,13 @@ retry: goto retry; case AUDCLNT_E_DEVICE_IN_USE: case AUDCLNT_E_DEVICE_INVALIDATED: - if (retry_wait > 8) { + if (retry_wait > MP_TIME_US_TO_NS(8)) { MP_FATAL(ao, "Bad device retry failed\n"); return false; } wasapi_thread_uninit(ao); - MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait); - mp_sleep_us(retry_wait); + MP_WARN(ao, "Retrying in %"PRId64" ns\n", retry_wait); + mp_sleep_ns(retry_wait); retry_wait *= 2; goto retry; } |