summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_wasapi_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_wasapi_utils.c')
-rw-r--r--audio/out/ao_wasapi_utils.c214
1 files changed, 142 insertions, 72 deletions
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index df844aad7f..2614872b91 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -18,12 +18,15 @@
*/
#include <math.h>
-#include <wchar.h>
+
#include <windows.h>
-#include <errors.h>
+#include <mmsystem.h>
+#include <mmreg.h>
#include <ksguid.h>
#include <ksmedia.h>
#include <avrt.h>
+#include <propsys.h>
+#include <functiondiscoverykeys_devpkey.h>
#include "audio/format.h"
#include "osdep/timer.h"
@@ -31,38 +34,47 @@
#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,
+#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 +96,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 +157,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 +262,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)
{
@@ -299,8 +324,9 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
}
#define mp_format_res_str(hres) \
- (SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
- ? "unsupported" : mp_HRESULT_to_str(hres))
+ (SUCCEEDED(hres) ? ((hres) == S_OK) ? "ok" : "close" \
+ : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
+ ? "unsupported" : mp_HRESULT_to_str(hres))
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
{
@@ -373,7 +399,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};
@@ -426,32 +452,57 @@ static bool find_formats_shared(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
{
struct wasapi_state *state = ao->priv;
- WAVEFORMATEX *closestMatch;
- HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
- AUDCLNT_SHAREMODE_SHARED,
- &wformat->Format,
- &closestMatch);
+ struct mp_chmap channels;
+ if (!chmap_from_waveformat(&channels, &wformat->Format)) {
+ MP_ERR(ao, "Error converting channel map\n");
+ return false;
+ }
+
+ HRESULT hr;
+ WAVEFORMATEX *mix_format;
+ hr = IAudioClient_GetMixFormat(state->pAudioClient, &mix_format);
+ EXIT_ON_ERROR(hr);
+
+ // WASAPI doesn't do any sample rate conversion on its own and
+ // will typically only accept the mix format samplerate. Although
+ // it will accept any PCM sample format, everything gets converted
+ // to the mix format anyway (pretty much always float32), so just
+ // use that.
+ WAVEFORMATEXTENSIBLE try_format;
+ waveformat_copy(&try_format, mix_format);
+ CoTaskMemFree(mix_format);
+
+ // WASAPI may accept channel maps other than the mix format
+ // if a surround emulator is enabled.
+ change_waveformat_channels(&try_format, &channels);
+
+ hr = IAudioClient_IsFormatSupported(state->pAudioClient,
+ AUDCLNT_SHAREMODE_SHARED,
+ &try_format.Format,
+ &mix_format);
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
- waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
+ waveformat_to_str(&try_format.Format), mp_format_res_str(hr));
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
EXIT_ON_ERROR(hr);
switch (hr) {
case S_OK:
+ waveformat_copy(wformat, &try_format.Format);
break;
case S_FALSE:
- waveformat_copy(wformat, closestMatch);
- CoTaskMemFree(closestMatch);
+ waveformat_copy(wformat, mix_format);
+ CoTaskMemFree(mix_format);
MP_VERBOSE(ao, "Closest match is %s\n",
waveformat_to_str(&wformat->Format));
break;
default:
- hr = IAudioClient_GetMixFormat(state->pAudioClient, &closestMatch);
+ hr = IAudioClient_GetMixFormat(state->pAudioClient, &mix_format);
EXIT_ON_ERROR(hr);
- waveformat_copy(wformat, closestMatch);
+ waveformat_copy(wformat, mix_format);
+ CoTaskMemFree(mix_format);
MP_VERBOSE(ao, "Fallback to mix format %s\n",
waveformat_to_str(&wformat->Format));
- CoTaskMemFree(closestMatch);
+
}
return true;
@@ -519,29 +570,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;
}
@@ -582,16 +640,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
@@ -604,15 +674,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);
@@ -636,13 +706,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
@@ -675,7 +745,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);
@@ -902,7 +972,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;
@@ -985,13 +1055,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;
}