summaryrefslogtreecommitdiffstats
path: root/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'mixer.c')
-rw-r--r--mixer.c374
1 files changed, 177 insertions, 197 deletions
diff --git a/mixer.c b/mixer.c
index d115dc3e20..7be2179384 100644
--- a/mixer.c
+++ b/mixer.c
@@ -18,6 +18,8 @@
#include <string.h>
+#include <libavutil/common.h>
+
#include "config.h"
#include "libao2/audio_out.h"
#include "libaf/af.h"
@@ -25,243 +27,171 @@
#include "mixer.h"
-static void internal_setvolume(mixer_t *mixer, float l, float r);
-
-
-// Called after the audio filter chain is built or rebuilt.
-void mixer_reinit(mixer_t *mixer)
+static void checkvolume(struct mixer *mixer)
{
if (!mixer->ao)
return;
- // Some of this might be incorrect when the AO behavior changes (e.g.
- // different AO due to file specific -ao options), but we assume this
- // doesn't happen. We could attempt to handle this by trying to detect
- // whether a system mixer or mute is supported, but it would probably add
- // even more bugs to the code.
- if (mixer->restore_volume) {
- // restore previous volume (softvol, or no persistent AO volume)
- internal_setvolume(mixer, mixer->restore_vol_l, mixer->restore_vol_r);
- }
- if (mixer->muted &&
- (mixer->mute_emulation || mixer->ao->no_persistent_volume))
- {
- // undo mixer_uninit(), or restore mute state
- mixer_setmuted(mixer, true);
- }
- if (mixer->restore_balance) {
- // balance control always uses af_pan, it always needs to be restored
- mixer_setbalance(mixer, mixer->balance);
- }
-}
-// Called before the audio output is uninitialized.
-// Note that this doesn't necessarily terminate the mixer_t instance, and it's
-// possible that mixer_reinit() will be called later.
-void mixer_uninit(mixer_t *mixer)
-{
- if (!mixer->ao)
- return;
- // The player is supposed to restore the volume, when mute was enabled, and
- // the player terminates. No other attempts at restoring anything are done.
- // One complication is that the mute state should survive audio
- // reinitialization (e.g. when switching to a new file), so we have to be
- // sure mixer_reinit() will restore the mute state.
- // This is only needed when a global system mixer without mute control is
- // used, i.e. we emulate mute by setting the volume to 0.
- if (mixer->mute_emulation && mixer_getmuted(mixer)) {
- // avoid playing the rest of the audio buffer at restored volume
- ao_reset(mixer->ao);
- mixer_setmuted(mixer, false);
- mixer->muted = true;
- }
-}
-
-static void internal_getvolume(mixer_t *mixer, float *l, float *r)
-{
ao_control_vol_t vol;
- *l = 0;
- *r = 0;
- if (mixer->ao) {
- if (mixer->softvol ||
- CONTROL_OK != ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol))
- {
- if (!mixer->afilter)
- return;
- float db_vals[AF_NCH];
- if (!af_control_any_rev(mixer->afilter,
+ if (mixer->softvol || CONTROL_OK != ao_control(mixer->ao,
+ AOCONTROL_GET_VOLUME, &vol)) {
+ mixer->softvol = true;
+ if (!mixer->afilter)
+ return;
+ float db_vals[AF_NCH];
+ if (!af_control_any_rev(mixer->afilter,
AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_GET, db_vals))
- {
- db_vals[0] = db_vals[1] = 1.0;
- } else {
- af_from_dB(2, db_vals, db_vals, 20.0, -200.0, 60.0);
- }
- vol.left = (db_vals[0] / (mixer->softvol_max / 100.0)) * 100.0;
- vol.right = (db_vals[1] / (mixer->softvol_max / 100.0)) * 100.0;
- }
- *r = vol.right;
- *l = vol.left;
+ db_vals[0] = db_vals[1] = 1.0;
+ else
+ af_from_dB(2, db_vals, db_vals, 20.0, -200.0, 60.0);
+ vol.left = (db_vals[0] / (mixer->softvol_max / 100.0)) * 100.0;
+ vol.right = (db_vals[1] / (mixer->softvol_max / 100.0)) * 100.0;
}
+ float l = mixer->vol_l;
+ float r = mixer->vol_r;
+ if (mixer->muted_using_volume)
+ l = r = 0;
+ /* Try to detect cases where the volume has been changed by some external
+ * action (such as something else changing a shared system-wide volume).
+ * We don't test for exact equality, as some AOs may round the value
+ * we last set to some nearby supported value. 3 has been the default
+ * volume step for increase/decrease keys, and is apparently big enough
+ * to step to the next possible value in most setups.
+ */
+ if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) {
+ mixer->vol_l = vol.left;
+ mixer->vol_r = vol.right;
+ if (mixer->muted_using_volume)
+ mixer->muted = false;
+ }
+ if (!mixer->softvol)
+ // Rely on the value not changing if the query is not supported
+ ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted);
+ mixer->muted_by_us &= mixer->muted;
+ mixer->muted_using_volume &= mixer->muted;
}
-static float clip_vol(float v)
+void mixer_getvolume(mixer_t *mixer, float *l, float *r)
{
- return v > 100 ? 100 : (v < 0 ? 0 : v);
+ checkvolume(mixer);
+ *l = mixer->vol_l;
+ *r = mixer->vol_r;
}
-static void internal_setvolume(mixer_t *mixer, float l, float r)
+static void setvolume_internal(mixer_t *mixer, float l, float r)
{
- l = clip_vol(l);
- r = clip_vol(r);
- ao_control_vol_t vol;
- vol.right = r;
- vol.left = l;
- if (mixer->ao) {
- bool use_softvol = mixer->softvol;
- if (!use_softvol) {
- if (CONTROL_OK != ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol))
- {
- use_softvol = true;
- } else {
- mixer->restore_volume = mixer->ao->no_persistent_volume;
- }
- }
- if (use_softvol) {
- if (!mixer->afilter)
- return;
- // af_volume uses values in dB
- float db_vals[AF_NCH];
- int i;
- db_vals[0] = (l / 100.0) * (mixer->softvol_max / 100.0);
- db_vals[1] = (r / 100.0) * (mixer->softvol_max / 100.0);
- for (i = 2; i < AF_NCH; i++)
- db_vals[i] = ((l + r) / 100.0) * (mixer->softvol_max / 100.0)
- / 2.0;
- af_to_dB(AF_NCH, db_vals, db_vals, 20.0);
- if (!af_control_any_rev(mixer->afilter,
- AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, db_vals))
- {
- mp_tmsg(MSGT_GLOBAL, MSGL_INFO,
- "[Mixer] No hardware mixing, inserting volume filter.\n");
- if (!(af_add(mixer->afilter, "volume")
- && af_control_any_rev(mixer->afilter,
- AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, db_vals)))
- {
- mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
- "[Mixer] No volume control available.\n");
- return;
- }
- }
- mixer->restore_volume = true;
- }
- if (mixer->restore_volume) {
- mixer->restore_vol_l = l;
- mixer->restore_vol_r = r;
- }
+ struct ao_control_vol vol = {.left = l, .right = r};
+ if (!mixer->softvol) {
+ // relies on the driver data being permanent (so ptr stays valid)
+ mixer->restore_volume = mixer->ao->no_persistent_volume ?
+ mixer->ao->driver->info->short_name : NULL;
+ if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
+ mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
+ "[Mixer] Failed to change audio output volume.\n");
+ return;
+ }
+ mixer->restore_volume = "softvol";
+ if (!mixer->afilter)
+ return;
+ // af_volume uses values in dB
+ float db_vals[AF_NCH];
+ int i;
+ db_vals[0] = (l / 100.0) * (mixer->softvol_max / 100.0);
+ db_vals[1] = (r / 100.0) * (mixer->softvol_max / 100.0);
+ for (i = 2; i < AF_NCH; i++)
+ db_vals[i] = ((l + r) / 100.0) * (mixer->softvol_max / 100.0) / 2.0;
+ af_to_dB(AF_NCH, db_vals, db_vals, 20.0);
+ if (!af_control_any_rev(mixer->afilter,
+ AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET,
+ db_vals)) {
+ mp_tmsg(MSGT_GLOBAL, MSGL_INFO,
+ "[Mixer] No hardware mixing, inserting volume filter.\n");
+ if (!(af_add(mixer->afilter, "volume")
+ && af_control_any_rev(mixer->afilter,
+ AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET,
+ db_vals)))
+ mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
+ "[Mixer] No volume control available.\n");
}
}
void mixer_setvolume(mixer_t *mixer, float l, float r)
{
- internal_setvolume(mixer, l, r);
- // Changing the volume clears mute; these are mplayer semantics. (If this
- // is not desired, this would be removed, and code for restoring the softvol
- // volume had to be added.)
- mixer_setmuted(mixer, false);
-}
-
-void mixer_getvolume(mixer_t *mixer, float *l, float *r)
-{
- *l = 0;
- *r = 0;
- if (mixer->ao) {
- float real_l, real_r;
- internal_getvolume(mixer, &real_l, &real_r);
- // consider the case when the system mixer volumes change independently
- if (real_l != 0 || real_r != 0)
- mixer->muted = false;
- if (mixer->muted) {
- *l = mixer->last_l;
- *r = mixer->last_r;
- } else {
- *l = real_l;
- *r = real_r;
- }
- }
+ checkvolume(mixer); // to check mute status and AO support for volume
+ mixer->vol_l = av_clip(l, 0, 100);
+ mixer->vol_r = av_clip(r, 0, 100);
+ if (!mixer->ao || mixer->muted)
+ return;
+ setvolume_internal(mixer, mixer->vol_l, mixer->vol_r);
}
-static void mixer_addvolume(mixer_t *mixer, float d)
+void mixer_getbothvolume(mixer_t *mixer, float *b)
{
float mixer_l, mixer_r;
mixer_getvolume(mixer, &mixer_l, &mixer_r);
- mixer_setvolume(mixer, mixer_l + d, mixer_r + d);
-}
-
-void mixer_incvolume(mixer_t *mixer)
-{
- mixer_addvolume(mixer, +mixer->volstep);
+ *b = (mixer_l + mixer_r) / 2;
}
-void mixer_decvolume(mixer_t *mixer)
+void mixer_setmute(struct mixer *mixer, bool mute)
{
- mixer_addvolume(mixer, -mixer->volstep);
+ checkvolume(mixer);
+ if (mute != mixer->muted) {
+ if (!mixer->softvol && !mixer->muted_using_volume && ao_control(
+ mixer->ao, AOCONTROL_SET_MUTE, &mute) == CONTROL_OK) {
+ mixer->muted_using_volume = false;
+ } else {
+ setvolume_internal(mixer, mixer->vol_l*!mute, mixer->vol_r*!mute);
+ mixer->muted_using_volume = mute;
+ }
+ mixer->muted = mute;
+ mixer->muted_by_us = mute;
+ }
}
-void mixer_getbothvolume(mixer_t *mixer, float *b)
+bool mixer_getmute(struct mixer *mixer)
{
- float mixer_l, mixer_r;
- mixer_getvolume(mixer, &mixer_l, &mixer_r);
- *b = (mixer_l + mixer_r) / 2;
+ checkvolume(mixer);
+ return mixer->muted;
}
-void mixer_mute(mixer_t *mixer)
+static void addvolume(struct mixer *mixer, float d)
{
- mixer_setmuted(mixer, !mixer_getmuted(mixer));
+ checkvolume(mixer);
+ mixer_setvolume(mixer, mixer->vol_l + d, mixer->vol_r + d);
+ if (d > 0)
+ mixer_setmute(mixer, false);
}
-bool mixer_getmuted(mixer_t *mixer)
+void mixer_incvolume(mixer_t *mixer)
{
- ao_control_vol_t vol = {0};
- if (!mixer->softvol &&
- CONTROL_OK == ao_control(mixer->ao, AOCONTROL_GET_MUTE, &vol))
- {
- mixer->muted = vol.left == 0.0f || vol.right == 0.0f;
- } else {
- float l, r;
- mixer_getvolume(mixer, &l, &r); // updates mixer->muted
- }
- return mixer->muted;
+ addvolume(mixer, mixer->volstep);
}
-void mixer_setmuted(mixer_t *mixer, bool mute)
+void mixer_decvolume(mixer_t *mixer)
{
- bool muted = mixer_getmuted(mixer);
- if (mute == muted)
- return;
- ao_control_vol_t vol;
- vol.left = vol.right = mute ? 0.0f : 1.0f;
- mixer->mute_emulation = mixer->softvol ||
- CONTROL_OK != ao_control(mixer->ao, AOCONTROL_SET_MUTE, &vol);
- if (mixer->mute_emulation) {
- // mute is emulated by setting volume to 0
- if (!mute) {
- internal_setvolume(mixer, mixer->last_l, mixer->last_r);
- } else {
- mixer_getvolume(mixer, &mixer->last_l, &mixer->last_r);
- internal_setvolume(mixer, 0, 0);
- }
- }
- mixer->muted = mute;
+ addvolume(mixer, -mixer->volstep);
}
void mixer_getbalance(mixer_t *mixer, float *val)
{
- *val = 0.f;
- if (!mixer->afilter)
- return;
- af_control_any_rev(mixer->afilter, AF_CONTROL_PAN_BALANCE | AF_CONTROL_GET,
- val);
+ if (mixer->afilter)
+ af_control_any_rev(mixer->afilter,
+ AF_CONTROL_PAN_BALANCE | AF_CONTROL_GET,
+ &mixer->balance);
+ *val = mixer->balance;
}
+/* NOTE: Currently the balance code is seriously buggy: it always changes
+ * the af_pan mapping between the first two input channels and first two
+ * output channels to particular values. These values make sense for an
+ * af_pan instance that was automatically inserted for balance control
+ * only and is otherwise an identity transform, but if the filter was
+ * there for another reason, then ignoring and overriding the original
+ * values is completely wrong. In particular, this will break
+ * automatically inserted downmix filters; the original coefficients that
+ * are significantly below 1 will be overwritten with much higher values.
+ */
+
void mixer_setbalance(mixer_t *mixer, float val)
{
float level[AF_NCH];
@@ -269,20 +199,21 @@ void mixer_setbalance(mixer_t *mixer, float val)
af_control_ext_t arg_ext = { .arg = level };
af_instance_t *af_pan_balance;
+ mixer->balance = val;
+
if (!mixer->afilter)
return;
- mixer->balance = val;
- mixer->restore_balance = true;
-
if (af_control_any_rev(mixer->afilter,
AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val))
return;
+ if (val == 0 || mixer->ao->channels < 2)
+ return;
+
if (!(af_pan_balance = af_add(mixer->afilter, "pan"))) {
mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
"[Mixer] No balance control available.\n");
- mixer->restore_balance = false;
return;
}
@@ -301,3 +232,52 @@ void mixer_setbalance(mixer_t *mixer, float val)
af_pan_balance->control(af_pan_balance,
AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val);
}
+
+// Called after the audio filter chain is built or rebuilt.
+void mixer_reinit(struct mixer *mixer, struct ao *ao)
+{
+ mixer->ao = ao;
+ /* Use checkvolume() to see if softvol needs to be enabled because of
+ * lacking AO support, but first store values it could overwrite. */
+ float left = mixer->vol_l, right = mixer->vol_r;
+ bool muted = mixer->muted_by_us;
+ checkvolume(mixer);
+ /* Try to avoid restoring volume stored from one control method with
+ * another. Especially, restoring softvol volume (typically high) on
+ * system mixer could have very nasty effects. */
+ const char *restore_reason = mixer->softvol ? "softvol" :
+ mixer->ao->driver->info->short_name;
+ if (mixer->restore_volume && !strcmp(mixer->restore_volume,
+ restore_reason))
+ mixer_setvolume(mixer, left, right);
+ /* We turn mute off at AO uninit, so it has to be restored (unless
+ * we're reinitializing filter chain while keeping AO); but we only
+ * enable mute, not turn external mute off. */
+ if (muted)
+ mixer_setmute(mixer, true);
+ if (mixer->balance != 0)
+ mixer_setbalance(mixer, mixer->balance);
+}
+
+/* Called before uninitializing the audio output. The main purpose is to
+ * turn off mute, in case it's a global/persistent setting which might
+ * otherwise be left enabled even after this player instance exits.
+ */
+void mixer_uninit(struct mixer *mixer)
+{
+ checkvolume(mixer);
+ if (mixer->muted_by_us) {
+ /* Current audio output API combines playing the remaining buffered
+ * audio and uninitializing the AO into one operation, even though
+ * ideally unmute would happen between those two steps. We can't do
+ * volume changes after uninitialization, but we don't want the
+ * remaining audio to play at full volume either. Thus this
+ * workaround to drop remaining audio first. */
+ ao_reset(mixer->ao);
+ mixer_setmute(mixer, false);
+ /* We remember mute status and re-enable it if we play more audio
+ * in the same process. */
+ mixer->muted_by_us = true;
+ }
+ mixer->ao = NULL;
+}