Skip to content

Commit 55e6640

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 <CE.Mohammad.AlSaleh@gmail.com>
1 parent 7037ff4 commit 55e6640

File tree

6 files changed

+267
-0
lines changed

6 files changed

+267
-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: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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 next_pts;
44+
struct mp_filter *lavfi_filter;
45+
const struct mp_filter_info *lavfi_filter_info;
46+
};
47+
48+
static bool set_speed(struct mp_filter *f) {
49+
struct priv *p = f->priv;
50+
51+
char *arg = talloc_asprintf(NULL, "%f", p->speed);
52+
struct mp_filter_command cmd = {
53+
.type = MP_FILTER_COMMAND_TEXT,
54+
.target = p->opts->filter,
55+
.cmd = "tempo",
56+
.arg = arg,
57+
};
58+
bool ret = p->lavfi_filter_info->command(p->lavfi_filter, &cmd);
59+
talloc_free(arg);
60+
if (!ret) {
61+
MP_FATAL(f, "failed to set %s=%s for lavfi filter %s\n", cmd.cmd, cmd.arg, p->opts->filter);
62+
return false;
63+
} else {
64+
p->set_speed = p->speed;
65+
return ret;
66+
}
67+
}
68+
69+
static bool init_lavfi_tempo(struct mp_filter *f)
70+
{
71+
struct priv *p = f->priv;
72+
73+
if (strcmp(p->opts->filter, "ascale") && strcmp(p->opts->filter, "atempo")) {
74+
MP_FATAL(f, "'%s' is not recognized in this context, use 'ascale' or 'atempo'.\n",
75+
p->opts->filter);
76+
return false;
77+
}
78+
79+
mp_assert(!p->lavfi_filter);
80+
81+
if (!mp_lavfi_is_usable(p->opts->filter, AVMEDIA_TYPE_AUDIO)) {
82+
MP_WARN(f, "%s filter is not available, using atempo instead.\n", p->opts->filter);
83+
p->opts->filter = "atempo";
84+
p->fall_backed = true;
85+
}
86+
87+
p->lavfi_filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, p->opts->filter, NULL);
88+
if (!p->lavfi_filter) {
89+
MP_FATAL(f, "failed to create lavfi %s filter.\n", p->opts->filter);
90+
return false;
91+
}
92+
93+
p->lavfi_filter_info = mp_filter_get_info(p->lavfi_filter);
94+
p->lavfi_ready = false;
95+
p->initialized = true;
96+
97+
return true;
98+
}
99+
100+
static void af_lavfi_tempo_process(struct mp_filter *f)
101+
{
102+
struct priv *p = f->priv;
103+
104+
if (!p->initialized && !init_lavfi_tempo(f))
105+
return;
106+
107+
if (p->lavfi_ready && p->set_speed != p->speed) {
108+
set_speed(f);
109+
}
110+
111+
if (mp_pin_can_transfer_data(p->lavfi_filter->pins[0], f->ppins[0])) {
112+
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
113+
if (!mp_pin_in_write(p->lavfi_filter->pins[0], frame)) {
114+
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
115+
} else {
116+
p->lavfi_ready = true;
117+
}
118+
}
119+
120+
if (mp_pin_can_transfer_data(f->ppins[1], p->lavfi_filter->pins[1])) {
121+
struct mp_frame frame = mp_pin_out_read(p->lavfi_filter->pins[1]);
122+
if (frame.type == MP_FRAME_AUDIO) {
123+
struct mp_aframe *aframe = frame.data;
124+
if (p->next_pts != MP_NOPTS_VALUE) {
125+
mp_aframe_set_pts(aframe, p->next_pts);
126+
}
127+
mp_aframe_set_speed(aframe, p->set_speed);
128+
p->next_pts = mp_aframe_end_pts(aframe);
129+
}
130+
if (!mp_pin_in_write(f->ppins[1], frame)) {
131+
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
132+
} else {
133+
p->lavfi_ready = true;
134+
}
135+
}
136+
137+
return;
138+
}
139+
140+
static void af_lavfi_tempo_reset(struct mp_filter *f)
141+
{
142+
struct priv *p = f->priv;
143+
p->next_pts = MP_NOPTS_VALUE;
144+
p->lavfi_ready = false;
145+
p->set_speed = 1.0;
146+
p->lavfi_filter_info->reset(p->lavfi_filter);
147+
148+
}
149+
150+
static bool af_lavfi_tempo_command(struct mp_filter *f, struct mp_filter_command *cmd)
151+
{
152+
struct priv *p = f->priv;
153+
154+
switch (cmd->type) {
155+
case MP_FILTER_COMMAND_SET_SPEED:
156+
if (cmd->speed == p->speed) {
157+
return true;
158+
} else {
159+
// to make sure next_pts gets correct values
160+
af_lavfi_tempo_reset(f);
161+
}
162+
163+
p->speed = cmd->speed;
164+
return p->lavfi_ready ? set_speed(f) : true;
165+
}
166+
return false;
167+
}
168+
169+
static void af_lavfi_tempo_destroy(struct mp_filter *f)
170+
{
171+
struct priv *p = f->priv;
172+
if (p->fall_backed) {
173+
// prevent ta_alloc abort since filter name is const
174+
p->opts->filter = NULL;
175+
}
176+
if (p->lavfi_filter_info) {
177+
p->lavfi_filter_info->destroy(p->lavfi_filter);
178+
}
179+
}
180+
181+
static const struct mp_filter_info af_lavfi_tempo_filter = {
182+
.name = "lavfi_tempo",
183+
.priv_size = sizeof(struct priv),
184+
.process = af_lavfi_tempo_process,
185+
.command = af_lavfi_tempo_command,
186+
.reset = af_lavfi_tempo_reset,
187+
.destroy = af_lavfi_tempo_destroy,
188+
};
189+
190+
static struct mp_filter *af_lavfi_tempo_create(struct mp_filter *parent,
191+
void *options)
192+
{
193+
struct mp_filter *f = mp_filter_create(parent, &af_lavfi_tempo_filter);
194+
if (!f) {
195+
talloc_free(options);
196+
return NULL;
197+
}
198+
199+
mp_filter_add_pin(f, MP_PIN_IN, "in");
200+
mp_filter_add_pin(f, MP_PIN_OUT, "out");
201+
202+
203+
struct priv *p = f->priv;
204+
p->opts = talloc_steal(p, options);
205+
p->speed = 1.0;
206+
p->set_speed = 1.0;
207+
p->next_pts = MP_NOPTS_VALUE;
208+
209+
if (!init_lavfi_tempo(f)) {
210+
return NULL;
211+
}
212+
213+
return f;
214+
}
215+
216+
#define OPT_BASE_STRUCT struct f_opts
217+
218+
const struct mp_user_filter_entry af_lavfi_tempo = {
219+
.desc = {
220+
.description = "Tempo change with lavfi ascale or atempo filters",
221+
.name = "lavfi-tempo",
222+
.priv_size = sizeof(OPT_BASE_STRUCT),
223+
.priv_defaults = &(const OPT_BASE_STRUCT) {
224+
.filter = "atempo",
225+
},
226+
.options = (const struct m_option[]) {
227+
{"filter", OPT_STRING(filter)},
228+
{0}
229+
},
230+
},
231+
.create = af_lavfi_tempo_create,
232+
};

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)