mirror of
https://github.com/mpv-player/mpv
synced 2024-11-18 21:16:10 +01:00
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:
parent
f3e9b94622
commit
f8bdada77f
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user