2014-03-09 23:13:36 +00:00
|
|
|
/*
|
|
|
|
* This file is part of mpv.
|
|
|
|
*
|
|
|
|
* Original author: Jonathan Yong <10walls@gmail.com>
|
|
|
|
*
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* mpv 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.
|
2014-03-09 23:13:36 +00:00
|
|
|
*
|
|
|
|
* mpv 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
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2014-03-09 23:13:36 +00:00
|
|
|
*
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2014-03-09 23:13:36 +00:00
|
|
|
*/
|
|
|
|
|
2014-11-18 12:17:20 +00:00
|
|
|
#include <math.h>
|
2023-12-02 05:12:50 +00:00
|
|
|
|
2015-03-27 23:09:22 +00:00
|
|
|
#include <windows.h>
|
2024-05-01 17:57:51 +00:00
|
|
|
// 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__
|
2023-12-02 05:12:50 +00:00
|
|
|
#include <mmreg.h>
|
2015-03-27 23:09:22 +00:00
|
|
|
#include <ksguid.h>
|
2024-05-01 17:57:51 +00:00
|
|
|
#else
|
|
|
|
#include <ks.h>
|
|
|
|
#include <ksguid.h>
|
|
|
|
#include <mmreg.h>
|
|
|
|
#endif
|
|
|
|
#include <mmsystem.h>
|
2015-03-27 23:09:22 +00:00
|
|
|
#include <ksmedia.h>
|
2014-03-09 23:13:36 +00:00
|
|
|
#include <avrt.h>
|
2023-12-02 05:12:50 +00:00
|
|
|
#include <propsys.h>
|
|
|
|
#include <functiondiscoverykeys_devpkey.h>
|
2014-03-09 23:13:36 +00:00
|
|
|
|
|
|
|
#include "audio/format.h"
|
2014-11-14 13:59:22 +00:00
|
|
|
#include "osdep/timer.h"
|
2016-01-05 03:47:38 +00:00
|
|
|
#include "osdep/io.h"
|
2016-02-07 12:27:46 +00:00
|
|
|
#include "osdep/strnlen.h"
|
2016-01-05 03:47:38 +00:00
|
|
|
#include "ao_wasapi.h"
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2024-05-01 21:25:42 +00:00
|
|
|
#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
|
|
|
|
|
2023-12-02 05:12:50 +00:00
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS,
|
|
|
|
WAVE_FORMAT_DTS, 0x0000, 0x0010, 0x80, 0x00,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL,
|
|
|
|
WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_AAC
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_AAC,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x00000006, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3,
|
2023-12-02 03:16:26 +00:00
|
|
|
0x00000005, 0x0cea, 0x0010, 0x80, 0x00,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x0000000a, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x0000000b, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP
|
|
|
|
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
|
2015-04-02 07:30:43 +00:00
|
|
|
0x0000000c, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
2023-12-02 05:12:50 +00:00
|
|
|
#endif
|
2015-04-02 07:30:43 +00:00
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
struct wasapi_sample_fmt {
|
|
|
|
int mp_format; // AF_FORMAT_*
|
|
|
|
int bits; // aka wBitsPerSample
|
|
|
|
int used_msb; // aka wValidBitsPerSample
|
2015-04-02 07:30:43 +00:00
|
|
|
const GUID *subtype;
|
|
|
|
};
|
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
// 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},
|
2017-08-07 21:40:19 +00:00
|
|
|
// aka S24 (with conversion on output)
|
2017-07-09 07:55:16 +00:00
|
|
|
{AF_FORMAT_S32, 24, 24, &KSDATAFORMAT_SUBTYPE_PCM},
|
2017-07-07 15:41:16 +00:00
|
|
|
{AF_FORMAT_FLOAT, 32, 32, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT},
|
2023-12-02 05:12:50 +00:00
|
|
|
{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},
|
2017-07-07 15:41:16 +00:00
|
|
|
{0},
|
2015-04-02 07:30:43 +00:00
|
|
|
};
|
|
|
|
|
2017-07-29 08:37:00 +00:00
|
|
|
static void wasapi_get_best_sample_formats(
|
|
|
|
int src_format, struct wasapi_sample_fmt *out_formats)
|
|
|
|
{
|
2018-01-23 23:02:13 +00:00
|
|
|
int mp_formats[AF_FORMAT_COUNT + 1];
|
2017-07-29 08:37:00 +00:00
|
|
|
af_get_best_sample_formats(src_format, mp_formats);
|
|
|
|
for (int n = 0; mp_formats[n]; n++) {
|
|
|
|
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
|
|
|
if (wasapi_formats[i].mp_format == mp_formats[n])
|
|
|
|
*out_formats++ = wasapi_formats[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*out_formats = (struct wasapi_sample_fmt) {0};
|
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
static const GUID *format_to_subtype(int format)
|
|
|
|
{
|
2017-07-07 15:41:16 +00:00
|
|
|
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
|
|
|
if (format == wasapi_formats[i].mp_format)
|
|
|
|
return wasapi_formats[i].subtype;
|
2015-04-02 07:30:43 +00:00
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
return &KSDATAFORMAT_SPECIFIER_NONE;
|
2015-04-02 07:30:43 +00:00
|
|
|
}
|
|
|
|
|
2014-11-28 12:01:10 +00:00
|
|
|
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
|
|
|
|
{
|
|
|
|
buf = mp_GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
|
|
|
|
size_t guid_len = strnlen(buf, buf_size);
|
2015-12-21 00:43:23 +00:00
|
|
|
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32,
|
|
|
|
(uint32_t) pkey->pid);
|
2014-11-28 12:01:10 +00:00
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
static void update_waveformat_datarate(WAVEFORMATEXTENSIBLE *wformat)
|
2015-03-22 09:19:36 +00:00
|
|
|
{
|
|
|
|
WAVEFORMATEX *wf = &wformat->Format;
|
|
|
|
wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample / 8;
|
|
|
|
wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
|
|
|
|
}
|
|
|
|
|
2014-12-25 05:39:30 +00:00
|
|
|
static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
|
2017-07-29 08:37:00 +00:00
|
|
|
struct wasapi_sample_fmt *format,
|
2014-12-25 05:39:30 +00:00
|
|
|
DWORD samplerate, struct mp_chmap *channels)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2015-04-03 05:16:57 +00:00
|
|
|
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
|
|
|
wformat->Format.nChannels = channels->num;
|
2014-03-09 23:13:36 +00:00
|
|
|
wformat->Format.nSamplesPerSec = samplerate;
|
2017-07-29 08:37:00 +00:00
|
|
|
wformat->Format.wBitsPerSample = format->bits;
|
2014-11-28 15:50:34 +00:00
|
|
|
wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
2015-04-02 07:30:43 +00:00
|
|
|
|
2017-07-29 08:37:00 +00:00
|
|
|
wformat->SubFormat = *format_to_subtype(format->mp_format);
|
|
|
|
wformat->Samples.wValidBitsPerSample = format->used_msb;
|
2023-02-02 19:02:57 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
update_waveformat_datarate(wformat);
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
// other wformat parameters must already be set with set_waveformat
|
2015-03-22 09:19:36 +00:00
|
|
|
static void change_waveformat_samplerate(WAVEFORMATEXTENSIBLE *wformat,
|
|
|
|
DWORD samplerate)
|
|
|
|
{
|
|
|
|
wformat->Format.nSamplesPerSec = samplerate;
|
2015-04-02 07:30:43 +00:00
|
|
|
update_waveformat_datarate(wformat);
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
// other wformat parameters must already be set with set_waveformat
|
2015-03-22 09:19:36 +00:00
|
|
|
static void change_waveformat_channels(WAVEFORMATEXTENSIBLE *wformat,
|
|
|
|
struct mp_chmap *channels)
|
|
|
|
{
|
|
|
|
wformat->Format.nChannels = channels->num;
|
2015-04-02 07:30:43 +00:00
|
|
|
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
|
|
|
|
update_waveformat_datarate(wformat);
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
static struct wasapi_sample_fmt format_from_waveformat(WAVEFORMATEX *wf)
|
2014-12-25 05:39:30 +00:00
|
|
|
{
|
2017-07-07 15:41:16 +00:00
|
|
|
struct wasapi_sample_fmt res = {0};
|
2014-12-25 05:39:30 +00:00
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
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;
|
2015-04-02 07:30:43 +00:00
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
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;
|
2015-04-02 07:30:43 +00:00
|
|
|
break;
|
2014-12-01 08:46:15 +00:00
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
|
|
|
|
return res;
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
static bool chmap_from_waveformat(struct mp_chmap *channels,
|
|
|
|
const WAVEFORMATEX *wf)
|
2015-03-22 09:19:36 +00:00
|
|
|
{
|
|
|
|
if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
|
|
|
WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf;
|
|
|
|
mp_chmap_from_waveext(channels, wformat->dwChannelMask);
|
|
|
|
} else {
|
|
|
|
mp_chmap_from_channels(channels, wf->nChannels);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channels->num != wf->nChannels) {
|
|
|
|
mp_chmap_from_str(channels, bstr0("empty"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf)
|
|
|
|
{
|
|
|
|
struct mp_chmap channels;
|
|
|
|
chmap_from_waveformat(&channels, wf);
|
2015-04-02 07:30:43 +00:00
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
struct wasapi_sample_fmt format = format_from_waveformat(wf);
|
2015-04-02 07:30:43 +00:00
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
snprintf(buf, buf_size, "%s %s (%d/%d bits) @ %uhz",
|
2015-12-21 00:43:23 +00:00
|
|
|
mp_chmap_to_str(&channels),
|
2017-07-07 15:41:16 +00:00
|
|
|
af_fmt_to_str(format.mp_format), format.bits, format.used_msb,
|
|
|
|
(unsigned) wf->nSamplesPerSec);
|
2015-03-22 09:19:36 +00:00
|
|
|
return buf;
|
|
|
|
}
|
2023-01-30 23:31:25 +00:00
|
|
|
#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)
|
2015-03-22 09:19:36 +00:00
|
|
|
|
2014-12-01 00:44:19 +00:00
|
|
|
static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src)
|
|
|
|
{
|
2015-03-26 12:52:34 +00:00
|
|
|
if (src->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
2014-12-01 00:44:19 +00:00
|
|
|
*dst = *(WAVEFORMATEXTENSIBLE *)src;
|
2015-03-26 12:52:34 +00:00
|
|
|
} else {
|
2014-12-01 00:44:19 +00:00
|
|
|
dst->Format = *src;
|
2015-03-26 12:52:34 +00:00
|
|
|
}
|
2014-12-01 00:44:19 +00:00
|
|
|
}
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
|
|
|
|
AUDCLNT_SHAREMODE share_mode)
|
2014-09-23 19:12:28 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2017-07-07 15:41:16 +00:00
|
|
|
struct wasapi_sample_fmt format = format_from_waveformat(wf);
|
|
|
|
if (!format.mp_format) {
|
2015-03-22 09:19:36 +00:00
|
|
|
MP_ERR(ao, "Unable to construct sample format from WAVEFORMAT %s\n",
|
|
|
|
waveformat_to_str(wf));
|
2014-11-28 14:51:06 +00:00
|
|
|
return false;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
2014-09-23 19:12:28 +00:00
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
// Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX
|
|
|
|
// correctly.
|
2017-07-07 15:41:16 +00:00
|
|
|
if (af_fmt_is_pcm(format.mp_format)) {
|
2015-04-02 07:30:43 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
|
|
|
|
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;
|
2015-04-02 07:30:43 +00:00
|
|
|
ao->samplerate = wf->nSamplesPerSec;
|
2017-07-07 15:41:16 +00:00
|
|
|
ao->format = format.mp_format;
|
2015-04-03 05:16:57 +00:00
|
|
|
ao->channels = channels;
|
2015-04-02 07:30:43 +00:00
|
|
|
}
|
2014-12-01 08:46:15 +00:00
|
|
|
waveformat_copy(&state->format, wf);
|
2015-04-02 07:30:43 +00:00
|
|
|
state->share_mode = share_mode;
|
2017-07-13 08:33:54 +00:00
|
|
|
|
|
|
|
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz -> %s (%s)\n",
|
|
|
|
mp_chmap_to_str(&ao->channels),
|
|
|
|
af_fmt_to_str(ao->format), ao->samplerate,
|
|
|
|
waveformat_to_str(wf),
|
|
|
|
state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE
|
|
|
|
? "exclusive" : "shared");
|
2014-11-28 14:51:06 +00:00
|
|
|
return true;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-29 20:31:25 +00:00
|
|
|
#define mp_format_res_str(hres) \
|
2020-03-19 07:13:51 +00:00
|
|
|
(SUCCEEDED(hres) ? ((hres) == S_OK) ? "ok" : "close" \
|
|
|
|
: ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
|
|
|
|
? "unsupported" : mp_HRESULT_to_str(hres))
|
2017-07-29 20:31:25 +00:00
|
|
|
|
2015-03-22 09:19:36 +00:00
|
|
|
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2014-03-09 23:13:36 +00:00
|
|
|
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
2015-03-22 09:19:36 +00:00
|
|
|
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
|
|
|
&wformat->Format, NULL);
|
2016-11-30 16:44:59 +00:00
|
|
|
MP_VERBOSE(ao, "Trying %s (exclusive) -> %s\n",
|
|
|
|
waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
|
2016-02-19 06:24:15 +00:00
|
|
|
return SUCCEEDED(hr);
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool search_sample_formats(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
|
|
|
|
int samplerate, struct mp_chmap *channels)
|
|
|
|
{
|
2017-07-29 08:37:00 +00:00
|
|
|
struct wasapi_sample_fmt alt_formats[MP_ARRAY_SIZE(wasapi_formats)];
|
|
|
|
wasapi_get_best_sample_formats(ao->format, alt_formats);
|
|
|
|
for (int n = 0; alt_formats[n].mp_format; n++) {
|
|
|
|
set_waveformat(wformat, &alt_formats[n], samplerate, channels);
|
|
|
|
if (try_format_exclusive(ao, wformat))
|
|
|
|
return true;
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wformat->Format.wBitsPerSample = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool search_samplerates(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
|
|
|
|
struct mp_chmap *channels)
|
|
|
|
{
|
2017-07-09 04:42:29 +00:00
|
|
|
// put common samplerates first so that we find format early
|
|
|
|
int try[] = {48000, 44100, 96000, 88200, 192000, 176400,
|
|
|
|
32000, 22050, 11025, 8000, 16000, 352800, 384000, 0};
|
2015-03-22 09:19:36 +00:00
|
|
|
|
|
|
|
// get a list of supported rates
|
|
|
|
int n = 0;
|
|
|
|
int supported[MP_ARRAY_SIZE(try)] = {0};
|
|
|
|
|
|
|
|
wformat->Format.wBitsPerSample = 0;
|
|
|
|
for (int i = 0; try[i]; i++) {
|
|
|
|
if (!wformat->Format.wBitsPerSample) {
|
|
|
|
if (search_sample_formats(ao, wformat, try[i], channels))
|
|
|
|
supported[n++] = try[i];
|
|
|
|
} else {
|
|
|
|
change_waveformat_samplerate(wformat, try[i]);
|
|
|
|
if (try_format_exclusive(ao, wformat))
|
|
|
|
supported[n++] = try[i];
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2016-03-17 06:41:30 +00:00
|
|
|
int samplerate = af_select_best_samplerate(ao->samplerate, supported);
|
|
|
|
if (samplerate > 0) {
|
|
|
|
change_waveformat_samplerate(wformat, samplerate);
|
2015-03-22 09:19:36 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise, this is probably an unsupported channel map
|
|
|
|
wformat->Format.nSamplesPerSec = 0;
|
2014-11-28 14:51:06 +00:00
|
|
|
return false;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 09:19:36 +00:00
|
|
|
static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2015-03-22 09:19:36 +00:00
|
|
|
struct mp_chmap_sel chmap_sel = {.tmp = state};
|
|
|
|
struct mp_chmap entry;
|
|
|
|
// put common layouts first so that we find sample rate/format early
|
|
|
|
char *channel_layouts[] =
|
2017-07-09 04:42:29 +00:00
|
|
|
{"stereo", "5.1", "7.1", "6.1", "mono", "2.1", "4.0", "5.0",
|
2015-03-22 09:19:36 +00:00
|
|
|
"3.0", "3.0(back)",
|
|
|
|
"quad", "quad(side)", "3.1",
|
|
|
|
"5.0(side)", "4.1",
|
2023-11-18 22:22:41 +00:00
|
|
|
"5.1(side)", "6.0", "6.0(front)", "hexagonal",
|
2015-03-22 09:19:36 +00:00
|
|
|
"6.1(back)", "6.1(front)", "7.0", "7.0(front)",
|
|
|
|
"7.1(wide)", "7.1(wide-side)", "7.1(rear)", "octagonal", NULL};
|
|
|
|
|
|
|
|
wformat->Format.nSamplesPerSec = 0;
|
|
|
|
for (int j = 0; channel_layouts[j]; j++) {
|
|
|
|
mp_chmap_from_str(&entry, bstr0(channel_layouts[j]));
|
|
|
|
if (!wformat->Format.nSamplesPerSec) {
|
2016-11-30 16:44:59 +00:00
|
|
|
if (search_samplerates(ao, wformat, &entry))
|
2015-03-22 09:19:36 +00:00
|
|
|
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
|
|
|
} else {
|
|
|
|
change_waveformat_channels(wformat, &entry);
|
2015-10-26 14:52:08 +00:00
|
|
|
if (try_format_exclusive(ao, wformat))
|
2015-03-22 09:19:36 +00:00
|
|
|
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = ao->channels;
|
2016-08-05 14:11:42 +00:00
|
|
|
if (ao_chmap_sel_adjust2(ao, &chmap_sel, &entry, !state->opt_exclusive)){
|
2015-03-22 09:19:36 +00:00
|
|
|
change_waveformat_channels(wformat, &entry);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_ERR(ao, "No suitable audio format found\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
static bool find_formats_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
2015-03-22 09:19:36 +00:00
|
|
|
{
|
2017-07-13 08:33:54 +00:00
|
|
|
// Try the specified format as is
|
|
|
|
if (try_format_exclusive(ao, wformat))
|
|
|
|
return true;
|
2015-03-22 09:19:36 +00:00
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
if (af_fmt_is_spdif(ao->format)) {
|
|
|
|
if (ao->format != AF_FORMAT_S_AC3) {
|
|
|
|
// If the requested format failed and it is passthrough, but not
|
|
|
|
// AC3, try lying and saying it is.
|
|
|
|
MP_VERBOSE(ao, "Retrying as AC3.\n");
|
|
|
|
wformat->SubFormat = *format_to_subtype(AF_FORMAT_S_AC3);
|
|
|
|
if (try_format_exclusive(ao, wformat))
|
|
|
|
return true;
|
|
|
|
}
|
2015-03-22 09:19:36 +00:00
|
|
|
return false;
|
2017-07-13 08:33:54 +00:00
|
|
|
}
|
2015-03-22 09:19:36 +00:00
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
// Fallback on the PCM format search
|
|
|
|
return search_channels(ao, wformat);
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
static bool find_formats_shared(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2020-03-19 07:45:30 +00:00
|
|
|
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);
|
2016-11-30 16:44:59 +00:00
|
|
|
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
|
2020-03-19 07:45:30 +00:00
|
|
|
waveformat_to_str(&try_format.Format), mp_format_res_str(hr));
|
2015-03-22 09:19:36 +00:00
|
|
|
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
|
|
|
switch (hr) {
|
|
|
|
case S_OK:
|
2020-03-19 07:45:30 +00:00
|
|
|
waveformat_copy(wformat, &try_format.Format);
|
2015-03-22 09:19:36 +00:00
|
|
|
break;
|
|
|
|
case S_FALSE:
|
2020-03-19 07:45:30 +00:00
|
|
|
waveformat_copy(wformat, mix_format);
|
|
|
|
CoTaskMemFree(mix_format);
|
2015-03-22 09:19:36 +00:00
|
|
|
MP_VERBOSE(ao, "Closest match is %s\n",
|
2017-07-13 08:33:54 +00:00
|
|
|
waveformat_to_str(&wformat->Format));
|
2015-03-22 09:19:36 +00:00
|
|
|
break;
|
|
|
|
default:
|
2020-03-19 07:45:30 +00:00
|
|
|
hr = IAudioClient_GetMixFormat(state->pAudioClient, &mix_format);
|
2015-03-22 09:19:36 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
2020-03-19 07:45:30 +00:00
|
|
|
waveformat_copy(wformat, mix_format);
|
|
|
|
CoTaskMemFree(mix_format);
|
2015-03-22 09:19:36 +00:00
|
|
|
MP_VERBOSE(ao, "Fallback to mix format %s\n",
|
2017-07-13 08:33:54 +00:00
|
|
|
waveformat_to_str(&wformat->Format));
|
2020-03-19 07:45:30 +00:00
|
|
|
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2014-11-28 17:53:38 +00:00
|
|
|
exit_label:
|
2015-12-21 00:43:23 +00:00
|
|
|
MP_ERR(state, "Error finding shared mode format: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
2014-12-01 08:09:44 +00:00
|
|
|
return false;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2014-11-28 14:51:06 +00:00
|
|
|
static bool find_formats(struct ao *ao)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2017-07-29 08:37:00 +00:00
|
|
|
struct mp_chmap channels = ao->channels;
|
|
|
|
|
|
|
|
if (mp_chmap_is_unknown(&channels))
|
|
|
|
mp_chmap_from_channels(&channels, channels.num);
|
|
|
|
mp_chmap_reorder_to_waveext(&channels);
|
|
|
|
if (!mp_chmap_is_valid(&channels))
|
|
|
|
mp_chmap_from_channels(&channels, 2);
|
|
|
|
|
|
|
|
struct wasapi_sample_fmt alt_formats[MP_ARRAY_SIZE(wasapi_formats)];
|
|
|
|
wasapi_get_best_sample_formats(ao->format, alt_formats);
|
|
|
|
struct wasapi_sample_fmt wasapi_format =
|
|
|
|
{AF_FORMAT_S16, 16, 16, &KSDATAFORMAT_SUBTYPE_PCM};;
|
|
|
|
if (alt_formats[0].mp_format)
|
|
|
|
wasapi_format = alt_formats[0];
|
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
AUDCLNT_SHAREMODE share_mode;
|
|
|
|
WAVEFORMATEXTENSIBLE wformat;
|
2017-07-29 08:37:00 +00:00
|
|
|
set_waveformat(&wformat, &wasapi_format, ao->samplerate, &channels);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2017-07-13 08:33:54 +00:00
|
|
|
if (state->opt_exclusive || af_fmt_is_spdif(ao->format)) {
|
|
|
|
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
|
|
|
|
if(!find_formats_exclusive(ao, &wformat))
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
share_mode = AUDCLNT_SHAREMODE_SHARED;
|
|
|
|
if(!find_formats_shared(ao, &wformat))
|
|
|
|
return false;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
2017-07-13 08:33:54 +00:00
|
|
|
|
|
|
|
return set_ao_format(ao, &wformat.Format, share_mode);
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
static HRESULT init_clock(struct wasapi_state *state) {
|
2015-03-31 21:12:24 +00:00
|
|
|
HRESULT hr = IAudioClient_GetService(state->pAudioClient,
|
|
|
|
&IID_IAudioClock,
|
|
|
|
(void **)&state->pAudioClock);
|
2014-03-09 23:13:36 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
hr = IAudioClock_GetFrequency(state->pAudioClock, &state->clock_frequency);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
|
|
|
QueryPerformanceFrequency(&state->qpc_frequency);
|
|
|
|
|
2014-05-20 23:04:47 +00:00
|
|
|
atomic_store(&state->sample_count, 0);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
MP_VERBOSE(state,
|
|
|
|
"IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n",
|
2014-11-17 10:48:02 +00:00
|
|
|
(uint64_t) state->clock_frequency);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
return S_OK;
|
2014-03-09 23:13:36 +00:00
|
|
|
exit_label:
|
2015-04-01 09:20:12 +00:00
|
|
|
MP_ERR(state, "Error obtaining the audio device's timing: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
2014-11-17 10:48:02 +00:00
|
|
|
return hr;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2023-06-19 17:52:10 +00:00
|
|
|
static void init_session_display(struct wasapi_state *state, const char *name) {
|
2015-03-31 21:12:24 +00:00
|
|
|
HRESULT hr = IAudioClient_GetService(state->pAudioClient,
|
|
|
|
&IID_IAudioSessionControl,
|
|
|
|
(void **)&state->pSessionControl);
|
2014-03-11 06:46:22 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2023-12-23 17:46:54 +00:00
|
|
|
wchar_t *path = talloc_array(NULL, wchar_t, MP_PATH_MAX);
|
|
|
|
GetModuleFileNameW(NULL, path, MP_PATH_MAX);
|
2015-12-21 08:08:07 +00:00
|
|
|
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
|
2023-12-23 17:46:54 +00:00
|
|
|
talloc_free(path);
|
2015-12-21 08:08:07 +00:00
|
|
|
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));
|
|
|
|
}
|
2014-03-11 06:46:22 +00:00
|
|
|
|
2023-06-19 17:52:10 +00:00
|
|
|
assert(name);
|
|
|
|
if (!name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
wchar_t *title = mp_from_utf8(NULL, name);
|
|
|
|
hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
|
|
|
talloc_free(title);
|
|
|
|
|
2014-03-11 06:46:22 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
2015-12-21 08:08:07 +00:00
|
|
|
return;
|
2014-03-11 06:46:22 +00:00
|
|
|
exit_label:
|
2015-12-21 08:08:07 +00:00
|
|
|
// if we got here then the session control is useless - release it
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(state->pSessionControl);
|
2023-06-19 17:52:10 +00:00
|
|
|
MP_WARN(state, "Error setting audio session name: %s\n",
|
2015-04-01 09:20:12 +00:00
|
|
|
mp_HRESULT_to_str(hr));
|
2015-12-21 08:08:07 +00:00
|
|
|
return;
|
2014-03-11 06:46:22 +00:00
|
|
|
}
|
|
|
|
|
2015-12-21 11:34:25 +00:00
|
|
|
static void init_volume_control(struct wasapi_state *state)
|
|
|
|
{
|
|
|
|
HRESULT hr;
|
|
|
|
if (state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
|
|
|
|
MP_DBG(state, "Activating pEndpointVolume interface\n");
|
|
|
|
hr = IMMDeviceActivator_Activate(state->pDevice,
|
|
|
|
&IID_IAudioEndpointVolume,
|
2024-06-07 11:07:07 +00:00
|
|
|
CLSCTX_INPROC_SERVER, NULL,
|
2015-12-21 11:34:25 +00:00
|
|
|
(void **)&state->pEndpointVolume);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
|
|
|
MP_DBG(state, "IAudioEndpointVolume::QueryHardwareSupport\n");
|
|
|
|
hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
|
|
|
|
&state->vol_hw_support);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
} else {
|
|
|
|
MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n");
|
|
|
|
hr = IAudioClient_GetService(state->pAudioClient,
|
|
|
|
&IID_ISimpleAudioVolume,
|
|
|
|
(void **)&state->pAudioVolume);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
exit_label:
|
|
|
|
state->vol_hw_support = 0;
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(state->pEndpointVolume);
|
|
|
|
SAFE_RELEASE(state->pAudioVolume);
|
2015-12-21 11:34:25 +00:00
|
|
|
MP_WARN(state, "Error setting up volume control: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
}
|
|
|
|
|
2017-06-28 16:28:19 +00:00
|
|
|
static HRESULT fix_format(struct ao *ao, bool align_hack)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2014-11-18 12:17:20 +00:00
|
|
|
MP_DBG(state, "IAudioClient::GetDevicePeriod\n");
|
2024-04-01 09:20:47 +00:00
|
|
|
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);
|
2015-12-21 00:43:23 +00:00
|
|
|
|
2024-04-13 07:10:19 +00:00
|
|
|
// 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
|
2024-04-01 09:20:47 +00:00
|
|
|
REFERENCE_TIME bufferDuration;
|
2016-02-19 08:27:12 +00:00
|
|
|
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
|
2024-04-13 07:10:19 +00:00
|
|
|
bufferDuration = 0;
|
2024-04-01 09:20:47 +00:00
|
|
|
} 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);
|
2016-02-19 08:27:12 +00:00
|
|
|
}
|
2015-12-21 00:43:23 +00:00
|
|
|
|
2017-06-28 16:28:19 +00:00
|
|
|
// handle unsupported buffer size if AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED was
|
|
|
|
// returned in a previous attempt. hopefully this shouldn't happen because
|
|
|
|
// of the above integer device period
|
|
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx
|
|
|
|
if (align_hack) {
|
|
|
|
bufferDuration = (REFERENCE_TIME) (0.5 +
|
|
|
|
(10000.0 * 1000 / state->format.Format.nSamplesPerSec
|
|
|
|
* state->bufferFrameCount));
|
|
|
|
}
|
|
|
|
|
2024-04-13 07:10:19 +00:00
|
|
|
if (state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE)
|
|
|
|
MP_VERBOSE(state, "Trying buffer duration %lld us\n", bufferDuration / 10);
|
2017-06-28 16:32:01 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(state, "IAudioClient::Initialize\n");
|
2014-03-09 23:13:36 +00:00
|
|
|
hr = IAudioClient_Initialize(state->pAudioClient,
|
|
|
|
state->share_mode,
|
|
|
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
2014-11-18 12:17:20 +00:00
|
|
|
bufferDuration,
|
2024-04-13 07:10:19 +00:00
|
|
|
bufferDuration,
|
2014-03-09 23:13:36 +00:00
|
|
|
&(state->format.Format),
|
|
|
|
NULL);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-11-17 10:48:02 +00:00
|
|
|
|
|
|
|
MP_DBG(state, "IAudioClient::Initialize pRenderClient\n");
|
2014-03-09 23:13:36 +00:00
|
|
|
hr = IAudioClient_GetService(state->pAudioClient,
|
|
|
|
&IID_IAudioRenderClient,
|
|
|
|
(void **)&state->pRenderClient);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-11-18 12:17:20 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n");
|
2015-03-29 00:12:48 +00:00
|
|
|
hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hWake);
|
2014-03-09 23:13:36 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
2014-11-18 12:17:20 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(state, "IAudioClient::Initialize IAudioClient_GetBufferSize\n");
|
2014-03-09 23:13:36 +00:00
|
|
|
hr = IAudioClient_GetBufferSize(state->pAudioClient,
|
|
|
|
&state->bufferFrameCount);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-11-18 12:17:20 +00:00
|
|
|
|
|
|
|
ao->device_buffer = state->bufferFrameCount;
|
2015-12-21 00:43:23 +00:00
|
|
|
bufferDuration = (REFERENCE_TIME) (0.5 +
|
|
|
|
(10000.0 * 1000 / state->format.Format.nSamplesPerSec
|
|
|
|
* state->bufferFrameCount));
|
2024-04-01 09:20:47 +00:00
|
|
|
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%lld us)\n",
|
|
|
|
state->bufferFrameCount, bufferDuration / 10);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
hr = init_clock(state);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2023-06-19 17:52:10 +00:00
|
|
|
init_session_display(state, ao->client_name);
|
2015-12-21 11:34:25 +00:00
|
|
|
init_volume_control(state);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2017-06-27 11:50:58 +00:00
|
|
|
#if !HAVE_UWP
|
2015-11-24 12:25:01 +00:00
|
|
|
state->hTask = AvSetMmThreadCharacteristics(L"Pro Audio", &(DWORD){0});
|
|
|
|
if (!state->hTask) {
|
|
|
|
MP_WARN(state, "Failed to set AV thread to Pro Audio: %s\n",
|
|
|
|
mp_LastError_to_str());
|
2014-11-28 18:43:48 +00:00
|
|
|
}
|
2017-06-27 11:50:58 +00:00
|
|
|
#endif
|
2014-11-28 18:43:48 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
return S_OK;
|
2014-03-09 23:13:36 +00:00
|
|
|
exit_label:
|
2015-04-01 09:20:12 +00:00
|
|
|
MP_ERR(state, "Error initializing device: %s\n", mp_HRESULT_to_str(hr));
|
2014-11-17 10:48:02 +00:00
|
|
|
return hr;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
struct device_desc {
|
|
|
|
LPWSTR deviceID;
|
|
|
|
char *id;
|
|
|
|
char *name;
|
|
|
|
};
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static char* get_device_name(struct mp_log *l, void *talloc_ctx, IMMDevice *pDevice)
|
|
|
|
{
|
2014-03-09 23:13:36 +00:00
|
|
|
char *namestr = NULL;
|
2015-12-26 20:57:16 +00:00
|
|
|
IPropertyStore *pProps = NULL;
|
|
|
|
PROPVARIANT devname;
|
|
|
|
PropVariantInit(&devname);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
|
|
|
HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2023-12-02 05:12:50 +00:00
|
|
|
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName,
|
2015-12-21 00:43:23 +00:00
|
|
|
&devname);
|
2014-03-09 23:13:36 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
namestr = mp_to_utf8(talloc_ctx, devname.pwszVal);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
|
|
|
exit_label:
|
2015-12-26 20:57:16 +00:00
|
|
|
if (FAILED(hr))
|
|
|
|
mp_warn(l, "Failed getting device name: %s\n", mp_HRESULT_to_str(hr));
|
2014-03-09 23:13:36 +00:00
|
|
|
PropVariantClear(&devname);
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(pProps);
|
2015-12-26 20:57:16 +00:00
|
|
|
return namestr ? namestr : talloc_strdup(talloc_ctx, "");
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static struct device_desc *get_device_desc(struct mp_log *l, IMMDevice *pDevice)
|
|
|
|
{
|
|
|
|
LPWSTR deviceID;
|
|
|
|
HRESULT hr = IMMDevice_GetId(pDevice, &deviceID);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
mp_err(l, "Failed getting device id: %s\n", mp_HRESULT_to_str(hr));
|
2014-03-09 23:13:36 +00:00
|
|
|
return NULL;
|
2015-12-26 20:57:16 +00:00
|
|
|
}
|
2016-01-05 00:53:39 +00:00
|
|
|
struct device_desc *d = talloc_zero(NULL, struct device_desc);
|
2015-12-26 20:57:16 +00:00
|
|
|
d->deviceID = talloc_memdup(d, deviceID,
|
|
|
|
(wcslen(deviceID) + 1) * sizeof(wchar_t));
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_DESTROY(deviceID, CoTaskMemFree(deviceID));
|
2015-12-26 20:57:16 +00:00
|
|
|
|
|
|
|
char *full_id = mp_to_utf8(NULL, d->deviceID);
|
|
|
|
bstr id = bstr0(full_id);
|
|
|
|
bstr_eatstart0(&id, "{0.0.0.00000000}.");
|
|
|
|
d->id = bstrdup0(d, id);
|
|
|
|
talloc_free(full_id);
|
|
|
|
|
|
|
|
d->name = get_device_name(l, d, pDevice);
|
|
|
|
return d;
|
|
|
|
}
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
struct enumerator {
|
|
|
|
struct mp_log *log;
|
|
|
|
IMMDeviceEnumerator *pEnumerator;
|
|
|
|
IMMDeviceCollection *pDevices;
|
2016-01-22 05:37:04 +00:00
|
|
|
UINT count;
|
2015-12-26 20:57:16 +00:00
|
|
|
};
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static void destroy_enumerator(struct enumerator *e)
|
|
|
|
{
|
|
|
|
if (!e)
|
|
|
|
return;
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(e->pDevices);
|
|
|
|
SAFE_RELEASE(e->pEnumerator);
|
2015-12-26 20:57:16 +00:00
|
|
|
talloc_free(e);
|
|
|
|
}
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static struct enumerator *create_enumerator(struct mp_log *log)
|
|
|
|
{
|
|
|
|
struct enumerator *e = talloc_zero(NULL, struct enumerator);
|
|
|
|
e->log = log;
|
|
|
|
HRESULT hr = CoCreateInstance(
|
2024-06-07 11:07:07 +00:00
|
|
|
&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator,
|
2015-12-26 20:57:16 +00:00
|
|
|
(void **)&e->pEnumerator);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
hr = IMMDeviceEnumerator_EnumAudioEndpoints(
|
|
|
|
e->pEnumerator, eRender, DEVICE_STATE_ACTIVE, &e->pDevices);
|
2014-03-09 23:13:36 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
hr = IMMDeviceCollection_GetCount(e->pDevices, &e->count);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
return e;
|
2014-03-09 23:13:36 +00:00
|
|
|
exit_label:
|
2015-12-26 20:57:16 +00:00
|
|
|
mp_err(log, "Error getting device enumerator: %s\n", mp_HRESULT_to_str(hr));
|
|
|
|
destroy_enumerator(e);
|
|
|
|
return NULL;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2016-01-22 05:37:04 +00:00
|
|
|
static struct device_desc *device_desc_for_num(struct enumerator *e, UINT i)
|
2015-12-26 20:57:16 +00:00
|
|
|
{
|
|
|
|
IMMDevice *pDevice = NULL;
|
|
|
|
HRESULT hr = IMMDeviceCollection_Item(e->pDevices, i, &pDevice);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_ERR(e, "Failed getting device #%d: %s\n", i, mp_HRESULT_to_str(hr));
|
|
|
|
return NULL;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
2015-12-26 20:57:16 +00:00
|
|
|
struct device_desc *d = get_device_desc(e->log, pDevice);
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(pDevice);
|
2015-12-26 20:57:16 +00:00
|
|
|
return d;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static struct device_desc *default_device_desc(struct enumerator *e)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
|
|
|
IMMDevice *pDevice = NULL;
|
2015-12-26 20:57:16 +00:00
|
|
|
HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
|
|
|
|
e->pEnumerator, eRender, eMultimedia, &pDevice);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_ERR(e, "Error from GetDefaultAudioEndpoint: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
struct device_desc *d = get_device_desc(e->log, pDevice);
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(pDevice);
|
2015-12-26 20:57:16 +00:00
|
|
|
return d;
|
|
|
|
}
|
2014-10-13 16:21:45 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
void wasapi_list_devs(struct ao *ao, struct ao_device_list *list)
|
|
|
|
{
|
|
|
|
struct enumerator *enumerator = create_enumerator(ao->log);
|
|
|
|
if (!enumerator)
|
|
|
|
return;
|
|
|
|
|
2016-01-22 05:37:04 +00:00
|
|
|
for (UINT i = 0; i < enumerator->count; i++) {
|
2015-12-26 20:57:16 +00:00
|
|
|
struct device_desc *d = device_desc_for_num(enumerator, i);
|
|
|
|
if (!d)
|
|
|
|
goto exit_label;
|
|
|
|
ao_device_list_add(list, ao, &(struct ao_device_desc){d->id, d->name});
|
|
|
|
talloc_free(d);
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exit_label:
|
2015-12-26 20:57:16 +00:00
|
|
|
destroy_enumerator(enumerator);
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 06:37:45 +00:00
|
|
|
static bool load_device(struct mp_log *l,
|
2015-12-26 20:57:16 +00:00
|
|
|
IMMDevice **ppDevice, LPWSTR deviceID)
|
|
|
|
{
|
|
|
|
IMMDeviceEnumerator *pEnumerator = NULL;
|
2024-06-07 11:07:07 +00:00
|
|
|
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER,
|
2015-12-26 20:57:16 +00:00
|
|
|
&IID_IMMDeviceEnumerator,
|
|
|
|
(void **)&pEnumerator);
|
2014-11-17 10:13:09 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice);
|
|
|
|
EXIT_ON_ERROR(hr);
|
2014-11-17 10:13:09 +00:00
|
|
|
|
|
|
|
exit_label:
|
2015-12-26 20:57:16 +00:00
|
|
|
if (FAILED(hr))
|
|
|
|
mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr));
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(pEnumerator);
|
2017-07-13 06:37:45 +00:00
|
|
|
return SUCCEEDED(hr);
|
2014-11-17 10:13:09 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 02:09:03 +00:00
|
|
|
static LPWSTR select_device(struct mp_log *l, struct device_desc *d)
|
|
|
|
{
|
|
|
|
if (!d)
|
|
|
|
return NULL;
|
|
|
|
mp_verbose(l, "Selecting device \'%s\' (%s)\n", d->id, d->name);
|
|
|
|
return talloc_memdup(NULL, d->deviceID,
|
|
|
|
(wcslen(d->deviceID) + 1) * sizeof(wchar_t));
|
|
|
|
}
|
|
|
|
|
2016-01-23 05:35:51 +00:00
|
|
|
bstr wasapi_get_specified_device_string(struct ao *ao)
|
2014-03-09 23:13:36 +00:00
|
|
|
{
|
2016-11-25 20:00:39 +00:00
|
|
|
return bstr_strip(bstr0(ao->device));
|
2016-01-23 05:35:51 +00:00
|
|
|
}
|
2015-12-26 20:57:16 +00:00
|
|
|
|
2016-01-28 08:45:38 +00:00
|
|
|
LPWSTR wasapi_find_deviceID(struct ao *ao)
|
2016-01-23 05:35:51 +00:00
|
|
|
{
|
|
|
|
LPWSTR deviceID = NULL;
|
|
|
|
bstr device = wasapi_get_specified_device_string(ao);
|
2015-12-26 20:57:16 +00:00
|
|
|
MP_DBG(ao, "Find device \'%.*s\'\n", BSTR_P(device));
|
|
|
|
|
|
|
|
struct device_desc *d = NULL;
|
|
|
|
struct enumerator *enumerator = create_enumerator(ao->log);
|
|
|
|
if (!enumerator)
|
|
|
|
goto exit_label;
|
|
|
|
|
2016-01-22 05:24:49 +00:00
|
|
|
if (!enumerator->count) {
|
|
|
|
MP_ERR(ao, "There are no playback devices available\n");
|
|
|
|
goto exit_label;
|
|
|
|
}
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
if (!device.len) {
|
2016-01-05 01:04:40 +00:00
|
|
|
MP_VERBOSE(ao, "No device specified. Selecting default.\n");
|
2015-12-26 20:57:16 +00:00
|
|
|
d = default_device_desc(enumerator);
|
2016-01-05 02:09:03 +00:00
|
|
|
deviceID = select_device(ao->log, d);
|
2015-12-26 20:57:16 +00:00
|
|
|
goto exit_label;
|
|
|
|
}
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
// try selecting by number
|
|
|
|
bstr rest;
|
|
|
|
long long devno = bstrtoll(device, &rest, 10);
|
2016-01-22 05:37:04 +00:00
|
|
|
if (!rest.len && 0 <= devno && devno < (long long)enumerator->count) {
|
2016-01-05 01:04:40 +00:00
|
|
|
MP_VERBOSE(ao, "Selecting device by number: #%lld\n", devno);
|
2015-12-26 20:57:16 +00:00
|
|
|
d = device_desc_for_num(enumerator, devno);
|
2016-01-05 02:09:03 +00:00
|
|
|
deviceID = select_device(ao->log, d);
|
2015-12-26 20:57:16 +00:00
|
|
|
goto exit_label;
|
|
|
|
}
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
// select by id or name
|
|
|
|
bstr_eatstart0(&device, "{0.0.0.00000000}.");
|
2016-01-22 05:37:04 +00:00
|
|
|
for (UINT i = 0; i < enumerator->count; i++) {
|
2015-12-26 20:57:16 +00:00
|
|
|
d = device_desc_for_num(enumerator, i);
|
2016-01-05 01:04:40 +00:00
|
|
|
if (!d)
|
2015-12-26 20:57:16 +00:00
|
|
|
goto exit_label;
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
if (bstrcmp(device, bstr_strip(bstr0(d->id))) == 0) {
|
|
|
|
MP_VERBOSE(ao, "Selecting device by id: \'%.*s\'\n", BSTR_P(device));
|
2016-01-05 02:09:03 +00:00
|
|
|
deviceID = select_device(ao->log, d);
|
2015-12-26 20:57:16 +00:00
|
|
|
goto exit_label;
|
|
|
|
}
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
if (bstrcmp(device, bstr_strip(bstr0(d->name))) == 0) {
|
2016-01-22 13:16:48 +00:00
|
|
|
if (!deviceID) {
|
2015-12-26 20:57:16 +00:00
|
|
|
MP_VERBOSE(ao, "Selecting device by name: \'%.*s\'\n", BSTR_P(device));
|
2016-01-05 02:09:03 +00:00
|
|
|
deviceID = select_device(ao->log, d);
|
2015-12-26 20:57:16 +00:00
|
|
|
} else {
|
|
|
|
MP_WARN(ao, "Multiple devices matched \'%.*s\'."
|
|
|
|
"Ignoring device \'%s\' (%s).\n",
|
|
|
|
BSTR_P(device), d->id, d->name);
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_DESTROY(d, talloc_free(d));
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 02:09:03 +00:00
|
|
|
if (!deviceID)
|
2015-12-26 20:57:16 +00:00
|
|
|
MP_ERR(ao, "Failed to find device \'%.*s\'\n", BSTR_P(device));
|
2014-03-09 23:13:36 +00:00
|
|
|
|
|
|
|
exit_label:
|
2015-12-26 20:57:16 +00:00
|
|
|
talloc_free(d);
|
|
|
|
destroy_enumerator(enumerator);
|
2016-01-05 02:09:03 +00:00
|
|
|
return deviceID;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-13 06:37:45 +00:00
|
|
|
bool wasapi_thread_init(struct ao *ao)
|
2014-03-11 03:47:33 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(ao, "Init wasapi thread\n");
|
2023-10-11 16:34:14 +00:00
|
|
|
int64_t retry_wait = MP_TIME_US_TO_NS(1);
|
2017-06-28 16:28:19 +00:00
|
|
|
bool align_hack = false;
|
2017-06-27 12:45:30 +00:00
|
|
|
HRESULT hr;
|
2017-07-07 15:41:16 +00:00
|
|
|
|
|
|
|
ao->format = af_fmt_from_planar(ao->format);
|
|
|
|
|
2017-07-08 22:29:14 +00:00
|
|
|
retry:
|
2017-06-27 12:45:30 +00:00
|
|
|
if (state->deviceID) {
|
2017-07-13 06:37:45 +00:00
|
|
|
if (!load_device(ao->log, &state->pDevice, state->deviceID))
|
|
|
|
return false;
|
2014-03-11 03:47:33 +00:00
|
|
|
|
2017-06-27 12:45:30 +00:00
|
|
|
MP_DBG(ao, "Activating pAudioClient interface\n");
|
|
|
|
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
|
2024-06-07 11:07:07 +00:00
|
|
|
CLSCTX_INPROC_SERVER, NULL,
|
2017-07-08 22:29:14 +00:00
|
|
|
(void **)&state->pAudioClient);
|
2017-07-13 06:37:45 +00:00
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_FATAL(ao, "Error activating device: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-27 12:45:30 +00:00
|
|
|
} else {
|
|
|
|
MP_VERBOSE(ao, "Trying UWP wrapper.\n");
|
|
|
|
|
|
|
|
HRESULT (*wuCreateDefaultAudioRenderer)(IUnknown **res) = NULL;
|
|
|
|
HANDLE lib = LoadLibraryW(L"wasapiuwp2.dll");
|
|
|
|
if (!lib) {
|
|
|
|
MP_ERR(ao, "Wrapper not found: %d\n", (int)GetLastError());
|
2017-07-13 06:37:45 +00:00
|
|
|
return false;
|
2017-06-27 12:45:30 +00:00
|
|
|
}
|
2017-07-13 06:37:45 +00:00
|
|
|
|
|
|
|
wuCreateDefaultAudioRenderer =
|
|
|
|
(void*)GetProcAddress(lib, "wuCreateDefaultAudioRenderer");
|
2017-06-27 12:45:30 +00:00
|
|
|
if (!wuCreateDefaultAudioRenderer) {
|
|
|
|
MP_ERR(ao, "Function not found.\n");
|
2017-07-13 06:37:45 +00:00
|
|
|
return false;
|
2017-06-27 12:45:30 +00:00
|
|
|
}
|
|
|
|
IUnknown *res = NULL;
|
|
|
|
hr = wuCreateDefaultAudioRenderer(&res);
|
|
|
|
MP_VERBOSE(ao, "Device: %s %p\n", mp_HRESULT_to_str(hr), res);
|
2017-07-13 06:37:45 +00:00
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_FATAL(ao, "Error activating device: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-27 12:45:30 +00:00
|
|
|
hr = IUnknown_QueryInterface(res, &IID_IAudioClient,
|
2015-12-21 00:43:23 +00:00
|
|
|
(void **)&state->pAudioClient);
|
2017-06-27 12:45:30 +00:00
|
|
|
IUnknown_Release(res);
|
2017-07-13 06:37:45 +00:00
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_FATAL(ao, "Failed to get UWP audio client: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-27 12:45:30 +00:00
|
|
|
}
|
2014-03-11 03:47:33 +00:00
|
|
|
|
2017-07-09 22:19:50 +00:00
|
|
|
// In the event of an align hack, we've already done this.
|
|
|
|
if (!align_hack) {
|
|
|
|
MP_DBG(ao, "Probing formats\n");
|
2017-07-13 06:37:45 +00:00
|
|
|
if (!find_formats(ao))
|
|
|
|
return false;
|
2014-11-17 10:48:02 +00:00
|
|
|
}
|
2014-03-11 05:50:49 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(ao, "Fixing format\n");
|
2017-06-28 16:28:19 +00:00
|
|
|
hr = fix_format(ao, align_hack);
|
2017-07-09 22:34:53 +00:00
|
|
|
switch (hr) {
|
|
|
|
case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
|
2017-07-13 06:37:45 +00:00
|
|
|
if (align_hack) {
|
|
|
|
MP_FATAL(ao, "Align hack failed\n");
|
2017-07-09 22:34:53 +00:00
|
|
|
break;
|
2017-07-13 06:37:45 +00:00
|
|
|
}
|
2017-06-28 16:28:19 +00:00
|
|
|
// According to MSDN, we must use this as base after the failure.
|
2017-07-13 06:37:45 +00:00
|
|
|
hr = IAudioClient_GetBufferSize(state->pAudioClient,
|
|
|
|
&state->bufferFrameCount);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
MP_FATAL(ao, "Error getting buffer size for align hack: %s\n",
|
|
|
|
mp_HRESULT_to_str(hr));
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-09 22:19:28 +00:00
|
|
|
wasapi_thread_uninit(ao);
|
2017-06-28 16:28:19 +00:00
|
|
|
align_hack = true;
|
|
|
|
MP_WARN(ao, "This appears to require a weird Windows 7 hack. Retrying.\n");
|
|
|
|
goto retry;
|
2017-07-09 22:34:53 +00:00
|
|
|
case AUDCLNT_E_DEVICE_IN_USE:
|
|
|
|
case AUDCLNT_E_DEVICE_INVALIDATED:
|
2023-10-11 16:34:14 +00:00
|
|
|
if (retry_wait > MP_TIME_US_TO_NS(8)) {
|
2017-07-13 06:37:45 +00:00
|
|
|
MP_FATAL(ao, "Bad device retry failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
2014-11-14 13:59:22 +00:00
|
|
|
wasapi_thread_uninit(ao);
|
2023-10-11 16:34:14 +00:00
|
|
|
MP_WARN(ao, "Retrying in %"PRId64" ns\n", retry_wait);
|
2023-09-29 22:24:21 +00:00
|
|
|
mp_sleep_ns(retry_wait);
|
2014-11-14 13:59:22 +00:00
|
|
|
retry_wait *= 2;
|
|
|
|
goto retry;
|
|
|
|
}
|
2017-07-13 06:37:45 +00:00
|
|
|
return SUCCEEDED(hr);
|
2014-03-11 03:47:33 +00:00
|
|
|
}
|
|
|
|
|
2014-11-14 16:03:12 +00:00
|
|
|
void wasapi_thread_uninit(struct ao *ao)
|
2014-03-11 03:47:33 +00:00
|
|
|
{
|
2015-03-31 21:12:24 +00:00
|
|
|
struct wasapi_state *state = ao->priv;
|
2015-03-29 00:12:48 +00:00
|
|
|
MP_DBG(ao, "Thread shutdown\n");
|
2014-11-15 06:47:28 +00:00
|
|
|
|
2014-03-11 03:47:33 +00:00
|
|
|
if (state->pAudioClient)
|
|
|
|
IAudioClient_Stop(state->pAudioClient);
|
2014-03-11 05:50:49 +00:00
|
|
|
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_RELEASE(state->pRenderClient);
|
|
|
|
SAFE_RELEASE(state->pAudioClock);
|
|
|
|
SAFE_RELEASE(state->pAudioVolume);
|
|
|
|
SAFE_RELEASE(state->pEndpointVolume);
|
|
|
|
SAFE_RELEASE(state->pSessionControl);
|
|
|
|
SAFE_RELEASE(state->pAudioClient);
|
|
|
|
SAFE_RELEASE(state->pDevice);
|
2017-06-27 11:50:58 +00:00
|
|
|
#if !HAVE_UWP
|
2017-01-29 12:59:24 +00:00
|
|
|
SAFE_DESTROY(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
2017-06-27 11:50:58 +00:00
|
|
|
#endif
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(ao, "Thread uninit done\n");
|
2014-03-11 03:47:33 +00:00
|
|
|
}
|