diff options
author | wm4 <wm4@nowhere> | 2018-01-17 07:07:15 +0100 |
---|---|---|
committer | Kevin Mitchell <kevmitch@gmail.com> | 2018-01-18 01:25:53 -0800 |
commit | 082029f8503f68c903ec6eda4bf4e37cc0065760 (patch) | |
tree | 778708f90951a8a50f526a163d360925c381c6a0 | |
parent | ca67928d7ab176c080a7e99f0d4ce0c5d1070844 (diff) | |
download | mpv-082029f8503f68c903ec6eda4bf4e37cc0065760.tar.bz2 mpv-082029f8503f68c903ec6eda4bf4e37cc0065760.tar.xz |
player: redo hack for video keyframe seeks with external audio
If you play a video with an external audio track, and do backwards
keyframe seeks, then audio can be missing. This is because a backwards
seek can end up way before the seek target (this is just how this seek
mode works). The audio file will be seeked at the correct seek target
(since audio usually has a much higher seek granularity), which results
in silence being played until the video reaches the originally intended
seek target.
There was a hack in audio.c to deal with this. Replace it with a
different hack. The new hack probably works about as well as the old
hack, except it doesn't add weird crap to the audio resync path (which
is some of the worst code here, so this is some nice preparation for
rewriting it). As a more practical advantage, it doesn't discard the
audio demuxer packet cache. The old code did, which probably ruined
seeking in youtube DASH streams.
A non-hacky solution would be handling external files in the demuxer
layer. Then chaining the seeks would be pretty easy. But we're pretty
far from that, because it would either require intrusive changes to the
demuxer layer, or wouldn't be flexible enough to load/unload external
files at runtime. Maybe later.
-rw-r--r-- | demux/demux.c | 31 | ||||
-rw-r--r-- | demux/demux.h | 2 | ||||
-rw-r--r-- | player/audio.c | 22 | ||||
-rw-r--r-- | player/core.h | 6 | ||||
-rw-r--r-- | player/loadfile.c | 5 | ||||
-rw-r--r-- | player/playloop.c | 47 |
6 files changed, 78 insertions, 35 deletions
diff --git a/demux/demux.c b/demux/demux.c index 9646bf4f64..4ccb03a17d 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -197,6 +197,8 @@ struct demux_internal { double highest_av_pts; // highest non-subtitle PTS seen - for duration + bool blocked; + // Cached state. bool force_cache_update; struct mp_tags *stream_metadata; @@ -605,6 +607,7 @@ static void update_stream_selection_state(struct demux_internal *in, // other streams too, because they depend on other stream's selections. bool any_av_streams = false; + bool any_streams = false; for (int n = 0; n < in->num_streams; n++) { struct demux_stream *s = in->streams[n]->ds; @@ -612,6 +615,7 @@ static void update_stream_selection_state(struct demux_internal *in, s->eager = s->selected && !s->sh->attached_picture; if (s->eager) any_av_streams |= s->type != STREAM_SUB; + any_streams |= s->selected; } // Subtitles are only eagerly read if there are no other eagerly read @@ -625,6 +629,9 @@ static void update_stream_selection_state(struct demux_internal *in, } } + if (!any_streams) + in->blocked = false; + // Make sure any stream reselection or addition is reflected in the seek // ranges, and also get rid of data that is not needed anymore (or // rather, which can't be kept consistent). This has to happen after we've @@ -1279,7 +1286,7 @@ static bool read_packet(struct demux_internal *in) in->eof = false; in->idle = true; - if (!in->reading) + if (!in->reading || in->blocked) return false; // Check if we need to read a new packet. We do this if all queues are below @@ -1552,7 +1559,7 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds) pkt->stream = ds->sh->index; return pkt; } - if (!ds->reader_head) + if (!ds->reader_head || ds->in->blocked) return NULL; struct demux_packet *pkt = ds->reader_head; ds->reader_head = pkt->next; @@ -1622,7 +1629,7 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh) const char *t = stream_type_name(ds->type); MP_DBG(in, "reading packet for %s\n", t); in->eof = false; // force retry - while (ds->selected && !ds->reader_head) { + while (ds->selected && !ds->reader_head && !in->blocked) { in->reading = true; // Note: the following code marks EOF if it can't continue if (in->threading) { @@ -1673,6 +1680,8 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt) r = *out_pkt ? 1 : -1; } pthread_mutex_unlock(&ds->in->lock); + } else if (ds->in->blocked) { + r = 0; } else { *out_pkt = demux_read_packet(sh); r = *out_pkt ? 1 : -1; @@ -1698,7 +1707,7 @@ struct demux_packet *demux_read_any_packet(struct demuxer *demuxer) struct demux_internal *in = demuxer->in; assert(!in->threading); // doesn't work with threading bool read_more = true; - while (read_more) { + while (read_more && !in->blocked) { for (int n = 0; n < in->num_streams; n++) { in->reading = true; // force read_packet() to read struct demux_packet *pkt = dequeue_packet(in->streams[n]->ds); @@ -2223,6 +2232,7 @@ static void clear_reader_state(struct demux_internal *in) ds_clear_reader_state(in->streams[n]->ds); in->warned_queue_overflow = false; in->d_user->filepos = -1; // implicitly synchronized + in->blocked = false; assert(in->fw_bytes == 0); } @@ -2719,6 +2729,19 @@ void demux_disable_cache(demuxer_t *demuxer) pthread_mutex_unlock(&in->lock); } +// Disallow reading any packets and make readers think there is no new data +// yet, until a seek is issued. +void demux_block_reading(struct demuxer *demuxer, bool block) +{ + struct demux_internal *in = demuxer->in; + assert(demuxer == in->d_user); + + pthread_mutex_lock(&in->lock); + in->blocked = block; + pthread_cond_signal(&in->wakeup); + pthread_mutex_unlock(&in->lock); +} + // must be called not locked static void update_cache(struct demux_internal *in) { diff --git a/demux/demux.h b/demux/demux.h index e0c68a82cb..ffed39c770 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -285,6 +285,8 @@ void demux_set_ts_offset(struct demuxer *demuxer, double offset); int demux_control(struct demuxer *demuxer, int cmd, void *arg); +void demux_block_reading(struct demuxer *demuxer, bool block); + void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream, double ref_pts, bool selected); void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect); diff --git a/player/audio.c b/player/audio.c index 9b842a9003..ccddd790e7 100644 --- a/player/audio.c +++ b/player/audio.c @@ -283,7 +283,6 @@ void reset_audio_state(struct MPContext *mpctx) mpctx->delay = 0; mpctx->audio_drop_throttle = 0; mpctx->audio_stat_start = 0; - mpctx->audio_allow_second_chance_seek = false; } void uninit_audio_out(struct MPContext *mpctx) @@ -800,27 +799,6 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip) } ptsdiff = MPCLAMP(ptsdiff, -3600, 3600); - // Heuristic: if audio is "too far" ahead, and one of them is a separate - // track, allow a refresh seek to the correct position to fix it. - if (ptsdiff > 0.2 && mpctx->audio_allow_second_chance_seek && sync_to_video) { - struct ao_chain *ao_c = mpctx->ao_chain; - if (ao_c && ao_c->track && mpctx->vo_chain && mpctx->vo_chain->track && - ao_c->track->demuxer != mpctx->vo_chain->track->demuxer) - { - struct track *track = ao_c->track; - double pts = mpctx->video_pts; - if (pts != MP_NOPTS_VALUE) - pts += get_track_seek_offset(mpctx, track); - // (disable it first to make it take any effect) - demuxer_select_track(track->demuxer, track->stream, pts, false); - demuxer_select_track(track->demuxer, track->stream, pts, true); - reset_audio_state(mpctx); - MP_VERBOSE(mpctx, "retrying audio seek\n"); - return false; - } - } - mpctx->audio_allow_second_chance_seek = false; - int align = af_format_sample_alignment(ao_format); *skip = (int)(-ptsdiff * play_samplerate) / align * align; return true; diff --git a/player/core.h b/player/core.h index 041065e1f6..f2fed55366 100644 --- a/player/core.h +++ b/player/core.h @@ -412,9 +412,9 @@ typedef struct MPContext { struct seek_params seek; - // Allow audio to issue a second seek if audio is too far ahead (for non-hr - // seeks with external audio tracks). - bool audio_allow_second_chance_seek; + // Can be temporarily set to an external audio track after seeks. Then it + // must be seeked to the video position once video is done seeking. + struct track *seek_slave; /* Heuristic for relative chapter seeks: keep track which chapter * the user wanted to go to, even if we aren't exactly within the diff --git a/player/loadfile.c b/player/loadfile.c index edba9caa1b..4a886ff156 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -227,6 +227,8 @@ void reselect_demux_stream(struct MPContext *mpctx, struct track *track) if (pts != MP_NOPTS_VALUE) pts += get_track_seek_offset(mpctx, track); demuxer_select_track(track->demuxer, track->stream, pts, track->selected); + if (track == mpctx->seek_slave) + mpctx->seek_slave = NULL; } // Called from the demuxer thread if a new packet is available. @@ -548,6 +550,9 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track) sub_destroy(track->d_sub); + if (mpctx->seek_slave == track) + mpctx->seek_slave = NULL; + int index = 0; while (index < mpctx->num_tracks && mpctx->tracks[index] != track) index++; diff --git a/player/playloop.c b/player/playloop.c index 3db5818773..535bff883f 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -239,6 +239,7 @@ void reset_playback_state(struct MPContext *mpctx) mpctx->restart_complete = false; mpctx->paused_for_cache = false; mpctx->cache_buffer = 100; + mpctx->seek_slave = NULL; #if HAVE_ENCODING encode_lavc_discontinuity(mpctx->encode_lavc_ctx); @@ -252,7 +253,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) { struct MPOpts *opts = mpctx->opts; - if (!mpctx->demuxer || seek.type == MPSEEK_NONE || seek.amount == MP_NOPTS_VALUE) + if (!mpctx->demuxer || !seek.type || seek.amount == MP_NOPTS_VALUE) return; bool hr_seek_very_exact = seek.exact == MPSEEK_VERY_EXACT; @@ -326,13 +327,15 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) if (!demux_seek(mpctx->demuxer, demux_pts, demux_flags)) { if (!mpctx->demuxer->seekable) { - MP_ERR(mpctx, "Cannot seek in this file.\n"); + MP_ERR(mpctx, "Cannot seek in this stream.\n"); MP_ERR(mpctx, "You can force it with '--force-seekable=yes'.\n"); } return; } // Seek external, extra files too: + bool has_video = false; + struct track *external_audio = NULL; for (int t = 0; t < mpctx->num_tracks; t++) { struct track *track = mpctx->tracks[t]; if (track->selected && track->is_external && track->demuxer) { @@ -342,7 +345,12 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) if (demux_flags & SEEK_FACTOR) main_new_pos = seek_pts; demux_seek(track->demuxer, main_new_pos, 0); + if (track->type == STREAM_AUDIO && !external_audio) + external_audio = track; } + if (track->selected && !track->is_external && track->stream && + track->type == STREAM_VIDEO && !track->stream->attached_picture) + has_video = true; } if (!(seek.flags & MPSEEK_FLAG_NOFLUSH)) @@ -352,6 +360,17 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) if (mpctx->recorder) mp_recorder_mark_discontinuity(mpctx->recorder); + // When doing keyframe seeks (hr_seek=false) backwards (no SEEK_FORWARD), + // then video can seek before the external audio track (because video seek + // granularity is coarser than audio). The result would be playing video with + // silence until the audio seek target is reached. Work around by blocking + // the demuxer (decoders can't read) and seeking to video position later. + if (has_video && external_audio && !hr_seek && !(demux_flags & SEEK_FORWARD)) { + MP_VERBOSE(mpctx, "delayed seek for aid=%d\n", external_audio->user_tid); + demux_block_reading(external_audio->demuxer, true); + mpctx->seek_slave = external_audio; + } + /* Use the target time as "current position" for further relative * seeks etc until a new video frame has been decoded */ mpctx->last_seek_pts = seek_pts; @@ -376,9 +395,6 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek) mp_notify(mpctx, MPV_EVENT_SEEK, NULL); mp_notify(mpctx, MPV_EVENT_TICK, NULL); - mpctx->audio_allow_second_chance_seek = - !hr_seek && !(demux_flags & SEEK_FORWARD); - mpctx->ab_loop_clip = mpctx->last_seek_pts < opts->ab_loop[1]; mpctx->current_seek = seek; @@ -949,6 +965,24 @@ static void handle_playback_time(struct MPContext *mpctx) } } +static void handle_delayed_audio_seek(struct MPContext *mpctx) +{ + if (mpctx->seek_slave) { + if (mpctx->video_pts != MP_NOPTS_VALUE) { + // We know the video position now, so seek external audio to the + // correct position. + double pts = mpctx->video_pts + + get_track_seek_offset(mpctx, mpctx->seek_slave); + demux_seek(mpctx->seek_slave->demuxer, pts, 0); + mpctx->seek_slave = NULL; + } else if (mpctx->video_status >= STATUS_EOF) { + // We won't get a video position; don't stall the audio stream. + demux_block_reading(mpctx->seek_slave->demuxer, false); + mpctx->seek_slave = NULL; + } + } +} + // We always make sure audio and video buffers are filled before actually // starting playback. This code handles starting them at the same time. static void handle_playback_restart(struct MPContext *mpctx) @@ -991,7 +1025,6 @@ static void handle_playback_restart(struct MPContext *mpctx) mpctx->hrseek_active = false; mpctx->restart_complete = true; mpctx->current_seek = (struct seek_params){0}; - mpctx->audio_allow_second_chance_seek = false; handle_playback_time(mpctx); mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL); update_core_idle_state(mpctx); @@ -1099,6 +1132,8 @@ void run_playloop(struct MPContext *mpctx) fill_audio_out_buffers(mpctx); write_video(mpctx); + handle_delayed_audio_seek(mpctx); + handle_playback_restart(mpctx); handle_playback_time(mpctx); |