Skip to content

Commit 8f4d487

Browse files
committed
audio/filter: implement lavfi-tempo leveraging lavfi's atempo/ascale
Implement `lavfi-tempo` audio filter, which uses `atempo` or `ascale` form libavfilter for setting the tempo. Users (myself included) seem to be always looking for alternative tempo scalers to try, and this provides a faster implementation (or two), and may prove useful beyond performance/speed. `atempo` is picked as a default, and as a fall-back if `ascale` is not available. Signed-off-by: Mohammad AlSaleh <[email protected]>
1 parent 7037ff4 commit 8f4d487

File tree

6 files changed

+280
-0
lines changed

6 files changed

+280
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
implement `lavfi-tempo` an alternative speed changing audio filter

DOCS/man/af.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,37 @@ Available filters are:
189189
``window-size=<amount>``
190190
Length in milliseconds of the overlap-and-add window. (default: 12)
191191

192+
``lavfi-tempo[=[filter=]<filter_name>]``
193+
Scales audio tempo using ``atempo`` or ``ascale`` filters from FFmpeg's
194+
libavfilter.
195+
196+
If ``ascale`` (Librempeg only) is not available in the loaded
197+
libavfilter, ``atempo`` will be used as a fall-back.
198+
199+
This can be used in place of ``scaletempo`` and ``scaletempo2``.
200+
201+
202+
.. admonition:: Examples
203+
204+
``mpv --af=lavfi-tempo --speed=1.2 media.ogg``
205+
Would play media at 1.2x normal speed at normal pitch with tempo
206+
scaled by the ``atempo`` filter from lavfi.
207+
208+
``mpv --af=lavfi-tempo=atempo --speed=1.2 media.ogg``
209+
Same as above.
210+
211+
``mpv --af=lavfi-tempo=filter=atempo --speed=1.2 media.ogg``
212+
Same as above.
213+
214+
``mpv --af=lavfi-tempo=ascale --speed=1.2 media.ogg``
215+
Would play media at 1.2x normal speed at normal pitch with tempo
216+
scaled by the ``ascale`` filter from lavfi, unless the filter is
217+
not available, then ``atempo`` will be used as a fall-back.
218+
219+
``mpv --af=lavfi-tempo=filter=ascale --speed=1.2 media.ogg``
220+
Same as above.
221+
222+
192223
``rubberband``
193224
High quality pitch correction with librubberband. This can be used in place
194225
of ``scaletempo`` and ``scaletempo2``, and will be used to adjust audio pitch

