summaryrefslogtreecommitdiffstats
path: root/audio/out
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out')
-rw-r--r--audio/out/ao_wasapi.c5
-rw-r--r--audio/out/ao_wasapi.h1
-rw-r--r--audio/out/ao_wasapi_utils.c244
3 files changed, 133 insertions, 117 deletions
diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c
index 1337717f7b..dbca270e00 100644
--- a/audio/out/ao_wasapi.c
+++ b/audio/out/ao_wasapi.c
@@ -129,8 +129,9 @@ static bool thread_feed(struct ao *ao)
BYTE *data[1] = {pData};
- ao_read_data(ao, (void **)data, frame_count,
- mp_time_us() + (int64_t)llrint(delay_us));
+ ao_read_data_converted(ao, &state->convert_format,
+ (void **)data, frame_count,
+ mp_time_us() + (int64_t)llrint(delay_us));
// note, we can't use ao_read_data return value here since we already
// committed to frame_count above in the GetBuffer call
diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h
index 8f6e38867c..81c3d93386 100644
--- a/audio/out/ao_wasapi.h
+++ b/audio/out/ao_wasapi.h
@@ -101,6 +101,7 @@ typedef struct wasapi_state {
WAVEFORMATEXTENSIBLE format;
AUDCLNT_SHAREMODE share_mode; // AUDCLNT_SHAREMODE_EXCLUSIVE / SHARED
UINT32 bufferFrameCount; // number of frames in buffer
+ struct ao_convert_fmt convert_format;
change_notify change;
} wasapi_state;
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index 8cfeecf08d..ce0d6cf225 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -64,44 +64,53 @@ DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
0x0000000c, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-struct wasapi_fmt_mapping {
+struct wasapi_sample_fmt {
+ int mp_format; // AF_FORMAT_*
+ int bits; // aka wBitsPerSample
+ int used_msb; // aka wValidBitsPerSample
const GUID *subtype;
- int format;
};
-const struct wasapi_fmt_mapping wasapi_fmt_table[] = {
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL, AF_FORMAT_S_AC3},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS, AF_FORMAT_S_DTS},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC, AF_FORMAT_S_AAC},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3, AF_FORMAT_S_MP3},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP, AF_FORMAT_S_TRUEHD},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS, AF_FORMAT_S_EAC3},
- {&mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD, AF_FORMAT_S_DTSHD},
- {0}
+// some common bit depths / container sizes (requests welcome)
+// Entries that have the same mp_format must be:
+// 1. consecutive
+// 2. sorted by preferred format (worst comes last)
+static const struct wasapi_sample_fmt wasapi_formats[] = {
+ {AF_FORMAT_U8, 8, 8, &KSDATAFORMAT_SUBTYPE_PCM},
+ {AF_FORMAT_S16, 16, 16, &KSDATAFORMAT_SUBTYPE_PCM},
+ {AF_FORMAT_S32, 32, 32, &KSDATAFORMAT_SUBTYPE_PCM},
+ // compatible, assume LSBs are ignored
+ {AF_FORMAT_S32, 32, 24, &KSDATAFORMAT_SUBTYPE_PCM},
+ // aka S24 (with conversion) - untested, thus commented.
+ //{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},
+ {0},
};
static const GUID *format_to_subtype(int format)
{
- if (af_fmt_is_spdif(format)) {
- for (int i = 0; wasapi_fmt_table[i].format; i++) {
- if (wasapi_fmt_table[i].format == format)
- return wasapi_fmt_table[i].subtype;
- }
- return &KSDATAFORMAT_SPECIFIER_NONE;
- } else if (format == AF_FORMAT_FLOAT) {
- return &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- } else if (af_fmt_is_int(format)) {
- return &KSDATAFORMAT_SUBTYPE_PCM;
+ for (int i = 0; wasapi_formats[i].mp_format; i++) {
+ if (format == wasapi_formats[i].mp_format)
+ return wasapi_formats[i].subtype;
}
- return NULL;
+ return &KSDATAFORMAT_SPECIFIER_NONE;
}
// "solve" the under-determined inverse of format_to_subtype by assuming the
// input subtype is "special" (i.e. IEC61937)
-static int special_subtype_to_format(const GUID *subtype) {
- for (int i = 0; wasapi_fmt_table[i].format; i++) {
- if (IsEqualGUID(subtype, wasapi_fmt_table[i].subtype))
- return wasapi_fmt_table[i].format;
+static int special_subtype_to_format(const GUID *subtype)
+{
+ for (int i = 0; wasapi_formats[i].mp_format; i++) {
+ if (IsEqualGUID(subtype, wasapi_formats[i].subtype) &&
+ af_fmt_is_spdif(wasapi_formats[i].mp_format))
+ return wasapi_formats[i].mp_format;
}
return 0;
}
@@ -123,32 +132,17 @@ static void update_waveformat_datarate(WAVEFORMATEXTENSIBLE *wformat)
}
static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
- int format, WORD valid_bits,
+ struct wasapi_sample_fmt format,
DWORD samplerate, struct mp_chmap *channels)
{
- // First find a format that is actually representable.
- // (Notably excludes AF_FORMAT_DOUBLE.)
- const GUID *sub_format = &KSDATAFORMAT_SPECIFIER_NONE;
- int alt_formats[AF_FORMAT_COUNT];
- af_get_best_sample_formats(format, alt_formats);
- for (int n = 0; alt_formats[n]; n++) {
- const GUID *guid = format_to_subtype(alt_formats[n]);
- if (guid) {
- format = alt_formats[n];
- sub_format = guid;
- break;
- }
- }
-
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wformat->Format.nChannels = channels->num;
wformat->Format.nSamplesPerSec = samplerate;
- wformat->Format.wBitsPerSample = af_fmt_to_bytes(format) * 8;
+ wformat->Format.wBitsPerSample = format.bits;
wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
- wformat->SubFormat = *sub_format;
- wformat->Samples.wValidBitsPerSample =
- valid_bits ? valid_bits : wformat->Format.wBitsPerSample;
+ wformat->SubFormat = *format_to_subtype(format.mp_format);
+ wformat->Samples.wValidBitsPerSample = format.used_msb;
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
update_waveformat_datarate(wformat);
}
@@ -161,7 +155,21 @@ static void set_waveformat_with_ao(WAVEFORMATEXTENSIBLE *wformat, struct ao *ao)
struct mp_chmap channels = ao->channels;
mp_chmap_reorder_to_waveext(&channels);
- set_waveformat(wformat, ao->format, 0, ao->samplerate, &channels);
+ // First find a format that is actually representable.
+ // (Notably excludes AF_FORMAT_DOUBLE.)
+ struct wasapi_sample_fmt format =
+ {AF_FORMAT_S16, 16, 16, &KSDATAFORMAT_SUBTYPE_PCM};
+
+ int alt_formats[AF_FORMAT_COUNT];
+ af_get_best_sample_formats(ao->format, alt_formats);
+ for (int n = 0; alt_formats[n]; n++) {
+ for (int i = 0; wasapi_formats[i].mp_format; i++) {
+ if (wasapi_formats[i].mp_format == alt_formats[n])
+ format = wasapi_formats[i];
+ }
+ }
+
+ set_waveformat(wformat, format, ao->samplerate, &channels);
}
// other wformat parameters must already be set with set_waveformat
@@ -181,51 +189,45 @@ static void change_waveformat_channels(WAVEFORMATEXTENSIBLE *wformat,
update_waveformat_datarate(wformat);
}
-static WORD waveformat_valid_bits(const WAVEFORMATEX *wf)
+static struct wasapi_sample_fmt format_from_waveformat(WAVEFORMATEX *wf)
{
- if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
- WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
- return wformat->Samples.wValidBitsPerSample;
- } else {
- return wf->wBitsPerSample;
- }
-}
+ struct wasapi_sample_fmt res = {0};
-static int format_from_waveformat(WAVEFORMATEX *wf)
-{
- int format;
- switch (wf->wFormatTag) {
- case WAVE_FORMAT_EXTENSIBLE:
- {
- WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
- if (IsEqualGUID(&wformat->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
- format = wf->wBitsPerSample == 8 ? AF_FORMAT_U8 : AF_FORMAT_S32;
- } else if (IsEqualGUID(&wformat->SubFormat,
- &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
- format = AF_FORMAT_FLOAT;
- } else {
- format = special_subtype_to_format(&wformat->SubFormat);
+ for (int n = 0; wasapi_formats[n].mp_format; n++) {
+ const struct wasapi_sample_fmt *fmt = &wasapi_formats[n];
+ int valid_bits = 0;
+
+ if (wf->wBitsPerSample != fmt->bits)
+ continue;
+
+ const GUID *wf_guid = NULL;
+
+ switch (wf->wFormatTag) {
+ case WAVE_FORMAT_EXTENSIBLE: {
+ WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
+ wf_guid = &wformat->SubFormat;
+ if (IsEqualGUID(wf_guid, &KSDATAFORMAT_SUBTYPE_PCM))
+ valid_bits = wformat->Samples.wValidBitsPerSample;
+ break;
}
+ case WAVE_FORMAT_PCM:
+ wf_guid = &KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case WAVE_FORMAT_IEEE_FLOAT:
+ wf_guid = &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ break;
+ }
+
+ if (!wf_guid || !IsEqualGUID(wf_guid, fmt->subtype))
+ continue;
+
+ res = *fmt;
+ if (valid_bits > 0 && valid_bits < fmt->bits)
+ res.used_msb = valid_bits;
break;
}
- case WAVE_FORMAT_PCM:
- format = wf->wBitsPerSample == 8 ? AF_FORMAT_U8 : AF_FORMAT_S32;
- break;
- case WAVE_FORMAT_IEEE_FLOAT:
- format = AF_FORMAT_FLOAT;
- break;
- default:
- return 0;
- }
- // https://msdn.microsoft.com/en-us/library/windows/hardware/ff538802%28v=vs.85%29.aspx:
- // Since mpv doesn't have the notion of "valid bits", we just specify a
- // format with the container size. The least significant, "invalid" bits
- // will be excess precision ignored by wasapi. The change_bytes operations
- // should be a no-op for properly configured "special" formats, otherwise it
- // will return 0.
- if (wf->wBitsPerSample % 8)
- return 0;
- return af_fmt_change_bytes(format, wf->wBitsPerSample / 8);
+
+ return res;
}
static bool chmap_from_waveformat(struct mp_chmap *channels,
@@ -251,18 +253,15 @@ static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf)
struct mp_chmap channels;
chmap_from_waveformat(&channels, wf);
- unsigned valid_bits = waveformat_valid_bits(wf);
- char validstr[12] = "";
- if (valid_bits != wf->wBitsPerSample)
- snprintf(validstr, sizeof(validstr), " (%u valid)", valid_bits);
+ struct wasapi_sample_fmt format = format_from_waveformat(wf);
- snprintf(buf, buf_size, "%s %s%s @ %uhz",
+ snprintf(buf, buf_size, "%s %s (%d/%d bits) @ %uhz",
mp_chmap_to_str(&channels),
- af_fmt_to_str(format_from_waveformat(wf)),
- validstr, (unsigned) wf->nSamplesPerSec);
+ af_fmt_to_str(format.mp_format), format.bits, format.used_msb,
+ (unsigned) wf->nSamplesPerSec);
return buf;
}
-#define waveformat_to_str(wf) waveformat_to_str_buf((char[40]){0}, 40, (wf))
+#define waveformat_to_str(wf) waveformat_to_str_buf((char[64]){0}, 64, (wf))
static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src)
{
@@ -277,8 +276,8 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
AUDCLNT_SHAREMODE share_mode)
{
struct wasapi_state *state = ao->priv;
- int format = format_from_waveformat(wf);
- if (!format) {
+ struct wasapi_sample_fmt format = format_from_waveformat(wf);
+ if (!format.mp_format) {
MP_ERR(ao, "Unable to construct sample format from WAVEFORMAT %s\n",
waveformat_to_str(wf));
return false;
@@ -286,15 +285,28 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
// Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX
// correctly.
- if (af_fmt_is_pcm(format)) {
+ if (af_fmt_is_pcm(format.mp_format)) {
struct mp_chmap channels;
if (!chmap_from_waveformat(&channels, wf)) {
MP_ERR(ao, "Unable to construct channel map from WAVEFORMAT %s\n",
waveformat_to_str(wf));
return false;
}
+
+ struct ao_convert_fmt conv = {
+ .src_fmt = format.mp_format,
+ .channels = channels.num,
+ .dst_bits = format.bits,
+ .pad_lsb = format.bits - format.used_msb,
+ };
+ if (!ao_can_convert_inplace(&conv)) {
+ MP_ERR(ao, "Unable to convert to %s\n", waveformat_to_str(wf));
+ return false;
+ }
+
+ state->convert_format = conv;
ao->samplerate = wf->nSamplesPerSec;
- ao->format = format;
+ ao->format = format.mp_format;
ao->channels = channels;
}
waveformat_copy(&state->format, wf);
@@ -333,17 +345,16 @@ static bool try_format_exclusive_with_spdif_fallback(struct ao *ao,
static bool search_sample_formats(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
int samplerate, struct mp_chmap *channels)
{
- // some common bit depths / container sizes (requests welcome)
- int try[] = {AF_FORMAT_FLOAT , AF_FORMAT_S32 ,
- AF_FORMAT_S24 , AF_FORMAT_S32 , AF_FORMAT_S16,
- AF_FORMAT_U8 , 0};
- unsigned valid[] = {0 , 0,
- 0 , 24, 0,
- 0 };
- for (int i = 0; try[i]; i++) {
- set_waveformat(wformat, try[i], valid[i], samplerate, channels);
- if (try_format_exclusive(ao, wformat))
- return true;
+ int alt_formats[AF_FORMAT_COUNT];
+ af_get_best_sample_formats(ao->format, alt_formats);
+ for (int n = 0; alt_formats[n]; n++) {
+ for (int i = 0; wasapi_formats[i].mp_format; i++) {
+ if (wasapi_formats[i].mp_format == alt_formats[n]) {
+ set_waveformat(wformat, wasapi_formats[i], samplerate, channels);
+ if (try_format_exclusive(ao, wformat))
+ return true;
+ }
+ }
}
wformat->Format.wBitsPerSample = 0;
@@ -436,9 +447,10 @@ static bool find_formats_exclusive(struct ao *ao, bool do_search)
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_EXCLUSIVE))
return false;
- MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (exclusive)\n",
+ MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (exclusive) -> %s\n",
mp_chmap_to_str(&ao->channels),
- af_fmt_to_str(ao->format), ao->samplerate);
+ af_fmt_to_str(ao->format), ao->samplerate,
+ waveformat_to_str(&wformat.Format));
return true;
}
@@ -479,9 +491,10 @@ static bool find_formats_shared(struct ao *ao)
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_SHARED))
return false;
- MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (shared)\n",
+ MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (shared) -> %s\n",
mp_chmap_to_str(&ao->channels),
- af_fmt_to_str(ao->format), ao->samplerate);
+ af_fmt_to_str(ao->format), ao->samplerate,
+ waveformat_to_str(&wformat.Format));
return true;
exit_label:
MP_ERR(state, "Error finding shared mode format: %s\n",
@@ -623,8 +636,6 @@ static HRESULT fix_format(struct ao *ao, bool align_hack)
REFERENCE_TIME bufferPeriod =
state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? bufferDuration : 0;
- ao->format = af_fmt_from_planar(ao->format);
-
MP_DBG(state, "IAudioClient::Initialize\n");
hr = IAudioClient_Initialize(state->pAudioClient,
state->share_mode,
@@ -923,6 +934,9 @@ HRESULT wasapi_thread_init(struct ao *ao)
int64_t retry_wait = 1;
bool align_hack = false;
HRESULT hr;
+
+ ao->format = af_fmt_from_planar(ao->format);
+
retry: ;
if (state->deviceID) {
hr = load_device(ao->log, &state->pDevice, state->deviceID);