Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DOCS/interface-changes/lavfi-tempo_audio_filter.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
implement `lavfi-tempo` an alternative speed changing audio filter
31 changes: 31 additions & 0 deletions DOCS/man/af.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,37 @@ Available filters are:
``window-size=<amount>``
Length in milliseconds of the overlap-and-add window. (default: 12)

``lavfi-tempo[=[filter=]<filter_name>]``
Scales audio tempo using ``atempo`` or ``ascale`` filters from FFmpeg's
libavfilter.

If ``ascale`` (Librempeg only) is not available in the loaded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of mentioning home grown ffmpeg forks, just say it accepts filters like atempo so you don't have to go around documenting every fork that implements a pitch correction filter in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I opted to support specific filters because I can, and did, test them.
  • Non filter-specific support will require making both the filter name and filter tempo argument exposed to users. ascale and atempo just happened to use the same argument, and if they didn't, it's a minor code change. Non filter-specific support would add too much flexibility, complexity, and as a result non-reliability, IMHO.
  • This filter obviously doesn't add build-time or run-time dependency on librempeg. It just optionally supports internally a filter implemented there if available at run-time, like you can optionally pass such filters with lavfi=<some_filter>.

libavfilter, ``atempo`` will be used as a fall-back.

This can be used in place of ``scaletempo`` and ``scaletempo2``.


.. admonition:: Examples

``mpv --af=lavfi-tempo --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``atempo`` filter from lavfi.

``mpv --af=lavfi-tempo=atempo --speed=1.2 media.ogg``
Same as above.

``mpv --af=lavfi-tempo=filter=atempo --speed=1.2 media.ogg``
Same as above.

``mpv --af=lavfi-tempo=ascale --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``ascale`` filter from lavfi, unless the filter is
not available, then ``atempo`` will be used as a fall-back.

``mpv --af=lavfi-tempo=filter=ascale --speed=1.2 media.ogg``
Same as above.


``rubberband``
High quality pitch correction with librubberband. This can be used in place
of ``scaletempo`` and ``scaletempo2``, and will be used to adjust audio pitch
Expand Down
232 changes: 232 additions & 0 deletions audio/filter/af_lavfi_tempo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/

#include <assert.h>

#include "common/common.h"
#include "common/msg.h"

#include "audio/aframe.h"
#include "filters/f_lavfi.h"
#include "filters/filter.h"
#include "filters/filter_internal.h"
#include "filters/frame.h"
#include "filters/user_filters.h"
#include "options/m_option.h"
#include "ta/ta_talloc.h"

struct f_opts {
char *filter;
};

struct priv {
struct f_opts *opts;
bool initialized;
bool fall_backed;
bool lavfi_ready;
double speed;
double set_speed;
double in_pts;
struct mp_filter *lavfi_filter;
const struct mp_filter_info *lavfi_filter_info;
};

static bool set_speed(struct mp_filter *f) {
struct priv *p = f->priv;

char *arg = talloc_asprintf(NULL, "%f", p->speed);
struct mp_filter_command cmd = {
.type = MP_FILTER_COMMAND_TEXT,
.target = p->opts->filter,
.cmd = "tempo",
.arg = arg,
};
bool ret = p->lavfi_filter_info->command(p->lavfi_filter, &cmd);
talloc_free(arg);
if (!ret) {
MP_FATAL(f, "failed to set %s=%s for lavfi filter %s\n", cmd.cmd, cmd.arg, p->opts->filter);
return false;
} else {
p->set_speed = p->speed;
return ret;
}
}

static bool init_lavfi_tempo(struct mp_filter *f)
{
struct priv *p = f->priv;

if (strcmp(p->opts->filter, "ascale") && strcmp(p->opts->filter, "atempo")) {
MP_FATAL(f, "'%s' is not recognized in this context, use 'ascale' or 'atempo'.\n",
p->opts->filter);
return false;
}

mp_assert(!p->lavfi_filter);

if (!mp_lavfi_is_usable(p->opts->filter, AVMEDIA_TYPE_AUDIO)) {
MP_WARN(f, "%s filter is not available, using atempo instead.\n", p->opts->filter);
p->opts->filter = "atempo";
p->fall_backed = true;
}

p->lavfi_filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, p->opts->filter, NULL);
if (!p->lavfi_filter) {
MP_FATAL(f, "failed to create lavfi %s filter.\n", p->opts->filter);
return false;
}

p->lavfi_filter_info = mp_filter_get_info(p->lavfi_filter);
p->lavfi_ready = false;
p->initialized = true;

