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>
|
2016-01-05 03:47:38 +00:00
|
|
|
#include <wchar.h>
|
2015-03-27 23:09:22 +00:00
|
|
|
#include <windows.h>
|
2015-03-31 09:33:24 +00:00
|
|
|
#include <errors.h>
|
2015-03-27 23:09:22 +00:00
|
|
|
#include <ksguid.h>
|
|
|
|
#include <ksmedia.h>
|
2014-03-09 23:13:36 +00:00
|
|
|
#include <avrt.h>
|
|
|
|
|
|
|
|
#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
|
|
|
|
2015-08-01 18:51:52 +00:00
|
|
|
#define MIXER_DEFAULT_LABEL L"mpv - video player"
|
2014-03-11 06:46:22 +00:00
|
|
|
|
2014-11-28 10:25:18 +00:00
|
|
|
DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName,
|
2014-03-09 23:13:36 +00:00
|
|
|
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
|
|
|
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
|
2014-11-28 10:25:18 +00:00
|
|
|
DEFINE_PROPERTYKEY(mp_PKEY_Device_DeviceDesc,
|
2014-03-09 23:13:36 +00:00
|
|
|
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
|
|
|
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2);
|
2015-04-02 07:30:43 +00:00
|
|
|
// CEA 861 subformats
|
|
|
|
// should work on vista
|
|
|
|
DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS,
|
|
|
|
0x00000008, 0x0000, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL,
|
|
|
|
0x00000092, 0x0000, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
// might require 7+
|
|
|
|
DEFINE_GUID(mp_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,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
DEFINE_GUID(mp_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,
|
|
|
|
0x0000000b, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
|
|
|
|
0x0000000c, 0x0cea, 0x0010, 0x80, 0x00,
|
|
|
|
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
|
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},
|
|
|
|
// aka S24 (with conversion) - untested, thus commented.
|
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},
|
|
|
|
{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},
|
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
|
|
|
}
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
// "solve" the under-determined inverse of format_to_subtype by assuming the
|
|
|
|
// input subtype is "special" (i.e. IEC61937)
|
2017-07-07 15:41:16 +00:00
|
|
|
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;
|
2015-04-02 07:30:43 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2014-03-09 23:13:36 +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-07 15:41:16 +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-07 15:41:16 +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-07 15:41:16 +00:00
|
|
|
wformat->SubFormat = *format_to_subtype(format.mp_format);
|
|
|
|
wformat->Samples.wValidBitsPerSample = format.used_msb;
|
2015-04-03 05:16:57 +00:00
|
|
|
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
|
2015-04-02 07:30:43 +00:00
|
|
|
update_waveformat_datarate(wformat);
|
2015-03-22 09:19:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
// This implicitly transforms all pcm formats to: interleaved / signed (except
|
|
|
|
// 8-bit is unsigned) / waveext channel order. "Special" formats should be
|
|
|
|
// exempt as they should already satisfy these properties.
|
2015-03-22 09:19:36 +00:00
|
|
|
static void set_waveformat_with_ao(WAVEFORMATEXTENSIBLE *wformat, struct ao *ao)
|
|
|
|
{
|
|
|
|
struct mp_chmap channels = ao->channels;
|
2017-07-07 15:41:44 +00:00
|
|
|
|
|
|
|
if (mp_chmap_is_unknown(&channels))
|
|
|
|
mp_chmap_from_channels(&channels, channels.num);
|
2015-03-22 09:19:36 +00:00
|
|
|
mp_chmap_reorder_to_waveext(&channels);
|
2017-07-07 15:41:44 +00:00
|
|
|
if (!mp_chmap_is_valid(&channels))
|
|
|
|
mp_chmap_from_channels(&channels, 2);
|
2015-03-22 09:19:36 +00:00
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
// 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++) {
|
2017-07-08 22:22:42 +00:00
|
|
|
if (wasapi_formats[i].mp_format == alt_formats[n]) {
|
2017-07-07 15:41:16 +00:00
|
|
|
format = wasapi_formats[i];
|
2017-07-08 22:22:42 +00:00
|
|
|
goto found_format;
|
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-08 22:22:42 +00:00
|
|
|
found_format:
|
2017-07-07 15:41:16 +00:00
|
|
|
|
|
|
|
set_waveformat(wformat, format, ao->samplerate, &channels);
|
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;
|
|
|
|
}
|
2017-07-07 15:41:16 +00:00
|
|
|
#define waveformat_to_str(wf) waveformat_to_str_buf((char[64]){0}, 64, (wf))
|
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;
|
2014-11-28 14:51:06 +00:00
|
|
|
return true;
|
2014-03-09 23:13:36 +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
|
|
|
}
|
|
|
|
|
2015-11-18 23:08:07 +00:00
|
|
|
// This works like try_format_exclusive(), but will try to fallback to the AC3
|
|
|
|
// format if the format is a non-AC3 passthrough format. *wformat will be
|
|
|
|
// adjusted accordingly.
|
|
|
|
static bool try_format_exclusive_with_spdif_fallback(struct ao *ao,
|
|
|
|
WAVEFORMATEXTENSIBLE *wformat)
|
|
|
|
{
|
|
|
|
if (try_format_exclusive(ao, wformat))
|
|
|
|
return true;
|
|
|
|
int special_format = special_subtype_to_format(&wformat->SubFormat);
|
|
|
|
if (special_format && special_format != AF_FORMAT_S_AC3) {
|
|
|
|
MP_VERBOSE(ao, "Retrying as AC3.\n");
|
|
|
|
wformat->SubFormat = *format_to_subtype(AF_FORMAT_S_AC3);
|
|
|
|
return try_format_exclusive(ao, wformat);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
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-07 15:41:16 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
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",
|
|
|
|
"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};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
static bool find_formats_exclusive(struct ao *ao, bool do_search)
|
2015-03-22 09:19:36 +00:00
|
|
|
{
|
|
|
|
WAVEFORMATEXTENSIBLE wformat;
|
|
|
|
set_waveformat_with_ao(&wformat, ao);
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
// Try the requested format as is. If that doesn't work, and the do_search
|
|
|
|
// argument is set, do the pcm format search.
|
2015-11-18 23:08:07 +00:00
|
|
|
if (!try_format_exclusive_with_spdif_fallback(ao, &wformat) &&
|
2015-04-02 07:30:43 +00:00
|
|
|
(!do_search || !search_channels(ao, &wformat)))
|
2015-03-22 09:19:36 +00:00
|
|
|
return false;
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_EXCLUSIVE))
|
2015-03-22 09:19:36 +00:00
|
|
|
return false;
|
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (exclusive) -> %s\n",
|
2015-03-22 09:19:36 +00:00
|
|
|
mp_chmap_to_str(&ao->channels),
|
2017-07-07 15:41:16 +00:00
|
|
|
af_fmt_to_str(ao->format), ao->samplerate,
|
|
|
|
waveformat_to_str(&wformat.Format));
|
2015-03-22 09:19:36 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool find_formats_shared(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;
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-03-22 09:19:36 +00:00
|
|
|
WAVEFORMATEXTENSIBLE wformat;
|
|
|
|
set_waveformat_with_ao(&wformat, ao);
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-03-22 09:19:36 +00:00
|
|
|
WAVEFORMATEX *closestMatch;
|
|
|
|
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
|
|
|
AUDCLNT_SHAREMODE_SHARED,
|
|
|
|
&wformat.Format, &closestMatch);
|
2016-11-30 16:44:59 +00:00
|
|
|
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
|
|
|
|
waveformat_to_str(&wformat.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:
|
|
|
|
break;
|
|
|
|
case S_FALSE:
|
|
|
|
waveformat_copy(&wformat, closestMatch);
|
|
|
|
CoTaskMemFree(closestMatch);
|
|
|
|
MP_VERBOSE(ao, "Closest match is %s\n",
|
|
|
|
waveformat_to_str(&wformat.Format));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
hr = IAudioClient_GetMixFormat(state->pAudioClient, &closestMatch);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
waveformat_copy(&wformat, closestMatch);
|
|
|
|
MP_VERBOSE(ao, "Fallback to mix format %s\n",
|
|
|
|
waveformat_to_str(&wformat.Format));
|
|
|
|
CoTaskMemFree(closestMatch);
|
|
|
|
}
|
|
|
|
|
2015-04-02 07:30:43 +00:00
|
|
|
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_SHARED))
|
2015-03-22 09:19:36 +00:00
|
|
|
return false;
|
|
|
|
|
2017-07-07 15:41:16 +00:00
|
|
|
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (shared) -> %s\n",
|
2015-03-22 09:19:36 +00:00
|
|
|
mp_chmap_to_str(&ao->channels),
|
2017-07-07 15:41:16 +00:00
|
|
|
af_fmt_to_str(ao->format), ao->samplerate,
|
|
|
|
waveformat_to_str(&wformat.Format));
|
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;
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2015-03-26 12:52:34 +00:00
|
|
|
if (state->opt_exclusive) {
|
2015-04-02 07:30:43 +00:00
|
|
|
// If exclusive is requested, try the requested format (which
|
|
|
|
// might be passthrough). If that fails, do a pcm format
|
|
|
|
// search.
|
|
|
|
return find_formats_exclusive(ao, true);
|
2015-06-26 21:06:37 +00:00
|
|
|
} else if (af_fmt_is_spdif(ao->format)) {
|
2015-04-02 07:30:43 +00:00
|
|
|
// If a passthrough format is requested, but exclusive mode
|
|
|
|
// was not explicitly set, try only the requested passthrough
|
|
|
|
// format in exclusive mode. Fall back on shared mode if that
|
|
|
|
// fails without doing the exclusive pcm format search.
|
|
|
|
if (find_formats_exclusive(ao, false))
|
|
|
|
return true;
|
2014-03-09 23:13:36 +00:00
|
|
|
}
|
2015-04-02 07:30:43 +00:00
|
|
|
// Default is to use shared mode
|
2015-03-22 09:19:36 +00:00
|
|
|
return find_formats_shared(ao);
|
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
|
|
|
}
|
|
|
|
|
2015-12-21 08:08:07 +00:00
|
|
|
static void init_session_display(struct wasapi_state *state) {
|
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);
|
|
|
|
|
2015-12-21 08:08:07 +00:00
|
|
|
wchar_t path[MAX_PATH+12] = {0};
|
2014-03-11 06:46:22 +00:00
|
|
|
GetModuleFileNameW(NULL, path, MAX_PATH);
|
2017-06-27 12:09:26 +00:00
|
|
|
wcscat(path, L",-IDI_ICON1");
|
2015-12-21 08:08:07 +00:00
|
|
|
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
|
|
|
|
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
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
hr = IAudioSessionControl_SetDisplayName(state->pSessionControl,
|
|
|
|
MIXER_DEFAULT_LABEL, NULL);
|
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);
|
2015-04-01 09:20:12 +00:00
|
|
|
MP_WARN(state, "Error setting audio session display name: %s\n",
|
|
|
|
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,
|
|
|
|
CLSCTX_ALL, NULL,
|
|
|
|
(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");
|
2017-06-28 16:32:01 +00:00
|
|
|
REFERENCE_TIME devicePeriod;
|
2015-12-21 00:43:23 +00:00
|
|
|
HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod,
|
|
|
|
NULL);
|
|
|
|
MP_VERBOSE(state, "Device period: %.2g ms\n",
|
|
|
|
(double) devicePeriod / 10000.0 );
|
|
|
|
|
2017-06-28 16:32:01 +00:00
|
|
|
REFERENCE_TIME bufferDuration = devicePeriod;
|
2016-02-19 08:27:12 +00:00
|
|
|
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);
|
|
|
|
}
|
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));
|
|
|
|
}
|
|
|
|
|
2017-06-28 16:32:01 +00:00
|
|
|
REFERENCE_TIME bufferPeriod =
|
|
|
|
state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? bufferDuration : 0;
|
|
|
|
|
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,
|
2014-11-18 12:33:49 +00:00
|
|
|
bufferPeriod,
|
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));
|
2014-11-18 12:17:20 +00:00
|
|
|
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n",
|
|
|
|
state->bufferFrameCount, (double) bufferDuration / 10000.0 );
|
2014-03-09 23:13:36 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
hr = init_clock(state);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
|
2015-12-21 08:08:07 +00:00
|
|
|
init_session_display(state);
|
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);
|
|
|
|
|
2015-12-21 00:43:23 +00:00
|
|
|
hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName,
|
|
|
|
&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(
|
|
|
|
&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
|
|
|
|
(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
|
|
|
}
|
|
|
|
|
2015-12-26 20:57:16 +00:00
|
|
|
static HRESULT load_device(struct mp_log *l,
|
|
|
|
IMMDevice **ppDevice, LPWSTR deviceID)
|
|
|
|
{
|
|
|
|
IMMDeviceEnumerator *pEnumerator = NULL;
|
|
|
|
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
|
|
|
&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);
|
2014-11-17 10:13:09 +00:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
HRESULT 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");
|
2014-11-14 13:59:22 +00:00
|
|
|
int64_t retry_wait = 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) {
|
|
|
|
hr = load_device(ao->log, &state->pDevice, state->deviceID);
|
|
|
|
EXIT_ON_ERROR(hr);
|
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,
|
2017-07-08 22:29:14 +00:00
|
|
|
CLSCTX_ALL, NULL,
|
|
|
|
(void **)&state->pAudioClient);
|
2017-06-27 12:45:30 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
} 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());
|
|
|
|
hr = E_FAIL;
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
}
|
|
|
|
if (lib) {
|
|
|
|
wuCreateDefaultAudioRenderer =
|
|
|
|
(void*)GetProcAddress(lib, "wuCreateDefaultAudioRenderer");
|
|
|
|
}
|
|
|
|
if (!wuCreateDefaultAudioRenderer) {
|
|
|
|
MP_ERR(ao, "Function not found.\n");
|
|
|
|
hr = E_FAIL;
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
}
|
|
|
|
IUnknown *res = NULL;
|
|
|
|
hr = wuCreateDefaultAudioRenderer(&res);
|
|
|
|
MP_VERBOSE(ao, "Device: %s %p\n", mp_HRESULT_to_str(hr), res);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
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);
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
}
|
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");
|
|
|
|
if (!find_formats(ao)) {
|
|
|
|
hr = E_FAIL;
|
|
|
|
EXIT_ON_ERROR(hr);
|
|
|
|
}
|
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:
|
|
|
|
if (align_hack)
|
|
|
|
break;
|
2017-06-28 16:28:19 +00:00
|
|
|
// According to MSDN, we must use this as base after the failure.
|
|
|
|
IAudioClient_GetBufferSize(state->pAudioClient,
|
|
|
|
&state->bufferFrameCount);
|
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:
|
|
|
|
if (retry_wait > 8)
|
|
|
|
break;
|
2014-11-14 13:59:22 +00:00
|
|
|
wasapi_thread_uninit(ao);
|
2014-11-18 01:16:46 +00:00
|
|
|
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
|
2014-11-14 13:59:22 +00:00
|
|
|
mp_sleep_us(retry_wait);
|
|
|
|
retry_wait *= 2;
|
|
|
|
goto retry;
|
|
|
|
}
|
2014-11-17 10:48:02 +00:00
|
|
|
EXIT_ON_ERROR(hr);
|
2014-03-11 05:50:49 +00:00
|
|
|
|
2014-11-17 10:48:02 +00:00
|
|
|
MP_DBG(ao, "Init wasapi thread done\n");
|
|
|
|
return S_OK;
|
2014-03-11 03:47:33 +00:00
|
|
|
exit_label:
|
2016-02-26 18:44:55 +00:00
|
|
|
MP_FATAL(state, "Error setting up audio thread: %s\n", mp_HRESULT_to_str(hr));
|
2014-11-17 10:48:02 +00:00
|
|
|
return 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
|
|
|
}
|