* audio volume filter
*/
-#include "libavutil/audioconvert.h"
+#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/eval.h"
#include "libavutil/float_dsp.h"
+#include "libavutil/intreadwrite.h"
#include "libavutil/opt.h"
+#include "libavutil/replaygain.h"
+
#include "audio.h"
#include "avfilter.h"
#include "formats.h"
{ "fixed", "8-bit fixed-point.", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A, "precision" },
{ "float", "32-bit floating-point.", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A, "precision" },
{ "double", "64-bit floating-point.", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A, "precision" },
+ { "replaygain", "Apply replaygain side data when present",
+ OFFSET(replaygain), AV_OPT_TYPE_INT, { .i64 = REPLAYGAIN_DROP }, REPLAYGAIN_DROP, REPLAYGAIN_ALBUM, A, "replaygain" },
+ { "drop", "replaygain side data is dropped", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_DROP }, 0, 0, A, "replaygain" },
+ { "ignore", "replaygain side data is ignored", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_IGNORE }, 0, 0, A, "replaygain" },
+ { "track", "track gain is preferred", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_TRACK }, 0, 0, A, "replaygain" },
+ { "album", "album gain is preferred", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_ALBUM }, 0, 0, A, "replaygain" },
+ { "replaygain_preamp", "Apply replaygain pre-amplification",
+ OFFSET(replaygain_preamp), AV_OPT_TYPE_DOUBLE, { .dbl = 0.0 }, -15.0, 15.0, A },
+ { "replaygain_noclip", "Apply replaygain clipping prevention",
+ OFFSET(replaygain_noclip), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, A },
{ NULL },
};
.version = LIBAVUTIL_VERSION_INT,
};
-static av_cold int init(AVFilterContext *ctx, const char *args)
+static av_cold int init(AVFilterContext *ctx)
{
VolumeContext *vol = ctx->priv;
- int ret;
-
- vol->class = &volume_class;
- av_opt_set_defaults(vol);
-
- if ((ret = av_set_options_string(vol, args, "=", ":")) < 0) {
- av_log(ctx, AV_LOG_ERROR, "Error parsing options string '%s'.\n", args);
- return ret;
- }
if (vol->precision == PRECISION_FIXED) {
vol->volume_i = (int)(vol->volume * 256 + 0.5);
precision_str[vol->precision]);
}
- av_opt_free(vol);
- return ret;
+ return 0;
}
static int query_formats(AVFilterContext *ctx)
-static void volume_init(VolumeContext *vol)
+static av_cold void volume_init(VolumeContext *vol)
{
vol->samples_align = 1;
AVFilterLink *outlink = inlink->dst->outputs[0];
int nb_samples = buf->nb_samples;
AVFrame *out_buf;
+ AVFrameSideData *sd = av_frame_get_side_data(buf, AV_FRAME_DATA_REPLAYGAIN);
+ int ret;
+
+ if (sd && vol->replaygain != REPLAYGAIN_IGNORE) {
+ if (vol->replaygain != REPLAYGAIN_DROP) {
+ AVReplayGain *replaygain = (AVReplayGain*)sd->data;
+ int32_t gain = 100000;
+ uint32_t peak = 100000;
+ float g, p;
+
+ if (vol->replaygain == REPLAYGAIN_TRACK &&
+ replaygain->track_gain != INT32_MIN) {
+ gain = replaygain->track_gain;
+
+ if (replaygain->track_peak != 0)
+ peak = replaygain->track_peak;
+ } else if (replaygain->album_gain != INT32_MIN) {
+ gain = replaygain->album_gain;
+
+ if (replaygain->album_peak != 0)
+ peak = replaygain->album_peak;
+ } else {
+ av_log(inlink->dst, AV_LOG_WARNING, "Both ReplayGain gain "
+ "values are unknown.\n");
+ }
+ g = gain / 100000.0f;
+ p = peak / 100000.0f;
+
+ av_log(inlink->dst, AV_LOG_VERBOSE,
+ "Using gain %f dB from replaygain side data.\n", g);
+
+ vol->volume = pow(10, (g + vol->replaygain_preamp) / 20);
+ if (vol->replaygain_noclip)
+ vol->volume = FFMIN(vol->volume, 1.0 / p);
+ vol->volume_i = (int)(vol->volume * 256 + 0.5);
+
+ volume_init(vol);
+ }
+ av_frame_remove_side_data(buf, AV_FRAME_DATA_REPLAYGAIN);
+ }
if (vol->volume == 1.0 || vol->volume_i == 256)
return ff_filter_frame(outlink, buf);
out_buf = ff_get_audio_buffer(inlink, nb_samples);
if (!out_buf)
return AVERROR(ENOMEM);
- out_buf->pts = buf->pts;
+ ret = av_frame_copy_props(out_buf, buf);
+ if (ret < 0) {
+ av_frame_free(&out_buf);
+ av_frame_free(&buf);
+ return ret;
+ }
}
if (vol->precision != PRECISION_FIXED || vol->volume_i > 0) {
}
}
+ emms_c();
+
if (buf != out_buf)
av_frame_free(&buf);
{ NULL }
};
-AVFilter avfilter_af_volume = {
+AVFilter ff_af_volume = {
.name = "volume",
.description = NULL_IF_CONFIG_SMALL("Change input volume."),
.query_formats = query_formats,
.priv_size = sizeof(VolumeContext),
+ .priv_class = &volume_class,
.init = init,
.inputs = avfilter_af_volume_inputs,
.outputs = avfilter_af_volume_outputs,