From c0de087ba191a4daf3a152e0ab09b5687fab8449 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 5 Feb 2016 23:19:56 +0100 Subject: player: add complex filter graph support See --lavfi-complex option. This is still quite rough. There's no support for dynamic configuration of any kind. There are probably corner cases where playback might freeze or burn 100% CPU (due to dataflow problems when interaction with libavfilter). Future possible plans might include: - freely switch tracks by providing some sort of default track graph label - automatically enabling audio visualization - automatically mix audio or stack video when multiple tracks are selected at once (similar to how multiple sub tracks can be selected) --- DOCS/man/options.rst | 44 +++- options/options.c | 2 + options/options.h | 1 + player/audio.c | 76 ++++-- player/core.h | 17 ++ player/lavfi.c | 722 +++++++++++++++++++++++++++++++++++++++++++++++++++ player/lavfi.h | 32 +++ player/loadfile.c | 136 +++++++++- player/playloop.c | 53 ++++ player/video.c | 90 +++++-- wscript_build.py | 1 + 11 files changed, 1121 insertions(+), 53 deletions(-) create mode 100644 player/lavfi.c create mode 100644 player/lavfi.h diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index dc4d6ed3e0..97f84c6219 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1396,7 +1396,7 @@ Subtitles .. admonition:: Warning Enabling hinting can lead to mispositioned text (in situations it's - supposed to match up with video background), or reduce the smoothness + supposed to match up video background), or reduce the smoothness of animations with some badly authored ASS scripts. It is recommended to not use this option, unless really needed. @@ -3553,3 +3553,45 @@ Miscellaneous Force the contents of the ``media-title`` property to this value. Useful for scripts which want to set a title, without overriding the user's setting in ``--title``. + +``--lavfi-complex=`` + Set a "complex" libavfilter filter, which means a single filter graph can + take input from multiple source audio and video tracks. The graph can result + in a single audio or video output (or both). + + Currently, the filter graph labels are used to select the participating + input tracks and audio/video output. The following rules apply: + + - A label of the form ``aidN`` selects audio track N as input (e.g. + ``aid1``). + - A label of the form ``vidN`` selects video track N as input. + - A label named ``ao`` will be connected to the audio input. + - A label named ``vo`` will be connected to the video output. + + Each label can be used only once. If you want to use e.g. an audio stream + for multiple filters, you need to use the ``asplit`` filter. Multiple + video or audio outputs are not possible, but you can use filters to merge + them into one. + + The complex filter can not be changed yet during playback. It's also not + possible to change the tracks connected to the filter at runtime. Other + tracks, as long as they're not connected to the filter, and the + corresponding output is not connected to the filter, can still be freely + changed. + + .. admonition:: Examples + + - ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [vo]'`` + Play audio track 1, and visualize it as video using the ``aphasemeter`` + filter. + - ``--lavfi-complex='[vid1] [vid2] vstack [vo]'`` + Stack video track 1 and 2 and play them at the same time. Note that + both tracks need to have the same width, or filter initialization + will fail (you can add ``scale`` filters before the ``vstack`` filter + to fix the size). + - ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [t2] ; [vid1] [t2] overlay [vo]'`` + Play audio track 1, and overlay its visualization over video track 1. + + See the Ffmpeg libavfilter documentation for details on the filter. + + diff --git a/options/options.c b/options/options.c index 0377854c0e..4352b6ceed 100644 --- a/options/options.c +++ b/options/options.c @@ -226,6 +226,8 @@ const m_option_t mp_opts[] = { OPT_STRINGLIST("alang", stream_lang[STREAM_AUDIO], 0), OPT_STRINGLIST("slang", stream_lang[STREAM_SUB], 0), + OPT_STRING("lavfi-complex", lavfi_complex, 0), + OPT_CHOICE("audio-display", audio_display, 0, ({"no", 0}, {"attachment", 1})), diff --git a/options/options.h b/options/options.h index 6ce4be472a..488823ca15 100644 --- a/options/options.h +++ b/options/options.h @@ -186,6 +186,7 @@ typedef struct MPOpts { int ignore_path_in_watch_later_config; int pause; int keep_open; + char *lavfi_complex; int stream_id[2][STREAM_TYPE_COUNT]; int stream_id_ff[STREAM_TYPE_COUNT]; char **stream_lang[STREAM_TYPE_COUNT]; diff --git a/player/audio.c b/player/audio.c index 5ecbab5ad1..18f2818fe5 100644 --- a/player/audio.c +++ b/player/audio.c @@ -49,6 +49,7 @@ enum { AD_EOF = -2, AD_NEW_FMT = -3, AD_WAIT = -4, + AD_NO_PROGRESS = -5, }; // Use pitch correction only for speed adjustments by the user, not minor sync @@ -204,6 +205,18 @@ void uninit_audio_out(struct MPContext *mpctx) static void ao_chain_uninit(struct ao_chain *ao_c) { + struct track *track = ao_c->track; + if (track) { + assert(track->ao_c == ao_c); + track->ao_c = NULL; + assert(track->d_audio == ao_c->audio_src); + track->d_audio = NULL; + audio_uninit(ao_c->audio_src); + } + + if (ao_c->filter_src) + lavfi_set_connected(ao_c->filter_src, false); + af_destroy(ao_c->af); talloc_free(ao_c->input_frame); talloc_free(ao_c->ao_buffer); @@ -213,18 +226,10 @@ static void ao_chain_uninit(struct ao_chain *ao_c) void uninit_audio_chain(struct MPContext *mpctx) { if (mpctx->ao_chain) { - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - assert(track); - assert(track->d_audio == mpctx->ao_chain->audio_src); - mixer_uninit_audio(mpctx->mixer); - - audio_uninit(track->d_audio); - track->d_audio = NULL; - mpctx->ao_chain->audio_src = NULL; - ao_chain_uninit(mpctx->ao_chain); mpctx->ao_chain = NULL; + mpctx->audio_status = STATUS_EOF; reselect_demux_streams(mpctx); @@ -379,6 +384,9 @@ int init_audio_decoder(struct MPContext *mpctx, struct track *track) return 1; init_error: + if (track->sink) + lavfi_set_connected(track->sink, false); + track->sink = NULL; audio_uninit(track->d_audio); track->d_audio = NULL; error_on_track(mpctx, track); @@ -387,14 +395,24 @@ init_error: void reinit_audio_chain(struct MPContext *mpctx) { - assert(!mpctx->ao_chain); + reinit_audio_chain_src(mpctx, NULL); +} - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - struct sh_stream *sh = track ? track->stream : NULL; - if (!sh) { - uninit_audio_out(mpctx); - goto no_audio; +void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src) +{ + struct track *track = NULL; + struct sh_stream *sh = NULL; + if (!src) { + track = mpctx->current_track[0][STREAM_AUDIO]; + if (!track) + return; + sh = track ? track->stream : NULL; + if (!sh) { + uninit_audio_out(mpctx); + goto no_audio; + } } + assert(!mpctx->ao_chain); mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL); @@ -402,16 +420,21 @@ void reinit_audio_chain(struct MPContext *mpctx) mpctx->ao_chain = ao_c; ao_c->log = mpctx->log; ao_c->af = af_new(mpctx->global); - ao_c->af->replaygain_data = sh->codec->replaygain_data; + if (sh) + ao_c->af->replaygain_data = sh->codec->replaygain_data; 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; - if (!init_audio_decoder(mpctx, track)) - goto init_error; - - ao_c->audio_src = track->d_audio; + ao_c->filter_src = src; + if (!ao_c->filter_src) { + ao_c->track = track; + track->ao_c = ao_c; + if (!init_audio_decoder(mpctx, track)) + goto init_error; + ao_c->audio_src = track->d_audio; + } reset_audio_state(mpctx); @@ -597,8 +620,10 @@ static int decode_new_frame(struct ao_chain *ao_c) if (ao_c->input_frame) return AD_OK; - int res = DATA_AGAIN; - while (res == DATA_AGAIN) { + int res = DATA_EOF; + if (ao_c->filter_src) { + res = lavfi_request_frame_a(ao_c->filter_src, &ao_c->input_frame); + } else if (ao_c->audio_src) { audio_work(ao_c->audio_src); res = audio_get_frame(ao_c->audio_src, &ao_c->input_frame); } @@ -606,6 +631,7 @@ static int decode_new_frame(struct ao_chain *ao_c) switch (res) { case DATA_OK: return AD_OK; case DATA_WAIT: return AD_WAIT; + case DATA_AGAIN: return AD_NO_PROGRESS; case DATA_EOF: return AD_EOF; default: abort(); } @@ -633,6 +659,8 @@ static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf, break; res = decode_new_frame(ao_c); + if (res == AD_NO_PROGRESS) + break; if (res < 0) { // drain filters first (especially for true EOF case) copy_output(afs, outbuf, minsamples, true); @@ -764,6 +792,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts) status = filter_audio(mpctx->ao_chain, ao_c->ao_buffer, playsize); if (status == AD_WAIT) return; + if (status == AD_NO_PROGRESS) { + mpctx->sleeptime = 0; + 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 diff --git a/player/core.h b/player/core.h index df79dd8c01..72820c5c5a 100644 --- a/player/core.h +++ b/player/core.h @@ -31,6 +31,8 @@ #include "video/mp_image.h" #include "video/out/vo.h" +#include "lavfi.h" + // definitions used internally by the core player code enum stop_play_reason { @@ -149,6 +151,11 @@ struct track { struct dec_video *d_video; struct dec_audio *d_audio; + // Where the decoded result goes to (one of them is not NULL if active) + struct vo_chain *vo_c; + struct ao_chain *ao_c; + struct lavfi_pad *sink; + // For external subtitles, which are read fully on init. Do not attempt // to read packets from them. bool preloaded; @@ -170,6 +177,8 @@ struct vo_chain { // Last known input_mpi format (so vf can be reinitialized any time). struct mp_image_params input_format; + struct track *track; + struct lavfi_pad *filter_src; struct dec_video *video_src; // - video consists of a single picture, which should be shown only once @@ -195,6 +204,8 @@ struct ao_chain { // Last known input_mpi format (so vf can be reinitialized any time). struct mp_audio input_format; + struct track *track; + struct lavfi_pad *filter_src; struct dec_audio *audio_src; }; @@ -294,6 +305,8 @@ typedef struct MPContext { // Currently, this is used for the secondary subtitle track only. struct track *current_track[NUM_PTRACKS][STREAM_TYPE_COUNT]; + struct lavfi *lavfi; + // Uses: accessing metadata (consider ordered chapters case, where the main // demuxer defines metadata), or special purpose demuxers like TV. struct demuxer *master_demuxer; @@ -438,6 +451,8 @@ void clear_audio_output_buffers(struct MPContext *mpctx); void update_playback_speed(struct MPContext *mpctx); void uninit_audio_out(struct MPContext *mpctx); void uninit_audio_chain(struct MPContext *mpctx); +int init_audio_decoder(struct MPContext *mpctx, struct track *track); +void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src); // configfiles.c void mp_parse_cfgfiles(struct MPContext *mpctx); @@ -558,11 +573,13 @@ int video_vf_vo_control(struct vo_chain *vo_c, int vf_cmd, void *data); void reset_video_state(struct MPContext *mpctx); int init_video_decoder(struct MPContext *mpctx, struct track *track); int reinit_video_chain(struct MPContext *mpctx); +int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src); int reinit_video_filters(struct MPContext *mpctx); void write_video(struct MPContext *mpctx, double endpts); void mp_force_video_refresh(struct MPContext *mpctx); void uninit_video_out(struct MPContext *mpctx); void uninit_video_chain(struct MPContext *mpctx); double calc_average_frame_duration(struct MPContext *mpctx); +int init_video_decoder(struct MPContext *mpctx, struct track *track); #endif /* MPLAYER_MP_CORE_H */ diff --git a/player/lavfi.c b/player/lavfi.c new file mode 100644 index 0000000000..118a766ea2 --- /dev/null +++ b/player/lavfi.c @@ -0,0 +1,722 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "common/av_common.h" +#include "common/msg.h" + +#include "audio/audio.h" +#include "video/mp_image.h" +#include "audio/fmt-conversion.h" +#include "video/fmt-conversion.h" + +#include "lavfi.h" + +struct lavfi { + struct mp_log *log; + char *graph_string; + + AVFilterGraph *graph; + // Set to true once all inputs have been initialized, and the graph is + // linked. + bool initialized; + + // Set if all inputs have been marked as LAVFI_WAIT (except LAVFI_EOF pads). + bool all_waiting; + + // Graph is draining to undo previously sent EOF. (If a stream leaves EOF + // state, the graph needs to be recreated to "unstuck" it.) + bool draining_recover_eof; + // Graph is draining for format changes. + bool draining_new_format; + + // Filter can't be put into a working state. + bool failed; + + struct lavfi_pad **pads; + int num_pads; +}; + +struct lavfi_pad { + struct lavfi *main; + enum stream_type type; + enum lavfi_direction dir; + char *name; // user-given pad name + AVFrame *tmp_frame; + + bool connected; // if false, inputs/otuputs are considered always EOF + + AVFilterContext *filter; + int filter_pad; + // buffersrc or buffersink connected to filter/filter_pad + AVFilterContext *buffer; + AVRational timebase; + bool buffer_is_eof; // received/sent EOF to the buffer + + // 1-frame queue (used for both input and output) + struct mp_image *pending_v; + struct mp_audio *pending_a; + + // -- dir==LAVFI_IN + + bool input_needed; // filter has signaled it needs new input + bool input_waiting; // caller notified us that it will feed after a wakeup + bool input_again; // caller wants us to feed data in the next iteration + bool input_eof; // caller notified us that no input will come anymore + + // used to check for format changes manually + struct mp_image_params in_fmt_v; + struct mp_audio in_fmt_a; + + // -- dir==LAVFI_OUT + + bool output_needed; // caller has signaled it needs new output + bool output_eof; // last filter output was EOF +}; + +static void add_pad(struct lavfi *c, enum lavfi_direction dir, AVFilterInOut *item) +{ + int type = -1; + enum AVMediaType avmt; + if (dir == LAVFI_IN) { + avmt = avfilter_pad_get_type(item->filter_ctx->input_pads, item->pad_idx); + } else { + avmt = avfilter_pad_get_type(item->filter_ctx->output_pads, item->pad_idx); + } + switch (avmt) { + case AVMEDIA_TYPE_VIDEO: type = STREAM_VIDEO; break; + case AVMEDIA_TYPE_AUDIO: type = STREAM_AUDIO; break; + default: abort(); + } + + if (!item->name) { + MP_FATAL(c, "what the shit\n"); + return; + } + + struct lavfi_pad *p = lavfi_find_pad(c, item->name); + if (p) { + // Graph recreation case: reassociate an existing pad. + if (p->dir != dir || p->type != type) { + MP_FATAL(c, "pad '%s' changed type or direction\n", item->name); + return; + } + } else { + p = talloc_zero(c, struct lavfi_pad); + p->main = c; + p->dir = dir; + p->name = talloc_strdup(p, item->name); + p->tmp_frame = av_frame_alloc(); + if (!p->tmp_frame) + abort(); + p->type = type; + MP_TARRAY_APPEND(c, c->pads, c->num_pads, p); + } + p->filter = item->filter_ctx; + p->filter_pad = item->pad_idx; +} + +static void add_pads(struct lavfi *c, enum lavfi_direction dir, AVFilterInOut *list) +{ + for (; list; list = list->next) + add_pad(c, dir, list); +} + +// Parse the user-provided filter graph, and populate the unlinked filter pads. +static void precreate_graph(struct lavfi *c) +{ + assert(!c->graph); + c->graph = avfilter_graph_alloc(); + if (!c->graph) + abort(); + AVFilterInOut *in = NULL, *out = NULL; + if (avfilter_graph_parse2(c->graph, c->graph_string, &in, &out) < 0) { + c->graph = NULL; + MP_FATAL(c, "parsing the filter graph failed\n"); + c->failed = true; + return; + } + add_pads(c, LAVFI_IN, in); + add_pads(c, LAVFI_OUT, out); + avfilter_inout_free(&in); + avfilter_inout_free(&out); + + // Now check for pads which could not be reassociated. + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + // ok, not much we can do + if (!pad->filter) + MP_FATAL(c, "filter pad '%s' can not be reconnected\n", pad->name); + } +} + +static void free_graph(struct lavfi *c) +{ + avfilter_graph_free(&c->graph); + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + pad->filter = NULL; + pad->filter_pad = -1; + pad->buffer = NULL; + pad->in_fmt_v = (struct mp_image_params){0}; + pad->in_fmt_a = (struct mp_audio){0}; + pad->buffer_is_eof = false; + pad->input_needed = false; + pad->input_waiting = false; + pad->input_again = false; + pad->input_eof = false; + pad->output_needed = false; + pad->output_eof = false; + } + c->initialized = false; + c->all_waiting = false; + c->draining_recover_eof = false; + c->draining_new_format = false; +} + +static void drop_pad_data(struct lavfi_pad *pad) +{ + talloc_free(pad->pending_a); + pad->pending_a = NULL; + talloc_free(pad->pending_v); + pad->pending_v = NULL; +} + +static void clear_data(struct lavfi *c) +{ + for (int n = 0; n < c->num_pads; n++) + drop_pad_data(c->pads[n]); +} + +void lavfi_seek_reset(struct lavfi *c) +{ + free_graph(c); + clear_data(c); + precreate_graph(c); +} + +struct lavfi *lavfi_create(struct mp_log *log, char *graph_string) +{ + struct lavfi *c = talloc_zero(NULL, struct lavfi); + c->log = log; + c->graph_string = graph_string; + precreate_graph(c); + return c; +} + +void lavfi_destroy(struct lavfi *c) +{ + free_graph(c); + clear_data(c); + talloc_free(c); +} + +struct lavfi_pad *lavfi_find_pad(struct lavfi *c, char *name) +{ + for (int n = 0; n < c->num_pads; n++) { + if (strcmp(c->pads[n]->name, name) == 0) + return c->pads[n]; + } + return NULL; +} + +enum lavfi_direction lavfi_pad_direction(struct lavfi_pad *pad) +{ + return pad->dir; +} + +enum stream_type lavfi_pad_type(struct lavfi_pad *pad) +{ + return pad->type; +} + +void lavfi_set_connected(struct lavfi_pad *pad, bool connected) +{ + pad->connected = connected; +} + +bool lavfi_get_connected(struct lavfi_pad *pad) +{ + return pad->connected; +} + +// Ensure to send EOF to each input pad, so the graph can be drained properly. +static void send_global_eof(struct lavfi *c) +{ + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + if (!pad->buffer || pad->dir != LAVFI_IN || pad->buffer_is_eof) + continue; + + if (av_buffersrc_add_frame(pad->buffer, NULL) < 0) + MP_FATAL(c, "could not send EOF to filter\n"); + + pad->buffer_is_eof = true; + } +} + +// libavfilter allows changing some parameters on the fly, but not +// others. +static bool is_aformat_ok(struct mp_audio *a, struct mp_audio *b) +{ + return mp_audio_config_equals(a, b); +} +static bool is_vformat_ok(struct mp_image_params *a, struct mp_image_params *b) +{ + return a->imgfmt == b->imgfmt && + a->w == b->w && a->h && b->h && + a->p_w == b->p_w && a->p_h == b->p_h; +} + +static void check_format_changes(struct lavfi *c) +{ + // check each pad for new input format + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + if (!pad->buffer || pad->dir != LAVFI_IN) + continue; + + if (pad->type == STREAM_AUDIO && pad->pending_a && pad->in_fmt_a.format) { + c->draining_new_format |= !is_aformat_ok(pad->pending_a, + &pad->in_fmt_a); + } + if (pad->type == STREAM_VIDEO && pad->pending_v && pad->in_fmt_v.imgfmt) { + c->draining_new_format |= !is_vformat_ok(&pad->pending_v->params, + &pad->in_fmt_v); + } + } + + if (c->initialized && c->draining_new_format) + send_global_eof(c); +} + +// Attempt to initialize all pads. Return true if all are initialized, or +// false if more data is needed (or on error). +static bool init_pads(struct lavfi *c) +{ + if (!c->graph) + goto error; + + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + if (pad->buffer) + continue; + + if (!pad->filter) + goto error; // can happen if pad reassociation fails + + if (pad->dir == LAVFI_OUT) { + AVFilter *dst_filter; + if (pad->type == STREAM_AUDIO) { + dst_filter = avfilter_get_by_name("abuffersink"); + } else if (pad->type == STREAM_VIDEO) { + dst_filter = avfilter_get_by_name("buffersink"); + } else { + assert(0); + } + + char name[256]; + snprintf(name, sizeof(name), "mpv_sink_%s", pad->name); + + if (avfilter_graph_create_filter(&pad->buffer, dst_filter, + name, NULL, NULL, c->graph) < 0) + goto error; + + if (avfilter_link(pad->filter, pad->filter_pad, pad->buffer, 0) < 0) + goto error; + } else { + char src_args[256]; + AVFilter *src_filter; + + pad->input_eof |= !pad->connected; + + if (pad->pending_a) { + assert(pad->type == STREAM_AUDIO); + mp_audio_copy_config(&pad->in_fmt_a, pad->pending_a); + } else if (pad->pending_v) { + assert(pad->type == STREAM_VIDEO); + pad->in_fmt_v = pad->pending_v->params; + } else if (pad->input_eof) { + // libavfilter makes this painful. Init it with a dummy config, + // just so we can tell it the stream is EOF. + if (pad->type == STREAM_AUDIO) { + mp_audio_set_format(&pad->in_fmt_a, AF_FORMAT_FLOAT); + mp_audio_set_num_channels(&pad->in_fmt_a, 2); + pad->in_fmt_a.rate = 48000; + } else if (pad->type == STREAM_VIDEO) { + pad->in_fmt_v = (struct mp_image_params){ + .imgfmt = IMGFMT_420P, + .w = 64, .h = 64, + .p_w = 1, .p_h = 1, + }; + } + } else { + // no input data, format unknown, can't init, wait longer. + pad->input_needed = true; + return false; + } + + if (pad->type == STREAM_AUDIO) { + pad->timebase = (AVRational){1, pad->in_fmt_a.rate}; + snprintf(src_args, sizeof(src_args), + "sample_rate=%d:sample_fmt=%s:time_base=%d/%d:" + "channel_layout=0x%"PRIx64, pad->in_fmt_a.rate, + av_get_sample_fmt_name(af_to_avformat(pad->in_fmt_a.format)), + pad->timebase.num, pad->timebase.den, + mp_chmap_to_lavc(&pad->in_fmt_a.channels)); + src_filter = avfilter_get_by_name("abuffer"); + } else if (pad->type == STREAM_VIDEO) { + pad->timebase = AV_TIME_BASE_Q; + snprintf(src_args, sizeof(src_args), "%d:%d:%d:%d:%d:%d:%d", + pad->in_fmt_v.w, pad->in_fmt_v.h, + imgfmt2pixfmt(pad->in_fmt_v.imgfmt), + pad->timebase.num, pad->timebase.den, + pad->in_fmt_v.p_w, pad->in_fmt_v.p_h); + src_filter = avfilter_get_by_name("buffer"); + } else { + assert(0); + } + + char name[256]; + snprintf(name, sizeof(name), "mpv_src_%s", pad->name); + + if (avfilter_graph_create_filter(&pad->buffer, src_filter, + name, src_args, NULL, c->graph) < 0) + goto error; + + if (avfilter_link(pad->buffer, 0, pad->filter, pad->filter_pad) < 0) + goto error; + } + } + + return true; +error: + MP_FATAL(c, "could not initialize filter pads\n"); + c->failed = true; + return false; +} + +// Initialize the graph if all inputs have formats set. If it's already +// initialized, or can't be initialized yet, do nothing. +static void init_graph(struct lavfi *c) +{ + assert(!c->initialized); + + if (init_pads(c)) { + // And here the actual libavfilter initialization happens. + if (avfilter_graph_config(c->graph, NULL) < 0) { + MP_FATAL(c, "failed to configure the filter graph\n"); + free_graph(c); + c->failed = true; + return; + } + + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + if (pad->dir == LAVFI_OUT) + pad->timebase = pad->buffer->inputs[0]->time_base; + } + + c->initialized = true; + } +} + +static void feed_input_pads(struct lavfi *c) +{ + assert(c->initialized); + + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + if (pad->dir != LAVFI_IN) + continue; + + pad->input_needed = false; + pad->input_eof |= !pad->connected; + + if (!av_buffersrc_get_nb_failed_requests(pad->buffer)) + continue; + + if (c->draining_recover_eof || c->draining_new_format) + continue; + + if (pad->buffer_is_eof) + continue; + + AVFrame *frame = NULL; + double pts = 0; + bool eof = false; + if (pad->pending_v) { + pts = pad->pending_v->pts; + frame = mp_image_to_av_frame_and_unref(pad->pending_v); + pad->pending_v = NULL; + } else if (pad->pending_a) { + pts = pad->pending_a->pts; + frame = mp_audio_to_avframe_and_unref(pad->pending_a); + pad->pending_a = NULL; + } else { + if (!pad->input_eof) { + pad->input_needed = true; + continue; + } + eof = true; + } + + if (!frame && !eof) { + MP_FATAL(c, "out of memory or unsupported format\n"); + continue; + } + + if (frame) + frame->pts = mp_pts_to_av(pts, &pad->timebase); + + pad->buffer_is_eof = !frame; + + if (av_buffersrc_add_frame(pad->buffer, frame) < 0) + MP_FATAL(c, "could not pass frame to filter\n"); + av_frame_free(&frame); + + pad->input_waiting = pad->input_again = false; + pad->input_eof = eof; + } +} + +static void read_output_pads(struct lavfi *c) +{ + assert(c->initialized); + + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + + if (pad->dir != LAVFI_OUT) + continue; + + // If disconnected, read and discard everything. + if (!pad->pending_v && !pad->pending_a && !pad->connected) + pad->output_needed = true; + + if (!pad->output_needed) + continue; + + assert(pad->buffer); + assert(!pad->pending_v && !pad->pending_a); + + int r = AVERROR(EAGAIN); + if (!pad->buffer_is_eof) + r = av_buffersink_get_frame(pad->buffer, pad->tmp_frame); + if (r >= 0) { + pad->output_needed = false; + double pts = mp_pts_from_av(pad->tmp_frame->pts, &pad->timebase); + if (pad->type == STREAM_AUDIO) { + pad->pending_a = mp_audio_from_avframe(pad->tmp_frame); + if (pad->pending_a) + pad->pending_a->pts = pts; + } else if (pad->type == STREAM_VIDEO) { + pad->pending_v = mp_image_from_av_frame(pad->tmp_frame); + if (pad->pending_v) + pad->pending_v->pts = pts; + } else { + assert(0); + } + av_frame_unref(pad->tmp_frame); + if (!pad->pending_v && !pad->pending_a) + MP_ERR(c, "could not use filter output\n"); + pad->output_eof = false; + if (!pad->connected) + drop_pad_data(pad); + } else if (r == AVERROR(EAGAIN)) { + // We expect that libavfilter will request input on one of the + // input pads (via av_buffersrc_get_nb_failed_requests()). + pad->output_eof = false; + } else if (r == AVERROR_EOF) { + pad->buffer_is_eof = true; + if (!c->draining_recover_eof && !c->draining_new_format) + pad->output_eof = true; + } else { + // Real error - ignore it. + MP_ERR(c, "error on filtering (%d)\n", r); + } + } +} + +// Process filter input and outputs. Return if progress was made (then the +// caller should repeat). If it returns false, the caller should go to sleep +// (as all inputs are asleep as well and no further output can be produced). +bool lavfi_process(struct lavfi *c) +{ + check_format_changes(c); + + if (!c->initialized) + init_graph(c); + + if (c->initialized) { + read_output_pads(c); + feed_input_pads(c); + } + + bool all_waiting = true; + bool any_needs_input = false; + bool any_needs_output = false; + bool all_lavfi_eof = true; + bool all_input_eof = true; + + // Determine the graph state + for (int n = 0; n < c->num_pads; n++) { + struct lavfi_pad *pad = c->pads[n]; + + if (pad->dir == LAVFI_IN) { + all_waiting &= pad->input_waiting; + any_needs_input |= pad->input_needed; + all_input_eof &= pad->input_eof; + } else if (pad->dir == LAVFI_OUT) { + all_lavfi_eof &= pad->buffer_is_eof; + any_needs_output |= pad->output_needed; + } + } + + if (all_lavfi_eof && !all_input_eof) { + free_graph(c); + precreate_graph(c); + all_waiting = false; + any_needs_input = true; + } + + c->all_waiting = all_waiting; + return (any_needs_input || any_needs_output) && !all_waiting; +} + +bool lavfi_has_failed(struct lavfi *c) +{ + return c->failed; +} + +// Request an output frame on this output pad. +// Returns req_status +static int lavfi_request_frame(struct lavfi_pad *pad) +{ + assert(pad->dir == LAVFI_OUT); + + if (pad->main->failed) + return DATA_EOF; + + if (!(pad->pending_a || pad->pending_v)) { + pad->output_needed = true; + lavfi_process(pad->main); + } + + if (pad->pending_a || pad->pending_v) { + return DATA_OK; + } else if (pad->output_eof) { + return DATA_EOF; + } else if (pad->main->all_waiting) { + return DATA_WAIT; + } + return DATA_AGAIN; +} + +// Try to read a new frame from an output pad. Returns one of the following: +// DATA_OK: a frame is returned +// DATA_AGAIN: needs more input data +// DATA_WAIT: needs more input data, and all inputs in LAVFI_WAIT state +// DATA_EOF: no more data +int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_audio **out_aframe) +{ + int r = lavfi_request_frame(pad); + *out_aframe = pad->pending_a; + pad->pending_a = NULL; + return r; +} + +// See lavfi_request_frame_a() for remarks. +int lavfi_request_frame_v(struct lavfi_pad *pad, struct mp_image **out_vframe) +{ + int r = lavfi_request_frame(pad); + *out_vframe = pad->pending_v; + pad->pending_v = NULL; + return r; +} + +bool lavfi_needs_input(struct lavfi_pad *pad) +{ + assert(pad->dir == LAVFI_IN); + lavfi_process(pad->main); + return pad->input_needed; +} + +// A filter user is supposed to call lavfi_needs_input(), and if that returns +// true, send either a new status or a frame. A status can be one of: +// DATA_AGAIN: a new frame/status will come, caller will retry +// DATA_WAIT: a new frame/status will come, but caller goes to sleep +// DATA_EOF: no more input possible (in near time) +// If you have a new frame, use lavfi_send_frame_ instead. +// Calling this without lavfi_needs_input() returning true before is not +// allowed. +void lavfi_send_status(struct lavfi_pad *pad, int status) +{ + assert(pad->dir == LAVFI_IN); + assert(pad->input_needed); + assert(status != DATA_OK); + assert(!pad->pending_v && !pad->pending_a); + + pad->input_waiting = status == DATA_WAIT; + pad->input_again = status == DATA_AGAIN; + pad->input_eof = status == DATA_EOF; +} + +static void lavfi_sent_frame(struct lavfi_pad *pad) +{ + assert(pad->dir == LAVFI_IN); + assert(pad->input_needed); + assert(pad->pending_a || pad->pending_v); + pad->input_waiting = pad->input_again = pad->input_eof = false; + pad->input_needed = false; +} + +// See lavfi_send_status() for remarks. +void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_audio *aframe) +{ + assert(pad->type == STREAM_AUDIO); + assert(!pad->pending_a); + pad->pending_a = aframe; + lavfi_sent_frame(pad); +} + +// See lavfi_send_status() for remarks. +void lavfi_send_frame_v(struct lavfi_pad *pad, struct mp_image *vframe) +{ + assert(pad->type == STREAM_VIDEO); + assert(!pad->pending_v); + pad->pending_v = vframe; + lavfi_sent_frame(pad); +} + diff --git a/player/lavfi.h b/player/lavfi.h new file mode 100644 index 0000000000..d39ecb0219 --- /dev/null +++ b/player/lavfi.h @@ -0,0 +1,32 @@ +#ifndef MP_LAVFI +#define MP_LAVFI + +struct mp_log; +struct lavfi; +struct lavfi_pad; +struct mp_image; +struct mp_audio; + +enum lavfi_direction { + LAVFI_IN = 1, + LAVFI_OUT, +}; + +struct lavfi *lavfi_create(struct mp_log *log, char *graph_string); +void lavfi_destroy(struct lavfi *c); +struct lavfi_pad *lavfi_find_pad(struct lavfi *c, char *name); +enum lavfi_direction lavfi_pad_direction(struct lavfi_pad *pad); +enum stream_type lavfi_pad_type(struct lavfi_pad *pad); +void lavfi_set_connected(struct lavfi_pad *pad, bool connected); +bool lavfi_get_connected(struct lavfi_pad *pad); +bool lavfi_process(struct lavfi *c); +bool lavfi_has_failed(struct lavfi *c); +void lavfi_seek_reset(struct lavfi *c); +int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_audio **out_aframe); +int lavfi_request_frame_v(struct lavfi_pad *pad, struct mp_image **out_vframe); +bool lavfi_needs_input(struct lavfi_pad *pad); +void lavfi_send_status(struct lavfi_pad *pad, int status); +void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_audio *aframe); +void lavfi_send_frame_v(struct lavfi_pad *pad, struct mp_image *vframe); + +#endif diff --git a/player/loadfile.c b/player/loadfile.c index 57c3588b9f..7ed4cb6201 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -564,6 +564,17 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type if (track == current) return; + if (current && current->sink) { + MP_ERR(mpctx, "Can't disable input to complex filter.\n"); + return; + } + if ((type == STREAM_VIDEO && mpctx->vo_chain && !mpctx->vo_chain->track) || + (type == STREAM_AUDIO && mpctx->ao_chain && !mpctx->ao_chain->track)) + { + MP_ERR(mpctx, "Can't switch away from complex filter output.\n"); + return; + } + if (track && track->selected) { // Track has been selected in a different order parameter. MP_ERR(mpctx, "Track %d is already selected.\n", track->user_tid); @@ -1016,6 +1027,115 @@ static void load_timeline(struct MPContext *mpctx) print_timeline(mpctx); } +static void init_complex_filters(struct MPContext *mpctx) +{ + assert(!mpctx->lavfi); + + char *graph = mpctx->opts->lavfi_complex; + + if (!graph || !graph[0]) + return; + + if (mpctx->tl) { + MP_ERR(mpctx, "complex filters not supported with timeline\n"); + return; + } + + mpctx->lavfi = lavfi_create(mpctx->log, graph); + if (!mpctx->lavfi) + return; + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + + char label[32]; + char prefix; + switch (track->type) { + case STREAM_VIDEO: prefix = 'v'; break; + case STREAM_AUDIO: prefix = 'a'; break; + default: continue; + } + snprintf(label, sizeof(label), "%cid%d", prefix, track->user_tid); + + struct lavfi_pad *pad = lavfi_find_pad(mpctx->lavfi, label); + if (!pad) + continue; + if (lavfi_pad_type(pad) != track->type) + continue; + if (lavfi_pad_direction(pad) != LAVFI_IN) + continue; + if (lavfi_get_connected(pad)) + continue; + + track->sink = pad; + lavfi_set_connected(pad, true); + track->selected = true; + } + + struct lavfi_pad *pad = lavfi_find_pad(mpctx->lavfi, "vo"); + if (pad && lavfi_pad_type(pad) == STREAM_VIDEO && + lavfi_pad_direction(pad) == LAVFI_OUT) + { + lavfi_set_connected(pad, true); + reinit_video_chain_src(mpctx, pad); + } + + pad = lavfi_find_pad(mpctx->lavfi, "ao"); + if (pad && lavfi_pad_type(pad) == STREAM_AUDIO && + lavfi_pad_direction(pad) == LAVFI_OUT) + { + lavfi_set_connected(pad, true); + reinit_audio_chain_src(mpctx, pad); + } +} + +static bool init_complex_filter_decoders(struct MPContext *mpctx) +{ + if (!mpctx->lavfi) + return true; + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->sink && track->type == STREAM_VIDEO) { + if (!init_video_decoder(mpctx, track)) + return false; + } + if (track->sink && track->type == STREAM_AUDIO) { + if (!init_audio_decoder(mpctx, track)) + return false; + } + } + + return true; +} + +static void uninit_complex_filters(struct MPContext *mpctx) +{ + if (!mpctx->lavfi) + return; + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + + if (track->d_video && !track->vo_c) { + video_uninit(track->d_video); + track->d_video = NULL; + } + if (track->d_audio && !track->ao_c) { + audio_uninit(track->d_audio); + track->d_audio = NULL; + } + } + + if (mpctx->vo_chain && mpctx->vo_chain->filter_src) + uninit_video_chain(mpctx); + if (mpctx->ao_chain && mpctx->ao_chain->filter_src) + uninit_audio_chain(mpctx); + + lavfi_destroy(mpctx->lavfi); + mpctx->lavfi = NULL; +} + // Start playing the current playlist entry. // Handle initialization and deinitialization. static void play_current_file(struct MPContext *mpctx) @@ -1133,10 +1253,18 @@ reopen_file: check_previous_track_selection(mpctx); + init_complex_filters(mpctx); + assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2 for (int t = 0; t < STREAM_TYPE_COUNT; t++) { - for (int i = 0; i < NUM_PTRACKS; i++) - mpctx->current_track[i][t] = select_default_track(mpctx, i, t); + for (int i = 0; i < NUM_PTRACKS; i++) { + struct track *sel = NULL; + bool taken = (t == STREAM_VIDEO && mpctx->vo_chain) || + (t == STREAM_AUDIO && mpctx->ao_chain); + if (!taken) + sel = select_default_track(mpctx, i, t); + mpctx->current_track[i][t] = sel; + } } for (int t = 0; t < STREAM_TYPE_COUNT; t++) { for (int i = 0; i < NUM_PTRACKS; i++) { @@ -1171,6 +1299,9 @@ reopen_file: update_playback_speed(mpctx); + if (!init_complex_filter_decoders(mpctx)) + goto terminate_playback; + reinit_video_chain(mpctx); reinit_audio_chain(mpctx); reinit_sub_all(mpctx); @@ -1239,6 +1370,7 @@ terminate_playback: mp_cancel_trigger(mpctx->playback_abort); // time to uninit all, except global stuff: + uninit_complex_filters(mpctx); uninit_audio_chain(mpctx); uninit_video_chain(mpctx); uninit_sub_all(mpctx); diff --git a/player/playloop.c b/player/playloop.c index 6f4521b32a..9fd465b6a2 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -151,6 +151,16 @@ void add_step_frame(struct MPContext *mpctx, int dir) // Clear some playback-related fields on file loading or after seeks. void reset_playback_state(struct MPContext *mpctx) { + if (mpctx->lavfi) + lavfi_seek_reset(mpctx->lavfi); + + for (int n = 0; n < mpctx->num_tracks; n++) { + if (mpctx->tracks[n]->d_video) + video_reset(mpctx->tracks[n]->d_video); + if (mpctx->tracks[n]->d_audio) + audio_reset_decoding(mpctx->tracks[n]->d_audio); + } + reset_video_state(mpctx); reset_audio_state(mpctx); reset_subtitle_state(mpctx); @@ -922,6 +932,40 @@ static void handle_segment_switch(struct MPContext *mpctx, bool end_is_new_segme } } +static void handle_complex_filter_decoders(struct MPContext *mpctx) +{ + if (!mpctx->lavfi) + return; + + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (!track->selected) + continue; + if (!track->sink || !lavfi_needs_input(track->sink)) + continue; + if (track->d_audio) { + audio_work(track->d_audio); + struct mp_audio *fr; + int res = audio_get_frame(track->d_audio, &fr); + if (res == DATA_OK) { + lavfi_send_frame_a(track->sink, fr); + } else { + lavfi_send_status(track->sink, res); + } + } + if (track->d_video) { + video_work(track->d_video); + struct mp_image *fr; + int res = video_get_frame(track->d_video, &fr); + if (res == DATA_OK) { + lavfi_send_frame_v(track->sink, fr); + } else { + lavfi_send_status(track->sink, res); + } + } + } +} + void run_playloop(struct MPContext *mpctx) { double endpts = get_play_end_pts(mpctx); @@ -944,6 +988,8 @@ void run_playloop(struct MPContext *mpctx) } } + handle_complex_filter_decoders(mpctx); + handle_cursor_autohide(mpctx); handle_vo_events(mpctx); handle_heartbeat_cmd(mpctx); @@ -952,6 +998,13 @@ void run_playloop(struct MPContext *mpctx) fill_audio_out_buffers(mpctx, endpts); write_video(mpctx, endpts); + if (mpctx->lavfi) { + if (lavfi_process(mpctx->lavfi)) + mpctx->sleeptime = 0; + if (lavfi_has_failed(mpctx->lavfi)) + mpctx->stop_play = AT_END_OF_FILE; + } + handle_playback_restart(mpctx, endpts); // Use the audio timestamp if no video, or video is enabled, but has ended. diff --git a/player/video.c b/player/video.c index 4df51b1128..17e8f7d987 100644 --- a/player/video.c +++ b/player/video.c @@ -279,6 +279,18 @@ void uninit_video_out(struct MPContext *mpctx) static void vo_chain_uninit(struct vo_chain *vo_c) { + struct track *track = vo_c->track; + if (track) { + assert(track->vo_c == vo_c); + track->vo_c = NULL; + assert(track->d_video == vo_c->video_src); + track->d_video = NULL; + video_uninit(vo_c->video_src); + } + + if (vo_c->filter_src) + lavfi_set_connected(vo_c->filter_src, false); + mp_image_unrefp(&vo_c->input_mpi); vf_destroy(vo_c->vf); talloc_free(vo_c); @@ -288,18 +300,10 @@ static void vo_chain_uninit(struct vo_chain *vo_c) void uninit_video_chain(struct MPContext *mpctx) { if (mpctx->vo_chain) { - struct track *track = mpctx->current_track[0][STREAM_VIDEO]; - assert(track); - assert(track->d_video == mpctx->vo_chain->video_src); - reset_video_state(mpctx); - - video_uninit(track->d_video); - track->d_video = NULL; - mpctx->vo_chain->video_src = NULL; - vo_chain_uninit(mpctx->vo_chain); mpctx->vo_chain = NULL; + mpctx->video_status = STATUS_EOF; reselect_demux_streams(mpctx); remove_deint_filter(mpctx); @@ -337,6 +341,9 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track) return 1; err_out: + if (track->sink) + lavfi_set_connected(track->sink, false); + track->sink = NULL; video_uninit(track->d_video); track->d_video = NULL; error_on_track(mpctx, track); @@ -344,13 +351,24 @@ err_out: } int reinit_video_chain(struct MPContext *mpctx) +{ + return reinit_video_chain_src(mpctx, NULL); +} + +int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src) { struct MPOpts *opts = mpctx->opts; + struct track *track = NULL; + struct sh_stream *sh = NULL; + if (!src) { + track = mpctx->current_track[0][STREAM_VIDEO]; + if (!track) + return 0; + sh = track ? track->stream : NULL; + if (!sh) + goto no_video; + } assert(!mpctx->vo_chain); - struct track *track = mpctx->current_track[0][STREAM_VIDEO]; - struct sh_stream *sh = track ? track->stream : NULL; - if (!sh) - goto no_video; if (!mpctx->video_out) { struct vo_extra ex = { @@ -378,12 +396,20 @@ int reinit_video_chain(struct MPContext *mpctx) vo_control(vo_c->vo, VOCTRL_GET_HWDEC_INFO, &vo_c->hwdec_info); - if (!init_video_decoder(mpctx, track)) - goto err_out; + vo_c->filter_src = src; + if (!vo_c->filter_src) { + vo_c->track = track; + track->vo_c = vo_c; + if (!init_video_decoder(mpctx, track)) + goto err_out; - vo_c->video_src = track->d_video; - vo_c->container_fps = vo_c->video_src->fps; - vo_c->is_coverart = !!sh->attached_picture; + vo_c->video_src = track->d_video; + vo_c->container_fps = vo_c->video_src->fps; + vo_c->is_coverart = !!sh->attached_picture; + + track->vo_c = vo_c; + vo_c->track = track; + } #if HAVE_ENCODING if (mpctx->encode_lavc_ctx) @@ -460,22 +486,30 @@ static bool check_framedrop(struct MPContext *mpctx, struct vo_chain *vo_c) static int decode_image(struct MPContext *mpctx) { struct vo_chain *vo_c = mpctx->vo_chain; - struct dec_video *d_video = vo_c->video_src; + if (vo_c->input_mpi) + return VD_PROGRESS; - bool hrseek = mpctx->hrseek_active && mpctx->video_status == STATUS_SYNCING && - mpctx->hrseek_framedrop; - video_set_start(d_video, hrseek ? mpctx->hrseek_pts : MP_NOPTS_VALUE); + int res = DATA_EOF; + if (vo_c->filter_src) { + res = lavfi_request_frame_v(vo_c->filter_src, &vo_c->input_mpi); + } else if (vo_c->video_src) { + struct dec_video *d_video = vo_c->video_src; + bool hrseek = mpctx->hrseek_active && mpctx->hrseek_framedrop && + mpctx->video_status == STATUS_SYNCING; + video_set_start(d_video, hrseek ? mpctx->hrseek_pts : MP_NOPTS_VALUE); - video_set_framedrop(d_video, check_framedrop(mpctx, vo_c)); + video_set_framedrop(d_video, check_framedrop(mpctx, vo_c)); - video_work(d_video); + video_work(d_video); + res = video_get_frame(d_video, &vo_c->input_mpi); + } - assert(!vo_c->input_mpi); - int st = video_get_frame(d_video, &vo_c->input_mpi); - switch (st) { + switch (res) { case DATA_WAIT: return VD_WAIT; + case DATA_OK: + case DATA_AGAIN: return VD_PROGRESS; case DATA_EOF: return VD_EOF; - default: return VD_PROGRESS; + default: abort(); } } diff --git a/wscript_build.py b/wscript_build.py index 7295384815..d06a80f1e6 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -217,6 +217,7 @@ def build(ctx): ( "player/loadfile.c" ), ( "player/main.c" ), ( "player/misc.c" ), + ( "player/lavfi.c" ), ( "player/lua.c", "lua" ), ( "player/osd.c" ), ( "player/playloop.c" ), -- cgit v1.2.3