diff options
Diffstat (limited to 'demux/demux.c')
-rw-r--r-- | demux/demux.c | 334 |
1 files changed, 220 insertions, 114 deletions
diff --git a/demux/demux.c b/demux/demux.c index 6038d244e0..648e629f77 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,7 +156,10 @@ 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; + bool correct_dts; // packet DTS is strictly monotonically increasing + bool correct_pos; // packet pos is strictly monotonically increasing size_t packs; // number of packets in buffer size_t bytes; // total bytes of packets in buffer double base_ts; // timestamp of the last packet returned to decoder @@ -161,12 +168,12 @@ struct demux_stream { size_t last_br_bytes; // summed packet sizes since last bitrate calculation double bitrate; int64_t last_pos; + double last_dts; struct demux_packet *head; struct demux_packet *tail; // for closed captions (demuxer_feed_caption) struct sh_stream *cc; - }; // Return "a", or if that is NOPTS, return "def". @@ -199,7 +206,10 @@ 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; + ds->last_dts = MP_NOPTS_VALUE; + ds->correct_dts = ds->correct_pos = true; } void demux_set_ts_offset(struct demuxer *demuxer, double offset) @@ -222,6 +232,7 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type) .ff_index = -1, // may be overwritten by demuxer .demuxer_id = -1, // ... same .codec = talloc_zero(sh, struct mp_codec_params), + .tags = talloc_zero(sh, struct mp_tags), }; sh->codec->type = type; return sh; @@ -266,6 +277,33 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh) pthread_mutex_unlock(&in->lock); } +// Update sh->tags (lazily). This must be called by demuxers which update +// stream tags after init. (sh->tags can be accessed by the playback thread, +// which means the demuxer thread cannot write or read it directly.) +// Before init is finished, sh->tags can still be accessed freely. +// Ownership of tags goes to the function. +void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh, + struct mp_tags *tags) +{ + struct demux_internal *in = demuxer->in; + assert(demuxer == in->d_thread); + + if (sh->ds) { + while (demuxer->num_update_stream_tags <= sh->index) { + MP_TARRAY_APPEND(demuxer, demuxer->update_stream_tags, + demuxer->num_update_stream_tags, NULL); + } + talloc_free(demuxer->update_stream_tags[sh->index]); + demuxer->update_stream_tags[sh->index] = talloc_steal(demuxer, tags); + + demux_changed(demuxer, DEMUX_EVENT_METADATA); + } else { + // not added yet + talloc_free(sh->tags); + sh->tags = talloc_steal(sh, tags); + } +} + // Return a stream with the given index. Since streams can only be added during // the lifetime of the demuxer, it is guaranteed that an index within the valid // range [0, demux_get_num_stream()) always returns a valid sh_stream pointer, @@ -389,6 +427,59 @@ 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; + bool refresh_possible = true; + for (int n = 0; n < in->num_streams; n++) { + struct demux_stream *ds = in->streams[n]->ds; + + if (!ds->selected) + continue; + + 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->need_refresh = false; + + refresh_possible &= ds->correct_dts || ds->correct_pos; + } + + 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 (!refresh_possible) { + MP_VERBOSE(in, "can't issue refresh seek\n"); + 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 have any packets yet will return all packets, + // other streams return packets only starting from the last position. + if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE) + 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; @@ -399,25 +490,34 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp) struct demux_internal *in = ds->in; pthread_mutex_lock(&in->lock); - bool drop = false; + bool drop = ds->refreshing; if (ds->refreshing) { // Resume reading once the old position was reached (i.e. we start // returning packets where we left off before the refresh). - drop = dp->pos <= ds->last_pos; - if (dp->pos >= ds->last_pos) - ds->refreshing = false; + // If it's the same position, drop, but continue normally next time. + if (ds->correct_dts) { + ds->refreshing = dp->dts < ds->last_dts; + } else if (ds->correct_pos) { + ds->refreshing = dp->pos < ds->last_pos; + } else { + ds->refreshing = false; // should not happen + } } - if (!ds->selected || in->seeking || drop) { + if (!ds->selected || ds->need_refresh || in->seeking || drop) { pthread_mutex_unlock(&in->lock); talloc_free(dp); return; } + ds->correct_pos &= dp->pos >= 0 && dp->pos > ds->last_pos; + ds->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > ds->last_dts; + ds->last_pos = dp->pos; + ds->last_dts = dp->dts; + dp->stream = stream->index; dp->next = NULL; - ds->last_pos = dp->pos; ds->packs++; ds->bytes += dp->len; if (ds->tail) { @@ -505,27 +605,40 @@ 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) { - for (int n = 0; n < in->num_streams; n++) - in->streams[n]->ds->eof = true; - // If we had EOF previously, then don't wakeup (avoids wakeup loop) - if (!in->last_eof) { - if (in->wakeup_cb) - in->wakeup_cb(in->wakeup_cb_ctx); - pthread_cond_signal(&in->wakeup); - MP_VERBOSE(in, "EOF reached.\n"); + if (!in->seeking) { + if (eof) { + for (int n = 0; n < in->num_streams; n++) + in->streams[n]->ds->eof = true; + // If we had EOF previously, then don't wakeup (avoids wakeup loop) + if (!in->last_eof) { + if (in->wakeup_cb) + in->wakeup_cb(in->wakeup_cb_ctx); + pthread_cond_signal(&in->wakeup); + MP_VERBOSE(in, "EOF reached.\n"); + } } + in->eof = in->last_eof = eof; } - in->eof = in->last_eof = eof; return true; } @@ -550,42 +663,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 +680,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 +687,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); @@ -853,17 +928,18 @@ static int decode_float(char *str, float *out) return 0; } -static int decode_gain(demuxer_t *demuxer, const char *tag, float *out) +static int decode_gain(struct mp_log *log, struct mp_tags *tags, + const char *tag, float *out) { char *tag_val = NULL; float dec_val; - tag_val = mp_tags_get_str(demuxer->metadata, tag); + tag_val = mp_tags_get_str(tags, tag); if (!tag_val) return -1; - if (decode_float(tag_val, &dec_val)) { - mp_msg(demuxer->log, MSGL_ERR, "Invalid replaygain value\n"); + if (decode_float(tag_val, &dec_val) < 0) { + mp_msg(log, MSGL_ERR, "Invalid replaygain value\n"); return -1; } @@ -871,59 +947,65 @@ static int decode_gain(demuxer_t *demuxer, const char *tag, float *out) return 0; } -static int decode_peak(demuxer_t *demuxer, const char *tag, float *out) +static int decode_peak(struct mp_log *log, struct mp_tags *tags, + const char *tag, float *out) { char *tag_val = NULL; float dec_val; *out = 1.0; - tag_val = mp_tags_get_str(demuxer->metadata, tag); + tag_val = mp_tags_get_str(tags, tag); if (!tag_val) return 0; - if (decode_float(tag_val, &dec_val)) - return 0; - - if (dec_val == 0.0) - return 0; + if (decode_float(tag_val, &dec_val) < 0 || dec_val <= 0.0) + return -1; *out = dec_val; return 0; } -static void apply_replaygain(demuxer_t *demuxer, struct replaygain_data *rg) -{ - struct demux_internal *in = demuxer->in; - for (int n = 0; n < in->num_streams; n++) { - struct sh_stream *sh = in->streams[n]; - if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) { - MP_VERBOSE(demuxer, "Replaygain: Track=%f/%f Album=%f/%f\n", - rg->track_gain, rg->track_peak, - rg->album_gain, rg->album_peak); - sh->codec->replaygain_data = talloc_memdup(in, rg, sizeof(*rg)); - } - } -} - -static void demux_export_replaygain(demuxer_t *demuxer) +static struct replaygain_data *decode_rgain(struct mp_log *log, + struct mp_tags *tags) { struct replaygain_data rg = {0}; - if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) && - !decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) && - !decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) && - !decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak)) + if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 && + decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0) { - apply_replaygain(demuxer, &rg); + if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 || + decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0) + { + rg.album_gain = rg.track_gain; + rg.album_peak = rg.track_peak; + } + return talloc_memdup(NULL, &rg, sizeof(rg)); } - if (!decode_gain(demuxer, "REPLAYGAIN_GAIN", &rg.track_gain) && - !decode_peak(demuxer, "REPLAYGAIN_PEAK", &rg.track_peak)) + if (decode_gain(log, tags, "REPLAYGAIN_GAIN", &rg.track_gain) >= 0 && + decode_peak(log, tags, "REPLAYGAIN_PEAK", &rg.track_peak) >= 0) { rg.album_gain = rg.track_gain; rg.album_peak = rg.track_peak; - apply_replaygain(demuxer, &rg); + return talloc_memdup(NULL, &rg, sizeof(rg)); + } + + return NULL; +} + +static void demux_update_replaygain(demuxer_t *demuxer) +{ + struct demux_internal *in = demuxer->in; + for (int n = 0; n < in->num_streams; n++) { + struct sh_stream *sh = in->streams[n]; + if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) { + struct replaygain_data *rg = decode_rgain(demuxer->log, sh->tags); + if (!rg) + rg = decode_rgain(demuxer->log, demuxer->metadata); + if (rg) + sh->codec->replaygain_data = talloc_steal(in, rg); + } } } @@ -947,15 +1029,29 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src) dst->partially_seekable = src->partially_seekable; dst->filetype = src->filetype; dst->ts_resets_possible = src->ts_resets_possible; - dst->allow_refresh_seeks = src->allow_refresh_seeks; dst->fully_read = src->fully_read; dst->start_time = src->start_time; dst->priv = src->priv; } + if (src->events & DEMUX_EVENT_METADATA) { talloc_free(dst->metadata); dst->metadata = mp_tags_dup(dst, src->metadata); + + if (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]); + dst->update_stream_tags[n] = + talloc_steal(dst->update_stream_tags, src->update_stream_tags[n]); + src->update_stream_tags[n] = NULL; + } } + dst->events |= src->events; src->events = 0; } @@ -977,8 +1073,6 @@ void demux_changed(demuxer_t *demuxer, int events) if (demuxer->events & DEMUX_EVENT_INIT) demuxer_sort_chapters(demuxer); - if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS)) - demux_export_replaygain(demuxer); demux_copy(in->d_buffer, demuxer); @@ -1001,8 +1095,28 @@ void demux_update(demuxer_t *demuxer) demux_copy(demuxer, in->d_buffer); demuxer->events |= in->events; in->events = 0; - if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA)) - mp_tags_merge(demuxer->metadata, in->stream_metadata); + if (demuxer->events & DEMUX_EVENT_METADATA) { + int num_streams = MPMIN(in->num_streams, demuxer->num_update_stream_tags); + for (int n = 0; n < num_streams; n++) { + struct mp_tags *tags = demuxer->update_stream_tags[n]; + demuxer->update_stream_tags[n] = NULL; + if (tags) { + struct sh_stream *sh = in->streams[n]; + talloc_free(sh->tags); + sh->tags = talloc_steal(sh, tags); + } + } + + // Often useful audio-only files, which have metadata in the audio track + // metadata instead of the main metadata (especially OGG). + if (in->num_streams == 1) + mp_tags_merge(demuxer->metadata, in->streams[0]->tags); + + if (in->stream_metadata) + mp_tags_merge(demuxer->metadata, in->stream_metadata); + } + if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS)) + demux_update_replaygain(demuxer); pthread_mutex_unlock(&in->lock); } @@ -1074,6 +1188,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 +1392,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 +1404,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 { |