aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar wm4 <wm4@nowhere>2018-01-18 14:44:20 +0100
committerGravatar Kevin Mitchell <kevmitch@gmail.com>2018-01-30 03:10:27 -0800
commitb9f804b566c4c528714e4ec5e63675ad7ba5fefd (patch)
tree49d6fcd42ce6597a67aa2af59b7f20beb21a2e14
parent76276c92104c31ee936ba5c76a76072f09978c5f (diff)
audio: rewrite filtering glue code
Use the new filtering code for audio too.
-rw-r--r--Copyright10
-rw-r--r--DOCS/interface-changes.rst22
-rw-r--r--DOCS/man/af.rst7
-rw-r--r--audio/aconverter.h41
-rw-r--r--audio/aframe.c130
-rw-r--r--audio/aframe.h11
-rw-r--r--audio/audio.c625
-rw-r--r--audio/audio.h102
-rw-r--r--audio/filter/af.c824
-rw-r--r--audio/filter/af.h163
-rw-r--r--audio/filter/af_format.c173
-rw-r--r--audio/filter/af_lavcac3enc.c504
-rw-r--r--audio/filter/af_lavfi.c413
-rw-r--r--audio/filter/af_lavrresample.c187
-rw-r--r--audio/filter/af_rubberband.c446
-rw-r--r--audio/filter/af_scaletempo.c577
-rw-r--r--audio/filter/tools.c72
-rw-r--r--audio/format.c2
-rw-r--r--audio/format.h1
-rw-r--r--filters/f_auto_filters.c90
-rw-r--r--filters/f_auto_filters.h3
-rw-r--r--filters/f_autoconvert.c159
-rw-r--r--filters/f_autoconvert.h11
-rw-r--r--filters/f_lavfi.c101
-rw-r--r--filters/f_output_chain.c355
-rw-r--r--filters/f_output_chain.h27
-rw-r--r--filters/f_swresample.c (renamed from audio/aconverter.c)336
-rw-r--r--filters/f_swresample.h42
-rw-r--r--filters/f_utils.c118
-rw-r--r--filters/f_utils.h6
-rw-r--r--filters/filter.h5
-rw-r--r--filters/user_filters.c29
-rw-r--r--filters/user_filters.h7
-rw-r--r--options/options.c12
-rw-r--r--options/options.h1
-rw-r--r--player/audio.c529
-rw-r--r--player/command.c75
-rw-r--r--player/core.h11
-rw-r--r--wscript_build.py14
39 files changed, 2483 insertions, 3758 deletions
diff --git a/Copyright b/Copyright
index 4a8b056059..b4f3dcfe96 100644
--- a/Copyright
+++ b/Copyright
@@ -34,8 +34,6 @@ a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
by asking the MPlayer authors for permission. Since permission could not be
obtained from everyone, LGPL mode disables the following features, some of
them quite central:
-- no audio filtering, which breaks: --af, pitch correction, fine control over
- downmix/upmix/resampling behavior
- Linux X11 video output
- BSD audio output via OSS
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
@@ -47,15 +45,8 @@ at all.
The following files are still GPL only (--enable-lgpl disables them):
- audio/filter/* will be replaced with new filter chain
- audio/filter/af_format.c mostly LGPL (except af glue code)
- audio/filter/af_lavc3enc.c as above
- audio/filter/af_lavfi.c as above
- audio/filter/af_scaletempo.c as above
- audio/filter/af_rubberband.c as above
audio/out/ao_jack.c will stay GPL
audio/out/ao_oss.c will stay GPL
- audio/audio.* needed by af code only
demux/demux_tv.c will stay GPL
stream/ai_* will stay GPL (TV code)
stream/audio_in.* will stay GPL (TV code)
@@ -88,7 +79,6 @@ The following files are still GPL only (--enable-lgpl disables them):
The following files contain some optional GPL code (--enable-lgpl disables it):
options/parse_commandline.c dvd:// expansion
- player/audio.c libaf glue code
None of the exceptions listed above affect the final binary if it's built as
LGPL. Linked libraries still can affect the final license (for example if
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 2379470277..64dd36670a 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -43,6 +43,28 @@ Interface changes
- inserting an incompatible filter with hwdec at runtime would refuse
to insert the filter; now it will add it successfully, but disables
the filter slightly later
+ - some behavior changes in the audio filter chain, including:
+ - a manually inserted lavrresample filter is not necessarily used for
+ sample format conversion anymore, so it's pretty useless
+ - changing playback speed will not respect --af-defaults anymore
+ - having libavfilter based filters after the scaletempo or rubberband
+ filters is not supported anymore, and may desync if playback speed is
+ changed (libavfilter does not support the metadata for playback speed)
+ - the lavcac3enc filter does not auto detach itself anymore; instead it
+ passes through the data after converting it to the sample rate and
+ channel configuration the ac3 encoder expects; also, if the audio
+ format changes midstream in a way that causes the filter to switch
+ between PCM and AC3 output, the audio output won't be reconfigured,
+ and audio playback will fail due to libswresample being unable to
+ convert between PCM and AC3 (Note: the responsible developer didn't
+ give a shit)
+ - inserting a filter that changes the output channel layout will not
+ reconfigure the AO - you need to run an additional "ao-reload"
+ command to force this if you want that
+ - using "string" gapless audio (--gapless-audio=yes) can fail if the
+ audio formats are not convertible (such as switching between PCM and
+ AC3 passthrough)
+ - remove out-format sub-parameter from "format" audio filter (no replacement)
--- mpv 0.28.0 ---
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
conventions followed by other hardware video decoding APIs
diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst
index eb139bc833..5d190cc82b 100644
--- a/DOCS/man/af.rst
+++ b/DOCS/man/af.rst
@@ -52,7 +52,8 @@ Available filters are:
Do not detach if input and output audio format/rate/channels match.
(If you just want to set defaults for this filter that will be used
even by automatically inserted lavrresample instances, you should
- prefer setting them with ``--af-defaults=lavrresample:...``.)
+ prefer setting them with the ``--audio-resample-...`` options.) This
+ does not do anything anymore and the filter will never detach.
``normalize=<yes|no|auto>``
Whether to normalize when remixing channel layouts (default: auto).
``auto`` uses the value set by ``--audio-normalize-downmix``.
@@ -97,7 +98,7 @@ Available filters are:
Select the libavcodec encoder used. Currently, this should be an AC-3
encoder, and using another codec will fail horribly.
-``format=format:srate:channels:out-format:out-srate:out-channels``
+``format=format:srate:channels:out-srate:out-channels``
Does not do any format conversion itself. Rather, it may cause the
filter system to insert necessary conversion filters before or after this
filter if needed. It is primarily useful for controlling the audio format
@@ -126,8 +127,6 @@ Available filters are:
Force mixing to a specific channel layout. See ``--audio-channels`` option
for possible values.
- ``<out-format>``
-
``<out-srate>``
``<out-channels>``
diff --git a/audio/aconverter.h b/audio/aconverter.h
deleted file mode 100644
index 22ca93e4c1..0000000000
--- a/audio/aconverter.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-
-#include "chmap.h"
-
-struct mp_aconverter;
-struct mp_aframe;
-struct mpv_global;
-struct mp_log;
-
-struct mp_resample_opts {
- int filter_size;
- int phase_shift;
- int linear;
- double cutoff;
- int normalize;
- int allow_passthrough;
- char **avopts;
-};
-
-#define MP_RESAMPLE_OPTS_DEF { \
- .filter_size = 16, \
- .cutoff = 0.0, \
- .phase_shift = 10, \
- .normalize = 0, \
- }
-
-extern const struct m_sub_options resample_config;
-
-struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
- struct mp_log *log,
- const struct mp_resample_opts *opts);
-bool mp_aconverter_reconfig(struct mp_aconverter *p,
- int in_rate, int in_format, struct mp_chmap in_channels,
- int out_rate, int out_format, struct mp_chmap out_channels);
-void mp_aconverter_flush(struct mp_aconverter *p);
-void mp_aconverter_set_speed(struct mp_aconverter *p, double speed);
-bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in);
-struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof);
-double mp_aconverter_get_latency(struct mp_aconverter *p);
diff --git a/audio/aframe.c b/audio/aframe.c
index 1f053a6715..9115cf67fd 100644
--- a/audio/aframe.c
+++ b/audio/aframe.c
@@ -32,6 +32,11 @@ struct mp_aframe {
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
int format;
double pts;
+ double speed;
+};
+
+struct avframe_opaque {
+ double speed;
};
static void free_frame(void *ptr)
@@ -43,11 +48,11 @@ static void free_frame(void *ptr)
struct mp_aframe *mp_aframe_create(void)
{
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
- frame->pts = MP_NOPTS_VALUE;
frame->av_frame = av_frame_alloc();
if (!frame->av_frame)
abort();
talloc_set_destructor(frame, free_frame);
+ mp_aframe_reset(frame);
return frame;
}
@@ -61,6 +66,7 @@ struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
dst->chmap = frame->chmap;
dst->format = frame->format;
dst->pts = frame->pts;
+ dst->speed = frame->speed;
if (mp_aframe_is_allocated(frame)) {
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
@@ -80,6 +86,7 @@ void mp_aframe_reset(struct mp_aframe *frame)
frame->chmap.num = 0;
frame->format = 0;
frame->pts = MP_NOPTS_VALUE;
+ frame->speed = 1.0;
}
// Remove all actual audio data and leave only the metadata.
@@ -120,6 +127,11 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
mp_chmap_from_channels(&frame->chmap, av_frame->channels);
#endif
+ if (av_frame->opaque_ref) {
+ struct avframe_opaque *op = (void *)av_frame->opaque_ref->data;
+ frame->speed = op->speed;
+ }
+
return frame;
}
@@ -137,6 +149,16 @@ struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame)
if (!mp_chmap_is_lavc(&frame->chmap))
return NULL;
+ if (!frame->av_frame->opaque_ref && frame->speed != 1.0) {
+ frame->av_frame->opaque_ref =
+ av_buffer_alloc(sizeof(struct avframe_opaque));
+ if (!frame->av_frame->opaque_ref)
+ return NULL;
+
+ struct avframe_opaque *op = (void *)frame->av_frame->opaque_ref->data;
+ op->speed = frame->speed;
+ }
+
return av_frame_clone(frame->av_frame);
}
@@ -183,6 +205,7 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src)
{
dst->pts = src->pts;
+ dst->speed = src->speed;
int rate = dst->av_frame->sample_rate;
@@ -316,6 +339,37 @@ void mp_aframe_set_pts(struct mp_aframe *frame, double pts)
frame->pts = pts;
}
+// Set a speed factor. This is multiplied with the sample rate to get the
+// "effective" samplerate (mp_aframe_get_effective_rate()), which will be used
+// to do PTS calculations. If speed!=1.0, the PTS values always refer to the
+// original PTS (before changing speed), and if you want reasonably continuous
+// PTS between frames, you need to use the effective samplerate.
+void mp_aframe_set_speed(struct mp_aframe *frame, double factor)
+{
+ frame->speed = factor;
+}
+
+// Adjust current speed factor.
+void mp_aframe_mul_speed(struct mp_aframe *frame, double factor)
+{
+ frame->speed *= factor;
+}
+
+double mp_aframe_get_speed(struct mp_aframe *frame)
+{
+ return frame->speed;
+}
+
+// Matters for speed changed frames (such as a frame which has been resampled
+// to play at a different speed).
+// Return the sample rate at which the frame would have to be played to result
+// in the same duration as the original frame before the speed change.
+// This is used for A/V sync.
+double mp_aframe_get_effective_rate(struct mp_aframe *frame)
+{
+ return mp_aframe_get_rate(frame) / frame->speed;
+}
+
// Return number of data pointers.
int mp_aframe_get_planes(struct mp_aframe *frame)
{
@@ -339,6 +393,18 @@ int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
? 1 : mp_aframe_get_channels(frame));
}
+char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt)
+{
+ char ch[128];
+ mp_chmap_to_str_buf(ch, sizeof(ch), &fmt->chmap);
+ char *hr_ch = mp_chmap_to_str_hr(&fmt->chmap);
+ if (strcmp(hr_ch, ch) != 0)
+ mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
+ snprintf(buf, buf_size, "%dHz %s %dch %s", fmt->av_frame->sample_rate,
+ ch, fmt->chmap.num, af_fmt_to_str(fmt->format));
+ return buf;
+}
+
// Set data to the audio after the given number of samples (i.e. slice it).
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
{
@@ -352,25 +418,25 @@ void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
f->av_frame->nb_samples -= samples;
if (f->pts != MP_NOPTS_VALUE)
- f->pts += samples / (double)mp_aframe_get_rate(f);
+ f->pts += samples / mp_aframe_get_effective_rate(f);
}
// Return the timestamp of the sample just after the end of this frame.
double mp_aframe_end_pts(struct mp_aframe *f)
{
- int rate = mp_aframe_get_rate(f);
- if (f->pts == MP_NOPTS_VALUE || rate < 1)
+ double rate = mp_aframe_get_effective_rate(f);
+ if (f->pts == MP_NOPTS_VALUE || rate <= 0)
return MP_NOPTS_VALUE;
- return f->pts + f->av_frame->nb_samples / (double)rate;
+ return f->pts + f->av_frame->nb_samples / rate;
}
// Return the duration in seconds of the frame (0 if invalid).
double mp_aframe_duration(struct mp_aframe *f)
{
- int rate = mp_aframe_get_rate(f);
- if (rate < 1)
+ double rate = mp_aframe_get_effective_rate(f);
+ if (rate <= 0)
return 0;
- return f->av_frame->nb_samples / (double)rate;
+ return f->av_frame->nb_samples / rate;
}
// Clip the given frame to the given timestamp range. Adjusts the frame size
@@ -378,7 +444,7 @@ double mp_aframe_duration(struct mp_aframe *f)
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
{
double f_end = mp_aframe_end_pts(f);
- int rate = mp_aframe_get_rate(f);
+ double rate = mp_aframe_get_effective_rate(f);
if (f_end == MP_NOPTS_VALUE)
return;
if (end != MP_NOPTS_VALUE) {
@@ -405,6 +471,52 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
}
}
+bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
+ struct mp_aframe *src, int src_offset,
+ int samples)
+{
+ if (!mp_aframe_config_equals(dst, src))
+ return false;
+
+ if (mp_aframe_get_size(dst) < dst_offset + samples ||
+ mp_aframe_get_size(src) < src_offset + samples)
+ return false;
+
+ uint8_t **s = mp_aframe_get_data_ro(src);
+ uint8_t **d = mp_aframe_get_data_rw(dst);
+ if (!s || !d)
+ return false;
+
+ int planes = mp_aframe_get_planes(dst);
+ size_t sstride = mp_aframe_get_sstride(dst);
+
+ for (int n = 0; n < planes; n++) {
+ memcpy(d[n] + dst_offset * sstride, s[n] + src_offset * sstride,
+ samples * sstride);
+ }
+
+ return true;
+}
+
+bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples)
+{
+ if (mp_aframe_get_size(f) < offset + samples)
+ return false;
+
+ int format = mp_aframe_get_format(f);
+ uint8_t **d = mp_aframe_get_data_rw(f);
+ if (!d)
+ return false;
+
+ int planes = mp_aframe_get_planes(f);
+ size_t sstride = mp_aframe_get_sstride(f);
+
+ for (int n = 0; n < planes; n++)
+ af_fill_silence(d[n] + offset * sstride, samples * sstride, format);
+
+ return true;
+}
+
struct mp_aframe_pool {
AVBufferPool *avpool;
int element_size;
diff --git a/audio/aframe.h b/audio/aframe.h
index 8ea676c198..ed92c223f6 100644
--- a/audio/aframe.h
+++ b/audio/aframe.h
@@ -36,21 +36,32 @@ int mp_aframe_get_channels(struct mp_aframe *frame);
int mp_aframe_get_rate(struct mp_aframe *frame);
int mp_aframe_get_size(struct mp_aframe *frame);
double mp_aframe_get_pts(struct mp_aframe *frame);
+double mp_aframe_get_speed(struct mp_aframe *frame);
+double mp_aframe_get_effective_rate(struct mp_aframe *frame);
bool mp_aframe_set_format(struct mp_aframe *frame, int format);
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in);
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate);
bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
+void mp_aframe_set_speed(struct mp_aframe *frame, double factor);
+void mp_aframe_mul_speed(struct mp_aframe *frame, double factor);
int mp_aframe_get_planes(struct mp_aframe *frame);
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
+char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt);
+#define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt))
+
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
double mp_aframe_end_pts(struct mp_aframe *f);
double mp_aframe_duration(struct mp_aframe *f);
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end);
+bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
+ struct mp_aframe *src, int src_offset,
+ int samples);
+bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples);
struct mp_aframe_pool;
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent);
diff --git a/audio/audio.c b/audio/audio.c
deleted file mode 100644
index 55e4266f45..0000000000
--- a/audio/audio.c
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdint.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include <libavutil/buffer.h>
-#include <libavutil/frame.h>
-#include <libavutil/mem.h>
-#include <libavutil/version.h>
-
-#include "mpv_talloc.h"
-#include "common/common.h"
-#include "fmt-conversion.h"
-#include "audio.h"
-#include "aframe.h"
-
-static void update_redundant_info(struct mp_audio *mpa)
-{
- assert(mp_chmap_is_empty(&mpa->channels) ||
- mp_chmap_is_valid(&mpa->channels));
- mpa->nch = mpa->channels.num;
- mpa->bps = af_fmt_to_bytes(mpa->format);
- if (af_fmt_is_planar(mpa->format)) {
- mpa->spf = 1;
- mpa->num_planes = mpa->nch;
- mpa->sstride = mpa->bps;
- } else {
- mpa->spf = mpa->nch;
- mpa->num_planes = 1;
- mpa->sstride = mpa->bps * mpa->nch;
- }
-}
-
-void mp_audio_set_format(struct mp_audio *mpa, int format)
-{
- mpa->format = format;
- update_redundant_info(mpa);
-}
-
-void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
-{
- mp_chmap_from_channels(&mpa->channels, num_channels);
- update_redundant_info(mpa);
-}
-
-void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap)
-{
- mpa->channels = *chmap;
- update_redundant_info(mpa);
-}
-
-void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src)
-{
- dst->format = src->format;
- dst->channels = src->channels;
- dst->rate = src->rate;
- update_redundant_info(dst);
-}
-
-bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
-{
- return a->format == b->format && a->rate == b->rate &&
- mp_chmap_equals(&a->channels, &b->channels);
-}
-
-bool mp_audio_config_valid(const struct mp_audio *mpa)
-{
- return mp_chmap_is_valid(&mpa->channels) && af_fmt_is_valid(mpa->format)
- && mpa->rate >= 1 && mpa->rate < 10000000;
-}
-
-char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
-{
- char ch[128];
- mp_chmap_to_str_buf(ch, sizeof(ch), &mpa->channels);
- char *hr_ch = mp_chmap_to_str_hr(&mpa->channels);
- if (strcmp(hr_ch, ch) != 0)
- mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
- snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
- ch, mpa->channels.num, af_fmt_to_str(mpa->format));
- return buf;
-}
-
-void mp_audio_force_interleaved_format(struct mp_audio *mpa)
-{
- if (af_fmt_is_planar(mpa->format))
- mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
-}
-
-// Return used size of a plane. (The size is the same for all planes.)
-int mp_audio_psize(struct mp_audio *mpa)
-{
- return mpa->samples * mpa->sstride;
-}
-
-void mp_audio_set_null_data(struct mp_audio *mpa)
-{
- for (int n = 0; n < MP_NUM_CHANNELS; n++) {
- mpa->planes[n] = NULL;
- mpa->allocated[n] = NULL;
- }
- mpa->samples = 0;
-}
-
-static int get_plane_size(const struct mp_audio *mpa, int samples)
-{
- if (samples < 0 || !mp_audio_config_valid(mpa))
- return -1;
- if (samples >= INT_MAX / mpa->sstride)
- return -1;
- return MPMAX(samples * mpa->sstride, 1);
-}
-
-static void mp_audio_destructor(void *ptr)
-{
- struct mp_audio *mpa = ptr;
- for (int n = 0; n < MP_NUM_CHANNELS; n++)
- av_buffer_unref(&mpa->allocated[n]);
-}
-
-/* Reallocate the data stored in mpa->planes[n] so that enough samples are
- * available on every plane. The previous data is kept (for the smallest
- * common number of samples before/after resize).
- *
- * This also makes sure the resulting buffer is writable (even in the case
- * the buffer has the correct size).
- *
- * mpa->samples is not set or used.
- *
- * This function is flexible enough to handle format and channel layout
- * changes. In these cases, all planes are reallocated as needed. Unused
- * planes are freed.
- *
- * mp_audio_realloc(mpa, 0) will still yield non-NULL for mpa->data[n].
- *
- * Allocated data is implicitly freed on talloc_free(mpa).
- */
-void mp_audio_realloc(struct mp_audio *mpa, int samples)
-{
- int size = get_plane_size(mpa, samples);
- if (size < 0)
- abort(); // oom or invalid parameters
- if (!mp_audio_is_writeable(mpa)) {
- for (int n = 0; n < MP_NUM_CHANNELS; n++) {
- av_buffer_unref(&mpa->allocated[n]);
- mpa->planes[n] = NULL;
- }
- }
- for (int n = 0; n < mpa->num_planes; n++) {
- if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
- if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
- abort(); // OOM
- }
- mpa->planes[n] = mpa->allocated[n]->data;
- }
- for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
- av_buffer_unref(&mpa->allocated[n]);
- mpa->planes[n] = NULL;
- }
- talloc_set_destructor(mpa, mp_audio_destructor);
-}
-
-// Like mp_audio_realloc(), but only reallocate if the audio grows in size.
-// If the buffer is reallocated, also preallocate.
-void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
-{
- if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
- size_t alloc = ta_calc_prealloc_elems(samples);
- if (alloc > INT_MAX)
- abort(); // oom
- mp_audio_realloc(mpa, alloc);
- }
-}
-
-/* Get the size allocated for the data, in number of samples. If the allocated
- * size isn't on sample boundaries (e.g. after format changes), the returned
- * sample number is a rounded down value.
- *
- * Note that this only works in situations where mp_audio_realloc() also works!
- */
-int mp_audio_get_allocated_size(struct mp_audio *mpa)
-{
- int size = 0;
- for (int n = 0; n < mpa->num_planes; n++) {
- for (int i = 0; i < MP_NUM_CHANNELS && mpa->allocated[i]; i++) {
- uint8_t *start = mpa->allocated[i]->data;
- uint8_t *end = start + mpa->allocated[i]->size;
- uint8_t *plane = mpa->planes[n];
- if (plane >= start && plane < end) {
- int s = MPMIN((end - plane) / mpa->sstride, INT_MAX);
- size = n == 0 ? s : MPMIN(size, s);
- goto next;
- }
- }
- return 0; // plane is not covered by any buffer
- next: ;
- }
- return size;
-}
-
-// Clear the samples [start, start + length) with silence.
-void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length)
-{
- assert(start >= 0 && length >= 0 && start + length <= mpa->samples);
- int offset = start * mpa->sstride;
- int size = length * mpa->sstride;
- for (int n = 0; n < mpa->num_planes; n++) {
- if (n > 0 && mpa->planes[n] == mpa->planes[0])
- continue; // silly optimization for special cases
- af_fill_silence((char *)mpa->planes[n] + offset, size, mpa->format);
- }
-}
-
-// All integer parameters are in samples.
-// dst and src can overlap.
-void mp_audio_copy(struct mp_audio *dst, int dst_offset,
- struct mp_audio *src, int src_offset, int length)
-{
- assert(mp_audio_config_equals(dst, src));
- assert(length >= 0);
- assert(dst_offset >= 0 && dst_offset + length <= dst->samples);
- assert(src_offset >= 0 && src_offset + length <= src->samples);
-
- for (int n = 0; n < dst->num_planes; n++) {
- memmove((char *)dst->planes[n] + dst_offset * dst->sstride,
- (char *)src->planes[n] + src_offset * src->sstride,
- length * dst->sstride);
- }
-}
-
-// Copy fields that describe characteristics of the audio frame, but which are
-// not part of the core format (format/channels/rate), and not part of the
-// data (samples).
-void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src)
-{
- // nothing yet
-}
-
-// Set data to the audio after the given number of samples (i.e. slice it).
-void mp_audio_skip_samples(struct mp_audio *data, int samples)
-{
- assert(samples >= 0 && samples <= data->samples);
-
- for (int n = 0; n < data->num_planes; n++)
- data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
-
- data->samples -= samples;
-
- if (data->pts != MP_NOPTS_VALUE)
- data->pts += samples / (double)data->rate;
-}
-
-// Return the timestamp of the sample just after the end of this frame.
-double mp_audio_end_pts(struct mp_audio *f)
-{
- if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
- return MP_NOPTS_VALUE;
- return f->pts + f->samples / (double)f->rate;
-}
-
-// Clip the given frame to the given timestamp range. Adjusts the frame size
-// and timestamp.
-void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
-{
- double f_end = mp_audio_end_pts(f);
- if (f_end == MP_NOPTS_VALUE)
- return;
- if (end != MP_NOPTS_VALUE) {
- if (f_end >= end) {
- if (f->pts >= end) {
- f->samples = 0;
- } else {
- int new = (end - f->pts) * f->rate;
- f->samples = MPCLAMP(new, 0, f->samples);
- }
- }
- }
- if (start != MP_NOPTS_VALUE) {
- if (f->pts < start) {
- if (f_end <= start) {
- f->samples = 0;
- f->pts = f_end;
- } else {
- int skip = (start - f->pts) * f->rate;
- skip = MPCLAMP(skip, 0, f->samples);
- mp_audio_skip_samples(f, skip);
- }
- }
- }
-}
-
-
-// Return false if the frame data is shared, true otherwise.
-// Will return true for non-refcounted frames.
-bool mp_audio_is_writeable(struct mp_audio *data)
-{
- bool ok = true;
- for (int n = 0; n < MP_NUM_CHANNELS; n++) {
- if (data->allocated[n])
- ok &= av_buffer_is_writable(data->allocated[n]);
- }
- return ok;
-}
-
-static void mp_audio_steal_data(struct mp_audio *dst, struct mp_audio *src)
-{
- talloc_set_destructor(dst, mp_audio_destructor);
- mp_audio_destructor(dst);
- *dst = *src;
- talloc_set_destructor(src, NULL);
- talloc_free(src);
-}
-
-// Make sure the frame owns the audio data, and if not, copy the data.
-// Return negative value on failure (which means it can't be made writeable).
-// Non-refcounted frames are always considered writeable.
-int mp_audio_make_writeable(struct mp_audio *data)
-{
- if (!mp_audio_is_writeable(data)) {
- struct mp_audio *new = talloc(NULL, struct mp_audio);
- *new = *data;
- mp_audio_set_null_data(new); // use format only
- mp_audio_realloc(new, data->samples);
- new->samples = data->samples;
- mp_audio_copy(new, 0, data, 0, data->samples);
- mp_audio_steal_data(data, new);
- }
- return 0;
-}
-
-struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
-{
- AVFrame *tmp = NULL;
- struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
- talloc_set_destructor(new, mp_audio_destructor);
-
- mp_audio_set_format(new, af_from_avformat(avframe->format));
-
- struct mp_chmap lavc_chmap;
- mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
-
-#if LIBAVUTIL_VERSION_MICRO >= 100
- // FFmpeg being stupid POS again
- if (lavc_chmap.num != avframe->channels)
- mp_chmap_from_channels(&lavc_chmap, avframe->channels);
-#endif
-
- new->rate = avframe->sample_rate;
-
- mp_audio_set_channels(new, &lavc_chmap);
-
- // Force refcounted frame.
- if (!avframe->buf[0]) {
- tmp = av_frame_alloc();
- if (!tmp)
- goto fail;
- if (av_frame_ref(tmp, avframe) < 0)
- goto fail;
- avframe = tmp;
- }
-
- // If we can't handle the format (e.g. too many channels), bail out.
- if (!mp_audio_config_valid(new))
- goto fail;
-
- for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
- AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
- : avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
- if (!buf)
- break;
- if (n >= MP_NUM_CHANNELS)
- goto fail;
- new->allocated[n] = av_buffer_ref(buf);
- if (!new->allocated[n])
- goto fail;
- }
-
- for (int n = 0; n < new->num_planes; n++)
- new->planes[n] = avframe->extended_data[n];
- new->samples = avframe->nb_samples;
-
- return new;
-
-fail:
- talloc_free(new);
- av_frame_free(&tmp);
- return NULL;
-}
-
-struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
-{
- if (!aframe)
- return NULL;
-
- struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
- struct mp_audio *res = mp_audio_from_avframe(av);
- if (!res)
- return NULL;
- struct mp_chmap chmap = {0};
- mp_aframe_get_chmap(aframe, &chmap);
- mp_audio_set_channels(res, &chmap);
- mp_audio_set_format(res, mp_aframe_get_format(aframe));
- res->pts = mp_aframe_get_pts(aframe);
- return res;
-}
-
-void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
-{
- *dst = (struct mp_audio){0};
- struct mp_chmap chmap = {0};
- mp_aframe_get_chmap(src, &chmap);
- mp_audio_set_channels(dst, &chmap);
- mp_audio_set_format(dst, mp_aframe_get_format(src));
- dst->rate = mp_aframe_get_rate(src);
-}
-
-struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa)
-{
- if (!mpa)
- return NULL;
-
- struct mp_aframe *aframe = mp_aframe_create();
- struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
- mp_aframe_set_format(aframe, mpa->format);
- mp_aframe_set_chmap(aframe, &mpa->channels);
- mp_aframe_set_rate(aframe, mpa->rate);
-
- // bullshit it into ffmpeg-compatible parameters
- struct mp_audio mpb = *mpa;
- struct mp_chmap chmap;
- mp_chmap_set_unknown(&chmap, mpb.channels.num);
- mp_audio_set_channels(&mpb, &chmap);
- if (af_fmt_is_spdif(mpb.format))
- mp_audio_set_format(&mpb, AF_FORMAT_S16);
-
- // put the reference into av, which magically puts it into aframe
- // aframe keeps its parameters, so the bullshit doesn't matter
- if (mp_audio_to_avframe(&mpb, av) < 0) {
- talloc_free(aframe);
- return NULL;
- }
- return aframe;
-}
-
-int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
-{
- av_frame_unref(avframe);
-
- avframe->nb_samples = frame->samples;
- avframe->format = af_to_avformat(frame->format);
- if (avframe->format == AV_SAMPLE_FMT_NONE)
- goto fail;
-
- avframe->channel_layout = mp_chmap_to_lavc(&frame->channels);
- if (!avframe->channel_layout)
- goto fail;
-#if LIBAVUTIL_VERSION_MICRO >= 100
- // FFmpeg being a stupid POS again
- avframe->channels = frame->channels.num;
-#endif
- avframe->sample_rate = frame->rate;
-
- if (frame->num_planes > AV_NUM_DATA_POINTERS) {
- avframe->extended_data =
- av_mallocz_array(frame->num_planes, sizeof(avframe->extended_data[0]));
- int extbufs = frame->num_planes - AV_NUM_DATA_POINTERS;
- avframe->extended_buf =
- av_mallocz_array(extbufs, sizeof(avframe->extended_buf[0]));
- if (!avframe->extended_data || !avframe->extended_buf)
- goto fail;
- avframe->nb_extended_buf = extbufs;
- }
-
- for (int p = 0; p < frame->num_planes; p++)
- avframe->extended_data[p] = frame->planes[p];
- avframe->linesize[0] = frame->samples * frame->sstride;
-
- for (int p = 0; p < AV_NUM_DATA_POINTERS; p++)
- avframe->data[p] = avframe->extended_data[p];
-
- for (int p = 0; p < frame->num_planes; p++) {
- if (!frame->allocated[p])
- break;
- AVBufferRef *nref = av_buffer_ref(frame->allocated[p]);
- if (!nref)
- goto fail;
- if (p < AV_NUM_DATA_POINTERS) {
- avframe->buf[p] = nref;
- } else {
- avframe->extended_buf[p - AV_NUM_DATA_POINTERS] = nref;
- }
- }
-
- // Force refcounted frame.
- if (!avframe->buf[0]) {
- AVFrame *tmp = av_frame_alloc();
- if (!tmp)
- goto fail;
- if (av_frame_ref(tmp, avframe) < 0)
- goto fail;
- av_frame_free(&avframe);
- avframe = tmp;
- }
-
- return 0;
-
-fail:
- av_frame_unref(avframe);
- return -1;
-}
-
-// Returns NULL on failure. The input is always unreffed.
-struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
-{
- struct AVFrame *avframe = av_frame_alloc();
- if (!avframe)
- goto fail;
-
- if (mp_audio_to_avframe(frame, avframe) < 0)
- goto fail;
-
- talloc_free(frame);
- return avframe;
-
-fail:
- av_frame_free(&avframe);
- talloc_free(frame);
- return NULL;
-}
-
-struct mp_audio_pool {
- AVBufferPool *avpool;
- int element_size;
-};
-
-struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
-{
- return talloc_zero(ta_parent, struct mp_audio_pool);
-}
-
-static void mp_audio_pool_destructor(void *p)
-{
- struct mp_audio_pool *pool = p;
- av_buffer_pool_uninit(&pool->avpool);
-}
-
-// Allocate data using the given format and number of samples.
-// Returns NULL on error.
-struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
- const struct mp_audio *fmt, int samples)
-{
- int size = get_plane_size(fmt, samples);
- if (size < 0)
- return NULL;
- if (!pool->avpool || size > pool->element_size) {
- size_t alloc = ta_calc_prealloc_elems(size);
- if (alloc >= INT_MAX)
- return NULL;
- av_buffer_pool_uninit(&pool->avpool);
- pool->element_size = alloc;
- pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
- if (!pool->avpool)
- return NULL;
- talloc_set_destructor(pool, mp_audio_pool_destructor);
- }
- struct mp_audio *new = talloc_ptrtype(NULL, new);
- talloc_set_destructor(new, mp_audio_destructor);
- *new = *fmt;
- mp_audio_set_null_data(new);
- new->samples = samples;
- for (int n = 0; n < new->num_planes; n++) {
- new->allocated[n] = av_buffer_pool_get(pool->avpool);
- if (!new->allocated[n]) {
- talloc_free(new);
- return NULL;
- }
- new->planes[n] = new->allocated[n]->data;
- }
- return new;
-}
-
-// Return a copy of the given frame.
-// Returns NULL on error.
-struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
- struct mp_audio *frame)
-{
- struct mp_audio *new = mp_audio_pool_get(pool, frame, frame->samples);
- if (new) {
- mp_audio_copy(new, 0, frame, 0, new->samples);
- mp_audio_copy_attributes(new, frame);
- }
- return new;
-}
-
-// Exactly like mp_audio_make_writeable(), but get the data from the pool.
-int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
- struct mp_audio *data)
-{
- if (mp_audio_is_writeable(data))
- return 0;
- struct mp_audio *new = mp_audio_pool_get(pool, data, data->samples);
- if (!new)
- return -1;
- mp_audio_copy(new, 0, data, 0, data->samples);
- mp_audio_copy_attributes(new, data);
- mp_audio_steal_data(data, new);
- return 0;
-}
diff --git a/audio/audio.h b/audio/audio.h
deleted file mode 100644
index f370067b78..0000000000
--- a/audio/audio.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MP_AUDIO_H
-#define MP_AUDIO_H
-
-#include "format.h"
-#include "chmap.h"
-
-// Audio data chunk
-struct mp_audio {
- int samples; // number of samples in data (per channel)
- void *planes[MP_NUM_CHANNELS]; // data buffer (one per plane)
- int rate; // sample rate
- struct mp_chmap channels; // channel layout, use mp_audio_set_*() to set
- int format; // format (AF_FORMAT_...), use mp_audio_set_format() to set
- // Redundant fields, for convenience
- int sstride; // distance between 2 samples in bytes on a plane
- // interleaved: bps * nch
- // planar: bps
- int nch; // number of channels (redundant with chmap)
- int spf; // sub-samples per sample on each plane
- int num_planes; // number of planes
- int bps; // size of sub-samples (af_fmt_to_bytes(format))
-
- double pts; // currently invalid within the filter chain
-
- // --- private
- // These do not necessarily map directly to planes[]. They can have
- // different order or count. There shouldn't be more buffers than planes.
- // If allocated[n] is NULL, allocated[n+1] must also be NULL.
- struct AVBufferRef *allocated[MP_NUM_CHANNELS];
-};
-
-void mp_audio_set_format(struct mp_audio *mpa, int format);
-void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels);
-void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap);
-void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
-bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
-bool mp_audio_config_valid(const struct mp_audio *mpa);
-
-char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
-#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
-
-void mp_audio_force_interleaved_format(struct mp_audio *mpa);
-
-int mp_audio_psize(struct mp_audio *mpa);
-
-void mp_audio_set_null_data(struct mp_audio *mpa);
-
-void mp_audio_realloc(struct mp_audio *mpa, int samples);
-void mp_audio_realloc_min(struct mp_audio *mpa, int samples);
-int mp_audio_get_allocated_size(struct mp_audio *mpa);
-
-void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length);
-
-void mp_audio_copy(struct mp_audio *dst, int dst_offset,
- struct mp_audio *src, int src_offset, int length);
-void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
-void mp_audio_skip_samples(struct mp_audio *data, int samples);
-void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
-double mp_audio_end_pts(struct mp_audio *data);
-
-bool mp_audio_is_writeable(struct mp_audio *data);
-int mp_audio_make_writeable(struct mp_audio *data);
-
-struct AVFrame;
-struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
-struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
-int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
-
-struct mp_aframe;
-struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
-void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
-struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa);
-
-struct mp_audio_pool;
-struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
-struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
- const struct mp_audio *fmt, int samples);
-struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
- struct mp_audio *frame);
-int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
- struct mp_audio *frame);
-
-#include "filter/af.h"
-
-#endif
diff --git a/audio/filter/af.c b/audio/filter/af.c
deleted file mode 100644
index cf200bbb84..0000000000
--- a/audio/filter/af.c
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "config.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "common/common.h"
-#include "common/global.h"
-
-#include "options/m_option.h"
-#include "options/m_config.h"
-
-#include "audio/audio_buffer.h"
-#include "af.h"
-
-// Static list of filters
-extern const struct af_info af_info_format;
-extern const struct af_info af_info_lavcac3enc;
-extern const struct af_info af_info_lavrresample;
-extern const struct af_info af_info_scaletempo;
-extern const struct af_info af_info_lavfi;
-extern const struct af_info af_info_lavfi_bridge;
-extern const struct af_info af_info_rubberband;
-
-static const struct af_info *const filter_list[] = {
- &af_info_format,
- &af_info_lavcac3enc,
- &af_info_lavrresample,
-#if HAVE_RUBBERBAND
- &af_info_rubberband,
-#endif
- &af_info_scaletempo,
- &af_info_lavfi,
- &af_info_lavfi_bridge,
- NULL
-};
-
-static bool get_desc(struct m_obj_desc *dst, int index)
-{
- if (index >= MP_ARRAY_SIZE(filter_list) - 1)
- return false;
- const struct af_info *af = filter_list[index];
- *dst = (struct m_obj_desc) {
- .name = af->name,
- .description = af->info,
- .priv_size = af->priv_size,
- .priv_defaults = af->priv_defaults,
- .options = af->options,
- .set_defaults = af->set_defaults,
- .p = af,
- };
- return true;
-}
-
-const struct m_obj_list af_obj_list = {
- .get_desc = get_desc,
- .description = "audio filters",
- .allow_disable_entries = true,
- .allow_unknown_entries = true,
- .aliases = {
- {"force", "format"},
- {0}
- },
-};
-
-static void af_forget_frames(struct af_instance *af)
-{
- for (int n = 0; n < af->num_out_queued; n++)
- talloc_free(af->out_queued[n]);
- af->num_out_queued = 0;
-}
-
-static void af_chain_forget_frames(struct af_stream *s)
-{
- for (struct af_instance *cur = s->first; cur; cur = cur->next)
- af_forget_frames(cur);
-}
-
-static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
-{
- if (dst->format == AF_FORMAT_UNKNOWN)
- mp_audio_set_format(dst, src->format);
- if (dst->nch == 0)
- mp_audio_set_channels(dst, &src->channels);
- if (dst->rate == 0)
- dst->rate = src->rate;
-}
-
-static int input_control(struct af_instance* af, int cmd, void* arg)
-{
- switch (cmd) {
- case AF_CONTROL_REINIT:
- assert(arg == &((struct af_stream *)af->priv)->input);
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-static int output_control(struct af_instance* af, int cmd, void* arg)
-{
- struct af_stream *s = af->priv;
- struct mp_audio *output = &s->output;
- struct mp_audio *filter_output = &s->filter_output;
-
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
-
- *filter_output = *output;
- af_copy_unset_fields(filter_output, in);
- *in = *filter_output;
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- }
- return AF_UNKNOWN;
-}
-
-static int dummy_filter(struct af_instance *af, struct mp_audio *frame)
-{
- af_add_output_frame(af, frame);
- return 0;
-}
-
-/* Function for creating a new filter of type name.The name may
-contain the commandline parameters for the filter */
-static struct af_instance *af_create(struct af_stream *s, char *name,
- char **args)
-{
- const char *lavfi_name = NULL;
- char **lavfi_args = NULL;
- struct m_obj_desc desc;
- if (!m_obj_list_find(&desc, &af_obj_list, bstr0(name))) {
- if (!m_obj_list_find(&desc, &af_obj_list, bstr0("lavfi-bridge"))) {
- MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
- return NULL;
- }
- lavfi_name = name;
- lavfi_args = args;
- args = NULL;
- if (strncmp(lavfi_name, "lavfi-", 6) == 0)
- lavfi_name += 6;
- }
- MP_VERBOSE(s, "Adding filter %s \n", name);
-
- struct af_instance *af = talloc_zero(NULL, struct af_instance);
- *af = (struct af_instance) {
- .full_name = talloc_strdup(af, name),
- .info = desc.p,
- .data = talloc_zero(af, struct mp_audio),
- .log = mp_log_new(af, s->log, name),
- .opts = s->opts,
- .global = s->global,
- .out_pool = mp_audio_pool_create(af),
- };
- struct m_config *config =
- m_config_from_obj_desc_and_args(af, s->log, s->global, &desc,
- name, s->opts->af_defs, args);
- if (!config)
- goto error;
- if (lavfi_name) {
- // Pass the filter arguments as proper sub-options to the bridge filter.
- struct m_config_option *name_opt = m_config_get_co(config, bstr0("name"));
- assert(name_opt);
- assert(name_opt->opt->type == &m_option_type_string);
- if (m_config_set_option_raw(config, name_opt, &lavfi_name, 0) < 0)
- goto error;
- struct m_config_option *opts = m_config_get_co(config, bstr0("opts"));
- assert(opts);
- assert(opts->opt->type == &m_option_type_keyvalue_list);
- if (m_config_set_option_raw(config, opts, &lavfi_args, 0) < 0)
- goto error;
- af->full_name = talloc_asprintf(af, "%s (lavfi)", af->full_name);
- }
- af->priv = config->optstruct;
-
- // Initialize the new filter
- if (af->info->open(af) != AF_OK)
- goto error;
-
- return af;
-
-error:
- MP_ERR(s, "Couldn't create or open audio filter '%s'\n", name);
- talloc_free(af);
- return NULL;
-}
-
-/* Create and insert a new filter of type name before the filter in the
- argument. This function can be called during runtime, the return
- value is the new filter */
-static struct af_instance *af_prepend(struct af_stream *s,
- struct af_instance *af,
- char *name, char **args)
-{
- if (!af)
- af = s->last;
- if (af == s->first)
- af = s->first->next;
- // Create the new filter and make sure it is OK
- struct af_instance *new = af_create(s, name, args);
- if (!new)
- return NULL;
- // Update pointers
- new->next = af;
- new->prev = af->prev;
- af->prev = new;
- new->prev->next = new;
- return new;
-}
-
-// Uninit and remove the filter "af"
-static void af_remove(struct af_stream *s, struct af_instance *af)
-{
- if (!af)
- return;
-
- if (af == s->first || af == s->last)
- return;
-
- // Print friendly message
- MP_VERBOSE(s, "Removing filter %s \n", af->info->name);
-
- // Detach pointers
- af->prev->next = af->next;
- af->next->prev = af->prev;
-
- if (af->uninit)
- af->uninit(af);
- af_forget_frames(af);
- talloc_free(af);
-}
-
-static void remove_auto_inserted_filters(struct af_stream *s)
-{
-repeat:
- for (struct af_instance *af = s->first; af; af = af->next) {
- if (af->auto_inserted) {
- af_remove(s, af);
- goto repeat;
- }
- }
-}
-
-static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
- int msg_level)
-{
- MP_MSG(s, msg_level, "Audio filter chain:\n");
-
- struct af_instance *af = s->first;
- while (af) {
- char b[128] = {0};
- mp_snprintf_cat(b, sizeof(b), " [%s] ", af->full_name);
- if (af->label)
- mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
- if (af->data)
- mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
- if (af->auto_inserted)
- mp_snprintf_cat(b, sizeof(b), " [a]");
- if (af == at)
- mp_snprintf_cat(b, sizeof(b), " <-");
- MP_MSG(s, msg_level, "%s\n", b);
-
- af = af->next;
- }
-
- MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
-}
-
-static void reset_formats(struct af_stream *s)
-{
- struct mp_audio none = {0};
- for (struct af_instance *af = s->first; af; af = af->next) {
- if (af != s->first && af != s->last)
- mp_audio_copy_config(af->data, &none);
- }
-}
-
-static int filter_reinit(struct af_instance *af)
-{
- struct af_instance *prev = af->prev;
- assert(prev);
-
- // Check if this is the first filter
- struct mp_audio in = *prev->data;
- // Reset just in case...
- mp_audio_set_null_data(&in);
-
- if (!mp_audio_config_valid(&in))
- return AF_ERROR;
-
- af->fmt_in = in;
- int rv = af->control(af, AF_CONTROL_REINIT, &in);
- if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
- rv = AF_FALSE; // conversion filter needed
- if (rv == AF_FALSE)
- af->fmt_in = in;
-
- if (rv == AF_OK) {
- if (!mp_audio_config_valid(af->data))
- return AF_ERROR;
- af->fmt_out = *af->data;
- }
-
- return rv;
-}
-
-static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
-{
- int rv = filter_reinit(af);
-
- // Conversion filter is needed
- if (rv == AF_FALSE) {
- // First try if we can change the output format of the previous
- // filter to the input format the current filter is expecting.
- struct mp_audio in = af->fmt_in;
- if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
- // This should have been successful (because it succeeded
- // before), even if just reverting to the old output format.
- mp_audio_copy_config(af->prev->data, &in);
- rv = filter_reinit(af->prev);
- if (rv != AF_OK)
- return rv;
- }
- if (!mp_audio_config_equals(af->prev->data, &in)) {
- // Retry with conversion filter added.
- char *opts[] = {"deprecation-warning", "no", NULL};
- struct af_instance *new =
- af_prepend(s, af, "lavrresample", opts);
- if (!new)
- return AF_ERROR;
- new->auto_inserted = true;
- mp_audio_copy_config(new->data, &in);
- rv = filter_reinit(new);
- if (rv != AF_OK)
- af_remove(s, new);
- }
- if (rv == AF_OK)
- rv = filter_reinit(af);
- }
-
- return rv;
-}
-
-static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
-{
- assert(mp_audio_config_valid(&s->output));
- assert(s->initialized > 0);
-
- if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
- return AF_ERROR;
-
- // Heuristic to detect point of conversion. If it looks like something
- // more complicated is going on, better bail out.
- // We expect that the last filter converts channels.
- struct af_instance *conv = s->last->prev;
- if (!conv->auto_inserted)
- return AF_ERROR;
- if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
- mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
- return AF_ERROR;
- // Also, should be the only one which does auto conversion.
- for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
- {
- if (af != conv && af->auto_inserted &&
- !mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
- return AF_ERROR;
- }
- // And not if it's the only filter.
- if (conv->prev == s->first && conv->next == s->last)
- return AF_ERROR;
-
- *cfg = s->output;
- return AF_OK;
-}
-
-// Return AF_OK on success or AF_ERROR on failure.
-static int af_do_reinit(struct af_stream *s, bool second_pass)
-{
- struct mp_audio convert_early = {0};
- if (second_pass) {
- // If a channel conversion happens, and it is done by an auto-inserted
- // filter, then insert a filter to convert it early. Otherwise, do
- // nothing and return immediately.
- if (af_find_output_conversion(s, &convert_early) != AF_OK)
- return AF_OK;
- }
-
- remove_auto_inserted_filters(s);
- af_chain_forget_frames(s);
- reset_formats(s);
- s->first->fmt_in = s->first->fmt_out = s->input;
-
- if (mp_audio_config_valid(&convert_early)) {
- char *opts[] = {"deprecation-warning", "no", NULL};
- struct af_instance *new = af_prepend(s, s->first, "lavrresample", opts);
- if (!new)
- return AF_ERROR;
- new->auto_inserted = true;
- mp_audio_copy_config(new->data, &convert_early);
- int rv = filter_reinit(new);
- if (rv != AF_DETACH && rv != AF_OK)
- return AF_ERROR;
- MP_VERBOSE(s, "Moving up output conversion.\n");
- }
-
- // Start with the second filter, as the first filter is the special input
- // filter which needs no initialization.
- struct af_instance *af = s->first->next;
- while (af) {
- int rv = filter_reinit_with_conversion(s, af);
-
- switch (rv) {
- case AF_OK:
- af = af->next;
- break;
- case AF_FALSE: {
- // If the format conversion is (probably) caused by spdif, then
- // (as a feature) drop the filter, instead of failing hard.
- int fmt_in1 = af->prev->data->format;
- int fmt_in2 = af->fmt_in.format;
- if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
- bool spd1 = af_fmt_is_spdif(fmt_in1);
- bool spd2 = af_fmt_is_spdif(fmt_in2);
- if (spd1 != spd2 && af->next) {
- MP_WARN(af, "Filter %s apparently cannot be used due to "
- "spdif passthrough - removing it.\n",
- af->info->name);
- struct af_instance *aft = af->prev;
- af_remove(s, af);
- af = aft->next;
- continue;
- }
- }
- goto negotiate_error;
- }
- case AF_DETACH: { // Filter is redundant and wants to be unloaded
- struct af_instance *aft = af->prev; // never NULL
- af_remove(s, af);
- af = aft->next;
- break;
- }
- default:
- MP_ERR(s, "Reinitialization did not work, "
- "audio filter '%s' returned error code %i\n",
- af->info->name, rv);
- goto error;
- }
- }
-
- /* Set previously unset fields in s->output to those of the filter chain
- * output. This is used to make the output format fixed, and even if you
- * insert new filters or change the input format, the output format won't
- * change. (Audio outputs generally can't change format at runtime.) */
- af_copy_unset_fields(&s->output, &s->filter_output);
- if (mp_audio_config_equals(&s->output, &s->filter_output)) {
- s->initialized = 1;
- af_print_filter_chain(s, NULL, MSGL_V);
- return AF_OK;
- }
-
- goto error;
-
-negotiate_error:
- MP_ERR(s, "Unable to convert audio input format to output format.\n");
-error:
- s->initialized = -1;
- af_print_filter_chain(s, af, MSGL_ERR);
- return AF_ERROR;
-}
-
-static int af_reinit(struct af_stream *s)
-{
- int r = af_do_reinit(s, false);
- if (r == AF_OK && mp_audio_config_valid(&s->output)) {
- r = af_do_reinit(s, true);
- if (r != AF_OK) {
- MP_ERR(s, "Failed second pass filter negotiation.\n");
- r = af_do_reinit(s, false);
- }
- }
- return r;
-}
-
-// Uninit and remove all filters
-void af_uninit(struct af_stream *s)
-{
- while (s->first->next && s->first->next != s->last)
- af_remove(s, s->first->next);
- af_chain_forget_frames(s);
- s->initialized = 0;
-}
-
-struct af_stream *af_new(struct mpv_global *global)
-{
- struct af_stream *s = talloc_zero(NULL, struct af_stream);
- s->log = mp_log_new(s, global->log, "!af");
-
- static const struct af_info in = { .name = "in" };
- s->first = talloc(s, struct af_instance);
- *s->first = (struct af_instance) {
- .full_name = "in",
- .info = &in,
- .log = s->log,
- .control = input_control,
- .filter_frame = dummy_filter,
- .priv = s,
- .data = &s->input,
- };
-
- static const struct af_info out = { .name = "out" };
- s->last = talloc(s, struct af_instance);
- *s->last = (struct af_instance) {
- .full_name = "out",
- .info = &out,
- .log = s->log,
- .control = output_control,
- .filter_frame = dummy_filter,
- .priv = s,
- .data = &s->filter_output,
- };
-
- s->first->next = s->last;
- s->last->prev = s->first;
- s->opts = global->opts;
- s->global = global;
- return s;
-}
-
-void af_destroy(struct af_stream *s)
-{
- af_uninit(s);
- talloc_free(s);
-}
-
-/* Initialize the stream "s". This function creates a new filter list
- if necessary according to the values set in input and output. Input
- and output should contain the format of the current movie and the
- format of the preferred output respectively. The function is
- reentrant i.e. if called with an already initialized stream the
- stream will be reinitialized.
- If one of the preferred output parameters is 0 the one that needs
- no conversion is used (i.e. the output format in the last filter).
- The return value is 0 if success and -1 if failure */
-int af_init(struct af_stream *s)
-{
- // Precaution in case caller is misbehaving
- mp_audio_set_null_data(&s->input);
- mp_audio_set_null_data(&s->output);
-
- // Check if this is the first call
- if (s->first->next == s->last) {
- // Add all filters in the list (if there are any)
- struct m_obj_settings *list = s->opts->af_settings;
- for (int i = 0; list && list[i].name; i++) {
- if (!list[i].enabled)
- continue;
- struct af_instance *af =
- af_prepend(s, s->last, list[i].name, list[i].attribs);
- if (!af) {
- af_uninit(s);
- s->initialized = -1;
- return -1;
- }
- af->label = talloc_strdup(af, list[i].label);
- }
- }
-
- if (af_reinit(s) != AF_OK) {
- // Something is stuffed audio out will not work
- MP_ERR(s, "Could not create audio filter chain.\n");
- return -1;
- }
- return 0;
-}
-
-/* Add filter during execution. This function adds the filter "name"
- to the stream s. The filter will be inserted somewhere nice in the
- list of filters. The return value is a pointer to the new filter,
- If the filter couldn't be added the return value is NULL. */
-struct af_instance *af_add(struct af_stream *s, char *name, char *label,
- char **args)
-{
- assert(label);
-
- if (af_find_by_label(s, label))
- return NULL;
-
- struct af_instance *new = af_prepend(s, s->last, name, args);
- if (!new)
- return NULL;
- new->label = talloc_strdup(new, label);
-
- // Reinitialize the filter list
- if (af_reinit(s) != AF_OK) {
- af_remove_by_label(s, label);
- return NULL;
- }
- return af_find_by_label(s, label);
-}
-
-struct af_instance *af_find_by_label(struct af_stream *s, char *label)
-{
- for (struct af_instance *af = s->first; af; af = af->next) {
- if (af->label && strcmp(af->label, label) == 0)
- return af;
- }
- return NULL;
-}
-
-/* Remove the first filter that matches this name. Return number of filters
- * removed (0, 1), or a negative error code if reinit after removing failed.
- */
-int af_remove_by_label(struct af_stream *s, char *label)
-{
- struct af_instance *af = af_find_by_label(s, label);
- if (!af)
- return 0;
- af_remove(s, af);
- if (af_reinit(s) != AF_OK) {
- af_uninit(s);
- af_init(s);
- return -1;
- }
- return 1;
-}
-
-/* Calculate the total delay [seconds of output] caused by the filters */
-double af_calc_delay(struct af_stream *s)
-{
- struct af_instance *af = s->first;
- double delay = 0.0;
- while (af) {
- delay += af->delay;
- for (int n = 0; n < af->num_out_queued; n++)
- delay += af->out_queued[n]->samples / (double)af->data->rate;
- af = af->next;
- }
- return delay;
-}
-
-/* Send control to all filters, starting with the last until one accepts the
- * command with AF_OK. Return the accepting filter. */
-struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
-{
- int res = AF_UNKNOWN;
- struct af_instance *filt = s->last;
- while (filt) {
- res = filt->control(filt, cmd, arg);
- if (res == AF_OK)
- return filt;
- filt = filt->prev;
- }
- return NULL;
-}
-
-/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
-void af_control_all(struct af_stream *s, int cmd, void *arg)
-{
- for (struct af_instance *af = s->first; af; af = af->next)
- af->control(af, cmd, arg);
-}
-
-int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
-{
- char *label_str = bstrdup0(NULL, label);
- struct af_instance *cur = af_find_by_label(s, label_str);
- talloc_free(label_str);
- if (cur) {
- return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
- } else {
- return CONTROL_UNKNOWN;
- }
-}
-
-int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
-{
- char *args[2] = {cmd, arg};
- if (strcmp(label, "all") == 0) {
- af_control_all(s, AF_CONTROL_COMMAND, args);
- return 0;
- } else {
- return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
- }
-}
-
-// Used by filters to add a filtered frame to the output queue.
-// Ownership of frame is transferred from caller to the filter chain.
-void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
-{
- if (frame) {
- assert(mp_audio_config_equals(&af->fmt_out, frame));
- MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
- }
-}
-
-static bool af_has_output_frame(struct af_instance *af)
-{
- if (!af->num_out_queued && af->filter_out) {
- if (af->filter_out(af) < 0)
- MP_ERR(af, "Error filtering frame.\n");
- }
- return af->num_out_queued > 0;
-}
-
-static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
-{
- struct mp_audio *res = NULL;
- if (af_has_output_frame(af)) {
- res = af->out_queued[0];
- MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
- }
- return res;
-}
-
-static void read_remaining(struct af_instance *af)
-{
- int num_frames;
- do {
- num_frames = af->num_out_queued;
- if (!af->filter_out || af->filter_out(af) < 0)
- break;
- } while (num_frames != af->num_out_queued);
-}
-
-static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
-{
- if (frame)
- assert(mp_audio_config_equals(&af->fmt_in, frame));
- int r = af->filter_frame(af, frame);
- if (r < 0)
- MP_ERR(af, "Error filtering frame.\n");
- return r;
-}
-
-// Input a frame into the filter chain. Ownership of frame is transferred.
-// Return >= 0 on success, < 0 on failure (even if output frames were produced)
-int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
-{
- assert(frame);
- if (s->initialized < 1) {
- talloc_free(frame);
- return -1;
- }
- return af_do_filter(s->first, frame);
-}
-
-// Output the next queued frame (if any) from the full filter chain.
-// The frame can be retrieved with af_read_output_frame().
-// eof: if set, assume there's no more input i.e. af_filter_frame() will
-// not be called (until reset) - flush all internally delayed frames
-// returns: -1: error, 0: no output, 1: output available
-int af_output_frame(struct af_stream *s, bool eof)
-{
- if (s->last->num_out_queued)
- return 1;
- if (s->initialized < 1)
- return -1;
- while (1) {
- struct af_instance *last = NULL;
- for (struct af_instance * cur = s->first; cur; cur = cur->next) {
- // Flush remaining frames on EOF, but do that only if the previous
- // filters have been flushed (i.e. they have no more output).
- if (eof && !last) {
- read_remaining(cur);
- int r = af_do_filter(cur, NULL);
- if (r < 0)
- return r;
- }
- if (af_has_output_frame(cur))
- last = cur;
- }
- if (!last)
- return 0;
- if (!last->next)
- return 1;
- int r = af_do_filter(last->next, af_dequeue_output_frame(last));
- if (r < 0)
- return r;
- }
-}
-
-struct mp_audio *af_read_output_frame(struct af_stream *s)
-{
- if (!s->last->num_out_queued)
- af_output_frame(s, false);
- return af_dequeue_output_frame(s->last);
-}
-
-void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
-{
- struct af_instance *af = s->last;
- MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
-}
-
-// Make sure the caller can change data referenced by the frame.
-// Return negative error code on failure (i.e. you can't write).
-int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
-{
- return mp_audio_pool_make_writeable(af->out_pool, frame);
-}
-
-void af_seek_reset(struct af_stream *s)
-{
- af_control_all(s, AF_CONTROL_RESET, NULL);
- af_chain_forget_frames(s);
-}
diff --git a/audio/filter/af.h b/audio/filter/af.h
deleted file mode 100644
index 3a07a5465f..0000000000
--- a/audio/filter/af.h
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MPLAYER_AF_H
-#define MPLAYER_AF_H
-
-#include <stdio.h>
-#include <stdbool.h>
-#include <sys/types.h>
-
-#include "config.h"
-#if !(HAVE_LIBAF && HAVE_GPL)
-#error "libaf/GPL disabled"
-#endif
-
-#include "options/options.h"
-#include "audio/format.h"
-#include "audio/chmap.h"
-#include "audio/audio.h"
-#include "common/msg.h"
-#include "common/common.h"
-
-struct af_instance;
-struct mpv_global;
-
-// Number of channels
-#define AF_NCH MP_NUM_CHANNELS
-
-// Flags for af->filter()
-#define AF_FILTER_FLAG_EOF 1
-
-/* Audio filter information not specific for current instance, but for
- a specific filter */
-struct af_info {
- const char *info;
- const char *name;
- int (*open)(struct af_instance *vf);
- int priv_size;
- const void *priv_defaults;
- const struct m_option *options;
- // For m_obj_desc.set_defaults
- void (*set_defaults)(struct mpv_global *global, void *p);
-};
-
-// Linked list of audio filters
-struct af_instance {
- const struct af_info *info;
- char *full_name;
- struct mp_log *log;
- struct MPOpts *opts;
- struct mpv_global *global;
- int (*control)(struct af_instance *af, int cmd, void *arg);
- void (*uninit)(struct af_instance *af);
- /* Feed a frame. The frame is NULL if EOF was reached, and the filter
- * should drain all remaining buffered data.
- * Use af_add_output_frame() to output data. The optional filter_out
- * callback can be set to produce output frames gradually.
- */
- int (*filter_frame)(struct af_instance *af, struct mp_audio *frame);
- int (*filter_out)(struct af_instance *af);
- void *priv;
- struct mp_audio *data; // configuration and buffer for outgoing data stream
-
- struct af_instance *next;
- struct af_instance *prev;
- double delay; /* Delay caused by the filter, in seconds of audio consumed
- * without corresponding output */
- bool auto_inserted; // inserted by af.c, such as conversion filters
- char *label;
-
- struct mp_audio fmt_in, fmt_out;
-
- struct mp_audio **out_queued;
- int num_out_queued;
-
- struct mp_audio_pool *out_pool;
-};
-
-// Current audio stream
-struct af_stream {
- int initialized; // 0: no, 1: yes, -1: attempted to, but failed
-
- // The first and last filter in the list
- struct af_instance *first;
- struct af_instance *last;
- // The user sets the input format (what the decoder outputs), and sets some
- // or all fields in output to the output format the AO accepts.
- struct mp_audio input;
- struct mp_audio output;
- struct mp_audio filter_output;
-
- struct mp_log *log;
- struct MPOpts *opts;
- struct mpv_global *global;
-};
-
-// Return values
-#define AF_DETACH (CONTROL_OK + 1)
-#define AF_OK CONTROL_OK
-#define AF_TRUE CONTROL_TRUE
-#define AF_FALSE CONTROL_FALSE
-#define AF_UNKNOWN CONTROL_UNKNOWN
-#define AF_ERROR CONTROL_ERROR
-
-// Parameters for af_control_*
-enum af_control {
- AF_CONTROL_REINIT = 1,
- AF_CONTROL_RESET,
- AF_CONTROL_SET_PLAYBACK_SPEED,
- AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
- AF_CONTROL_GET_METADATA,
- AF_CONTROL_COMMAND,
-};
-
-// Argument for AF_CONTROL_SET_PAN_LEVEL
-typedef struct af_control_ext_s {
- void* arg; // Argument
- int ch; // Chanel number
-} af_control_ext_t;
-
-struct af_stream *af_new(struct mpv_global *global);
-void af_destroy(struct af_stream *s);
-int af_init(struct af_stream *s);
-void af_uninit(struct af_stream *s);
-struct af_instance *af_add(struct af_stream *s, char *name, char *label,
- char **args);
-int af_remove_by_label(struct af_stream *s, char *label);
-struct af_instance *af_find_by_label(struct af_stream *s, char *label);
-struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
-void af_control_all(struct af_stream *s, int cmd, void *arg);
-int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label);
-void af_seek_reset(struct af_stream *s);
-int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg);
-
-void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
-int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
-int af_output_frame(struct af_stream *s, bool eof);
-struct mp_audio *af_read_output_frame(struct af_stream *s);
-void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame);
-int af_make_writeable(struct af_instance *af, struct mp_audio *frame);
-
-double af_calc_delay(struct af_stream *s);
-
-int af_test_output(struct af_instance *af, struct mp_audio *out);
-
-int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
-float af_softclip(float a);
-
-#endif /* MPLAYER_AF_H */
diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c
index c4af9b768b..3e1eef664c 100644
--- a/audio/filter/af_format.c
+++ b/audio/filter/af_format.c
@@ -15,18 +15,14 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdlib.h>
-
-#include <libavutil/common.h>
-
-#include "options/m_option.h"
-
+#include "audio/aframe.h"
#include "audio/format.h"
-#include "af.h"
-
-struct priv {
- struct m_config *config;
+#include "filters/f_autoconvert.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
+#include "options/m_option.h"
+struct f_opts {
int in_format;
int in_srate;
struct m_channels in_channels;
@@ -37,98 +33,109 @@ struct priv {
int fail;
};
-static void force_in_params(struct af_instance *af, struct mp_audio *in)
-{
- struct priv *priv = af->priv;
-
- if (priv->in_format != AF_FORMAT_UNKNOWN)
- mp_audio_set_format(in, priv->in_format);
-
- if (priv->in_channels.num_chmaps > 0)
- mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
-
- if (priv->in_srate)
- in->rate = priv->in_srate;
-}
+struct priv {
+ struct f_opts *opts;
+ struct mp_pin *in_pin;
+};
-static void force_out_params(struct af_instance *af, struct mp_audio *out)
+static void process(struct mp_filter *f)
{
- struct priv *priv = af->priv;
+ struct priv *p = f->priv;
- if (priv->out_format != AF_FORMAT_UNKNOWN)
- mp_audio_set_format(out, priv->out_format);
+ if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
+ return;
- if (priv->out_channels.num_chmaps > 0)
- mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
+ struct mp_frame frame = mp_pin_out_read(p->in_pin);
- if (priv->out_srate)
- out->rate = priv->out_srate;
-}
+ if (p->opts->fail) {
+ MP_ERR(f, "Failing on purpose.\n");
+ goto error;
+ }
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- struct priv *priv = af->priv;
+ if (frame.type == MP_FRAME_EOF) {
+ mp_pin_in_write(f->ppins[1], frame);
+ return;
+ }
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
- struct mp_audio *out = af->data;
+ if (frame.type != MP_FRAME_AUDIO) {
+ MP_ERR(f, "audio frame expected\n");
+ goto error;
+ }
- force_in_params(af, in);
- mp_audio_copy_config(out, in);
- force_out_params(af, out);
+ struct mp_aframe *in = frame.data;
- if (in->nch != out->nch || in->bps != out->bps) {
- MP_ERR(af, "Forced input/output formats are incompatible.\n");
- return AF_ERROR;
+ if (p->opts->out_channels.num_chmaps > 0) {
+ if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
+ MP_ERR(f, "could not force output channels\n");
+ goto error;
}
+ }
- if (priv->fail) {
- MP_ERR(af, "Failing on purpose.\n");
- return AF_ERROR;
- }
+ if (p->opts->out_srate)
+ mp_aframe_set_rate(in, p->opts->out_srate);
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- }
- return AF_UNKNOWN;
-}
+ mp_pin_in_write(f->ppins[1], frame);
+ return;
-static int filter(struct af_instance *af, struct mp_audio *data)
-{
- if (data)
- mp_audio_copy_config(data, af->data);
- af_add_output_frame(af, data);
- return 0;
+error:
+ mp_frame_unref(&frame);
+ mp_filter_internal_mark_failed(f);
}
-static int af_open(struct af_instance *af)
+static const struct mp_filter_info af_format_filter = {
+ .name = "format",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+};
+
+static struct mp_filter *af_format_create(struct mp_filter *parent,
+ void *options)
{
- af->control = control;
- af->filter_frame = filter;
+ struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
+ if (!f) {
+ talloc_free(options);
+ return NULL;
+ }
- force_in_params(af, af->data);
- force_out_params(af, af->data);
+ struct priv *p = f->priv;
+ p->opts = talloc_steal(p, options);
- return AF_OK;
-}
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
-#define OPT_BASE_STRUCT struct priv
+ struct mp_autoconvert *conv = mp_autoconvert_create(f);
+ if (!conv)
+ abort();
-const struct af_info af_info_format = {
- .info = "Force audio format",
- .name = "format",
- .open = af_open,
- .priv_size = sizeof(struct priv),
- .options = (const struct m_option[]) {
- OPT_AUDIOFORMAT("format", in_format, 0),
- OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
- OPT_CHANNELS("channels", in_channels, 0, .min = 1),
- OPT_AUDIOFORMAT("out-format", out_format, 0),
- OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
- OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
- OPT_FLAG("fail", fail, 0),
- {0}
+ if (p->opts->in_format)
+ mp_autoconvert_add_afmt(conv, p->opts->in_format);
+ if (p->opts->in_srate)
+ mp_autoconvert_add_srate(conv, p->opts->in_srate);
+ if (p->opts->in_channels.num_chmaps > 0)
+ mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
+
+ mp_pin_connect(conv->f->pins[0], f->ppins[0]);
+ p->in_pin = conv->f->pins[1];
+
+ return f;
+}
+
+#define OPT_BASE_STRUCT struct f_opts
+
+const struct mp_user_filter_entry af_format = {
+ .desc = {
+ .name = "format",
+ .description = "Force audio format",
+ .priv_size = sizeof(struct f_opts),
+ .options = (const struct m_option[]) {
+ OPT_AUDIOFORMAT("format", in_format, 0),
+ OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
+ OPT_CHANNELS("channels", in_channels, 0, .min = 1),
+ OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
+ OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
+ OPT_FLAG("fail", fail, 0),
+ {0}
+ },
},
+ .create = af_format_create,
};
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index 14aa53b980..c7582cf52b 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -31,14 +31,17 @@
#include <libavutil/bswap.h>
#include <libavutil/mem.h>
-#include "config.h"
-
-#include "common/av_common.h"
-#include "common/common.h"
-#include "af.h"
-#include "audio/audio_buffer.h"
+#include "audio/aframe.h"
#include "audio/chmap_sel.h"
#include "audio/fmt-conversion.h"
+#include "audio/format.h"
+#include "common/av_common.h"
+#include "common/common.h"
+#include "filters/f_autoconvert.h"
+#include "filters/f_utils.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
+#include "options/m_option.h"
#define AC3_MAX_CHANNELS 6
@@ -49,173 +52,89 @@ const uint16_t ac3_bitrate_tab[19] = {
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
};
-// Data for specific instances of this filter
-typedef struct af_ac3enc_s {
+struct f_opts {
+ int add_iec61937_header;
+ int bit_rate;
+ int min_channel_num;
+ char *encoder;
+ char **avopts;
+};
+
+struct priv {
+ struct f_opts *opts;
+
+ struct mp_pin *in_pin;
+ struct mp_aframe *cur_format;
+ struct mp_aframe *in_frame;
+ struct mp_aframe_pool *out_pool;
+
struct AVCodec *lavc_acodec;
struct AVCodecContext *lavc_actx;
int bit_rate;
- struct mp_audio *input; // frame passed to libavcodec
- struct mp_audio *pending; // unconsumed input data
- int in_samples; // samples of input per AC3 frame
int out_samples; // upper bound on encoded output per AC3 frame
- int64_t encoder_buffered;
-
- int cfg_add_iec61937_header;
- int cfg_bit_rate;
- int cfg_min_channel_num;
- char *cfg_encoder;
- char **cfg_avopts;
-} af_ac3enc_t;
-
-// fmt carries the input format. Change it to the best next-possible format
-// the encoder likely accepts.
-static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
-{
- int formats[AF_FORMAT_COUNT];
- af_get_best_sample_formats(fmt->format, formats);
-
- for (int n = 0; formats[n]; n++) {
- const enum AVSampleFormat *lf = c->codec->sample_fmts;
- for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
- int mpfmt = af_from_avformat(lf[i]);
- if (mpfmt && mpfmt == formats[n]) {
- mp_audio_set_format(fmt, mpfmt);
- goto done_fmt;
- }
- }
- }
-done_fmt: ;
+};
- int rate =
- af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
- if (rate > 0)
- fmt->rate = rate;
+static bool reinit(struct mp_filter *f)
+{
+ struct priv *s = f->priv;
- struct mp_chmap_sel sel = {0};
- const uint64_t *lch = c->codec->channel_layouts;
- for (int n = 0; lch && lch[n]; n++) {
- struct mp_chmap chmap = {0};
- mp_chmap_from_lavc(&chmap, lch[n]);
- mp_chmap_sel_add_map(&sel, &chmap);
- }
- struct mp_chmap res = fmt->channels;
- mp_chmap_sel_adjust(&sel, &res);
- if (!mp_chmap_is_empty(&res))
- mp_audio_set_channels(fmt, &res);
-}
+ mp_aframe_reset(s->cur_format);
-// Initialization and runtime control
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- af_ac3enc_t *s = af->priv;
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
{0, 96000, 192000, 256000, 384000, 448000, 448000};
- switch (cmd){
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
-
- if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
- return AF_DETACH;
-
- // At least currently, the AC3 encoder doesn't export sample rates.
- in->rate = 48000;
- select_encode_format(s->lavc_actx, in);
-
- af->data->rate = in->rate;
- mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
- mp_audio_set_num_channels(af->data, 2);
-
- if (!mp_audio_config_equals(in, &orig_in))
- return AF_FALSE;
-
- if (s->cfg_add_iec61937_header) {
- s->out_samples = AC3_FRAME_SIZE;
- } else {
- s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
- }
-
- mp_audio_copy_config(s->input, in);
-
- talloc_free(s->pending);
- s->pending = NULL;
-
- MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
+ if (s->opts->add_iec61937_header) {
+ s->out_samples = AC3_FRAME_SIZE;
+ } else {
+ s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
+ mp_aframe_get_sstride(s->in_frame);
+ }
- int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
+ int format = mp_aframe_get_format(s->in_frame);
+ int rate = mp_aframe_get_rate(s->in_frame);
+ struct mp_chmap chmap = {0};
+ mp_aframe_get_chmap(s->in_frame, &chmap);
- if (s->lavc_actx->channels != in->nch ||
- s->lavc_actx->sample_rate != in->rate ||
- s->lavc_actx->bit_rate != bit_rate)
- {
- avcodec_close(s->lavc_actx);
+ int bit_rate = s->bit_rate;
+ if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
+ bit_rate = default_bit_rate[chmap.num];
- // Put sample parameters
- s->lavc_actx->sample_fmt = af_to_avformat(in->format);
- s->lavc_actx->channels = in->nch;
- s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
- s->lavc_actx->sample_rate = in->rate;
- s->lavc_actx->bit_rate = bit_rate;
+ avcodec_close(s->lavc_actx);
- if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
- MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
- return AF_ERROR;
- }
+ // Put sample parameters
+ s->lavc_actx->sample_fmt = af_to_avformat(format);
+ s->lavc_actx->channels = chmap.num;
+ s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
+ s->lavc_actx->sample_rate = rate;
+ s->lavc_actx->bit_rate = bit_rate;
- if (s->lavc_actx->frame_size < 1) {
- MP_ERR(af, "encoder didn't specify input frame size\n");
- return AF_ERROR;
- }
- }
- s->in_samples = s->lavc_actx->frame_size;
- mp_audio_realloc(s->input, s->in_samples);
- s->input->samples = 0;
- s->encoder_buffered = 0;
- return AF_OK;
+ if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
+ MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
+ return false;
}
- case AF_CONTROL_RESET:
- if (avcodec_is_open(s->lavc_actx))
- avcodec_flush_buffers(s->lavc_actx);
- talloc_free(s->pending);
- s->pending = NULL;
- s->input->samples = 0;
- s->encoder_buffered = 0;
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-// Deallocate memory
-static void uninit(struct af_instance* af)
-{
- af_ac3enc_t *s = af->priv;
- if (s) {
- avcodec_free_context(&s->lavc_actx);
- talloc_free(s->pending);
+ if (s->lavc_actx->frame_size < 1) {
+ MP_ERR(f, "encoder didn't specify input frame size\n");
+ return false;
}
+
+ mp_aframe_config_copy(s->cur_format, s->in_frame);
+ return true;
}
-static void update_delay(struct af_instance *af)
+static void reset(struct mp_filter *f)
{
- af_ac3enc_t *s = af->priv;
- af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
- s->encoder_buffered) / (double)s->input->rate;
+ struct priv *s = f->priv;
+
+ TA_FREEP(&s->in_frame);
}
-static int filter_frame(struct af_instance *af, struct mp_audio *audio)
+static void destroy(struct mp_filter *f)
{
- af_ac3enc_t *s = af->priv;
+ struct priv *s = f->priv;
- // filter_output must have been called until no output was produced.
- if (s->pending && s->pending->samples)
- MP_ERR(af, "broken data flow\n");
-
- talloc_free(s->pending);
- s->pending = audio;
- update_delay(af);
- return 0;
+ reset(f);
+ avcodec_free_context(&s->lavc_actx);
}
static void swap_16(uint16_t *ptr, size_t size)
@@ -224,105 +143,84 @@ static void swap_16(uint16_t *ptr, size_t size)
ptr[n] = av_bswap16(ptr[n]);
}
-// Copy data from input frame to encode frame (because libavcodec wants a full
-// AC3 frame for encoding, while filter input frames can be smaller or larger).
-// Return true if the frame is complete.
-static bool fill_buffer(struct af_instance *af)
-{
- af_ac3enc_t *s = af->priv;
-
- af->delay = 0;
-
- if (s->pending) {
- if (!mp_audio_is_writeable(s->input))
- assert(s->input->samples == 0); // we can't have sent a partial frame
- mp_audio_realloc_min(s->input, s->in_samples);
- int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
- s->input->samples += copy;
- mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
- mp_audio_skip_samples(s->pending, copy);
- }
- update_delay(af);
- return s->input->samples >= s->in_samples;
-}
-
-// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
-// To actually advance the read pointer, set s->input->samples=0 afterwards.
-static int read_input_frame(struct af_instance *af, AVFrame *frame)
+static void process(struct mp_filter *f)
{
- af_ac3enc_t *s = af->priv;
- if (!fill_buffer(af))
- return 0; // need more input
+ struct priv *s = f->priv;
- if (mp_audio_to_avframe(s->input, frame) < 0)
- return -1;
-
- return 1;
-}
-
-static int filter_out(struct af_instance *af)
-{
- af_ac3enc_t *s = af->priv;
-
- if (!s->pending)
- return 0;
-
- AVFrame *frame = av_frame_alloc();
- if (!frame) {
- MP_FATAL(af, "Could not allocate memory \n");
- return -1;
- }
- int err = -1;
+ if (!mp_pin_in_needs_data(f->ppins[1]))
+ return;
+ bool err = true;
+ struct mp_aframe *out = NULL;
AVPacket pkt = {0};
av_init_packet(&pkt);
// Send input as long as it wants.
while (1) {
- err = read_input_frame(af, frame);
- if (err < 0)
+ if (avcodec_is_open(s->lavc_actx)) {
+ int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
+ if (lavc_ret >= 0)
+ break;
+ if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
+ MP_FATAL(f, "Encode failed (receive).\n");
+ goto done;
+ }
+ }
+ AVFrame *frame = NULL;
+ struct mp_frame input = mp_pin_out_read(s->in_pin);
+ // The following code assumes no sample data buffering in the encoder.
+ if (input.type == MP_FRAME_EOF) {
+ mp_pin_in_write(f->ppins[1], input);
+ return;
+ } else if (input.type == MP_FRAME_AUDIO) {
+ TA_FREEP(&s->in_frame);
+ s->in_frame = input.data;
+ frame = mp_frame_to_av(input, NULL);
+ if (!frame)
+ goto done;
+ if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
+ // Just pass it through.
+ s->in_frame = NULL;
+ mp_pin_in_write(f->ppins[1], input);
+ return;
+ }
+ if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
+ if (!reinit(f))
+ goto done;
+ }
+ } else if (input.type) {
goto done;
- if (err == 0)
- break;
- err = -1;
+ } else {
+ return; // no data yet
+ }
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
- // On EAGAIN, we're supposed to read remaining output.
- if (lavc_ret == AVERROR(EAGAIN))
- break;
- if (lavc_ret < 0) {
- MP_FATAL(af, "Encode failed.\n");
+ av_frame_free(&frame);
+ if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
+ MP_FATAL(f, "Encode failed (send).\n");
goto done;
}
- s->encoder_buffered += s->input->samples;
- s->input->samples = 0;
- }
- int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
- if (lavc_ret == AVERROR(EAGAIN)) {
- // Need to buffer more input.
- err = 0;
- goto done;
- }
- if (lavc_ret < 0) {
- MP_FATAL(af, "Encode failed.\n");
- goto done;
}
- MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
- pkt.size, s->pending->samples + s->input->samples);
+ if (!s->in_frame)
+ goto done;
- s->encoder_buffered -= AC3_FRAME_SIZE;
+ out = mp_aframe_create();
+ mp_aframe_set_format(out, AF_FORMAT_S_AC3);
+ mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
+ mp_aframe_set_rate(out, 48000);
- struct mp_audio *out =
- mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
- if (!out)
+ if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
goto done;
- mp_audio_copy_attributes(out, s->pending);
+
+ int sstride = mp_aframe_get_sstride(out);
+
+ mp_aframe_copy_attributes(out, s->in_frame);
int frame_size = pkt.size;
int header_len = 0;
char hdr[8];
- if (s->cfg_add_iec61937_header && pkt.size > 5) {
+ if (s->opts->add_iec61937_header && pkt.size > 5) {
int bsmod = pkt.data[5] & 0x7;
int len = frame_size;
@@ -336,48 +234,69 @@ static int filter_out(struct af_instance *af)
AV_WL16(hdr + 6, len << 3); // number of bits in payload
}
- if (frame_size > out->samples * out->sstride)
+ if (frame_size > s->out_samples * sstride)
abort();
- char *buf = (char *)out->planes[0];
+ uint8_t **planes = mp_aframe_get_data_rw(out);
+ if (!planes)
+ goto done;
+ char *buf = planes[0];
memcpy(buf, hdr, header_len);
memcpy(buf + header_len, pkt.data, pkt.size);
memset(buf + header_len + pkt.size, 0,
frame_size - (header_len + pkt.size));
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
- out->samples = frame_size / out->sstride;
- af_add_output_frame(af, out);
+ mp_aframe_set_size(out, frame_size / sstride);
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
+ out = NULL;
err = 0;
done:
av_packet_unref(&pkt);
- av_frame_free(&frame);
- update_delay(af);
- return err;
+ talloc_free(out);
+ if (err)
+ mp_filter_internal_mark_failed(f);
}
-static int af_open(struct af_instance* af){
+static const struct mp_filter_info af_lavcac3enc_filter = {
+ .name = "lavcac3enc",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .reset = reset,
+ .destroy = destroy,
+};
- af_ac3enc_t *s = af->priv;
- af->control=control;
- af->uninit=uninit;
- af->filter_frame = filter_frame;
- af->filter_out = filter_out;
+static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
+ void *options)
+{
+ struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_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");
- s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
+ struct priv *s = f->priv;
+ s->opts = talloc_steal(s, options);
+ s->cur_format = talloc_steal(s, mp_aframe_create());
+ s->out_pool = mp_aframe_pool_create(s);
+
+ s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
if (!s->lavc_acodec) {
- MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
- return AF_ERROR;
+ MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
+ goto error;
}
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
if (!s->lavc_actx) {
- MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
- return AF_ERROR;
+ MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
+ goto error;
}
- if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
- return AF_ERROR;
+ if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
+ goto error;
// For this one, we require the decoder to expert lists of all supported
// parameters. (Not all decoders do that, but the ones we're interested
@@ -385,50 +304,85 @@ static int af_open(struct af_instance* af){
if (!s->lavc_acodec->sample_fmts ||
!s->lavc_acodec->channel_layouts)
{
- MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
- return AF_ERROR;
+ MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
+ goto error;
}
- s->input = talloc_zero(s, struct mp_audio);
-
- if (s->cfg_bit_rate) {
+ if (s->opts->bit_rate) {
int i;
for (i = 0; i < 19; i++) {
- if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
+ if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
s->bit_rate = ac3_bitrate_tab[i] * 1000;
break;
}
}
if (i >= 19) {
- MP_WARN(af, "unable set unsupported bitrate %d, using default "
+ MP_WARN(f, "unable set unsupported bitrate %d, using default "
"bitrate (check manpage to see supported bitrates).\n",
- s->cfg_bit_rate);
+ s->opts->bit_rate);
}
}
- return AF_OK;
-}
+ struct mp_autoconvert *conv = mp_autoconvert_create(f);
+ if (!conv)
+ abort();
-#define OPT_BASE_STRUCT struct af_ac3enc_s
+ const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
+ for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
+ int mpfmt = af_from_avformat(lf[i]);
+ if (mpfmt)
+ mp_autoconvert_add_afmt(conv, mpfmt);
+ }
-const struct af_info af_info_lavcac3enc = {
- .info = "runtime encode to ac3 using libavcodec",
- .name = "lavcac3enc",
- .open = af_open,
- .priv_size = sizeof(struct af_ac3enc_s),
- .priv_defaults = &(const struct af_ac3enc_s){
- .cfg_add_iec61937_header = 1,
- .cfg_bit_rate = 640,
- .cfg_min_channel_num = 3,
- .cfg_encoder = "ac3",
- },
- .options = (const struct m_option[]) {
- OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
- OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
- ({"auto", 0}, {"default", 0})),
- OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
- OPT_STRING("encoder", cfg_encoder, 0),
- OPT_KEYVALUELIST("o", cfg_avopts, 0),
- {0}
+ const uint64_t *lch = s->lavc_acodec->channel_layouts;
+ for (int n = 0; lch && lch[n]; n++) {
+ struct mp_chmap chmap = {0};
+ mp_chmap_from_lavc(&chmap, lch[n]);
+ if (mp_chmap_is_valid(&chmap))
+ mp_autoconvert_add_chmap(conv, &chmap);
+ }
+
+ // At least currently, the AC3 encoder doesn't export sample rates.
+ mp_autoconvert_add_srate(conv, 48000);
+
+ mp_pin_connect(conv->f->pins[0], f->ppins[0]);
+
+ struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
+ if (!fs)
+ abort();
+
+ mp_pin_connect(fs->pins[0], conv->f->pins[1]);
+ s->in_pin = fs->pins[1];
+
+ return f;
+
+error:
+ talloc_free(f);
+ return NULL;
+}
+
+#define OPT_BASE_STRUCT struct f_opts
+
+const struct mp_user_filter_entry af_lavcac3enc = {
+ .desc = {
+ .description = "runtime encode to ac3 using libavcodec",
+ .name = "lavcac3enc",
+ .priv_size = sizeof(OPT_BASE_STRUCT),
+ .priv_defaults = &(const OPT_BASE_STRUCT) {
+ .add_iec61937_header = 1,
+ .bit_rate = 640,
+ .min_channel_num = 3,
+ .encoder = "ac3",
+ },
+ .options = (const struct m_option[]) {
+ OPT_FLAG("tospdif", add_iec61937_header, 0),
+ OPT_CHOICE_OR_INT("bitrate", bit_rate, 0, 32, 640,
+ ({"auto", 0}, {"default", 0})),
+ OPT_INTRANGE("minch", min_channel_num, 0, 2, 6),
+ OPT_STRING("encoder", encoder, 0),
+ OPT_KEYVALUELIST("o", avopts, 0),
+ {0}
+ },
},
+ .create = af_lavcac3enc_create,
};
diff --git a/audio/filter/af_lavfi.c b/audio/filter/af_lavfi.c
deleted file mode 100644
index ab8a026de7..0000000000
--- a/audio/filter/af_lavfi.c
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * Filter graph creation code taken from FFmpeg ffplay.c (LGPL 2.1 or later)
- *
- * 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 <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <assert.h>
-
-#include <libavutil/avstring.h>
-#include <libavutil/mem.h>
-#include <libavutil/mathematics.h>
-#include <libavutil/rational.h>
-#include <libavutil/samplefmt.h>
-#include <libavutil/time.h>
-#include <libavutil/opt.h>
-#include <libavfilter/avfilter.h>
-#include <libavfilter/buffersink.h>
-#include <libavfilter/buffersrc.h>
-
-#include "config.h"
-
-#include "audio/format.h"
-#include "audio/fmt-conversion.h"
-#include "af.h"
-
-#include "common/av_common.h"
-#include "common/tags.h"
-
-#include "options/m_option.h"
-
-// FFmpeg and Libav have slightly different APIs, just enough to cause us
-// unnecessary pain. <Expletive deleted.>
-#if LIBAVFILTER_VERSION_MICRO < 100
-#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
- avfilter_graph_parse(graph, filters, inputs, outputs, log_ctx)
-#define avfilter_graph_send_command(a, b, c, d, e, f, g) -1
-#else
-#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
- avfilter_graph_parse_ptr(graph, filters, &(inputs), &(outputs), log_ctx)
-#endif
-
-struct priv {
- // Single filter bridge, instead of a graph.
- bool is_bridge;
-
- AVFilterGraph *graph;
- AVFilterContext *in;
- AVFilterContext *out;
-
- int64_t samples_in;
-
- AVRational timebase_out;
-
- bool eof;
-
- struct mp_tags *metadata;
-
- // options
- char *cfg_graph;
- char **cfg_avopts;
- char *cfg_filter_name;
- char **cfg_filter_opts;
-};
-
-static void destroy_graph(struct af_instance *af)
-{
- struct priv *p = af->priv;
- avfilter_graph_free(&p->graph);
- p->in = p->out = NULL;
- p->samples_in = 0;
- p->eof = false;
-}
-
-static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
-{
- void *tmp = talloc_new(NULL);
- struct priv *p = af->priv;
- AVFilterContext *in = NULL, *out = NULL;
- bool ok = false;
-
- if (!p->is_bridge && bstr0(p->cfg_graph).len == 0) {
- MP_FATAL(af, "lavfi: no filter graph set\n");
- return false;
- }
-
- destroy_graph(af);
-
- AVFilterGraph *graph = avfilter_graph_alloc();
- if (!graph)
- goto error;
-
- if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
- goto error;
-
- AVFilterInOut *outputs = avfilter_inout_alloc();
- AVFilterInOut *inputs = avfilter_inout_alloc();
- if (!outputs || !inputs)
- goto error;
-
- char *src_args = talloc_asprintf(tmp,
- "sample_rate=%d:sample_fmt=%s:time_base=%d/%d:"
- "channel_layout=0x%"PRIx64, config->rate,
- av_get_sample_fmt_name(af_to_avformat(config->format)),
- 1, config->rate, mp_chmap_to_lavc(&config->channels));
-
- if (avfilter_graph_create_filter(&in, avfilter_get_by_name("abuffer"),
- "src", src_args, NULL, graph) < 0)
- goto error;
-
- if (avfilter_graph_create_filter(&out, avfilter_get_by_name("abuffersink"),
- "out", NULL, NULL, graph) < 0)
- goto error;
-
- if (p->is_bridge) {
- AVFilterContext *filter = avfilter_graph_alloc_filter(graph,
- avfilter_get_by_name(p->cfg_filter_name), "filter");
- if (!filter)
- goto error;
-
- if (mp_set_avopts(af->log, filter->priv, p->cfg_filter_opts) < 0)
- goto error;
-
- if (avfilter_init_str(filter, NULL) < 0)
- goto error;
-
- // Yep, we have to manually link those filters.
- if (filter->nb_inputs != 1 ||
- avfilter_pad_get_type(filter->input_pads, 0) != AVMEDIA_TYPE_AUDIO ||
- filter->nb_outputs != 1 ||
- avfilter_pad_get_type(filter->output_pads, 0) != AVMEDIA_TYPE_AUDIO)
- {
- MP_ERR(af, "The filter is required to have 1 audio input pad and "
- "1 audio output pad.\n");
- goto error;
- }
- if (avfilter_link(in, 0, filter, 0) < 0 ||
- avfilter_link(filter, 0, out, 0) < 0)
- {
- MP_ERR(af, "Failed to link filter.\n");
- goto error;
- }
- } else {
- MP_VERBOSE(af, "lavfi: create graph: '%s'\n", p->cfg_graph);
-
- outputs->name = av_strdup("in");
- outputs->filter_ctx = in;
-
- inputs->name = av_strdup("out");
- inputs->filter_ctx = out;
-
- if (graph_parse(graph, p->cfg_graph, inputs, outputs, NULL) < 0)
- goto error;
- }
-
- if (avfilter_graph_config(graph, NULL) < 0)
- goto error;
-
- p->in = in;
- p->out = out;
- p->graph = graph;
-
- assert(out->nb_inputs == 1);
- assert(in->nb_outputs == 1);
-
- ok = true;
-error:
-
- if (!ok) {
- MP_FATAL(af, "Can't configure libavfilter graph.\n");
- avfilter_graph_free(&graph);
- }
- avfilter_inout_free(&inputs);
- avfilter_inout_free(&outputs);
- talloc_free(tmp);
- return ok;
-}
-
-static void reset(struct af_instance *af)
-{
- if (!recreate_graph(af, &af->fmt_in))
- MP_FATAL(af, "Can't recreate libavfilter filter after a seek reset.\n");
-}
-
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- struct priv *p = af->priv;
-
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
- struct mp_audio *out = af->data;
-
- if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
- mp_audio_set_format(in, AF_FORMAT_FLOAT);
-
- // Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
- if (in->channels.num > AV_NUM_DATA_POINTERS)
- return AF_ERROR;
-
- if (!mp_chmap_is_lavc(&in->channels))
- mp_chmap_reorder_to_lavc(&in->channels); // will always work
-
- if (!recreate_graph(af, in))
- return AF_ERROR;
-
- AVFilterLink *l_out = p->out->inputs[0];
-
- out->rate = l_out->sample_rate;
-
- mp_audio_set_format(out, af_from_avformat(l_out->format));
-
- struct mp_chmap out_cm;
- mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
- mp_audio_set_channels(out, &out_cm);
-
- if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
- return AF_ERROR;
-
- p->timebase_out = l_out->time_base;
-
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- case AF_CONTROL_COMMAND: {
- if (!p->graph)
- break;
- char **args = arg;
- return avfilter_graph_send_command(p->graph, "all",
- args[0], args[1], &(char){0}, 0, 0)
- >= 0 ? CONTROL_OK : CONTROL_ERROR;
- }
- case AF_CONTROL_GET_METADATA:
- if (p->metadata) {
- *(struct mp_tags *)arg = *p->metadata;
- return CONTROL_OK;
- }
- return CONTROL_NA;
- case AF_CONTROL_RESET:
- reset(af);
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
-{
-#if LIBAVUTIL_VERSION_MICRO >= 100
- struct priv *p = af->priv;
- if (!p->metadata)
- p->metadata = talloc_zero(p, struct mp_tags);
-
- mp_tags_copy_from_av_dictionary(p->metadata, frame->metadata);
-#endif
-}
-
-static int filter_frame(struct af_instance *af, struct mp_audio *data)
-{
- struct priv *p = af->priv;
- AVFrame *frame = NULL;
-
- if (p->eof && data)
- reset(af);
-
- if (!p->graph)
- goto error;
-
- if (!data) {
- if (p->eof)
- return 0;
- p->eof = true;
- }
-
- if (data) {
- frame = mp_audio_to_avframe_and_unref(data);
- data = NULL;
- if (!frame)
- goto error;
-
- // Timebase is 1/sample_rate
- frame->pts = p->samples_in;
- p->samples_in += frame->nb_samples;
- }
-
- if (av_buffersrc_add_frame(p->in, frame) < 0)
- goto error;
-
- av_frame_free(&frame);
- talloc_free(data);
- return 0;
-error:
- av_frame_free(&frame);
- talloc_free(data);
- return -1;
-}
-
-static int filter_out(struct af_instance *af)
-{
- struct priv *p = af->priv;
-
- if (!p->graph)
- goto error;
-
- AVFrame *frame = av_frame_alloc();
- if (!frame)
- goto error;
-
- int err = av_buffersink_get_frame(p->out, frame);
- if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
- // Not an error situation - no more output buffers in queue.
- // AVERROR_EOF means we shouldn't even give the filter more
- // input, but we don't handle that completely correctly.
- av_frame_free(&frame);
- p->eof |= err == AVERROR_EOF;
- return 0;
- }
-
- struct mp_audio *out = mp_audio_from_avframe(frame);
- if (!out)
- goto error;
-
- mp_audio_copy_config(out, af->data);
-
- if (frame->pts != AV_NOPTS_VALUE) {
- double in_time = p->samples_in / (double)af->fmt_in.rate;
- double out_time = frame->pts * av_q2d(p->timebase_out);
- // Need pts past the last output sample.
- out_time += out->samples / (double)out->rate;
-
- af->delay = in_time - out_time;
- }
-
- get_metadata_from_av_frame(af, frame);
- af_add_output_frame(af, out);
- av_frame_free(&frame);
- return 0;
-error:
- av_frame_free(&frame);
- return -1;
-}
-
-static void uninit(struct af_instance *af)
-{
- destroy_graph(af);
-}
-
-static int af_open(struct af_instance *af)
-{
- struct priv *p = af->priv;
-
- af->control = control;
- af->uninit = uninit;
- af->filter_frame = filter_frame;
- af->filter_out = filter_out;
-
- if (p->is_bridge) {
- if (!p->cfg_filter_name) {
- MP_ERR(af, "Filter name not set!\n");
- return 0;
- }
- if (!avfilter_get_by_name(p->cfg_filter_name)) {
- MP_ERR(af, "libavfilter filter '%s' not found!\n", p->cfg_filter_name);
- return 0;
- }
- }
- return AF_OK;
-}
-
-#define OPT_BASE_STRUCT struct priv
-
-const struct af_info af_info_lavfi = {
- .info = "libavfilter bridge",
- .name = "lavfi",
- .open = af_open,
- .priv_size = sizeof(struct priv),
- .options = (const struct m_option[]) {
- OPT_STRING("graph", cfg_graph, 0),
- OPT_KEYVALUELIST("o", cfg_avopts, 0),
- {0}
- },
-};
-
-const struct af_info af_info_lavfi_bridge = {
- .info = "libavfilter bridge (explicit options)",
- .name = "lavfi-bridge",
- .open = af_open,
- .priv_size = sizeof(struct priv),
- .priv_defaults = &(const struct priv){
- .is_bridge = true,
- },
- .options = (const struct m_option[]) {
- OPT_STRING("name", cfg_filter_name, M_OPT_MIN, .min = 1),
- OPT_KEYVALUELIST("opts", cfg_filter_opts, 0),
- OPT_STRING("graph", cfg_graph, 0),
- OPT_KEYVALUELIST("o", cfg_avopts, 0),
- {0}
- },
-};
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index f13093da40..baa78acb6e 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -32,132 +32,19 @@
#include "common/av_common.h"
#include "common/msg.h"
+#include "filters/f_swresample.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
#include "options/m_config.h"
#include "options/m_option.h"
-#include "audio/filter/af.h"
-#include "audio/fmt-conversion.h"
-#include "osdep/endian.h"
-#include "audio/aconverter.h"
+#include "options/options.h"
struct af_resample {
int allow_detach;
- double playback_speed;
struct mp_resample_opts opts;
int global_normalize;
- struct mp_aconverter *converter;
- int deprecation_warning;
};
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- struct af_resample *s = af->priv;
-
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio *out = af->data;
- struct mp_audio orig_in = *in;
-
- if (((out->rate == in->rate) || (out->rate == 0)) &&
- (out->format == in->format) &&
- (mp_chmap_equals(&out->channels, &in->channels) || out->nch == 0) &&
- s->allow_detach && s->playback_speed == 1.0)
- return AF_DETACH;
-
- if (out->rate == 0)
- out->rate = in->rate;
-
- if (mp_chmap_is_empty(&out->channels))
- mp_audio_set_channels(out, &in->channels);
-
- if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
- mp_audio_set_format(in, AF_FORMAT_FLOAT);
- if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
- mp_audio_set_format(out, in->format);
-
- int r = ((in->format == orig_in.format) &&
- mp_chmap_equals(&in->channels, &orig_in.channels))
- ? AF_OK : AF_FALSE;
-
- if (r == AF_OK) {
- if (!mp_aconverter_reconfig(s->converter,
- in->rate, in->format, in->channels,
- out->rate, out->format, out->channels))
- r = AF_ERROR;
- }
- return r;
- }
- case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
- s->playback_speed = *(double *)arg;
- return AF_OK;
- }
- case AF_CONTROL_RESET:
- mp_aconverter_flush(s->converter);
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-static void uninit(struct af_instance *af)
-{
- struct af_resample *s = af->priv;
-
- talloc_free(s->converter);
-}
-
-static int filter(struct af_instance *af, struct mp_audio *in)
-{
- struct af_resample *s = af->priv;
-
- mp_aconverter_set_speed(s->converter, s->playback_speed);
-
- af->filter_out(af);
-
- struct mp_aframe *aframe = mp_audio_to_aframe(in);
- if (!aframe && in)
- return -1;
- talloc_free(in);
- bool ok = mp_aconverter_write_input(s->converter, aframe);
- if (!ok)
- talloc_free(aframe);
-
- return ok ? 0 : -1;
-}
-
-static int filter_out(struct af_instance *af)
-{
- struct af_resample *s = af->priv;
- bool eof;
- struct mp_aframe *out = mp_aconverter_read_output(s->converter, &eof);
- if (out)
- af_add_output_frame(af, mp_audio_from_aframe(out));
- talloc_free(out);
- af->delay = mp_aconverter_get_latency(s->converter);
- return 0;
-}
-
-static int af_open(struct af_instance *af)
-{
- struct af_resample *s = af->priv;
-
- af->control = control;
- af->uninit = uninit;
- af->filter_frame = filter;
- af->filter_out = filter_out;
-
- if (s->opts.normalize < 0)
- s->opts.normalize = s->global_normalize;
-
- s->converter = mp_aconverter_create(af->global, af->log, &s->opts);
-
- if (s->deprecation_warning) {
- MP_WARN(af, "This filter is deprecated! Use the --audio-resample- options"
- " to customize resampling, or the --af=aresample filter.\n");
- }
-
- return AF_OK;
-}
-
static void set_defaults(struct mpv_global *global, void *p)
{
struct af_resample *s = p;
@@ -165,7 +52,7 @@ static void set_defaults(struct mpv_global *global, void *p)
struct mp_resample_opts *opts = &s->opts;
struct mp_resample_opts *src_opts =
- mp_get_config_group(s, global, &resample_config);
+ mp_get_config_group(s, global, &resample_conf);
s->global_normalize = src_opts->normalize;
@@ -180,28 +67,46 @@ static void set_defaults(struct mpv_global *global, void *p)
#define OPT_BASE_STRUCT struct af_resample
-const struct af_info af_info_lavrresample = {
- .info = "Sample frequency conversion using libavresample",
- .name = "lavrresample",
- .open = af_open,
- .priv_size = sizeof(struct af_resample),
- .priv_defaults = &(const struct af_resample) {
- .opts = MP_RESAMPLE_OPTS_DEF,
- .playback_speed = 1.0,
- .allow_detach = 1,
- .deprecation_warning = 1,
- },
- .options = (const struct m_option[]) {
- OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
- OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
- OPT_FLAG("linear", opts.linear, 0),
- OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
- OPT_FLAG("detach", allow_detach, 0),
- OPT_CHOICE("normalize", opts.normalize, 0,
- ({"no", 0}, {"yes", 1}, {"auto", -1})),
- OPT_KEYVALUELIST("o", opts.avopts, 0),
- OPT_FLAG("deprecation-warning", deprecation_warning, 0),
- {0}
+static struct mp_filter *af_lavrresample_create(struct mp_filter *parent,
+ void *options)
+{
+ struct af_resample *s = options;
+
+ if (s->opts.normalize < 0)
+ s->opts.normalize = s->global_normalize;
+
+ struct mp_swresample *swr = mp_swresample_create(parent, &s->opts);
+ if (!swr)
+ abort();
+
+ MP_WARN(swr->f, "This filter is deprecated! Use the --audio-resample- options"
+ " to customize resampling, or the --af=aresample filter.\n");
+
+ talloc_free(s);
+ return swr->f;
+}
+
+const struct mp_user_filter_entry af_lavrresample = {
+ .desc = {
+ .description = "Sample frequency conversion using libavresample",
+ .name = "lavrresample",
+ .priv_size = sizeof(struct af_resample),
+ .priv_defaults = &(const struct af_resample) {
+ .opts = MP_RESAMPLE_OPTS_DEF,
+ .allow_detach = 1,
+ },
+ .options = (const struct m_option[]) {
+ OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
+ OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
+ OPT_FLAG("linear", opts.linear, 0),
+ OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
+ OPT_FLAG("detach", allow_detach, 0), // does nothing
+ OPT_CHOICE("normalize", opts.normalize, 0,
+ ({"no", 0}, {"yes", 1}, {"auto", -1})),
+ OPT_KEYVALUELIST("o", opts.avopts, 0),
+ {0}
+ },
+ .set_defaults = set_defaults,
},
- .set_defaults = set_defaults,
+ .create = af_lavrresample_create,
};
diff --git a/audio/filter/af_rubberband.c b/audio/filter/af_rubberband.c
index 58cf077d8b..6c8c773e62 100644
--- a/audio/filter/af_rubberband.c
+++ b/audio/filter/af_rubberband.c
@@ -20,242 +20,348 @@
#include <rubberband/rubberband-c.h>
+#include "audio/aframe.h"
+#include "audio/format.h"
#include "common/common.h"
-#include "af.h"
+#include "filters/f_autoconvert.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
+#include "options/m_option.h"
+
+// command line options
+struct f_opts {
+ int transients, detector, phase, window,
+ smoothing, formant, pitch, channels;
+ double scale;
+};
struct priv {
+ struct f_opts *opts;
+
+ struct mp_pin *in_pin;
+ struct mp_aframe *cur_format;
+ struct mp_aframe_pool *out_pool;
+ bool sent_final;
RubberBandState rubber;
double speed;
double pitch;
- struct mp_audio *pending;
- bool needs_reset;
+ struct mp_aframe *pending;
// Estimate how much librubberband has buffered internally.
// I could not find a way to do this with the librubberband API.
double rubber_delay;
- // command line options
- int opt_transients, opt_detector, opt_phase, opt_window,
- opt_smoothing, opt_formant, opt_pitch, opt_channels;
};
-static void update_speed(struct af_instance *af, double new_speed)
+static void update_speed(struct priv *p, double new_speed)
{
- struct priv *p = af->priv;
-
p->speed = new_speed;
- rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
+ if (p->rubber)
+ rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
}
-static bool update_pitch(struct af_instance *af, double new_pitch)
+static bool update_pitch(struct priv *p, double new_pitch)
{
if (new_pitch < 0.01 || new_pitch > 100.0)
return false;
- struct priv *p = af->priv;
-
p->pitch = new_pitch;
- rubberband_set_pitch_scale(p->rubber, p->pitch);
+ if (p->rubber)
+ rubberband_set_pitch_scale(p->rubber, p->pitch);
return true;
}
-static int control(struct af_instance *af, int cmd, void *arg)
+static bool init_rubberband(struct mp_filter *f)
{
- struct priv *p = af->priv;
+ struct priv *p = f->priv;
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
- struct mp_audio *out = af->data;
+ assert(!p->rubber);
+ assert(p->pending);
- in->format = AF_FORMAT_FLOATP;
- mp_audio_copy_config(out, in);
-
- if (p->rubber)
- rubberband_delete(p->rubber);
-
- int opts = p->opt_transients | p->opt_detector | p->opt_phase |
- p->opt_window | p->opt_smoothing | p->opt_formant |
- p->opt_pitch | p-> opt_channels |
- RubberBandOptionProcessRealTime;
-
- p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
- if (!p->rubber) {
- MP_FATAL(af, "librubberband initialization failed.\n");
- return AF_ERROR;
- }
+ int opts = p->opts->transients | p->opts->detector | p->opts->phase |
+ p->opts->window | p->opts->smoothing | p->opts->formant |
+ p->opts->pitch | p-> opts->channels |
+ RubberBandOptionProcessRealTime;
- update_speed(af, p->speed);
- update_pitch(af, p->pitch);
- control(af, AF_CONTROL_RESET, NULL);
+ int rate = mp_aframe_get_rate(p->pending);
+ int channels = mp_aframe_get_channels(p->pending);
+ if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
+ return false;
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- case AF_CONTROL_SET_PLAYBACK_SPEED: {
- update_speed(af, *(double *)arg);
- return AF_OK;
- }
- case AF_CONTROL_RESET:
- if (p->rubber)
- rubberband_reset(p->rubber);
- talloc_free(p->pending);
- p->pending = NULL;
- p->rubber_delay = 0;
- return AF_OK;
- case AF_CONTROL_COMMAND: {
- char **args = arg;
- char *endptr;
- double pitch = p->pitch;
- if (!strcmp(args[0], "set-pitch")) {
- pitch = strtod(args[1], &endptr);
- if (*endptr)
- return CONTROL_ERROR;
- return update_pitch(af, pitch) ? CONTROL_OK : CONTROL_ERROR;
- } else if (!strcmp(args[0], "multiply-pitch")) {
- double mult = strtod(args[1], &endptr);
- if (*endptr || mult <= 0)
- return CONTROL_ERROR;
- pitch *= mult;
- return update_pitch(af, pitch) ? CONTROL_OK : CONTROL_ERROR;
- } else {
- return CONTROL_ERROR;
- }
- }
+ p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
+ if (!p->rubber) {
+ MP_FATAL(f, "librubberband initialization failed.\n");
+ return false;
}
- return AF_UNKNOWN;
-}
-static int filter_frame(struct af_instance *af, struct mp_audio *data)
-{
- struct priv *p = af->priv;
+ mp_aframe_config_copy(p->cur_format, p->pending);
- talloc_free(p->pending);
- p->pending = data;
+ update_speed(p, p->speed);
+ update_pitch(p, p->pitch);
- return 0;
+ return true;
}
-static int filter_out(struct af_instance *af)
+static void process(struct mp_filter *f)
{
- struct priv *p = af->priv;
+ struct priv *p = f->priv;
+
+ if (!mp_pin_in_needs_data(f->ppins[1]))
+ return;
- while (rubberband_available(p->rubber) <= 0) {
+ while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
const float *dummy[MP_NUM_CHANNELS] = {0};
const float **in_data = dummy;
size_t in_samples = 0;
- if (p->pending) {
- if (!p->pending->samples)
- break;
- // recover from previous EOF
- if (p->needs_reset) {
- rubberband_reset(p->rubber);
- p->rubber_delay = 0;
+ bool eof = false;
+ if (!p->pending || !mp_aframe_get_size(p->pending)) {
+ struct mp_frame frame = mp_pin_out_read(p->in_pin);
+ if (frame.type == MP_FRAME_AUDIO) {
+ TA_FREEP(&p->pending);
+ p->pending = frame.data;
+ } else if (frame.type == MP_FRAME_EOF) {
+ eof = true;
+ } else if (frame.type) {
+ MP_ERR(f, "unexpected frame type\n");
+ goto error;
+ } else {
+ return; // no new data yet
}
- p->needs_reset = false;
+ }
+ assert(p->pending || eof);
+ if (!p->rubber) {
+ if (!p->pending) {
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ return;
+ }
+ if (!init_rubberband(f))
+ goto error;
+ }
+
+ bool format_change =
+ p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
+
+ if (p->pending && !format_change) {
size_t needs = rubberband_get_samples_required(p->rubber);
- in_data = (void *)&p->pending->planes;
- in_samples = MPMIN(p->pending->samples, needs);
+ uint8_t **planes = mp_aframe_get_data_ro(p->pending);
+ int num_planes = mp_aframe_get_planes(p->pending);
+ for (int n = 0; n < num_planes; n++)
+ in_data[n] = (void *)planes[n];
+ in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
}
- if (p->needs_reset)
- break; // previous EOF
- p->needs_reset = !p->pending; // EOF
+ bool final = format_change || eof;
+ if (!p->sent_final)
+ rubberband_process(p->rubber, in_data, in_samples, final);
+ p->sent_final |= final;
- rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
p->rubber_delay += in_samples;
- if (!p->pending)
- break;
- mp_audio_skip_samples(p->pending, in_samples);
+ if (p->pending && !format_change)
+ mp_aframe_skip_samples(p->pending, in_samples);
+
+ if (rubberband_available(p->rubber) > 0) {
+ if (eof)
+ mp_pin_out_repeat_eof(p->in_pin); // drain more next time
+ } else {
+ if (eof) {
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ rubberband_reset(p->rubber);
+ TA_FREEP(&p->pending);
+ p->sent_final = false;
+ return;
+ } else if (format_change) {
+ // go on with proper reinit on the next iteration
+ rubberband_delete(p->rubber);
+ p->sent_final = false;
+ p->rubber = NULL;
+ }
+ }
}
+ assert(p->pending);
+
int out_samples = rubberband_available(p->rubber);
if (out_samples > 0) {
- struct mp_audio *out =
- mp_audio_pool_get(af->out_pool, af->data, out_samples);
- if (!out)
- return -1;
- if (p->pending)
- mp_audio_copy_config(out, p->pending);
-
- float **out_data = (void *)&out->planes;
- out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
- p->rubber_delay -= out->samples * p->speed;
-
- af_add_output_frame(af, out);
+ struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
+ if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
+ talloc_free(out);
+ goto error;
+ }
+
+ mp_aframe_copy_attributes(out, p->pending);
+
+ float *out_data[MP_NUM_CHANNELS] = {0};
+ uint8_t **planes = mp_aframe_get_data_rw(out);
+ assert(planes);
+ int num_planes = mp_aframe_get_planes(out);
+ for (int n = 0; n < num_planes; n++)
+ out_data[n] = (void *)planes[n];
+
+ out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
+
+ if (!out_samples) {
+ mp_filter_internal_mark_progress(f); // unexpected, just try again
+ talloc_free(out);
+ return;
+ }
+
+ mp_aframe_set_size(out, out_samples);
+
+ p->rubber_delay -= out_samples * p->speed;
+
+ double pts = mp_aframe_get_pts(p->pending);
+ if (pts != MP_NOPTS_VALUE) {
+ // Note: rubberband_get_latency() does not do what you'd expect.
+ double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
+ mp_aframe_set_pts(out, pts - delay);
+ }
+
+ mp_aframe_mul_speed(out, p->speed);
+
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
}
- int delay_samples = p->rubber_delay;
- if (p->pending)
- delay_samples += p->pending->samples;
- af->delay = delay_samples / (af->data->rate * p->speed);
+ return;
+error:
+ mp_filter_internal_mark_failed(f);
+}
- return 0;
+static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
+{
+ struct priv *p = f->priv;
+
+ switch (cmd->type) {
+ case MP_FILTER_COMMAND_TEXT: {
+ char *endptr = NULL;
+ double pitch = p->pitch;
+ if (!strcmp(cmd->cmd, "set-pitch")) {
+ pitch = strtod(cmd->arg, &endptr);
+ if (*endptr)
+ return false;
+ return update_pitch(p, pitch);
+ } else if (!strcmp(cmd->cmd, "multiply-pitch")) {
+ double mult = strtod(cmd->arg, &endptr);
+ if (*endptr || mult <= 0)
+ return false;
+ pitch *= mult;
+ return update_pitch(p, pitch);
+ }
+ return false;
+ }
+ case MP_FILTER_COMMAND_SET_SPEED:
+ update_speed(p, cmd->speed);
+ return true;
+ }
+
+ return false;
}
-static void uninit(struct af_instance *af)
+static void reset(struct mp_filter *f)
{
- struct priv *p = af->priv;
+ struct priv *p = f->priv;
if (p->rubber)
- rubberband_delete(p->rubber);
- talloc_free(p->pending);
+ rubberband_reset(p->rubber);
+ p->sent_final = false;
+ TA_FREEP(&p->pending);
}
-static int af_open(struct af_instance *af)
+static void destroy(struct mp_filter *f)
{
- af->control = control;
- af->filter_frame = filter_frame;
- af->filter_out = filter_out;
- af->uninit = uninit;
- return AF_OK;
+ struct priv *p = f->priv;
+
+ if (p->rubber)
+ rubberband_delete(p->rubber);
+ talloc_free(p->pending);
}
-#define OPT_BASE_STRUCT struct priv
-const struct af_info af_info_rubberband = {
- .info = "Pitch conversion with librubberband",
+static const struct mp_filter_info af_rubberband_filter = {
.name = "rubberband",
- .open = af_open,
.priv_size = sizeof(struct priv),
- .priv_defaults = &(const struct priv) {
- .speed = 1.0,
- .pitch = 1.0,
- .opt_pitch = RubberBandOptionPitchHighConsistency,
- .opt_transients = RubberBandOptionTransientsMixed,
- .opt_formant = RubberBandOptionFormantPreserved,
- .opt_channels = RubberBandOptionChannelsTogether,
- },
- .options = (const struct m_option[]) {
- OPT_CHOICE("transients", opt_transients, 0,
- ({"crisp", RubberBandOptionTransientsCrisp},
- {"mixed", RubberBandOptionTransientsMixed},
- {"smooth", RubberBandOptionTransientsSmooth})),
- OPT_CHOICE("detector", opt_detector, 0,
- ({"compound", RubberBandOptionDetectorCompound},
- {"percussive", RubberBandOptionDetectorPercussive},
- {"soft", RubberBandOptionDetectorSoft})),
- OPT_CHOICE("phase", opt_phase, 0,
- ({"laminar", RubberBandOptionPhaseLaminar},
- {"independent", RubberBandOptionPhaseIndependent})),
- OPT_CHOICE("window", opt_window, 0,
- ({"standard", RubberBandOptionWindowStandard},
- {"short", RubberBandOptionWindowShort},
- {"long", RubberBandOptionWindowLong})),
- OPT_CHOICE("smoothing", opt_smoothing, 0,
- ({"off", RubberBandOptionSmoothingOff},
- {"on", RubberBandOptionSmoothingOn})),
- OPT_CHOICE("formant", opt_formant, 0,
- ({"shifted", RubberBandOptionFormantShifted},
- {"preserved", RubberBandOptionFormantPreserved})),
- OPT_CHOICE("pitch", opt_pitch, 0,
- ({"quality", RubberBandOptionPitchHighQuality},
- {"speed", RubberBandOptionPitchHighSpeed},
- {"consistency", RubberBandOptionPitchHighConsistency})),
- OPT_CHOICE("channels", opt_channels, 0,
- ({"apart", RubberBandOptionChannelsApart},
- {"together", RubberBandOptionChannelsTogether})),
- OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
- {0}
+ .process = process,
+ .command = command,
+ .reset = reset,
+ .destroy = destroy,
+};
+
+static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
+ void *options)
+{
+ struct mp_filter *f = mp_filter_create(parent, &af_rubberband_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->pitch = p->opts->scale;
+ p->cur_format = talloc_steal(p, mp_aframe_create());
+ p->out_pool = mp_aframe_pool_create(p);
+
+ struct mp_autoconvert *conv = mp_autoconvert_create(f);
+ if (!conv)
+ abort();
+
+ mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
+
+ mp_pin_connect(conv->f->pins[0], f->ppins[0]);
+ p->in_pin = conv->f->pins[1];
+
+ return f;
+}
+
+#define OPT_BASE_STRUCT struct f_opts
+
+const struct mp_user_filter_entry af_rubberband = {
+ .desc = {
+ .description = "Pitch conversion with librubberband",
+ .name = "rubberband",
+ .priv_size = sizeof(OPT_BASE_STRUCT),
+ .priv_defaults = &(const OPT_BASE_STRUCT) {
+ .scale = 1.0,
+ .pitch = RubberBandOptionPitchHighConsistency,
+ .transients = RubberBandOptionTransientsMixed,
+ .formant = RubberBandOptionFormantPreserved,
+ .channels = RubberBandOptionChannelsTogether,
+ },
+ .options = (const struct m_option[]) {
+ OPT_CHOICE("transients", transients, 0,
+ ({"crisp", RubberBandOptionTransientsCrisp},
+ {"mixed", RubberBandOptionTransientsMixed},
+ {"smooth", RubberBandOptionTransientsSmooth})),
+ OPT_CHOICE("detector", detector, 0,
+ ({"compound", RubberBandOptionDetectorCompound},
+ {"percussive", RubberBandOptionDetectorPercussive},
+ {"soft", RubberBandOptionDetectorSoft})),
+ OPT_CHOICE("phase", phase, 0,
+ ({"laminar", RubberBandOptionPhaseLaminar},
+ {"independent", RubberBandOptionPhaseIndependent})),
+ OPT_CHOICE("window", window, 0,
+ ({"standard", RubberBandOptionWindowStandard},
+ {"short", RubberBandOptionWindowShort},
+ {"long", RubberBandOptionWindowLong})),
+ OPT_CHOICE("smoothing", smoothing, 0,
+ ({"off", RubberBandOptionSmoothingOff},
+ {"on", RubberBandOptionSmoothingOn})),
+ OPT_CHOICE("formant", formant, 0,
+ ({"shifted", RubberBandOptionFormantShifted},
+ {"preserved", RubberBandOptionFormantPreserved})),
+ OPT_CHOICE("pitch", pitch, 0,
+ ({"quality", RubberBandOptionPitchHighQuality},
+ {"speed", RubberBandOptionPitchHighSpeed},
+ {"consistency", RubberBandOptionPitchHighConsistency})),
+ OPT_CHOICE("channels", channels, 0,
+ ({"apart", RubberBandOptionChannelsApart},
+ {"together", RubberBandOptionChannelsTogether})),
+ OPT_DOUBLE("pitch-scale", scale, M_OPT_RANGE, .min = 0.01, .max = 100),
+ {0}
+ },
},
+ .create = af_rubberband_create,
};
diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c
index 0499631ea9..4e48e6168b 100644
--- a/audio/filter/af_scaletempo.c
+++ b/audio/filter/af_scaletempo.c
@@ -35,14 +35,32 @@
#include <limits.h>
#include <assert.h>
+#include "audio/aframe.h"
+#include "audio/format.h"
#include "common/common.h"
-
-#include "af.h"
+#include "filters/f_autoconvert.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
#include "options/m_option.h"
-// Data for specific instances of this filter
-typedef struct af_scaletempo_s
-{
+struct f_opts {
+ float scale_nominal;
+ float ms_stride;
+ float ms_search;
+ float percent_overlap;
+#define SCALE_TEMPO 1
+#define SCALE_PITCH 2
+ int speed_opt;
+};
+
+struct priv {
+ struct f_opts *opts;
+
+ struct mp_pin *in_pin;
+ struct mp_aframe *cur_format;
+ struct mp_aframe_pool *out_pool;
+ double current_pts;
+
// stride
float scale;
float speed;
@@ -62,28 +80,21 @@ typedef struct af_scaletempo_s
int bytes_standing;
void *buf_overlap;
void *table_blend;
- void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
+ void (*output_overlap)(struct priv *s, void *out_buf,
int bytes_off);
// best overlap
int frames_search;
int num_channels;
void *buf_pre_corr;
void *table_window;
- int (*best_overlap_offset)(struct af_scaletempo_s *s);
- // command line
- float scale_nominal;
- float ms_stride;
- float percent_overlap;
- float ms_search;
-#define SCALE_TEMPO 1
-#define SCALE_PITCH 2
- int speed_opt;
-} af_scaletempo_t;
+ int (*best_overlap_offset)(struct priv *s);
+};
-static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
+static bool reinit(struct mp_filter *f, struct mp_aframe *in);
+
+static int fill_queue(struct priv *s, struct mp_aframe *in, int offset)
{
- af_scaletempo_t *s = af->priv;
- int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
+ int bytes_in = in ? mp_aframe_get_size(in) * s->bytes_per_frame - offset : 0;
int offset_unchanged = offset;
if (s->bytes_to_slide > 0) {
@@ -106,8 +117,8 @@ static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
if (bytes_in > 0) {
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
assert(bytes_copy >= 0);
- memcpy(s->buf_queue + s->bytes_queued,
- (int8_t *)data->planes[0] + offset, bytes_copy);
+ uint8_t **planes = mp_aframe_get_data_ro(in);
+ memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
s->bytes_queued += bytes_copy;
offset += bytes_copy;
}
@@ -117,7 +128,7 @@ static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
#define UNROLL_PADDING (4 * 4)
-static int best_overlap_offset_float(af_scaletempo_t *s)
+static int best_overlap_offset_float(struct priv *s)
{
float best_corr = INT_MIN;
int best_off = 0;
@@ -146,7 +157,7 @@ static int best_overlap_offset_float(af_scaletempo_t *s)
return best_off * 4 * s->num_channels;
}
-static int best_overlap_offset_s16(af_scaletempo_t *s)
+static int best_overlap_offset_s16(struct priv *s)
{
int64_t best_corr = INT64_MIN;
int best_off = 0;
@@ -183,7 +194,7 @@ static int best_overlap_offset_s16(af_scaletempo_t *s)
return best_off * 2 * s->num_channels;
}
-static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
+static void output_overlap_float(struct priv *s, void *buf_out,
int bytes_off)
{
float *pout = buf_out;
@@ -196,7 +207,7 @@ static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
}
}
-static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
+static void output_overlap_s16(struct priv *s, void *buf_out,
int bytes_off)
{
int16_t *pout = buf_out;
@@ -209,28 +220,65 @@ static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
}
}
-static int filter(struct af_instance *af, struct mp_audio *data)
+static void process(struct mp_filter *f)
{
- af_scaletempo_t *s = af->priv;
+ struct priv *s = f->priv;
+
+ if (!mp_pin_can_transfer_data(f->ppins[1], s->in_pin))
+ return;
- if (s->scale == 1.0) {
- af->delay = 0;
- af_add_output_frame(af, data);
- return 0;
+ struct mp_aframe *in = NULL, *out = NULL;
+
+ struct mp_frame frame = mp_pin_out_read(s->in_pin);
+ if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
+ MP_ERR(f, "unexpected frame type\n");
+ goto error;
+ }
+
+ in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
+ bool is_eof = !in;
+
+ // EOF before it was even initialized once.
+ if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ return;
+ }
+
+ if (in && !mp_aframe_config_equals(in, s->cur_format)) {
+ if (s->bytes_queued) {
+ // Drain remaining data before executing the format change.
+ MP_VERBOSE(f, "draining\n");
+ mp_pin_out_unread(s->in_pin, frame);
+ in = NULL;
+ } else {
+ if (!reinit(f, in)) {
+ MP_ERR(f, "initialization failed\n");
+ goto error;
+ }
+ }
}
- int in_samples = data ? data->samples : 0;
- struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
- ((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
- if (!out) {
- talloc_free(data);
- return -1;
+ int in_samples = in ? mp_aframe_get_size(in) : 0;
+
+ int max_out_samples =
+ ((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride;
+ if (!in)
+ max_out_samples += s->bytes_queued;
+ out = mp_aframe_new_ref(s->cur_format);
+ if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
+ goto error;
+
+ if (in) {
+ mp_aframe_copy_attributes(out, in);
+ s->current_pts = mp_aframe_end_pts(in);
}
- if (data)
- mp_audio_copy_attributes(out, data);
- int offset_in = fill_queue(af, data, 0);
- int8_t *pout = out->planes[0];
+ int offset_in = fill_queue(s, in, 0);
+ uint8_t **out_planes = mp_aframe_get_data_rw(out);
+ if (!out_planes)
+ goto error;
+ int8_t *pout = out_planes[0];
+ int out_offset = 0;
while (s->bytes_queued >= s->bytes_queue) {
int ti;
float tf;
@@ -240,12 +288,12 @@ static int filter(struct af_instance *af, struct mp_audio *data)
if (s->output_overlap) {
if (s->best_overlap_offset)
bytes_off = s->best_overlap_offset(s);
- s->output_overlap(s, pout, bytes_off);
+ s->output_overlap(s, pout + out_offset, bytes_off);
}
- memcpy(pout + s->bytes_overlap,
+ memcpy(pout + out_offset + s->bytes_overlap,
s->buf_queue + bytes_off + s->bytes_overlap,
s->bytes_standing);
- pout += s->bytes_stride;
+ out_offset += s->bytes_stride;
// input stride
memcpy(s->buf_overlap,
@@ -256,239 +304,302 @@ static int filter(struct af_instance *af, struct mp_audio *data)
s->frames_stride_error = tf - ti;
s->bytes_to_slide = ti * s->bytes_per_frame;
- offset_in += fill_queue(af, data, offset_in);
+ offset_in += fill_queue(s, in, offset_in);
}
+ // Drain remaining buffered data.
+ if (!in && s->bytes_queued) {
+ memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
+ out_offset += s->bytes_queued;
+ s->bytes_queued = 0;
+ }
+ mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
// This filter can have a negative delay when scale > 1:
// output corresponding to some length of input can be decided and written
// after receiving only a part of that input.
- af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
- / out->sstride / out->rate;
+ double delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
+ s->bytes_per_frame / mp_aframe_get_effective_rate(out);
- out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
- talloc_free(data);
- if (out->samples) {
- af_add_output_frame(af, out);
- } else {
- talloc_free(out);
+ if (s->current_pts != MP_NOPTS_VALUE)
+ mp_aframe_set_pts(out, s->current_pts - delay);
+
+ mp_aframe_mul_speed(out, s->speed);
+
+ if (!mp_aframe_get_size(out))
+ TA_FREEP(&out);
+
+ if (is_eof && out) {
+ mp_pin_out_repeat_eof(s->in_pin);
+ } else if (is_eof && !out) {
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ } else if (!is_eof && !out) {
+ mp_pin_out_request_data(s->in_pin);
}
- return 0;
+
+ if (out)
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
+
+ talloc_free(in);
+ return;
+
+error:
+ talloc_free(in);
+ talloc_free(out);
+ mp_filter_internal_mark_failed(f);
}
-static void update_speed(struct af_instance *af, float speed)
+static void update_speed(struct priv *s, float speed)
{
- af_scaletempo_t *s = af->priv;
-
s->speed = speed;
- double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
- s->scale = factor * s->scale_nominal;
+ double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
+ s->scale = factor * s->opts->scale_nominal;
s->frames_stride_scaled = s->scale * s->frames_stride;
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
}
-// Initialization and runtime control
-static int control(struct af_instance *af, int cmd, void *arg)
+static bool reinit(struct mp_filter *f, struct mp_aframe *in)
{
- af_scaletempo_t *s = af->priv;
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *data = (struct mp_audio *)arg;
- float srate = data->rate / 1000.0;
- int nch = data->nch;
- int use_int = 0;
-
- mp_audio_force_interleaved_format(data);
- mp_audio_copy_config(af->data, data);
-
- if (data->format == AF_FORMAT_S16) {
- use_int = 1;
- } else {
- mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
- }
- int bps = af->data->bps;
+ struct priv *s = f->priv;
- s->frames_stride = srate * s->ms_stride;
- s->bytes_stride = s->frames_stride * bps * nch;
- af->delay = 0;
+ mp_aframe_reset(s->cur_format);
- update_speed(af, s->speed);
+ float srate = mp_aframe_get_rate(in) / 1000.0;
+ int nch = mp_aframe_get_channels(in);
+ int format = mp_aframe_get_format(in);
- int frames_overlap = s->frames_stride * s->percent_overlap;
- if (frames_overlap <= 0) {
- s->bytes_standing = s->bytes_stride;
- s->samples_standing = s->bytes_standing / bps;
- s->output_overlap = NULL;
- s->bytes_overlap = 0;
- } else {
- s->samples_overlap = frames_overlap * nch;
- s->bytes_overlap = frames_overlap * nch * bps;
- s->bytes_standing = s->bytes_stride - s->bytes_overlap;
- s->samples_standing = s->bytes_standing / bps;
- s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
- s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
- if (!s->buf_overlap || !s->table_blend) {
- MP_FATAL(af, "Out of memory\n");
- return AF_ERROR;
+ int use_int = 0;
+ if (format == AF_FORMAT_S16) {
+ use_int = 1;
+ } else if (format != AF_FORMAT_FLOAT) {
+ return false;
+ }
+ int bps = use_int ? 2 : 4;
+
+ s->frames_stride = srate * s->opts->ms_stride;
+ s->bytes_stride = s->frames_stride * bps * nch;
+
+ update_speed(s, s->speed);
+
+ int frames_overlap = s->frames_stride * s->opts->percent_overlap;
+ if (frames_overlap <= 0) {
+ s->bytes_standing = s->bytes_stride;
+ s->samples_standing = s->bytes_standing / bps;
+ s->output_overlap = NULL;
+ s->bytes_overlap = 0;
+ } else {
+ s->samples_overlap = frames_overlap * nch;
+ s->bytes_overlap = frames_overlap * nch * bps;
+ s->bytes_standing = s->bytes_stride - s->bytes_overlap;
+ s->samples_standing = s->bytes_standing / bps;
+ s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
+ s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
+ if (!s->buf_overlap || !s->table_blend) {
+ MP_FATAL(f, "Out of memory\n");
+ return false;
+ }
+ memset(s->buf_overlap, 0, s->bytes_overlap);
+ if (use_int) {
+ int32_t *pb = s->table_blend;
+ int64_t blend = 0;
+ for (int i = 0; i < frames_overlap; i++) {
+ int32_t v = blend / frames_overlap;
+ for (int j = 0; j < nch; j++)
+ *pb++ = v;
+ blend += 65536; // 2^16
}
- memset(s->buf_overlap, 0, s->bytes_overlap);
- if (use_int) {
- int32_t *pb = s->table_blend;
- int64_t blend = 0;
- for (int i = 0; i < frames_overlap; i++) {
- int32_t v = blend / frames_overlap;
- for (int j = 0; j < nch; j++)
- *pb++ = v;
- blend += 65536; // 2^16
- }
- s->output_overlap = output_overlap_s16;
- } else {
- float *pb = s->table_blend;
- for (int i = 0; i < frames_overlap; i++) {
- float v = i / (float)frames_overlap;
- for (int j = 0; j < nch; j++)
- *pb++ = v;
- }
- s->output_overlap = output_overlap_float;
+ s->output_overlap = output_overlap_s16;
+ } else {
+ float *pb = s->table_blend;
+ for (int i = 0; i < frames_overlap; i++) {
+ float v = i / (float)frames_overlap;
+ for (int j = 0; j < nch; j++)
+ *pb++ = v;
}
+ s->output_overlap = output_overlap_float;
}
+ }
- s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
- if (s->frames_search <= 0)
- s->best_overlap_offset = NULL;
- else {
- if (use_int) {
- int64_t t = frames_overlap;
- int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
- s->buf_pre_corr = realloc(s->buf_pre_corr,
- s->bytes_overlap * 2 + UNROLL_PADDING);
- s->table_window = realloc(s->table_window,
- s->bytes_overlap * 2 - nch * bps * 2);
- if (!s->buf_pre_corr || !s->table_window) {
- MP_FATAL(af, "Out of memory\n");
- return AF_ERROR;
- }
- memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
- UNROLL_PADDING);
- int32_t *pw = s->table_window;
- for (int i = 1; i < frames_overlap; i++) {
- int32_t v = (i * (t - i) * n) >> 15;
- for (int j = 0; j < nch; j++)
- *pw++ = v;
- }
- s->best_overlap_offset = best_overlap_offset_s16;
- } else {
- s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
- s->table_window = realloc(s->table_window,
- s->bytes_overlap - nch * bps);
- if (!s->buf_pre_corr || !s->table_window) {
- MP_FATAL(af, "Out of memory\n");
- return AF_ERROR;
- }
- float *pw = s->table_window;
- for (int i = 1; i < frames_overlap; i++) {
- float v = i * (frames_overlap - i);
- for (int j = 0; j < nch; j++)
- *pw++ = v;
- }
- s->best_overlap_offset = best_overlap_offset_float;
+ s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
+ if (s->frames_search <= 0)
+ s->best_overlap_offset = NULL;
+ else {
+ if (use_int) {
+ int64_t t = frames_overlap;
+ int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
+ s->buf_pre_corr = realloc(s->buf_pre_corr,
+ s->bytes_overlap * 2 + UNROLL_PADDING);
+ s->table_window = realloc(s->table_window,
+ s->bytes_overlap * 2 - nch * bps * 2);
+ if (!s->buf_pre_corr || !s->table_window) {
+ MP_FATAL(f, "Out of memory\n");
+ return false;
+ }
+ memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
+ UNROLL_PADDING);
+ int32_t *pw = s->table_window;
+ for (int i = 1; i < frames_overlap; i++) {
+ int32_t v = (i * (t - i) * n) >> 15;
+ for (int j = 0; j < nch; j++)
+ *pw++ = v;
}
+ s->best_overlap_offset = best_overlap_offset_s16;
+ } else {
+ s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
+ s->table_window = realloc(s->table_window,
+ s->bytes_overlap - nch * bps);
+ if (!s->buf_pre_corr || !s->table_window) {
+ MP_FATAL(f, "Out of memory\n");
+ return false;
+ }
+ float *pw = s->table_window;
+ for (int i = 1; i < frames_overlap; i++) {
+ float v = i * (frames_overlap - i);
+ for (int j = 0; j < nch; j++)
+ *pw++ = v;
+ }
+ s->best_overlap_offset = best_overlap_offset_float;
}
+ }
- s->bytes_per_frame = bps * nch;
- s->num_channels = nch;
-
- s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
- * bps * nch;
- s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
- if (!s->buf_queue) {
- MP_FATAL(af, "Out of memory\n");
- return AF_ERROR;
- }
+ s->bytes_per_frame = bps * nch;
+ s->num_channels = nch;
- s->bytes_queued = 0;
- s->bytes_to_slide = 0;
-
- MP_DBG(af, ""
- "%.2f stride_in, %i stride_out, %i standing, "
- "%i overlap, %i search, %i queue, %s mode\n",
- s->frames_stride_scaled,
- (int)(s->bytes_stride / nch / bps),
- (int)(s->bytes_standing / nch / bps),
- (int)(s->bytes_overlap / nch / bps),
- s->frames_search,
- (int)(s->bytes_queue / nch / bps),
- (use_int ? "s16" : "float"));
-
- return af_test_output(af, (struct mp_audio *)arg);
+ s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
+ * bps * nch;
+ s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
+ if (!s->buf_queue) {
+ MP_FATAL(f, "Out of memory\n");
+ return false;
}
- case AF_CONTROL_SET_PLAYBACK_SPEED: {
- double speed = *(double *)arg;
- if (s->speed_opt & SCALE_TEMPO) {
- if (s->speed_opt & SCALE_PITCH)
- break;
- update_speed(af, speed);
- } else if (s->speed_opt & SCALE_PITCH) {
- update_speed(af, speed);
- break; // do not signal OK
+
+ s->bytes_queued = 0;
+ s->bytes_to_slide = 0;
+
+ MP_DBG(f, ""
+ "%.2f stride_in, %i stride_out, %i standing, "
+ "%i overlap, %i search, %i queue, %s mode\n",
+ s->frames_stride_scaled,
+ (int)(s->bytes_stride / nch / bps),
+ (int)(s->bytes_standing / nch / bps),
+ (int)(s->bytes_overlap / nch / bps),
+ s->frames_search,
+ (int)(s->bytes_queue / nch / bps),
+ (use_int ? "s16" : "float"));
+
+ mp_aframe_config_copy(s->cur_format, in);
+
+ return true;
+}
+
+static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
+{
+ struct priv *s = f->priv;
+
+ if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
+ if (s->opts->speed_opt & SCALE_TEMPO) {
+ if (s->opts->speed_opt & SCALE_PITCH)
+ return false;
+ update_speed(s, cmd->speed);
+ return true;
+ } else if (s->opts->speed_opt & SCALE_PITCH) {
+ update_speed(s, cmd->speed);
+ return false; // do not signal OK
}
- return AF_OK;
- }
- case AF_CONTROL_RESET:
- s->bytes_queued = 0;
- s->bytes_to_slide = 0;
- s->frames_stride_error = 0;
- memset(s->buf_overlap, 0, s->bytes_overlap);
}
- return AF_UNKNOWN;
+
+ return false;
}
-// Deallocate memory
-static void uninit(struct af_instance *af)
+static void reset(struct mp_filter *f)
{
- af_scaletempo_t *s = af->priv;
+ struct priv *s = f->priv;
+
+ s->current_pts = MP_NOPTS_VALUE;
+ s->bytes_queued = 0;
+ s->bytes_to_slide = 0;
+ s->frames_stride_error = 0;
+ memset(s->buf_overlap, 0, s->bytes_overlap);
+}
+
+static void destroy(struct mp_filter *f)
+{
+ struct priv *s = f->priv;
free(s->buf_queue);
free(s->buf_overlap);
free(s->buf_pre_corr);
free(s->table_blend);
free(s->table_window);
+ mp_filter_free_children(f);
}
-// Allocate memory and set function pointers
-static int af_open(struct af_instance *af)
+static const struct mp_filter_info af_scaletempo_filter = {
+ .name = "scaletempo",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .command = command,
+ .reset = reset,
+ .destroy = destroy,
+};
+
+static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
+ void *options)
{
- af->control = control;
- af->uninit = uninit;
- af->filter_frame = filter;
- return AF_OK;
-}
+ struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
+ if (!f) {
+ talloc_free(options);
+ return NULL;
+ }
-#define OPT_BASE_STRUCT af_scaletempo_t
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
-const struct af_info af_info_scaletempo = {
- .info = "Scale audio tempo while maintaining pitch",
- .name = "scaletempo",
- .open = af_open,
- .priv_size = sizeof(af_scaletempo_t),
- .priv_defaults = &(const af_scaletempo_t) {
- .ms_stride = 60,
- .percent_overlap = .20,
- .ms_search = 14,
- .speed_opt = SCALE_TEMPO,
- .speed = 1.0,
- .scale_nominal = 1.0,
- },
- .options = (const struct m_option[]) {
- OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
- OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
- OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
- OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
- OPT_CHOICE("speed", speed_opt, 0,
- ({"pitch", SCALE_PITCH},
- {"tempo", SCALE_TEMPO},
- {"none", 0},
- {"both", SCALE_TEMPO | SCALE_PITCH})),
- {0}
+ struct priv *s = f->priv;
+ s->opts = talloc_steal(s, options);
+ s->speed = 1.0;
+ s->cur_format = talloc_steal(s, mp_aframe_create());
+ s->out_pool = mp_aframe_pool_create(s);
+
+ struct mp_autoconvert *conv = mp_autoconvert_create(f);
+ if (!conv)
+ abort();
+
+ mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
+ mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
+
+ mp_pin_connect(conv->f->pins[0], f->ppins[0]);
+ s->in_pin = conv->f->pins[1];
+
+ return f;
+}
+
+#define OPT_BASE_STRUCT struct f_opts
+
+const struct mp_user_filter_entry af_scaletempo = {
+ .desc = {
+ .description = "Scale audio tempo while maintaining pitch",
+ .name = "scaletempo",
+ .priv_size = sizeof(OPT_BASE_STRUCT),
+ .priv_defaults = &(const OPT_BASE_STRUCT) {
+ .ms_stride = 60,
+ .percent_overlap = .20,
+ .ms_search = 14,
+ .speed_opt = SCALE_TEMPO,
+ .scale_nominal = 1.0,
+ },
+ .options = (const struct m_option[]) {
+ OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
+ OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
+ OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
+ OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
+ OPT_CHOICE("speed", speed_opt, 0,
+ ({"pitch", SCALE_PITCH},
+ {"tempo", SCALE_TEMPO},
+ {"none", 0},
+ {"both", SCALE_TEMPO | SCALE_PITCH})),
+ {0}
+ },
},
+ .create = af_scaletempo_create,
};
diff --git a/audio/filter/tools.c b/audio/filter/tools.c
deleted file mode 100644
index 4ebea64d4a..0000000000
--- a/audio/filter/tools.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <math.h>
-#include <string.h>
-
-#include "common/common.h"
-#include "af.h"
-
-/* Convert from ms to sample time */
-int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
-{
- int i = 0;
- // Sanity check
- if(!in || !out)
- return AF_ERROR;
-
- for(i=0;i<n;i++)
- out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
-
- return AF_OK;
-}
-
-/*
- * test if output format matches
- * af: audio filter
- * out: needed format, will be overwritten by available
- * format if they do not match
- * returns: AF_FALSE if formats do not match, AF_OK if they match
- *
- * compares the format, rate and nch values of af->data with out
- * Note: logically, *out=*af->data always happens, because out contains the
- * format only, no actual audio data or memory allocations. *out always
- * contains the parameters from af->data after the function returns.
- */
-int af_test_output(struct af_instance* af, struct mp_audio* out)
-{
- if((af->data->format != out->format) ||
- (af->data->bps != out->bps) ||
- (af->data->rate != out->rate) ||
- !mp_chmap_equals(&af->data->channels, &out->channels)){
- *out = *af->data;
- return AF_FALSE;
- }
- return AF_OK;
-}
-
-/* Soft clipping, the sound of a dream, thanks to Jon Wattes
- post to Musicdsp.org */
-float af_softclip(float a)
-{
- if (a >= M_PI/2)
- return 1.0;
- else if (a <= -M_PI/2)
- return -1.0;
- else
- return sin(a);
-}
diff --git a/audio/format.c b/audio/format.c
index b6d6761b65..8a13698ff7 100644
--- a/audio/format.c
+++ b/audio/format.c
@@ -154,7 +154,7 @@ void af_fill_silence(void *dst, size_t bytes, int format)
// If the formats are equal, 1024 is returned. If they are gravely incompatible
// (like s16<->ac3), INT_MIN is returned. If there is implied loss of precision
// (like s16->s8), a value <0 is returned.
-static int af_format_conversion_score(int dst_format, int src_format)
+int af_format_conversion_score(int dst_format, int src_format)
{
if (dst_format == AF_FORMAT_UNKNOWN || src_format == AF_FORMAT_UNKNOWN)
return INT_MIN;
diff --git a/audio/format.h b/audio/format.h
index 8c620226df..0afc6567c9 100644
--- a/audio/format.h
+++ b/audio/format.h
@@ -70,6 +70,7 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
void af_fill_silence(void *dst, size_t bytes, int format);
void af_get_best_sample_formats(int src_format, int *out_formats);
+int af_format_conversion_score(int dst_format, int src_format);
int af_select_best_samplerate(int src_sampelrate, const int *available);
int af_format_sample_alignment(int format);
diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c
index eac6f745ca..b9f32026d5 100644
--- a/filters/f_auto_filters.c
+++ b/filters/f_auto_filters.c
@@ -242,3 +242,93 @@ struct mp_filter *mp_autorotate_create(struct mp_filter *parent)
return f;
}
+
+struct aspeed_priv {
+ struct mp_subfilter sub;
+ double cur_speed;
+};
+
+static void aspeed_process(struct mp_filter *f)
+{
+ struct aspeed_priv *p = f->priv;
+
+ if (!mp_subfilter_read(&p->sub))
+ return;
+
+ if (fabs(p->cur_speed - 1.0) < 1e-8) {
+ if (p->sub.filter)
+ MP_VERBOSE(f, "removing scaletempo\n");
+ if (!mp_subfilter_drain_destroy(&p->sub))
+ return;
+ } else if (!p->sub.filter) {
+ MP_VERBOSE(f, "adding scaletempo\n");
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, "scaletempo", NULL);
+ if (!p->sub.filter) {
+ MP_ERR(f, "could not create scaletempo filter\n");
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+ }
+
+ if (p->sub.filter) {
+ struct mp_filter_command cmd = {
+ .type = MP_FILTER_COMMAND_SET_SPEED,
+ .speed = p->cur_speed,
+ };
+ mp_filter_command(p->sub.filter, &cmd);
+ }
+
+ mp_subfilter_continue(&p->sub);
+}
+
+static bool aspeed_command(struct mp_filter *f, struct mp_filter_command *cmd)
+{
+ struct aspeed_priv *p = f->priv;
+
+ if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
+ p->cur_speed = cmd->speed;
+ return true;
+ }
+
+ return false;
+}
+
+static void aspeed_reset(struct mp_filter *f)
+{
+ struct aspeed_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+}
+
+static void aspeed_destroy(struct mp_filter *f)
+{
+ struct aspeed_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+ TA_FREEP(&p->sub.filter);
+}
+
+static const struct mp_filter_info aspeed_filter = {
+ .name = "autoaspeed",
+ .priv_size = sizeof(struct aspeed_priv),
+ .command = aspeed_command,
+ .process = aspeed_process,
+ .reset = aspeed_reset,
+ .destroy = aspeed_destroy,
+};
+
+struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent)
+{
+ struct mp_filter *f = mp_filter_create(parent, &aspeed_filter);
+ if (!f)
+ return NULL;
+
+ struct aspeed_priv *p = f->priv;
+ p->cur_speed = 1.0;
+
+ p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
+ p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ return f;
+}
diff --git a/filters/f_auto_filters.h b/filters/f_auto_filters.h
index 5f1a99f636..98043c9301 100644
--- a/filters/f_auto_filters.h
+++ b/filters/f_auto_filters.h
@@ -8,3 +8,6 @@ struct mp_filter *mp_deint_create(struct mp_filter *parent);
// Rotate according to mp_image.rotate and VO capabilities.
struct mp_filter *mp_autorotate_create(struct mp_filter *parent);
+
+// Insert a filter that inserts scaletempo depending on speed settings.
+struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent);
diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c
index 687a846ae5..ce9d82cbc2 100644
--- a/filters/f_autoconvert.c
+++ b/filters/f_autoconvert.c
@@ -1,5 +1,8 @@
#include "config.h"
+#include "audio/aframe.h"
+#include "audio/chmap_sel.h"
+#include "audio/format.h"
#include "common/common.h"
#include "common/msg.h"
#include "video/hwdec.h"
@@ -7,6 +10,7 @@
#include "f_autoconvert.h"
#include "f_hwtransfer.h"
+#include "f_swresample.h"
#include "f_swscale.h"
#include "f_utils.h"
#include "filter.h"
@@ -29,6 +33,18 @@ struct priv {
// sws state
int in_imgfmt, in_subfmt;
+ int *afmts;
+ int num_afmts;
+ int *srates;
+ int num_srates;
+ struct mp_chmap_sel chmaps;
+
+ int in_afmt, in_srate;
+ struct mp_chmap in_chmap;
+
+ double audio_speed;
+ bool resampling_forced;
+
struct mp_autoconvert public;
};
@@ -56,6 +72,10 @@ void mp_autoconvert_clear(struct mp_autoconvert *c)
struct priv *p = c->f->priv;
p->num_imgfmts = 0;
+ p->num_afmts = 0;
+ p->num_srates = 0;
+ p->chmaps = (struct mp_chmap_sel){0};
+ p->force_update = true;
}
void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt)
@@ -110,6 +130,33 @@ void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
p->vo_convert = true;
}
+void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt)
+{
+ struct priv *p = c->f->priv;
+
+ MP_TARRAY_APPEND(p, p->afmts, p->num_afmts, afmt);
+ p->force_update = true;
+}
+
+void mp_autoconvert_add_chmap(struct mp_autoconvert *c, struct mp_chmap *chmap)
+{
+ struct priv *p = c->f->priv;
+
+ mp_chmap_sel_add_map(&p->chmaps, chmap);
+ p->force_update = true;
+}
+
+void mp_autoconvert_add_srate(struct mp_autoconvert *c, int rate)
+{
+ struct priv *p = c->f->priv;
+
+ MP_TARRAY_APPEND(p, p->srates, p->num_srates, rate);
+ // Some other API we call expects a 0-terminated sample rates array.
+ MP_TARRAY_GROW(p, p->srates, p->num_srates);
+ p->srates[p->num_srates] = 0;
+ p->force_update = true;
+}
+
static void handle_video_frame(struct mp_filter *f)
{
struct priv *p = f->priv;
@@ -227,6 +274,94 @@ static void handle_video_frame(struct mp_filter *f)
mp_subfilter_continue(&p->sub);
}
+static void handle_audio_frame(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ struct mp_frame frame = p->sub.frame;
+ if (frame.type != MP_FRAME_AUDIO) {
+ MP_ERR(p, "audio input required!\n");
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+
+ struct mp_aframe *aframe = frame.data;
+
+ int afmt = mp_aframe_get_format(aframe);
+ int srate = mp_aframe_get_rate(aframe);
+ struct mp_chmap chmap = {0};
+ mp_aframe_get_chmap(aframe, &chmap);
+
+ if (afmt == p->in_afmt && srate == p->in_srate &&
+ mp_chmap_equals(&chmap, &p->in_chmap) &&
+ (!p->resampling_forced || p->sub.filter) &&
+ !p->force_update)
+ {
+ goto cont;
+ }
+
+ if (!mp_subfilter_drain_destroy(&p->sub))
+ return;
+
+ p->in_afmt = afmt;
+ p->in_srate = srate;
+ p->in_chmap = chmap;
+ p->force_update = false;
+
+ int out_afmt = 0;
+ int best_score = 0;
+ for (int n = 0; n < p->num_afmts; n++) {
+ int score = af_format_conversion_score(p->afmts[n], afmt);
+ if (!out_afmt || score > best_score) {
+ best_score = score;
+ out_afmt = p->afmts[n];
+ }
+ }
+ if (!out_afmt)
+ out_afmt = afmt;
+
+ // (The p->srates array is 0-terminated already.)
+ int out_srate = af_select_best_samplerate(srate, p->srates);
+ if (out_srate <= 0)
+ out_srate = p->num_srates ? p->srates[0] : srate;
+
+ struct mp_chmap out_chmap = chmap;
+ if (p->chmaps.num_chmaps) {
+ if (!mp_chmap_sel_adjust(&p->chmaps, &out_chmap))
+ out_chmap = p->chmaps.chmaps[0]; // violently force fallback
+ }
+
+ if (out_afmt == p->in_afmt && out_srate == p->in_srate &&
+ mp_chmap_equals(&out_chmap, &p->in_chmap) && !p->resampling_forced)
+ {
+ goto cont;
+ }
+
+ MP_VERBOSE(p, "inserting resampler\n");
+
+ struct mp_swresample *s = mp_swresample_create(f, NULL);
+ if (!s)
+ abort();
+
+ s->out_format = out_afmt;
+ s->out_rate = out_srate;
+ s->out_channels = out_chmap;
+
+ p->sub.filter = s->f;
+
+cont:
+
+ if (p->sub.filter) {
+ struct mp_filter_command cmd = {
+ .type = MP_FILTER_COMMAND_SET_SPEED_RESAMPLE,
+ .speed = p->audio_speed,
+ };
+ mp_filter_command(p->sub.filter, &cmd);
+ }
+
+ mp_subfilter_continue(&p->sub);
+}
+
static void process(struct mp_filter *f)
{
struct priv *p = f->priv;
@@ -241,11 +376,33 @@ static void process(struct mp_filter *f)
handle_video_frame(f);
return;
}
+ if (p->num_afmts || p->num_srates || p->chmaps.num_chmaps ||
+ p->resampling_forced)
+ {
+ handle_audio_frame(f);
+ return;
+ }
}
mp_subfilter_continue(&p->sub);
}
+static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
+{
+ struct priv *p = f->priv;
+
+ if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) {
+ p->audio_speed = cmd->speed;
+ // If we needed resampling once, keep forcing resampling, as it might be
+ // quickly changing between 1.0 and other values for A/V compensation.
+ if (p->audio_speed != 1.0)
+ p->resampling_forced = true;
+ return true;
+ }
+
+ return false;
+}
+
static void reset(struct mp_filter *f)
{
struct priv *p = f->priv;
@@ -265,6 +422,7 @@ static const struct mp_filter_info autoconvert_filter = {
.name = "autoconvert",
.priv_size = sizeof(struct priv),
.process = process,
+ .command = command,
.reset = reset,
.destroy = destroy,
};
@@ -281,6 +439,7 @@ struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent)
struct priv *p = f->priv;
p->public.f = f;
p->log = f->log;
+ p->audio_speed = 1.0;
p->sub.in = f->ppins[0];
p->sub.out = f->ppins[1];
diff --git a/filters/f_autoconvert.h b/filters/f_autoconvert.h
index 72af21a0df..77e07aecf1 100644
--- a/filters/f_autoconvert.h
+++ b/filters/f_autoconvert.h
@@ -29,6 +29,17 @@ struct mp_hwdec_devices;
void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
struct mp_hwdec_devices *devs);
+// Add afmt (an AF_FORMAT_* value) as allowed audio format.
+// See mp_autoconvert_add_imgfmt() for other remarks.
+void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt);
+
+// Add allowed audio channel configuration.
+struct mp_chmap;
+void mp_autoconvert_add_chmap(struct mp_autoconvert *c, struct mp_chmap *chmap);
+
+// Add allowed audio sample rate.
+void mp_autoconvert_add_srate(struct mp_autoconvert *c, int rate);
+
// Reset set of allowed formats back to initial state. (This does not flush
// any frames or remove currently active filters, although to get reasonable
// behavior, you need to readd all previously allowed formats, or reset the
diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c
index a97f126efb..b3f74b508b 100644
--- a/filters/f_lavfi.c
+++ b/filters/f_lavfi.c
@@ -71,6 +71,8 @@ struct lavfi {
// linked.
bool initialized;
+ bool warned_nospeed;
+
// Graph is draining to either handle format changes (if input format
// changes for one pad, recreate the graph after draining all buffered
// frames), or undo previously sent EOF (libavfilter does not accept
@@ -597,6 +599,15 @@ static bool feed_input_pads(struct lavfi *c)
continue;
}
+ if (pad->pending.type == MP_FRAME_AUDIO && !c->warned_nospeed) {
+ struct mp_aframe *aframe = pad->pending.data;
+ if (mp_aframe_get_speed(aframe) != 1.0) {
+ MP_ERR(c, "speed changing filters before libavfilter are not "
+ "supported and can cause desyncs\n");
+ c->warned_nospeed = true;
+ }
+ }
+
AVFrame *frame = mp_frame_to_av(pad->pending, &pad->timebase);
bool eof = pad->pending.type == MP_FRAME_EOF;
@@ -853,6 +864,7 @@ struct mp_lavfi *mp_lavfi_create_filter(struct mp_filter *parent,
struct lavfi_user_opts {
bool is_bridge;
+ enum mp_frame_type type;
char *graph;
char **avopts;
@@ -861,62 +873,109 @@ struct lavfi_user_opts {
char **filter_opts;
};
-static struct mp_filter *vf_lavfi_create(struct mp_filter *parent, void *options)
+static struct mp_filter *lavfi_create(struct mp_filter *parent, void *options)
{
struct lavfi_user_opts *opts = options;
struct mp_lavfi *l;
if (opts->is_bridge) {
- l = mp_lavfi_create_filter(parent, MP_FRAME_VIDEO, true,
- opts->avopts, opts->filter_name,
- opts->filter_opts);
+ l = mp_lavfi_create_filter(parent, opts->type, true, opts->avopts,
+ opts->filter_name, opts->filter_opts);
} else {
- l = mp_lavfi_create_graph(parent, MP_FRAME_VIDEO, true,
+ l = mp_lavfi_create_graph(parent, opts->type, true,
opts->avopts, opts->graph);
}
talloc_free(opts);
return l ? l->f : NULL;
}
-static bool is_single_video_only(const AVFilterPad *pads)
+static bool is_single_media_only(const AVFilterPad *pads, int media_type)
{
int count = avfilter_pad_count(pads);
if (count != 1)
return false;
- return avfilter_pad_get_type(pads, 0) == AVMEDIA_TYPE_VIDEO;
+ return avfilter_pad_get_type(pads, 0) == media_type;
}
// Does it have exactly one video input and one video output?
-static bool is_usable(const AVFilter *filter)
+static bool is_usable(const AVFilter *filter, int media_type)
{
- return is_single_video_only(filter->inputs) &&
- is_single_video_only(filter->outputs);
+ return is_single_media_only(filter->inputs, media_type) &&
+ is_single_media_only(filter->outputs, media_type);
}
-static void print_help(struct mp_log *log)
+static void print_help(struct mp_log *log, int mediatype, char *name, char *ex)
{
mp_info(log, "List of libavfilter filters:\n");
for (const AVFilter *filter = avfilter_next(NULL); filter;
filter = avfilter_next(filter))
{
- if (is_usable(filter))
+ if (is_usable(filter, mediatype))
mp_info(log, " %-16s %s\n", filter->name, filter->description);
}
mp_info(log, "\n"
- "This lists video->video filters only. Refer to\n"
+ "This lists %s->%s filters only. Refer to\n"
"\n"
" https://ffmpeg.org/ffmpeg-filters.html\n"
"\n"
"to see how to use each filter and what arguments each filter takes.\n"
"Also, be sure to quote the FFmpeg filter string properly, e.g.:\n"
"\n"
- " \"--vf=lavfi=[gradfun=20:30]\"\n"
+ " \"%s\"\n"
"\n"
"Otherwise, mpv and libavfilter syntax will conflict.\n"
- "\n");
+ "\n", name, name, ex);
+}
+
+static void print_help_v(struct mp_log *log)
+{
+ print_help(log, AVMEDIA_TYPE_VIDEO, "video", "--vf=lavfi=[gradfun=20:30]");
+}
+
+static void print_help_a(struct mp_log *log)
+{
+ print_help(log, AVMEDIA_TYPE_AUDIO, "audio", "--af=lavfi=[volume=0.5]");
}
#define OPT_BASE_STRUCT struct lavfi_user_opts
+const struct mp_user_filter_entry af_lavfi = {
+ .desc = {
+ .description = "libavfilter bridge",
+ .name = "lavfi",
+ .priv_size = sizeof(OPT_BASE_STRUCT),
+ .options = (const m_option_t[]){
+ OPT_STRING("graph", graph, M_OPT_MIN, .min = 1),
+ OPT_KEYVALUELIST("o", avopts, 0),
+ {0}
+ },
+ .priv_defaults = &(const OPT_BASE_STRUCT){
+ .type = MP_FRAME_AUDIO,
+ },
+ .print_help = print_help_a,
+ },
+ .create = lavfi_create,
+};
+
+const struct mp_user_filter_entry af_lavfi_bridge = {
+ .desc = {
+ .description = "libavfilter bridge (explicit options)",
+ .name = "lavfi-bridge",
+ .priv_size = sizeof(OPT_BASE_STRUCT),
+ .options = (const m_option_t[]){
+ OPT_STRING("name", filter_name, M_OPT_MIN, .min = 1),
+ OPT_KEYVALUELIST("opts", filter_opts, 0),
+ OPT_KEYVALUELIST("o", avopts, 0),
+ {0}
+ },
+ .priv_defaults = &(const OPT_BASE_STRUCT){
+ .is_bridge = true,
+ .type = MP_FRAME_AUDIO,
+ },
+ .print_help = print_help_a,
+ },
+ .create = lavfi_create,
+};
+
const struct mp_user_filter_entry vf_lavfi = {
.desc = {
.description = "libavfilter bridge",
@@ -927,9 +986,12 @@ const struct mp_user_filter_entry vf_lavfi = {
OPT_KEYVALUELIST("o", avopts, 0),
{0}
},
- .print_help = print_help,
+ .priv_defaults = &(const OPT_BASE_STRUCT){
+ .type = MP_FRAME_VIDEO,
+ },
+ .print_help = print_help_v,
},
- .create = vf_lavfi_create,
+ .create = lavfi_create,
};
const struct mp_user_filter_entry vf_lavfi_bridge = {
@@ -945,8 +1007,9 @@ const struct mp_user_filter_entry vf_lavfi_bridge = {
},
.priv_defaults = &(const OPT_BASE_STRUCT){
.is_bridge = true,
+ .type = MP_FRAME_VIDEO,
},
- .print_help = print_help,
+ .print_help = print_help_v,
},
- .create = vf_lavfi_create,
+ .create = lavfi_create,
};
diff --git a/filters/f_output_chain.c b/filters/f_output_chain.c
index d98c7ca4b3..3cbbaa2ccb 100644
--- a/filters/f_output_chain.c
+++ b/filters/f_output_chain.c
@@ -1,3 +1,5 @@
+#include "audio/aframe.h"
+#include "audio/out/ao.h"
#include "common/global.h"
#include "options/m_config.h"
#include "options/m_option.h"
@@ -41,6 +43,22 @@ struct chain {
struct mp_autoconvert *convert;
struct vo *vo;
+ struct ao *ao;
+
+ struct mp_frame pending_input;
+
+ // Some chain types (MP_OUTPUT_CHAIN_AUDIO) require draining the entire
+ // filter chain on format changes and further complex actions:
+ // 0: normal filtering
+ // 1: input changed, flushing out remaining frames from current filters
+ // 2: flushing finished
+ // 3: sent new frame through chain for format probing
+ // 4: sent EOF through chain for format probing
+ // 5: received format probing frame; now waiting for API user to call
+ // mp_output_chain_set_ao().
+ int format_change_phase;
+ // True if it's a second run trying to see if downmix can be moved up.
+ bool format_change_second_try;
struct mp_output_chain public;
};
@@ -60,13 +78,20 @@ struct mp_user_filter {
char *name;
bool is_output_converter;
bool is_input;
+ bool is_channelremix;
struct mp_image_params last_out_params;
+ struct mp_aframe *last_out_aformat;
+
+ int64_t last_in_pts, last_out_pts;
bool failed;
bool error_eof_sent;
};
+static void recheck_channelremix_filter(struct chain *p);
+static void remove_channelremix_filter(struct chain *p);
+
static void update_output_caps(struct chain *p)
{
if (p->type != MP_OUTPUT_CHAIN_VIDEO)
@@ -119,6 +144,25 @@ static bool check_out_format_change(struct mp_user_filter *u,
}
}
+ if (frame.type == MP_FRAME_AUDIO) {
+ struct mp_aframe *aframe = frame.data;
+
+ if (!mp_aframe_config_equals(aframe, u->last_out_aformat)) {
+ MP_VERBOSE(p, "[%s] %s\n", u->name,
+ mp_aframe_format_str(aframe));
+ mp_aframe_config_copy(u->last_out_aformat, aframe);
+
+ if (u->is_input) {
+ mp_aframe_config_copy(p->public.input_aformat, aframe);
+ } else if (u->is_output_converter) {
+ mp_aframe_config_copy(p->public.output_aformat, aframe);
+ }
+
+ p->public.reconfig_happened = true;
+ changed = true;
+ }
+ }
+
return changed;
}
@@ -137,12 +181,15 @@ static void process_user(struct mp_filter *f)
MP_FATAL(p, "Cannot convert decoder/filter output to any format "
"supported by the output.\n");
p->public.failed_output_conversion = true;
+ p->format_change_phase = 0;
mp_filter_wakeup(p->f);
} else {
MP_ERR(p, "Disabling filter %s because it has failed.\n", name);
mp_filter_reset(u->f); // clear out staled buffered data
}
u->failed = true;
+ if (p->format_change_phase)
+ p->format_change_phase = 2; // redo without it
}
if (u->failed) {
@@ -159,12 +206,35 @@ static void process_user(struct mp_filter *f)
return;
}
- mp_pin_transfer_data(u->f->pins[0], f->ppins[0]);
+ if (mp_pin_can_transfer_data(u->f->pins[0], f->ppins[0])) {
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+
+ double pts = mp_frame_get_pts(frame);
+ if (pts != MP_NOPTS_VALUE)
+ u->last_in_pts = pts;
+
+ mp_pin_in_write(u->f->pins[0], frame);
+ }
if (mp_pin_can_transfer_data(f->ppins[1], u->f->pins[1])) {
struct mp_frame frame = mp_pin_out_read(u->f->pins[1]);
- check_out_format_change(u, frame);
+ bool changed = check_out_format_change(u, frame);
+ if (p->type == MP_OUTPUT_CHAIN_AUDIO && (!p->ao || changed) &&
+ u->is_input && !p->format_change_phase)
+ {
+ // Format changed -> block filtering, start draining current filters.
+ MP_VERBOSE(p, "format changed, draining filter chain\n");
+ mp_frame_unref(&p->pending_input);
+ p->pending_input = frame;
+ p->format_change_phase = 1;
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ return;
+ }
+
+ double pts = mp_frame_get_pts(frame);
+ if (pts != MP_NOPTS_VALUE)
+ u->last_out_pts = pts;
mp_pin_in_write(f->ppins[1], frame);
}
@@ -175,6 +245,7 @@ static void reset_user(struct mp_filter *f)
struct mp_user_filter *u = f->priv;
u->error_eof_sent = false;
+ u->last_in_pts = u->last_out_pts = MP_NOPTS_VALUE;
}
static void destroy_user(struct mp_filter *f)
@@ -203,6 +274,7 @@ static struct mp_user_filter *create_wrapper_filter(struct chain *p)
struct mp_user_filter *wrapper = f->priv;
wrapper->wrapper = f;
wrapper->p = p;
+ wrapper->last_out_aformat = talloc_steal(wrapper, mp_aframe_create());
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
return wrapper;
@@ -237,10 +309,100 @@ static void relink_filter_list(struct chain *p)
}
}
+// Special logic for draining on format changes (for audio). Never used or
+// initiated video.
+static void process_format_change(struct mp_filter *f)
+{
+ struct chain *p = f->priv;
+
+ if (mp_pin_in_needs_data(p->filters_in)) {
+ if (p->format_change_phase == 2) {
+ MP_VERBOSE(p, "probing new format\n");
+ // Clear any old state.
+ if (!p->format_change_second_try) {
+ mp_autoconvert_clear(p->convert);
+ remove_channelremix_filter(p);
+ }
+ for (int n = 0; n < p->num_all_filters; n++)
+ mp_filter_reset(p->all_filters[n]->f);
+ // Filter a copy of the new input frame to see what comes out.
+ struct mp_frame frame = mp_frame_ref(p->pending_input);
+ if (!frame.type)
+ abort();
+ mp_pin_in_write(p->filters_in, frame);
+ mp_pin_out_request_data(p->filters_out);
+ p->format_change_phase = 3;
+ } else if (p->format_change_phase == 3) {
+ MP_VERBOSE(p, "probing new format (drain)\n");
+ mp_pin_in_write(p->filters_in, MP_EOF_FRAME);
+ p->format_change_phase = 4;
+ }
+ }
+
+ if (mp_pin_can_transfer_data(f->ppins[1], p->filters_out)) {
+ struct mp_frame frame = mp_pin_out_read(p->filters_out);
+
+ if (frame.type == MP_FRAME_EOF) {
+ // We're apparently draining for a format change, and we got EOF
+ // from the chain, which means we're done draining.
+ if (p->format_change_phase == 1) {
+ MP_VERBOSE(p, "done format change draining\n");
+ // Then we need to start probing the new format.
+ p->format_change_phase = 2;
+ mp_pin_out_request_data(p->filters_out);
+ } else if (!p->public.failed_output_conversion) {
+ MP_ERR(p, "we didn't get an output frame? (broken filter?)\n");
+ }
+ mp_filter_internal_mark_progress(f);
+ return;
+ }
+
+ if (p->format_change_phase >= 2) {
+ // We were filtering a "test" frame to probe the format. Now
+ // that we have it (apparently), just discard it, and make the
+ // user aware of the previously grabbed format.
+ MP_VERBOSE(p, "got output format from probing\n");
+ mp_frame_unref(&frame);
+ for (int n = 0; n < p->num_all_filters; n++)
+ mp_filter_reset(p->all_filters[n]->f);
+ if (p->format_change_second_try) {
+ p->format_change_second_try = false;
+ p->format_change_phase = 0;
+ recheck_channelremix_filter(p);
+ } else {
+ p->ao = NULL;
+ p->public.ao_needs_update = true;
+ p->format_change_phase = 5;
+ }
+ // Do something silly to ensure the f_output_chain user gets
+ // notified properly.
+ mp_filter_wakeup(f);
+ return;
+ }
+
+ // Draining remaining data.
+ mp_pin_in_write(f->ppins[1], frame);
+ }
+}
+
+
static void process(struct mp_filter *f)
{
struct chain *p = f->priv;
+ if (p->format_change_phase) {
+ process_format_change(f);
+ return;
+ }
+
+ // Send remaining input from previous format change.
+ if (p->pending_input.type) {
+ if (mp_pin_in_needs_data(p->filters_in)) {
+ mp_pin_in_write(p->filters_in, p->pending_input);
+ p->pending_input = MP_NO_FRAME;
+ }
+ }
+
if (mp_pin_can_transfer_data(p->filters_in, f->ppins[0])) {
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
@@ -268,6 +430,14 @@ static void reset(struct mp_filter *f)
{
struct chain *p = f->priv;
+ // (if format initialization was in progress, this can be repeated next time)
+ mp_frame_unref(&p->pending_input);
+ p->format_change_phase = 0;
+ if (p->format_change_second_try)
+ remove_channelremix_filter(p);
+ p->format_change_second_try = false;
+ p->public.ao_needs_update = false;
+
p->public.got_input_eof = false;
p->public.got_output_eof = false;
}
@@ -322,6 +492,120 @@ void mp_output_chain_set_vo(struct mp_output_chain *c, struct vo *vo)
update_output_caps(p);
}
+// If there are any user filters, and they don't affect the channel config,
+// then move upmix/downmix to the start of the chain.
+static void maybe_move_up_channelremix(struct chain *p, struct mp_chmap *final)
+{
+ assert(p->num_all_filters >= 2); // at least in/convert filters
+ struct mp_user_filter *first = p->all_filters[0]; // "in" pseudo filter
+ struct mp_chmap in = {0};
+ mp_aframe_get_chmap(first->last_out_aformat, &in);
+ if (mp_chmap_is_unknown(&in))
+ return;
+ mp_chmap_reorder_to_lavc(&in);
+ if (!mp_chmap_is_valid(&in) || mp_chmap_equals_reordered(&in, final))
+ return;
+ for (int n = 0; n < p->num_all_filters; n++) {
+ struct mp_user_filter *u = p->all_filters[n];
+ struct mp_chmap chmap = {0};
+ mp_aframe_get_chmap(u->last_out_aformat, &chmap);
+ if (!mp_chmap_equals_reordered(&in, &chmap))
+ return; // some remix going in
+ }
+
+ if (!p->num_user_filters)
+ return; // would be a NOP
+
+ MP_VERBOSE(p, "trying with channel remixing moved to start of chain\n");
+
+ struct mp_user_filter *remix = create_wrapper_filter(p);
+ struct mp_autoconvert *convert = mp_autoconvert_create(remix->wrapper);
+ if (!convert)
+ abort();
+ mp_autoconvert_add_chmap(convert, final);
+ remix->name = "channelremix";
+ remix->f = convert->f;
+ remix->is_channelremix = true;
+ MP_TARRAY_APPEND(p, p->pre_filters, p->num_pre_filters, remix);
+ relink_filter_list(p);
+
+ // now run the scary state machine again in order to see what filters do
+ // with the remixed channel data and if it was a good idea
+ p->format_change_phase = 2;
+ p->format_change_second_try = true;
+}
+
+static void remove_channelremix_filter(struct chain *p)
+{
+ for (int n = 0; n < p->num_pre_filters; n++) {
+ struct mp_user_filter *u = p->pre_filters[n];
+ if (u->is_channelremix) {
+ MP_TARRAY_REMOVE_AT(p->pre_filters, p->num_pre_filters, n);
+ talloc_free(u->wrapper);
+ relink_filter_list(p);
+ break;
+ }
+ }
+}
+
+static void recheck_channelremix_filter(struct chain *p)
+{
+ struct mp_chmap in = {0};
+ int start = -1;
+ for (int n = 0; n < p->num_all_filters; n++) {
+ struct mp_user_filter *u = p->all_filters[n];
+ if (u->is_channelremix) {
+ mp_aframe_get_chmap(u->last_out_aformat, &in);
+ start = n;
+ break;
+ }
+ }
+
+ if (start < 0 || !mp_chmap_is_valid(&in))
+ goto remove;
+
+ for (int n = start; n < p->num_all_filters; n++) {
+ struct mp_user_filter *u = p->all_filters[n];
+ struct mp_chmap chmap = {0};
+ mp_aframe_get_chmap(u->last_out_aformat, &chmap);
+ if (!mp_chmap_equals_reordered(&in, &chmap))
+ goto remove;
+ }
+
+ return;
+remove:
+ MP_VERBOSE(p, "reverting moved up channel remixing\n");
+ remove_channelremix_filter(p);
+}
+
+void mp_output_chain_set_ao(struct mp_output_chain *c, struct ao *ao)
+{
+ struct chain *p = c->f->priv;
+
+ assert(p->public.ao_needs_update); // can't just call it any time
+ assert(p->format_change_phase == 5);
+ assert(!p->format_change_second_try);
+
+ p->public.ao_needs_update = false;
+ p->format_change_phase = 0;
+
+ p->ao = ao;
+
+ int out_format = 0;
+ int out_rate = 0;
+ struct mp_chmap out_channels = {0};
+ ao_get_format(p->ao, &out_rate, &out_format, &out_channels);
+
+ mp_autoconvert_clear(p->convert);
+ mp_autoconvert_add_afmt(p->convert, out_format);
+ mp_autoconvert_add_srate(p->convert, out_rate);
+ mp_autoconvert_add_chmap(p->convert, &out_channels);
+
+ maybe_move_up_channelremix(p, &out_channels);
+
+ mp_filter_wakeup(p->f);
+}
+
static struct mp_user_filter *find_by_label(struct chain *p, const char *label)
{
for (int n = 0; n < p->num_user_filters; n++) {
@@ -354,6 +638,57 @@ bool mp_output_chain_command(struct mp_output_chain *c, const char *target,
return mp_filter_command(f->f, cmd);
}
+// Set the speed on the last filter in the chain that supports it. If a filter
+// supports it, reset *speed, then keep setting the speed on the other filters.
+// The purpose of this is to make sure only 1 filter changes speed.
+static void set_speed_any(struct mp_user_filter **filters, int num_filters,
+ bool resample, double *speed)
+{
+ for (int n = num_filters - 1; n >= 0; n--) {
+ assert(*speed);
+ struct mp_filter_command cmd = {
+ .type = resample ? MP_FILTER_COMMAND_SET_SPEED_RESAMPLE
+ : MP_FILTER_COMMAND_SET_SPEED,
+ .speed = *speed,
+ };
+ if (mp_filter_command(filters[n]->f, &cmd))
+ *speed = 1.0;
+ }
+}
+
+void mp_output_chain_set_audio_speed(struct mp_output_chain *c,
+ double speed, double resample)
+{
+ struct chain *p = c->f->priv;
+
+ // We always resample with the final libavresample instance.
+ set_speed_any(p->post_filters, p->num_post_filters, true, &resample);
+
+ // If users have filters like "scaletempo" insert anywhere, use that,
+ // otherwise use the builtin ones.
+ set_speed_any(p->user_filters, p->num_user_filters, false, &speed);
+ set_speed_any(p->post_filters, p->num_post_filters, false, &speed);
+}
+
+double mp_output_get_measured_total_delay(struct mp_output_chain *c)
+{
+ struct chain *p = c->f->priv;
+
+ double delay = 0;
+
+ for (int n = 0; n < p->num_all_filters; n++) {
+ struct mp_user_filter *u = p->all_filters[n];
+
+ if (u->last_in_pts != MP_NOPTS_VALUE &&
+ u->last_out_pts != MP_NOPTS_VALUE)
+ {
+ delay += u->last_in_pts - u->last_out_pts;
+ }
+ }
+
+ return delay;
+}
+
static bool compare_filter(struct m_obj_settings *a, struct m_obj_settings *b)
{
if (a == b || !a || !b)
@@ -509,6 +844,18 @@ static void create_video_things(struct chain *p)
MP_TARRAY_APPEND(p, p->post_filters, p->num_post_filters, f);
}
+static void create_audio_things(struct chain *p)
+{
+ p->frame_type = MP_FRAME_AUDIO;
+
+ struct mp_user_filter *f = create_wrapper_filter(p);
+ f->name = "userspeed";
+ f->f = mp_autoaspeed_create(f->wrapper);
+ if (!f->f)
+ abort();
+ MP_TARRAY_APPEND(p, p->post_filters, p->num_post_filters, f);
+}
+
struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent,
enum mp_output_chain_type type)
{
@@ -522,6 +869,7 @@ struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent,
const char *log_name = NULL;
switch (type) {
case MP_OUTPUT_CHAIN_VIDEO: log_name = "!vf"; break;
+ case MP_OUTPUT_CHAIN_AUDIO: log_name = "!af"; break;
}
if (log_name)
f->log = mp_log_new(f, parent->global->log, log_name);
@@ -533,6 +881,8 @@ struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent,
struct mp_output_chain *c = &p->public;
c->f = f;
+ c->input_aformat = talloc_steal(p, mp_aframe_create());
+ c->output_aformat = talloc_steal(p, mp_aframe_create());
// Dummy filter for reporting and logging the input format.
p->input = create_wrapper_filter(p);
@@ -545,6 +895,7 @@ struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent,
switch (type) {
case MP_OUTPUT_CHAIN_VIDEO: create_video_things(p); break;
+ case MP_OUTPUT_CHAIN_AUDIO: create_audio_things(p); break;
}
p->output = create_wrapper_filter(p);
diff --git a/filters/f_output_chain.h b/filters/f_output_chain.h
index 64667ed1bd..246f84181f 100644
--- a/filters/f_output_chain.h
+++ b/filters/f_output_chain.h
@@ -7,6 +7,7 @@
enum mp_output_chain_type {
MP_OUTPUT_CHAIN_VIDEO = 1, // --vf
+ MP_OUTPUT_CHAIN_AUDIO, // --af
};
// A classic single-media filter chain, which reflects --vf and --af.
@@ -31,6 +32,15 @@ struct mp_output_chain {
struct mp_image_params input_params;
struct mp_image_params output_params;
double container_fps;
+
+ // --- for type==MP_OUTPUT_CHAIN_AUDIO
+ struct mp_aframe *input_aformat;
+ struct mp_aframe *output_aformat;
+ // If true, there was a format change. output_aformat might have changed,
+ // and the implementation drained the filter chain and unset the internal ao
+ // reference. The API user needs to call mp_output_chain_set_ao() again.
+ // Until this is done, the filter chain will not output new data.
+ bool ao_needs_update;
};
// (free by freeing mp_output_chain.f)
@@ -43,6 +53,13 @@ struct mp_output_chain *mp_output_chain_create(struct mp_filter *parent,
struct vo;
void mp_output_chain_set_vo(struct mp_output_chain *p, struct vo *vo);
+// Set the AO. The AO format will be used to determine the filter chain output.
+// The API user may be asked to update the AO midstream if ao_needs_update is
+// set.
+// For type==MP_OUTPUT_CHAIN_AUDIO only.
+struct ao;
+void mp_output_chain_set_ao(struct mp_output_chain *p, struct ao *ao);
+
// Send a command to the filter with the target label.
bool mp_output_chain_command(struct mp_output_chain *p, const char *target,
struct mp_filter_command *cmd);
@@ -57,3 +74,13 @@ struct m_obj_settings;
bool mp_output_chain_update_filters(struct mp_output_chain *p,
struct m_obj_settings *list);
+// Desired audio speed, with resample being strict resampling.
+void mp_output_chain_set_audio_speed(struct mp_output_chain *p,
+ double speed, double resample);
+
+// Total delay incured by the filter chain, as measured by the recent filtered
+// frames. The intention is that this sums the measured delays for each filter,
+// so if a filter is removed, the caller can estimate how much audio is missing
+// due to the change.
+// Makes sense for audio only.
+double mp_output_get_measured_total_delay(struct mp_output_chain *p);
diff --git a/audio/aconverter.c b/filters/f_swresample.c
index 2475df878d..48bd08d847 100644
--- a/audio/aconverter.c
+++ b/filters/f_swresample.c
@@ -23,15 +23,17 @@
#include "config.h"
+#include "audio/aframe.h"
+#include "audio/fmt-conversion.h"
+#include "audio/format.h"
#include "common/common.h"
#include "common/av_common.h"
#include "common/msg.h"
#include "options/m_config.h"
#include "options/m_option.h"
-#include "aconverter.h"
-#include "aframe.h"
-#include "fmt-conversion.h"
-#include "format.h"
+
+#include "f_swresample.h"
+#include "filter_internal.h"
#define HAVE_LIBSWRESAMPLE (!HAVE_LIBAV)
#define HAVE_LIBAVRESAMPLE HAVE_LIBAV
@@ -54,18 +56,15 @@
#error "config.h broken or no resampler found"
#endif
-struct mp_aconverter {
+struct priv {
struct mp_log *log;
- struct mpv_global *global;
- double playback_speed;
bool is_resampling;
- bool passthrough_mode;
struct AVAudioResampleContext *avrctx;
struct mp_aframe *avrctx_fmt; // output format of avrctx
struct mp_aframe *pool_fmt; // format used to allocate frames for avrctx output
struct mp_aframe *pre_out_fmt; // format before final conversion
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
- const struct mp_resample_opts *opts; // opts requested by the user
+ struct mp_resample_opts *opts; // opts requested by the user
// At least libswresample keeps a pointer around for this:
int reorder_in[MP_NUM_CHANNELS];
int reorder_out[MP_NUM_CHANNELS];
@@ -80,14 +79,16 @@ struct mp_aconverter {
int out_format;
struct mp_chmap out_channels;
- struct mp_aframe *input; // queued input frame
- bool input_eof; // queued input EOF
- struct mp_aframe *output; // queued output frame
- bool output_eof; // queued output EOF
+ double current_pts;
+
+ double cmd_speed;
+ double speed;
+
+ struct mp_swresample public;
};
#define OPT_BASE_STRUCT struct mp_resample_opts
-const struct m_sub_options resample_config = {
+const struct m_sub_options resample_conf = {
.opts = (const m_option_t[]) {
OPT_INTRANGE("audio-resample-filter-size", filter_size, 0, 0, 32),
OPT_INTRANGE("audio-resample-phase-shift", phase_shift, 0, 0, 30),
@@ -104,28 +105,28 @@ const struct m_sub_options resample_config = {
};
#if HAVE_LIBAVRESAMPLE
-static double get_delay(struct mp_aconverter *p)
+static double get_delay(struct priv *p)
{
return avresample_get_delay(p->avrctx) / (double)p->in_rate +
avresample_available(p->avrctx) / (double)p->out_rate;
}
-static int get_out_samples(struct mp_aconverter *p, int in_samples)
+static int get_out_samples(struct priv *p, int in_samples)
{
return avresample_get_out_samples(p->avrctx, in_samples);
}
#else
-static double get_delay(struct mp_aconverter *p)
+static double get_delay(struct priv *p)
{
int64_t base = p->in_rate * (int64_t)p->out_rate;
return swr_get_delay(p->avrctx, base) / (double)base;
}
-static int get_out_samples(struct mp_aconverter *p, int in_samples)
+static int get_out_samples(struct priv *p, int in_samples)
{
return swr_get_out_samples(p->avrctx, in_samples);
}
#endif
-static void close_lavrr(struct mp_aconverter *p)
+static void close_lavrr(struct priv *p)
{
if (p->avrctx)
avresample_close(p->avrctx);
@@ -156,7 +157,7 @@ static struct mp_chmap fudge_pairs[][2] = {
// strictly speaking incompatible channel pairs. For example, 7.1 should be
// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
// it to libswresample to create the remix matrix.)
-static uint64_t fudge_layout_conversion(struct mp_aconverter *p,
+static uint64_t fudge_layout_conversion(struct priv *p,
uint64_t in, uint64_t out)
{
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
@@ -191,19 +192,17 @@ static void transpose_order(int *map, int num)
memcpy(map, nmap, sizeof(nmap));
}
-static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
+static bool configure_lavrr(struct priv *p, bool verbose)
{
close_lavrr(p);
- p->in_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
+ p->in_rate = rate_from_speed(p->in_rate_user, p->speed);
- p->passthrough_mode = p->opts->allow_passthrough &&
- p->in_rate == p->out_rate &&
- p->in_format == p->out_format &&
- mp_chmap_equals(&p->in_channels, &p->out_channels);
-
- if (p->passthrough_mode)
- return true;
+ MP_VERBOSE(p, "%dHz %s %s -> %dHz %s %s\n",
+ p->in_rate, mp_chmap_to_str(&p->in_channels),
+ af_fmt_to_str(p->in_format),
+ p->out_rate, mp_chmap_to_str(&p->out_channels),
+ af_fmt_to_str(p->out_format));
p->avrctx = avresample_alloc_context();
p->avrctx_out = avresample_alloc_context();
@@ -217,7 +216,11 @@ static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
+ {
+ MP_ERR(p, "unsupported conversion: %s -> %s\n",
+ af_fmt_to_str(p->in_format), af_fmt_to_str(p->out_format));
goto error;
+ }
av_opt_set_int(p->avrctx, "filter_size", p->opts->filter_size, 0);
av_opt_set_int(p->avrctx, "phase_shift", p->opts->phase_shift, 0);
@@ -344,33 +347,17 @@ static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
error:
close_lavrr(p);
+ mp_filter_internal_mark_failed(p->public.f);
+ MP_FATAL(p, "libswresample failed to initialize.\n");
return false;
}
-bool mp_aconverter_reconfig(struct mp_aconverter *p,
- int in_rate, int in_format, struct mp_chmap in_channels,
- int out_rate, int out_format, struct mp_chmap out_channels)
+static void reset(struct mp_filter *f)
{
- close_lavrr(p);
-
- TA_FREEP(&p->input);
- TA_FREEP(&p->output);
- p->input_eof = p->output_eof = false;
+ struct priv *p = f->priv;
- p->playback_speed = 1.0;
+ p->current_pts = MP_NOPTS_VALUE;
- p->in_rate_user = in_rate;
- p->in_format = in_format;
- p->in_channels = in_channels;
- p->out_rate = out_rate;
- p->out_format = out_format;
- p->out_channels = out_channels;
-
- return configure_lavrr(p, true);
-}
-
-void mp_aconverter_flush(struct mp_aconverter *p)
-{
if (!p->avrctx)
return;
#if HAVE_LIBSWRESAMPLE
@@ -382,11 +369,6 @@ void mp_aconverter_flush(struct mp_aconverter *p)
#endif
}
-void mp_aconverter_set_speed(struct mp_aconverter *p, double speed)
-{
- p->playback_speed = speed;
-}
-
static void extra_output_conversion(struct mp_aframe *mpa)
{
int format = af_fmt_from_planar(mp_aframe_get_format(mpa));
@@ -460,7 +442,8 @@ static int resample_frame(struct AVAudioResampleContext *r,
av_i ? av_i->nb_samples : 0);
}
-static void filter_resample(struct mp_aconverter *p, struct mp_aframe *in)
+static struct mp_frame filter_resample_output(struct priv *p,
+ struct mp_aframe *in)
{
struct mp_aframe *out = NULL;
@@ -505,43 +488,122 @@ static void filter_resample(struct mp_aconverter *p, struct mp_aframe *in)
extra_output_conversion(out);
- if (in)
+ if (in) {
mp_aframe_copy_attributes(out, in);
+ p->current_pts = mp_aframe_end_pts(in);
+ }
if (out_samples) {
- p->output = out;
+ if (p->current_pts != MP_NOPTS_VALUE) {
+ double delay = get_delay(p) * mp_aframe_get_speed(out) +
+ mp_aframe_duration(out);
+ mp_aframe_set_pts(out, p->current_pts - delay);
+ mp_aframe_mul_speed(out, p->speed);
+ }
} else {
- talloc_free(out);
+ TA_FREEP(&out);
}
- p->output_eof = !in; // we've read everything
- return;
+ return out ? MAKE_FRAME(MP_FRAME_AUDIO, out) : MP_NO_FRAME;
error:
talloc_free(out);
MP_ERR(p, "Error on resampling.\n");
+ mp_filter_internal_mark_failed(p->public.f);
+ return MP_NO_FRAME;
}
-static void filter(struct mp_aconverter *p)
+static void process(struct mp_filter *f)
{
- if (p->output || p->output_eof || !(p->input || p->input_eof))
+ struct priv *p = f->priv;
+
+ if (!mp_pin_can_transfer_data(f->ppins[1], f->ppins[0]))
return;
- int new_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
+ p->speed = p->cmd_speed * p->public.speed;
- if (p->passthrough_mode && new_rate != p->in_rate)
- configure_lavrr(p, false);
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
- if (p->passthrough_mode) {
- p->output = p->input;
- p->input = NULL;
- p->output_eof = p->input_eof;
- p->input_eof = false;
+ struct mp_aframe *input = NULL;
+ if (frame.type == MP_FRAME_AUDIO) {
+ input = frame.data;
+ } else if (frame.type != MP_FRAME_EOF) {
+ MP_ERR(p, "Unsupported frame type.\n");
+ mp_frame_unref(&frame);
+ mp_filter_internal_mark_failed(f);
return;
}
+ if (!input && !p->avrctx) {
+ // Obviously no draining needed.
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ return;
+ }
+
+ if (input) {
+ struct mp_swresample *s = &p->public;
+
+ int in_rate = mp_aframe_get_rate(input);
+ int in_format = mp_aframe_get_format(input);
+ struct mp_chmap in_channels = {0};
+ mp_aframe_get_chmap(input, &in_channels);
+
+ if (!in_rate || !in_format || !in_channels.num) {
+ MP_ERR(p, "Frame with invalid format unsupported\n");
+ mp_frame_unref(&frame);
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+
+ int out_rate = s->out_rate ? s->out_rate : in_rate;
+ int out_format = s->out_format ? s->out_format : in_format;
+ struct mp_chmap out_channels =
+ s->out_channels.num ? s->out_channels : in_channels;
+
+ if (p->in_rate_user != in_rate ||
+ p->in_format != in_format ||
+ !mp_chmap_equals(&p->in_channels, &in_channels) ||
+ p->out_rate != out_rate ||
+ p->out_format != out_format ||
+ !mp_chmap_equals(&p->out_channels, &out_channels) ||
+ !p->avrctx)
+ {
+ if (p->avrctx) {
+ // drain remaining audio
+ struct mp_frame out = filter_resample_output(p, NULL);
+ if (out.type) {
+ mp_pin_in_write(f->ppins[1], out);
+ // continue filtering next time.
+ mp_pin_out_unread(f->ppins[0], frame);
+ input = NULL;
+ }
+ }
+
+ MP_VERBOSE(p, "format change, reinitializing resampler\n");
+
+ p->in_rate_user = in_rate;
+ p->in_format = in_format;
+ p->in_channels = in_channels;
+ p->out_rate = out_rate;
+ p->out_format = out_format;
+ p->out_channels = out_channels;
+
+ if (!configure_lavrr(p, true)) {
+ talloc_free(input);
+ return;
+ }
+
+ if (!input) {
+ // continue filtering next time
+ mp_filter_internal_mark_progress(f);
+ return;
+ }
+ }
+ }
+
+ int new_rate = rate_from_speed(p->in_rate_user, p->speed);
if (p->avrctx && !(!p->is_resampling && new_rate == p->in_rate)) {
- AVRational r = av_d2q(p->playback_speed * p->in_rate_user / p->in_rate,
- INT_MAX / 2);
+ AVRational r =
+ av_d2q(p->speed * p->in_rate_user / p->in_rate, INT_MAX / 2);
// Essentially, swr/avresample_set_compensation() does 2 things:
// - adjust output sample rate by sample_delta/compensation_distance
// - reset the adjustment after compensation_distance output samples
@@ -560,94 +622,96 @@ static void filter(struct mp_aconverter *p)
if (need_reinit && new_rate != p->in_rate) {
// Before reconfiguring, drain the audio that is still buffered
// in the resampler.
- filter_resample(p, NULL);
+ struct mp_frame out = filter_resample_output(p, NULL);
+ bool need_drain = !!out.type;
+ if (need_drain) {
+ mp_pin_in_write(f->ppins[1], out);
+ // Drain; continue filtering next time.
+ mp_pin_out_unread(f->ppins[0], frame);
+ }
// Reinitialize resampler.
configure_lavrr(p, false);
- p->output_eof = false;
- if (p->output)
- return; // need to read output before continuing filtering
+ if (need_drain) {
+ mp_filter_internal_mark_progress(f);
+ return;
+ }
}
- filter_resample(p, p->input);
- TA_FREEP(&p->input);
- p->input_eof = false;
-}
+ struct mp_frame out = filter_resample_output(p, input);
-// Queue input. If true, ownership of in passes to mp_aconverted and the input
-// was accepted. Otherwise, return false and reject in.
-// in==NULL means trigger EOF.
-bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in)
-{
- if (p->input || p->input_eof)
- return false;
+ if (input && out.type) {
+ mp_pin_in_write(f->ppins[1], out);
+ mp_pin_out_request_data(f->ppins[0]);
+ } else if (!input && out.type) {
+ mp_pin_in_write(f->ppins[1], out);
+ mp_pin_out_repeat_eof(f->ppins[0]);
+ } else if (!input) {
+ mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
+ }
- p->input = in;
- p->input_eof = !in;
- return true;
+ talloc_free(input);
}
-// Return output frame, or NULL if nothing available.
-// *eof is set to true if NULL is returned, and it was due to EOF.
-struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof)
+double mp_swresample_get_delay(struct mp_swresample *s)
{
- *eof = false;
-
- filter(p);
+ struct priv *p = s->f->priv;
- if (p->output) {
- struct mp_aframe *out = p->output;
- p->output = NULL;
- return out;
- }
-
- *eof = p->output_eof;
- p->output_eof = false;
- return NULL;
+ return get_delay(p);
}
-double mp_aconverter_get_latency(struct mp_aconverter *p)
+static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
{
- double delay = get_delay(p);
+ struct priv *p = f->priv;
- if (p->input)
- delay += mp_aframe_duration(p->input);
-
- // In theory this is influenced by playback speed, but other parts of the
- // player get it wrong anyway.
- if (p->output)
- delay += mp_aframe_duration(p->output);
+ if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) {
+ p->cmd_speed = cmd->speed;
+ return true;
+ }
- return delay;
+ return false;
}
-static void destroy_aconverter(void *ptr)
+static void destroy(struct mp_filter *f)
{
- struct mp_aconverter *p = ptr;
+ struct priv *p = f->priv;
close_lavrr(p);
-
- talloc_free(p->input);
- talloc_free(p->output);
}
-// If opts is not NULL, the pointer must be valid for the lifetime of the
-// mp_aconverter.
-struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
- struct mp_log *log,
- const struct mp_resample_opts *opts)
-{
- struct mp_aconverter *p = talloc_zero(NULL, struct mp_aconverter);
- p->log = log;
- p->global = global;
+static const struct mp_filter_info swresample_filter = {
+ .name = "swresample",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .command = command,
+ .reset = reset,
+ .destroy = destroy,
+};
- p->opts = opts;
- if (!p->opts)
- p->opts = mp_get_config_group(p, global, &resample_config);
+struct mp_swresample *mp_swresample_create(struct mp_filter *parent,
+ struct mp_resample_opts *opts)
+{
+ struct mp_filter *f = mp_filter_create(parent, &swresample_filter);
+ if (!f)
+ 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->public.f = f;
+ p->public.speed = 1.0;
+ p->cmd_speed = 1.0;
+ p->log = f->log;
+
+ if (opts) {
+ p->opts = talloc_dup(p, opts);
+ p->opts->avopts = mp_dup_str_array(p, p->opts->avopts);
+ } else {
+ p->opts = mp_get_config_group(p, f->global, &resample_conf);
+ }
p->reorder_buffer = mp_aframe_pool_create(p);
p->out_pool = mp_aframe_pool_create(p);
- talloc_set_destructor(p, destroy_aconverter);
-
- return p;
+ return &p->public;
}
diff --git a/filters/f_swresample.h b/filters/f_swresample.h
new file mode 100644
index 0000000000..44b2e35d08
--- /dev/null
+++ b/filters/f_swresample.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "audio/chmap.h"
+#include "filter.h"
+
+// Resampler filter, wrapping libswresample or libavresample.
+struct mp_swresample {
+ struct mp_filter *f;
+ // Desired output parameters. For unset parameters, passes through the
+ // format.
+ int out_rate;
+ int out_format;
+ struct mp_chmap out_channels;
+ double speed;
+};
+
+struct mp_resample_opts {
+ int filter_size;
+ int phase_shift;
+ int linear;
+ double cutoff;
+ int normalize;
+ int allow_passthrough;
+ char **avopts;
+};
+
+#define MP_RESAMPLE_OPTS_DEF { \
+ .filter_size = 16, \
+ .cutoff = 0.0, \
+ .phase_shift = 10, \
+ .normalize = 0, \
+ }
+
+// Create the filter. If opts==NULL, use the global options as defaults.
+// Free with talloc_free(mp_swresample.f).
+struct mp_swresample *mp_swresample_create(struct mp_filter *parent,
+ struct mp_resample_opts *opts);
+
+// Internal resampler delay. Does not include data buffered in mp_pins and such.
+double mp_swresample_get_delay(struct mp_swresample *s);
diff --git a/filters/f_utils.c b/filters/f_utils.c
index f984a3b33a..d15d063879 100644
--- a/filters/f_utils.c
+++ b/filters/f_utils.c
@@ -1,3 +1,4 @@
+#include "audio/aframe.h"
#include "video/mp_image.h"
#include "f_utils.h"
@@ -173,3 +174,120 @@ struct mp_filter *mp_bidir_nop_filter_create(struct mp_filter *parent)
return f;
}
+
+struct fixed_aframe_size_priv {
+ int samples;
+ bool pad_silence;
+ struct mp_aframe *in;
+ struct mp_aframe *out;
+ int out_written; // valid samples in out
+ struct mp_aframe_pool *pool;
+};
+
+static void fixed_aframe_size_process(struct mp_filter *f)
+{
+ struct fixed_aframe_size_priv *p = f->priv;
+
+ if (!mp_pin_in_needs_data(f->ppins[1]))
+ return;
+
+ if (p->in && !mp_aframe_get_size(p->in))
+ TA_FREEP(&p->in);
+
+ if (!p->in) {
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ if (frame.type == MP_FRAME_EOF) {
+ if (!p->out) {
+ mp_pin_in_write(f->ppins[1], frame);
+ return;
+ }
+ mp_pin_out_repeat_eof(f->ppins[0]);
+ } else if (frame.type == MP_FRAME_AUDIO) {
+ p->in = frame.data;
+ if (p->out && !mp_aframe_config_equals(p->out, p->in)) {
+ mp_pin_out_unread(f->ppins[0], frame);
+ p->in = NULL;
+ }
+ } else if (frame.type) {
+ MP_ERR(f, "unsupported frame type\n");
+ mp_filter_internal_mark_failed(f);
+ return;
+ } else {
+ return; // no new data yet
+ }
+ }
+
+ if (p->in) {
+ if (!p->out) {
+ p->out = mp_aframe_create();
+ mp_aframe_config_copy(p->out, p->in);
+ mp_aframe_copy_attributes(p->out, p->in);
+ if (mp_aframe_pool_allocate(p->pool, p->out, p->samples) < 0) {
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+ p->out_written = 0;
+ }
+ int in_samples = mp_aframe_get_size(p->in);
+ int copy = MPMIN(in_samples, p->samples - p->out_written);
+ if (!mp_aframe_copy_samples(p->out, p->out_written, p->in, 0, copy))
+ assert(0);
+ mp_aframe_skip_samples(p->in, copy);
+ p->out_written += copy;
+ }
+
+ // p->in not set means draining for EOF or format change
+ if ((!p->in && p->out_written) || p->out_written == p->samples) {
+ int missing = p->samples - p->out_written;
+ assert(missing >= 0);
+ if (missing) {
+ mp_aframe_set_silence(p->out, p->out_written, missing);
+ if (!p->pad_silence)
+ mp_aframe_set_size(p->out, p->out_written);
+ }
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, p->out));
+ p->out = NULL;
+ p->out_written = 0;
+ } else {
+ if (mp_pin_out_request_data(f->ppins[0]))
+ mp_filter_internal_mark_progress(f);
+ }
+}
+
+static void fixed_aframe_size_reset(struct mp_filter *f)
+{
+ struct fixed_aframe_size_priv *p = f->priv;
+
+ TA_FREEP(&p->in);
+ TA_FREEP(&p->out);
+ p->out_written = 0;
+}
+
+static const struct mp_filter_info fixed_aframe_size_filter = {
+ .name = "fixed_aframe_size",
+ .priv_size = sizeof(struct fixed_aframe_size_priv),
+ .process = fixed_aframe_size_process,
+ .reset = fixed_aframe_size_reset,
+ .destroy = fixed_aframe_size_reset,
+};
+
+struct mp_filter *mp_fixed_aframe_size_create(struct mp_filter *parent,
+ int samples, bool pad_silence)
+{
+ if (samples < 1)
+ return NULL;
+
+ struct mp_filter *f = mp_filter_create(parent, &fixed_aframe_size_filter);
+ if (!f)
+ return NULL;
+
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ struct fixed_aframe_size_priv *p = f->priv;
+ p->samples = samples;
+ p->pad_silence = pad_silence;
+ p->pool = mp_aframe_pool_create(p);
+
+ return f;
+}
diff --git a/filters/f_utils.h b/filters/f_utils.h
index a59ac1601d..6ab288bcea 100644
--- a/filters/f_utils.h
+++ b/filters/f_utils.h
@@ -70,3 +70,9 @@ bool mp_subfilter_drain_destroy(struct mp_subfilter *sub);
// A bidrectional filter which passes through all data.
struct mp_filter *mp_bidir_nop_filter_create(struct mp_filter *parent);
+
+// A filter which repacks audio frame to fixed frame sizes with the given
+// number of samples. On hard format changes (sample format/channels/srate),
+// the frame can be shorter, unless pad_silence is true. Fails on non-aframes.
+struct mp_filter *mp_fixed_aframe_size_create(struct mp_filter *parent,
+ int samples, bool pad_silence);
diff --git a/filters/filter.h b/filters/filter.h
index 3fd8af9195..eb554c29c8 100644
--- a/filters/filter.h
+++ b/filters/filter.h
@@ -313,6 +313,8 @@ void mp_filter_reset(struct mp_filter *filter);
enum mp_filter_command_type {
MP_FILTER_COMMAND_TEXT = 1,
MP_FILTER_COMMAND_GET_META,
+ MP_FILTER_COMMAND_SET_SPEED,
+ MP_FILTER_COMMAND_SET_SPEED_RESAMPLE,
};
struct mp_filter_command {
@@ -324,6 +326,9 @@ struct mp_filter_command {
// For MP_FILTER_COMMAND_GET_META
void *res; // must point to struct mp_tags*, will be set to new instance
+
+ // For MP_FILTER_COMMAND_SET_SPEED and MP_FILTER_COMMAND_SET_SPEED_RESAMPLE
+ double speed;
};
// Run a command on the filter. Returns success. For libavfilter.
diff --git a/filters/user_filters.c b/filters/user_filters.c
index f59dd22d76..30358a4a94 100644
--- a/filters/user_filters.c
+++ b/filters/user_filters.c
@@ -18,6 +18,31 @@ static bool get_desc_from(const struct mp_user_filter_entry **list, int num,
return true;
}
+// --af option
+
+const struct mp_user_filter_entry *af_list[] = {
+ &af_lavfi,
+ &af_lavfi_bridge,
+ &af_scaletempo,
+ &af_format,
+#if HAVE_RUBBERBAND
+ &af_rubberband,
+#endif
+ &af_lavcac3enc,
+};
+
+static bool get_af_desc(struct m_obj_desc *dst, int index)
+{
+ return get_desc_from(af_list, MP_ARRAY_SIZE(af_list), dst, index);
+}
+
+const struct m_obj_list af_obj_list = {
+ .get_desc = get_af_desc,
+ .description = "audio filters",
+ .allow_disable_entries = true,
+ .allow_unknown_entries = true,
+};
+
// --vf option
const struct mp_user_filter_entry *vf_list[] = {
@@ -66,6 +91,10 @@ struct mp_filter *mp_create_user_filter(struct mp_filter *parent,
frame_type = MP_FRAME_VIDEO;
obj_list = &vf_obj_list;
defs_name = "vf-defaults";
+ } else if (type == MP_OUTPUT_CHAIN_AUDIO) {
+ frame_type = MP_FRAME_AUDIO;
+ obj_list = &af_obj_list;
+ defs_name = "af-defaults";
}
assert(frame_type && obj_list);
diff --git a/filters/user_filters.h b/filters/user_filters.h
index 8e1e3e380d..a2cca7cba0 100644
--- a/filters/user_filters.h
+++ b/filters/user_filters.h
@@ -18,6 +18,13 @@ struct mp_filter *mp_create_user_filter(struct mp_filter *parent,
enum mp_output_chain_type type,
const char *name, char **args);
+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_format;
+extern const struct mp_user_filter_entry af_rubberband;
+extern const struct mp_user_filter_entry af_lavcac3enc;
+
extern const struct mp_user_filter_entry vf_lavfi;
extern const struct mp_user_filter_entry vf_lavfi_bridge;
extern const struct mp_user_filter_entry vf_sub;
diff --git a/options/options.c b/options/options.c
index ce602f2683..bce2bf3be6 100644
--- a/options/options.c
+++ b/options/options.c
@@ -47,10 +47,6 @@
#include "player/command.h"
#include "stream/stream.h"
-#if HAVE_LIBAF
-#include "audio/filter/af.h"
-#endif
-
#if HAVE_DRM
#include "video/out/drm_common.h"
#endif
@@ -94,8 +90,6 @@ extern const struct m_sub_options angle_conf;
extern const struct m_sub_options cocoa_conf;
extern const struct m_sub_options android_conf;
-extern const struct m_sub_options resample_config;
-
static const struct m_sub_options screenshot_conf = {
.opts = image_writer_opts,
.size = sizeof(struct image_writer_opts),
@@ -485,7 +479,7 @@ const m_option_t mp_opts[] = {
// force video/audio rate:
OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
- OPT_INTRANGE("audio-samplerate", force_srate, UPDATE_AUDIO, 1000, 16*48000),
+ OPT_INTRANGE("audio-samplerate", force_srate, UPDATE_AUDIO, 0, 16*48000),
OPT_CHANNELS("audio-channels", audio_output_channels, UPDATE_AUDIO),
OPT_AUDIOFORMAT("audio-format", audio_output_format, UPDATE_AUDIO),
OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE, .min = 0.01, .max = 100.0),
@@ -497,11 +491,9 @@ const m_option_t mp_opts[] = {
// ------------------------- codec/vfilter options --------------------
-#if HAVE_LIBAF
OPT_SETTINGSLIST("af-defaults", af_defs, 0, &af_obj_list,
.deprecation_message = "use --af + enable/disable flags"),
OPT_SETTINGSLIST("af", af_settings, 0, &af_obj_list, ),
-#endif
OPT_SETTINGSLIST("vf-defaults", vf_defs, 0, &vf_obj_list,
.deprecation_message = "use --vf + enable/disable flags"),
OPT_SETTINGSLIST("vf", vf_settings, 0, &vf_obj_list, ),
@@ -711,7 +703,7 @@ const m_option_t mp_opts[] = {
OPT_STRING("record-file", record_file, M_OPT_FILE),
- OPT_SUBSTRUCT("", resample_opts, resample_config, 0),
+ OPT_SUBSTRUCT("", resample_opts, resample_conf, 0),
OPT_SUBSTRUCT("", input_opts, input_config, 0),
diff --git a/options/options.h b/options/options.h
index a3444dbd4d..db5b92b86a 100644
--- a/options/options.h
+++ b/options/options.h
@@ -365,6 +365,7 @@ extern const struct m_sub_options dvd_conf;
extern const struct m_sub_options mp_subtitle_sub_opts;
extern const struct m_sub_options mp_osd_render_sub_opts;
extern const struct m_sub_options filter_conf;
+extern const struct m_sub_options resample_conf;
int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param);
diff --git a/player/audio.c b/player/audio.c
index ccddd790e7..d8f43278b0 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -13,9 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_LIBAF are partially licensed under GNU General Public
- * License (libaf/af.h glue code only).
*/
#include <stddef.h>
@@ -35,7 +32,6 @@
#include "osdep/timer.h"
#include "audio/audio_buffer.h"
-#include "audio/aconverter.h"
#include "audio/format.h"
#include "audio/decode/dec_audio.h"
#include "audio/out/ao.h"
@@ -55,101 +51,41 @@ enum {
AD_STARVE = -6,
};
-#if HAVE_LIBAF
-
-#include "audio/audio.h"
-#include "audio/filter/af.h"
-
-// Use pitch correction only for speed adjustments by the user, not minor sync
-// correction ones.
-static int get_speed_method(struct MPContext *mpctx)
-{
- return mpctx->opts->pitch_correction && mpctx->opts->playback_speed != 1.0
- ? AF_CONTROL_SET_PLAYBACK_SPEED : AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE;
-}
-
// Try to reuse the existing filters to change playback speed. If it works,
// return true; if filter recreation is needed, return false.
-static bool update_speed_filters(struct MPContext *mpctx)
+static void update_speed_filters(struct MPContext *mpctx)
{
- struct af_stream *afs = mpctx->ao_chain->af;
- double speed = mpctx->audio_speed;
-
- if (afs->initialized < 1)
- return false;
-
- // Make sure only exactly one filter changes speed; resetting them all
- // and setting 1 filter is the easiest way to achieve this.
- af_control_all(afs, AF_CONTROL_SET_PLAYBACK_SPEED, &(double){1});
- af_control_all(afs, AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE, &(double){1});
-
- if (speed == 1.0)
- return !af_find_by_label(afs, "playback-speed");
-
- // Compatibility: if the user uses --af=scaletempo, always use this
- // filter to change speed. Don't insert a second filter (any) either.
- if (!af_find_by_label(afs, "playback-speed") &&
- af_control_any_rev(afs, AF_CONTROL_SET_PLAYBACK_SPEED, &speed))
- return true;
-
- return !!af_control_any_rev(afs, get_speed_method(mpctx), &speed);
-}
-
-// Update speed, and insert/remove filters if necessary.
-static void recreate_speed_filters(struct MPContext *mpctx)
-{
- struct af_stream *afs = mpctx->ao_chain->af;
-
- if (update_speed_filters(mpctx))
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ if (!ao_c)
return;
- if (af_remove_by_label(afs, "playback-speed") < 0)
- goto fail;
-
- if (mpctx->audio_speed == 1.0)
- return;
+ double speed = mpctx->opts->playback_speed;
+ double resample = mpctx->speed_factor_a;
- int method = get_speed_method(mpctx);
- char *filter = "lavrresample";
- char *args[] = {"deprecation-warning", "no", NULL};
- if (method == AF_CONTROL_SET_PLAYBACK_SPEED) {
- filter = "scaletempo";
- args[0] = NULL;
+ if (!mpctx->opts->pitch_correction) {
+ resample *= speed;
+ speed = 1.0;
}
- if (!af_add(afs, filter, "playback-speed", args))
- goto fail;
-
- if (!update_speed_filters(mpctx))
- goto fail;
-
- return;
-
-fail:
- mpctx->opts->playback_speed = 1.0;
- mpctx->speed_factor_a = 1.0;
- mpctx->audio_speed = 1.0;
- mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
+ mp_output_chain_set_audio_speed(ao_c->filter, speed, resample);
}
static int recreate_audio_filters(struct MPContext *mpctx)
{
- assert(mpctx->ao_chain);
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ assert(ao_c);
- struct af_stream *afs = mpctx->ao_chain->af;
- if (afs->initialized < 1 && af_init(afs) < 0)
+ if (!mp_output_chain_update_filters(ao_c->filter, mpctx->opts->af_settings))
goto fail;
- recreate_speed_filters(mpctx);
- if (afs->initialized < 1 && af_init(afs) < 0)
- goto fail;
+ update_speed_filters(mpctx);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
return 0;
fail:
- MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
+ MP_ERR(mpctx, "Audio filter initialized failed!\n");
return -1;
}
@@ -159,27 +95,20 @@ int reinit_audio_filters(struct MPContext *mpctx)
if (!ao_c)
return 0;
- double delay = 0;
- if (ao_c->af->initialized > 0)
- delay = af_calc_delay(ao_c->af);
+ double delay = mp_output_get_measured_total_delay(ao_c->filter);
- af_uninit(ao_c->af);
if (recreate_audio_filters(mpctx) < 0)
return -1;
+ double ndelay = mp_output_get_measured_total_delay(ao_c->filter);
+
// Only force refresh if the amount of dropped buffered data is going to
// cause "issues" for the A/V sync logic.
- if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2)
+ if (mpctx->audio_status == STATUS_PLAYING && delay - ndelay >= 0.2)
issue_refresh_seek(mpctx, MPSEEK_EXACT);
return 1;
}
-#else /* HAVE_LIBAV */
-
-int reinit_audio_filters(struct MPContext *mpctx) { return 0; }
-
-#endif /* else HAVE_LIBAF */
-
static double db_gain(double db)
{
return pow(10.0, db/20.0);
@@ -249,26 +178,17 @@ void update_playback_speed(struct MPContext *mpctx)
mpctx->audio_speed = mpctx->opts->playback_speed * mpctx->speed_factor_a;
mpctx->video_speed = mpctx->opts->playback_speed * mpctx->speed_factor_v;
-#if HAVE_LIBAF
- if (!mpctx->ao_chain || mpctx->ao_chain->af->initialized < 1)
- return;
-
- if (!update_speed_filters(mpctx))
- recreate_audio_filters(mpctx);
-#endif
+ update_speed_filters(mpctx);
}
static void ao_chain_reset_state(struct ao_chain *ao_c)
{
+ ao_c->last_out_pts = MP_NOPTS_VALUE;
ao_c->pts = MP_NOPTS_VALUE;
ao_c->pts_reset = false;
TA_FREEP(&ao_c->input_frame);
TA_FREEP(&ao_c->output_frame);
-#if HAVE_LIBAF
- af_seek_reset(ao_c->af);
-#endif
- if (ao_c->conv)
- mp_aconverter_flush(ao_c->conv);
+
mp_audio_buffer_clear(ao_c->ao_buffer);
if (ao_c->audio_src)
@@ -296,8 +216,7 @@ void uninit_audio_out(struct MPContext *mpctx)
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
}
mpctx->ao = NULL;
- talloc_free(mpctx->ao_decoder_fmt);
- mpctx->ao_decoder_fmt = NULL;
+ TA_FREEP(&mpctx->ao_filter_fmt);
}
static void ao_chain_uninit(struct ao_chain *ao_c)
@@ -314,14 +233,9 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
if (ao_c->filter_src)
lavfi_set_connected(ao_c->filter_src, false);
-#if HAVE_LIBAF
- af_destroy(ao_c->af);
-#endif
- talloc_free(ao_c->conv);
+ talloc_free(ao_c->filter->f);
talloc_free(ao_c->input_frame);
- talloc_free(ao_c->input_format);
talloc_free(ao_c->output_frame);
- talloc_free(ao_c->filter_input_format);
talloc_free(ao_c->ao_buffer);
talloc_free(ao_c);
}
@@ -358,186 +272,127 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
assert(ao_c);
struct track *track = ao_c->track;
- if (!mp_aframe_config_is_valid(ao_c->input_format)) {
- // We don't know the audio format yet - so configure it later as we're
- // resyncing. fill_audio_buffers() will call this function again.
- mp_wakeup_core(mpctx);
+ if (!ao_c->filter->ao_needs_update)
return;
- }
- // Weak gapless audio: drain AO on decoder format changes
- if (mpctx->ao_decoder_fmt && mpctx->ao && opts->gapless_audio < 0 &&
- !mp_aframe_config_equals(mpctx->ao_decoder_fmt, ao_c->input_format))
- {
- uninit_audio_out(mpctx);
- }
+ TA_FREEP(&ao_c->output_frame); // stale?
- TA_FREEP(&ao_c->output_frame);
+ // The "ideal" filter output format
+ struct mp_aframe *out_fmt = mp_aframe_new_ref(ao_c->filter->output_aformat);
+ if (!out_fmt)
+ abort();
- int out_rate = 0;
- int out_format = 0;
- struct mp_chmap out_channels = {0};
- if (mpctx->ao) {
- ao_get_format(mpctx->ao, &out_rate, &out_format, &out_channels);
- } else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
- out_rate = opts->force_srate;
- out_format = opts->audio_output_format;
+ if (af_fmt_is_pcm(mp_aframe_get_format(out_fmt))) {
+ if (opts->force_srate)
+ mp_aframe_set_rate(out_fmt, opts->force_srate);
+ if (opts->audio_output_format)
+ mp_aframe_set_format(out_fmt, opts->audio_output_format);
if (opts->audio_output_channels.num_chmaps == 1)
- out_channels = opts->audio_output_channels.chmaps[0];
+ mp_aframe_set_chmap(out_fmt, &opts->audio_output_channels.chmaps[0]);
}
-#if HAVE_LIBAF
- struct af_stream *afs = ao_c->af;
-
- struct mp_audio in_format;
- mp_audio_config_from_aframe(&in_format, ao_c->input_format);
- if (mpctx->ao && mp_audio_config_equals(&in_format, &afs->input))
+ // Weak gapless audio: if the filter output format is the same as the
+ // previous one, keep the AO and don't reinit anything.
+ // Strong gapless: always keep the AO
+ if ((mpctx->ao_filter_fmt && mpctx->ao && opts->gapless_audio < 0 &&
+ mp_aframe_config_equals(mpctx->ao_filter_fmt, out_fmt)) ||
+ (mpctx->ao && opts->gapless_audio > 0))
+ {
+ mp_output_chain_set_ao(ao_c->filter, mpctx->ao);
+ talloc_free(out_fmt);
return;
-
- afs->output = (struct mp_audio){0};
- afs->output.rate = out_rate;
- mp_audio_set_format(&afs->output, out_format);
- mp_audio_set_channels(&afs->output, &out_channels);
-
- // filter input format: same as codec's output format:
- afs->input = in_format;
-
- // Determine what the filter chain outputs. recreate_audio_filters() also
- // needs this for testing whether playback speed is changed by resampling
- // or using a special filter.
- if (af_init(afs) < 0) {
- MP_ERR(mpctx, "Error at audio filter chain pre-init!\n");
- goto init_error;
}
- out_rate = afs->output.rate;
- out_format = afs->output.format;
- out_channels = afs->output.channels;
-#else
- if (mpctx->ao && ao_c->filter_input_format &&
- mp_aframe_config_equals(ao_c->filter_input_format, ao_c->input_format))
- return;
+ uninit_audio_out(mpctx);
- TA_FREEP(&ao_c->filter_input_format);
+ int out_rate = mp_aframe_get_rate(out_fmt);
+ int out_format = mp_aframe_get_format(out_fmt);
+ struct mp_chmap out_channels = {0};
+ mp_aframe_get_chmap(out_fmt, &out_channels);
- if (!out_rate)
- out_rate = mp_aframe_get_rate(ao_c->input_format);
- if (!out_format)
- out_format = mp_aframe_get_format(ao_c->input_format);
- if (!out_channels.num)
- mp_aframe_get_chmap(ao_c->input_format, &out_channels);
-#endif
+ int ao_flags = 0;
+ bool spdif_fallback = af_fmt_is_spdif(out_format) &&
+ ao_c->spdif_passthrough;
- if (!mpctx->ao) {
- int ao_flags = 0;
- bool spdif_fallback = af_fmt_is_spdif(out_format) &&
- ao_c->spdif_passthrough;
+ if (opts->ao_null_fallback && !spdif_fallback)
+ ao_flags |= AO_INIT_NULL_FALLBACK;
- if (opts->ao_null_fallback && !spdif_fallback)
- ao_flags |= AO_INIT_NULL_FALLBACK;
+ if (opts->audio_stream_silence)
+ ao_flags |= AO_INIT_STREAM_SILENCE;
- if (opts->audio_stream_silence)
- ao_flags |= AO_INIT_STREAM_SILENCE;
+ if (opts->audio_exclusive)
+ ao_flags |= AO_INIT_EXCLUSIVE;
- if (opts->audio_exclusive)
- ao_flags |= AO_INIT_EXCLUSIVE;
+ if (af_fmt_is_pcm(out_format)) {
+ if (!opts->audio_output_channels.set ||
+ opts->audio_output_channels.auto_safe)
+ ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
- if (af_fmt_is_pcm(out_format)) {
- if (!opts->audio_output_channels.set ||
- opts->audio_output_channels.auto_safe)
- ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
+ mp_chmap_sel_list(&out_channels,
+ opts->audio_output_channels.chmaps,
+ opts->audio_output_channels.num_chmaps);
+ }
- mp_chmap_sel_list(&out_channels,
- opts->audio_output_channels.chmaps,
- opts->audio_output_channels.num_chmaps);
- }
+ mpctx->ao_filter_fmt = out_fmt;
- mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
- mpctx, mpctx->encode_lavc_ctx, out_rate,
- out_format, out_channels);
- ao_c->ao = mpctx->ao;
-
- int ao_rate = 0;
- int ao_format = 0;
- struct mp_chmap ao_channels = {0};
- if (mpctx->ao)
- ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
-
- // Verify passthrough format was not changed.
- if (mpctx->ao && af_fmt_is_spdif(out_format)) {
- if (out_rate != ao_rate || out_format != ao_format ||
- !mp_chmap_equals(&out_channels, &ao_channels))
- {
- MP_ERR(mpctx, "Passthrough format unsupported.\n");
- ao_uninit(mpctx->ao);
- mpctx->ao = NULL;
- ao_c->ao = NULL;
- }
- }
+ mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
+ mpctx, mpctx->encode_lavc_ctx, out_rate,
+ out_format, out_channels);
+ ao_c->ao = mpctx->ao;
- if (!mpctx->ao) {
- // If spdif was used, try to fallback to PCM.
- if (spdif_fallback && ao_c->audio_src) {
- MP_VERBOSE(mpctx, "Falling back to PCM output.\n");
- ao_c->spdif_passthrough = false;
- ao_c->spdif_failed = true;
- ao_c->audio_src->try_spdif = false;
- if (!audio_init_best_codec(ao_c->audio_src))
- goto init_error;
- reset_audio_state(mpctx);
- mp_aframe_reset(ao_c->input_format);
- mp_wakeup_core(mpctx); // reinit with new format next time
- return;
- }
+ int ao_rate = 0;
+ int ao_format = 0;
+ struct mp_chmap ao_channels = {0};
+ if (mpctx->ao)
+ ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
- MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
- mpctx->error_playing = MPV_ERROR_AO_INIT_FAILED;
- goto init_error;
+ // Verify passthrough format was not changed.
+ if (mpctx->ao && af_fmt_is_spdif(out_format)) {
+ if (out_rate != ao_rate || out_format != ao_format ||
+ !mp_chmap_equals(&out_channels, &ao_channels))
+ {
+ MP_ERR(mpctx, "Passthrough format unsupported.\n");
+ ao_uninit(mpctx->ao);
+ mpctx->ao = NULL;
+ ao_c->ao = NULL;
}
+ }
- mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, ao_format, &ao_channels,
- ao_rate);
-
-#if HAVE_LIBAF
- afs->output = (struct mp_audio){0};
- afs->output.rate = ao_rate;
- mp_audio_set_format(&afs->output, ao_format);
- mp_audio_set_channels(&afs->output, &ao_channels);
- if (!mp_audio_config_equals(&afs->output, &afs->filter_output))
- afs->initialized = 0;
-#else
- int in_rate = mp_aframe_get_rate(ao_c->input_format);
- int in_format = mp_aframe_get_format(ao_c->input_format);
- struct mp_chmap in_chmap = {0};
- mp_aframe_get_chmap(ao_c->input_format, &in_chmap);
- if (!mp_aconverter_reconfig(ao_c->conv, in_rate, in_format, in_chmap,
- ao_rate, ao_format, ao_channels))
- {
- MP_ERR(mpctx, "Cannot convert audio data for output.\n");
- goto init_error;
+ if (!mpctx->ao) {
+ // If spdif was used, try to fallback to PCM.
+ if (spdif_fallback && ao_c->audio_src) {
+ MP_VERBOSE(mpctx, "Falling back to PCM output.\n");
+ ao_c->spdif_passthrough = false;
+ ao_c->spdif_failed = true;
+ ao_c->audio_src->try_spdif = false;
+ if (!audio_init_best_codec(ao_c->audio_src))
+ goto init_error;
+ reset_audio_state(mpctx);
+ mp_output_chain_reset_harder(ao_c->filter);
+ mp_wakeup_core(mpctx); // reinit with new format next time
+ return;
}
- ao_c->filter_input_format = mp_aframe_new_ref(ao_c->input_format);
-#endif
- mpctx->ao_decoder_fmt = mp_aframe_new_ref(ao_c->input_format);
+ MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
+ mpctx->error_playing = MPV_ERROR_AO_INIT_FAILED;
+ goto init_error;
+ }
- char tmp[80];
- MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
- audio_config_to_str_buf(tmp, sizeof(tmp), ao_rate, ao_format,
- ao_channels));
- MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
- update_window_title(mpctx, true);
+ mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, ao_format, &ao_channels,
+ ao_rate);
- ao_c->ao_resume_time =
- opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
- }
+ char tmp[80];
+ MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
+ audio_config_to_str_buf(tmp, sizeof(tmp), ao_rate, ao_format,
+ ao_channels));
+ MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
+ update_window_title(mpctx, true);
-#if HAVE_LIBAF
- if (recreate_audio_filters(mpctx) < 0)
- goto init_error;
-#endif
+ ao_c->ao_resume_time =
+ opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
+
+ mp_output_chain_set_ao(ao_c->filter, mpctx->ao);
- update_playback_speed(mpctx);
audio_update_volume(mpctx);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
@@ -603,16 +458,12 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
struct ao_chain *ao_c = talloc_zero(NULL, struct ao_chain);
mpctx->ao_chain = ao_c;
ao_c->log = mpctx->log;
-#if HAVE_LIBAF
- ao_c->af = af_new(mpctx->global);
-#else
- ao_c->conv = mp_aconverter_create(mpctx->global, mpctx->log, NULL);
-#endif
+ ao_c->filter =
+ mp_output_chain_create(mpctx->filter_root, MP_OUTPUT_CHAIN_AUDIO);
ao_c->spdif_passthrough = true;
ao_c->pts = MP_NOPTS_VALUE;
ao_c->ao_buffer = mp_audio_buffer_create(NULL);
ao_c->ao = mpctx->ao;
- ao_c->input_format = mp_aframe_create();
if (track) {
ao_c->track = track;
@@ -624,6 +475,9 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
reset_audio_state(mpctx);
+ if (recreate_audio_filters(mpctx) < 0)
+ goto init_error;
+
if (mpctx->ao) {
int rate;
int format;
@@ -651,34 +505,15 @@ double written_audio_pts(struct MPContext *mpctx)
if (!ao_c)
return MP_NOPTS_VALUE;
- // first calculate the end pts of audio that has been output by decoder
- double a_pts = ao_c->pts;
+ // end pts of audio that has been output by filters
+ double a_pts = ao_c->last_out_pts;
if (a_pts == MP_NOPTS_VALUE)
return MP_NOPTS_VALUE;
- // Data buffered in audio filters, measured in seconds of "missing" output
- double buffered_output = 0;
-
-#if HAVE_LIBAF
- if (ao_c->af->initialized < 1)
- return MP_NOPTS_VALUE;
-
- buffered_output += af_calc_delay(ao_c->af);
-#endif
-
- if (ao_c->conv)
- buffered_output += mp_aconverter_get_latency(ao_c->conv);
-
- if (ao_c->output_frame)
- buffered_output += mp_aframe_duration(ao_c->output_frame);
-
// Data that was ready for ao but was buffered because ao didn't fully
- // accept everything to internal buffers yet
- buffered_output += mp_audio_buffer_seconds(ao_c->ao_buffer);
-
- // Filters divide audio length by audio_speed, so multiply by it
- // to get the length in original units without speedup or slowdown
- a_pts -= buffered_output * mpctx->audio_speed;
+ // accept everything to internal buffers yet. This also does not correctly
+ // track playback speed, so we use the current speed.
+ a_pts -= mp_audio_buffer_seconds(ao_c->ao_buffer) * mpctx->audio_speed;
return a_pts;
}
@@ -791,6 +626,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
}
double ptsdiff = written_pts - sync_pts;
+
// Missing timestamp, or PTS reset, or just broken.
if (written_pts == MP_NOPTS_VALUE) {
MP_WARN(mpctx, "Failed audio resync.\n");
@@ -830,20 +666,16 @@ static bool copy_output(struct MPContext *mpctx, struct ao_chain *ao_c,
if (!ao_c->output_frame || !mp_aframe_get_size(ao_c->output_frame)) {
TA_FREEP(&ao_c->output_frame);
-#if HAVE_LIBAF
- struct af_stream *afs = mpctx->ao_chain->af;
- if (af_output_frame(afs, eof) < 0)
- return true; // error, stop doing stuff
- struct mp_audio *mpa = af_read_output_frame(afs);
- ao_c->output_frame = mp_audio_to_aframe(mpa);
- talloc_free(mpa);
-#else
- if (eof)
- mp_aconverter_write_input(ao_c->conv, NULL);
- mp_aconverter_set_speed(ao_c->conv, mpctx->audio_speed);
- bool got_eof;
- ao_c->output_frame = mp_aconverter_read_output(ao_c->conv, &got_eof);
-#endif
+
+ struct mp_frame frame = mp_pin_out_read(ao_c->filter->f->pins[1]);
+ if (frame.type == MP_FRAME_AUDIO) {
+ ao_c->output_frame = frame.data;
+ ao_c->last_out_pts = mp_aframe_end_pts(ao_c->output_frame);
+ } else if (frame.type == MP_FRAME_EOF) {
+ *seteof = true;
+ } else if (frame.type) {
+ MP_ERR(mpctx, "unknown frame type\n");
+ }
}
if (!ao_c->output_frame)
@@ -882,9 +714,6 @@ static int decode_new_frame(struct ao_chain *ao_c)
res = audio_get_frame(ao_c->audio_src, &ao_c->input_frame);
}
- if (ao_c->input_frame)
- mp_aframe_config_copy(ao_c->input_format, ao_c->input_frame);
-
switch (res) {
case DATA_OK: return AD_OK;
case DATA_WAIT: return AD_WAIT;
@@ -904,14 +733,6 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
int minsamples)
{
struct ao_chain *ao_c = mpctx->ao_chain;
-#if HAVE_LIBAF
- struct af_stream *afs = ao_c->af;
- if (afs->initialized < 1)
- return AD_ERR;
-#else
- if (!ao_c->filter_input_format)
- return AD_ERR;
-#endif
MP_STATS(ao_c, "start audio");
@@ -932,28 +753,12 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
break;
if (res < 0) {
// drain filters first (especially for true EOF case)
+ if (!ao_c->filter->got_input_eof)
+ mp_pin_in_write(ao_c->filter->f->pins[0], MP_EOF_FRAME);
copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
break;
}
-
- // On format change, make sure to drain the filter chain.
-#if HAVE_LIBAF
- struct mp_audio in_format;
- mp_audio_config_from_aframe(&in_format, ao_c->input_format);
- if (!mp_audio_config_equals(&afs->input, &in_format)) {
- copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
- res = AD_NEW_FMT;
- break;
- }
-#else
- if (!mp_aframe_config_equals(ao_c->filter_input_format,
- ao_c->input_format))
- {
- copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
- res = AD_NEW_FMT;
- break;
- }
-#endif
+ assert(ao_c->input_frame);
double pts = mp_aframe_get_pts(ao_c->input_frame);
if (pts == MP_NOPTS_VALUE) {
@@ -972,18 +777,13 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
ao_c->pts = mp_aframe_end_pts(ao_c->input_frame);
}
-#if HAVE_LIBAF
- struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
- talloc_free(ao_c->input_frame);
+ if (!mp_pin_in_needs_data(ao_c->filter->f->pins[0])) {
+ res = AD_WAIT;
+ break;
+ }
+ mp_pin_in_write(ao_c->filter->f->pins[0],
+ MAKE_FRAME(MP_FRAME_AUDIO, ao_c->input_frame));
ao_c->input_frame = NULL;
- if (!mpa)
- abort();
- if (af_filter_frame(afs, mpa) < 0)
- return AD_ERR;
-#else
- if (mp_aconverter_write_input(ao_c->conv, ao_c->input_frame))
- ao_c->input_frame = NULL;
-#endif
}
if (res == 0 && mp_audio_buffer_samples(outbuf) < minsamples && eof)
@@ -1013,10 +813,6 @@ void reload_audio_output(struct MPContext *mpctx)
ao_c->spdif_passthrough = true;
ao_c->spdif_failed = false;
d_audio->try_spdif = true;
-#if HAVE_LIBAF
- ao_c->af->initialized = 0;
-#endif
- TA_FREEP(&ao_c->filter_input_format);
if (!audio_init_best_codec(d_audio)) {
MP_ERR(mpctx, "Error reinitializing audio.\n");
error_on_track(mpctx, ao_c->track);
@@ -1041,12 +837,21 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (!ao_c)
return;
- bool is_initialized = !!ao_c->filter_input_format;
-#if HAVE_LIBAF
- is_initialized = ao_c->af->initialized == 1;
-#endif
+ if (ao_c->filter->failed_output_conversion) {
+ error_on_track(mpctx, ao_c->track);
+ return;
+ }
- if (!is_initialized || !mpctx->ao) {
+ if (ao_c->input_frame && mp_pin_in_needs_data(ao_c->filter->f->pins[0])) {
+ mp_pin_in_write(ao_c->filter->f->pins[0],
+ MAKE_FRAME(MP_FRAME_AUDIO, ao_c->input_frame));
+ ao_c->input_frame = NULL;
+ }
+
+ // (if AO is set due to gapless from previous file, then we can try to
+ // filter normally until the filter tells us to change the AO)
+ if (!mpctx->ao) {
+ mp_pin_out_request_data(ao_c->filter->f->pins[1]);
// Probe the initial audio format. Returns AD_OK (and does nothing) if
// the format is already known.
int r = AD_NO_PROGRESS;
@@ -1122,23 +927,17 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
bool working = false;
if (playsize > mp_audio_buffer_samples(ao_c->ao_buffer)) {
status = filter_audio(mpctx, ao_c->ao_buffer, playsize);
+ if (ao_c->filter->ao_needs_update) {
+ reinit_audio_filters_and_output(mpctx);
+ mp_wakeup_core(mpctx);
+ return; // retry on next iteration
+ }
if (status == AD_WAIT)
return;
if (status == AD_NO_PROGRESS || status == AD_STARVE) {
mp_wakeup_core(mpctx);
return;
}
- if (status == AD_NEW_FMT) {
- /* The format change isn't handled too gracefully. A more precise
- * implementation would require draining buffered old-format audio
- * while displaying video, then doing the output format switch.
- */
- if (mpctx->opts->gapless_audio < 1)
- uninit_audio_out(mpctx);
- reinit_audio_filters_and_output(mpctx);
- mp_wakeup_core(mpctx);
- return; // retry on next iteration
- }
if (status == AD_ERR)
mp_wakeup_core(mpctx);
working = true;
diff --git a/player/command.c b/player/command.c
index 9020ffe158..809f859280 100644
--- a/player/command.c
+++ b/player/command.c
@@ -68,10 +68,6 @@
#include "core.h"
-#if HAVE_LIBAF
-#include "audio/filter/af.h"
-#endif
-
#ifdef _WIN32
#include <windows.h>
#endif
@@ -1430,34 +1426,26 @@ static int mp_property_filter_metadata(void *ctx, struct m_property *prop,
bstr key;
char *rem;
m_property_split_path(ka->key, &key, &rem);
- struct mp_tags metadata = {0};
- void *metadata_mem = NULL;
+ struct mp_tags *metadata = NULL;
int res = CONTROL_UNKNOWN;
+ struct mp_output_chain *chain = NULL;
if (strcmp(type, "vf") == 0) {
- if (!mpctx->vo_chain)
- return M_PROPERTY_UNAVAILABLE;
-
- struct mp_tags *metadata_ptr = NULL;
- struct mp_filter_command cmd = {
- .type = MP_FILTER_COMMAND_GET_META,
- .res = &metadata_ptr,
- };
- char *key0 = mp_tprintf(80, "%.*s", BSTR_P(key));
- mp_output_chain_command(mpctx->vo_chain->filter, key0, &cmd);
-
- if (metadata_ptr) {
- metadata = *metadata_ptr;
- metadata_mem = metadata_ptr;
- res = CONTROL_OK;
- }
+ chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL;
} else if (strcmp(type, "af") == 0) {
-#if HAVE_LIBAF
- if (!(mpctx->ao_chain && mpctx->ao_chain->af))
- return M_PROPERTY_UNAVAILABLE;
- struct af_stream *af = mpctx->ao_chain->af;
- res = af_control_by_label(af, AF_CONTROL_GET_METADATA, &metadata, key);
-#endif
+ chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL;
}
+ if (!chain)
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct mp_filter_command cmd = {
+ .type = MP_FILTER_COMMAND_GET_META,
+ .res = &metadata,
+ };
+ mp_output_chain_command(chain, mp_tprintf(80, "%.*s", BSTR_P(key)), &cmd);
+
+ if (metadata)
+ res = CONTROL_OK;
+
switch (res) {
case CONTROL_UNKNOWN:
return M_PROPERTY_UNKNOWN;
@@ -1466,11 +1454,11 @@ static int mp_property_filter_metadata(void *ctx, struct m_property *prop,
if (strlen(rem)) {
struct m_property_action_arg next_ka = *ka;
next_ka.key = rem;
- res = tag_property(M_PROPERTY_KEY_ACTION, &next_ka, &metadata);
+ res = tag_property(M_PROPERTY_KEY_ACTION, &next_ka, metadata);
} else {
- res = tag_property(ka->action, ka->arg, &metadata);
+ res = tag_property(ka->action, ka->arg, metadata);
}
- talloc_free(metadata_mem);
+ talloc_free(metadata);
return res;
default:
return M_PROPERTY_ERROR;
@@ -2076,8 +2064,8 @@ static int mp_property_audio_params(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- return property_audiofmt(mpctx->ao_chain ? mpctx->ao_chain->input_format : NULL,
- action, arg);
+ return property_audiofmt(mpctx->ao_chain ?
+ mpctx->ao_chain->filter->input_aformat : NULL, action, arg);
}
static int mp_property_audio_out_params(void *ctx, struct m_property *prop,
@@ -5483,26 +5471,25 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
return edit_filters_osd(mpctx, STREAM_VIDEO, cmd->args[0].v.s,
cmd->args[1].v.s, msg_osd);
+ case MP_CMD_AF_COMMAND:
case MP_CMD_VF_COMMAND: {
- if (!mpctx->vo_chain)
+ struct mp_output_chain *chain = NULL;
+ if (cmd->id == MP_CMD_VF_COMMAND) {
+ chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL;
+ } else {
+ chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL;
+ }
+ if (!chain)
return -1;
struct mp_filter_command filter_cmd = {
.type = MP_FILTER_COMMAND_TEXT,
.cmd = cmd->args[1].v.s,
.arg = cmd->args[2].v.s,
};
- return mp_output_chain_command(mpctx->vo_chain->filter, cmd->args[0].v.s,
- &filter_cmd) ? 0 : -1;
+ return mp_output_chain_command(chain, cmd->args[0].v.s, &filter_cmd)
+ ? 0 : -1;
}
-#if HAVE_LIBAF
- case MP_CMD_AF_COMMAND:
- if (!mpctx->ao_chain)
- return -1;
- return af_send_command(mpctx->ao_chain->af, cmd->args[0].v.s,
- cmd->args[1].v.s, cmd->args[2].v.s);
-#endif
-
case MP_CMD_SCRIPT_BINDING: {
mpv_event_client_message event = {0};
char *name = cmd->args[0].v.s;
diff --git a/player/core.h b/player/core.h
index c980e068fe..a4cffa5d09 100644
--- a/player/core.h
+++ b/player/core.h
@@ -201,8 +201,8 @@ struct ao_chain {
bool spdif_passthrough, spdif_failed;
bool pts_reset;
- struct af_stream *af;
- struct mp_aconverter *conv; // if af unavailable
+ struct mp_output_chain *filter;
+
struct ao *ao;
struct mp_audio_buffer *ao_buffer;
double ao_resume_time;
@@ -213,10 +213,7 @@ struct ao_chain {
// 1-element output frame queue.
struct mp_aframe *output_frame;
- // Last known input_mpi format (so af can be reinitialized any time).
- struct mp_aframe *input_format;
-
- struct mp_aframe *filter_input_format;
+ double last_out_pts;
struct track *track;
struct lavfi_pad *filter_src;
@@ -323,7 +320,7 @@ typedef struct MPContext {
struct mp_filter *filter_root;
struct ao *ao;
- struct mp_aframe *ao_decoder_fmt; // for weak gapless audio check
+ struct mp_aframe *ao_filter_fmt; // for weak gapless audio check
struct ao_chain *ao_chain;
struct vo_chain *vo_chain;
diff --git a/wscript_build.py b/wscript_build.py
index 228392068f..7aeac23dea 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -172,8 +172,6 @@ def build(ctx):
sources = [
## Audio
- ( "audio/aconverter.c" ),
- ( "audio/audio.c", "libaf" ),
( "audio/audio_buffer.c" ),
( "audio/chmap.c" ),
( "audio/chmap_sel.c" ),
@@ -183,14 +181,11 @@ def build(ctx):
( "audio/decode/ad_lavc.c" ),
( "audio/decode/ad_spdif.c" ),
( "audio/decode/dec_audio.c" ),
- ( "audio/filter/af.c", "libaf" ),
- ( "audio/filter/af_format.c", "libaf" ),
- ( "audio/filter/af_lavcac3enc.c", "libaf" ),
- ( "audio/filter/af_lavfi.c", "libaf" ),
- ( "audio/filter/af_lavrresample.c", "libaf" ),
+ ( "audio/filter/af_format.c" ),
+ ( "audio/filter/af_lavcac3enc.c" ),
+ ( "audio/filter/af_lavrresample.c" ),
( "audio/filter/af_rubberband.c", "rubberband" ),
- ( "audio/filter/af_scaletempo.c", "libaf" ),
- ( "audio/filter/tools.c", "libaf" ),
+ ( "audio/filter/af_scaletempo.c" ),
( "audio/out/ao.c" ),
( "audio/out/ao_alsa.c", "alsa" ),
( "audio/out/ao_audiounit.m", "audiounit" ),
@@ -258,6 +253,7 @@ def build(ctx):
( "filters/f_hwtransfer.c" ),
( "filters/f_lavfi.c" ),
( "filters/f_output_chain.c" ),
+ ( "filters/f_swresample.c" ),
( "filters/f_swscale.c" ),
( "filters/f_utils.c" ),
( "filters/filter.c" ),