audio/filter/af_lavfi_tempo.c

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
* This file is part of mpv.
3+
*
4+
* mpv is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2.1 of the License, or (at your option) any later version.
8+
*
9+
* mpv is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
#include <assert.h>
19+
20+
#include "common/common.h"
21+
#include "common/msg.h"
22+
23+
#include "audio/aframe.h"
24+
#include "filters/f_lavfi.h"
25+
#include "filters/filter.h"
26+
#include "filters/filter_internal.h"
27+
#include "filters/frame.h"
28+
#include "filters/user_filters.h"
29+
#include "options/m_option.h"
30+
#include "ta/ta_talloc.h"
31+
32+
struct f_opts {
33+
char *filter;
34+
};
35+
36+
struct priv {
37+
struct f_opts *opts;
38+
bool initialized;
39+
bool fall_backed;
40+
bool lavfi_ready;
41+
double speed;
42+
double set_speed;
43+
double in_pts;
44+
double next_pts;
45+
struct mp_filter *lavfi_filter;
46+
const struct mp_filter_info *lavfi_filter_info;
47+
};
48+
49+
static bool set_speed(struct mp_filter *f) {
50+
struct priv *p = f->priv;
51+
52+
char *arg = talloc_asprintf(NULL, "%f", p->speed);
53+
struct mp_filter_command cmd = {
54+
.type = MP_FILTER_COMMAND_TEXT,
55+
.target = p->opts->filter,
56+
.cmd = "tempo",
57+
.arg = arg,
58+
};
59+
bool ret = p->lavfi_filter_info->command(p->lavfi_filter, &cmd);
60+
talloc_free(arg);
61+
if (!ret) {
62+
MP_FATAL(f, "failed to set %s=%s for lavfi filter %s\n", cmd.cmd, cmd.arg, p->opts->filter);
63+
return false;
64+
} else {
65+
p->set_speed = p->speed;
66+
return ret;
67+
}
68+
}
69+
70+
static bool init_lavfi_tempo(struct mp_filter *f)
71+
{
72+
struct priv *p = f->priv;
73+
74+
if (strcmp(p->opts->filter, "ascale") && strcmp(p->opts->filter, "atempo")) {
75+
MP_FATAL(f, "'%s' is not recognized in this context, use 'ascale' or 'atempo'.\n",
76+
p->opts->filter);
77+
return false;
78+
}
79+
80+
mp_assert(!p->lavfi_filter);
81+
82+
if (!mp_lavfi_is_usable(p->opts->filter, AVMEDIA_TYPE_AUDIO)) {
83+
MP_WARN(f, "%s filter is not available, using atempo instead.\n", p->opts->filter);
84+
p->opts->filter = "atempo";
85+
p->fall_backed = true;
86+
}
87+
88+
p->lavfi_filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, p->opts->filter, NULL);
89+
if (!p->lavfi_filter) {
90+
MP_FATAL(f, "failed to create lavfi %s filter.\n", p->opts->filter);
91+
return false;
92+
}
93+
94+
p->lavfi_filter_info = mp_filter_get_info(p->lavfi_filter);
95+
p->lavfi_ready = false;
96+
p->initialized = true;
97+
98+
return true;
99+
}
100+
101+
static void af_lavfi_tempo_reset(struct mp_filter *f)
102+
{
103+
struct priv *p = f->priv;
104+
p->in_pts = MP_NOPTS_VALUE;
105+
p->next_pts = MP_NOPTS_VALUE;
106+
p->lavfi_ready = false;
107+
p->set_speed = 1.0;
108+
p->lavfi_filter_info->reset(p->lavfi_filter);
109+
110+
}
111+
112+
static void af_lavfi_tempo_process(struct mp_filter *f)
113+
{
114+
struct priv *p = f->priv;
115+
116+
if (!p->initialized && !init_lavfi_tempo(f))
117+
return;
118+
119+
if (p->lavfi_ready && p->set_speed != p->speed) {
120+
set_speed(f);
121+
}
122+
123+
if (mp_pin_can_transfer_data(p->lavfi_filter->pins[0], f->ppins[0])) {
124+
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
125+
if (frame.type == MP_FRAME_AUDIO) {
126+
struct mp_aframe *aframe = frame.data;
127+
double in_pts = mp_aframe_get_pts(aframe);
128+
if (p->in_pts != MP_NOPTS_VALUE && fabs(in_pts - p->in_pts) > 0.1) {
129+
MP_WARN(f, "large jump in PTS (%lf -> %lf). reset to ensure resync\n", p->in_pts, in_pts);
130+
af_lavfi_tempo_reset(f);
131+
} else {
132+
p->in_pts = in_pts;
133+
}
134+
}
135+
if (!mp_pin_in_write(p->lavfi_filter->pins[0], frame)) {
136+
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
137+
} else {
138+
p->lavfi_ready = true;
139+
}
140+
}
141+
142+
if (mp_pin_can_transfer_data(f->ppins[1], p->lavfi_filter->pins[1])) {
143+
struct mp_frame frame = mp_pin_out_read(p->lavfi_filter->pins[1]);
144+
if (frame.type == MP_FRAME_AUDIO) {
145+
struct mp_aframe *aframe = frame.data;
146+
if (p->next_pts != MP_NOPTS_VALUE) {
147+
mp_aframe_set_pts(aframe, p->next_pts);
148+
}
149+
mp_aframe_set_speed(aframe, p->set_speed);
150+
p->next_pts = mp_aframe_end_pts(aframe);
151+
}
152+
if (!mp_pin_in_write(f->ppins[1], frame)) {
153+
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
154+
} else {
155+
p->lavfi_ready = true;
156+
}
157+
}
158+
159+
return;
160+
}
161+
162+
static bool af_lavfi_tempo_command(struct mp_filter *f, struct mp_filter_command *cmd)
163+
{
164+
struct priv *p = f->priv;
165+
166+
switch (cmd->type) {
167+
case MP_FILTER_COMMAND_SET_SPEED:
168+
if (cmd->speed == p->speed) {
169+
return true;
170+
} else {
171+
// to make sure next_pts gets correct values
172+
af_lavfi_tempo_reset(f);
173+
}
174+
175+
p->speed = cmd->speed;
176+
return p->lavfi_ready ? set_speed(f) : true;
177+
}
178+
return false;
179+
}
180+
181+
static void af_lavfi_tempo_destroy(struct mp_filter *f)
182+
{
183+
struct priv *p = f->priv;
184+
if (p->fall_backed) {
185+
// prevent ta_alloc abort since filter name is const
186+
p->opts->filter = NULL;
187+
}
188+
if (p->lavfi_filter_info) {
189+
p->lavfi_filter_info->destroy(p->lavfi_filter);
190+
}
191+
}
192+
193+
static const struct mp_filter_info af_lavfi_tempo_filter = {
194+
.name = "lavfi_tempo",
195+
.priv_size = sizeof(struct priv),
196+
.process = af_lavfi_tempo_process,
197+
.command = af_lavfi_tempo_command,
198+
.reset = af_lavfi_tempo_reset,
199+
.destroy = af_lavfi_tempo_destroy,
200+
};
201+
202+
static struct mp_filter *af_lavfi_tempo_create(struct mp_filter *parent,
203+
void *options)
204+
{
205+
struct mp_filter *f = mp_filter_create(parent, &af_lavfi_tempo_filter);
206+
if (!f) {
207+
talloc_free(options);
208+
return NULL;
209+
}
210+
211+
mp_filter_add_pin(f, MP_PIN_IN, "in");
212+
mp_filter_add_pin(f, MP_PIN_OUT, "out");
213+
214+
215+
struct priv *p = f->priv;
216+
p->opts = talloc_steal(p, options);
217+
p->speed = 1.0;
218+
p->set_speed = 1.0;
219+
p->in_pts = MP_NOPTS_VALUE;
220+
p->next_pts = MP_NOPTS_VALUE;
221+
222+
if (!init_lavfi_tempo(f)) {
223+
return NULL;
224+
}
225+
226+
return f;
227+
}
228+
229+
#define OPT_BASE_STRUCT struct f_opts
230+
231+
const struct mp_user_filter_entry af_lavfi_tempo = {
232+
.desc = {
233+
.description = "Tempo change with lavfi ascale or atempo filters",
234+
.name = "lavfi-tempo",
235+
.priv_size = sizeof(OPT_BASE_STRUCT),
236+
.priv_defaults = &(const OPT_BASE_STRUCT) {
237+
.filter = "atempo",
238+
},
239+
.options = (const struct m_option[]) {
240+
{"filter", OPT_STRING(filter)},
241+
{0}
242+
},
243+
},
244+
.create = af_lavfi_tempo_create,
245+
};