return true;
}

static void af_lavfi_tempo_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
p->in_pts = MP_NOPTS_VALUE;
p->lavfi_ready = false;
p->set_speed = 1.0;
p->lavfi_filter_info->reset(p->lavfi_filter);

}

static void af_lavfi_tempo_process(struct mp_filter *f)
{
struct priv *p = f->priv;

if (!p->initialized && !init_lavfi_tempo(f))
return;

if (p->lavfi_ready && p->set_speed != p->speed) {
set_speed(f);
}

if (mp_pin_can_transfer_data(p->lavfi_filter->pins[0], f->ppins[0])) {
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
p->in_pts = mp_aframe_get_pts(aframe);
}
if (!mp_pin_in_write(p->lavfi_filter->pins[0], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}

if (mp_pin_can_transfer_data(f->ppins[1], p->lavfi_filter->pins[1])) {
struct mp_frame frame = mp_pin_out_read(p->lavfi_filter->pins[1]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
mp_aframe_set_speed(aframe, p->set_speed);
if (p->in_pts != MP_NOPTS_VALUE) {
mp_aframe_set_pts(aframe, p->in_pts);
}
}
if (!mp_pin_in_write(f->ppins[1], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}

return;
}

static bool af_lavfi_tempo_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
struct priv *p = f->priv;

switch (cmd->type) {
case MP_FILTER_COMMAND_SET_SPEED:
if (cmd->speed == p->speed) {
return true;
}

p->speed = cmd->speed;
return p->lavfi_ready ? set_speed(f) : true;
}
return false;
}

static void af_lavfi_tempo_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
if (p->fall_backed) {
// prevent ta_alloc abort since filter name is const
p->opts->filter = NULL;
}
if (p->lavfi_filter_info) {
p->lavfi_filter_info->destroy(p->lavfi_filter);
}
}

static const struct mp_filter_info af_lavfi_tempo_filter = {
.name = "lavfi_tempo",
.priv_size = sizeof(struct priv),
.process = af_lavfi_tempo_process,
.command = af_lavfi_tempo_command,
.reset = af_lavfi_tempo_reset,
.destroy = af_lavfi_tempo_destroy,
};

static struct mp_filter *af_lavfi_tempo_create(struct mp_filter *parent,
void *options)
{
struct mp_filter *f = mp_filter_create(parent, &af_lavfi_tempo_filter);
if (!f) {
talloc_free(options);
return NULL;
}

mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");


struct priv *p = f->priv;
p->opts = talloc_steal(p, options);
p->speed = 1.0;
p->set_speed = 1.0;
p->in_pts = MP_NOPTS_VALUE;

if (!init_lavfi_tempo(f)) {
return NULL;
}

return f;
}

#define OPT_BASE_STRUCT struct f_opts

const struct mp_user_filter_entry af_lavfi_tempo = {
.desc = {
.description = "Tempo change with lavfi ascale or atempo filters",
.name = "lavfi-tempo",
.priv_size = sizeof(OPT_BASE_STRUCT),
.priv_defaults = &(const OPT_BASE_STRUCT) {
.filter = "atempo",
},
.options = (const struct m_option[]) {
{"filter", OPT_STRING(filter)},
{0}
},
},
.create = af_lavfi_tempo_create,
};
1 change: 1 addition & 0 deletions filters/user_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const struct mp_user_filter_entry *af_list[] = {
&af_lavfi_bridge,
&af_scaletempo,
&af_scaletempo2,
&af_lavfi_tempo,
&af_format,
#if HAVE_RUBBERBAND
&af_rubberband,
Expand Down
1 change: 1 addition & 0 deletions filters/user_filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern const struct mp_user_filter_entry af_lavfi;
extern const struct mp_user_filter_entry af_lavfi_bridge;
extern const struct mp_user_filter_entry af_scaletempo;
extern const struct mp_user_filter_entry af_scaletempo2;
extern const struct mp_user_filter_entry af_lavfi_tempo;
extern const struct mp_user_filter_entry af_format;
extern const struct mp_user_filter_entry af_rubberband;
extern const struct mp_user_filter_entry af_lavcac3enc;
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ sources = files(
'audio/filter/af_drop.c',
'audio/filter/af_format.c',
'audio/filter/af_lavcac3enc.c',
'audio/filter/af_lavfi_tempo.c',
'audio/filter/af_scaletempo.c',
'audio/filter/af_scaletempo2.c',
'audio/filter/af_scaletempo2_internals.c',
Expand Down