diff options
author | wm4 <wm4@nowhere> | 2018-01-18 14:44:20 +0100 |
---|---|---|
committer | Kevin Mitchell <kevmitch@gmail.com> | 2018-01-30 03:10:27 -0800 |
commit | b9f804b566c4c528714e4ec5e63675ad7ba5fefd (patch) | |
tree | 49d6fcd42ce6597a67aa2af59b7f20beb21a2e14 /filters | |
parent | 76276c92104c31ee936ba5c76a76072f09978c5f (diff) | |
download | mpv-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.c | 90 | ||||
-rw-r--r-- | filters/f_auto_filters.h | 3 | ||||
-rw-r--r-- | filters/f_autoconvert.c | 159 | ||||
-rw-r--r-- | filters/f_autoconvert.h | 11 | ||||
-rw-r--r-- | filters/f_lavfi.c | 101 | ||||
-rw-r--r-- | filters/f_output_chain.c | 355 | ||||
-rw-r--r-- | filters/f_output_chain.h | 27 | ||||
-rw-r--r-- | filters/f_swresample.c | 717 | ||||
-rw-r--r-- | filters/f_swresample.h | 42 | ||||
-rw-r--r-- | filters/f_utils.c | 118 | ||||
-rw-r--r-- | filters/f_utils.h | 6 | ||||
-rw-r--r-- | filters/filter.h | 5 | ||||
-rw-r--r-- | filters/user_filters.c | 29 | ||||
-rw-r--r-- | filters/user_filters.h | 7 |
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; + } + |