From 795b65b2ffadcaa8d5a4e56340e9f70e0fac9e39 Mon Sep 17 00:00:00 2001 From: Martin Herkt Date: Mon, 15 Aug 2016 16:16:17 +0200 Subject: DOCS: Update version --- DOCS/client-api-changes.rst | 2 +- DOCS/interface-changes.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index 18c9657149..17fc4bd60a 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -32,7 +32,7 @@ API changes :: - --- mpv 0.18.2 + --- mpv 0.19.0 1.22 - add stream_cb API for custom protocols --- mpv 0.18.1 --- ---- - remove "status" log level from mpv_request_log_messages() docs. This diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 8789cd943c..d7db0106b4 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -19,7 +19,7 @@ Interface changes :: - --- mpv 0.18.2 --- + --- mpv 0.19.0 --- - deprecate "balance" option/property (no replacement) --- mpv 0.18.1 --- - deprecate --heartbeat-cmd -- cgit v1.2.3 From 86fa1e6129b89a59492d7eb4b84e52dbec3d49ad Mon Sep 17 00:00:00 2001 From: wm4 Date: Mon, 15 Aug 2016 21:07:32 +0200 Subject: player: allow passing flags to queue_seek() Change the last parameter from a bool to an int, which is supposed to take bit-flags. The at this point only flag is MPSEEK_FLAG_DELAY, which replaces the previous bool parameter. The old false parameter becomes 0, the old true parameter becomes MPSEEK_FLAG_DELAY. Since the old "immediate" parameter is now essentially inverted, two coalesced immediate and delayed seeks end up as delayed instead of immediate. This change doesn't matter, since there are no relative immediate seeks anyway. --- player/audio.c | 2 +- player/command.c | 26 ++++++++++++++------------ player/core.h | 8 ++++++-- player/loadfile.c | 2 +- player/playloop.c | 15 ++++++++------- player/video.c | 2 +- 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/player/audio.c b/player/audio.c index 5b52eceb11..9fe7eb802a 100644 --- a/player/audio.c +++ b/player/audio.c @@ -229,7 +229,7 @@ int reinit_audio_filters(struct MPContext *mpctx) mpctx->playback_pts != MP_NOPTS_VALUE && delay > 0.2) { queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->playback_pts, - MPSEEK_EXACT, true); + MPSEEK_EXACT, 0); } return 1; } diff --git a/player/command.c b/player/command.c index ac97e1022d..f941e864dc 100644 --- a/player/command.c +++ b/player/command.c @@ -649,7 +649,7 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop, switch (action) { case M_PROPERTY_SET: { double pos = *(double *)arg; - queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, MPSEEK_DEFAULT, 0); return M_PROPERTY_OK; } case M_PROPERTY_GET: { @@ -694,7 +694,7 @@ static int mp_property_time_pos(void *ctx, struct m_property *prop, return M_PROPERTY_UNAVAILABLE; if (action == M_PROPERTY_SET) { - queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0); return M_PROPERTY_OK; } return property_time(action, arg, get_current_time(mpctx)); @@ -743,7 +743,7 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop, return M_PROPERTY_UNAVAILABLE; if (action == M_PROPERTY_SET) { - queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0); return M_PROPERTY_OK; } return property_time(action, arg, get_playback_time(mpctx)); @@ -844,7 +844,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop, } else { double pts = chapter_start_time(mpctx, chapter); if (pts != MP_NOPTS_VALUE) { - queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, 0); mpctx->last_chapter_seek = chapter; mpctx->last_chapter_pts = pts; } @@ -2210,7 +2210,7 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop, video_vd_control(vd, VDCTRL_REINIT, NULL); double last_pts = mpctx->last_vo_pts; if (last_pts != MP_NOPTS_VALUE) - queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0); } return M_PROPERTY_OK; } @@ -4622,19 +4622,19 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re mark_seek(mpctx); switch (abs) { case 0: { // Relative seek - queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, false); + queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, MPSEEK_FLAG_DELAY); set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW); break; } case 1: { // Absolute seek by percentage double ratio = v / 100.0; double cur_pos = get_current_pos_ratio(mpctx, false); - queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, false); + queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, MPSEEK_FLAG_DELAY); set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW); break; } case 2: { // Absolute seek to a timestamp in seconds - queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, false); + queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, MPSEEK_FLAG_DELAY); set_osd_function(mpctx, v > get_current_time(mpctx) ? OSD_FFW : OSD_REW); break; @@ -4642,7 +4642,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re case 3: { // Relative seek by percentage queue_seek(mpctx, MPSEEK_FACTOR, get_current_pos_ratio(mpctx, false) + v / 100.0, - precision, false); + precision, MPSEEK_FLAG_DELAY); set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW); break; }} @@ -4664,7 +4664,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re } else if (oldpts != MP_NOPTS_VALUE) { cmdctx->last_seek_pts = get_current_time(mpctx); cmdctx->marked_pts = MP_NOPTS_VALUE; - queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, false); + queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, + MPSEEK_FLAG_DELAY); set_osd_function(mpctx, OSD_REW); if (bar_osd) mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; @@ -4850,7 +4851,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re // rounding for the mess of it. a[0] += 0.01 * (a[1] >= 0 ? 1 : -1); mark_seek(mpctx); - queue_seek(mpctx, MPSEEK_RELATIVE, a[0], MPSEEK_EXACT, false); + queue_seek(mpctx, MPSEEK_RELATIVE, a[0], MPSEEK_EXACT, + MPSEEK_FLAG_DELAY); set_osd_function(mpctx, (a[0] > 0) ? OSD_FFW : OSD_REW); if (bar_osd) mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; @@ -5421,7 +5423,7 @@ void handle_ab_loop(struct MPContext *mpctx) { mark_seek(mpctx); queue_seek(mpctx, MPSEEK_ABSOLUTE, start, - MPSEEK_EXACT, false); + MPSEEK_EXACT, MPSEEK_FLAG_DELAY); } } ctx->prev_pts = now; diff --git a/player/core.h b/player/core.h index 8f90a1903f..94bfe4aada 100644 --- a/player/core.h +++ b/player/core.h @@ -80,6 +80,10 @@ enum seek_precision { MPSEEK_VERY_EXACT, }; +enum seek_flags { + MPSEEK_FLAG_DELAY = 1 << 0, // give player chance to coalesce multiple seeks +}; + enum video_sync { VS_DEFAULT = 0, VS_DISP_RESAMPLE, @@ -380,7 +384,7 @@ typedef struct MPContext { enum seek_type type; enum seek_precision exact; double amount; - bool immediate; // disable seek delay logic + unsigned flags; // MPSEEK_FLAG_* } seek; // Allow audio to issue a second seek if audio is too far ahead (for non-hr @@ -512,7 +516,7 @@ void pause_player(struct MPContext *mpctx); void unpause_player(struct MPContext *mpctx); void add_step_frame(struct MPContext *mpctx, int dir); void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount, - enum seek_precision exact, bool immediate); + enum seek_precision exact, int flags); double get_time_length(struct MPContext *mpctx); double get_current_time(struct MPContext *mpctx); double get_playback_time(struct MPContext *mpctx); diff --git a/player/loadfile.c b/player/loadfile.c index 590839c799..c41ff4367a 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -1140,7 +1140,7 @@ reopen_file: startpos = start; } if (startpos != MP_NOPTS_VALUE) { - queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, MPSEEK_DEFAULT, 0); execute_queued_seek(mpctx); } diff --git a/player/playloop.c b/player/playloop.c index 9b15ac3f50..abba107dc5 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -141,7 +141,7 @@ void add_step_frame(struct MPContext *mpctx, int dir) unpause_player(mpctx); } else if (dir < 0) { if (!mpctx->hrseek_active) { - queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, true); + queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, 0); pause_player(mpctx); } } @@ -305,7 +305,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) // This combines consecutive seek requests. void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount, - enum seek_precision exact, bool immediate) + enum seek_precision exact, int flags) { struct seek_params *seek = &mpctx->seek; @@ -314,7 +314,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount, switch (type) { case MPSEEK_RELATIVE: - seek->immediate |= immediate; + seek->flags |= flags; if (seek->type == MPSEEK_FACTOR) return; // Well... not common enough to bother doing better seek->amount += amount; @@ -332,7 +332,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount, .type = type, .amount = amount, .exact = exact, - .immediate = immediate, + .flags = flags, }; return; case MPSEEK_NONE: @@ -351,7 +351,8 @@ void execute_queued_seek(struct MPContext *mpctx) /* If the user seeks continuously (keeps arrow key down) * try to finish showing a frame from one location before doing * another seek (which could lead to unchanging display). */ - if (!mpctx->seek.immediate && mpctx->video_status < STATUS_PLAYING && + bool delay = mpctx->seek.flags & MPSEEK_FLAG_DELAY; + if (delay && mpctx->video_status < STATUS_PLAYING && mp_time_sec() - mpctx->start_timestamp < 0.3) return; mp_seek(mpctx, mpctx->seek); @@ -682,7 +683,7 @@ static void handle_sstep(struct MPContext *mpctx) if (opts->step_sec > 0 && !mpctx->paused) { set_osd_function(mpctx, OSD_FFW); - queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, MPSEEK_DEFAULT, 0); } if (mpctx->video_status >= STATUS_EOF) { @@ -699,7 +700,7 @@ static void handle_loop_file(struct MPContext *mpctx) if (opts->loop_file && mpctx->stop_play == AT_END_OF_FILE) { mpctx->stop_play = KEEP_PLAYING; set_osd_function(mpctx, OSD_FFW); - queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, true); + queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, 0); if (opts->loop_file > 0) opts->loop_file--; } diff --git a/player/video.c b/player/video.c index 9790f34966..e18f8cba70 100644 --- a/player/video.c +++ b/player/video.c @@ -543,7 +543,7 @@ void mp_force_video_refresh(struct MPContext *mpctx) mpctx->last_vo_pts != MP_NOPTS_VALUE) { queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, - MPSEEK_VERY_EXACT, true); + MPSEEK_VERY_EXACT, 0); } } -- cgit v1.2.3 From 814dacdd7d010407a4bea74b3a047079f49c5c39 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 16 Aug 2016 00:03:02 +0200 Subject: af_lavrresample: work around libswresample misbehavior The touched code is for seek resets and such - we simply want to reset the entire resample state. But I noticed after a seek a tiny bit of audio is missing (mpv's audio sync code inserted silence to compensate). It turns out swr_drop_output() either does not reset some internal state as we expect, or it's designed to drop not only buffered samples, but also future samples. On the other hand, libavresample's avresample_read(), does not have this problem. (It is also pretty explicit in what it does - return/skip buffered data, nothing else.) Is the libswresample behavior a bug? Or a feature? Does nobody even know? Who cares - use the hammer to unfuck the situation. Destroy and deallocate the libswresample context and recreate it. On every seek. --- audio/filter/af_lavrresample.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c index fdef69a16b..99c080478d 100644 --- a/audio/filter/af_lavrresample.c +++ b/audio/filter/af_lavrresample.c @@ -99,10 +99,6 @@ static double get_delay(struct af_resample *s) return avresample_get_delay(s->avrctx) / (double)s->in_rate + avresample_available(s->avrctx) / (double)s->out_rate; } -static void drop_all_output(struct af_resample *s) -{ - while (avresample_read(s->avrctx, NULL, 1000) > 0) {} -} static int get_out_samples(struct af_resample *s, int in_samples) { return avresample_get_out_samples(s->avrctx, in_samples); @@ -113,10 +109,6 @@ static double get_delay(struct af_resample *s) int64_t base = s->in_rate * (int64_t)s->out_rate; return swr_get_delay(s->avrctx, base) / (double)base; } -static void drop_all_output(struct af_resample *s) -{ - while (swr_drop_output(s->avrctx, 1000) > 0) {} -} static int get_out_samples(struct af_resample *s, int in_samples) { #if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2 @@ -406,8 +398,16 @@ static int control(struct af_instance *af, int cmd, void *arg) return AF_OK; } case AF_CONTROL_RESET: - if (s->avrctx) - drop_all_output(s); + if (s->avrctx) { +#if HAVE_LIBSWRESAMPLE + // This POS either can't drop state correctly, or doesn't want to. + // It will swallow some minor audio e.g. after a seek. + // Deallocate and recreate the resample state for a full reset. + configure_lavrr(af, &af->fmt_in, &af->fmt_out, false); +#else + while (avresample_read(s->avrctx, NULL, 1000) > 0) {} +#endif + } return AF_OK; } return AF_UNKNOWN; -- cgit v1.2.3 From 12e251c29e1be905ab35c4caed9a6d926c1825b0 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 16 Aug 2016 10:48:54 +0200 Subject: demux: fix undefined behavior with ogg metadata update When an ogg track upodates metadata, we have to perform a complicated runtime update due to the demux.c architecture. A detail was broken and an array was allocated with the previous number of streams, which usually led to invalid memory write accesses at least on the first update. See github commit comment on commit b9ba9a89. --- demux/demux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demux/demux.c b/demux/demux.c index 648e629f77..0c42efee92 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -1039,10 +1039,10 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src) dst->metadata = mp_tags_dup(dst, src->metadata); if (dst->num_update_stream_tags != src->num_update_stream_tags) { + dst->num_update_stream_tags = src->num_update_stream_tags; talloc_free(dst->update_stream_tags); dst->update_stream_tags = talloc_zero_array(dst, struct mp_tags *, dst->num_update_stream_tags); - dst->num_update_stream_tags = src->num_update_stream_tags; } for (int n = 0; n < dst->num_update_stream_tags; n++) { talloc_free(dst->update_stream_tags[n]); -- cgit v1.2.3 From 3a7e86ff6efc834281d3f112b05a82fc6b2e1023 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 17 Aug 2016 21:44:05 +0200 Subject: m_option: simplify float value range handling Use clamp_double() to handle all value restriction/verification. The error messages become a bit less nice, but they were kind of incomplete before. --- options/m_option.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/options/m_option.c b/options/m_option.c index fd1e6770ee..4dedfe476e 100644 --- a/options/m_option.c +++ b/options/m_option.c @@ -921,22 +921,8 @@ static int parse_double(struct mp_log *log, const m_option_t *opt, return M_OPT_INVALID; } - if (opt->flags & M_OPT_MIN) - if (tmp_float < opt->min) { - mp_err(log, "The %.*s option must be >= %f: %.*s\n", - BSTR_P(name), opt->min, BSTR_P(param)); - return M_OPT_OUT_OF_RANGE; - } - - if (opt->flags & M_OPT_MAX) - if (tmp_float > opt->max) { - mp_err(log, "The %.*s option must be <= %f: %.*s\n", - BSTR_P(name), opt->max, BSTR_P(param)); - return M_OPT_OUT_OF_RANGE; - } - - if (!isfinite(tmp_float)) { - mp_err(log, "The %.*s option must be a finite number: %.*s\n", + if (clamp_double(opt, &tmp_float) < 0) { + mp_err(log, "The %.*s option is out of range: %.*s\n", BSTR_P(name), BSTR_P(param)); return M_OPT_OUT_OF_RANGE; } @@ -990,11 +976,7 @@ static int double_set(const m_option_t *opt, void *dst, struct mpv_node *src) } else { return M_OPT_UNKNOWN; } - if ((opt->flags & M_OPT_MIN) && val < opt->min) - return M_OPT_OUT_OF_RANGE; - if ((opt->flags & M_OPT_MAX) && val > opt->max) - return M_OPT_OUT_OF_RANGE; - if (!isfinite(val)) + if (clamp_double(opt, &val) < 0) return M_OPT_OUT_OF_RANGE; *(double *)dst = val; return 1; -- cgit v1.2.3 From 07f8b647547cadb61d0677200faeaf1498b8377e Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 17 Aug 2016 21:48:45 +0200 Subject: m_option: add mechanism to allow inf/-inf float options Used by the next commit. --- options/m_option.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/options/m_option.c b/options/m_option.c index 4dedfe476e..0acdc55b0a 100644 --- a/options/m_option.c +++ b/options/m_option.c @@ -894,7 +894,8 @@ static int clamp_double(const m_option_t *opt, void *val) v = opt->min; r = M_OPT_OUT_OF_RANGE; } - if (!isfinite(v)) { + // (setting max/min to INFINITY/-INFINITY is allowed) + if (!isfinite(v) && v != opt->max && v != opt->min) { v = opt->min; r = M_OPT_OUT_OF_RANGE; } -- cgit v1.2.3 From f5bbb5aed22f9c52ce078522dc3da6a9403e8a9d Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 17 Aug 2016 22:45:44 +0200 Subject: player: add option to control duration of image display The --image-display-duration option controls how long an image is displayed. It's also possible to display the image forever (until manual user interaction stops playback). With this, the core drops the old method to "drain" video (i.e. waiting for the last frame duration on end of playback). Instead, we reuse MPContext.time_frame. The old mechanism was disabled for non-images anyway. Fixes #3425. --- DOCS/interface-changes.rst | 4 ++++ DOCS/man/options.rst | 17 +++++++++++++++++ options/options.c | 4 ++++ options/options.h | 1 + player/video.c | 34 ++++++++++++++++++++++------------ 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index d7db0106b4..f02f6588ff 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -19,6 +19,10 @@ Interface changes :: + --- mpv 0.20.0 --- + - add --image-display-duration option - this also means that image duration + is not influenced by --mf-fps anymore in the general case (this is an + incompatible change) --- mpv 0.19.0 --- - deprecate "balance" option/property (no replacement) --- mpv 0.18.1 --- diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 39416d2a64..0301e6971d 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1873,6 +1873,23 @@ Window file.mkv normally, then fail to open ``/dev/null``, then exit). (In mpv 0.8.0, ``always`` was introduced, which restores the old behavior.) +``--image-display-duration=`` + If the current file is an image, play the image for the given amount of + seconds (default: 1). ``inf`` means the file is kept open forever (until + the user stops playback manually). + + Unlike ``--keep-open``, the player is not paused, but simply continues + playback until the time has elapsed. (It should not use any resources + during "playback".) + + This affects image files, which are defined as having only 1 video frame + and no audio. The player may recognize certain non-images as images, for + example if ``--length`` is used to reduce the length to 1 frame, or if + you seek to the last frame. + + This option does not affect the framerate used for ``mf://`` or + ``--merge-files``. For that, use ``--mf-fps`` instead. + ``--force-window=`` Create a video output window even if there is no video. This can be useful when pretending that mpv is a GUI application. Currently, the window diff --git a/options/options.c b/options/options.c index 9e3e70385e..1a8ecaca2f 100644 --- a/options/options.c +++ b/options/options.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "config.h" @@ -214,6 +215,8 @@ const m_option_t mp_opts[] = { ({"no", 0}, {"yes", 1}, {"always", 2})), + OPT_DOUBLE("image-display-duration", image_display_duration, + M_OPT_RANGE, 0, INFINITY), OPT_CHOICE("index", index_mode, 0, ({"default", 1}, {"recreate", 0})), @@ -796,6 +799,7 @@ const struct MPOpts mp_default_opts = { .play_frames = -1, .rebase_start_time = 1, .keep_open = 0, + .image_display_duration = 1.0, .stream_id = { { [STREAM_AUDIO] = -1, [STREAM_VIDEO] = -1, [STREAM_SUB] = -1, }, diff --git a/options/options.h b/options/options.h index 4de4a831bb..263caaa8de 100644 --- a/options/options.h +++ b/options/options.h @@ -190,6 +190,7 @@ typedef struct MPOpts { int ignore_path_in_watch_later_config; int pause; int keep_open; + double image_display_duration; char *lavfi_complex; int stream_id[2][STREAM_TYPE_COUNT]; int stream_id_ff[STREAM_TYPE_COUNT]; diff --git a/player/video.c b/player/video.c index e18f8cba70..23f8eefa33 100644 --- a/player/video.c +++ b/player/video.c @@ -1283,10 +1283,6 @@ static void calculate_frame_duration(struct MPContext *mpctx) double pts1 = mpctx->next_frames[1]->pts; if (pts0 != MP_NOPTS_VALUE && pts1 != MP_NOPTS_VALUE && pts1 >= pts0) duration = pts1 - pts0; - } else { - // E.g. last frame on EOF. Only use it if it's significant. - if (demux_duration >= 0.1) - duration = demux_duration; } // The following code tries to compensate for rounded Matroska timestamps @@ -1350,17 +1346,31 @@ void write_video(struct MPContext *mpctx) return; if (r == VD_EOF) { - int prev_state = mpctx->video_status; - mpctx->video_status = STATUS_EOF; - if (mpctx->num_past_frames > 0 && mpctx->past_frames[0].duration > 0) { - if (vo_still_displaying(vo)) - mpctx->video_status = STATUS_DRAINING; - } mpctx->delay = 0; mpctx->last_av_difference = 0; + + if (mpctx->video_status <= STATUS_PLAYING) { + mpctx->video_status = STATUS_DRAINING; + get_relative_time(mpctx); + if (mpctx->num_past_frames == 1 && mpctx->past_frames[0].pts == 0 && + !mpctx->ao_chain) + { + mpctx->time_frame += opts->image_display_duration; + } else { + mpctx->time_frame = 0; + } + } + + if (mpctx->video_status == STATUS_DRAINING) { + mpctx->time_frame -= get_relative_time(mpctx); + mpctx->sleeptime = MPMIN(mpctx->sleeptime, mpctx->time_frame); + if (mpctx->time_frame <= 0) { + MP_VERBOSE(mpctx, "video EOF reached\n"); + mpctx->video_status = STATUS_EOF; + } + } + MP_DBG(mpctx, "video EOF (status=%d)\n", mpctx->video_status); - if (prev_state != mpctx->video_status) - mpctx->sleeptime = 0; return; } -- cgit v1.2.3 From e02cb674ceb3470e938100514825edfafa6e03de Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 17 Aug 2016 23:25:52 +0200 Subject: manpage: input: fix define-section syntax Source says "force", manpage said "forced". Now both say "force". --- DOCS/man/input.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 2cc41bf5d5..8cdba41a3d 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -526,7 +526,7 @@ Input Commands that are Possibly Subject to Change ``disable-section "
"`` Disable the named input section. Undoes ``enable-section``. -``define-section "
" "" [default|forced]`` +``define-section "
" "" [default|force]`` Create a named input section, or replace the contents of an already existing input section. The ``contents`` parameter uses the same syntax as the ``input.conf`` file (except that using the section syntax in it is not @@ -544,7 +544,7 @@ Input Commands that are Possibly Subject to Change (also used if parameter omitted) Use a key binding defined by this section only if the user hasn't already bound this key to a command. - + Always bind a key. (The input section that was made active most recently wins if there are ambiguities.) -- cgit v1.2.3 From 0a0967f48b23a20dce393ec5983b46bb6b361971 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 14:55:43 +0200 Subject: build: make avutil-mastering-metadata check slightly more robust Fixes the specific scenario of compiling against a local Libav build, while the system has FFmpeg installed. --- wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wscript b/wscript index 4c49caf4b2..9ee9533930 100644 --- a/wscript +++ b/wscript @@ -508,7 +508,7 @@ FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_ }, { 'name': 'avutil-mastering-metadata', 'desc': 'libavutil mastering display metadata struct', - 'func': check_statement('libavutil/mastering_display_metadata.h', + 'func': check_statement('libavutil/frame.h', 'AV_FRAME_DATA_MASTERING_DISPLAY_METADATA', use='libav'), } -- cgit v1.2.3 From 7bba97b301c732fb1eb4dad891d00c947d2f6363 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 20:37:25 +0200 Subject: video: don't discard video frames after endpts Instead of letting it keep decoding by trying to find a new frame, "plug" the frame queue by not removing it. (Or actually, by putting it back instead of discarding it.) Matters for seamless looping (following commits), and possibly some other corner cases. The added function vf_unread_output_frame() is a bit of a sin, but still reasonable, since its implementation is trivial. --- player/video.c | 8 +++++--- video/filter/vf.c | 7 +++++++ video/filter/vf.h | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/player/video.c b/player/video.c index 23f8eefa33..6373123407 100644 --- a/player/video.c +++ b/player/video.c @@ -840,9 +840,11 @@ static int video_output_image(struct MPContext *mpctx) struct mp_image *img = vf_read_output_frame(vo_c->vf); if (img) { double endpts = get_play_end_pts(mpctx); - if (endpts != MP_NOPTS_VALUE && img->pts >= endpts) { - r = VD_EOF; - } else if (mpctx->max_frames == 0) { + if ((endpts != MP_NOPTS_VALUE && img->pts >= endpts) || + mpctx->max_frames == 0) + { + vf_unread_output_frame(vo_c->vf, img); + img = NULL; r = VD_EOF; } else if (hrseek && mpctx->hrseek_lastframe) { mp_image_setrefp(&mpctx->saved_frame, img); diff --git a/video/filter/vf.c b/video/filter/vf.c index 274ca945a2..b632314426 100644 --- a/video/filter/vf.c +++ b/video/filter/vf.c @@ -458,6 +458,13 @@ struct mp_image *vf_read_output_frame(struct vf_chain *c) return vf_dequeue_output_frame(c->last); } +// Undo the previous vf_read_output_frame(). +void vf_unread_output_frame(struct vf_chain *c, struct mp_image *img) +{ + struct vf_instance *vf = c->last; + MP_TARRAY_INSERT_AT(vf, vf->out_queued, vf->num_out_queued, 0, img); +} + // Some filters (vf_vapoursynth) filter on separate threads, and may need new // input from the decoder, even though the core does not need a new output image // yet (this is required to get proper pipelining in the filter). If the filter diff --git a/video/filter/vf.h b/video/filter/vf.h index 49296fb9b2..901ccead95 100644 --- a/video/filter/vf.h +++ b/video/filter/vf.h @@ -157,6 +157,7 @@ int vf_filter_frame(struct vf_chain *c, struct mp_image *img); int vf_output_frame(struct vf_chain *c, bool eof); int vf_needs_input(struct vf_chain *c); struct mp_image *vf_read_output_frame(struct vf_chain *c); +void vf_unread_output_frame(struct vf_chain *c, struct mp_image *img); void vf_seek_reset(struct vf_chain *c); struct vf_instance *vf_append_filter(struct vf_chain *c, const char *name, char **args); -- cgit v1.2.3 From bbcd0b6a03e2ff4c70c2923db84467fbdddce17e Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 20:38:09 +0200 Subject: audio: improve aspects of EOF handling The code actually kept going out of EOF mode into resync mode back into EOF mode when the playloop had to wait after an audio EOF caused by the endpts. This would break seamless looping (as added by the next commit). Apply endpts earlier, to ensure the filter_audio() function always returns AD_EOF in this case. The idiotic ao_buffer makes this an amazing pain in the ass. --- audio/filter/af.c | 6 ++++++ audio/filter/af.h | 1 + player/audio.c | 59 +++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/audio/filter/af.c b/audio/filter/af.c index a132965295..f380459747 100644 --- a/audio/filter/af.c +++ b/audio/filter/af.c @@ -779,6 +779,12 @@ struct mp_audio *af_read_output_frame(struct af_stream *s) return af_dequeue_output_frame(s->last); } +void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame) +{ + struct af_instance *af = s->last; + MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame); +} + // Make sure the caller can change data referenced by the frame. // Return negative error code on failure (i.e. you can't write). int af_make_writeable(struct af_instance *af, struct mp_audio *frame) diff --git a/audio/filter/af.h b/audio/filter/af.h index 697024b781..a773f561b3 100644 --- a/audio/filter/af.h +++ b/audio/filter/af.h @@ -148,6 +148,7 @@ void af_add_output_frame(struct af_instance *af, struct mp_audio *frame); int af_filter_frame(struct af_stream *s, struct mp_audio *frame); int af_output_frame(struct af_stream *s, bool eof); struct mp_audio *af_read_output_frame(struct af_stream *s); +void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame); int af_make_writeable(struct af_instance *af, struct mp_audio *frame); double af_calc_delay(struct af_stream *s); diff --git a/player/audio.c b/player/audio.c index 9fe7eb802a..a98fa7784d 100644 --- a/player/audio.c +++ b/player/audio.c @@ -722,15 +722,40 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip) } -static bool copy_output(struct af_stream *afs, struct mp_audio_buffer *outbuf, - int minsamples, bool eof) +static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf, + int minsamples, double endpts, bool eof, bool *seteof) { + struct af_stream *afs = mpctx->ao_chain->af; + while (mp_audio_buffer_samples(outbuf) < minsamples) { if (af_output_frame(afs, eof) < 0) return true; // error, stop doing stuff + + int cursamples = mp_audio_buffer_samples(outbuf); + int maxsamples = INT_MAX; + if (endpts != MP_NOPTS_VALUE) { + double rate = afs->output.rate / mpctx->audio_speed; + double curpts = written_audio_pts(mpctx); + if (curpts != MP_NOPTS_VALUE) + maxsamples = (endpts - curpts - mpctx->opts->audio_delay) * rate; + } + struct mp_audio *mpa = af_read_output_frame(afs); if (!mpa) return false; // out of data + + if (cursamples + mpa->samples > maxsamples) { + if (cursamples < maxsamples) { + struct mp_audio pre = *mpa; + pre.samples = maxsamples - cursamples; + mp_audio_buffer_append(outbuf, &pre); + mp_audio_skip_samples(mpa, pre.samples); + } + af_unread_output_frame(afs, mpa); + *seteof = true; + return true; + } + mp_audio_buffer_append(outbuf, mpa); talloc_free(mpa); } @@ -764,20 +789,24 @@ static int decode_new_frame(struct ao_chain *ao_c) * Return 0 on success, or negative AD_* error code. * In the former case outbuf has at least minsamples buffered on return. * In case of EOF/error it might or might not be. */ -static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf, +static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf, int minsamples) { + struct ao_chain *ao_c = mpctx->ao_chain; struct af_stream *afs = ao_c->af; if (afs->initialized < 1) return AD_ERR; MP_STATS(ao_c, "start audio"); + double endpts = get_play_end_pts(mpctx); + + bool eof = false; int res; while (1) { res = 0; - if (copy_output(afs, outbuf, minsamples, false)) + if (copy_output(mpctx, outbuf, minsamples, endpts, false, &eof)) break; res = decode_new_frame(ao_c); @@ -785,13 +814,13 @@ static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf, break; if (res < 0) { // drain filters first (especially for true EOF case) - copy_output(afs, outbuf, minsamples, true); + copy_output(mpctx, outbuf, minsamples, endpts, true, &eof); break; } // On format change, make sure to drain the filter chain. if (!mp_audio_config_equals(&afs->input, ao_c->input_frame)) { - copy_output(afs, outbuf, minsamples, true); + copy_output(mpctx, outbuf, minsamples, endpts, true, &eof); res = AD_NEW_FMT; break; } @@ -817,6 +846,9 @@ static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf, return AD_ERR; } + if (res == 0 && mp_audio_buffer_samples(outbuf) < minsamples && eof) + res = AD_EOF; + MP_STATS(ao_c, "end audio"); return res; @@ -918,10 +950,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx) playsize = playsize / align * align; - int status = AD_OK; + int status = mpctx->audio_status >= STATUS_DRAINING ? AD_EOF : AD_OK; bool working = false; if (playsize > mp_audio_buffer_samples(ao_c->ao_buffer)) { - status = filter_audio(mpctx->ao_chain, ao_c->ao_buffer, playsize); + status = filter_audio(mpctx, ao_c->ao_buffer, playsize); if (status == AD_WAIT) return; if (status == AD_NO_PROGRESS) { @@ -1015,17 +1047,6 @@ void fill_audio_out_buffers(struct MPContext *mpctx) bool partial_fill = false; int playflags = 0; - double endpts = get_play_end_pts(mpctx); - if (endpts != MP_NOPTS_VALUE) { - double samples = (endpts - written_audio_pts(mpctx) - opts->audio_delay) - * play_samplerate; - if (playsize > samples) { - playsize = MPMAX((int)samples / align * align, 0); - audio_eof = true; - partial_fill = true; - } - } - if (playsize > mp_audio_buffer_samples(ao_c->ao_buffer)) { playsize = mp_audio_buffer_samples(ao_c->ao_buffer); partial_fill = true; -- cgit v1.2.3 From a1dec6f54a31180968d3c8e5012bb3ccd1a7c716 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 20:40:23 +0200 Subject: player: make looping slightly more seamless This affects A-B loops and --loop-file, and audio. Instead of dropping audio by resetting the AO, try to make it seamless by not sending data after the loop point, and after the seek send new data without a reset. --- player/command.c | 42 +++++------------------------------------- player/command.h | 2 +- player/core.h | 2 ++ player/misc.c | 6 ++++++ player/playloop.c | 28 ++++++++++++++++++++++++---- 5 files changed, 38 insertions(+), 42 deletions(-) diff --git a/player/command.c b/player/command.c index f941e864dc..99538cebcf 100644 --- a/player/command.c +++ b/player/command.c @@ -75,8 +75,6 @@ struct command_ctx { double last_seek_pts; double marked_pts; - double prev_pts; - char **warned_deprecated; int num_warned_deprecated; @@ -220,7 +218,7 @@ static void mp_hook_add(struct MPContext *mpctx, char *client, char *name, } // Call before a seek, in order to allow revert-seek to undo the seek. -static void mark_seek(struct MPContext *mpctx) +void mark_seek(struct MPContext *mpctx) { struct command_ctx *cmd = mpctx->command_ctx; double now = mp_time_sec(); @@ -3301,9 +3299,11 @@ static int mp_property_ab_loop(void *ctx, struct m_property *prop, } int r = mp_property_generic_option(mpctx, prop, action, arg); if (r > 0 && action == M_PROPERTY_SET) { + mpctx->ab_loop_clip = mpctx->playback_pts < opts->ab_loop[1]; if (strcmp(prop->name, "ab-loop-b") == 0) { - struct command_ctx *cctx = mpctx->command_ctx; - cctx->prev_pts = opts->ab_loop[0]; + if (opts->ab_loop[1] != MP_NOPTS_VALUE && + mpctx->playback_pts <= opts->ab_loop[1]) + mpctx->ab_loop_clip = true; } // Update if visible set_osd_bar_chapters(mpctx, OSD_BAR_SEEK); @@ -5375,7 +5375,6 @@ void command_init(struct MPContext *mpctx) mpctx->command_ctx = talloc(NULL, struct command_ctx); *mpctx->command_ctx = (struct command_ctx){ .last_seek_pts = MP_NOPTS_VALUE, - .prev_pts = MP_NOPTS_VALUE, }; } @@ -5388,8 +5387,6 @@ static void command_event(struct MPContext *mpctx, int event, void *arg) ctx->marked_pts = MP_NOPTS_VALUE; } - if (event == MPV_EVENT_SEEK) - ctx->prev_pts = MP_NOPTS_VALUE; if (event == MPV_EVENT_IDLE) ctx->is_idle = true; if (event == MPV_EVENT_START_FILE) @@ -5400,35 +5397,6 @@ static void command_event(struct MPContext *mpctx, int event, void *arg) } } -void handle_ab_loop(struct MPContext *mpctx) -{ - struct command_ctx *ctx = mpctx->command_ctx; - struct MPOpts *opts = mpctx->opts; - - if (opts->pause) - return; - - double now = mpctx->restart_complete ? mpctx->playback_pts : MP_NOPTS_VALUE; - if (now != MP_NOPTS_VALUE && (opts->ab_loop[0] != MP_NOPTS_VALUE || - opts->ab_loop[1] != MP_NOPTS_VALUE)) - { - double start = opts->ab_loop[0]; - if (start == MP_NOPTS_VALUE) - start = 0; - double end = opts->ab_loop[1]; - if (end == MP_NOPTS_VALUE) - end = INFINITY; - if (ctx->prev_pts >= start && ctx->prev_pts < end && - (now >= end || mpctx->stop_play == AT_END_OF_FILE)) - { - mark_seek(mpctx); - queue_seek(mpctx, MPSEEK_ABSOLUTE, start, - MPSEEK_EXACT, MPSEEK_FLAG_DELAY); - } - } - ctx->prev_pts = now; -} - void handle_command_updates(struct MPContext *mpctx) { struct command_ctx *ctx = mpctx->command_ctx; diff --git a/player/command.h b/player/command.h index a233319ad7..9ffa5c1340 100644 --- a/player/command.h +++ b/player/command.h @@ -57,6 +57,6 @@ enum { bool mp_hook_test_completion(struct MPContext *mpctx, char *type); void mp_hook_run(struct MPContext *mpctx, char *client, char *type); -void handle_ab_loop(struct MPContext *mpctx); +void mark_seek(struct MPContext *mpctx); #endif /* MPLAYER_COMMAND_H */ diff --git a/player/core.h b/player/core.h index 94bfe4aada..25eb8b71a8 100644 --- a/player/core.h +++ b/player/core.h @@ -82,6 +82,7 @@ enum seek_precision { enum seek_flags { MPSEEK_FLAG_DELAY = 1 << 0, // give player chance to coalesce multiple seeks + MPSEEK_FLAG_NOFLUSH = 1 << 1, // keeping remaining data for seamless loops }; enum video_sync { @@ -326,6 +327,7 @@ typedef struct MPContext { bool hrseek_lastframe; // drop everything until last frame reached bool hrseek_backstep; // go to frame before seek target double hrseek_pts; + bool ab_loop_clip; // clip to the "b" part of an A-B loop if available // AV sync: the next frame should be shown when the audio out has this // much (in seconds) buffered data left. Increased when more data is // written to the ao, decreased when moving to the next video frame. diff --git a/player/misc.c b/player/misc.c index a9174c41a9..bd65fb9d5b 100644 --- a/player/misc.c +++ b/player/misc.c @@ -90,6 +90,12 @@ double get_play_end_pts(struct MPContext *mpctx) if (cend != MP_NOPTS_VALUE && (end == MP_NOPTS_VALUE || cend < end)) end = cend; } + if (mpctx->ab_loop_clip && opts->ab_loop[1] != MP_NOPTS_VALUE && + opts->ab_loop[1] > opts->ab_loop[0]) + { + if (end == MP_NOPTS_VALUE || end > opts->ab_loop[1]) + end = opts->ab_loop[1]; + } return end; } diff --git a/player/playloop.c b/player/playloop.c index abba107dc5..ed4f57dd5c 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -172,6 +172,7 @@ void reset_playback_state(struct MPContext *mpctx) mpctx->last_seek_pts = MP_NOPTS_VALUE; mpctx->cache_wait_time = 0; mpctx->step_frames = 0; + mpctx->ab_loop_clip = true; mpctx->restart_complete = false; #if HAVE_ENCODING @@ -272,7 +273,9 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) } } - clear_audio_output_buffers(mpctx); + if (!(seek.flags & MPSEEK_FLAG_NOFLUSH)) + clear_audio_output_buffers(mpctx); + reset_playback_state(mpctx); /* Use the target time as "current position" for further relative @@ -301,6 +304,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) mpctx->audio_allow_second_chance_seek = !hr_seek && !(demux_flags & SEEK_FORWARD); + + mpctx->ab_loop_clip = mpctx->last_seek_pts < opts->ab_loop[1]; } // This combines consecutive seek requests. @@ -697,10 +702,25 @@ static void handle_sstep(struct MPContext *mpctx) static void handle_loop_file(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; + + if (mpctx->stop_play == AT_END_OF_FILE && + (opts->ab_loop[0] != MP_NOPTS_VALUE || opts->ab_loop[1] != MP_NOPTS_VALUE)) + { + // Assumes execute_queued_seek() happens before next audio/video is + // attempted to be decoded or filtered. + mpctx->stop_play = KEEP_PLAYING; + double start = 0; + if (opts->ab_loop[0] != MP_NOPTS_VALUE) + start = opts->ab_loop[0]; + mark_seek(mpctx); + queue_seek(mpctx, MPSEEK_ABSOLUTE, start, MPSEEK_EXACT, + MPSEEK_FLAG_NOFLUSH); + } + if (opts->loop_file && mpctx->stop_play == AT_END_OF_FILE) { mpctx->stop_play = KEEP_PLAYING; set_osd_function(mpctx, OSD_FFW); - queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, 0); + queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, MPSEEK_FLAG_NOFLUSH); if (opts->loop_file > 0) opts->loop_file--; } @@ -894,6 +914,7 @@ static void handle_playback_restart(struct MPContext *mpctx) mpctx->hrseek_active = false; mpctx->restart_complete = true; mpctx->audio_allow_second_chance_seek = false; + handle_playback_time(mpctx); mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL); if (!mpctx->playing_msg_shown) { if (opts->playing_msg && opts->playing_msg[0]) { @@ -913,6 +934,7 @@ static void handle_playback_restart(struct MPContext *mpctx) } mpctx->playing_msg_shown = true; mpctx->sleeptime = 0; + mpctx->ab_loop_clip = mpctx->playback_pts < opts->ab_loop[1]; MP_VERBOSE(mpctx, "playback restart complete\n"); } } @@ -1013,8 +1035,6 @@ void run_playloop(struct MPContext *mpctx) handle_loop_file(mpctx); - handle_ab_loop(mpctx); - handle_keep_open(mpctx); handle_sstep(mpctx); -- cgit v1.2.3 From 1e53fc3a159c6b3a4239f17501c2edc93d5020f3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 21:03:01 +0200 Subject: demux_lavf: don't report start time for ogg Better with ogg shoutcast streams. These have PTS resets on each playlist item, so the PTS would usually reset to some negative value. --- demux/demux_lavf.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index e28ebd0c23..ef0a8583d5 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -114,6 +114,7 @@ struct format_hack { // Do not confuse player's position estimation (position is into external // segment, with e.g. HLS, player knows about the playlist main file only). bool clear_filepos : 1; + bool ignore_start : 1; }; #define BLACKLIST(fmt) {fmt, .ignore = true} @@ -137,6 +138,9 @@ static const struct format_hack format_hacks[] = { {"h264", .if_flags = AVFMT_NOTIMESTAMPS }, {"hevc", .if_flags = AVFMT_NOTIMESTAMPS }, + // Rebasing start time to 0 is very weird with ogg shoutcast streams. + {"ogg", .ignore_start = true}, + TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"), TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"), TEXTSUB("sami"), TEXTSUB("srt"), TEXTSUB("stl"), TEXTSUB("subviewer"), @@ -855,8 +859,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check) demuxer->ts_resets_possible = priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS); - demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ? - 0 : (double)priv->avfc->start_time / AV_TIME_BASE; + if (avfc->start_time != AV_NOPTS_VALUE && !priv->format_hack.ignore_start) + demuxer->start_time = avfc->start_time / (double)AV_TIME_BASE; demuxer->fully_read = priv->format_hack.fully_read; -- cgit v1.2.3 From 0f83caf96a6da71fa8b13734a1b333d0d2116243 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 21:05:20 +0200 Subject: github: encourage bug reports Word this sentence slightly more positively because we are a positive project. --- .github/ISSUE_TEMPLATE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 9edfac412d..799723d176 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -13,7 +13,9 @@ If you're not using git master or the latest release, update. Make a log file made with -v or --log-file=output.txt, paste it to http://sprunge.us or a similar site, and replace this text with a link to it. -Do not bother reporting a bug if you do not provide the required information. +Providing a log file is strongly encouraged. It is very helpful for reproducing +bugs or getting important technical context. Without log it might not be +possibly to analyze and fix certain bugs. ### Sample files -- cgit v1.2.3 From e6952c70532bbc491cbe1ec484030b101fc71b7d Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 18 Aug 2016 21:17:25 +0200 Subject: github: fix typo --- .github/ISSUE_TEMPLATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 799723d176..8576f3d072 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -15,7 +15,7 @@ http://sprunge.us or a similar site, and replace this text with a link to it. Providing a log file is strongly encouraged. It is very helpful for reproducing bugs or getting important technical context. Without log it might not be -possibly to analyze and fix certain bugs. +possible to analyze and fix certain bugs. ### Sample files -- cgit v1.2.3 From 4e3663a2da5da33231ced7ae513c2a9017cd4298 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 09:37:52 +0200 Subject: vf_rotate: allow arbitrary rotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vf_rotate selects the correct filter for 90° rotation, but it can be extended to use lavfi's vf_rotate as fallback. See #3434. --- player/video.c | 4 ++-- video/filter/vf_rotate.c | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/player/video.c b/player/video.c index 6373123407..fb4b41f718 100644 --- a/player/video.c +++ b/player/video.c @@ -217,8 +217,8 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c) if (mpctx->opts->deinterlace >= 0) video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0}); - if (params.rotate && (params.rotate % 90 == 0)) { - if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) { + if (params.rotate) { + if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90) || params.rotate % 90) { // Try to insert a rotation filter. char *args[] = {"angle", "auto", NULL}; if (try_filter(vo_c, "rotate", "autorotate", args) < 0) diff --git a/video/filter/vf_rotate.c b/video/filter/vf_rotate.c index 60e52a00bd..dcaba53c6a 100644 --- a/video/filter/vf_rotate.c +++ b/video/filter/vf_rotate.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "common/msg.h" @@ -46,11 +47,17 @@ static int lavfi_reconfig(struct vf_instance *vf, struct vf_priv_s *p = vf_lw_old_priv(vf); if (p->angle == 4) { // "auto" int r = in->rotate; - if (r < 0 || r >= 360 || (r % 90) != 0) { + if (r < 0 || r >= 360) { MP_ERR(vf, "Can't apply rotation of %d degrees.\n", r); return -1; } - vf_lw_update_graph(vf, NULL, "%s", rot[(r / 90) % 360]); + if (r % 90) { + double a = r / 180.0 * M_PI; + vf_lw_update_graph(vf, NULL, "rotate=%f:ow=rotw(%f):oh=roth(%f)", + a, a, a); + } else { + vf_lw_update_graph(vf, NULL, "%s", rot[(r / 90) % 360]); + } out->rotate = 0; } return 0; -- cgit v1.2.3 From 5d2dfbdfca04d6b130af63527bb3a8a432205448 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 09:38:38 +0200 Subject: player: refresh very low framerate video on filter changes Limit the max. time the refresh is delayed. Make it refresh at all if image mode is enabled. Fixes #3435. --- player/video.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/player/video.c b/player/video.c index fb4b41f718..fb8cf9cbc0 100644 --- a/player/video.c +++ b/player/video.c @@ -539,7 +539,9 @@ void mp_force_video_refresh(struct MPContext *mpctx) return; // If not paused, the next frame should come soon enough. - if (opts->pause && mpctx->video_status == STATUS_PLAYING && + if ((opts->pause || mpctx->time_frame >= 0.5) && + (mpctx->video_status >= STATUS_PLAYING || + mpctx->video_status <= STATUS_DRAINING) && mpctx->last_vo_pts != MP_NOPTS_VALUE) { queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, -- cgit v1.2.3 From 4aaa83339ca9515113e936af79bc3d860d67cb4b Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 14:11:32 +0200 Subject: av_common: improve rounding for float->int timestamp conversions --- common/av_common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/av_common.c b/common/av_common.c index e40c751e9f..6856a27d93 100644 --- a/common/av_common.c +++ b/common/av_common.c @@ -97,8 +97,10 @@ union pts { int64_t i; double d; }; int64_t mp_pts_to_av(double mp_pts, AVRational *tb) { assert(sizeof(int64_t) >= sizeof(double)); - if (tb && tb->num > 0 && tb->den > 0) - return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : mp_pts / av_q2d(*tb); + if (tb && tb->num > 0 && tb->den > 0) { + return mp_pts == MP_NOPTS_VALUE ? + AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb)); + } // The + 0.0 is to squash possible negative zero mp_pts, which would // happen to end up as AV_NOPTS_VALUE. return (union pts){.d = mp_pts + 0.0}.i; -- cgit v1.2.3 From 05e4df3f0c7679e8ac3b32a1a414be751b6b6b37 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 14:19:46 +0200 Subject: video/audio: always provide "proper" timestamps to libavcodec Instead of passing through double float timestamps opaquely, pass real timestamps. Do so by always setting a valid timebase on the AVCodecContext for audio and video decoding. Specifically try not to round timestamps to a too coarse timebase, which could round off small adjustments to timestamps (such as for start time rebasing or demux_timeline). If the timebase is considered too coarse, make it finer. This gets rid of the need to do this specifically for some hardware decoding wrapper. The old method of passing through double timestamps was also a bit questionable. While libavcodec is not supposed to interpret timestamps at all if no timebase is provided, it was needlessly tricky. Also, it actually does compare them with AV_NOPTS_VALUE. This change will probably also reduce confusion in the future. --- audio/decode/ad_lavc.c | 2 +- common/av_common.c | 25 +++++++++++++++++++++++++ common/av_common.h | 1 + demux/demux_lavf.c | 2 ++ demux/demux_mf.c | 1 + demux/demux_raw.c | 4 ++++ demux/stheader.h | 4 ++++ video/decode/vd_lavc.c | 5 +---- 8 files changed, 39 insertions(+), 5 deletions(-) diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c index c785c62c90..26e67fe1b9 100644 --- a/audio/decode/ad_lavc.c +++ b/audio/decode/ad_lavc.c @@ -86,7 +86,7 @@ static int init(struct dec_audio *da, const char *decoder) struct priv *ctx = talloc_zero(NULL, struct priv); da->priv = ctx; - ctx->codec_timebase = (AVRational){0}; + ctx->codec_timebase = mp_get_codec_timebase(da->codec); ctx->force_channel_map = c->force_channels; diff --git a/common/av_common.c b/common/av_common.c index 6856a27d93..3a424b0c62 100644 --- a/common/av_common.c +++ b/common/av_common.c @@ -16,6 +16,7 @@ */ #include +#include #include #include @@ -84,6 +85,30 @@ void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c) #endif } +// Pick a "good" timebase, which will be used to convert double timestamps +// back to fractions for passing them through libavcodec. +AVRational mp_get_codec_timebase(struct mp_codec_params *c) +{ + AVRational tb = {c->native_tb_num, c->native_tb_den}; + if (tb.num < 1 || tb.den < 1) { + if (c->reliable_fps) + tb = av_inv_q(av_d2q(c->fps, 1000000)); + if (tb.num < 1 || tb.den < 1) + tb = AV_TIME_BASE_Q; + } + + // If the timebase is too coarse, raise its precision, or small adjustments + // to timestamps done between decoder and demuxer could be lost. + if (av_q2d(tb) > 0.001) { + AVRational r = av_div_q(tb, (AVRational){1, 1000}); + tb.den *= (r.num + r.den - 1) / r.den; + } + + av_reduce(&tb.num, &tb.den, tb.num, tb.den, 1000000); + + return tb; +} + // We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it. union pts { int64_t i; double d; }; diff --git a/common/av_common.h b/common/av_common.h index e2b86bfe40..4b13dcdd0c 100644 --- a/common/av_common.h +++ b/common/av_common.h @@ -33,6 +33,7 @@ struct mp_log; int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size); void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st); void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c); +AVRational mp_get_codec_timebase(struct mp_codec_params *c); void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb); int64_t mp_pts_to_av(double mp_pts, AVRational *tb); double mp_pts_from_av(int64_t av_pts, AVRational *tb); diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index ef0a8583d5..4b236ee2be 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -670,6 +670,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i) if (sh->codec->lav_headers) mp_copy_lav_codec_headers(sh->codec->lav_headers, codec); #endif + sh->codec->native_tb_num = st->time_base.num; + sh->codec->native_tb_den = st->time_base.den; if (st->disposition & AV_DISPOSITION_DEFAULT) sh->default_track = true; diff --git a/demux/demux_mf.c b/demux/demux_mf.c index c0b159e4ea..20b948630c 100644 --- a/demux/demux_mf.c +++ b/demux/demux_mf.c @@ -321,6 +321,7 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check) c->disp_w = 0; c->disp_h = 0; c->fps = demuxer->opts->mf_fps; + c->reliable_fps = true; demux_add_sh_stream(demuxer, sh); diff --git a/demux/demux_raw.c b/demux/demux_raw.c index bd8e11306d..51378e7031 100644 --- a/demux/demux_raw.c +++ b/demux/demux_raw.c @@ -145,6 +145,9 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check) c->force_channels = true; c->samplerate = opts->samplerate; + c->native_tb_num = 1; + c->native_tb_den = c->samplerate; + int f = opts->aformat; // See PCM(): sign float bits endian mp_set_pcm_codec(sh->codec, f & 1, f & 2, f >> 3, f & 4); @@ -233,6 +236,7 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check) c->codec = decoder; c->codec_tag = imgfmt; c->fps = opts->fps; + c->reliable_fps = true; c->disp_w = width; c->disp_h = height; demux_add_sh_stream(demuxer, sh); diff --git a/demux/stheader.h b/demux/stheader.h index f9d564c230..78e86a47ee 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -74,6 +74,9 @@ struct mp_codec_params { struct AVCodecContext *lav_headers; struct AVCodecParameters *lav_codecpar; + // Timestamp granularity for converting double<->rational timestamps. + int native_tb_num, native_tb_den; + // STREAM_AUDIO int samplerate; struct mp_chmap channels; @@ -85,6 +88,7 @@ struct mp_codec_params { // STREAM_VIDEO bool avi_dts; // use DTS timing; first frame and DTS is 0 float fps; // frames per second (set only if constant fps) + bool reliable_fps; // the fps field is definitely not broken int par_w, par_h; // pixel aspect ratio (0 if unknown/square) int disp_w, disp_h; // display size int rotate; // intended display rotation, in degrees, [0, 359] diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index eb63e58e92..804bf1112d 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -449,10 +449,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder, if (!lavc_codec) return; - ctx->codec_timebase = (AVRational){0}; - if (strstr(decoder, "_mmal") || strstr(decoder, "_mediacodec")) - ctx->codec_timebase = (AVRational){1, 1000000}; - + ctx->codec_timebase = mp_get_codec_timebase(vd->codec); ctx->pix_fmt = AV_PIX_FMT_NONE; ctx->hwdec = hwdec; ctx->hwdec_fmt = 0; -- cgit v1.2.3 From 2fbd1d36102114bda8bfea2afb94fb084cc6539c Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 14:53:01 +0200 Subject: demux: change fps field to double Because why not. --- demux/stheader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demux/stheader.h b/demux/stheader.h index 78e86a47ee..8dcd8351a6 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -87,7 +87,7 @@ struct mp_codec_params { // STREAM_VIDEO bool avi_dts; // use DTS timing; first frame and DTS is 0 - float fps; // frames per second (set only if constant fps) + double fps; // frames per second (set only if constant fps) bool reliable_fps; // the fps field is definitely not broken int par_w, par_h; // pixel aspect ratio (0 if unknown/square) int disp_w, disp_h; // display size -- cgit v1.2.3 From e5f61c2bd5d15821e9d6f0967884156e210e74f4 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 19 Aug 2016 14:55:18 +0200 Subject: vd_lavc: remove unnecessary initialization This is already the default value. --- video/decode/vd_lavc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 804bf1112d..a4f4e5e2e5 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -825,7 +825,6 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, struct demux_packet *cc = new_demux_packet_from(sd->data, sd->size); cc->pts = vd->codec_pts; cc->dts = vd->codec_dts; - cc->pos = -1; demuxer_feed_caption(vd->header, cc); } -- cgit v1.2.3 From 68dc869d6aa8eb7004e2f86e9e4dcbf203ae6a8c Mon Sep 17 00:00:00 2001 From: James Ross-Gowan Date: Fri, 19 Aug 2016 23:13:54 +1000 Subject: command: prevent O(n^2) behaviour for playlist property When fetching the playlist property, playlist_entry_from_index would be called for each playlist entry, which traversed a linked list to get the entry corresponding to the specified index. This was very slow for large playlists. Since get_playlist_entry is called for each index in order, it can avoid a full traversal of the linked list by using the next pointer on the previously requested entry. --- player/command.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/player/command.c b/player/command.c index 99538cebcf..e9a051db8c 100644 --- a/player/command.c +++ b/player/command.c @@ -3192,10 +3192,30 @@ static int mp_property_playlist_pos_1(void *ctx, struct m_property *pro