diff --git a/DOCS/interface-changes/lavfi-tempo_audio_filter.txt b/DOCS/interface-changes/lavfi-tempo_audio_filter.txt new file mode 100644 index 0000000000000..89d1c364755f2 --- /dev/null +++ b/DOCS/interface-changes/lavfi-tempo_audio_filter.txt @@ -0,0 +1 @@ +implement `lavfi-tempo` an alternative speed changing audio filter diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst index c1e937bf30aec..989e406a37a5f 100644 --- a/DOCS/man/af.rst +++ b/DOCS/man/af.rst @@ -189,6 +189,37 @@ Available filters are: ``window-size=`` Length in milliseconds of the overlap-and-add window. (default: 12) +``lavfi-tempo[=[filter=]]`` + Scales audio tempo using ``atempo`` or ``ascale`` filters from FFmpeg's + libavfilter. + + If ``ascale`` (Librempeg only) is not available in the loaded + 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 diff --git a/audio/filter/af_lavfi_tempo.c b/audio/filter/af_lavfi_tempo.c new file mode 100644 index 0000000000000..d4cab07a1d07c --- /dev/null +++ b/audio/filter/af_lavfi_tempo.c @@ -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 . + */ + +#include + +#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, +}; diff --git a/filters/user_filters.c b/filters/user_filters.c index 68c70163717b0..291ba8a7904f7 100644 --- a/filters/user_filters.c +++ b/filters/user_filters.c @@ -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, diff --git a/filters/user_filters.h b/filters/user_filters.h index 639ffe7ddf7dc..12ee89a8b99b8 100644 --- a/filters/user_filters.h +++ b/filters/user_filters.h @@ -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; diff --git a/meson.build b/meson.build index b5b6c2ca5023a..0ebdd54f73451 100644 --- a/meson.build +++ b/meson.build @@ -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',