The eval mode allows to evaluate the expression per-frame or just at
init.
In particular, address ticket #3234.
... | ... |
@@ -1796,7 +1796,7 @@ The filter accepts the following options: |
1796 | 1796 |
@table @option |
1797 | 1797 |
|
1798 | 1798 |
@item volume |
1799 |
-Expresses how the audio volume will be increased or decreased. |
|
1799 |
+Set audio volume expression. |
|
1800 | 1800 |
|
1801 | 1801 |
Output values are clipped to the maximum value. |
1802 | 1802 |
|
... | ... |
@@ -1805,7 +1805,7 @@ The output audio volume is given by the relation: |
1805 | 1805 |
@var{output_volume} = @var{volume} * @var{input_volume} |
1806 | 1806 |
@end example |
1807 | 1807 |
|
1808 |
-Default value for @var{volume} is 1.0. |
|
1808 |
+Default value for @var{volume} is "1.0". |
|
1809 | 1809 |
|
1810 | 1810 |
@item precision |
1811 | 1811 |
Set the mathematical precision. |
... | ... |
@@ -1821,8 +1821,55 @@ precision of the volume scaling. |
1821 | 1821 |
@item double |
1822 | 1822 |
64-bit floating-point; limits input sample format to DBL. |
1823 | 1823 |
@end table |
1824 |
+ |
|
1825 |
+@item eval |
|
1826 |
+Set when the volume expression is evaluated. |
|
1827 |
+ |
|
1828 |
+It accepts the following values: |
|
1829 |
+@table @samp |
|
1830 |
+@item once |
|
1831 |
+only evaluate expression once during the filter initialization |
|
1832 |
+ |
|
1833 |
+@item frame |
|
1834 |
+evaluate expression for each incoming frame |
|
1824 | 1835 |
@end table |
1825 | 1836 |
|
1837 |
+Default value is @samp{once}. |
|
1838 |
+@end table |
|
1839 |
+ |
|
1840 |
+The volume expression can contain the following parameters. |
|
1841 |
+ |
|
1842 |
+@table @option |
|
1843 |
+@item n |
|
1844 |
+frame number (starting at zero) |
|
1845 |
+@item nb_channels |
|
1846 |
+number of channels |
|
1847 |
+@item nb_consumed_samples |
|
1848 |
+number of samples consumed by the filter |
|
1849 |
+@item nb_samples |
|
1850 |
+number of samples in the current frame |
|
1851 |
+@item pos |
|
1852 |
+original frame position in the file |
|
1853 |
+@item pts |
|
1854 |
+frame PTS |
|
1855 |
+@item sample_rate |
|
1856 |
+sample rate |
|
1857 |
+@item startpts |
|
1858 |
+PTS at start of stream |
|
1859 |
+@item startt |
|
1860 |
+time at start of stream |
|
1861 |
+@item t |
|
1862 |
+frame time |
|
1863 |
+@item tb |
|
1864 |
+timestamp timebase |
|
1865 |
+@item volume |
|
1866 |
+last set volume value |
|
1867 |
+@end table |
|
1868 |
+ |
|
1869 |
+Note that when @option{eval} is set to @samp{once} only the |
|
1870 |
+@var{sample_rate} and @var{tb} variables are available, all other |
|
1871 |
+variables will evaluate to NAN. |
|
1872 |
+ |
|
1826 | 1873 |
@subsection Examples |
1827 | 1874 |
|
1828 | 1875 |
@itemize |
... | ... |
@@ -1845,6 +1892,12 @@ Increase input audio power by 6 decibels using fixed-point precision: |
1845 | 1845 |
@example |
1846 | 1846 |
volume=volume=6dB:precision=fixed |
1847 | 1847 |
@end example |
1848 |
+ |
|
1849 |
+@item |
|
1850 |
+Fade volume after time 10 with an annihilation period of 5 seconds: |
|
1851 |
+@example |
|
1852 |
+volume='if(lt(t,10),1,max(1-(t-10)/5,0))':eval=frame |
|
1853 |
+@end example |
|
1848 | 1854 |
@end itemize |
1849 | 1855 |
|
1850 | 1856 |
@section volumedetect |
... | ... |
@@ -39,41 +39,75 @@ static const char *precision_str[] = { |
39 | 39 |
"fixed", "float", "double" |
40 | 40 |
}; |
41 | 41 |
|
42 |
+static const char *const var_names[] = { |
|
43 |
+ "n", ///< frame number (starting at zero) |
|
44 |
+ "nb_channels", ///< number of channels |
|
45 |
+ "nb_consumed_samples", ///< number of samples consumed by the filter |
|
46 |
+ "nb_samples", ///< number of samples in the current frame |
|
47 |
+ "pos", ///< position in the file of the frame |
|
48 |
+ "pts", ///< frame presentation timestamp |
|
49 |
+ "sample_rate", ///< sample rate |
|
50 |
+ "startpts", ///< PTS at start of stream |
|
51 |
+ "startt", ///< time at start of stream |
|
52 |
+ "t", ///< time in the file of the frame |
|
53 |
+ "tb", ///< timebase |
|
54 |
+ "volume", ///< last set value |
|
55 |
+ NULL |
|
56 |
+}; |
|
57 |
+ |
|
42 | 58 |
#define OFFSET(x) offsetof(VolumeContext, x) |
43 | 59 |
#define A AV_OPT_FLAG_AUDIO_PARAM |
44 | 60 |
#define F AV_OPT_FLAG_FILTERING_PARAM |
45 | 61 |
|
46 | 62 |
static const AVOption volume_options[] = { |
47 |
- { "volume", "set volume adjustment", |
|
48 |
- OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F }, |
|
63 |
+ { "volume", "set volume adjustment expression", |
|
64 |
+ OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, |
|
49 | 65 |
{ "precision", "select mathematical precision", |
50 | 66 |
OFFSET(precision), AV_OPT_TYPE_INT, { .i64 = PRECISION_FLOAT }, PRECISION_FIXED, PRECISION_DOUBLE, A|F, "precision" }, |
51 | 67 |
{ "fixed", "select 8-bit fixed-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A|F, "precision" }, |
52 | 68 |
{ "float", "select 32-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A|F, "precision" }, |
53 | 69 |
{ "double", "select 64-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A|F, "precision" }, |
70 |
+ { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_ONCE}, 0, EVAL_MODE_NB-1, .flags = A|F, "eval" }, |
|
71 |
+ { "once", "eval volume expression once", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_ONCE}, .flags = A|F, .unit = "eval" }, |
|
72 |
+ { "frame", "eval volume expression per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = A|F, .unit = "eval" }, |
|
54 | 73 |
{ NULL } |
55 | 74 |
}; |
56 | 75 |
|
57 | 76 |
AVFILTER_DEFINE_CLASS(volume); |
58 | 77 |
|
59 |
-static av_cold int init(AVFilterContext *ctx) |
|
78 |
+static int set_expr(AVExpr **pexpr, const char *expr, void *log_ctx) |
|
60 | 79 |
{ |
61 |
- VolumeContext *vol = ctx->priv; |
|
62 |
- |
|
63 |
- if (vol->precision == PRECISION_FIXED) { |
|
64 |
- vol->volume_i = (int)(vol->volume * 256 + 0.5); |
|
65 |
- vol->volume = vol->volume_i / 256.0; |
|
66 |
- av_log(ctx, AV_LOG_VERBOSE, "volume:(%d/256)(%f)(%1.2fdB) precision:fixed\n", |
|
67 |
- vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); |
|
68 |
- } else { |
|
69 |
- av_log(ctx, AV_LOG_VERBOSE, "volume:(%f)(%1.2fdB) precision:%s\n", |
|
70 |
- vol->volume, 20.0*log(vol->volume)/M_LN10, |
|
71 |
- precision_str[vol->precision]); |
|
80 |
+ int ret; |
|
81 |
+ AVExpr *old = NULL; |
|
82 |
+ |
|
83 |
+ if (*pexpr) |
|
84 |
+ old = *pexpr; |
|
85 |
+ ret = av_expr_parse(pexpr, expr, var_names, |
|
86 |
+ NULL, NULL, NULL, NULL, 0, log_ctx); |
|
87 |
+ if (ret < 0) { |
|
88 |
+ av_log(log_ctx, AV_LOG_ERROR, |
|
89 |
+ "Error when evaluating the volume expression '%s'\n", expr); |
|
90 |
+ *pexpr = old; |
|
91 |
+ return ret; |
|
72 | 92 |
} |
73 | 93 |
|
94 |
+ av_expr_free(old); |
|
74 | 95 |
return 0; |
75 | 96 |
} |
76 | 97 |
|
98 |
+static av_cold int init(AVFilterContext *ctx) |
|
99 |
+{ |
|
100 |
+ VolumeContext *vol = ctx->priv; |
|
101 |
+ return set_expr(&vol->volume_pexpr, vol->volume_expr, ctx); |
|
102 |
+} |
|
103 |
+ |
|
104 |
+static av_cold void uninit(AVFilterContext *ctx) |
|
105 |
+{ |
|
106 |
+ VolumeContext *vol = ctx->priv; |
|
107 |
+ av_expr_free(vol->volume_pexpr); |
|
108 |
+ av_opt_free(vol); |
|
109 |
+} |
|
110 |
+ |
|
77 | 111 |
static int query_formats(AVFilterContext *ctx) |
78 | 112 |
{ |
79 | 113 |
VolumeContext *vol = ctx->priv; |
... | ... |
@@ -199,6 +233,37 @@ static av_cold void volume_init(VolumeContext *vol) |
199 | 199 |
ff_volume_init_x86(vol); |
200 | 200 |
} |
201 | 201 |
|
202 |
+static int set_volume(AVFilterContext *ctx) |
|
203 |
+{ |
|
204 |
+ VolumeContext *vol = ctx->priv; |
|
205 |
+ |
|
206 |
+ vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); |
|
207 |
+ if (isnan(vol->volume)) { |
|
208 |
+ if (vol->eval_mode == EVAL_MODE_ONCE) { |
|
209 |
+ av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n"); |
|
210 |
+ return AVERROR(EINVAL); |
|
211 |
+ } else { |
|
212 |
+ av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); |
|
213 |
+ vol->volume = 0; |
|
214 |
+ } |
|
215 |
+ } |
|
216 |
+ vol->var_values[VAR_VOLUME] = vol->volume; |
|
217 |
+ |
|
218 |
+ if (vol->precision == PRECISION_FIXED) { |
|
219 |
+ vol->volume_i = (int)(vol->volume * 256 + 0.5); |
|
220 |
+ vol->volume = vol->volume_i / 256.0; |
|
221 |
+ av_log(ctx, AV_LOG_VERBOSE, "volume:(%d/256)(%f)(%1.2fdB) precision:fixed\n", |
|
222 |
+ vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10); |
|
223 |
+ } else { |
|
224 |
+ av_log(ctx, AV_LOG_VERBOSE, "volume:(%f)(%1.2fdB) precision:%s\n", |
|
225 |
+ vol->volume, 20.0*log(vol->volume)/M_LN10, |
|
226 |
+ precision_str[vol->precision]); |
|
227 |
+ } |
|
228 |
+ |
|
229 |
+ volume_init(vol); |
|
230 |
+ return 0; |
|
231 |
+} |
|
232 |
+ |
|
202 | 233 |
static int config_output(AVFilterLink *outlink) |
203 | 234 |
{ |
204 | 235 |
AVFilterContext *ctx = outlink->src; |
... | ... |
@@ -209,20 +274,58 @@ static int config_output(AVFilterLink *outlink) |
209 | 209 |
vol->channels = inlink->channels; |
210 | 210 |
vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1; |
211 | 211 |
|
212 |
- volume_init(vol); |
|
213 |
- |
|
214 |
- return 0; |
|
212 |
+ vol->var_values[VAR_N] = |
|
213 |
+ vol->var_values[VAR_NB_CONSUMED_SAMPLES] = |
|
214 |
+ vol->var_values[VAR_NB_SAMPLES] = |
|
215 |
+ vol->var_values[VAR_POS] = |
|
216 |
+ vol->var_values[VAR_PTS] = |
|
217 |
+ vol->var_values[VAR_STARTPTS] = |
|
218 |
+ vol->var_values[VAR_STARTT] = |
|
219 |
+ vol->var_values[VAR_T] = |
|
220 |
+ vol->var_values[VAR_VOLUME] = NAN; |
|
221 |
+ |
|
222 |
+ vol->var_values[VAR_NB_CHANNELS] = inlink->channels; |
|
223 |
+ vol->var_values[VAR_TB] = av_q2d(inlink->time_base); |
|
224 |
+ vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate; |
|
225 |
+ |
|
226 |
+ av_log(inlink->src, AV_LOG_VERBOSE, "tb:%f sample_rate:%f nb_channels:%f\n", |
|
227 |
+ vol->var_values[VAR_TB], |
|
228 |
+ vol->var_values[VAR_SAMPLE_RATE], |
|
229 |
+ vol->var_values[VAR_NB_CHANNELS]); |
|
230 |
+ |
|
231 |
+ return set_volume(ctx); |
|
215 | 232 |
} |
216 | 233 |
|
234 |
+#define D2TS(d) (isnan(d) ? AV_NOPTS_VALUE : (int64_t)(d)) |
|
235 |
+#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)) |
|
236 |
+#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb)) |
|
237 |
+ |
|
217 | 238 |
static int filter_frame(AVFilterLink *inlink, AVFrame *buf) |
218 | 239 |
{ |
240 |
+ AVFilterContext *ctx = inlink->dst; |
|
219 | 241 |
VolumeContext *vol = inlink->dst->priv; |
220 | 242 |
AVFilterLink *outlink = inlink->dst->outputs[0]; |
221 | 243 |
int nb_samples = buf->nb_samples; |
222 | 244 |
AVFrame *out_buf; |
245 |
+ int64_t pos; |
|
246 |
+ |
|
247 |
+ if (isnan(vol->var_values[VAR_STARTPTS])) { |
|
248 |
+ vol->var_values[VAR_STARTPTS] = TS2D(buf->pts); |
|
249 |
+ vol->var_values[VAR_STARTT ] = TS2T(buf->pts, inlink->time_base); |
|
250 |
+ } |
|
251 |
+ vol->var_values[VAR_PTS] = TS2D(buf->pts); |
|
252 |
+ vol->var_values[VAR_T ] = TS2T(buf->pts, inlink->time_base); |
|
253 |
+ vol->var_values[VAR_N ] = inlink->frame_count; |
|
223 | 254 |
|
224 |
- if (vol->volume == 1.0 || vol->volume_i == 256) |
|
225 |
- return ff_filter_frame(outlink, buf); |
|
255 |
+ pos = av_frame_get_pkt_pos(buf); |
|
256 |
+ vol->var_values[VAR_POS] = pos == -1 ? NAN : pos; |
|
257 |
+ if (vol->eval_mode == EVAL_MODE_FRAME) |
|
258 |
+ set_volume(ctx); |
|
259 |
+ |
|
260 |
+ if (vol->volume == 1.0 || vol->volume_i == 256) { |
|
261 |
+ out_buf = buf; |
|
262 |
+ goto end; |
|
263 |
+ } |
|
226 | 264 |
|
227 | 265 |
/* do volume scaling in-place if input buffer is writable */ |
228 | 266 |
if (av_frame_is_writable(buf)) { |
... | ... |
@@ -266,6 +369,8 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *buf) |
266 | 266 |
if (buf != out_buf) |
267 | 267 |
av_frame_free(&buf); |
268 | 268 |
|
269 |
+end: |
|
270 |
+ vol->var_values[VAR_NB_CONSUMED_SAMPLES] += buf->nb_samples; |
|
269 | 271 |
return ff_filter_frame(outlink, out_buf); |
270 | 272 |
} |
271 | 273 |
|
... | ... |
@@ -294,6 +399,7 @@ AVFilter ff_af_volume = { |
294 | 294 |
.priv_size = sizeof(VolumeContext), |
295 | 295 |
.priv_class = &volume_class, |
296 | 296 |
.init = init, |
297 |
+ .uninit = uninit, |
|
297 | 298 |
.inputs = avfilter_af_volume_inputs, |
298 | 299 |
.outputs = avfilter_af_volume_outputs, |
299 | 300 |
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
... | ... |
@@ -25,6 +25,7 @@ |
25 | 25 |
#define AVFILTER_AF_VOLUME_H |
26 | 26 |
|
27 | 27 |
#include "libavutil/common.h" |
28 |
+#include "libavutil/eval.h" |
|
28 | 29 |
#include "libavutil/float_dsp.h" |
29 | 30 |
#include "libavutil/opt.h" |
30 | 31 |
#include "libavutil/samplefmt.h" |
... | ... |
@@ -35,10 +36,37 @@ enum PrecisionType { |
35 | 35 |
PRECISION_DOUBLE, |
36 | 36 |
}; |
37 | 37 |
|
38 |
+enum EvalMode { |
|
39 |
+ EVAL_MODE_ONCE, |
|
40 |
+ EVAL_MODE_FRAME, |
|
41 |
+ EVAL_MODE_NB |
|
42 |
+}; |
|
43 |
+ |
|
44 |
+enum VolumeVarName { |
|
45 |
+ VAR_N, |
|
46 |
+ VAR_NB_CHANNELS, |
|
47 |
+ VAR_NB_CONSUMED_SAMPLES, |
|
48 |
+ VAR_NB_SAMPLES, |
|
49 |
+ VAR_POS, |
|
50 |
+ VAR_PTS, |
|
51 |
+ VAR_SAMPLE_RATE, |
|
52 |
+ VAR_STARTPTS, |
|
53 |
+ VAR_STARTT, |
|
54 |
+ VAR_T, |
|
55 |
+ VAR_TB, |
|
56 |
+ VAR_VOLUME, |
|
57 |
+ VAR_VARS_NB |
|
58 |
+}; |
|
59 |
+ |
|
38 | 60 |
typedef struct VolumeContext { |
39 | 61 |
const AVClass *class; |
40 | 62 |
AVFloatDSPContext fdsp; |
41 | 63 |
enum PrecisionType precision; |
64 |
+ enum EvalMode eval_mode; |
|
65 |
+ const char *volume_expr; |
|
66 |
+ AVExpr *volume_pexpr; |
|
67 |
+ double var_values[VAR_VARS_NB]; |
|
68 |
+ |
|
42 | 69 |
double volume; |
43 | 70 |
int volume_i; |
44 | 71 |
int channels; |
... | ... |
@@ -31,7 +31,7 @@ |
31 | 31 |
|
32 | 32 |
#define LIBAVFILTER_VERSION_MAJOR 4 |
33 | 33 |
#define LIBAVFILTER_VERSION_MINOR 0 |
34 |
-#define LIBAVFILTER_VERSION_MICRO 100 |
|
34 |
+#define LIBAVFILTER_VERSION_MICRO 101 |
|
35 | 35 |
|
36 | 36 |
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ |
37 | 37 |
LIBAVFILTER_VERSION_MINOR, \ |