mirror of https://github.com/mpv-player/mpv
demux: support for some kinds of timed metadata
This makes ICY title changes show up at approximately the correct time, even if the demuxer buffer is huge. (It'll still be wrong if the stream byte cache contains a meaningful amount of data.) It should have the same effect for mid-stream metadata changes in e.g. OGG (untested). This is still somewhat fishy, but in parts due to ICY being fishy, and FFmpeg's metadata change API being somewhat fishy. For example, what happens if you seek? With FFmpeg AVFMT_EVENT_FLAG_METADATA_UPDATED and AVSTREAM_EVENT_FLAG_METADATA_UPDATED we hope that FFmpeg will correctly restore the correct metadata when the first packet is returned. If you seke with ICY, we're out of luck, and some audio will be associated with the wrong tag until we get a new title through ICY metadata update at an essentially random point (it's mostly inherent to ICY). Then the tags will switch back and forth, and this behavior will stick with the data stored in the demuxer cache. Fortunately, this can happen only if the HTTP stream is actually seekable, which it usually is not for ICY things. Seeking doesn't even make sense with ICY, since you can't know the exact metadata location. Basically ICY metsdata sucks. Some complexity is due to a microoptimization: I didn't want additional atomic accesses for each packet if no timed metadata is used. (It probably doesn't matter at all.)
This commit is contained in:
parent
020730da0b
commit
e7e06a47a0
|
@ -83,19 +83,27 @@ void mp_tags_clear(struct mp_tags *tags)
|
|||
talloc_free_children(tags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags)
|
||||
{
|
||||
struct mp_tags *new = talloc_zero(tparent, struct mp_tags);
|
||||
MP_RESIZE_ARRAY(new, new->keys, tags->num_keys);
|
||||
MP_RESIZE_ARRAY(new, new->values, tags->num_keys);
|
||||
new->num_keys = tags->num_keys;
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
new->keys[n] = talloc_strdup(new, tags->keys[n]);
|
||||
new->values[n] = talloc_strdup(new, tags->values[n]);
|
||||
}
|
||||
mp_tags_replace(new, tags);
|
||||
return new;
|
||||
}
|
||||
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src)
|
||||
{
|
||||
mp_tags_clear(dst);
|
||||
MP_RESIZE_ARRAY(dst, dst->keys, src->num_keys);
|
||||
MP_RESIZE_ARRAY(dst, dst->values, src->num_keys);
|
||||
dst->num_keys = src->num_keys;
|
||||
for (int n = 0; n < src->num_keys; n++) {
|
||||
dst->keys[n] = talloc_strdup(dst, src->keys[n]);
|
||||
dst->values[n] = talloc_strdup(dst, src->values[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a copy of the tags, but containing only keys in list. Also forces
|
||||
// the order and casing of the keys (for cosmetic reasons).
|
||||
// A trailing '*' matches the rest.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef MP_TAGS_H
|
||||
#define MP_TAGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
struct mp_tags {
|
||||
|
@ -17,6 +19,7 @@ char *mp_tags_get_str(struct mp_tags *tags, const char *key);
|
|||
char *mp_tags_get_bstr(struct mp_tags *tags, bstr key);
|
||||
void mp_tags_clear(struct mp_tags *tags);
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags);
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src);
|
||||
struct mp_tags *mp_tags_filtered(void *tparent, struct mp_tags *tags, char **list);
|
||||
void mp_tags_merge(struct mp_tags *tags, struct mp_tags *src);
|
||||
struct AVDictionary;
|
||||
|
|
304
demux/demux.c
304
demux/demux.c
|
@ -34,6 +34,7 @@
|
|||
#include "mpv_talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "osdep/threads.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
|
@ -125,13 +126,10 @@ struct demux_internal {
|
|||
|
||||
// The demuxer runs potentially in another thread, so we keep two demuxer
|
||||
// structs; the real demuxer can access the shadow struct only.
|
||||
// Since demuxer and user threads both don't use locks, a third demuxer
|
||||
// struct d_buffer is used to copy data between them in a synchronized way.
|
||||
struct demuxer *d_thread; // accessed by demuxer impl. (producer)
|
||||
struct demuxer *d_user; // accessed by player (consumer)
|
||||
struct demuxer *d_buffer; // protected by lock; used to sync d_user/thread
|
||||
|
||||
// The lock protects the packet queues (struct demux_stream), d_buffer,
|
||||
// The lock protects the packet queues (struct demux_stream),
|
||||
// and the fields below.
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
|
@ -147,6 +145,13 @@ struct demux_internal {
|
|||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
// If non-NULL, a _selected_ stream which is used for global (timed)
|
||||
// metadata. It will be an arbitrary stream that is hopefully not sparse
|
||||
// (i.e. not a subtitle stream). This is needed because due to variable
|
||||
// interleaving multiple streams won't agree whether timed metadata is in
|
||||
// effect yet at the same time position.
|
||||
struct demux_stream *master_stream;
|
||||
|
||||
int events;
|
||||
|
||||
bool warned_queue_overflow;
|
||||
|
@ -199,9 +204,10 @@ struct demux_internal {
|
|||
|
||||
bool blocked;
|
||||
|
||||
// Transient state.
|
||||
double duration;
|
||||
// Cached state.
|
||||
bool force_cache_update;
|
||||
struct mp_tags *stream_metadata;
|
||||
struct stream_cache_info stream_cache_info;
|
||||
int64_t stream_size;
|
||||
// Updated during init only.
|
||||
|
@ -305,6 +311,19 @@ struct demux_stream {
|
|||
// for closed captions (demuxer_feed_caption)
|
||||
struct sh_stream *cc;
|
||||
bool ignore_eof; // ignore stream in underrun detection
|
||||
|
||||
// timed metadata
|
||||
struct mp_packet_tags *tags_demux; // demuxer state (last updated metadata)
|
||||
struct mp_packet_tags *tags_reader; // reader state (last returned packet)
|
||||
struct mp_packet_tags *tags_init; // global state at start of demuxing
|
||||
};
|
||||
|
||||
// "Snapshot" of the tag state. Refcounted to avoid a copy per packet.
|
||||
struct mp_packet_tags {
|
||||
mp_atomic_int64 refcount;
|
||||
struct mp_tags *demux; // demuxer global tags (normal thing)
|
||||
struct mp_tags *stream; // byte stream tags (ICY crap)
|
||||
struct mp_tags *sh; // per sh_stream tags (e.g. OGG)
|
||||
};
|
||||
|
||||
// Return "a", or if that is NOPTS, return "def".
|
||||
|
@ -404,6 +423,56 @@ static void check_queue_consistency(struct demux_internal *in)
|
|||
}
|
||||
#endif
|
||||
|
||||
void mp_packet_tags_unref(struct mp_packet_tags *tags)
|
||||
{
|
||||
if (tags) {
|
||||
if (atomic_fetch_add(&tags->refcount, -1) == 1) {
|
||||
talloc_free(tags->sh);
|
||||
talloc_free(tags->demux);
|
||||
talloc_free(tags->stream);
|
||||
talloc_free(tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mp_packet_tags_setref(struct mp_packet_tags **dst, struct mp_packet_tags *src)
|
||||
{
|
||||
if (src)
|
||||
atomic_fetch_add(&src->refcount, 1);
|
||||
mp_packet_tags_unref(*dst);
|
||||
*dst = src;
|
||||
}
|
||||
|
||||
static struct mp_tags *tags_dup_or_null(struct mp_tags *t)
|
||||
{
|
||||
return t ? mp_tags_dup(NULL, t) : talloc_zero(NULL, struct mp_tags);
|
||||
}
|
||||
|
||||
// Return a "deep" copy. If tags==NULL, allocate a new one.
|
||||
static struct mp_packet_tags *mp_packet_tags_copy(struct mp_packet_tags *tags)
|
||||
{
|
||||
struct mp_packet_tags *new = talloc_ptrtype(NULL, new);
|
||||
*new = (struct mp_packet_tags){
|
||||
.refcount = ATOMIC_VAR_INIT(1),
|
||||
.demux = tags_dup_or_null(tags ? tags->demux : NULL),
|
||||
.stream = tags_dup_or_null(tags ? tags->stream : NULL),
|
||||
.sh = tags_dup_or_null(tags ? tags->sh : NULL),
|
||||
};
|
||||
return new;
|
||||
}
|
||||
|
||||
// Force a copy if refcount != 1.
|
||||
// (refcount==1 means we're the unambiguous owner.)
|
||||
// If *tags==NULL, allocate a blank one.
|
||||
static void mp_packet_tags_make_writable(struct mp_packet_tags **tags)
|
||||
{
|
||||
if (*tags && atomic_load(&(*tags)->refcount) == 1)
|
||||
return;
|
||||
struct mp_packet_tags *new = mp_packet_tags_copy(*tags);
|
||||
mp_packet_tags_unref(*tags);
|
||||
*tags = new;
|
||||
}
|
||||
|
||||
static void recompute_buffers(struct demux_stream *ds)
|
||||
{
|
||||
ds->fw_packs = 0;
|
||||
|
@ -629,16 +698,25 @@ static void update_stream_selection_state(struct demux_internal *in,
|
|||
|
||||
bool any_av_streams = false;
|
||||
bool any_streams = false;
|
||||
struct demux_stream *master = NULL;
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *s = in->streams[n]->ds;
|
||||
|
||||
s->eager = s->selected && !s->sh->attached_picture;
|
||||
if (s->eager)
|
||||
if (s->eager) {
|
||||
any_av_streams |= s->type != STREAM_SUB;
|
||||
if (!master ||
|
||||
(master->type == STREAM_VIDEO && s->type == STREAM_AUDIO))
|
||||
{
|
||||
master = s;
|
||||
}
|
||||
}
|
||||
any_streams |= s->selected;
|
||||
}
|
||||
|
||||
in->master_stream = master;
|
||||
|
||||
// Subtitles are only eagerly read if there are no other eagerly read
|
||||
// streams.
|
||||
if (any_av_streams) {
|
||||
|
@ -714,6 +792,14 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
|
|||
return sh;
|
||||
}
|
||||
|
||||
static void ds_destroy(void *ptr)
|
||||
{
|
||||
struct demux_stream *ds = ptr;
|
||||
mp_packet_tags_unref(ds->tags_init);
|
||||
mp_packet_tags_unref(ds->tags_reader);
|
||||
mp_packet_tags_unref(ds->tags_demux);
|
||||
}
|
||||
|
||||
// Add a new sh_stream to the demuxer. Note that as soon as the stream has been
|
||||
// added, it must be immutable, and must not be released (this will happen when
|
||||
// the demuxer is destroyed).
|
||||
|
@ -734,6 +820,7 @@ static void demux_add_sh_stream_locked(struct demux_internal *in,
|
|||
.global_correct_dts = true,
|
||||
.global_correct_pos = true,
|
||||
};
|
||||
talloc_set_destructor(sh->ds, ds_destroy);
|
||||
|
||||
if (!sh->codec->codec)
|
||||
sh->codec->codec = "";
|
||||
|
@ -758,6 +845,11 @@ static void demux_add_sh_stream_locked(struct demux_internal *in,
|
|||
|
||||
update_stream_selection_state(in, sh->ds);
|
||||
|
||||
mp_packet_tags_make_writable(&sh->ds->tags_init);
|
||||
mp_tags_replace(sh->ds->tags_init->demux, in->d_thread->metadata);
|
||||
mp_tags_replace(sh->ds->tags_init->sh, sh->tags);
|
||||
mp_packet_tags_setref(&sh->ds->tags_reader, sh->ds->tags_init);
|
||||
|
||||
in->events |= DEMUX_EVENT_STREAMS;
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
|
@ -767,11 +859,19 @@ static void demux_add_sh_stream_locked(struct demux_internal *in,
|
|||
void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_thread);
|
||||
pthread_mutex_lock(&in->lock);
|
||||
demux_add_sh_stream_locked(in, sh);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
static void ds_modify_demux_tags(struct demux_stream *ds)
|
||||
{
|
||||
if (!ds->tags_demux)
|
||||
mp_packet_tags_setref(&ds->tags_demux, ds->tags_init);
|
||||
mp_packet_tags_make_writable(&ds->tags_demux);
|
||||
}
|
||||
|
||||
// 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.)
|
||||
|
@ -782,21 +882,16 @@ void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
|||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_thread);
|
||||
struct demux_stream *ds = sh->ds;
|
||||
assert(ds); // stream must have been added
|
||||
|
||||
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);
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
} else {
|
||||
// not added yet
|
||||
talloc_free(sh->tags);
|
||||
sh->tags = talloc_steal(sh, tags);
|
||||
}
|
||||
ds_modify_demux_tags(ds);
|
||||
mp_tags_replace(ds->tags_demux->sh, tags);
|
||||
talloc_free(tags);
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// Return a stream with the given index. Since streams can only be added during
|
||||
|
@ -1233,6 +1328,7 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
|||
|
||||
dp->stream = stream->index;
|
||||
dp->next = NULL;
|
||||
mp_packet_tags_setref(&dp->metadata, ds->tags_demux);
|
||||
|
||||
// (keep in mind that even if the reader went out of data, the queue is not
|
||||
// necessarily empty due to the backbuffer)
|
||||
|
@ -1289,10 +1385,9 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
|||
double duration = in->highest_av_pts - in->d_thread->start_time;
|
||||
if (duration > in->d_thread->duration) {
|
||||
in->d_thread->duration = duration;
|
||||
// (Don't wakeup like like demux_changed(), would be too noisy.)
|
||||
in->d_thread->events |= DEMUX_EVENT_DURATION;
|
||||
in->d_buffer->duration = duration;
|
||||
in->d_buffer->events |= DEMUX_EVENT_DURATION;
|
||||
// (Don't wakeup user thread, would be too noisy.)
|
||||
in->events |= DEMUX_EVENT_DURATION;
|
||||
in->duration = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1631,6 +1726,18 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
|||
pkt->end = MP_ADD_PTS(pkt->end, ds->in->ts_offset);
|
||||
}
|
||||
|
||||
// Apply timed metadata when packet is returned to user.
|
||||
// (The tags_init thing is a microopt. to not do refcounting for sane files.)
|
||||
struct mp_packet_tags *metadata = pkt->metadata;
|
||||
if (!metadata)
|
||||
metadata = ds->tags_init;
|
||||
if (metadata != ds->tags_reader) {
|
||||
mp_packet_tags_setref(&ds->tags_reader, metadata);
|
||||
ds->in->events |= DEMUX_EVENT_METADATA;
|
||||
if (ds->in->wakeup_cb)
|
||||
ds->in->wakeup_cb(ds->in->wakeup_cb_ctx);
|
||||
}
|
||||
|
||||
prune_old_packets(ds->in);
|
||||
return pkt;
|
||||
}
|
||||
|
@ -1865,80 +1972,48 @@ static void demux_update_replaygain(demuxer_t *demuxer)
|
|||
}
|
||||
}
|
||||
|
||||
// Copy all fields from src to dst, depending on event flags.
|
||||
// Copy some fields from src to dst (for initialization).
|
||||
static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
{
|
||||
if (src->events & DEMUX_EVENT_INIT) {
|
||||
// Note that we do as shallow copies as possible. We expect the data
|
||||
// that is not-copied (only referenced) to be immutable.
|
||||
// This implies e.g. that no chapters are added after initialization.
|
||||
dst->chapters = src->chapters;
|
||||
dst->num_chapters = src->num_chapters;
|
||||
dst->editions = src->editions;
|
||||
dst->num_editions = src->num_editions;
|
||||
dst->edition = src->edition;
|
||||
dst->attachments = src->attachments;
|
||||
dst->num_attachments = src->num_attachments;
|
||||
dst->matroska_data = src->matroska_data;
|
||||
dst->playlist = src->playlist;
|
||||
dst->seekable = src->seekable;
|
||||
dst->partially_seekable = src->partially_seekable;
|
||||
dst->filetype = src->filetype;
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->duration = src->duration;
|
||||
dst->is_network = src->is_network;
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (src->events & DEMUX_EVENT_DURATION)
|
||||
dst->duration = src->duration;
|
||||
|
||||
dst->events |= src->events;
|
||||
src->events = 0;
|
||||
// Note that we do as shallow copies as possible. We expect the data
|
||||
// that is not-copied (only referenced) to be immutable.
|
||||
// This implies e.g. that no chapters are added after initialization.
|
||||
dst->chapters = src->chapters;
|
||||
dst->num_chapters = src->num_chapters;
|
||||
dst->editions = src->editions;
|
||||
dst->num_editions = src->num_editions;
|
||||
dst->edition = src->edition;
|
||||
dst->attachments = src->attachments;
|
||||
dst->num_attachments = src->num_attachments;
|
||||
dst->matroska_data = src->matroska_data;
|
||||
dst->playlist = src->playlist;
|
||||
dst->seekable = src->seekable;
|
||||
dst->partially_seekable = src->partially_seekable;
|
||||
dst->filetype = src->filetype;
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->duration = src->duration;
|
||||
dst->is_network = src->is_network;
|
||||
dst->priv = src->priv;
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
}
|
||||
|
||||
// This is called by demuxer implementations if certain parameters change
|
||||
// at runtime.
|
||||
// events is one of DEMUX_EVENT_*
|
||||
// The code will copy the fields references by the events to the user-thread.
|
||||
void demux_changed(demuxer_t *demuxer, int events)
|
||||
// This is called by demuxer implementations if demuxer->metadata changed.
|
||||
// (It will be propagated to the user as timed metadata.)
|
||||
void demux_metadata_changed(demuxer_t *demuxer)
|
||||
{
|
||||
assert(demuxer == demuxer->in->d_thread); // call from demuxer impl. only
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
demuxer->events |= events;
|
||||
|
||||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (demuxer->events & DEMUX_EVENT_INIT)
|
||||
demuxer_sort_chapters(demuxer);
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
ds_modify_demux_tags(ds);
|
||||
mp_tags_replace(ds->tags_demux->demux, demuxer->metadata);
|
||||
}
|
||||
|
||||
demux_copy(in->d_buffer, demuxer);
|
||||
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
|
@ -1950,15 +2025,18 @@ static void update_final_metadata(demuxer_t *demuxer)
|
|||
|
||||
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);
|
||||
}
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
// (replace them even if unnecessary, simpler and doesn't hurt)
|
||||
if (sh->ds->tags_reader)
|
||||
mp_tags_replace(sh->tags, sh->ds->tags_reader->sh);
|
||||
}
|
||||
|
||||
struct mp_packet_tags *tags =
|
||||
in->master_stream ? in->master_stream->tags_reader : NULL;
|
||||
|
||||
if (tags)
|
||||
mp_tags_replace(demuxer->metadata, tags->demux);
|
||||
|
||||
// Often for useful audio-only files, which have metadata in the audio track
|
||||
// metadata instead of the main metadata, but can also have cover art
|
||||
// metadata (which libavformat likes to treat as video streams).
|
||||
|
@ -1977,8 +2055,8 @@ static void update_final_metadata(demuxer_t *demuxer)
|
|||
if (vstreams == 0 && astreams == 1)
|
||||
mp_tags_merge(demuxer->metadata, in->streams[astream_id]->tags);
|
||||
|
||||
if (in->stream_metadata)
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
if (tags)
|
||||
mp_tags_merge(demuxer->metadata, tags->stream);
|
||||
}
|
||||
|
||||
// Called by the user thread (i.e. player) to update metadata and other things
|
||||
|
@ -1992,13 +2070,14 @@ void demux_update(demuxer_t *demuxer)
|
|||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
demux_copy(demuxer, in->d_buffer);
|
||||
demuxer->events |= in->events;
|
||||
in->events = 0;
|
||||
if (demuxer->events & DEMUX_EVENT_METADATA)
|
||||
update_final_metadata(demuxer);
|
||||
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
|
||||
demux_update_replaygain(demuxer);
|
||||
if (demuxer->events & DEMUX_EVENT_DURATION)
|
||||
demuxer->duration = in->duration;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
|
@ -2043,7 +2122,6 @@ static void demux_maybe_replace_stream(struct demuxer *demuxer)
|
|||
free_stream(demuxer->stream);
|
||||
demuxer->stream = open_memory_stream(NULL, 0); // dummy
|
||||
in->d_thread->stream = demuxer->stream;
|
||||
in->d_buffer->stream = demuxer->stream;
|
||||
|
||||
if (demuxer->desc->control)
|
||||
demuxer->desc->control(in->d_thread, DEMUXER_CTRL_REPLACE_STREAM, NULL);
|
||||
|
@ -2064,6 +2142,19 @@ static void demux_init_ccs(struct demuxer *demuxer, struct demux_opts *opts)
|
|||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// Each stream contains a copy of the global demuxer metadata, but this might
|
||||
// be outdated if a stream gets added and then metadata does get set during
|
||||
// early init.
|
||||
static void fixup_metadata(struct demux_internal *in)
|
||||
{
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
mp_packet_tags_make_writable(&ds->tags_init);
|
||||
mp_tags_replace(ds->tags_init->demux, in->d_thread->metadata);
|
||||
mp_packet_tags_setref(&ds->tags_reader, ds->tags_init);
|
||||
}
|
||||
}
|
||||
|
||||
static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct demuxer_desc *desc,
|
||||
|
@ -2098,7 +2189,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
|||
*in = (struct demux_internal){
|
||||
.log = demuxer->log,
|
||||
.d_thread = talloc(demuxer, struct demuxer),
|
||||
.d_buffer = talloc(demuxer, struct demuxer),
|
||||
.d_user = demuxer,
|
||||
.min_secs = opts->min_secs,
|
||||
.max_bytes = opts->max_bytes,
|
||||
|
@ -2119,11 +2209,8 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
|||
MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, in->current_range);
|
||||
|
||||
*in->d_thread = *demuxer;
|
||||
*in->d_buffer = *demuxer;
|
||||
|
||||
in->d_thread->metadata = talloc_zero(in->d_thread, struct mp_tags);
|
||||
in->d_user->metadata = talloc_zero(in->d_user, struct mp_tags);
|
||||
in->d_buffer->metadata = talloc_zero(in->d_buffer, struct mp_tags);
|
||||
|
||||
mp_dbg(log, "Trying demuxer: %s (force-level: %s)\n",
|
||||
desc->name, d_level(check));
|
||||
|
@ -2155,7 +2242,11 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
|||
demux_init_cuesheet(in->d_thread);
|
||||
demux_init_cache(demuxer);
|
||||
demux_init_ccs(demuxer, opts);
|
||||
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
|
||||
demux_copy(in->d_user, in->d_thread);
|
||||
in->duration = in->d_thread->duration;
|
||||
demuxer_sort_chapters(demuxer);
|
||||
fixup_metadata(in);
|
||||
in->events = DEMUX_EVENT_ALL;
|
||||
demux_update(demuxer);
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD,
|
||||
&(int){params ? params->initial_readahead : false});
|
||||
|
@ -2823,9 +2914,12 @@ static void update_cache(struct demux_internal *in)
|
|||
in->stream_size = stream_size;
|
||||
in->stream_cache_info = stream_cache_info;
|
||||
if (stream_metadata) {
|
||||
talloc_free(in->stream_metadata);
|
||||
in->stream_metadata = talloc_steal(in, stream_metadata);
|
||||
in->d_buffer->events |= DEMUX_EVENT_METADATA;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
ds_modify_demux_tags(ds);
|
||||
mp_tags_replace(ds->tags_demux->stream, stream_metadata);
|
||||
}
|
||||
talloc_free(stream_metadata);
|
||||
}
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
|
|
@ -303,7 +303,7 @@ void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
|||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_metadata_changed(demuxer_t *demuxer);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
void demux_disable_cache(demuxer_t *demuxer);
|
||||
|
@ -318,4 +318,7 @@ bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
|||
|
||||
const char *stream_type_name(enum stream_type type);
|
||||
|
||||
void mp_packet_tags_unref(struct mp_packet_tags *tags);
|
||||
void mp_packet_tags_setref(struct mp_packet_tags **dst, struct mp_packet_tags *src);
|
||||
|
||||
#endif /* MPLAYER_DEMUXER_H */
|
||||
|
|
|
@ -748,13 +748,13 @@ static void add_new_streams(demuxer_t *demuxer)
|
|||
handle_new_stream(demuxer, priv->num_streams);
|
||||
}
|
||||
|
||||
static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
static void update_metadata(demuxer_t *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
if (priv->avfc->event_flags & AVFMT_EVENT_FLAG_METADATA_UPDATED) {
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->metadata);
|
||||
priv->avfc->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
demux_metadata_changed(demuxer);
|
||||
}
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
|
@ -923,7 +923,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
|||
add_new_streams(demuxer);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
|
||||
demuxer->ts_resets_possible =
|
||||
priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS);
|
||||
|
@ -994,7 +993,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
|||
}
|
||||
|
||||
add_new_streams(demux);
|
||||
update_metadata(demux, pkt);
|
||||
update_metadata(demux);
|
||||
|
||||
assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
|
||||
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||
|
@ -1143,9 +1142,9 @@ redo:
|
|||
priv->cur_program = prog->progid = program->id;
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->programs[p]->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
update_metadata(demuxer);
|
||||
// Enforce metadata update even if no explicit METADATA_UPDATED since we switched program.
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
demux_metadata_changed(demuxer);
|
||||
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "demux.h"
|
||||
|
||||
#include "packet.h"
|
||||
|
||||
|
@ -34,6 +35,7 @@ static void packet_destroy(void *ptr)
|
|||
{
|
||||
struct demux_packet *dp = ptr;
|
||||
av_packet_unref(dp->avpacket);
|
||||
mp_packet_tags_unref(dp->metadata);
|
||||
}
|
||||
|
||||
// This actually preserves only data and side data, not PTS/DTS/pos/etc.
|
||||
|
@ -129,6 +131,7 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr
|
|||
dst->codec = src->codec;
|
||||
dst->keyframe = src->keyframe;
|
||||
dst->stream = src->stream;
|
||||
mp_packet_tags_setref(&dst->metadata, src->metadata);
|
||||
}
|
||||
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
|
|
|
@ -44,6 +44,7 @@ typedef struct demux_packet {
|
|||
struct demux_packet *next;
|
||||
struct AVPacket *avpacket; // keep the buffer allocation and sidedata
|
||||
double kf_seek_pts; // demux.c internal: seek pts for keyframe range
|
||||
struct mp_packet_tags *metadata; // timed metadata (demux.c internal)
|
||||
} demux_packet_t;
|
||||
|
||||
struct AVBufferRef;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#if HAVE_STDATOMIC
|
||||
#include <stdatomic.h>
|
||||
typedef _Atomic float mp_atomic_float;
|
||||
typedef _Atomic int64_t mp_atomic_int64;
|
||||
#else
|
||||
|
||||
// Emulate the parts of C11 stdatomic.h needed by mpv.
|
||||
|
@ -38,6 +39,7 @@ typedef struct { uint_least32_t v; } atomic_uint_least32_t;
|
|||
typedef struct { unsigned long long v; } atomic_ullong;
|
||||
|
||||
typedef struct { float v; } mp_atomic_float;
|
||||
typedef struct { int64_t v; } mp_atomic_int64;
|
||||
|
||||
#define ATOMIC_VAR_INIT(x) \
|
||||
{.v = (x)}
|
||||
|
|
Loading…
Reference in New Issue