summaryrefslogtreecommitdiffstats
path: root/demux/demux.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2016-08-06 15:47:04 +0200
committerwm4 <wm4@nowhere>2016-08-06 15:47:04 +0200
commitd41f0a54b07d855227b63a6b2c4e92794ff7b86f (patch)
tree45837e7bfd3d55e614c3c38a78c937153c0c491d /demux/demux.c
parentd4ee5e5a8ad450d16fb2ede212c536e01970ae16 (diff)
downloadmpv-d41f0a54b07d855227b63a6b2c4e92794ff7b86f.tar.bz2
mpv-d41f0a54b07d855227b63a6b2c4e92794ff7b86f.tar.xz
player: improve instant track switching
When switching tracks, we normally have the problem that data gets lost due to readahead buffering. (Which in turn is because we're stubborn and instruct the demuxers to discard data on unselected streams.) The demuxer layer has a hack that re-reads discarded buffered data if a stream is enabled mid-stream, so track switching will seem instant. A somewhat similar problem is when all tracks of an external files were disabled - when enabling the first track, we have to seek to the target position. Handle these with the same mechanism. Pass the "current time" to the demuxer's stream switch function, and let the demuxer figure out what to do. The demuxer will issue a refresh seek (if possible) to update the new stream, or will issue a "normal" seek if there was no active stream yet. One case that changes is when a video/audio stream is enabled on an external file with only a subtitle stream active, and the demuxer does not support rrefresh seeks. This is a fuzzy case, because subtitles are sparse, and the demuxer might have skipped large amounts of data. We used to seek (and send the subtitle decoder some subtitle packets twice). This case is sort of obscure and insane, and the fix would be questionable, so we simply don't care. Should mostly fix #3392.
Diffstat (limited to 'demux/demux.c')
-rw-r--r--demux/demux.c131
1 files changed, 72 insertions, 59 deletions
diff --git a/demux/demux.c b/demux/demux.c
index 6038d244e0..29a653a5c0 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -119,16 +119,20 @@ struct demux_internal {
int max_packs;
int max_bytes;
+ // Set if we know that we are at the start of the file. This is used to
+ // avoid a redundant initial seek after enabling streams. We could just
+ // allow it, but to avoid buggy seeking affecting normal playback, we don't.
+ bool initial_state;
+
bool tracks_switched; // thread needs to inform demuxer of this
bool seeking; // there's a seek queued
int seek_flags; // flags for next seek (if seeking==true)
double seek_pts;
- bool refresh_seeks_enabled;
- bool start_refresh_seek;
+ double ref_pts; // assumed player position (only for track switches)
- double ts_offset; // timestamp offset to apply everything
+ double ts_offset; // timestamp offset to apply to everything
void (*run_fn)(void *); // if non-NULL, function queued to be run on
void *run_fn_arg; // the thread as run_fn(run_fn_arg)
@@ -152,6 +156,7 @@ struct demux_stream {
// if false, this stream is disabled, or passively
// read (like subtitles)
bool eof; // end of demuxed stream? (true if all buffer empty)
+ bool need_refresh; // enabled mid-stream
bool refreshing;
size_t packs; // number of packets in buffer
size_t bytes; // total bytes of packets in buffer
@@ -166,7 +171,6 @@ struct demux_stream {
// for closed captions (demuxer_feed_caption)
struct sh_stream *cc;
-
};
// Return "a", or if that is NOPTS, return "def".
@@ -199,6 +203,7 @@ static void ds_flush(struct demux_stream *ds)
ds->eof = false;
ds->active = false;
ds->refreshing = false;
+ ds->need_refresh = false;
ds->last_pos = -1;
}
@@ -389,6 +394,49 @@ void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
demux_add_packet(sh, dp);
}
+// An obscure mechanism to get stream switching to be executed faster.
+// On a switch, it seeks back, and then grabs all packets that were
+// "missing" from the packet queue of the newly selected stream.
+// Returns MP_NOPTS_VALUE if no seek should happen.
+static double get_refresh_seek_pts(struct demux_internal *in)
+{
+ struct demuxer *demux = in->d_thread;
+
+ double start_ts = in->ref_pts;
+ bool needed = false;
+ bool normal_seek = true;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
+ start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
+ needed |= ds->need_refresh;
+ // If there were no other streams selected, we can use a normal seek.
+ normal_seek &= ds->need_refresh || !ds->selected;
+ ds->need_refresh = false;
+ }
+
+ if (!needed || start_ts == MP_NOPTS_VALUE || !demux->desc->seek ||
+ !demux->seekable || demux->partially_seekable)
+ return MP_NOPTS_VALUE;
+
+ if (normal_seek)
+ return start_ts;
+
+ if (!demux->allow_refresh_seeks)
+ return MP_NOPTS_VALUE;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ // Streams which didn't read any packets yet can return all packets,
+ // or they'd be stuck forever; affects newly selected streams too.
+ if (ds->last_pos != -1)
+ ds->refreshing = true;
+ }
+
+ // Seek back to player's current position, with a small offset added.
+ return start_ts - 1.0;
+}
+
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
{
struct demux_stream *ds = stream ? stream->ds : NULL;
@@ -505,13 +553,24 @@ static bool read_packet(struct demux_internal *in)
if (!read_more)
return false;
+ double seek_pts = get_refresh_seek_pts(in);
+
// Actually read a packet. Drop the lock while doing so, because waiting
// for disk or network I/O can take time.
in->idle = false;
+ in->initial_state = false;
pthread_mutex_unlock(&in->lock);
+
struct demuxer *demux = in->d_thread;
+
+ if (seek_pts != MP_NOPTS_VALUE) {
+ MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
+ demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
+ }
+
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
update_cache(in);
+
pthread_mutex_lock(&in->lock);
if (eof) {
@@ -550,42 +609,6 @@ static void ds_get_packets(struct demux_stream *ds)
}
}
-// An obscure mechanism to get stream switching to be executed faster.
-// On a switch, it seeks back, and then grabs all packets that were
-// "missing" from the packet queue of the newly selected stream.
-static void start_refreshing(struct demux_internal *in)
-{
- struct demuxer *demux = in->d_thread;
-
- in->start_refresh_seek = false;
-
- double start_ts = MP_NOPTS_VALUE;
- for (int n = 0; n < in->num_streams; n++) {
- struct demux_stream *ds = in->streams[n]->ds;
- if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
- start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
- }
-
- if (start_ts == MP_NOPTS_VALUE || !demux->desc->seek || !demux->seekable ||
- demux->partially_seekable || !demux->allow_refresh_seeks)
- return;
-
- for (int n = 0; n < in->num_streams; n++) {
- struct demux_stream *ds = in->streams[n]->ds;
- // Streams which didn't read any packets yet can return all packets,
- // or they'd be stuck forever; affects newly selected streams too.
- if (ds->last_pos != -1)
- ds->refreshing = true;
- }
-
- pthread_mutex_unlock(&in->lock);
-
- // Seek back to player's current position, with a small offset added.
- in->d_thread->desc->seek(in->d_thread, start_ts - 1.0, SEEK_BACKWARD | SEEK_HR);
-
- pthread_mutex_lock(&in->lock);
-}
-
static void execute_trackswitch(struct demux_internal *in)
{
in->tracks_switched = false;
@@ -603,9 +626,6 @@ static void execute_trackswitch(struct demux_internal *in)
&(int){any_selected});
pthread_mutex_lock(&in->lock);
-
- if (in->start_refresh_seek)
- start_refreshing(in);
}
static void execute_seek(struct demux_internal *in)
@@ -613,6 +633,7 @@ static void execute_seek(struct demux_internal *in)
int flags = in->seek_flags;
double pts = in->seek_pts;
in->seeking = false;
+ in->initial_state = false;
pthread_mutex_unlock(&in->lock);
@@ -1074,6 +1095,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.min_secs = demuxer->opts->demuxer_min_secs,
.max_packs = demuxer->opts->demuxer_max_packs,
.max_bytes = demuxer->opts->demuxer_max_bytes,
+ .initial_state = true,
};
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
@@ -1277,18 +1299,6 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
return 1;
}
-// Enable doing a "refresh seek" on the next stream switch.
-// Note that this by design does not disable ongoing refresh seeks, and
-// does not affect previous stream switch commands (even if they were
-// asynchronous).
-void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled)
-{
- struct demux_internal *in = demuxer->in;
- pthread_mutex_lock(&in->lock);
- in->refresh_seeks_enabled = enabled;
- pthread_mutex_unlock(&in->lock);
-}
-
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
enum stream_type t, int id)
{
@@ -1301,19 +1311,22 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
return NULL;
}
+// Set whether the given stream should return packets.
+// ref_pts is used only if the stream is enabled. Then it serves as approximate
+// start pts for this stream (in the worst case it is ignored).
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
- bool selected)
+ double ref_pts, bool selected)
{
struct demux_internal *in = demuxer->in;
pthread_mutex_lock(&in->lock);
// don't flush buffers if stream is already selected / unselected
if (stream->ds->selected != selected) {
stream->ds->selected = selected;
- stream->ds->active = false;
ds_flush(stream->ds);
in->tracks_switched = true;
- if (selected && in->refresh_seeks_enabled)
- in->start_refresh_seek = true;
+ stream->ds->need_refresh = selected && !in->initial_state;
+ if (stream->ds->need_refresh)
+ in->ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
if (in->threading) {
pthread_cond_signal(&in->wakeup);
} else {