mirror of
https://github.com/mpv-player/mpv.git
synced 2025-12-28 05:33:14 +00:00
Merge branch 'wasapi_fix2'
This commit is contained in:
@@ -36,11 +36,6 @@
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
static double get_device_delay(struct wasapi_state *state) {
|
||||
UINT64 sample_count = atomic_load(&state->sample_count);
|
||||
UINT64 position, qpc_position;
|
||||
@@ -50,7 +45,8 @@ static double get_device_delay(struct wasapi_state *state) {
|
||||
case S_OK: case S_FALSE:
|
||||
break;
|
||||
default:
|
||||
MP_ERR(state, "IAudioClock::GetPosition returned %s\n", wasapi_explain_err(hr));
|
||||
MP_ERR(state, "IAudioClock::GetPosition returned %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
}
|
||||
|
||||
LARGE_INTEGER qpc_count;
|
||||
@@ -65,7 +61,7 @@ static double get_device_delay(struct wasapi_state *state) {
|
||||
double diff = sample_count - position;
|
||||
double delay = diff / state->format.Format.nSamplesPerSec;
|
||||
|
||||
MP_TRACE(state, "device delay: %g samples (%g ms)\n", diff, delay * 1000);
|
||||
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, delay * 1000);
|
||||
|
||||
return delay;
|
||||
}
|
||||
@@ -103,7 +99,10 @@ static void thread_feed(struct ao *ao)
|
||||
|
||||
return;
|
||||
exit_label:
|
||||
MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr);
|
||||
MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
MP_VERBOSE(ao, "Requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,20 +112,27 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
if (!ao || !ao->priv)
|
||||
return -1;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
if (wasapi_thread_init(ao))
|
||||
goto exit_label;
|
||||
int thread_ret;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
MSG msg;
|
||||
DWORD waitstatus = WAIT_FAILED;
|
||||
state->init_ret = wasapi_thread_init(ao);
|
||||
SetEvent(state->init_done);
|
||||
if (state->init_ret != S_OK) {
|
||||
thread_ret = -1;
|
||||
goto exit_label;
|
||||
}
|
||||
|
||||
|
||||
DWORD waitstatus;
|
||||
HANDLE playcontrol[] =
|
||||
{state->hUninit, state->hFeed, state->hForceFeed, NULL};
|
||||
MP_VERBOSE(ao, "Entering dispatch loop!\n");
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
while (1) { /* watch events */
|
||||
waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE,
|
||||
QS_POSTMESSAGE | QS_SENDMESSAGE);
|
||||
switch (waitstatus) {
|
||||
case WAIT_OBJECT_0: /*shutdown*/
|
||||
wasapi_thread_uninit(state);
|
||||
thread_ret = 0;
|
||||
goto exit_label;
|
||||
case (WAIT_OBJECT_0 + 1): /* feed */
|
||||
thread_feed(ao);
|
||||
@@ -136,20 +142,19 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
SetEvent(state->hFeedDone);
|
||||
break;
|
||||
case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
wasapi_dispatch();
|
||||
break;
|
||||
case WAIT_FAILED: /* ??? */
|
||||
return -1;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled case in thread loop");
|
||||
thread_ret = -1;
|
||||
goto exit_label;
|
||||
}
|
||||
}
|
||||
exit_label:
|
||||
/* dispatch any possible pending messages */
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
return state->init_ret;
|
||||
wasapi_thread_uninit(ao);
|
||||
|
||||
CoUninitialize();
|
||||
return thread_ret;
|
||||
}
|
||||
|
||||
static void closehandles(struct ao *ao)
|
||||
@@ -165,22 +170,22 @@ static void closehandles(struct ao *ao)
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
MP_VERBOSE(ao, "uninit!\n");
|
||||
MP_DBG(ao, "Uninit wasapi\n");
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
wasapi_release_proxies(state);
|
||||
SetEvent(state->hUninit);
|
||||
/* wait up to 10 seconds */
|
||||
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT)
|
||||
MP_ERR(ao, "audio loop thread refuses to abort!");
|
||||
MP_ERR(ao, "Audio loop thread refuses to abort");
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
closehandles(ao);
|
||||
MP_VERBOSE(ao, "uninit END!\n");
|
||||
MP_DBG(ao, "Uninit wasapi done\n");
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
MP_VERBOSE(ao, "init!\n");
|
||||
MP_DBG(ao, "Init wasapi\n");
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
@@ -212,23 +217,27 @@ static int init(struct ao *ao)
|
||||
/* failed to init events */
|
||||
return -1;
|
||||
}
|
||||
state->init_ret = -1;
|
||||
|
||||
state->init_ret = E_FAIL;
|
||||
state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
|
||||
if (!state->threadLoop) {
|
||||
/* failed to init thread */
|
||||
MP_ERR(ao, "fail to create thread!\n");
|
||||
MP_ERR(ao, "Failed to create thread\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */
|
||||
if (state->init_ret) {
|
||||
if (!ao->probing) {
|
||||
MP_ERR(ao, "thread_init failed!\n");
|
||||
}
|
||||
} else
|
||||
MP_VERBOSE(ao, "Init Done!\n");
|
||||
if (state->init_ret != S_OK) {
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "Received failure from audio thread\n");
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wasapi_setup_proxies(state);
|
||||
return state->init_ret;
|
||||
MP_DBG(ao, "Init wasapi done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
@@ -249,7 +258,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
/* check to see if user manually changed volume through mixer;
|
||||
this information is used in exclusive mode for restoring the mixer volume on uninit */
|
||||
if (state->audio_volume != state->previous_volume) {
|
||||
MP_VERBOSE(state, "mixer difference: %.2g now, expected %.2g\n",
|
||||
MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n",
|
||||
state->audio_volume, state->previous_volume);
|
||||
state->initial_volume = state->audio_volume;
|
||||
}
|
||||
@@ -342,7 +351,7 @@ const struct ao_driver audio_out_wasapi = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
//.reset = audio_reset, <- doesn't wait for audio callback to return
|
||||
.reset = audio_reset,
|
||||
.resume = audio_resume,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(wasapi_state),
|
||||
|
||||
@@ -30,12 +30,26 @@
|
||||
|
||||
#include "osdep/atomics.h"
|
||||
|
||||
typedef struct change_notify {
|
||||
IMMNotificationClient client; /* this must be first in the structure! */
|
||||
LPWSTR monitored; /* Monitored device */
|
||||
struct ao *ao;
|
||||
} change_notify;
|
||||
|
||||
HRESULT wasapi_change_init(struct ao* ao);
|
||||
void wasapi_change_uninit(struct ao* ao);
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
typedef struct wasapi_state {
|
||||
struct mp_log *log;
|
||||
HANDLE threadLoop;
|
||||
|
||||
/* Init phase */
|
||||
int init_ret;
|
||||
HRESULT init_ret;
|
||||
HANDLE init_done;
|
||||
int share_mode;
|
||||
|
||||
@@ -62,6 +76,8 @@ typedef struct wasapi_state {
|
||||
ISimpleAudioVolume *pAudioVolume;
|
||||
IAudioEndpointVolume *pEndpointVolume;
|
||||
IAudioSessionControl *pSessionControl;
|
||||
IMMDeviceEnumerator *pEnumerator;
|
||||
|
||||
HANDLE hFeed; /* wasapi event */
|
||||
HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */
|
||||
HANDLE hFeedDone; /* set only after a hForceFeed */
|
||||
@@ -100,6 +116,8 @@ typedef struct wasapi_state {
|
||||
HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
WINBOOL (WINAPI *pAvRevertMmThreadCharacteristics)(HANDLE);
|
||||
} VistaBlob;
|
||||
|
||||
change_notify change;
|
||||
} wasapi_state;
|
||||
|
||||
#endif
|
||||
|
||||
284
audio/out/ao_wasapi_changenotify.c
Executable file
284
audio/out/ao_wasapi_changenotify.c
Executable file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define COBJMACROS 1
|
||||
#define _WIN32_WINNT 0x600
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <wchar.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
#include "ao_wasapi_utils.h"
|
||||
|
||||
static int GUID_compare(const GUID *l, const GUID *r)
|
||||
{
|
||||
unsigned int i;
|
||||
if (l->Data1 != r->Data1) return 1;
|
||||
if (l->Data2 != r->Data2) return 1;
|
||||
if (l->Data3 != r->Data3) return 1;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (l->Data4[i] != r->Data4[i]) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int PKEY_compare(const PROPERTYKEY *l, const PROPERTYKEY *r)
|
||||
{
|
||||
if (GUID_compare(&l->fmtid, &r->fmtid)) return 1;
|
||||
if (l->pid != r->pid) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
|
||||
{
|
||||
snprintf(buf, buf_size,
|
||||
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
|
||||
guid->Data1, guid->Data2, guid->Data3,
|
||||
guid->Data4[0], guid->Data4[1],
|
||||
guid->Data4[2], guid->Data4[3],
|
||||
guid->Data4[4], guid->Data4[5],
|
||||
guid->Data4[6], guid->Data4[7]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
|
||||
{
|
||||
buf = GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
|
||||
size_t guid_len = strnlen(buf, buf_size);
|
||||
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t)pkey->pid );
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define PKEY_to_str(pkey) PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
|
||||
static char* ERole_to_str(ERole role)
|
||||
{
|
||||
switch(role){
|
||||
case eConsole: return "console";
|
||||
case eMultimedia: return "multimedia";
|
||||
case eCommunications: return "communications";
|
||||
default: return "<Unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
static char* EDataFlow_to_str(EDataFlow flow)
|
||||
{
|
||||
switch(flow){
|
||||
case eRender: return "render";
|
||||
case eCapture: return "capture";
|
||||
case eAll: return "all";
|
||||
default: return "<Unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
IMMNotificationClient* This, REFIID riid, void **ppvObject)
|
||||
{
|
||||
/* Compatible with IMMNotificationClient and IUnknown */
|
||||
if (!GUID_compare(&IID_IMMNotificationClient, riid) ||
|
||||
!GUID_compare(&IID_IUnknown, riid)) {
|
||||
*ppvObject = (void *)This;
|
||||
return S_OK;
|
||||
} else {
|
||||
*ppvObject = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
/* these are required, but not actually used */
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* MSDN says it should free itself, but we're static */
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId,
|
||||
DWORD dwNewState)
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)){
|
||||
switch (dwNewState) {
|
||||
case DEVICE_STATE_DISABLED:
|
||||
case DEVICE_STATE_NOTPRESENT:
|
||||
case DEVICE_STATE_UNPLUGGED:
|
||||
MP_VERBOSE(ao,
|
||||
"OnDeviceStateChange triggered - requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
case DEVICE_STATE_ACTIVE:
|
||||
default:
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
MP_VERBOSE(ao, "OnDeviceAdded triggered\n");
|
||||
if(pwstrDeviceId)
|
||||
MP_VERBOSE(ao, "New device %S\n",pwstrDeviceId);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* maybe MPV can go over to the prefered device once it is plugged in? */
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
|
||||
MP_VERBOSE(ao, "OnDeviceRemoved triggered - requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
|
||||
IMMNotificationClient *This,
|
||||
EDataFlow flow,
|
||||
ERole role,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered for role:%s flow:%s\n",
|
||||
ERole_to_str(role), EDataFlow_to_str(flow));
|
||||
if(pwstrDeviceId)
|
||||
MP_VERBOSE(ao, "New default device %S\n", pwstrDeviceId);
|
||||
|
||||
/* don't care about "eCapture" or non-"eMultimedia" roles */
|
||||
if ( flow == eCapture ||
|
||||
role != eMultimedia ) return S_OK;
|
||||
|
||||
/* stay on the device the user specified */
|
||||
if (state->opt_device) {
|
||||
MP_VERBOSE(ao, "Staying on specified device \"%s\"", state->opt_device);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* don't reload if already on the new default */
|
||||
if ( pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId) ){
|
||||
MP_VERBOSE(ao, "Already using default device, no reload required\n");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* if we got here, we need to reload */
|
||||
ao_request_reload(ao);
|
||||
MP_VERBOSE(ao, "Requesting ao reload\n");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId,
|
||||
const PROPERTYKEY key)
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
|
||||
MP_VERBOSE(ao, "OnPropertyValueChanged triggered\n");
|
||||
MP_VERBOSE(ao, "Changed property: ");
|
||||
if (!PKEY_compare(&PKEY_AudioEngine_DeviceFormat, &key)) {
|
||||
MP_VERBOSE(change->ao,
|
||||
"PKEY_AudioEngine_DeviceFormat - requesting ao reload\n");
|
||||
ao_request_reload(change->ao);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "%s\n", PKEY_to_str(&key));
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = {
|
||||
.QueryInterface = sIMMNotificationClient_QueryInterface,
|
||||
.AddRef = sIMMNotificationClient_AddRef,
|
||||
.Release = sIMMNotificationClient_Release,
|
||||
.OnDeviceStateChanged = sIMMNotificationClient_OnDeviceStateChanged,
|
||||
.OnDeviceAdded = sIMMNotificationClient_OnDeviceAdded,
|
||||
.OnDeviceRemoved = sIMMNotificationClient_OnDeviceRemoved,
|
||||
.OnDefaultDeviceChanged = sIMMNotificationClient_OnDefaultDeviceChanged,
|
||||
.OnPropertyValueChanged = sIMMNotificationClient_OnPropertyValueChanged,
|
||||
};
|
||||
|
||||
|
||||
HRESULT wasapi_change_init(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Setting up monitoring on playback device\n");
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
/* COM voodoo to emulate c++ class */
|
||||
change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl;
|
||||
|
||||
/* so the callbacks can access the ao */
|
||||
change->ao = ao;
|
||||
|
||||
/* get the device string to compare with the pwstrDeviceId argument in callbacks */
|
||||
HRESULT hr = IMMDevice_GetId(state->pDevice, &change->monitored);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
/* register the change notification client */
|
||||
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_VERBOSE(state, "Monitoring changes in device: %S\n", state->change.monitored);
|
||||
return hr;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error setting up device change monitoring: %s\n",
|
||||
wasapi_explain_err(hr));
|
||||
wasapi_change_uninit(ao);
|
||||
return hr;
|
||||
}
|
||||
|
||||
void wasapi_change_uninit(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
|
||||
if( state->pEnumerator && change->client.lpVtbl )
|
||||
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
|
||||
if (change->monitored) CoTaskMemFree(change->monitored);
|
||||
}
|
||||
@@ -30,14 +30,10 @@
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#define MIXER_DEFAULT_LABEL L"mpv - video player"
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#ifndef PKEY_Device_FriendlyName
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
||||
@@ -85,6 +81,10 @@ const char *wasapi_explain_err(const HRESULT hr)
|
||||
#define E(x) case x : return # x ;
|
||||
switch (hr) {
|
||||
E(S_OK)
|
||||
E(E_FAIL)
|
||||
E(E_OUTOFMEMORY)
|
||||
E(E_POINTER)
|
||||
E(E_INVALIDARG)
|
||||
E(AUDCLNT_E_NOT_INITIALIZED)
|
||||
E(AUDCLNT_E_ALREADY_INITIALIZED)
|
||||
E(AUDCLNT_E_WRONG_ENDPOINT_TYPE)
|
||||
@@ -160,7 +160,7 @@ static int set_ao_format(struct wasapi_state *state,
|
||||
WAVEFORMATEXTENSIBLE wformat)
|
||||
{
|
||||
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
|
||||
MP_ERR(ao, "unknown SubFormat %"PRIu32"\n",
|
||||
MP_ERR(ao, "Unknown SubFormat %"PRIu32"\n",
|
||||
(uint32_t)wformat.SubFormat.Data1);
|
||||
return 0;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ static int try_format(struct wasapi_state *state,
|
||||
if (!af_format)
|
||||
return 0;
|
||||
|
||||
MP_VERBOSE(ao, "trying %dch %s @ %dhz\n",
|
||||
MP_VERBOSE(ao, "Trying %dch %s @ %dhz\n",
|
||||
channels.num, af_fmt_to_str(af_format), samplerate);
|
||||
|
||||
union WAVEFMT u;
|
||||
@@ -220,7 +220,7 @@ static int try_format(struct wasapi_state *state,
|
||||
|
||||
if (hr == S_FALSE) {
|
||||
if (set_ao_format(state, ao, wformat)) {
|
||||
MP_VERBOSE(ao, "accepted as %dch %s @ %dhz\n",
|
||||
MP_VERBOSE(ao, "Accepted as %dch %s @ %dhz\n",
|
||||
ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate);
|
||||
|
||||
return 1;
|
||||
@@ -283,7 +283,7 @@ static int try_passthrough(struct wasapi_state *state,
|
||||
union WAVEFMT u;
|
||||
u.extensible = &wformat;
|
||||
|
||||
MP_VERBOSE(ao, "trying passthrough for %s...\n", af_fmt_to_str(ao->format));
|
||||
MP_VERBOSE(ao, "Trying passthrough for %s...\n", af_fmt_to_str(ao->format));
|
||||
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
state->share_mode,
|
||||
@@ -304,7 +304,7 @@ static int find_formats(struct ao *const ao)
|
||||
if (try_passthrough(state, ao))
|
||||
return 0;
|
||||
|
||||
MP_ERR(ao, "couldn't use passthrough!");
|
||||
MP_ERR(ao, "Couldn't use passthrough");
|
||||
if (!state->opt_exclusive)
|
||||
MP_ERR(ao, " (try exclusive mode)");
|
||||
MP_ERR(ao, "\n");
|
||||
@@ -326,7 +326,7 @@ static int find_formats(struct ao *const ao)
|
||||
return 0;
|
||||
}
|
||||
|
||||
MP_WARN(ao, "couldn't use default mix format!\n");
|
||||
MP_WARN(ao, "Couldn't use default mix format\n");
|
||||
}
|
||||
|
||||
/* Exclusive mode, we have to guess. */
|
||||
@@ -397,13 +397,13 @@ static int find_formats(struct ao *const ao)
|
||||
bits = start_bits;
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
} else {
|
||||
MP_ERR(ao, "couldn't find acceptable audio format!\n");
|
||||
MP_ERR(ao, "Couldn't find acceptable audio format\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int init_clock(struct wasapi_state *state) {
|
||||
static HRESULT init_clock(struct wasapi_state *state) {
|
||||
HRESULT hr;
|
||||
|
||||
hr = IAudioClient_GetService(state->pAudioClient,
|
||||
@@ -417,16 +417,17 @@ static int init_clock(struct wasapi_state *state) {
|
||||
|
||||
atomic_store(&state->sample_count, 0);
|
||||
|
||||
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency);
|
||||
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n",
|
||||
(uint64_t) state->clock_frequency);
|
||||
|
||||
return 0;
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "init_clock failed with %s, unable to obtain the audio device's timing!\n",
|
||||
wasapi_explain_err(hr));
|
||||
return 1;
|
||||
MP_ERR(state, "Error obtaining the audio device's timing: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static int init_session_display(struct wasapi_state *state) {
|
||||
static HRESULT init_session_display(struct wasapi_state *state) {
|
||||
HRESULT hr;
|
||||
wchar_t path[MAX_PATH+12] = {0};
|
||||
|
||||
@@ -443,15 +444,14 @@ static int init_session_display(struct wasapi_state *state) {
|
||||
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
return 0;
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "init_session_display failed with %s.\n",
|
||||
wasapi_explain_err(hr));
|
||||
return 1;
|
||||
MP_ERR(state, "Error setting audio session display name: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static int fix_format(struct wasapi_state *state)
|
||||
static HRESULT fix_format(struct wasapi_state *state)
|
||||
{
|
||||
HRESULT hr;
|
||||
double offset = 0.5;
|
||||
@@ -459,10 +459,13 @@ static int fix_format(struct wasapi_state *state)
|
||||
/* cargo cult code to negotiate buffer block size, affected by hardware/drivers combinations,
|
||||
gradually grow it to 10s, by 0.5s, consider failure if it still doesn't work
|
||||
*/
|
||||
MP_DBG(state, "IAudioClient::GetDevicePeriod\n");
|
||||
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,
|
||||
&state->defaultRequestedDuration,
|
||||
&state->minRequestedDuration);
|
||||
|
||||
reinit:
|
||||
MP_DBG(state, "IAudioClient::Initialize\n");
|
||||
hr = IAudioClient_Initialize(state->pAudioClient,
|
||||
state->share_mode,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
@@ -472,10 +475,12 @@ reinit:
|
||||
NULL);
|
||||
/* something about buffer sizes on Win7, fixme might loop forever */
|
||||
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n",
|
||||
wasapi_explain_err(hr), state->defaultRequestedDuration);
|
||||
if (offset > 10.0)
|
||||
goto exit_label; /* is 10 enough to break out of the loop?*/
|
||||
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr, state->defaultRequestedDuration);
|
||||
if (offset > 10.0) {
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
}
|
||||
IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount);
|
||||
state->defaultRequestedDuration =
|
||||
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
|
||||
@@ -489,19 +494,22 @@ reinit:
|
||||
goto reinit;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(state, "IAudioClient::Initialize pRenderClient\n");
|
||||
hr = IAudioClient_GetService(state->pAudioClient,
|
||||
&IID_IAudioRenderClient,
|
||||
(void **)&state->pRenderClient);
|
||||
EXIT_ON_ERROR(hr);
|
||||
MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n");
|
||||
hr = IAudioClient_GetService(state->pAudioClient,
|
||||
&IID_ISimpleAudioVolume,
|
||||
(void **) &state->pAudioVolume);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
if (!state->hFeed)
|
||||
goto exit_label;
|
||||
MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n");
|
||||
hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hFeed);
|
||||
EXIT_ON_ERROR(hr);
|
||||
MP_DBG(state, "IAudioClient::Initialize IAudioClient_GetBufferSize\n");
|
||||
hr = IAudioClient_GetBufferSize(state->pAudioClient,
|
||||
&state->bufferFrameCount);
|
||||
EXIT_ON_ERROR(hr);
|
||||
@@ -509,20 +517,22 @@ reinit:
|
||||
state->format.Format.wBitsPerSample / 8 *
|
||||
state->bufferFrameCount;
|
||||
|
||||
if (init_clock(state))
|
||||
return 1;
|
||||
if (init_session_display(state))
|
||||
return 1;
|
||||
hr = init_clock(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
hr = init_session_display(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
state->hTask =
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
||||
MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n",
|
||||
MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n",
|
||||
(long long) state->buffer_block_size);
|
||||
return 0;
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(state, "fix_format fails with %s, failed to determine buffer block size!\n",
|
||||
wasapi_explain_err(hr));
|
||||
return 1;
|
||||
MP_ERR(state, "Error initializing device: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static char* get_device_id(IMMDevice *pDevice) {
|
||||
@@ -635,7 +645,7 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
|
||||
eRender, eConsole,
|
||||
eRender, eMultimedia,
|
||||
&pDevice);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -683,8 +693,8 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
|
||||
talloc_free(defid);
|
||||
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
return hr;
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
talloc_free(defid);
|
||||
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
|
||||
@@ -708,17 +718,35 @@ int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
exit_label:
|
||||
mp_err(log, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n",
|
||||
(uint32_t)hr, wasapi_explain_err(hr));
|
||||
mp_err(log, "Error enumerating devices: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
CoUninitialize();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
char *search)
|
||||
static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
|
||||
IMMDevice **ppDevice)
|
||||
{
|
||||
HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
|
||||
eRender, eMultimedia,
|
||||
ppDevice);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
char *id = get_device_id(*ppDevice);
|
||||
MP_VERBOSE(ao, "Default device ID: %s\n", id);
|
||||
talloc_free(id);
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_ERR(ao , "Error loading default device: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
|
||||
IMMDevice **ppDevice, char *search)
|
||||
{
|
||||
HRESULT hr;
|
||||
IMMDeviceEnumerator *pEnumerator = NULL;
|
||||
IMMDeviceCollection *pDevices = NULL;
|
||||
IMMDevice *pTempDevice = NULL;
|
||||
LPWSTR deviceID = NULL;
|
||||
@@ -731,10 +759,6 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
devid = search;
|
||||
}
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
int search_err = 0;
|
||||
|
||||
if (devid == NULL) {
|
||||
@@ -746,16 +770,16 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
IMMDeviceCollection_GetCount(pDevices, &count);
|
||||
|
||||
if (devno >= count) {
|
||||
MP_ERR(ao, "no device #%d!\n", devno);
|
||||
MP_ERR(ao, "No device #%d\n", devno);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "finding device #%d\n", devno);
|
||||
MP_VERBOSE(ao, "Finding device #%d\n", devno);
|
||||
hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
hr = IMMDevice_GetId(pTempDevice, &deviceID);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_VERBOSE(ao, "found device #%d\n", devno);
|
||||
MP_VERBOSE(ao, "Found device #%d\n", devno);
|
||||
}
|
||||
} else {
|
||||
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender,
|
||||
@@ -766,7 +790,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
int count;
|
||||
IMMDeviceCollection_GetCount(pDevices, &count);
|
||||
|
||||
MP_VERBOSE(ao, "finding device %s\n", devid);
|
||||
MP_VERBOSE(ao, "Finding device %s\n", devid);
|
||||
|
||||
IMMDevice *prevDevice = NULL;
|
||||
|
||||
@@ -784,7 +808,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
if (deviceID) {
|
||||
char *name;
|
||||
if (!search_err) {
|
||||
MP_ERR(ao, "multiple matching devices found!\n");
|
||||
MP_ERR(ao, "Multiple matching devices found\n");
|
||||
name = get_device_name(prevDevice);
|
||||
MP_ERR(ao, "%s\n", name);
|
||||
talloc_free(name);
|
||||
@@ -803,7 +827,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
}
|
||||
|
||||
if (deviceID == NULL) {
|
||||
MP_ERR(ao, "could not find device %s!\n", devid);
|
||||
MP_ERR(ao, "Could not find device %s\n", devid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,19 +837,20 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
if (deviceID == NULL || search_err) {
|
||||
hr = E_NOTFOUND;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "loading device %S\n", deviceID);
|
||||
MP_VERBOSE(ao, "Loading device %S\n", deviceID);
|
||||
|
||||
hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(ao, "could not load requested device!\n");
|
||||
MP_ERR(ao, "Could not load requested device\n");
|
||||
}
|
||||
}
|
||||
|
||||
exit_label:
|
||||
SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice));
|
||||
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
|
||||
CoTaskMemFree(deviceID);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -837,7 +862,7 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
return M_OPT_EXIT;
|
||||
}
|
||||
|
||||
mp_dbg(log, "validating device=%s\n", param.start);
|
||||
mp_dbg(log, "Validating device=%s\n", param.start);
|
||||
|
||||
char *end;
|
||||
int devno = (int) strtol(param.start, &end, 10);
|
||||
@@ -870,8 +895,8 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
|
||||
|
||||
exit_label:
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "error reading COM proxy: %08x %s\n", (unsigned int)hr,
|
||||
wasapi_explain_err(hr));
|
||||
MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
@@ -906,81 +931,117 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
int wasapi_thread_init(struct ao *ao)
|
||||
void wasapi_dispatch(void)
|
||||
{
|
||||
/* dispatch any possible pending messages */
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
HRESULT wasapi_thread_init(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
HRESULT hr;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
MP_DBG(ao, "Init wasapi thread\n");
|
||||
int64_t retry_wait = 1;
|
||||
retry:
|
||||
state->initial_volume = -1.0;
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void**)&state->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
char *device = state->opt_device;
|
||||
if (!device || !device[0])
|
||||
device = ao->device;
|
||||
|
||||
if (!device || !device[0]) {
|
||||
IMMDeviceEnumerator *pEnumerator;
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
|
||||
eRender, eConsole,
|
||||
&state->pDevice);
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
|
||||
char *id = get_device_id(state->pDevice);
|
||||
MP_VERBOSE(ao, "default device ID: %s\n", id);
|
||||
talloc_free(id);
|
||||
} else {
|
||||
hr = find_and_load_device(ao, &state->pDevice, device);
|
||||
}
|
||||
if (!device || !device[0])
|
||||
hr = load_default_device(ao, state->pEnumerator, &state->pDevice);
|
||||
else
|
||||
hr = find_and_load_device(ao, state->pEnumerator, &state->pDevice, device);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
char *name = get_device_name(state->pDevice);
|
||||
MP_VERBOSE(ao, "device loaded: %s\n", name);
|
||||
MP_VERBOSE(ao, "Device loaded: %s\n", name);
|
||||
talloc_free(name);
|
||||
|
||||
MP_DBG(ao, "Activating pAudioClient interface\n");
|
||||
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
|
||||
CLSCTX_ALL, NULL, (void **)&state->pAudioClient);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(ao, "Activating pEndpointVolume interface\n");
|
||||
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioEndpointVolume,
|
||||
CLSCTX_ALL, NULL,
|
||||
(void **)&state->pEndpointVolume);
|
||||
EXIT_ON_ERROR(hr);
|
||||
IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
|
||||
&state->vol_hw_support);
|
||||
|
||||
state->init_ret = find_formats(ao); /* Probe support formats */
|
||||
if (state->init_ret)
|
||||
goto exit_label;
|
||||
if (!fix_format(state)) { /* now that we're sure what format to use */
|
||||
EXIT_ON_ERROR(create_proxies(state));
|
||||
MP_DBG(ao, "Query hardware volume support\n");
|
||||
hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
|
||||
&state->vol_hw_support);
|
||||
if ( hr != S_OK )
|
||||
MP_WARN(ao, "Query hardware volume control: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
|
||||
&state->initial_volume);
|
||||
else
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
|
||||
&state->initial_volume);
|
||||
|
||||
state->previous_volume = state->initial_volume;
|
||||
|
||||
MP_VERBOSE(ao, "thread_init OK!\n");
|
||||
SetEvent(state->init_done);
|
||||
return state->init_ret;
|
||||
MP_DBG(ao, "Probing formats\n");
|
||||
if (find_formats(ao)){
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
}
|
||||
|
||||
MP_DBG(ao, "Fixing format\n");
|
||||
hr = fix_format(state);
|
||||
if ( (hr == AUDCLNT_E_DEVICE_IN_USE ||
|
||||
hr == AUDCLNT_E_DEVICE_INVALIDATED) &&
|
||||
retry_wait <= 8 ) {
|
||||
wasapi_thread_uninit(ao);
|
||||
MP_WARN(ao, "Retrying in %ld us\n", retry_wait);
|
||||
mp_sleep_us(retry_wait);
|
||||
retry_wait *= 2;
|
||||
goto retry;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(ao, "Creating proxies\n");
|
||||
hr = create_proxies(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(ao, "Read volume levels\n");
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
|
||||
&state->initial_volume);
|
||||
else
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
|
||||
&state->initial_volume);
|
||||
|
||||
state->previous_volume = state->initial_volume;
|
||||
|
||||
wasapi_change_init(ao);
|
||||
|
||||
MP_DBG(ao, "Init wasapi thread done\n");
|
||||
return S_OK;
|
||||
exit_label:
|
||||
state->init_ret = -1;
|
||||
SetEvent(state->init_done);
|
||||
return -1;
|
||||
MP_ERR(state, "Error setting up audio thread: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
void wasapi_thread_uninit(wasapi_state *state)
|
||||
void wasapi_thread_uninit(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
|
||||
wasapi_dispatch();
|
||||
|
||||
if (state->pAudioClient)
|
||||
IAudioClient_Stop(state->pAudioClient);
|
||||
|
||||
if (state->opt_exclusive)
|
||||
wasapi_change_uninit(ao);
|
||||
|
||||
if (state->opt_exclusive &&
|
||||
state->pEndpointVolume &&
|
||||
state->initial_volume > 0 )
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolume,
|
||||
state->initial_volume, NULL);
|
||||
|
||||
@@ -991,9 +1052,9 @@ void wasapi_thread_uninit(wasapi_state *state)
|
||||
SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
|
||||
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
|
||||
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
|
||||
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
|
||||
|
||||
if (state->hTask)
|
||||
state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask);
|
||||
CoUninitialize();
|
||||
ExitThread(0);
|
||||
MP_DBG(ao, "Thread uninit done\n");
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
|
||||
int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param);
|
||||
|
||||
int wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(wasapi_state *state);
|
||||
|
||||
void wasapi_dispatch(void);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
HRESULT wasapi_setup_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
|
||||
@@ -153,6 +153,7 @@ def build(ctx):
|
||||
( "audio/out/ao_sndio.c", "sndio" ),
|
||||
( "audio/out/ao_wasapi.c", "wasapi" ),
|
||||
( "audio/out/ao_wasapi_utils.c", "wasapi" ),
|
||||
( "audio/out/ao_wasapi_changenotify.c", "wasapi" ),
|
||||
( "audio/out/pull.c" ),
|
||||
( "audio/out/push.c" ),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user