filters/user_filters.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const struct mp_user_filter_entry *af_list[] = {
3535
&af_lavfi_bridge,
3636
&af_scaletempo,
3737
&af_scaletempo2,
38+
&af_lavfi_tempo,
3839
&af_format,
3940
#if HAVE_RUBBERBAND
4041
&af_rubberband,

filters/user_filters.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern const struct mp_user_filter_entry af_lavfi;
2222
extern const struct mp_user_filter_entry af_lavfi_bridge;
2323
extern const struct mp_user_filter_entry af_scaletempo;
2424
extern const struct mp_user_filter_entry af_scaletempo2;
25+
extern const struct mp_user_filter_entry af_lavfi_tempo;
2526
extern const struct mp_user_filter_entry af_format;
2627
extern const struct mp_user_filter_entry af_rubberband;
2728
extern const struct mp_user_filter_entry af_lavcac3enc;

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ sources = files(
6464
'audio/filter/af_drop.c',
6565
'audio/filter/af_format.c',
6666
'audio/filter/af_lavcac3enc.c',
67+
'audio/filter/af_lavfi_tempo.c',
6768
'audio/filter/af_scaletempo.c',
6869
'audio/filter/af_scaletempo2.c',
6970
'audio/filter/af_scaletempo2_internals.c',

0 commit comments

Comments
 (0)