From d725630b5f5817287d44cb31c3c7b9d815c187db Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 29 Nov 2017 21:30:10 +0100 Subject: [PATCH] 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. --- Copyright | 4 +- audio/filter/af.c | 1 - audio/filter/af.h | 2 - audio/out/ao.c | 66 ++++++++++++++++++++++++ audio/out/ao.h | 1 + audio/out/internal.h | 5 ++ audio/out/pull.c | 2 + audio/out/push.c | 1 + player/audio.c | 117 +++++++++++++++++++++---------------------- 9 files changed, 135 insertions(+), 64 deletions(-) diff --git a/Copyright b/Copyright index fc918c4be6..57b7a75cd9 100644 --- a/Copyright +++ b/Copyright @@ -34,8 +34,8 @@ a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+ by asking the MPlayer authors for permission. Since permission could not be obtained from everyone, LGPL mode disables the following features, some of them quite central: -- no audio filtering, which breaks: --volume, --af, replaygain, pitch - correction, fine control about downmix/upmix/resampling behavior +- no audio filtering, which breaks: --af, pitch correction, fine control over + downmix/upmix/resampling behavior - Linux X11 video output - BSD audio output via OSS - NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works) diff --git a/audio/filter/af.c b/audio/filter/af.c index dd78bb0cb5..5838c2e70b 100644 --- a/audio/filter/af.c +++ b/audio/filter/af.c @@ -167,7 +167,6 @@ static struct af_instance *af_create(struct af_stream *s, char *name, .log = mp_log_new(af, s->log, name), .opts = s->opts, .global = s->global, - .replaygain_data = s->replaygain_data, .out_pool = mp_audio_pool_create(af), }; struct m_config *config = diff --git a/audio/filter/af.h b/audio/filter/af.h index 58f67727a2..f27edee71a 100644 --- a/audio/filter/af.h +++ b/audio/filter/af.h @@ -61,7 +61,6 @@ struct af_instance { struct mp_log *log; struct MPOpts *opts; struct mpv_global *global; - struct replaygain_data *replaygain_data; int (*control)(struct af_instance *af, int cmd, void *arg); void (*uninit)(struct af_instance *af); /* Feed a frame. The frame is NULL if EOF was reached, and the filter @@ -105,7 +104,6 @@ struct af_stream { struct mp_log *log; struct MPOpts *opts; struct mpv_global *global; - struct replaygain_data *replaygain_data; }; // Return values 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 #include #include +#include #include #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); diff --git a/player/audio.c b/player/audio.c index e08259b1b5..e92d97ffd4 100644 --- a/player/audio.c +++ b/player/audio.c @@ -128,6 +128,57 @@ fail: mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL); } +static int recreate_audio_filters(struct MPContext *mpctx) +{ + assert(mpctx->ao_chain); + + struct af_stream *afs = mpctx->ao_chain->af; + if (afs->initialized < 1 && af_init(afs) < 0) + goto fail; + + recreate_speed_filters(mpctx); + if (afs->initialized < 1 && af_init(afs) < 0) + goto fail; + + if (mpctx->opts->softvol == SOFTVOL_NO) + MP_ERR(mpctx, "--softvol=no is not supported anymore.\n"); + + mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL); + + return 0; + +fail: + MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n"); + return -1; +} + +int reinit_audio_filters(struct MPContext *mpctx) +{ + struct ao_chain *ao_c = mpctx->ao_chain; + if (!ao_c) + return 0; + + double delay = 0; + if (ao_c->af->initialized > 0) + delay = af_calc_delay(ao_c->af); + + af_uninit(ao_c->af); + if (recreate_audio_filters(mpctx) < 0) + return -1; + + // Only force refresh if the amount of dropped buffered data is going to + // cause "issues" for the A/V sync logic. + if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2) + issue_refresh_seek(mpctx, MPSEEK_EXACT); + return 1; +} + +#else /* HAVE_LIBAV */ + +int reinit_audio_filters(struct MPContext *mpctx) { return 0; } + +#endif /* else HAVE_LIBAF */ + static double db_gain(double db) { return pow(10.0, db/20.0); @@ -136,11 +187,13 @@ static double db_gain(double db) static float compute_replaygain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; - struct ao_chain *ao_c = mpctx->ao_chain; float rgain = 1.0; - struct replaygain_data *rg = ao_c->af->replaygain_data; + struct replaygain_data *rg = NULL; + struct track *track = mpctx->current_track[0][STREAM_AUDIO]; + if (track) + rg = track->stream->codec->replaygain_data; if (opts->rgain_mode && rg) { MP_VERBOSE(mpctx, "Replaygain: Track=%f/%f Album=%f/%f\n", rg->track_gain, rg->track_peak, @@ -177,7 +230,7 @@ void audio_update_volume(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct ao_chain *ao_c = mpctx->ao_chain; - if (!ao_c || ao_c->af->initialized < 1) + if (!ao_c || !ao_c->ao) return; float gain = MPMAX(opts->softvol_volume / 100.0, 0); @@ -185,62 +238,10 @@ void audio_update_volume(struct MPContext *mpctx) gain *= compute_replaygain(mpctx); if (opts->softvol_mute == 1) gain = 0.0; + + ao_set_gain(ao_c->ao, gain); } -static int recreate_audio_filters(struct MPContext *mpctx) -{ - assert(mpctx->ao_chain); - - struct af_stream *afs = mpctx->ao_chain->af; - if (afs->initialized < 1 && af_init(afs) < 0) - goto fail; - - recreate_speed_filters(mpctx); - if (afs->initialized < 1 && af_init(afs) < 0) - goto fail; - - if (mpctx->opts->softvol == SOFTVOL_NO) - MP_ERR(mpctx, "--softvol=no is not supported anymore.\n"); - - audio_update_volume(mpctx); - - mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL); - - return 0; - -fail: - MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n"); - return -1; -} - -int reinit_audio_filters(struct MPContext *mpctx) -{ - struct ao_chain *ao_c = mpctx->ao_chain; - if (!ao_c) - return 0; - - double delay = 0; - if (ao_c->af->initialized > 0) - delay = af_calc_delay(ao_c->af); - - af_uninit(ao_c->af); - if (recreate_audio_filters(mpctx) < 0) - return -1; - - // Only force refresh if the amount of dropped buffered data is going to - // cause "issues" for the A/V sync logic. - if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2) - issue_refresh_seek(mpctx, MPSEEK_EXACT); - return 1; -} - -#else /* HAVE_LIBAV */ - -void audio_update_volume(struct MPContext *mpctx) {} -int reinit_audio_filters(struct MPContext *mpctx) { return 0; } - -#endif /* else HAVE_LIBAF */ - // Call this if opts->playback_speed or mpctx->speed_factor_* change. void update_playback_speed(struct MPContext *mpctx) { @@ -603,8 +604,6 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track) ao_c->log = mpctx->log; #if HAVE_LIBAF ao_c->af = af_new(mpctx->global); - if (track && track->stream) - ao_c->af->replaygain_data = track->stream->codec->replaygain_data; #else ao_c->conv = mp_aconverter_create(mpctx->global, mpctx->log, NULL); #endif