ao_wasapi: Implement per-application mixing

The volume controls in mpv now affect the session's volume (the
application's volume in the mixer). Since we do not request a
non-persistent session, the volume and mute status persist across mpv
invocations and system reboots.

In exclusive mode, WASAPI doesn't have access to a mixer so the endpoint
(sound card)'s master volume is modified instead. Since by definition
mpv is the only thing outputting audio in exclusive mode, this causes no
conflict, and ao_wasapi restores the last user-set volume when it's
uninitialized.
This commit is contained in:
Diogo Franco (Kovensky) 2014-03-11 02:50:49 -03:00
parent f3e9b94622
commit f8bdada77f
3 changed files with 95 additions and 16 deletions

View File

@ -195,6 +195,7 @@ static int init(struct ao *ao)
wasapi_enumerate_devices(state->log);
}
ao->per_application_mixer = true;
if (state->opt_exclusive) {
state->share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
} else {
@ -235,22 +236,54 @@ static int init(struct ao *ao)
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
if (!state->share_mode && !(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_VOLUME)) {
return CONTROL_UNKNOWN; /* hw does not support volume controls in exclusive mode */
}
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
BOOL mute;
switch (cmd) {
case AOCONTROL_GET_VOLUME:
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
&state->audio_volume);
if (state->opt_exclusive)
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
&state->audio_volume);
else
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolumeProxy,
&state->audio_volume);
/* 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",
state->audio_volume, state->previous_volume);
state->initial_volume = state->audio_volume;
}
vol->left = vol->right = 100.0f * state->audio_volume;
return CONTROL_OK;
case AOCONTROL_SET_VOLUME:
state->audio_volume = vol->left / 100.f;
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
state->audio_volume, NULL);
if (state->opt_exclusive)
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
state->audio_volume, NULL);
else
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolumeProxy,
state->audio_volume, NULL);
state->previous_volume = state->audio_volume;
return CONTROL_OK;
case AOCONTROL_GET_MUTE:
if (state->opt_exclusive)
IAudioEndpointVolume_GetMute(state->pEndpointVolumeProxy, &mute);
else
ISimpleAudioVolume_GetMute(state->pAudioVolumeProxy, &mute);
*(bool*)arg = mute;
return CONTROL_OK;
case AOCONTROL_SET_MUTE:
mute = *(bool*)arg;
if (state->opt_exclusive)
IAudioEndpointVolume_SetMute(state->pEndpointVolumeProxy, mute, NULL);
else
ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL);
return CONTROL_OK;
default:
return CONTROL_UNKNOWN;

View File

@ -41,6 +41,8 @@ typedef struct wasapi_state {
/* volume control */
DWORD vol_hw_support, status;
float audio_volume;
float previous_volume;
float initial_volume;
/* Buffers */
size_t buffer_block_size; /* Size of each block in bytes */
@ -54,6 +56,7 @@ typedef struct wasapi_state {
IMMDevice *pDevice;
IAudioClient *pAudioClient;
IAudioRenderClient *pRenderClient;
ISimpleAudioVolume *pAudioVolume;
IAudioEndpointVolume *pEndpointVolume;
HANDLE hFeed; /* wasapi event */
HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */
@ -65,12 +68,14 @@ typedef struct wasapi_state {
/* WASAPI proxy handles, for Single-Threaded Apartment communication.
One is needed for each object that's accessed by a different thread. */
IAudioClient *pAudioClientProxy;
ISimpleAudioVolume *pAudioVolumeProxy;
IAudioEndpointVolume *pEndpointVolumeProxy;
/* Streams used to marshal the proxy objects. The thread owning the actual objects
needs to marshal proxy objects into these streams, and the thread that wants the
proxies unmarshals them from here. */
IStream *sAudioClient;
IStream *sAudioVolume;
IStream *sEndpointVolume;
/* WASAPI internal clock information, for estimating delay */

View File

@ -506,6 +506,11 @@ reinit:
&IID_IAudioRenderClient,
(void **)&state->pRenderClient);
EXIT_ON_ERROR(hr);
hr = IAudioClient_GetService(state->pAudioClient,
&IID_ISimpleAudioVolume,
(void **) &state->pAudioVolume);
EXIT_ON_ERROR(hr);
if (!state->hFeed)
goto exit_label;
hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hFeed);
@ -857,12 +862,21 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
HRESULT hr;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hr = CoGetInterfaceAndReleaseStream(state->sAudioClient,
&IID_IAudioClient,
(void**) &state->pAudioClientProxy);
state->sAudioClient = NULL;
EXIT_ON_ERROR(hr);
hr = CoGetInterfaceAndReleaseStream(state->sAudioVolume,
&IID_ISimpleAudioVolume,
(void**) &state->pAudioVolumeProxy);
state->sAudioVolume = NULL;
EXIT_ON_ERROR(hr);
hr = CoGetInterfaceAndReleaseStream(state->sEndpointVolume,
&IID_IAudioEndpointVolume,
(void**) &state->pEndpointVolumeProxy);
@ -870,12 +884,18 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
EXIT_ON_ERROR(hr);
exit_label:
if (hr != S_OK) {
MP_ERR(state, "error reading COM proxy: %08x %s\n", hr, wasapi_explain_err(hr));
}
return hr;
}
void wasapi_release_proxies(wasapi_state *state) {
SAFE_RELEASE(state->pAudioClientProxy, IUnknown_Release(state->pAudioClientProxy));
SAFE_RELEASE(state->pAudioVolumeProxy, IUnknown_Release(state->pAudioVolumeProxy));
SAFE_RELEASE(state->pEndpointVolumeProxy, IUnknown_Release(state->pEndpointVolumeProxy));
CoUninitialize();
}
static HRESULT create_proxies(struct wasapi_state *state) {
@ -888,6 +908,13 @@ static HRESULT create_proxies(struct wasapi_state *state) {
&state->sAudioClient);
EXIT_ON_ERROR(hr);
hr = CreateStreamOnHGlobal(NULL, TRUE, &state->sAudioVolume);
EXIT_ON_ERROR(hr);
hr = CoMarshalInterThreadInterfaceInStream(&IID_ISimpleAudioVolume,
(IUnknown*) state->pAudioVolume,
&state->sAudioVolume);
EXIT_ON_ERROR(hr);
hr = CreateStreamOnHGlobal(NULL, TRUE, &state->sEndpointVolume);
EXIT_ON_ERROR(hr);
hr = CoMarshalInterThreadInterfaceInStream(&IID_IAudioEndpointVolume,
@ -942,6 +969,16 @@ int wasapi_thread_init(struct ao *ao)
goto exit_label;
if (!fix_format(state)) { /* now that we're sure what format to use */
EXIT_ON_ERROR(create_proxies(state));
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;
@ -956,14 +993,18 @@ void wasapi_thread_uninit(wasapi_state *state)
{
if (state->pAudioClient)
IAudioClient_Stop(state->pAudioClient);
if (state->pRenderClient)
IAudioRenderClient_Release(state->pRenderClient);
if (state->pAudioClock)
IAudioClock_Release(state->pAudioClock);
if (state->pAudioClient)
IAudioClient_Release(state->pAudioClient);
if (state->pDevice)
IMMDevice_Release(state->pDevice);
if (state->opt_exclusive)
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolume,
state->initial_volume, NULL);
SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
SAFE_RELEASE(state->pEndpointVolume, IAudioEndpointVolume_Release(state->pEndpointVolume));
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
if (state->hTask)
state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask);
CoUninitialize();