summaryrefslogtreecommitdiffstats
path: root/audio/out
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2017-11-29 21:30:10 +0100
committerwm4 <wm4@nowhere>2017-11-29 21:30:51 +0100
commitd725630b5f5817287d44cb31c3c7b9d815c187db (patch)
tree6c54b58b8bb3a4bfc25e0eafb253266a3a10f766 /audio/out
parent9c909fbb4d4abeabe9ac6731cb0c3f966dacf0ff (diff)
downloadmpv-d725630b5f5817287d44cb31c3c7b9d815c187db.tar.bz2
mpv-d725630b5f5817287d44cb31c3c7b9d815c187db.tar.xz
audio: add audio softvol processing to AO
This does what af_volume used to do. Since we couldn't relicense it, just rewrite it. Since we don't have a new filter mechanism yet, and the libavfilter is too inconvenient, do applying the volume gain in ao.c directly. This is done before handling the audio data to the driver. Since push.c runs a separate thread, and pull.c is called asynchronously from the audio driver's thread, the volume value needs to be synchronized. There's no existing central mutex, so do some shit with atomics. Since there's no atomic_float type predefined (which is at least needed when using the legacy wrapper), do some nonsense about reinterpret casting the float value to an int for the purpose of atomic access. Not sure if using memcpy() is undefined behavior, but for now I don't care. The advantage of not using a filter is lower complexity (no filter auto insertion), and lower latency (gain processing is done after our internal audio buffer of at least 200ms). Disavdantages include inability to use native volume control _before_ other filters with custom filter chains, and the need to add new processing for each new sample type. Since this doesn't reuse any of the old GPL code, nor does indirectly rely on it, volume and replaygain handling now works in LGPL mode. How to process the gain is inspired by libavfilter's af_volume (LGPL). In particular, we use exactly the same rounding, and we quantize processing for integer sample types by 256 steps. Some of libavfilter's copyright may or may not apply, but I think not, and it's the same license anyway.
Diffstat (limited to 'audio/out')
-rw-r--r--audio/out/ao.c66
-rw-r--r--audio/out/ao.h1
-rw-r--r--audio/out/internal.h5
-rw-r--r--audio/out/pull.c2
-rw-r--r--audio/out/push.c1
5 files changed, 75 insertions, 0 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index da2dacdcae..c40eae1b92 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <math.h>
#include <assert.h>
#include "mpv_talloc.h"
@@ -159,6 +160,7 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
if (!ao->priv)
goto error;
+ ao_set_gain(ao, 1.0f);
return ao;
error:
talloc_free(ao);
@@ -638,6 +640,70 @@ void ao_print_devices(struct mpv_global *global, struct mp_log *log)
ao_hotplug_destroy(hp);
}
+void ao_set_gain(struct ao *ao, float gain)
+{
+ uint_least32_t v = 0;
+ assert(sizeof(gain) <= sizeof(v));
+ memcpy(&v, &gain, sizeof(gain));
+ atomic_store(&ao->gain_fi, v);
+}
+
+static float ao_get_gain(struct ao *ao)
+{
+ uint_least32_t v = atomic_load_explicit(&ao->gain_fi, memory_order_relaxed);
+ float gain;
+ assert(sizeof(gain) <= sizeof(v));
+ memcpy(&gain, &v, sizeof(gain));
+ return gain;
+}
+
+#define MUL_GAIN_i(d, num_samples, gain, low, center, high) \
+ for (int n = 0; n < (num_samples); n++) \
+ (d)[n] = MPCLAMP( \
+ ((((int64_t)((d)[n]) - (center)) * (gain) + 128) >> 8) + (center), \
+ (low), (high))
+
+#define MUL_GAIN_f(d, num_samples, gain) \
+ for (int n = 0; n < (num_samples); n++) \
+ (d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0)
+
+static void process_plane(struct ao *ao, void *data, int num_samples)
+{
+ float gain = ao_get_gain(ao);
+ int format = af_fmt_from_planar(ao->format);
+ if (gain == 1.0f)
+ return;
+ int gi = lrint(256.0 * gain);
+ switch (format) {
+ case AF_FORMAT_U8:
+ MUL_GAIN_i((uint8_t *)data, num_samples, gi, 0, 128, 255);
+ break;
+ case AF_FORMAT_S16:
+ MUL_GAIN_i((int16_t *)data, num_samples, gi, INT16_MIN, 0, INT16_MAX);
+ break;
+ case AF_FORMAT_S32:
+ MUL_GAIN_i((int32_t *)data, num_samples, gi, INT32_MIN, 0, INT32_MAX);
+ break;
+ case AF_FORMAT_FLOAT:
+ MUL_GAIN_f((float *)data, num_samples, gain);
+ break;
+ case AF_FORMAT_DOUBLE:
+ MUL_GAIN_f((double *)data, num_samples, gain);
+ break;
+ default:;
+ // all other sample formats are simply not supported
+ }
+}
+
+void ao_post_process_data(struct ao *ao, void **data, int num_samples)
+{
+ bool planar = af_fmt_is_planar(ao->format);
+ int planes = planar ? ao->channels.num : 1;
+ int plane_samples = num_samples * (planar ? 1: ao->channels.num);
+ for (int n = 0; n < planes; n++)
+ process_plane(ao, data[n], plane_samples);
+}
+
static int get_conv_type(struct ao_convert_fmt *fmt)
{
if (af_fmt_to_bytes(fmt->src_fmt) * 8 == fmt->dst_bits && !fmt->pad_msb)
diff --git a/audio/out/ao.h b/audio/out/ao.h
index 116733fb39..b9df4ebb00 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -95,6 +95,7 @@ const char *ao_get_description(struct ao *ao);
bool ao_untimed(struct ao *ao);
int ao_play(struct ao *ao, void **data, int samples, int flags);
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
+void ao_set_gain(struct ao *ao, float gain);
double ao_get_delay(struct ao *ao);
int ao_get_space(struct ao *ao);
void ao_reset(struct ao *ao);
diff --git a/audio/out/internal.h b/audio/out/internal.h
index a6fcf7c5f6..9826630108 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -70,6 +70,9 @@ struct ao {
// Internal events (use ao_request_reload(), ao_hotplug_event())
atomic_int events_;
+ // Float gain multiplicator, reinterpret-casted to int.
+ atomic_uint_least32_t gain_fi;
+
int buffer;
double def_buffer;
void *api_priv;
@@ -212,6 +215,8 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e);
+void ao_post_process_data(struct ao *ao, void **data, int num_samples);
+
struct ao_convert_fmt {
int src_fmt; // source AF_FORMAT_*
int channels; // number of channels
diff --git a/audio/out/pull.c b/audio/out/pull.c
index 89aa98d52f..a4aa53821e 100644
--- a/audio/out/pull.c
+++ b/audio/out/pull.c
@@ -179,6 +179,8 @@ end:
for (int n = 0; n < ao->num_planes; n++)
af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
+ ao_post_process_data(ao, data, samples);
+
return bytes / ao->sstride;
}
diff --git a/audio/out/push.c b/audio/out/push.c
index 8546ec816d..1f87481183 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -304,6 +304,7 @@ static void ao_play_data(struct ao *ao)
samples = samples / ao->period_size * ao->period_size;
}
MP_STATS(ao, "start ao fill");
+ ao_post_process_data(ao, (void **)planes, samples);
int r = 0;
if (samples)
r = ao->driver->play(ao, (void **)planes, samples, flags);