summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2019-06-09 23:39:03 +0200
committerwm4 <wm4@nowhere>2019-09-19 20:37:05 +0200
commit27fcd4ddc6cc76073795fbb48c55bfa2dce47063 (patch)
tree264aebba0c0731745c8083b9d7d497165740820b
parentcb82a206a966ce746737e0c0b407ac116cd93990 (diff)
downloadmpv-27fcd4ddc6cc76073795fbb48c55bfa2dce47063.tar.bz2
mpv-27fcd4ddc6cc76073795fbb48c55bfa2dce47063.tar.xz
demux_lavf: compensate timestamp resets for OGG web radio streams
Some OGG web radio streams use timestamp resets when a new song starts (you can find those Xiph's directory - other streams there don't show this behavior). Basically, the OGG stream behaves like concatenated OGG files, and "of course" the timestamps will start at 0 again when the song changes. This is very inconvenient, and breaks the seekable demuxer cache. In fact, any kind of seeking will break This is more time wasted in Xiph's bullshit. No, having timestamp resets by design is not reasonable, and fuck you. I much prefer the awful ICY/mp3 streaming mess, even if that's lower quality and awful. Maybe it wouldn't be so bad if libavformat could tell us WHERE THE FUCK THE RESET HAPPENS. But it doesn't, and the randomly changing timestamps is the only thing we get from its API. At this point, demux_lavf.c is like 90% hacks. But well, if libavformat applies this strange mixture of being clever for us vs. giving us unfiltered garbage (while pretending it abstracts everything, and hiding _useful_ implementation/low level details), not much we can do. This timestamp linearizing would, in general, probably be better done after the decoder, because then we wouldn't need to deal with timestamp resets. But the main purpose of this change is to fix seeking within the demuxer cache, so we have to do it on the lowest level. This can probably be applied to other containers and video streams too. But that is untested. Some further caveats are explained in the manpage.
-rw-r--r--DOCS/man/options.rst16
-rw-r--r--demux/demux_lavf.c63
2 files changed, 74 insertions, 5 deletions
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=<yes|no|auto>``
+ 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=<yes|index|no>``, ``--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;