summaryrefslogtreecommitdiffstats
path: root/filters
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-01-18 14:44:20 +0100
committerKevin Mitchell <kevmitch@gmail.com>2018-01-30 03:10:27 -0800
commitb9f804b566c4c528714e4ec5e63675ad7ba5fefd (patch)
tree49d6fcd42ce6597a67aa2af59b7f20beb21a2e14 /filters
parent76276c92104c31ee936ba5c76a76072f09978c5f (diff)
downloadmpv-b9f804b566c4c528714e4ec5e63675ad7ba5fefd.tar.bz2
mpv-b9f804b566c4c528714e4ec5e63675ad7ba5fefd.tar.xz
audio: rewrite filtering glue code
Use the new filtering code for audio too.
Diffstat (limited to 'filters')
-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.c717
-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
14 files changed, 1649 insertions, 21 deletions
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;
+ }
+
+ retu