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.
This commit is contained in:
wm4 2017-11-29 21:30:10 +01:00
parent 9c909fbb4d
commit d725630b5f
9 changed files with 135 additions and 64 deletions

View File

@ -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)

View File

@ -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 =

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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