diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 944b50c1d1..ba9bac7e63 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -3037,6 +3037,22 @@ Demuxer libavformat might reallocate the buffer internally, or not fully use all of it. +``--demuxer-lavf-linearize-timestamps=`` + Attempt to linearize timestamp resets in demuxed streams (default: auto). + This was tested only for single audio streams. It's unknown whether it + works correctly for video (but likely won't). Note that the implementation + is slightly incorrect either way, and will introduce a discontinuity by + about 1 codec frame size. + + The ``auto`` mode enables this for OGG audio stream. This covers the common + and annoying case of OGG web radio streams. Some of these will reset + timestamps to 0 every time a new song begins. This breaks the mpv seekable + cache, which can't deal with timestamp resets. Note that FFmpeg/libavformat's + seeking API can't deal with this either; it's likely that if this option + breaks this even more, while if it's disabled, you can at least seek within + the first song in the stream. Well, you won't get anything useful either + way if the seek is outside of mpv's cache. + ``--demuxer-mkv-subtitle-preroll=``, ``--mkv-subtitle-preroll`` Try harder to show embedded soft subtitles when seeking somewhere. Normally, it can happen that the subtitle at the seek target is not shown due to how diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index 63dc4da79e..9626bdf918 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -80,6 +80,7 @@ struct demux_lavf_opts { int hacks; char *sub_cp; int rtsp_transport; + int linearize_ts; }; const struct m_sub_options demux_lavf_conf = { @@ -103,6 +104,8 @@ const struct m_sub_options demux_lavf_conf = { {"udp", 1}, {"tcp", 2}, {"http", 3})), + OPT_CHOICE("demuxer-lavf-linearize-timestamps", linearize_ts, 0, + ({"no", 0}, {"auto", -1}, {"yes", 1})), {0} }, .size = sizeof(struct demux_lavf_opts), @@ -116,6 +119,7 @@ const struct m_sub_options demux_lavf_conf = { .probescore = AVPROBE_SCORE_MAX/4 + 1, .sub_cp = "auto", .rtsp_transport = 2, + .linearize_ts = -1, }, }; @@ -136,7 +140,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; + bool linearize_audio_ts : 1;// compensate timestamp resets (audio only) bool fix_editlists : 1; bool is_network : 1; bool no_seek : 1; @@ -171,8 +175,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}, + // Some Ogg shoutcast streams are essentially concatenated OGG files. They + // reset timestamps, which causes all sorts of problems. + {"ogg", .linearize_audio_ts = true}, TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"), TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"), @@ -200,6 +205,9 @@ struct nested_stream { struct stream_info { struct sh_stream *sh; + double last_key_pts; + double highest_pts; + double ts_offset; }; typedef struct lavf_priv { @@ -226,6 +234,9 @@ typedef struct lavf_priv { AVStream *pcm_seek_hack; int pcm_seek_hack_packet_size; + int linearize_ts; + bool any_ts_fixed; + // Proxying nested streams. struct nested_stream *nested; int num_nested; @@ -527,6 +538,10 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check) if (lavfdopts->hacks) priv->avif_flags = priv->avif->flags | priv->format_hack.if_flags; + priv->linearize_ts = lavfdopts->linearize_ts; + if (priv->linearize_ts < 0 && !priv->format_hack.linearize_audio_ts) + priv->linearize_ts = 0; + demuxer->filetype = priv->avif->name; if (priv->format_hack.detect_charset) @@ -690,6 +705,9 @@ static void handle_new_stream(demuxer_t *demuxer, int i) // A real video stream probably means it's a packet based format. priv->pcm_seek_hack_disabled = true; priv->pcm_seek_hack = NULL; + // Also, we don't want to do this shit for ogv videos. + if (priv->linearize_ts < 0) + priv->linearize_ts = 0; } sh->codec->disp_w = codec->width; @@ -760,6 +778,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i) struct stream_info *info = talloc_zero(priv, struct stream_info); *info = (struct stream_info){ .sh = sh, + .last_key_pts = MP_NOPTS_VALUE, + .highest_pts = MP_NOPTS_VALUE, }; assert(priv->num_streams == i); // directly mapped MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, info); @@ -1050,7 +1070,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check) demuxer->ts_resets_possible = priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS); - if (avfc->start_time != AV_NOPTS_VALUE && !priv->format_hack.ignore_start) + if (avfc->start_time != AV_NOPTS_VALUE) demuxer->start_time = avfc->start_time / (double)AV_TIME_BASE; demuxer->fully_read = priv->format_hack.fully_read; @@ -1136,7 +1156,8 @@ static bool demux_lavf_read_packet(struct demuxer *demux, update_metadata(demux); assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams); - struct sh_stream *stream = priv->streams[pkt->stream_index]->sh; + struct stream_info *info = priv->streams[pkt->stream_index]; + struct sh_stream *stream = info->sh; AVStream *st = priv->avfc->streams[pkt->stream_index]; if (!demux_stream_is_selected(stream)) { @@ -1169,6 +1190,30 @@ static bool demux_lavf_read_packet(struct demuxer *demux, dp->stream = stream->index; + if (priv->linearize_ts) { + dp->pts = MP_ADD_PTS(dp->pts, info->ts_offset); + dp->dts = MP_ADD_PTS(dp->dts, info->ts_offset); + + double pts = MP_PTS_OR_DEF(dp->pts, dp->dts); + if (pts != MP_NOPTS_VALUE) { + if (dp->keyframe) { + if (pts < info->highest_pts) { + MP_WARN(demux, "Linearizing discontinuity: %f -> %f\n", + pts, info->highest_pts); + // Note: introduces a small discontinuity by a frame size. + double diff = info->highest_pts - pts; + dp->pts = MP_ADD_PTS(dp->pts, diff); + dp->dts = MP_ADD_PTS(dp->dts, diff); + pts += diff; + info->ts_offset += diff; + priv->any_ts_fixed = true; + } + info->last_key_pts = pts; + } + info->highest_pts = MP_PTS_MAX(info->highest_pts, pts); + } + } + *mp_pkt = dp; return true; } @@ -1187,6 +1232,14 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags) return; } + if (priv->any_ts_fixed) { + // helpful message to piss of users + MP_WARN(demuxer, "Some timestamps returned by the demuxer were linearized. " + "A low level seek was requested; this won't work due to " + "restrictions in libavformat's API. You may have more " + "luck by enabling or enlarging the mpv cache.\n"); + } + if (!(flags & SEEK_FORWARD)) avsflags = AVSEEK_FLAG_BACKWARD;