summaryrefslogtreecommitdiffstats
path: root/demux
diff options
context:
space:
mode:
Diffstat (limited to 'demux')
-rw-r--r--demux/cache.c331
-rw-r--r--demux/cache.h16
-rw-r--r--demux/codec_tags.c280
-rw-r--r--demux/codec_tags.h35
-rw-r--r--demux/cue.c270
-rw-r--r--demux/cue.h43
-rw-r--r--demux/demux.c4624
-rw-r--r--demux/demux.h361
-rw-r--r--demux/demux_cue.c304
-rw-r--r--demux/demux_disc.c360
-rw-r--r--demux/demux_edl.c651
-rw-r--r--demux/demux_lavf.c1448
-rw-r--r--demux/demux_libarchive.c120
-rw-r--r--demux/demux_mf.c373
-rw-r--r--demux/demux_mkv.c3392
-rw-r--r--demux/demux_mkv_timeline.c642
-rw-r--r--demux/demux_null.c35
-rw-r--r--demux/demux_playlist.c584
-rw-r--r--demux/demux_raw.c326
-rw-r--r--demux/demux_timeline.c719
-rw-r--r--demux/ebml.c619
-rw-r--r--demux/ebml.h93
-rw-r--r--demux/matroska.h24
-rw-r--r--demux/packet.c244
-rw-r--r--demux/packet.h86
-rw-r--r--demux/stheader.h119
-rw-r--r--demux/timeline.c41
-rw-r--r--demux/timeline.h72
28 files changed, 16212 insertions, 0 deletions
diff --git a/demux/cache.c b/demux/cache.c
new file mode 100644
index 0000000..562eab0
--- /dev/null
+++ b/demux/cache.c
@@ -0,0 +1,331 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "cache.h"
+#include "common/msg.h"
+#include "common/av_common.h"
+#include "demux.h"
+#include "options/path.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "osdep/io.h"
+
+struct demux_cache_opts {
+ char *cache_dir;
+ int unlink_files;
+};
+
+#define OPT_BASE_STRUCT struct demux_cache_opts
+
+const struct m_sub_options demux_cache_conf = {
+ .opts = (const struct m_option[]){
+ {"demuxer-cache-dir", OPT_STRING(cache_dir), .flags = M_OPT_FILE},
+ {"demuxer-cache-unlink-files", OPT_CHOICE(unlink_files,
+ {"immediate", 2}, {"whendone", 1}, {"no", 0}),
+ },
+ {"cache-dir", OPT_REPLACED("demuxer-cache-dir")},
+ {"cache-unlink-files", OPT_REPLACED("demuxer-cache-unlink-files")},
+ {0}
+ },
+ .size = sizeof(struct demux_cache_opts),
+ .defaults = &(const struct demux_cache_opts){
+ .unlink_files = 2,
+ },
+};
+
+struct demux_cache {
+ struct mp_log *log;
+ struct demux_cache_opts *opts;
+
+ char *filename;
+ bool need_unlink;
+ int fd;
+ int64_t file_pos;
+ uint64_t file_size;
+};
+
+struct pkt_header {
+ uint32_t data_len;
+ uint32_t av_flags;
+ uint32_t num_sd;
+};
+
+struct sd_header {
+ uint32_t av_type;
+ uint32_t len;
+};
+
+static void cache_destroy(void *p)
+{
+ struct demux_cache *cache = p;
+
+ if (cache->fd >= 0)
+ close(cache->fd);
+
+ if (cache->need_unlink && cache->opts->unlink_files >= 1) {
+ if (unlink(cache->filename))
+ MP_ERR(cache, "Failed to delete cache temporary file.\n");
+ }
+}
+
+// Create a cache. This also initializes the cache file from the options. The
+// log parameter must stay valid until demux_cache is destroyed.
+// Free with talloc_free().
+struct demux_cache *demux_cache_create(struct mpv_global *global,
+ struct mp_log *log)
+{
+ struct demux_cache *cache = talloc_zero(NULL, struct demux_cache);
+ talloc_set_destructor(cache, cache_destroy);
+ cache->opts = mp_get_config_group(cache, global, &demux_cache_conf);
+ cache->log = log;
+ cache->fd = -1;
+
+ char *cache_dir = cache->opts->cache_dir;
+ if (cache_dir && cache_dir[0]) {
+ cache_dir = mp_get_user_path(NULL, global, cache_dir);
+ } else {
+ cache_dir = mp_find_user_file(NULL, global, "cache", "");
+ }
+
+ if (!cache_dir || !cache_dir[0])
+ goto fail;
+
+ mp_mkdirp(cache_dir);
+ cache->filename = mp_path_join(cache, cache_dir, "mpv-cache-XXXXXX.dat");
+ cache->fd = mp_mkostemps(cache->filename, 4, O_CLOEXEC);
+ if (cache->fd < 0) {
+ MP_ERR(cache, "Failed to create cache temporary file.\n");
+ goto fail;
+ }
+ cache->need_unlink = true;
+ if (cache->opts->unlink_files >= 2) {
+ if (unlink(cache->filename)) {
+ MP_ERR(cache, "Failed to unlink cache temporary file after creation.\n");
+ } else {
+ cache->need_unlink = false;
+ }
+ }
+
+ return cache;
+fail:
+ talloc_free(cache);
+ return NULL;
+}
+
+uint64_t demux_cache_get_size(struct demux_cache *cache)
+{
+ return cache->file_size;
+}
+
+static bool do_seek(struct demux_cache *cache, uint64_t pos)
+{
+ if (cache->file_pos == pos)
+ return true;
+
+ off_t res = lseek(cache->fd, pos, SEEK_SET);
+
+ if (res == (off_t)-1) {
+ MP_ERR(cache, "Failed to seek in cache file.\n");
+ cache->file_pos = -1;
+ } else {
+ cache->file_pos = res;
+ }
+
+ return cache->file_pos >= 0;
+}
+
+static bool write_raw(struct demux_cache *cache, void *ptr, size_t len)
+{
+ ssize_t res = write(cache->fd, ptr, len);
+
+ if (res < 0) {
+ MP_ERR(cache, "Failed to write to cache file: %s\n", mp_strerror(errno));
+ return false;
+ }
+
+ cache->file_pos += res;
+ cache->file_size = MPMAX(cache->file_size, cache->file_pos);
+
+ // Should never happen, unless the disk is full, or someone succeeded to
+ // trick us to write into a pipe or a socket.
+ if (res != len) {
+ MP_ERR(cache, "Could not write all data.\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool read_raw(struct demux_cache *cache, void *ptr, size_t len)
+{
+ ssize_t res = read(cache->fd, ptr, len);
+
+ if (res < 0) {
+ MP_ERR(cache, "Failed to read cache file: %s\n", mp_strerror(errno));
+ return false;
+ }
+
+ cache->file_pos += res;
+
+ // Should never happen, unless the file was cut short, or someone succeeded
+ // to rick us to write into a pipe or a socket.
+ if (res != len) {
+ MP_ERR(cache, "Could not read all data.\n");
+ return false;
+ }
+
+ return true;
+}
+
+// Serialize a packet to the cache file. Returns the packet position, which can
+// be passed to demux_cache_read() to read the packet again.
+// Returns a negative value on errors, i.e. writing the file failed.
+int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *dp)
+{
+ assert(dp->avpacket);
+
+ // AV_PKT_FLAG_TRUSTED usually means there are embedded pointers and such
+ // in the packet data. The pointer will become invalid if the packet is
+ // unreferenced.
+ if (dp->avpacket->flags & AV_PKT_FLAG_TRUSTED) {
+ MP_ERR(cache, "Cannot serialize this packet to cache file.\n");
+ return -1;
+ }
+
+ assert(!dp->is_cached);
+ assert(dp->len >= 0 && dp->len <= INT32_MAX);
+ assert(dp->avpacket->flags >= 0 && dp->avpacket->flags <= INT32_MAX);
+ assert(dp->avpacket->side_data_elems >= 0 &&
+ dp->avpacket->side_data_elems <= INT32_MAX);
+
+ if (!do_seek(cache, cache->file_size))
+ return -1;
+
+ uint64_t pos = cache->file_pos;
+
+ struct pkt_header hd = {
+ .data_len = dp->len,
+ .av_flags = dp->avpacket->flags,
+ .num_sd = dp->avpacket->side_data_elems,
+ };
+
+ if (!write_raw(cache, &hd, sizeof(hd)))
+ goto fail;
+
+ if (!write_raw(cache, dp->buffer, dp->len))
+ goto fail;
+
+ // The handling of FFmpeg side data requires an extra long comment to
+ // explain why this code is fragile and insane.
+ // FFmpeg packet side data is per-packet out of band data, that contains
+ // further information for the decoder (extra metadata and such), which is
+ // not part of the codec itself and thus isn't contained in the packet
+ // payload. All types use a flat byte array. The format of this byte array
+ // is non-standard and FFmpeg-specific, and depends on the side data type
+ // field. The side data type is of course a FFmpeg ABI artifact.
+ // In some cases, the format is described as fixed byte layout. In others,
+ // it contains a struct, i.e. is bound to FFmpeg ABI. Some newer types make
+ // the format explicitly internal (and _not_ part of the ABI), and you need
+ // to use separate accessors to turn it into complex data structures.
+ // As of now, FFmpeg fortunately adheres to the idea that side data can not
+ // contain embedded pointers (due to API rules, but also because they forgot
+ // adding a refcount field, and can't change this until they break ABI).
+ // We rely on this. We hope that FFmpeg won't silently change their
+ // semantics, and add refcounting and embedded pointers. This way we can
+ // for example dump the data in a disk cache, even though we can't use the
+ // data from another process or if this process is restarted (unless we're
+ // absolutely sure the FFmpeg internals didn't change). The data has to be
+ // treated as a memory dump.
+ for (int n = 0; n < dp->avpacket->side_data_elems; n++) {
+ AVPacketSideData *sd = &dp->avpacket->side_data[n];
+
+ assert(sd->size >= 0 && sd->size <= INT32_MAX);
+ assert(sd->type >= 0 && sd->type <= INT32_MAX);
+
+ struct sd_header sd_hd = {
+ .av_type = sd->type,
+ .len = sd->size,
+ };
+
+ if (!write_raw(cache, &sd_hd, sizeof(sd_hd)))
+ goto fail;
+ if (!write_raw(cache, sd->data, sd->size))
+ goto fail;
+ }
+
+ return pos;
+
+fail:
+ // Reset file_size (try not to append crap forever).
+ do_seek(cache, pos);
+ cache->file_size = cache->file_pos;
+ return -1;
+}
+
+struct demux_packet *demux_cache_read(struct demux_cache *cache, uint64_t pos)
+{
+ if (!do_seek(cache, pos))
+ return NULL;
+
+ struct pkt_header hd;
+
+ if (!read_raw(cache, &hd, sizeof(hd)))
+ return NULL;
+
+ if (hd.data_len >= (size_t)-1)
+ return NULL;
+
+ struct demux_packet *dp = new_demux_packet(hd.data_len);
+ if (!dp)
+ goto fail;
+
+ if (!read_raw(cache, dp->buffer, dp->len))
+ goto fail;
+
+ dp->avpacket->flags = hd.av_flags;
+
+ for (uint32_t n = 0; n < hd.num_sd; n++) {
+ struct sd_header sd_hd;
+
+ if (!read_raw(cache, &sd_hd, sizeof(sd_hd)))
+ goto fail;
+
+ if (sd_hd.len > INT_MAX)
+ goto fail;
+
+ uint8_t *sd = av_packet_new_side_data(dp->avpacket, sd_hd.av_type,
+ sd_hd.len);
+ if (!sd)
+ goto fail;
+
+ if (!read_raw(cache, sd, sd_hd.len))
+ goto fail;
+ }
+
+ return dp;
+
+fail:
+ talloc_free(dp);
+ return NULL;
+}
diff --git a/demux/cache.h b/demux/cache.h
new file mode 100644
index 0000000..95ea964
--- /dev/null
+++ b/demux/cache.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+struct demux_packet;
+struct mp_log;
+struct mpv_global;
+
+struct demux_cache;
+
+struct demux_cache *demux_cache_create(struct mpv_global *global,
+ struct mp_log *log);
+
+int64_t demux_cache_write(struct demux_cache *cache, struct demux_packet *pkt);
+struct demux_packet *demux_cache_read(struct demux_cache *cache, uint64_t pos);
+uint64_t demux_cache_get_size(struct demux_cache *cache);
diff --git a/demux/codec_tags.c b/demux/codec_tags.c
new file mode 100644
index 0000000..55700d9
--- /dev/null
+++ b/demux/codec_tags.c
@@ -0,0 +1,280 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/common.h>
+#include <libavutil/intreadwrite.h>
+
+#include "codec_tags.h"
+#include "stheader.h"
+#include "common/av_common.h"
+
+static const char *lookup_tag(int type, uint32_t tag)
+{
+ const struct AVCodecTag *av_tags[3] = {0};
+ switch (type) {
+ case STREAM_VIDEO: {
+ av_tags[0] = avformat_get_riff_video_tags();
+ av_tags[1] = avformat_get_mov_video_tags();
+ break;
+ }
+ case STREAM_AUDIO: {
+ av_tags[0] = avformat_get_riff_audio_tags();
+ av_tags[1] = avformat_get_mov_audio_tags();
+ break;
+ }
+ }
+
+ int id = av_codec_get_id(av_tags, tag);
+ return id == AV_CODEC_ID_NONE ? NULL : mp_codec_from_av_codec_id(id);
+}
+
+
+/*
+ * As seen in the following page:
+ *
+ * https://web.archive.org/web/20220406060153/
+ * http://dream.cs.bath.ac.uk/researchdev/wave-ex/bformat.html
+ *
+ * Note that the GUID struct in the above citation has its
+ * integers encoded in little-endian format, which means that
+ * the unsigned short and unsigned long entries need to be
+ * byte-flipped for this encoding.
+ *
+ * In theory only the first element of this array should be used,
+ * however some encoders incorrectly encoded the GUID byte-for-byte
+ * and thus the second one exists as a fallback.
+ */
+static const unsigned char guid_ext_base[][16] = {
+ // MEDIASUBTYPE_BASE_GUID
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71},
+ // SUBTYPE_AMBISONIC_B_FORMAT_PCM
+ {0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xD3, 0x11,
+ 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00}
+};
+
+struct mp_waveformatex_guid {
+ const char *codec;
+ const unsigned char guid[16];
+};
+
+static const struct mp_waveformatex_guid guid_ext_other[] = {
+ {"ac3",
+ {0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11,
+ 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA}},
+ {"adpcm_agm",
+ {0x82, 0xEC, 0x1F, 0x6A, 0xCA, 0xDB, 0x19, 0x45,
+ 0xBD, 0xE7, 0x56, 0xD3, 0xB3, 0xEF, 0x98, 0x1D}},
+ {"atrac3p",
+ {0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44,
+ 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62}},
+ {"atrac9",
+ {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
+ 0x88, 0xFC, 0x61, 0x65, 0x4F, 0x8C, 0x83, 0x6C}},
+ {"dfpwm",
+ {0x3A, 0xC1, 0xFA, 0x38, 0x81, 0x1D, 0x43, 0x61,
+ 0xA4, 0x0D, 0xCE, 0x53, 0xCA, 0x60, 0x7C, 0xD1}},
+ {"eac3",
+ {0xAF, 0x87, 0xFB, 0xA7, 0x02, 0x2D, 0xFB, 0x42,
+ 0xA4, 0xD4, 0x05, 0xCD, 0x93, 0x84, 0x3B, 0xDD}},
+ {"mp2",
+ {0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11,
+ 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA}}
+};
+
+static void map_audio_pcm_tag(struct mp_codec_params *c)
+{
+ // MS PCM, Extended
+ if (c->codec_tag == 0xfffe && c->extradata_size >= 22) {
+ // WAVEFORMATEXTENSIBLE.wBitsPerSample
+ int bits_per_sample = AV_RL16(c->extradata);
+ if (bits_per_sample)
+ c->bits_per_coded_sample = bits_per_sample;
+
+ // WAVEFORMATEXTENSIBLE.dwChannelMask
+ uint64_t chmask = AV_RL32(c->extradata + 2);
+ struct mp_chmap chmap;
+ mp_chmap_from_waveext(&chmap, chmask);
+ if (c->channels.num == chmap.num)
+ c->channels = chmap;
+
+ // WAVEFORMATEXTENSIBLE.SubFormat
+ unsigned char *subformat = c->extradata + 6;
+ for (int i = 0; i < MP_ARRAY_SIZE(guid_ext_base); i++) {
+ if (memcmp(subformat + 4, guid_ext_base[i] + 4, 12) == 0) {
+ c->codec_tag = AV_RL32(subformat);
+ c->codec = lookup_tag(c->type, c->codec_tag);
+ break;
+ }
+ }
+
+ // extra subformat, not a base one
+ if (c->codec_tag == 0xfffe) {
+ for (int i = 0; i < MP_ARRAY_SIZE(guid_ext_other); i++) {
+ if (memcmp(subformat, &guid_ext_other[i].guid, 16) == 0) {
+ c->codec = guid_ext_other[i].codec;
+ c->codec_tag = mp_codec_to_av_codec_id(c->codec);
+ break;
+ }
+ }
+ }
+
+ // Compressed formats might use this.
+ c->extradata += 22;
+ c->extradata_size -= 22;
+ }
+
+ int bits = c->bits_per_coded_sample;
+ if (!bits)
+ return;
+
+ int bytes = (bits + 7) / 8;
+ switch (c->codec_tag) {
+ case 0x0: // Microsoft PCM
+ case 0x1:
+ if (bytes >= 1 && bytes <= 4)
+ mp_set_pcm_codec(c, bytes > 1, false, bytes * 8, false);
+ break;
+ case 0x3: // IEEE float
+ mp_set_pcm_codec(c, true, true, bits == 64 ? 64 : 32, false);
+ break;
+ }
+}
+
+void mp_set_codec_from_tag(struct mp_codec_params *c)
+{
+ c->codec = lookup_tag(c->type, c->codec_tag);
+ if (c->type == STREAM_AUDIO)
+ map_audio_pcm_tag(c);
+}
+
+void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float,
+ int bits, bool is_be)
+{
+ // This uses libavcodec pcm codec names, e.g. "pcm_u16le".
+ char codec[64] = "pcm_";
+ if (is_float) {
+ mp_snprintf_cat(codec, sizeof(codec), "f");
+ } else {
+ mp_snprintf_cat(codec, sizeof(codec), sign ? "s" : "u");
+ }
+ mp_snprintf_cat(codec, sizeof(codec), "%d", bits);
+ if (bits != 8)
+ mp_snprintf_cat(codec, sizeof(codec), is_be ? "be" : "le");
+ c->codec = talloc_strdup(c, codec);
+}
+
+// map file extension/type to an image codec name
+static const char *const type_to_codec[][2] = {
+ { "bmp", "bmp" },
+ { "dpx", "dpx" },
+ { "j2c", "jpeg2000" },
+ { "j2k", "jpeg2000" },
+ { "jp2", "jpeg2000" },
+ { "jpc", "jpeg2000" },
+ { "jpeg", "mjpeg" },
+ { "jpg", "mjpeg" },
+ { "jps", "mjpeg" },
+ { "jls", "ljpeg" },
+ { "thm", "mjpeg" },
+ { "db", "mjpeg" },
+ { "pcd", "photocd" },
+ { "pfm", "pfm" },
+ { "phm", "phm" },
+ { "hdr", "hdr" },
+ { "pcx", "pcx" },
+ { "png", "png" },
+ { "pns", "png" },
+ { "ptx", "ptx" },
+ { "tga", "targa" },
+ { "tif", "tiff" },
+ { "tiff", "tiff" },
+ { "sgi", "sgi" },
+ { "sun", "sunrast" },
+ { "ras", "sunrast" },
+ { "rs", "sunrast" },
+ { "ra", "sunrast" },
+ { "im1", "sunrast" },
+ { "im8", "sunrast" },
+ { "im24", "sunrast" },
+ { "im32", "sunrast" },
+ { "sunras", "sunrast" },
+ { "xbm", "xbm" },
+ { "pam", "pam" },
+ { "pbm", "pbm" },
+ { "pgm", "pgm" },
+ { "pgmyuv", "pgmyuv" },
+ { "ppm", "ppm" },
+ { "pnm", "ppm" },
+ { "gif", "gif" },
+ { "pix", "brender_pix" },
+ { "exr", "exr" },
+ { "pic", "pictor" },
+ { "qoi", "qoi" },
+ { "xface", "xface" },
+ { "xwd", "xwd" },
+ { "svg", "svg" },
+ {0}
+};
+
+bool mp_codec_is_image(const char *codec)
+{
+ if (codec) {
+ for (int n = 0; type_to_codec[n][0]; n++) {
+ if (strcasecmp(type_to_codec[n][1], codec) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+
+const char *mp_map_type_to_image_codec(const char *type)
+{
+ if (type) {
+ for (int n = 0; type_to_codec[n][0]; n++) {
+ if (strcasecmp(type_to_codec[n][0], type) == 0)
+ return type_to_codec[n][1];
+ }
+ }
+ return NULL;
+};
+
+static const char *const mimetype_to_codec[][2] = {
+ {"image/apng", "apng"},
+ {"image/avif", "av1"},
+ {"image/bmp", "bmp"},
+ {"image/gif", "gif"},
+ {"image/jpeg", "mjpeg"},
+ {"image/jxl", "jpegxl"},
+ {"image/png", "png"},
+ {"image/tiff", "tiff"},
+ {"image/webp", "webp"},
+ {0}
+};
+
+const char *mp_map_mimetype_to_video_codec(const char *mimetype)
+{
+ if (mimetype) {
+ for (int n = 0; mimetype_to_codec[n][0]; n++) {
+ if (strcasecmp(mimetype_to_codec[n][0], mimetype) == 0)
+ return mimetype_to_codec[n][1];
+ }
+ }
+ return NULL;
+}
diff --git a/demux/codec_tags.h b/demux/codec_tags.h
new file mode 100644
index 0000000..8fc49df
--- /dev/null
+++ b/demux/codec_tags.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_CODEC_TAGS_H
+#define MP_CODEC_TAGS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+struct mp_codec_params;
+
+void mp_set_codec_from_tag(struct mp_codec_params *c);
+
+void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float,
+ int bits, bool is_be);
+
+bool mp_codec_is_image(const char *codec);
+const char *mp_map_type_to_image_codec(const char *type);
+const char *mp_map_mimetype_to_video_codec(const char *mimetype);
+
+#endif
diff --git a/demux/cue.c b/demux/cue.c
new file mode 100644
index 0000000..104c598
--- /dev/null
+++ b/demux/cue.c
@@ -0,0 +1,270 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "mpv_talloc.h"
+
+#include "misc/bstr.h"
+#include "common/common.h"
+#include "common/tags.h"
+
+#include "cue.h"
+
+#define SECS_PER_CUE_FRAME (1.0/75.0)
+
+enum cue_command {
+ CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
+ CUE_EMPTY, // line with whitespace only
+ CUE_UNUSED, // valid CUE command, but ignored by this code
+ CUE_FILE,
+ CUE_TRACK,
+ CUE_INDEX,
+ CUE_TITLE,
+ CUE_PERFORMER,
+};
+
+static const struct {
+ enum cue_command command;
+ const char *text;
+} cue_command_strings[] = {
+ { CUE_FILE, "FILE" },
+ { CUE_TRACK, "TRACK" },
+ { CUE_INDEX, "INDEX" },
+ { CUE_TITLE, "TITLE" },
+ { CUE_UNUSED, "CATALOG" },
+ { CUE_UNUSED, "CDTEXTFILE" },
+ { CUE_UNUSED, "FLAGS" },
+ { CUE_UNUSED, "ISRC" },
+ { CUE_PERFORMER, "PERFORMER" },
+ { CUE_UNUSED, "POSTGAP" },
+ { CUE_UNUSED, "PREGAP" },
+ { CUE_UNUSED, "REM" },
+ { CUE_UNUSED, "SONGWRITER" },
+ { CUE_UNUSED, "MESSAGE" },
+ { -1 },
+};
+
+static const uint8_t spaces[] = {' ', '\f', '\n', '\r', '\t', '\v', 0xA0};
+
+static struct bstr lstrip_whitespace(struct bstr data)
+{
+ while (data.len) {
+ bstr rest = data;
+ int code = bstr_decode_utf8(data, &rest);
+ if (code < 0) {
+ // Tolerate Latin1 => probing works (which doesn't convert charsets).
+ code = data.start[0];
+ rest.start += 1;
+ rest.len -= 1;
+ }
+ for (size_t n = 0; n < MP_ARRAY_SIZE(spaces); n++) {
+ if (spaces[n] == code) {
+ data = rest;
+ goto next;
+ }
+ }
+ break;
+ next: ;
+ }
+ return data;
+}
+
+static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
+{
+ struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
+ line = lstrip_whitespace(line);
+ if (line.len == 0)
+ return CUE_EMPTY;
+ for (int n = 0; cue_command_strings[n].command != -1; n++) {
+ struct bstr name = bstr0(cue_command_strings[n].text);
+ if (bstr_case_startswith(line, name)) {
+ struct bstr rest = bstr_cut(line, name.len);
+ struct bstr par = lstrip_whitespace(rest);
+ if (rest.len && par.len == rest.len)
+ continue;
+ if (out_params)
+ *out_params = par;
+ return cue_command_strings[n].command;
+ }
+ }
+ return CUE_ERROR;
+}
+
+static bool eat_char(struct bstr *data, char ch)
+{
+ if (data->len && data->start[0] == ch) {
+ *data = bstr_cut(*data, 1);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static char *read_quoted(void *talloc_ctx, struct bstr *data)
+{
+ *data = lstrip_whitespace(*data);
+ if (!eat_char(data, '"'))
+ return NULL;
+ int end = bstrchr(*data, '"');
+ if (end < 0)
+ return NULL;
+ struct bstr res = bstr_splice(*data, 0, end);
+ *data = bstr_cut(*data, end + 1);
+ return bstrto0(talloc_ctx, res);
+}
+
+static struct bstr strip_quotes(struct bstr data)
+{
+ bstr s = data;
+ if (bstr_eatstart0(&s, "\"") && bstr_eatend0(&s, "\""))
+ return s;
+ return data;
+}
+
+// Read an unsigned decimal integer.
+// Optionally check if it is 2 digit.
+// Return -1 on failure.
+static int read_int(struct bstr *data, bool two_digit)
+{
+ *data = lstrip_whitespace(*data);
+ if (data->len && data->start[0] == '-')
+ return -1;
+ struct bstr s = *data;
+ int res = (int)bstrtoll(s, &s, 10);
+ if (data->len == s.len || (two_digit && data->len - s.len > 2))
+ return -1;
+ *data = s;
+ return res;
+}
+
+static double read_time(struct bstr *data)
+{
+ struct bstr s = *data;
+ bool ok = true;
+ double t1 = read_int(&s, false);
+ ok = eat_char(&s, ':') && ok;
+ double t2 = read_int(&s, true);
+ ok = eat_char(&s, ':') && ok;
+ double t3 = read_int(&s, true);
+ ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
+ return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
+}
+
+static struct bstr skip_utf8_bom(struct bstr data)
+{
+ return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
+}
+
+// Check if the text in data is most likely CUE data. This is used by the
+// demuxer code to check the file type.
+// data is the start of the probed file, possibly cut off at a random point.
+bool mp_probe_cue(struct bstr data)
+{
+ bool valid = false;
+ data = skip_utf8_bom(data);
+ for (;;) {
+ enum cue_command cmd = read_cmd(&data, NULL);
+ // End reached. Since the line was most likely cut off, don't use the
+ // result of the last parsing call.
+ if (data.len == 0)
+ break;
+ if (cmd == CUE_ERROR)
+ return false;
+ if (cmd != CUE_EMPTY)
+ valid = true;
+ }
+ return valid;
+}
+
+struct cue_file *mp_parse_cue(struct bstr data)
+{
+ struct cue_file *f = talloc_zero(NULL, struct cue_file);
+ f->tags = talloc_zero(f, struct mp_tags);
+
+ data = skip_utf8_bom(data);
+
+ char *filename = NULL;
+ // Global metadata, and copied into new tracks.
+ struct cue_track proto_track = {0};
+ struct cue_track *cur_track = NULL;
+
+ while (data.len) {
+ struct bstr param;
+ int cmd = read_cmd(&data, &param);
+ switch (cmd) {
+ case CUE_ERROR:
+ talloc_free(f);
+ return NULL;
+ case CUE_TRACK: {
+ MP_TARRAY_GROW(f, f->tracks, f->num_tracks);
+ f->num_tracks += 1;
+ cur_track = &f->tracks[f->num_tracks - 1];
+ *cur_track = proto_track;
+ cur_track->tags = talloc_zero(f, struct mp_tags);
+ break;
+ }
+ case CUE_TITLE:
+ case CUE_PERFORMER: {
+ static const char *metanames[] = {
+ [CUE_TITLE] = "title",
+ [CUE_PERFORMER] = "performer",
+ };
+ struct mp_tags *tags = cur_track ? cur_track->tags : f->tags;
+ mp_tags_set_bstr(tags, bstr0(metanames[cmd]), strip_quotes(param));
+ break;
+ }
+ case CUE_INDEX: {
+ int type = read_int(&param, true);
+ double time = read_time(&param);
+ if (cur_track) {
+ if (type == 1) {
+ cur_track->start = time;
+ cur_track->filename = filename;
+ } else if (type == 0) {
+ cur_track->pregap_start = time;
+ }
+ }
+ break;
+ }
+ case CUE_FILE:
+ // NOTE: FILE comes before TRACK, so don't use cur_track->filename
+ filename = read_quoted(f, &param);
+ break;
+ }
+ }
+
+ return f;
+}
+
+int mp_check_embedded_cue(struct cue_file *f)
+{
+ char *fn0 = f->tracks[0].filename;
+ for (int n = 1; n < f->num_tracks; n++) {
+ char *fn = f->tracks[n].filename;
+ // both filenames have the same address (including NULL)
+ if (fn0 == fn)
+ continue;
+ // only one filename is NULL, or the strings don't match
+ if (!fn0 || !fn || strcmp(fn0, fn) != 0)
+ return -1;
+ }
+ return 0;
+}
diff --git a/demux/cue.h b/demux/cue.h
new file mode 100644
index 0000000..61f18e6
--- /dev/null
+++ b/demux/cue.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_CUE_H_
+#define MP_CUE_H_
+
+#include <stdbool.h>
+
+#include "misc/bstr.h"
+
+struct cue_file {
+ struct cue_track *tracks;
+ int num_tracks;
+ struct mp_tags *tags;
+};
+
+struct cue_track {
+ double pregap_start; // corresponds to INDEX 00
+ double start; // corresponds to INDEX 01
+ char *filename;
+ int source;
+ struct mp_tags *tags;
+};
+
+bool mp_probe_cue(struct bstr data);
+struct cue_file *mp_parse_cue(struct bstr data);
+int mp_check_embedded_cue(struct cue_file *f);
+
+#endif
diff --git a/demux/demux.c b/demux/demux.c
new file mode 100644
index 0000000..256e1b6
--- /dev/null
+++ b/demux/demux.c
@@ -0,0 +1,4624 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "cache.h"
+#include "config.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "mpv_talloc.h"
+#include "common/av_common.h"
+#include "common/msg.h"
+#include "common/global.h"
+#include "common/recorder.h"
+#include "common/stats.h"
+#include "misc/charset_conv.h"
+#include "misc/thread_tools.h"
+#include "osdep/timer.h"
+#include "osdep/threads.h"
+
+#include "stream/stream.h"
+#include "demux.h"
+#include "timeline.h"
+#include "stheader.h"
+#include "cue.h"
+
+// Demuxer list
+extern const struct demuxer_desc demuxer_desc_edl;
+extern const struct demuxer_desc demuxer_desc_cue;
+extern const demuxer_desc_t demuxer_desc_rawaudio;
+extern const demuxer_desc_t demuxer_desc_rawvideo;
+extern const demuxer_desc_t demuxer_desc_mf;
+extern const demuxer_desc_t demuxer_desc_matroska;
+extern const demuxer_desc_t demuxer_desc_lavf;
+extern const demuxer_desc_t demuxer_desc_playlist;
+extern const demuxer_desc_t demuxer_desc_disc;
+extern const demuxer_desc_t demuxer_desc_rar;
+extern const demuxer_desc_t demuxer_desc_libarchive;
+extern const demuxer_desc_t demuxer_desc_null;
+extern const demuxer_desc_t demuxer_desc_timeline;
+
+static const demuxer_desc_t *const demuxer_list[] = {
+ &demuxer_desc_disc,
+ &demuxer_desc_edl,
+ &demuxer_desc_cue,
+ &demuxer_desc_rawaudio,
+ &demuxer_desc_rawvideo,
+ &demuxer_desc_matroska,
+#if HAVE_LIBARCHIVE
+ &demuxer_desc_libarchive,
+#endif
+ &demuxer_desc_lavf,
+ &demuxer_desc_mf,
+ &demuxer_desc_playlist,
+ &demuxer_desc_null,
+ NULL
+};
+
+#define OPT_BASE_STRUCT struct demux_opts
+
+static bool get_demux_sub_opts(int index, const struct m_sub_options **sub);
+
+const struct m_sub_options demux_conf = {
+ .opts = (const struct m_option[]){
+ {"cache", OPT_CHOICE(enable_cache,
+ {"no", 0}, {"auto", -1}, {"yes", 1})},
+ {"cache-on-disk", OPT_BOOL(disk_cache)},
+ {"demuxer-readahead-secs", OPT_DOUBLE(min_secs), M_RANGE(0, DBL_MAX)},
+ {"demuxer-hysteresis-secs", OPT_DOUBLE(hyst_secs), M_RANGE(0, DBL_MAX)},
+ {"demuxer-max-bytes", OPT_BYTE_SIZE(max_bytes),
+ M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"demuxer-max-back-bytes", OPT_BYTE_SIZE(max_bytes_bw),
+ M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"demuxer-donate-buffer", OPT_BOOL(donate_fw)},
+ {"force-seekable", OPT_BOOL(force_seekable)},
+ {"cache-secs", OPT_DOUBLE(min_secs_cache), M_RANGE(0, DBL_MAX)},
+ {"access-references", OPT_BOOL(access_references)},
+ {"demuxer-seekable-cache", OPT_CHOICE(seekable_cache,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"index", OPT_CHOICE(index_mode, {"default", 1}, {"recreate", 0})},
+ {"mf-fps", OPT_DOUBLE(mf_fps)},
+ {"mf-type", OPT_STRING(mf_type)},
+ {"sub-create-cc-track", OPT_BOOL(create_ccs)},
+ {"stream-record", OPT_STRING(record_file)},
+ {"video-backward-overlap", OPT_CHOICE(video_back_preroll, {"auto", -1}),
+ M_RANGE(0, 1024)},
+ {"audio-backward-overlap", OPT_CHOICE(audio_back_preroll, {"auto", -1}),
+ M_RANGE(0, 1024)},
+ {"video-backward-batch", OPT_INT(back_batch[STREAM_VIDEO]),
+ M_RANGE(0, 1024)},
+ {"audio-backward-batch", OPT_INT(back_batch[STREAM_AUDIO]),
+ M_RANGE(0, 1024)},
+ {"demuxer-backward-playback-step", OPT_DOUBLE(back_seek_size),
+ M_RANGE(0, DBL_MAX)},
+ {"metadata-codepage", OPT_STRING(meta_cp)},
+ {0}
+ },
+ .size = sizeof(struct demux_opts),
+ .defaults = &(const struct demux_opts){
+ .enable_cache = -1, // auto
+ .max_bytes = 150 * 1024 * 1024,
+ .max_bytes_bw = 50 * 1024 * 1024,
+ .donate_fw = true,
+ .min_secs = 1.0,
+ .min_secs_cache = 1000.0 * 60 * 60,
+ .seekable_cache = -1,
+ .index_mode = 1,
+ .mf_fps = 1.0,
+ .access_references = true,
+ .video_back_preroll = -1,
+ .audio_back_preroll = -1,
+ .back_seek_size = 60,
+ .back_batch = {
+ [STREAM_VIDEO] = 1,
+ [STREAM_AUDIO] = 10,
+ },
+ .meta_cp = "auto",
+ },
+ .get_sub_options = get_demux_sub_opts,
+};
+
+struct demux_internal {
+ struct mp_log *log;
+ struct mpv_global *global;
+ struct stats_ctx *stats;
+
+ bool can_cache; // not a slave demuxer; caching makes sense
+ bool can_record; // stream recording is allowed
+
+ // The demuxer runs potentially in another thread, so we keep two demuxer
+ // structs; the real demuxer can access the shadow struct only.
+ struct demuxer *d_thread; // accessed by demuxer impl. (producer)
+ struct demuxer *d_user; // accessed by player (consumer)
+
+ // The lock protects the packet queues (struct demux_stream),
+ // and the fields below.
+ mp_mutex lock;
+ mp_cond wakeup;
+ mp_thread thread;
+
+ // -- All the following fields are protected by lock.
+
+ bool thread_terminate;
+ bool threading;
+ bool shutdown_async;
+ void (*wakeup_cb)(void *ctx);
+ void *wakeup_cb_ctx;
+
+ struct sh_stream **streams;
+ int num_streams;
+
+ char *meta_charset;
+
+ // If non-NULL, a stream which is used for global (timed) metadata. It will
+ // be an arbitrary stream, which hopefully will happen to work.
+ struct sh_stream *metadata_stream;
+
+ int events;
+
+ struct demux_cache *cache;
+
+ bool warned_queue_overflow;
+ bool eof; // whether we're in EOF state
+ double min_secs;
+ double hyst_secs; // stop reading till there's hyst_secs remaining
+ bool hyst_active;
+ size_t max_bytes;
+ size_t max_bytes_bw;
+ bool seekable_cache;
+ bool using_network_cache_opts;
+ char *record_filename;
+
+ // Whether the demuxer thread should prefetch packets. This is set to false
+ // if EOF was reached or the demuxer cache is full. This is also important
+ // in the initial state: the decoder thread needs to select streams before
+ // the first packet is read, so this is set to true by packet reading only.
+ // Reset to false again on EOF or if prefetching is done.
+ bool reading;
+
+ // Set if we just performed a seek, without reading packets yet. 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 after_seek;
+ // Set in addition to after_seek if we think we seeked to the start of the
+ // file (or if the demuxer was just opened).
+ bool after_seek_to_start;
+
+ // Demuxing backwards. Since demuxer implementations don't support this
+ // directly, it is emulated by seeking backwards for every packet run. Also,
+ // packets between keyframes are demuxed forwards (you can't decode that
+ // stuff otherwise), which adds complexity on top of it.
+ bool back_demuxing;
+
+ // For backward demuxing:
+ bool need_back_seek; // back-step seek needs to be triggered
+ bool back_any_need_recheck; // at least 1 ds->back_need_recheck set
+
+ 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;
+
+ // (fields for debugging)
+ double seeking_in_progress; // low level seek state
+ int low_level_seeks; // number of started low level seeks
+ double demux_ts; // last demuxed DTS or PTS
+
+ double ts_offset; // timestamp offset to apply to everything
+
+ // (sorted by least recent use: index 0 is least recently used)
+ struct demux_cached_range **ranges;
+ int num_ranges;
+
+ size_t total_bytes; // total sum of packet data buffered
+ // Range from which decoder is reading, and to which demuxer is appending.
+ // This is normally never NULL. This is always ranges[num_ranges - 1].
+ // This is can be NULL during initialization or deinitialization.
+ struct demux_cached_range *current_range;
+
+ double highest_av_pts; // highest non-subtitle PTS seen - for duration
+
+ bool blocked;
+
+ // Transient state.
+ double duration;
+ // Cached state.
+ int64_t stream_size;
+ int64_t last_speed_query;
+ double speed_query_prev_sample;
+ uint64_t bytes_per_second;
+ int64_t next_cache_update;
+
+ // demux user state (user thread, somewhat similar to reader/decoder state)
+ double last_playback_pts; // last playback_pts from demux_update()
+ bool force_metadata_update;
+ int cached_metadata_index; // speed up repeated lookups
+
+ struct mp_recorder *dumper;
+ int dumper_status;
+
+ bool owns_stream;
+
+ // -- Access from demuxer thread only
+ bool enable_recording;
+ struct mp_recorder *recorder;
+ int64_t slave_unbuffered_read_bytes; // value repoted from demuxer impl.
+ int64_t hack_unbuffered_read_bytes; // for demux_get_bytes_read_hack()
+ int64_t cache_unbuffered_read_bytes; // for demux_reader_state.bytes_per_second
+ int64_t byte_level_seeks; // for demux_reader_state.byte_level_seeks
+};
+
+struct timed_metadata {
+ double pts;
+ struct mp_tags *tags;
+ bool from_stream;
+};
+
+// A continuous range of cached packets for all enabled streams.
+// (One demux_queue for each known stream.)
+struct demux_cached_range {
+ // streams[] is indexed by demux_stream->index
+ struct demux_queue **streams;
+ int num_streams;
+
+ // Computed from the stream queue's values. These fields (unlike as with
+ // demux_queue) are always either NOPTS, or fully valid.
+ double seek_start, seek_end;
+
+ bool is_bof; // set if the file begins with this range
+ bool is_eof; // set if the file ends with this range
+
+ struct timed_metadata **metadata;
+ int num_metadata;
+};
+
+#define QUEUE_INDEX_SIZE_MASK(queue) ((queue)->index_size - 1)
+
+// Access the idx-th entry in the given demux_queue.
+// Requirement: idx >= 0 && idx < queue->num_index
+#define QUEUE_INDEX_ENTRY(queue, idx) \
+ ((queue)->index[((queue)->index0 + (idx)) & QUEUE_INDEX_SIZE_MASK(queue)])
+
+// Don't index packets whose timestamps that are within the last index entry by
+// this amount of time (it's better to seek them manually).
+#define INDEX_STEP_SIZE 1.0
+
+struct index_entry {
+ double pts;
+ struct demux_packet *pkt;
+};
+
+// A continuous list of cached packets for a single stream/range. There is one
+// for each stream and range. Also contains some state for use during demuxing
+// (keeping it across seeks makes it easier to resume demuxing).
+struct demux_queue {
+ struct demux_stream *ds;
+ struct demux_cached_range *range;
+
+ struct demux_packet *head;
+ struct demux_packet *tail;
+
+ uint64_t tail_cum_pos; // cumulative size including tail packet
+
+ bool correct_dts; // packet DTS is strictly monotonically increasing
+ bool correct_pos; // packet pos is strictly monotonically increasing
+ int64_t last_pos; // for determining correct_pos
+ int64_t last_pos_fixup; // for filling in unset dp->pos values
+ double last_dts; // for determining correct_dts
+ double last_ts; // timestamp of the last packet added to queue
+
+ // for incrementally determining seek PTS range
+ struct demux_packet *keyframe_latest;
+ struct demux_packet *keyframe_first; // cached value of first KF packet
+
+ // incrementally maintained seek range, possibly invalid
+ double seek_start, seek_end;
+ double last_pruned; // timestamp of last pruned keyframe
+
+ bool is_bof; // started demuxing at beginning of file
+ bool is_eof; // received true EOF here
+
+ // Complete index, though it may skip some entries to reduce density.
+ struct index_entry *index; // ring buffer
+ size_t index_size; // size of index[] (0 or a power of 2)
+ size_t index0; // first index entry
+ size_t num_index; // number of index entries (wraps on index_size)
+};
+
+struct demux_stream {
+ struct demux_internal *in;
+ struct sh_stream *sh; // ds->sh->ds == ds
+ enum stream_type type; // equals to sh->type
+ int index; // equals to sh->index
+ // --- all fields are protected by in->lock
+
+ void (*wakeup_cb)(void *ctx);
+ void *wakeup_cb_ctx;
+
+ // demuxer state
+ bool selected; // user wants packets from this stream
+ bool eager; // try to keep at least 1 packet queued
+ // if false, this stream is disabled, or passively
+ // read (like subtitles)
+ bool still_image; // stream has still video images
+ bool refreshing; // finding old position after track switches
+ bool eof; // end of demuxed stream? (true if no more packets)
+
+ bool global_correct_dts;// all observed so far
+ bool global_correct_pos;
+
+ // current queue - used both for reading and demuxing (this is never NULL)
+ struct demux_queue *queue;
+
+ // reader (decoder) state (bitrate calculations are part of it because we
+ // want to return the bitrate closest to the "current position")
+ double base_ts; // timestamp of the last packet returned to decoder
+ double last_br_ts; // timestamp of last packet bitrate was calculated
+ size_t last_br_bytes; // summed packet sizes since last bitrate calculation
+ double bitrate;
+ struct demux_packet *reader_head; // points at current decoder position
+ bool skip_to_keyframe;
+ bool attached_picture_added;
+ bool need_wakeup; // call wakeup_cb on next reader_head state change
+ double force_read_until;// eager=false streams (subs): force read-ahead
+
+ // For demux_internal.dumper. Currently, this is used only temporarily
+ // during blocking dumping.
+ struct demux_packet *dump_pos;
+
+ // for refresh seeks: pos/dts of last packet returned to reader
+ int64_t last_ret_pos;
+ double last_ret_dts;
+
+ // Backwards demuxing.
+ bool back_need_recheck; // flag for incremental find_backward_restart_pos work
+ // pos/dts of the previous keyframe packet returned; always valid if back-
+ // demuxing is enabled, and back_restart_eof/back_restart_next are false.
+ int64_t back_restart_pos;
+ double back_restart_dts;
+ bool back_restart_eof; // restart position is at EOF; overrides pos/dts
+ bool back_restart_next; // restart before next keyframe; overrides above
+ bool back_restarting; // searching keyframe before restart pos
+ // Current PTS lower bound for back demuxing.
+ double back_seek_pos;
+ // pos/dts of the packet to resume demuxing from when another stream caused
+ // a seek backward to get more packets. reader_head will be reset to this
+ // packet as soon as it's encountered again.
+ int64_t back_resume_pos;
+ double back_resume_dts;
+ bool back_resuming; // resuming mode (above fields are valid/used)
+ // Set to true if the first packet (keyframe) of a range was returned.
+ bool back_range_started;
+ // Number of KF packets at start of range yet to return. -1 is used for BOF.
+ int back_range_count;
+ // Number of KF packets yet to return that are marked as preroll.
+ int back_range_preroll;
+ // Static packet preroll count.
+ int back_preroll;
+
+ // for closed captions (demuxer_feed_caption)
+ struct sh_stream *cc;
+ bool ignore_eof; // ignore stream in underrun detection
+};
+
+static void switch_to_fresh_cache_range(struct demux_internal *in);
+static void demuxer_sort_chapters(demuxer_t *demuxer);
+static MP_THREAD_VOID demux_thread(void *pctx);
+static void update_cache(struct demux_internal *in);
+static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp);
+static struct demux_packet *advance_reader_head(struct demux_stream *ds);
+static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
+ bool clear_back_state);
+static struct demux_packet *compute_keyframe_times(struct demux_packet *pkt,
+ double *out_kf_min,
+ double *out_kf_max);
+static void find_backward_restart_pos(struct demux_stream *ds);
+static struct demux_packet *find_seek_target(struct demux_queue *queue,
+ double pts, int flags);
+static void prune_old_packets(struct demux_internal *in);
+static void dumper_close(struct demux_internal *in);
+static void demux_convert_tags_charset(struct demuxer *demuxer);
+
+static uint64_t get_forward_buffered_bytes(struct demux_stream *ds)
+{
+ if (!ds->reader_head)
+ return 0;
+ return ds->queue->tail_cum_pos - ds->reader_head->cum_pos;
+}
+
+#if 0
+// very expensive check for redundant cached queue state
+static void check_queue_consistency(struct demux_internal *in)
+{
+ uint64_t total_bytes = 0;
+
+ assert(in->current_range && in->num_ranges > 0);
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+ int range_num_packets = 0;
+
+ assert(range->num_streams == in->num_streams);
+
+ for (int i = 0; i < range->num_streams; i++) {
+ struct demux_queue *queue = range->streams[i];
+
+ assert(queue->range == range);
+
+ size_t fw_bytes = 0;
+ bool is_forward = false;
+ bool kf_found = false;
+ bool kf1_found = false;
+ size_t next_index = 0;
+ uint64_t queue_total_bytes = 0;
+ for (struct demux_packet *dp = queue->head; dp; dp = dp->next) {
+ is_forward |= dp == queue->ds->reader_head;
+ kf_found |= dp == queue->keyframe_latest;
+ kf1_found |= dp == queue->keyframe_first;
+
+ size_t bytes = demux_packet_estimate_total_size(dp);
+ total_bytes += bytes;
+ queue_total_bytes += bytes;
+ if (is_forward) {
+ fw_bytes += bytes;
+ assert(range == in->current_range);
+ assert(queue->ds->queue == queue);
+ }
+ range_num_packets += 1;
+
+ if (!dp->next)
+ assert(queue->tail == dp);
+
+ if (next_index < queue->num_index &&
+ QUEUE_INDEX_ENTRY(queue, next_index).pkt == dp)
+ next_index += 1;
+ }
+ if (!queue->head)
+ assert(!queue->tail);
+ assert(next_index == queue->num_index);
+
+ uint64_t queue_total_bytes2 = 0;
+ if (queue->head)
+ queue_total_bytes2 = queue->tail_cum_pos - queue->head->cum_pos;
+
+ assert(queue_total_bytes == queue_total_bytes2);
+
+ // If the queue is currently used...
+ if (queue->ds->queue == queue) {
+ // ...reader_head and others must be in the queue.
+ assert(is_forward == !!queue->ds->reader_head);
+ assert(kf_found == !!queue->keyframe_latest);
+ uint64_t fw_bytes2 = get_forward_buffered_bytes(queue->ds);
+ assert(fw_bytes == fw_bytes2);
+ }
+
+ assert(kf1_found == !!queue->keyframe_first);
+
+ if (range != in->current_range) {
+ assert(fw_bytes == 0);
+ }
+
+ if (queue->keyframe_latest)
+ assert(queue->keyframe_latest->keyframe);
+
+ total_bytes += queue->index_size * sizeof(struct index_entry);
+ }
+
+ // Invariant needed by pruning; violation has worse effects than just
+ // e.g. broken seeking due to incorrect seek ranges.
+ if (range->seek_start != MP_NOPTS_VALUE)
+ assert(range_num_packets > 0);
+ }
+
+ assert(in->total_bytes == total_bytes);
+}
+#endif
+
+// (this doesn't do most required things for a switch, like updating ds->queue)
+static void set_current_range(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ in->current_range = range;
+
+ // Move to in->ranges[in->num_ranges-1] (for LRU sorting/invariant)
+ for (int n = 0; n < in->num_ranges; n++) {
+ if (in->ranges[n] == range) {
+ MP_TARRAY_REMOVE_AT(in->ranges, in->num_ranges, n);
+ break;
+ }
+ }
+ MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, range);
+}
+
+static void prune_metadata(struct demux_cached_range *range)
+{
+ int first_needed = 0;
+
+ if (range->seek_start == MP_NOPTS_VALUE) {
+ first_needed = range->num_metadata;
+ } else {
+ for (int n = 0; n < range->num_metadata ; n++) {
+ if (range->metadata[n]->pts > range->seek_start)
+ break;
+ first_needed = n;
+ }
+ }
+
+ // Always preserve the last entry.
+ first_needed = MPMIN(first_needed, range->num_metadata - 1);
+
+ // (Could make this significantly more efficient for large first_needed,
+ // however that might be very rare and even then it might not matter.)
+ for (int n = 0; n < first_needed; n++) {
+ talloc_free(range->metadata[0]);
+ MP_TARRAY_REMOVE_AT(range->metadata, range->num_metadata, 0);
+ }
+}
+
+// Refresh range->seek_start/end. Idempotent.
+static void update_seek_ranges(struct demux_cached_range *range)
+{
+ range->seek_start = range->seek_end = MP_NOPTS_VALUE;
+ range->is_bof = true;
+ range->is_eof = true;
+
+ double min_start_pts = MP_NOPTS_VALUE;
+ double max_end_pts = MP_NOPTS_VALUE;
+
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+
+ if (queue->ds->selected && queue->ds->eager) {
+ if (queue->is_bof) {
+ min_start_pts = MP_PTS_MIN(min_start_pts, queue->seek_start);
+ } else {
+ range->seek_start =
+ MP_PTS_MAX(range->seek_start, queue->seek_start);
+ }
+
+ if (queue->is_eof) {
+ max_end_pts = MP_PTS_MAX(max_end_pts, queue->seek_end);
+ } else {
+ range->seek_end = MP_PTS_MIN(range->seek_end, queue->seek_end);
+ }
+
+ range->is_eof &= queue->is_eof;
+ range->is_bof &= queue->is_bof;
+
+ bool empty = queue->is_eof && !queue->head;
+ if (queue->seek_start >= queue->seek_end && !empty &&
+ !(queue->seek_start == queue->seek_end &&
+ queue->seek_start != MP_NOPTS_VALUE))
+ goto broken;
+ }
+ }
+
+ if (range->is_eof)
+ range->seek_end = max_end_pts;
+ if (range->is_bof)
+ range->seek_start = min_start_pts;
+
+ // Sparse (subtitle) stream behavior is not very clearly defined, but
+ // usually we don't want it to restrict the range of other streams. For
+ // example, if there are subtitle packets at position 5 and 10 seconds, and
+ // the demuxer demuxed the other streams until position 7 seconds, the seek
+ // range end position is 7.
+ // Assume that reading a non-sparse (audio/video) packet gets all sparse
+ // packets that are needed before that non-sparse packet.
+ // This is incorrect in any of these cases:
+ // - sparse streams only (it's unknown how to determine an accurate range)
+ // - if sparse streams have non-keyframe packets (we set queue->last_pruned
+ // to the start of the pruned keyframe range - we'd need the end or so)
+ // We also assume that ds->eager equals to a stream not being sparse
+ // (usually true, except if only sparse streams are selected).
+ // We also rely on the fact that the demuxer position will always be ahead
+ // of the seek_end for audio/video, because they need to prefetch at least
+ // 1 packet to detect the end of a keyframe range. This means that there's
+ // a relatively high guarantee to have all sparse (subtitle) packets within
+ // the seekable range.
+ // As a consequence, the code _never_ checks queue->seek_end for a sparse
+ // queue, as the end of it is implied by the highest PTS of a non-sparse
+ // stream (i.e. the latest demuxer position).
+ // On the other hand, if a sparse packet was pruned, and that packet has
+ // a higher PTS than seek_start for non-sparse queues, that packet is
+ // missing. So the range's seek_start needs to be adjusted accordingly.
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+ if (queue->ds->selected && !queue->ds->eager &&
+ queue->last_pruned != MP_NOPTS_VALUE &&
+ range->seek_start != MP_NOPTS_VALUE)
+ {
+ // (last_pruned is _exclusive_ to the seekable range, so add a small
+ // value to exclude it from the valid range.)
+ range->seek_start =
+ MP_PTS_MAX(range->seek_start, queue->last_pruned + 0.1);
+ }
+ }
+
+ if (range->seek_start >= range->seek_end)
+ goto broken;
+
+ prune_metadata(range);
+ return;
+
+broken:
+ range->seek_start = range->seek_end = MP_NOPTS_VALUE;
+ prune_metadata(range);
+}
+
+// Remove queue->head from the queue.
+static void remove_head_packet(struct demux_queue *queue)
+{
+ struct demux_packet *dp = queue->head;
+
+ assert(queue->ds->reader_head != dp);
+ if (queue->keyframe_first == dp)
+ queue->keyframe_first = NULL;
+ if (queue->keyframe_latest == dp)
+ queue->keyframe_latest = NULL;
+ queue->is_bof = false;
+
+ uint64_t end_pos = dp->next ? dp->next->cum_pos : queue->tail_cum_pos;
+ queue->ds->in->total_bytes -= end_pos - dp->cum_pos;
+
+ if (queue->num_index && queue->index[queue->index0].pkt == dp) {
+ queue->index0 = (queue->index0 + 1) & QUEUE_INDEX_SIZE_MASK(queue);
+ queue->num_index -= 1;
+ }
+
+ queue->head = dp->next;
+ if (!queue->head)
+ queue->tail = NULL;
+
+ talloc_free(dp);
+}
+
+static void free_index(struct demux_queue *queue)
+{
+ struct demux_stream *ds = queue->ds;
+ struct demux_internal *in = ds->in;
+
+ in->total_bytes -= queue->index_size * sizeof(queue->index[0]);
+ queue->index_size = 0;
+ queue->index0 = 0;
+ queue->num_index = 0;
+ TA_FREEP(&queue->index);
+}
+
+static void clear_queue(struct demux_queue *queue)
+{
+ struct demux_stream *ds = queue->ds;
+ struct demux_internal *in = ds->in;
+
+ if (queue->head)
+ in->total_bytes -= queue->tail_cum_pos - queue->head->cum_pos;
+
+ free_index(queue);
+
+ struct demux_packet *dp = queue->head;
+ while (dp) {
+ struct demux_packet *dn = dp->next;
+ assert(ds->reader_head != dp);
+ talloc_free(dp);
+ dp = dn;
+ }
+ queue->head = queue->tail = NULL;
+ queue->keyframe_first = NULL;
+ queue->keyframe_latest = NULL;
+ queue->seek_start = queue->seek_end = queue->last_pruned = MP_NOPTS_VALUE;
+
+ queue->correct_dts = queue->correct_pos = true;
+ queue->last_pos = -1;
+ queue->last_ts = queue->last_dts = MP_NOPTS_VALUE;
+ queue->last_pos_fixup = -1;
+
+ queue->is_eof = false;
+ queue->is_bof = false;
+}
+
+static void clear_cached_range(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ for (int n = 0; n < range->num_streams; n++)
+ clear_queue(range->streams[n]);
+
+ for (int n = 0; n < range->num_metadata; n++)
+ talloc_free(range->metadata[n]);
+ range->num_metadata = 0;
+
+ update_seek_ranges(range);
+}
+
+// Remove ranges with no data (except in->current_range). Also remove excessive
+// ranges.
+static void free_empty_cached_ranges(struct demux_internal *in)
+{
+ while (1) {
+ struct demux_cached_range *worst = NULL;
+
+ int end = in->num_ranges - 1;
+
+ // (Not set during early init or late destruction.)
+ if (in->current_range) {
+ assert(in->current_range && in->num_ranges > 0);
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+ end -= 1;
+ }
+
+ for (int n = end; n >= 0; n--) {
+ struct demux_cached_range *range = in->ranges[n];
+ if (range->seek_start == MP_NOPTS_VALUE || !in->seekable_cache) {
+ clear_cached_range(in, range);
+ MP_TARRAY_REMOVE_AT(in->ranges, in->num_ranges, n);
+ for (int i = 0; i < range->num_streams; i++)
+ talloc_free(range->streams[i]);
+ talloc_free(range);
+ } else {
+ if (!worst || (range->seek_end - range->seek_start <
+ worst->seek_end - worst->seek_start))
+ worst = range;
+ }
+ }
+
+ if (in->num_ranges <= MAX_SEEK_RANGES || !worst)
+ break;
+
+ clear_cached_range(in, worst);
+ }
+}
+
+static void ds_clear_reader_queue_state(struct demux_stream *ds)
+{
+ ds->reader_head = NULL;
+ ds->eof = false;
+ ds->need_wakeup = true;
+}
+
+static void ds_clear_reader_state(struct demux_stream *ds,
+ bool clear_back_state)
+{
+ ds_clear_reader_queue_state(ds);
+
+ ds->base_ts = ds->last_br_ts = MP_NOPTS_VALUE;
+ ds->last_br_bytes = 0;
+ ds->bitrate = -1;
+ ds->skip_to_keyframe = false;
+ ds->attached_picture_added = false;
+ ds->last_ret_pos = -1;
+ ds->last_ret_dts = MP_NOPTS_VALUE;
+ ds->force_read_until = MP_NOPTS_VALUE;
+
+ if (clear_back_state) {
+ ds->back_restart_pos = -1;
+ ds->back_restart_dts = MP_NOPTS_VALUE;
+ ds->back_restart_eof = false;
+ ds->back_restart_next = ds->in->back_demuxing;
+ ds->back_restarting = ds->in->back_demuxing && ds->eager;
+ ds->back_seek_pos = MP_NOPTS_VALUE;
+ ds->back_resume_pos = -1;
+ ds->back_resume_dts = MP_NOPTS_VALUE;
+ ds->back_resuming = false;
+ ds->back_range_started = false;
+ ds->back_range_count = 0;
+ ds->back_range_preroll = 0;
+ }
+}
+
+// called locked, from user thread only
+static void clear_reader_state(struct demux_internal *in,
+ bool clear_back_state)
+{
+ for (int n = 0; n < in->num_streams; n++)
+ ds_clear_reader_state(in->streams[n]->ds, clear_back_state);
+ in->warned_queue_overflow = false;
+ in->d_user->filepos = -1; // implicitly synchronized
+ in->blocked = false;
+ in->need_back_seek = false;
+}
+
+// Call if the observed reader state on this stream somehow changes. The wakeup
+// is skipped if the reader successfully read a packet, because that means we
+// expect it to come back and ask for more.
+static void wakeup_ds(struct demux_stream *ds)
+{
+ if (ds->need_wakeup) {
+ if (ds->wakeup_cb) {
+ ds->wakeup_cb(ds->wakeup_cb_ctx);
+ } else if (ds->in->wakeup_cb) {
+ ds->in->wakeup_cb(ds->in->wakeup_cb_ctx);
+ }
+ ds->need_wakeup = false;
+ mp_cond_signal(&ds->in->wakeup);
+ }
+}
+
+static void update_stream_selection_state(struct demux_internal *in,
+ struct demux_stream *ds)
+{
+ ds->eof = false;
+ ds->refreshing = false;
+
+ // We still have to go over the whole stream list to update ds->eager for
+ // 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;
+
+ s->still_image = s->sh->still_image;
+ s->eager = s->selected && !s->sh->attached_picture;
+ if (s->eager && !s->still_image)
+ any_av_streams |= s->type != STREAM_SUB;
+ any_streams |= s->selected;
+ }
+
+ // Subtitles are only eagerly read if there are no other eagerly read
+ // streams.
+ if (any_av_streams) {
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *s = in->streams[n]->ds;
+
+ if (s->type == STREAM_SUB)
+ s->eager = false;
+ }
+ }
+
+ if (!any_streams)
+ in->blocked = false;
+
+ ds_clear_reader_state(ds, true);
+
+ // 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
+ // updated all the subtle state (like s->eager).
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+
+ if (!ds->selected)
+ clear_queue(range->streams[ds->index]);
+
+ update_seek_ranges(range);
+ }
+
+ free_empty_cached_ranges(in);
+
+ wakeup_ds(ds);
+}
+
+void demux_set_ts_offset(struct demuxer *demuxer, double offset)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ in->ts_offset = offset;
+ mp_mutex_unlock(&in->lock);
+}
+
+static void add_missing_streams(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ for (int n = range->num_streams; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ struct demux_queue *queue = talloc_ptrtype(NULL, queue);
+ *queue = (struct demux_queue){
+ .ds = ds,
+ .range = range,
+ };
+ clear_queue(queue);
+ MP_TARRAY_APPEND(range, range->streams, range->num_streams, queue);
+ assert(range->streams[ds->index] == queue);
+ }
+}
+
+// Allocate a new sh_stream of the given type. It either has to be released
+// with talloc_free(), or added to a demuxer with demux_add_sh_stream(). You
+// cannot add or read packets from the stream before it has been added.
+// type may be changed later, but only before demux_add_sh_stream().
+struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
+{
+ struct sh_stream *sh = talloc_ptrtype(NULL, sh);
+ *sh = (struct sh_stream) {
+ .type = type,
+ .index = -1,
+ .ff_index = -1, // may be overwritten by demuxer
+ .demuxer_id = -1, // ... same
+ .program_id = -1, // ... same
+ .codec = talloc_zero(sh, struct mp_codec_params),
+ .tags = talloc_zero(sh, struct mp_tags),
+ };
+ sh->codec->type = type;
+ return sh;
+}
+
+// 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).
+static void demux_add_sh_stream_locked(struct demux_internal *in,
+ struct sh_stream *sh)
+{
+ assert(!sh->ds); // must not be added yet
+
+ sh->index = in->num_streams;
+
+ sh->ds = talloc(sh, struct demux_stream);
+ *sh->ds = (struct demux_stream) {
+ .in = in,
+ .sh = sh,
+ .type = sh->type,
+ .index = sh->index,
+ .global_correct_dts = true,
+ .global_correct_pos = true,
+ };
+
+ struct demux_stream *ds = sh->ds;
+
+ if (!sh->codec->codec)
+ sh->codec->codec = "";
+
+ if (sh->ff_index < 0)
+ sh->ff_index = sh->index;
+
+ MP_TARRAY_APPEND(in, in->streams, in->num_streams, sh);
+ assert(in->streams[sh->index] == sh);
+
+ if (in->current_range) {
+ for (int n = 0; n < in->num_ranges; n++)
+ add_missing_streams(in, in->ranges[n]);
+
+ sh->ds->queue = in->current_range->streams[sh->ds->index];
+ }
+
+ update_stream_selection_state(in, sh->ds);
+
+ switch (ds->type) {
+ case STREAM_AUDIO:
+ ds->back_preroll = in->d_user->opts->audio_back_preroll;
+ if (ds->back_preroll < 0) { // auto
+ ds->back_preroll = mp_codec_is_lossless(sh->codec->codec) ? 0 : 1;
+ if (sh->codec->codec && (strcmp(sh->codec->codec, "opus") == 0 ||
+ strcmp(sh->codec->codec, "vorbis") == 0 ||
+ strcmp(sh->codec->codec, "mp3") == 0))
+ ds->back_preroll = 2;
+ }
+ break;
+ case STREAM_VIDEO:
+ ds->back_preroll = in->d_user->opts->video_back_preroll;
+ if (ds->back_preroll < 0)
+ ds->back_preroll = 0; // auto
+ break;
+ }
+
+ if (!ds->sh->attached_picture) {
+ // Typically this is used for webradio, so any stream will do.
+ if (!in->metadata_stream)
+ in->metadata_stream = sh;
+ }
+
+ in->events |= DEMUX_EVENT_STREAMS;
+ if (in->wakeup_cb)
+ in->wakeup_cb(in->wakeup_cb_ctx);
+}
+
+// For demuxer implementations only.
+void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_thread);
+ mp_mutex_lock(&in->lock);
+ demux_add_sh_stream_locked(in, sh);
+ mp_mutex_unlock(&in->lock);
+}
+
+// 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,
+// which will be valid until the demuxer is destroyed.
+struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ assert(index >= 0 && index < in->num_streams);
+ struct sh_stream *r = in->streams[index];
+ mp_mutex_unlock(&in->lock);
+ return r;
+}
+
+// See demux_get_stream().
+int demux_get_num_stream(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ int r = in->num_streams;
+ mp_mutex_unlock(&in->lock);
+ return r;
+}
+
+// It's UB to call anything but demux_dealloc() on the demuxer after this.
+static void demux_shutdown(struct demux_internal *in)
+{
+ struct demuxer *demuxer = in->d_user;
+
+ if (in->recorder) {
+ mp_recorder_destroy(in->recorder);
+ in->recorder = NULL;
+ }
+
+ dumper_close(in);
+
+ if (demuxer->desc->close)
+ demuxer->desc->close(in->d_thread);
+ demuxer->priv = NULL;
+ in->d_thread->priv = NULL;
+
+ demux_flush(demuxer);
+ assert(in->total_bytes == 0);
+
+ in->current_range = NULL;
+ free_empty_cached_ranges(in);
+
+ talloc_free(in->cache);
+ in->cache = NULL;
+
+ if (in->owns_stream)
+ free_stream(demuxer->stream);
+ demuxer->stream = NULL;
+}
+
+static void demux_dealloc(struct demux_internal *in)
+{
+ for (int n = 0; n < in->num_streams; n++)
+ talloc_free(in->streams[n]);
+ mp_mutex_destroy(&in->lock);
+ mp_cond_destroy(&in->wakeup);
+ talloc_free(in->d_user);
+}
+
+void demux_free(struct demuxer *demuxer)
+{
+ if (!demuxer)
+ return;
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ demux_stop_thread(demuxer);
+ demux_shutdown(in);
+ demux_dealloc(in);
+}
+
+// Start closing the demuxer and eventually freeing the demuxer asynchronously.
+// You must not access the demuxer once this has been started. Once the demuxer
+// is shutdown, the wakeup callback is invoked. Then you need to call
+// demux_free_async_finish() to end the operation (it must not be called from
+// the wakeup callback).
+// This can return NULL. Then the demuxer cannot be free'd asynchronously, and
+// you need to call demux_free() instead.
+struct demux_free_async_state *demux_free_async(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ if (!in->threading)
+ return NULL;
+
+ mp_mutex_lock(&in->lock);
+ in->thread_terminate = true;
+ in->shutdown_async = true;
+ mp_cond_signal(&in->wakeup);
+ mp_mutex_unlock(&in->lock);
+
+ return (struct demux_free_async_state *)demuxer->in; // lies
+}
+
+// As long as state is valid, you can call this to request immediate abort.
+// Roughly behaves as demux_cancel_and_free(), except you still need to wait
+// for the result.
+void demux_free_async_force(struct demux_free_async_state *state)
+{
+ struct demux_internal *in = (struct demux_internal *)state; // reverse lies
+
+ mp_cancel_trigger(in->d_user->cancel);
+}
+
+// Check whether the demuxer is shutdown yet. If not, return false, and you
+// need to call this again in the future (preferably after you were notified by
+// the wakeup callback). If yes, deallocate all state, and return true (in
+// particular, the state ptr becomes invalid, and the wakeup callback will never
+// be called again).
+bool demux_free_async_finish(struct demux_free_async_state *state)
+{
+ struct demux_internal *in = (struct demux_internal *)state; // reverse lies
+
+ mp_mutex_lock(&in->lock);
+ bool busy = in->shutdown_async;
+ mp_mutex_unlock(&in->lock);
+
+ if (busy)
+ return false;
+
+ demux_stop_thread(in->d_user);
+ demux_dealloc(in);
+ return true;
+}
+
+// Like demux_free(), but trigger an abort, which will force the demuxer to
+// terminate immediately. If this wasn't opened with demux_open_url(), there is
+// some chance this will accidentally abort other things via demuxer->cancel.
+void demux_cancel_and_free(struct demuxer *demuxer)
+{
+ if (!demuxer)
+ return;
+ mp_cancel_trigger(demuxer->cancel);
+ demux_free(demuxer);
+}
+
+// Start the demuxer thread, which reads ahead packets on its own.
+void demux_start_thread(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ if (!in->threading) {
+ in->threading = true;
+ if (mp_thread_create(&in->thread, demux_thread, in))
+ in->threading = false;
+ }
+}
+
+void demux_stop_thread(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ if (in->threading) {
+ mp_mutex_lock(&in->lock);
+ in->thread_terminate = true;
+ mp_cond_signal(&in->wakeup);
+ mp_mutex_unlock(&in->lock);
+ mp_thread_join(in->thread);
+ in->threading = false;
+ in->thread_terminate = false;
+ }
+}
+
+// The demuxer thread will call cb(ctx) if there's a new packet, or EOF is reached.
+void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ in->wakeup_cb = cb;
+ in->wakeup_cb_ctx = ctx;
+ mp_mutex_unlock(&in->lock);
+}
+
+void demux_start_prefetch(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ mp_mutex_lock(&in->lock);
+ in->reading = true;
+ mp_cond_signal(&in->wakeup);
+ mp_mutex_unlock(&in->lock);
+}
+
+const char *stream_type_name(enum stream_type type)
+{
+ switch (type) {
+ case STREAM_VIDEO: return "video";
+ case STREAM_AUDIO: return "audio";
+ case STREAM_SUB: return "sub";
+ default: return "unknown";
+ }
+}
+
+static struct sh_stream *demuxer_get_cc_track_locked(struct sh_stream *stream)
+{
+ struct sh_stream *sh = stream->ds->cc;
+
+ if (!sh) {
+ sh = demux_alloc_sh_stream(STREAM_SUB);
+ if (!sh)
+ return NULL;
+ sh->codec->codec = "eia_608";
+ sh->default_track = true;
+ sh->hls_bitrate = stream->hls_bitrate;
+ sh->program_id = stream->program_id;
+ stream->ds->cc = sh;
+ demux_add_sh_stream_locked(stream->ds->in, sh);
+ sh->ds->ignore_eof = true;
+ }
+
+ return sh;
+}
+
+void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
+{
+ struct demux_internal *in = stream->ds->in;
+
+ mp_mutex_lock(&in->lock);
+ struct sh_stream *sh = demuxer_get_cc_track_locked(stream);
+ if (!sh) {
+ mp_mutex_unlock(&in->lock);
+ talloc_free(dp);
+ return;
+ }
+
+ dp->keyframe = true;
+ dp->pts = MP_ADD_PTS(dp->pts, -in->ts_offset);
+ dp->dts = MP_ADD_PTS(dp->dts, -in->ts_offset);
+ dp->stream = sh->index;
+ add_packet_locked(sh, dp);
+ mp_mutex_unlock(&in->lock);
+}
+
+static void error_on_backward_demuxing(struct demux_internal *in)
+{
+ if (!in->back_demuxing)
+ return;
+ MP_ERR(in, "Disabling backward demuxing.\n");
+ in->back_demuxing = false;
+ clear_reader_state(in, true);
+}
+
+static void perform_backward_seek(struct demux_internal *in)
+{
+ double target = MP_NOPTS_VALUE;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ if (ds->reader_head && !ds->back_restarting && !ds->back_resuming &&
+ ds->eager)
+ {
+ ds->back_resuming = true;
+ ds->back_resume_pos = ds->reader_head->pos;
+ ds->back_resume_dts = ds->reader_head->dts;
+ }
+
+ target = MP_PTS_MIN(target, ds->back_seek_pos);
+ }
+
+ target = MP_PTS_OR_DEF(target, in->d_thread->start_time);
+
+ MP_VERBOSE(in, "triggering backward seek to get more packets\n");
+ queue_seek(in, target, SEEK_SATAN | SEEK_HR, false);
+ in->reading = true;
+
+ // Don't starve other threads.
+ mp_mutex_unlock(&in->lock);
+ mp_mutex_lock(&in->lock);
+}
+
+// For incremental backward demuxing search work.
+static void check_backward_seek(struct demux_internal *in)
+{
+ in->back_any_need_recheck = false;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ if (ds->back_need_recheck)
+ find_backward_restart_pos(ds);
+ }
+}
+
+// Search for a packet to resume demuxing from.
+// The implementation of this function is quite awkward, because the packet
+// queue is a singly linked list without back links, while it needs to search
+// backwards.
+// This is the core of backward demuxing.
+static void find_backward_restart_pos(struct demux_stream *ds)
+{
+ struct demux_internal *in = ds->in;
+
+ ds->back_need_recheck = false;
+ if (!ds->back_restarting)
+ return;
+
+ struct demux_packet *first = ds->reader_head;
+ struct demux_packet *last = ds->queue->tail;
+
+ if (first && !first->keyframe)
+ MP_WARN(in, "Queue not starting on keyframe.\n");
+
+ // Packet at back_restart_pos. (Note: we don't actually need it, only the
+ // packet immediately before it. But same effort.)
+ // If this is NULL, look for EOF (resume from very last keyframe).
+ struct demux_packet *back_restart = NULL;
+
+ if (ds->back_restart_next) {
+ // Initial state. Switch to one of the other modi.
+
+ for (struct demux_packet *cur = first; cur; cur = cur->next) {
+ // Restart for next keyframe after reader_head.
+ if (cur != first && cur->keyframe) {
+ ds->back_restart_dts = cur->dts;
+ ds->back_restart_pos = cur->pos;
+ ds->back_restart_eof = false;
+ ds->back_restart_next = false;
+ break;
+ }
+ }
+
+ if (ds->back_restart_next && ds->eof) {
+ // Restart from end if nothing was found.
+ ds->back_restart_eof = true;
+ ds->back_restart_next = false;
+ }
+
+ if (ds->back_restart_next)
+ return;
+ }
+
+ if (ds->back_restart_eof) {
+ // We're trying to find EOF (without discarding packets). Only continue
+ // if we really reach EOF.
+ if (!ds->eof)
+ return;
+ } else if (!first && ds->eof) {
+ // Reached EOF during normal backward demuxing. We probably returned the
+ // last keyframe range to user. Need to resume at an earlier position.
+ // Fall through, hit the no-keyframe case (and possibly the BOF check
+ // if there are no packets at all), and then resume_earlier.
+ } else if (!first) {
+ return; // no packets yet
+ } else {
+ assert(last);
+
+ if ((ds->global_correct_dts && last->dts < ds->back_restart_dts) ||
+ (ds->global_correct_pos && last->pos < ds->back_restart_pos))
+ return; // restart pos not reached yet
+
+ // The target we're searching for is apparently before the start of the
+ // queue.
+ if ((ds->global_correct_dts && first->dts > ds->back_restart_dts) ||
+ (ds->global_correct_pos && first->pos > ds->back_restart_pos))
+ goto resume_earlier; // current position is too late; seek back
+
+
+ for (struct demux_packet *cur = first; cur; cur = cur->next) {
+ if ((ds->global_correct_dts && cur->dts == ds->back_restart_dts) ||
+ (ds->global_correct_pos && cur->pos == ds->back_restart_pos))
+ {
+ back_restart = cur;
+ break;
+ }
+ }
+
+ if (!back_restart) {
+ // The packet should have been in the searched range; maybe dts/pos
+ // determinism assumptions were broken.
+ MP_ERR(in, "Demuxer not cooperating.\n");
+ error_on_backward_demuxing(in);
+ return;
+ }
+ }
+
+ // Find where to restart demuxing. It's usually the last keyframe packet
+ // before restart_pos, but might be up to back_preroll + batch keyframe
+ // packets earlier.
+
+ // (Normally, we'd just iterate backwards, but no back links.)
+ int num_kf = 0;
+ struct demux_packet *pre_1 = NULL; // idiotic "optimization" for total=1
+ for (struct demux_packet *dp = first; dp != back_restart; dp = dp->next) {
+ if (dp->keyframe) {
+ num_kf++;
+ pre_1 = dp;
+ }
+ }
+
+ // Number of renderable keyframes to return to user.
+ // (Excludes preroll, which is decoded by user, but then discarded.)
+ int batch = MPMAX(in->d_user->opts->back_batch[ds->type], 1);
+ // Number of keyframes to return to the user in total.
+ int total = batch + ds->back_preroll;
+
+ assert(total >= 1);
+
+ bool is_bof = ds->queue->is_bof &&
+ (first == ds->queue->head || ds->back_seek_pos < ds->queue->seek_start);
+
+ struct demux_packet *target = NULL; // resume pos
+ // nr. of keyframes, incl. target, excl. restart_pos
+ int got_total = num_kf < total && is_bof ? num_kf : total;
+ int got_preroll = MPMAX(got_total - batch, 0);
+
+ if (got_total == 1) {
+ target = pre_1;
+ } else if (got_total <= num_kf) {
+ int cur_kf = 0;
+ for (struct demux_packet *dp = first; dp != back_restart; dp = dp->next) {
+ if (dp->keyframe) {
+ if (num_kf - cur_kf == got_total) {
+ target = dp;
+ break;
+ }
+ cur_kf++;
+ }
+ }
+ }
+
+ if (!target) {
+ if (is_bof) {
+ MP_VERBOSE(in, "BOF for stream %d\n", ds->index);
+ ds->back_restarting = false;
+ ds->back_range_started = false;
+ ds->back_range_count = -1;
+ ds->back_range_preroll = 0;
+ ds->need_wakeup = true;
+ wakeup_ds(ds);
+ return;
+ }
+ goto resume_earlier;
+ }
+
+ // Skip reader_head from previous keyframe to current one.
+ // Or if preroll is involved, the first preroll packet.
+ while (ds->reader_head != target) {
+ if (!advance_reader_head(ds))
+ MP_ASSERT_UNREACHABLE(); // target must be in list
+ }
+
+ double seek_pts;
+ compute_keyframe_times(target, &seek_pts, NULL);
+ if (seek_pts != MP_NOPTS_VALUE)
+ ds->back_seek_pos = seek_pts;
+
+ // For next backward adjust action.
+ struct demux_packet *restart_pkt = NULL;
+ int kf_pos = 0;
+ for (struct demux_packet *dp = target; dp; dp = dp->next) {
+ if (dp->keyframe) {
+ if (kf_pos == got_preroll) {
+ restart_pkt = dp;
+ break;
+ }
+ kf_pos++;
+ }
+ }
+ assert(restart_pkt);
+ ds->back_restart_dts = restart_pkt->dts;
+ ds->back_restart_pos = restart_pkt->pos;
+
+ ds->back_restarting = false;
+ ds->back_range_started = false;
+ ds->back_range_count = got_total;
+ ds->back_range_preroll = got_preroll;
+ ds->need_wakeup = true;
+ wakeup_ds(ds);
+ return;
+
+resume_earlier:
+ // We want to seek back to get earlier packets. But before we do this, we
+ // must be sure that other streams have initialized their state. The only
+ // time when this state is not initialized is right after the seek that
+ // started backward demuxing (not any subsequent backstep seek). If this
+ // initialization is omitted, the stream would try to start demuxing from
+ // the "current" position. If another stream backstepped before that, the
+ // other stream will miss the original seek target, and start playback from
+ // a position that is too early.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds2 = in->streams[n]->ds;
+ if (ds2 == ds || !ds2->eager)
+ continue;
+
+ if (ds2->back_restarting && ds2->back_restart_next) {
+ MP_VERBOSE(in, "delaying stream %d for %d\n", ds->index, ds2->index);
+ return;
+ }
+ }
+
+ if (ds->back_seek_pos != MP_NOPTS_VALUE) {
+ struct demux_packet *t =
+ find_seek_target(ds->queue, ds->back_seek_pos - 0.001, 0);
+ if (t && t != ds->reader_head) {
+ double pts;
+ compute_keyframe_times(t, &pts, NULL);
+ ds->back_seek_pos = MP_PTS_MIN(ds->back_seek_pos, pts);
+ ds_clear_reader_state(ds, false);
+ ds->reader_head = t;
+ ds->back_need_recheck = true;
+ in->back_any_need_recheck = true;
+ mp_cond_signal(&in->wakeup);
+ } else {
+ ds->back_seek_pos -= in->d_user->opts->back_seek_size;
+ in->need_back_seek = true;
+ }
+ }
+}
+
+// Process that one or multiple packets were added.
+static void back_demux_see_packets(struct demux_stream *ds)
+{
+ struct demux_internal *in = ds->in;
+
+ if (!ds->selected || !in->back_demuxing || !ds->eager)
+ return;
+
+ assert(!(ds->back_resuming && ds->back_restarting));
+
+ if (!ds->global_correct_dts && !ds->global_correct_pos) {
+ MP_ERR(in, "Can't demux backward due to demuxer problems.\n");
+ error_on_backward_demuxing(in);
+ return;
+ }
+
+ while (ds->back_resuming && ds->reader_head) {
+ struct demux_packet *head = ds->reader_head;
+ if ((ds->global_correct_dts && head->dts == ds->back_resume_dts) ||
+ (ds->global_correct_pos && head->pos == ds->back_resume_pos))
+ {
+ ds->back_resuming = false;
+ ds->need_wakeup = true;
+ wakeup_ds(ds); // probably
+ break;
+ }
+ advance_reader_head(ds);
+ }
+
+ if (ds->back_restarting)
+ find_backward_restart_pos(ds);
+}
+
+// Add the keyframe to the end of the index. Not all packets are actually added.
+static void add_index_entry(struct demux_queue *queue, struct demux_packet *dp,
+ double pts)
+{
+ struct demux_internal *in = queue->ds->in;
+
+ assert(dp->keyframe && pts != MP_NOPTS_VALUE);
+
+ if (queue->num_index > 0) {
+ struct index_entry *last = &QUEUE_INDEX_ENTRY(queue, queue->num_index - 1);
+ if (pts - last->pts < INDEX_STEP_SIZE)
+ return;
+ }
+
+ if (queue->num_index == queue->index_size) {
+ // Needs to honor power-of-2 requirement.
+ size_t new_size = MPMAX(128, queue->index_size * 2);
+ assert(!(new_size & (new_size - 1)));
+ MP_DBG(in, "stream %d: resize index to %zu\n", queue->ds->index,
+ new_size);
+ // Note: we could tolerate allocation failure, and just discard the
+ // entire index (and prevent the index from being recreated).
+ MP_RESIZE_ARRAY(NULL, queue->index, new_size);
+ size_t highest_index = queue->index0 + queue->num_index;
+ for (size_t n = queue->index_size; n < highest_index; n++)
+ queue->index[n] = queue->index[n - queue->index_size];
+ in->total_bytes +=
+ (new_size - queue->index_size) * sizeof(queue->index[0]);
+ queue->index_size = new_size;
+ }
+
+ assert(queue->num_index < queue->index_size);
+
+ queue->num_index += 1;
+
+ QUEUE_INDEX_ENTRY(queue, queue->num_index - 1) = (struct index_entry){
+ .pts = pts,
+ .pkt = dp,
+ };
+}
+
+// Check whether the next range in the list is, and if it appears to overlap,
+// try joining it into a single range.
+static void attempt_range_joining(struct demux_internal *in)
+{
+ struct demux_cached_range *current = in->current_range;
+ struct demux_cached_range *next = NULL;
+ double next_dist = INFINITY;
+
+ assert(current && in->num_ranges > 0);
+ assert(current == in->ranges[in->num_ranges - 1]);
+
+ for (int n = 0; n < in->num_ranges - 1; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+
+ if (current->seek_start <= range->seek_start) {
+ // This uses ">" to get some non-0 overlap.
+ double dist = current->seek_end - range->seek_start;
+ if (dist > 0 && dist < next_dist) {
+ next = range;
+ next_dist = dist;
+ }
+ }
+ }
+
+ if (!next)
+ return;
+
+ MP_VERBOSE(in, "going to join ranges %f-%f + %f-%f\n",
+ current->seek_start, current->seek_end,
+ next->seek_start, next->seek_end);
+
+ // Try to find a join point, where packets obviously overlap. (It would be
+ // better and faster to do this incrementally, but probably too complex.)
+ // The current range can overlap arbitrarily with the next one, not only by
+ // the seek overlap, but for arbitrary packet readahead as well.
+ // We also drop the overlapping packets (if joining fails, we discard the
+ // entire next range anyway, so this does no harm).
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ struct demux_queue *q1 = current->streams[n];
+ struct demux_queue *q2 = next->streams[n];
+
+ if (!ds->global_correct_pos && !ds->global_correct_dts) {
+ MP_WARN(in, "stream %d: ranges unjoinable\n", n);
+ goto failed;
+ }
+
+ struct demux_packet *end = q1->tail;
+ bool join_point_found = !end; // no packets yet -> joining will work
+ if (end) {
+ while (q2->head) {
+ struct demux_packet *dp = q2->head;
+
+ // Some weird corner-case. We'd have to search the equivalent
+ // packet in q1 to update it correctly. Better just give up.
+ if (dp == q2->keyframe_latest) {
+ MP_VERBOSE(in, "stream %d: not enough keyframes for join\n", n);
+ goto failed;
+ }
+
+ if ((ds->global_correct_dts && dp->dts == end->dts) ||
+ (ds->global_correct_pos && dp->pos == end->pos))
+ {
+ // Do some additional checks as a (imperfect) sanity check
+ // in case pos/dts are not "correct" across the ranges (we
+ // never actually check that).
+ if (dp->dts != end->dts || dp->pos != end->pos ||
+ dp->pts != end->pts)
+ {
+ MP_WARN(in,
+ "stream %d: non-repeatable demuxer behavior\n", n);
+ goto failed;
+ }
+
+ remove_head_packet(q2);
+ join_point_found = true;
+ break;
+ }
+
+ // This happens if the next range misses the end packet. For
+ // normal streams (ds->eager==true), this is a failure to find
+ // an overlap. For subtitles, this can mean the current_range
+ // has a subtitle somewhere before the end of its range, and
+ // next has another subtitle somewhere after the start of its
+ // range.
+ if ((ds->global_correct_dts && dp->dts > end->dts) ||
+ (ds->global_correct_pos && dp->pos > end->pos))
+ break;
+
+ remove_head_packet(q2);
+ }
+ }
+
+ // For enabled non-sparse streams, always require an overlap packet.
+ if (ds->eager && !join_point_found) {
+ MP_WARN(in, "stream %d: no join point found\n", n);
+ goto failed;
+ }
+ }
+
+ // Actually join the ranges. Now that we think it will work, mutate the
+ // data associated with the current range.
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_queue *q1 = current->streams[n];
+ struct demux_queue *q2 = next->streams[n];
+
+ struct demux_stream *ds = in->streams[n]->ds;
+ assert(ds->queue == q1);
+
+ // First new packet that is appended to the current range.
+ struct demux_packet *join_point = q2->head;
+
+ if (q2->head) {
+ if (q1->head) {
+ q1->tail->next = q2->head;
+ } else {
+ q1->head = q2->head;
+ }
+ q1->tail = q2->tail;
+ }
+
+ q1->seek_end = q2->seek_end;
+ q1->correct_dts &= q2->correct_dts;
+ q1->correct_pos &= q2->correct_pos;
+ q1->last_pos = q2->last_pos;
+ q1->last_dts = q2->last_dts;
+ q1->last_ts = q2->last_ts;
+ q1->keyframe_latest = q2->keyframe_latest;
+ q1->is_eof = q2->is_eof;
+
+ q1->last_pos_fixup = -1;
+
+ q2->head = q2->tail = NULL;
+ q2->keyframe_first = NULL;
+ q2->keyframe_latest = NULL;
+
+ if (ds->selected && !ds->reader_head)
+ ds->reader_head = join_point;
+ ds->skip_to_keyframe = false;
+
+ // Make the cum_pos values in all q2 packets continuous.
+ for (struct demux_packet *dp = join_point; dp; dp = dp->next) {
+ uint64_t next_pos = dp->next ? dp->next->cum_pos : q2->tail_cum_pos;
+ uint64_t size = next_pos - dp->cum_pos;
+ dp->cum_pos = q1->tail_cum_pos;
+ q1->tail_cum_pos += size;
+ }
+
+ // And update the index with packets from q2.
+ for (size_t i = 0; i < q2->num_index; i++) {
+ struct index_entry *e = &QUEUE_INDEX_ENTRY(q2, i);
+ add_index_entry(q1, e->pkt, e->pts);
+ }
+ free_index(q2);
+
+ // For moving demuxer position.
+ ds->refreshing = ds->selected;
+ }
+
+ for (int n = 0; n < next->num_metadata; n++) {
+ MP_TARRAY_APPEND(current, current->metadata, current->num_metadata,
+ next->metadata[n]);
+ }
+ next->num_metadata = 0;
+
+ update_seek_ranges(current);
+
+ // Move demuxing position to after the current range.
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = next->seek_end - 1.0;
+
+ MP_VERBOSE(in, "ranges joined!\n");
+
+ for (int n = 0; n < in->num_streams; n++)
+ back_demux_see_packets(in->streams[n]->ds);
+
+failed:
+ clear_cached_range(in, next);
+ free_empty_cached_ranges(in);
+}
+
+// Compute the assumed first and last frame timestamp for keyframe range
+// starting at pkt. To get valid results, pkt->keyframe must be true, otherwise
+// nonsense will be returned.
+// Always sets *out_kf_min and *out_kf_max without reading them. Both are set
+// to NOPTS if there are no timestamps at all in the stream. *kf_max will not
+// be set to the actual end time of the decoded output, just the last frame
+// (audio will typically end up with kf_min==kf_max).
+// Either of out_kf_min and out_kf_max can be NULL, which discards the result.
+// Return the next keyframe packet after pkt, or NULL if there's none.
+static struct demux_packet *compute_keyframe_times(struct demux_packet *pkt,
+ double *out_kf_min,
+ double *out_kf_max)
+{
+ struct demux_packet *start = pkt;
+ double min = MP_NOPTS_VALUE;
+ double max = MP_NOPTS_VALUE;
+
+ while (pkt) {
+ if (pkt->keyframe && pkt != start)
+ break;
+
+ double ts = MP_PTS_OR_DEF(pkt->pts, pkt->dts);
+ if (pkt->segmented && ((pkt->start != MP_NOPTS_VALUE && ts < pkt->start) ||
+ (pkt->end != MP_NOPTS_VALUE && ts > pkt->end)))
+ ts = MP_NOPTS_VALUE;
+
+ min = MP_PTS_MIN(min, ts);
+ max = MP_PTS_MAX(max, ts);
+
+ pkt = pkt->next;
+ }
+
+ if (out_kf_min)
+ *out_kf_min = min;
+ if (out_kf_max)
+ *out_kf_max = max;
+ return pkt;
+}
+
+// Determine seekable range when a packet is added. If dp==NULL, treat it as
+// EOF (i.e. closes the current block).
+// This has to deal with a number of corner cases, such as demuxers potentially
+// starting output at non-keyframes.
+// Can join seek ranges, which messes with in->current_range and all.
+static void adjust_seek_range_on_packet(struct demux_stream *ds,
+ struct demux_packet *dp)
+{
+ struct demux_queue *queue = ds->queue;
+
+ if (!ds->in->seekable_cache)
+ return;
+
+ bool new_eof = !dp;
+ bool update_ranges = queue->is_eof != new_eof;
+ queue->is_eof = new_eof;
+
+ if (!dp || dp->keyframe) {
+ if (queue->keyframe_latest) {
+ double kf_min, kf_max;
+ compute_keyframe_times(queue->keyframe_latest, &kf_min, &kf_max);
+
+ if (kf_min != MP_NOPTS_VALUE) {
+ add_index_entry(queue, queue->keyframe_latest, kf_min);
+
+ // Initialize the queue's start if it's unset.
+ if (queue->seek_start == MP_NOPTS_VALUE) {
+ update_ranges = true;
+ queue->seek_start = kf_min + ds->sh->seek_preroll;
+ }
+ }
+
+ if (kf_max != MP_NOPTS_VALUE &&
+ (queue->seek_end == MP_NOPTS_VALUE || kf_max > queue->seek_end))
+ {
+ // If the queue was past the current range's end even before
+ // this update, it means _other_ streams are not there yet,
+ // and the seek range doesn't need to be updated. This means
+ // if the _old_ queue->seek_end was already after the range end,
+ // then the new seek_end won't extend the range either.
+ if (queue->range->seek_end == MP_NOPTS_VALUE ||
+ queue->seek_end <= queue->range->seek_end)
+ {
+ update_ranges = true;
+ }
+
+ queue->seek_end = kf_max;
+ }
+ }
+
+ queue->keyframe_latest = dp;
+ }
+
+ // Adding a sparse packet never changes the seek range.
+ if (update_ranges && ds->eager) {
+ update_seek_ranges(queue->range);
+ attempt_range_joining(ds->in);
+ }
+}
+
+static struct mp_recorder *recorder_create(struct demux_internal *in,
+ const char *dst)
+{
+ struct sh_stream **streams = NULL;
+ int num_streams = 0;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct sh_stream *stream = in->streams[n];
+ if (stream->ds->selected)
+ MP_TARRAY_APPEND(NULL, streams, num_streams, stream);
+ }
+
+ struct demuxer *demuxer = in->d_thread;
+ struct demux_attachment **attachments = talloc_array(NULL, struct demux_attachment*, demuxer->num_attachments);
+ for (int n = 0; n < demuxer->num_attachments; n++) {
+ attachments[n] = &demuxer->attachments[n];
+ }
+
+ struct mp_recorder *res = mp_recorder_create(in->d_thread->global, dst,
+ streams, num_streams,
+ attachments, demuxer->num_attachments);
+ talloc_free(streams);
+ talloc_free(attachments);
+ return res;
+}
+
+static void write_dump_packet(struct demux_internal *in, struct demux_packet *dp)
+{
+ assert(in->dumper);
+ assert(in->dumper_status == CONTROL_TRUE);
+
+ struct mp_recorder_sink *sink =
+ mp_recorder_get_sink(in->dumper, in->streams[dp->stream]);
+ if (sink) {
+ mp_recorder_feed_packet(sink, dp);
+ } else {
+ MP_ERR(in, "New stream appeared; stopping recording.\n");
+ in->dumper_status = CONTROL_ERROR;
+ }
+}
+
+static void record_packet(struct demux_internal *in, struct demux_packet *dp)
+{
+ // (should preferably be outside of the lock)
+ if (in->enable_recording && !in->recorder &&
+ in->d_user->opts->record_file && in->d_user->opts->record_file[0])
+ {
+ // Later failures shouldn't make it retry and overwrite the previously
+ // recorded file.
+ in->enable_recording = false;
+
+ in->recorder = recorder_create(in, in->d_user->opts->record_file);
+ if (!in->recorder)
+ MP_ERR(in, "Disabling recording.\n");
+ }
+
+ if (in->recorder) {
+ struct mp_recorder_sink *sink =
+ mp_recorder_get_sink(in->recorder, in->streams[dp->stream]);
+ if (sink) {
+ mp_recorder_feed_packet(sink, dp);
+ } else {
+ MP_ERR(in, "New stream appeared; stopping recording.\n");
+ mp_recorder_destroy(in->recorder);
+ in->recorder = NULL;
+ }
+ }
+
+ if (in->dumper_status == CONTROL_OK)
+ write_dump_packet(in, dp);
+}
+
+static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp)
+{
+ struct demux_stream *ds = stream ? stream->ds : NULL;
+ assert(ds && ds->in);
+ if (!dp->len || demux_cancel_test(ds->in->d_thread)) {
+ talloc_free(dp);
+ return;
+ }
+
+ assert(dp->stream == stream->index);
+ assert(!dp->next);
+
+ struct demux_internal *in = ds->in;
+
+ in->after_seek = false;
+ in->after_seek_to_start = false;
+
+ double ts = dp->dts == MP_NOPTS_VALUE ? dp->pts : dp->dts;
+ if (dp->segmented)
+ ts = MP_PTS_MIN(ts, dp->end);
+
+ if (ts != MP_NOPTS_VALUE)
+ in->demux_ts = ts;
+
+ struct demux_queue *queue = ds->queue;
+
+ bool drop = !ds->selected || in->seeking || ds->sh->attached_picture;
+
+ if (!drop) {
+ // If libavformat splits packets, some packets will have pos unset, so
+ // make up one based on the first packet => makes refresh seeks work.
+ if ((dp->pos < 0 || dp->pos == queue->last_pos_fixup) &&
+ !dp->keyframe && queue->last_pos_fixup >= 0)
+ dp->pos = queue->last_pos_fixup + 1;
+ queue->last_pos_fixup = dp->pos;
+ }
+
+ if (!drop && ds->refreshing) {
+ // Resume reading once the old position was reached (i.e. we start
+ // returning packets where we left off before the refresh).
+ // If it's the same position, drop, but continue normally next time.
+ if (queue->correct_dts) {
+ ds->refreshing = dp->dts < queue->last_dts;
+ } else if (queue->correct_pos) {
+ ds->refreshing = dp->pos < queue->last_pos;
+ } else {
+ ds->refreshing = false; // should not happen
+ MP_WARN(in, "stream %d: demux refreshing failed\n", ds->index);
+ }
+ drop = true;
+ }
+
+ if (drop) {
+ talloc_free(dp);
+ return;
+ }
+
+ record_packet(in, dp);
+
+ if (in->cache && in->d_user->opts->disk_cache) {
+ int64_t pos = demux_cache_write(in->cache, dp);
+ if (pos >= 0) {
+ demux_packet_unref_contents(dp);
+ dp->is_cached = true;
+ dp->cached_data.pos = pos;
+ }
+ }
+
+ queue->correct_pos &= dp->pos >= 0 && dp->pos > queue->last_pos;
+ queue->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > queue->last_dts;
+ queue->last_pos = dp->pos;
+ queue->last_dts = dp->dts;
+ ds->global_correct_pos &= queue->correct_pos;
+ ds->global_correct_dts &= queue->correct_dts;
+
+ // (keep in mind that even if the reader went out of data, the queue is not
+ // necessarily empty due to the backbuffer)
+ if (!ds->reader_head && (!ds->skip_to_keyframe || dp->keyframe)) {
+ ds->reader_head = dp;
+ ds->skip_to_keyframe = false;
+ }
+
+ size_t bytes = demux_packet_estimate_total_size(dp);
+ in->total_bytes += bytes;
+ dp->cum_pos = queue->tail_cum_pos;
+ queue->tail_cum_pos += bytes;
+
+ if (queue->tail) {
+ // next packet in stream
+ queue->tail->next = dp;
+ queue->tail = dp;
+ } else {
+ // first packet in stream
+ queue->head = queue->tail = dp;
+ }
+
+ if (!ds->ignore_eof) {
+ // obviously not true anymore
+ ds->eof = false;
+ in->eof = false;
+ }
+
+ // For video, PTS determination is not trivial, but for other media types
+ // distinguishing PTS and DTS is not useful.
+ if (stream->type != STREAM_VIDEO && dp->pts == MP_NOPTS_VALUE)
+ dp->pts = dp->dts;
+
+ if (ts != MP_NOPTS_VALUE && (ts > queue->last_ts || ts + 10 < queue->last_ts))
+ queue->last_ts = ts;
+ if (ds->base_ts == MP_NOPTS_VALUE)
+ ds->base_ts = queue->last_ts;
+
+ const char *num_pkts = queue->head == queue->tail ? "1" : ">1";
+ uint64_t fw_bytes = get_forward_buffered_bytes(ds);
+ MP_TRACE(in, "append packet to %s: size=%zu pts=%f dts=%f pos=%"PRIi64" "
+ "[num=%s size=%zd]\n", stream_type_name(stream->type),
+ dp->len, dp->pts, dp->dts, dp->pos, num_pkts, (size_t)fw_bytes);
+
+ adjust_seek_range_on_packet(ds, dp);
+
+ // May need to reduce backward cache.
+ prune_old_packets(in);
+
+ // Possibly update duration based on highest TS demuxed (but ignore subs).
+ if (stream->type != STREAM_SUB) {
+ if (dp->segmented)
+ ts = MP_PTS_MIN(ts, dp->end);
+ if (ts > in->highest_av_pts) {
+ in->highest_av_pts = ts;
+ 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 user thread, would be too noisy.)
+ in->events |= DEMUX_EVENT_DURATION;
+ in->duration = duration;
+ }
+ }
+ }
+
+ // Don't process the packet further if it's skipped by the previous seek
+ // (see reader_head check/assignment above).
+ if (!ds->reader_head)
+ return;
+
+ back_demux_see_packets(ds);
+
+ wakeup_ds(ds);
+}
+
+static void mark_stream_eof(struct demux_stream *ds)
+{
+ if (!ds->eof) {
+ ds->eof = true;
+ adjust_seek_range_on_packet(ds, NULL);
+ back_demux_see_packets(ds);
+ wakeup_ds(ds);
+ }
+}
+
+static bool lazy_stream_needs_wait(struct demux_stream *ds)
+{
+ struct demux_internal *in = ds->in;
+ // Attempt to read until force_read_until was reached, or reading has
+ // stopped for some reason (true EOF, queue overflow).
+ return !ds->eager && !in->back_demuxing &&
+ !in->eof && ds->force_read_until != MP_NOPTS_VALUE &&
+ (in->demux_ts == MP_NOPTS_VALUE ||
+ in->demux_ts <= ds->force_read_until);
+}
+
+// Returns true if there was "progress" (lock was released temporarily).
+static bool read_packet(struct demux_internal *in)
+{
+ bool was_reading = in->reading;
+ in->reading = false;
+
+ if (!was_reading || in->blocked || demux_cancel_test(in->d_thread))
+ return false;
+
+ // Check if we need to read a new packet. We do this if all queues are below
+ // the minimum, or if a stream explicitly needs new packets. Also includes
+ // safe-guards against packet queue overflow.
+ bool read_more = false, prefetch_more = false, refresh_more = false;
+ uint64_t total_fw_bytes = 0;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (ds->eager) {
+ read_more |= !ds->reader_head;
+ if (in->back_demuxing)
+ read_more |= ds->back_restarting || ds->back_resuming;
+ } else {
+ if (lazy_stream_needs_wait(ds)) {
+ read_more = true;
+ } else {
+ mark_stream_eof(ds); // let playback continue
+ }
+ }
+ refresh_more |= ds->refreshing;
+ if (ds->eager && ds->queue->last_ts != MP_NOPTS_VALUE &&
+ in->min_secs > 0 && ds->base_ts != MP_NOPTS_VALUE &&
+ ds->queue->last_ts >= ds->base_ts &&
+ !in->back_demuxing)
+ {
+ if (ds->queue->last_ts - ds->base_ts <= in->hyst_secs)
+ in->hyst_active = false;
+ if (!in->hyst_active)
+ prefetch_more |= ds->queue->last_ts - ds->base_ts < in->min_secs;
+ }
+ total_fw_bytes += get_forward_buffered_bytes(ds);
+ }
+
+ MP_TRACE(in, "bytes=%zd, read_more=%d prefetch_more=%d, refresh_more=%d\n",
+ (size_t)total_fw_bytes, read_more, prefetch_more, refresh_more);
+ if (total_fw_bytes >= in->max_bytes) {
+ // if we hit the limit just by prefetching, simply stop prefetching
+ if (!read_more) {
+ in->hyst_active = !!in->hyst_secs;
+ return false;
+ }
+ if (!in->warned_queue_overflow) {
+ in->warned_queue_overflow = true;
+ MP_WARN(in, "Too many packets in the demuxer packet queues:\n");
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (ds->selected) {
+ size_t num_pkts = 0;
+ for (struct demux_packet *dp = ds->reader_head;
+ dp; dp = dp->next)
+ num_pkts++;
+ uint64_t fw_bytes = get_forward_buffered_bytes(ds);
+ MP_WARN(in, " %s/%d: %zd packets, %zd bytes%s%s\n",
+ stream_type_name(ds->type), n,
+ num_pkts, (size_t)fw_bytes,
+ ds->eager ? "" : " (lazy)",
+ ds->refreshing ? " (refreshing)" : "");
+ }
+ }
+ if (in->back_demuxing)
+ MP_ERR(in, "Backward playback is likely stuck/broken now.\n");
+ }
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (!ds->reader_head)
+ mark_stream_eof(ds);
+ }
+ return false;
+ }
+
+ if (!read_more && !prefetch_more && !refresh_more) {
+ in->hyst_active = !!in->hyst_secs;
+ return false;
+ }
+
+ if (in->after_seek_to_start) {
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ in->current_range->streams[n]->is_bof =
+ ds->selected && !ds->refreshing;
+ }
+ }
+
+ // Actually read a packet. Drop the lock while doing so, because waiting
+ // for disk or network I/O can take time.
+ in->reading = true;
+ in->after_seek = false;
+ in->after_seek_to_start = false;
+ mp_mutex_unlock(&in->lock);
+
+ struct demuxer *demux = in->d_thread;
+ struct demux_packet *pkt = NULL;
+
+ bool eof = true;
+ if (demux->desc->read_packet && !demux_cancel_test(demux))
+ eof = !demux->desc->read_packet(demux, &pkt);
+
+ mp_mutex_lock(&in->lock);
+ update_cache(in);
+
+ if (pkt) {
+ assert(pkt->stream >= 0 && pkt->stream < in->num_streams);
+ add_packet_locked(in->streams[pkt->stream], pkt);
+ }
+
+ if (!in->seeking) {
+ if (eof) {
+ for (int n = 0; n < in->num_streams; n++)
+ mark_stream_eof(in->streams[n]->ds);
+ // If we had EOF previously, then don't wakeup (avoids wakeup loop)
+ if (!in->eof) {
+ if (in->wakeup_cb)
+ in->wakeup_cb(in->wakeup_cb_ctx);
+ mp_cond_signal(&in->wakeup);
+ MP_VERBOSE(in, "EOF reached.\n");
+ }
+ }
+ in->eof = eof;
+ in->reading = !eof;
+ }
+ return true;
+}
+
+static void prune_old_packets(struct demux_internal *in)
+{
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ // It's not clear what the ideal way to prune old packets is. For now, we
+ // prune the oldest packet runs, as long as the total cache amount is too
+ // big.
+ while (1) {
+ uint64_t fw_bytes = 0;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ fw_bytes += get_forward_buffered_bytes(ds);
+ }
+ uint64_t max_avail = in->max_bytes_bw;
+ // Backward cache (if enabled at all) can use unused forward cache.
+ // Still leave 1 byte free, so the read_packet logic doesn't get stuck.
+ if (max_avail && in->max_bytes > (fw_bytes + 1) && in->d_user->opts->donate_fw)
+ max_avail += in->max_bytes - (fw_bytes + 1);
+ if (in->total_bytes - fw_bytes <= max_avail)
+ break;
+
+ // (Start from least recently used range.)
+ struct demux_cached_range *range = in->ranges[0];
+ double earliest_ts = MP_NOPTS_VALUE;
+ struct demux_stream *earliest_stream = NULL;
+
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+ struct demux_stream *ds = queue->ds;
+
+ if (queue->head && queue->head != ds->reader_head) {
+ struct demux_packet *dp = queue->head;
+ double ts = queue->seek_start;
+ // If the ts is NOPTS, the queue has no retainable packets, so
+ // delete them all. This code is not run when there's enough
+ // free space, so normally the queue gets the chance to build up.
+ bool prune_always =
+ !in->seekable_cache || ts == MP_NOPTS_VALUE || !dp->keyframe;
+ if (prune_always || !earliest_stream || ts < earliest_ts) {
+ earliest_ts = ts;
+ earliest_stream = ds;
+ if (prune_always)
+ break;
+ }
+ }
+ }
+
+ // In some cases (like when the seek index became huge), there aren't
+ // any backwards packets, even if the total cache size is exceeded.
+ if (!earliest_stream)
+ break;
+
+ struct demux_stream *ds = earliest_stream;
+ struct demux_queue *queue = range->streams[ds->index];
+
+ bool non_kf_prune = queue->head && !queue->head->keyframe;
+ bool kf_was_pruned = false;
+
+ while (queue->head && queue->head != ds->reader_head) {
+ if (queue->head->keyframe) {
+ // If the cache is seekable, only delete until up the next
+ // keyframe. This is not always efficient, but ensures we
+ // prune all streams fairly.
+ // Also, if the first packet was _not_ a keyframe, we want it
+ // to remove all preceding non-keyframe packets first, before
+ // re-evaluating what to prune next.
+ if ((kf_was_pruned || non_kf_prune) && in->seekable_cache)
+ break;
+ kf_was_pruned = true;
+ }
+
+ remove_head_packet(queue);
+ }
+
+ // Need to update the seekable time range.
+ if (kf_was_pruned) {
+ assert(!queue->keyframe_first); // it was just deleted, supposedly
+
+ queue->keyframe_first = queue->head;
+ // (May happen if reader_head stopped pruning the range, and there's
+ // no next range.)
+ while (queue->keyframe_first && !queue->keyframe_first->keyframe)
+ queue->keyframe_first = queue->keyframe_first->next;
+
+ if (queue->seek_start != MP_NOPTS_VALUE)
+ queue->last_pruned = queue->seek_start;
+
+ double kf_min;
+ compute_keyframe_times(queue->keyframe_first, &kf_min, NULL);
+
+ bool update_range = true;
+
+ queue->seek_start = kf_min;
+
+ if (queue->seek_start != MP_NOPTS_VALUE) {
+ queue->seek_start += ds->sh->seek_preroll;
+
+ // Don't need to update if the new start is still before the
+ // range's start (or if the range was undefined anyway).
+ if (range->seek_start == MP_NOPTS_VALUE ||
+ queue->seek_start <= range->seek_start)
+ {
+ update_range = false;
+ }
+ }
+
+ if (update_range)
+ update_seek_ranges(range);
+ }
+
+ if (range != in->current_range && range->seek_start == MP_NOPTS_VALUE)
+ free_empty_cached_ranges(in);
+ }
+}
+
+static void execute_trackswitch(struct demux_internal *in)
+{
+ in->tracks_switched = false;
+
+ mp_mutex_unlock(&in->lock);
+
+ if (in->d_thread->desc->switched_tracks)
+ in->d_thread->desc->switched_tracks(in->d_thread);
+
+ mp_mutex_lock(&in->lock);
+}
+
+static void execute_seek(struct demux_internal *in)
+{
+ int flags = in->seek_flags;
+ double pts = in->seek_pts;
+ in->eof = false;
+ in->seeking = false;
+ in->seeking_in_progress = pts;
+ in->demux_ts = MP_NOPTS_VALUE;
+ in->low_level_seeks += 1;
+ in->after_seek = true;
+ in->after_seek_to_start =
+ !(flags & (SEEK_FORWARD | SEEK_FACTOR)) &&
+ pts <= in->d_thread->start_time;
+
+ for (int n = 0; n < in->num_streams; n++)
+ in->streams[n]->ds->queue->last_pos_fixup = -1;
+
+ if (in->recorder)
+ mp_recorder_mark_discontinuity(in->recorder);
+
+ mp_mutex_unlock(&in->lock);
+
+ MP_VERBOSE(in, "execute seek (to %f flags %d)\n", pts, flags);
+
+ if (in->d_thread->desc->seek)
+ in->d_thread->desc->seek(in->d_thread, pts, flags);
+
+ MP_VERBOSE(in, "seek done\n");
+
+ mp_mutex_lock(&in->lock);
+
+ in->seeking_in_progress = MP_NOPTS_VALUE;
+}
+
+static void update_opts(struct demuxer *demuxer)
+{
+ struct demux_opts *opts = demuxer->opts;
+ struct demux_internal *in = demuxer->in;
+
+ in->min_secs = opts->min_secs;
+ in->hyst_secs = opts->hyst_secs;
+ in->max_bytes = opts->max_bytes;
+ in->max_bytes_bw = opts->max_bytes_bw;
+
+ int seekable = opts->seekable_cache;
+ bool is_streaming = in->d_thread->is_streaming;
+ bool use_cache = is_streaming;
+ if (opts->enable_cache >= 0)
+ use_cache = opts->enable_cache == 1;
+
+ if (use_cache) {
+ in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
+ if (seekable < 0)
+ seekable = 1;
+ }
+ in->seekable_cache = seekable == 1;
+ in->using_network_cache_opts = is_streaming && use_cache;
+
+ if (!in->seekable_cache)
+ in->max_bytes_bw = 0;
+
+ if (!in->can_cache) {
+ in->seekable_cache = false;
+ in->min_secs = 0;
+ in->max_bytes = 1;
+ in->max_bytes_bw = 0;
+ in->using_network_cache_opts = false;
+ }
+
+ if (in->seekable_cache && opts->disk_cache && !in->cache) {
+ in->cache = demux_cache_create(in->global, in->log);
+ if (!in->cache)
+ MP_ERR(in, "Failed to create file cache.\n");
+ }
+
+ // The filename option really decides whether recording should be active.
+ // So if the filename changes, act upon it.
+ char *old = in->record_filename ? in->record_filename : "";
+ char *new = opts->record_file ? opts->record_file : "";
+ if (strcmp(old, new) != 0) {
+ if (in->recorder) {
+ MP_WARN(in, "Stopping recording.\n");
+ mp_recorder_destroy(in->recorder);
+ in->recorder = NULL;
+ }
+ talloc_free(in->record_filename);
+ in->record_filename = talloc_strdup(in, opts->record_file);
+ // Note: actual recording only starts once packets are read. It may be
+ // important to delay creating in->recorder to that point, because the
+ // demuxer might detect more streams until finding the first packet.
+ in->enable_recording = in->can_record;
+ }
+
+ // In case the cache was reduced in size.
+ prune_old_packets(in);
+
+ // In case the seekable cache was disabled.
+ free_empty_cached_ranges(in);
+}
+
+// Make demuxing progress. Return whether progress was made.
+static bool thread_work(struct demux_internal *in)
+{
+ if (m_config_cache_update(in->d_user->opts_cache))
+ update_opts(in->d_user);
+ if (in->tracks_switched) {
+ execute_trackswitch(in);
+ return true;
+ }
+ if (in->need_back_seek) {
+ perform_backward_seek(in);
+ return true;
+ }
+ if (in->back_any_need_recheck) {
+ check_backward_seek(in);
+ return true;
+ }
+ if (in->seeking) {
+ execute_seek(in);
+ return true;
+ }
+ if (read_packet(in))
+ return true; // read_packet unlocked, so recheck conditions
+ if (mp_time_ns() >= in->next_cache_update) {
+ update_cache(in);
+ return true;
+ }
+ return false;
+}
+
+static MP_THREAD_VOID demux_thread(void *pctx)
+{
+ struct demux_internal *in = pctx;
+ mp_thread_set_name("demux");
+ mp_mutex_lock(&in->lock);
+
+ stats_register_thread_cputime(in->stats, "thread");
+
+ while (!in->thread_terminate) {
+ if (thread_work(in))
+ continue;
+ mp_cond_signal(&in->wakeup);
+ mp_cond_timedwait_until(&in->wakeup, &in->lock, in->next_cache_update);
+ }
+
+ if (in->shutdown_async) {
+ mp_mutex_unlock(&in->lock);
+ demux_shutdown(in);
+ mp_mutex_lock(&in->lock);
+ in->shutdown_async = false;
+ if (in->wakeup_cb)
+ in->wakeup_cb(in->wakeup_cb_ctx);
+ }
+
+ stats_unregister_thread(in->stats, "thread");
+
+ mp_mutex_unlock(&in->lock);
+ MP_THREAD_RETURN();
+}
+
+// Low-level part of dequeueing a packet.
+static struct demux_packet *advance_reader_head(struct demux_stream *ds)
+{
+ struct demux_packet *pkt = ds->reader_head;
+ if (!pkt)
+ return NULL;
+
+ ds->reader_head = pkt->next;
+
+ ds->last_ret_pos = pkt->pos;
+ ds->last_ret_dts = pkt->dts;
+
+ return pkt;
+}
+
+// Return a newly allocated new packet. The pkt parameter may be either a
+// in-memory packet (then a new reference is made), or a reference to
+// packet in the disk cache (then the packet is read from disk).
+static struct demux_packet *read_packet_from_cache(struct demux_internal *in,
+ struct demux_packet *pkt)
+{
+ if (!pkt)
+ return NULL;
+
+ if (pkt->is_cached) {
+ assert(in->cache);
+ struct demux_packet *meta = pkt;
+ pkt = demux_cache_read(in->cache, pkt->cached_data.pos);
+ if (pkt) {
+ demux_packet_copy_attribs(pkt, meta);
+ } else {
+ MP_ERR(in, "Failed to retrieve packet from cache.\n");
+ }
+ } else {
+ // The returned packet is mutated etc. and will be owned by the user.
+ pkt = demux_copy_packet(pkt);
+ }
+
+ return pkt;
+}
+
+// Returns:
+// < 0: EOF was reached, *res is not set
+// == 0: no new packet yet, wait, *res is not set
+// > 0: new packet is moved to *res
+static int dequeue_packet(struct demux_stream *ds, double min_pts,
+ struct demux_packet **res)
+{
+ struct demux_internal *in = ds->in;
+
+ if (!ds->selected)
+ return -1;
+ if (in->blocked)
+ return 0;
+
+ if (ds->sh->attached_picture) {
+ ds->eof = true;
+ if (ds->attached_picture_added)
+ return -1;
+ ds->attached_picture_added = true;
+ struct demux_packet *pkt = demux_copy_packet(ds->sh->attached_picture);
+ MP_HANDLE_OOM(pkt);
+ pkt->stream = ds->sh->index;
+ *res = pkt;
+ return 1;
+ }
+
+ if (!in->reading && !in->eof) {
+ in->reading = true; // enable demuxer thread prefetching
+ mp_cond_signal(&in->wakeup);
+ }
+
+ ds->force_read_until = min_pts;
+
+ if (ds->back_resuming || ds->back_restarting) {
+ assert(in->back_demuxing);
+ return 0;
+ }
+
+ bool eof = !ds->reader_head && ds->eof;
+
+ if (in->back_demuxing) {
+ // Subtitles not supported => EOF.
+ if (!ds->eager)
+ return -1;
+
+ // Next keyframe (or EOF) was reached => step back.
+ if (ds->back_range_started && !ds->back_range_count &&
+ ((ds->reader_head && ds->reader_head->keyframe) || eof))
+ {
+ ds->back_restarting = true;
+ ds->back_restart_eof = false;
+ ds->back_restart_next = false;
+
+ find_backward_restart_pos(ds);
+
+ if (ds->back_restarting)
+ return 0;
+ }
+
+ eof = ds->back_range_count < 0;
+ }
+
+ ds->need_wakeup = !ds->reader_head;
+ if (!ds->reader_head || eof) {
+ if (!ds->eager) {
+ // Non-eager streams temporarily return EOF. If they returned 0,
+ // the reader would have to wait for new packets, which does not
+ // make sense due to the sparseness and passiveness of non-eager
+ // streams.
+ // Unless the min_pts feature is used: then EOF is only signaled
+ // if read-ahead went above min_pts.
+ if (!lazy_stream_needs_wait(ds))
+ ds->eof = eof = true;
+ }
+ return eof ? -1 : 0;
+ }
+
+ struct demux_packet *pkt = advance_reader_head(ds);
+ assert(pkt);
+ pkt = read_packet_from_cache(in, pkt);
+ if (!pkt)
+ return 0;
+
+ if (in->back_demuxing) {
+ if (pkt->keyframe) {
+ assert(ds->back_range_count > 0);
+ ds->back_range_count -= 1;
+ if (ds->back_range_preroll >= 0)
+ ds->back_range_preroll -= 1;
+ }
+
+ if (ds->back_range_preroll >= 0)
+ pkt->back_preroll = true;
+
+ if (!ds->back_range_started) {
+ pkt->back_restart = true;
+ ds->back_range_started = true;
+ }
+ }
+
+ double ts = MP_PTS_OR_DEF(pkt->dts, pkt->pts);
+ if (ts != MP_NOPTS_VALUE)
+ ds->base_ts = ts;
+
+ if (pkt->keyframe && ts != MP_NOPTS_VALUE) {
+ // Update bitrate - only at keyframe points, because we use the
+ // (possibly) reordered packet timestamps instead of realtime.
+ double d = ts - ds->last_br_ts;
+ if (ds->last_br_ts == MP_NOPTS_VALUE || d < 0) {
+ ds->bitrate = -1;
+ ds->last_br_ts = ts;
+ ds->last_br_bytes = 0;
+ } else if (d >= 0.5) { // a window of least 500ms for UI purposes
+ ds->bitrate = ds->last_br_bytes / d;
+ ds->last_br_ts = ts;
+ ds->last_br_bytes = 0;
+ }
+ }
+ ds->last_br_bytes += pkt->len;
+
+ // This implies this function is actually called from "the" user thread.
+ if (pkt->pos >= in->d_user->filepos)
+ in->d_user->filepos = pkt->pos;
+ in->d_user->filesize = in->stream_size;
+
+ pkt->pts = MP_ADD_PTS(pkt->pts, in->ts_offset);
+ pkt->dts = MP_ADD_PTS(pkt->dts, in->ts_offset);
+
+ if (pkt->segmented) {
+ pkt->start = MP_ADD_PTS(pkt->start, in->ts_offset);
+ pkt->end = MP_ADD_PTS(pkt->end, in->ts_offset);
+ }
+
+ prune_old_packets(in);
+ *res = pkt;
+ return 1;
+}
+
+// Poll the demuxer queue, and if there's a packet, return it. Otherwise, just
+// make the demuxer thread read packets for this stream, and if there's at
+// least one packet, call the wakeup callback.
+// This enables readahead if it wasn't yet (except for interleaved subtitles).
+// Returns:
+// < 0: EOF was reached, *out_pkt=NULL
+// == 0: no new packet yet, but maybe later, *out_pkt=NULL
+// > 0: new packet read, *out_pkt is set
+// Note: when reading interleaved subtitles, the demuxer won't try to forcibly
+// read ahead to get the next subtitle packet (as the next packet could be
+// minutes away). In this situation, this function will just return -1.
+int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
+{
+ return demux_read_packet_async_until(sh, MP_NOPTS_VALUE, out_pkt);
+}
+
+// Like demux_read_packet_async(). They are the same for min_pts==MP_NOPTS_VALUE.
+// If min_pts is set, and the stream is lazily read (eager=false, interleaved
+// subtitles), then return 0 until demuxing has reached min_pts, or the queue
+// overflowed, or EOF was reached, or a packet was read for this stream.
+int demux_read_packet_async_until(struct sh_stream *sh, double min_pts,
+ struct demux_packet **out_pkt)
+{
+ struct demux_stream *ds = sh ? sh->ds : NULL;
+ *out_pkt = NULL;
+ if (!ds)
+ return -1;
+ struct demux_internal *in = ds->in;
+
+ mp_mutex_lock(&in->lock);
+ int r = -1;
+ while (1) {
+ r = dequeue_packet(ds, min_pts, out_pkt);
+ if (in->threading || in->blocked || r != 0)
+ break;
+ // Needs to actually read packets until we got a packet or EOF.
+ thread_work(in);
+ }
+ mp_mutex_unlock(&in->lock);
+ return r;
+}
+
+// Read and return any packet we find. NULL means EOF.
+// Does not work with threading (don't call demux_start_thread()).
+struct demux_packet *demux_read_any_packet(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ assert(!in->threading); // doesn't work with threading
+ struct demux_packet *out_pkt = NULL;
+ bool read_more = true;
+ while (read_more && !in->blocked) {
+ bool all_eof = true;
+ for (int n = 0; n < in->num_streams; n++) {
+ int r = dequeue_packet(in->streams[n]->ds, MP_NOPTS_VALUE, &out_pkt);
+ if (r > 0)
+ goto done;
+ if (r == 0)
+ all_eof = false;
+ }
+ // retry after calling this
+ read_more = thread_work(in);
+ read_more &= !all_eof;
+ }
+done:
+ mp_mutex_unlock(&in->lock);
+ return out_pkt;
+}
+
+int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name)
+{
+ int i;
+
+ mp_info(log, "Available demuxers:\n");
+ mp_info(log, " demuxer: info:\n");
+ for (i = 0; demuxer_list[i]; i++) {
+ mp_info(log, "%10s %s\n",
+ demuxer_list[i]->name, demuxer_list[i]->desc);
+ }
+ mp_info(log, "\n");
+
+ return M_OPT_EXIT;
+}
+
+static const char *d_level(enum demux_check level)
+{
+ switch (level) {
+ case DEMUX_CHECK_FORCE: return "force";
+ case DEMUX_CHECK_UNSAFE: return "unsafe";
+ case DEMUX_CHECK_REQUEST:return "request";
+ case DEMUX_CHECK_NORMAL: return "normal";
+ }
+ MP_ASSERT_UNREACHABLE();
+}
+
+static int decode_float(char *str, float *out)
+{
+ char *rest;
+ float dec_val;
+
+ dec_val = strtod(str, &rest);
+ if (!rest || (rest == str) || !isfinite(dec_val))
+ return -1;
+
+ *out = dec_val;
+ return 0;
+}
+
+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(tags, tag);
+ if (!tag_val)
+ return -1;
+
+ if (decode_float(tag_val, &dec_val) < 0) {
+ mp_msg(log, MSGL_ERR, "Invalid replaygain value\n");
+ return -1;
+ }
+
+ *out = dec_val;
+ return 0;
+}
+
+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(tags, tag);
+ if (!tag_val)
+ return 0;
+
+ if (decode_float(tag_val, &dec_val) < 0 || dec_val <= 0.0)
+ return -1;
+
+ *out = dec_val;
+ return 0;
+}
+
+static struct replaygain_data *decode_rgain(struct mp_log *log,
+ struct mp_tags *tags)
+{
+ struct replaygain_data rg = {0};
+
+ // Set values in *rg, using track gain as a fallback for album gain if the
+ // latter is not present. This behavior matches that in demux/demux_lavf.c's
+ // export_replaygain; if you change this, please make equivalent changes
+ // there too.
+ if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 &&
+ decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0)
+ {
+ if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 ||
+ decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0)
+ {
+ // Album gain is undefined; fall back to track gain.
+ rg.album_gain = rg.track_gain;
+ rg.album_peak = rg.track_peak;
+ }
+ return talloc_dup(NULL, &rg);
+ }
+
+ 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;
+ return talloc_dup(NULL, &rg);
+ }
+
+ // The r128 replaygain tags declared in RFC 7845 for opus files. The tags
+ // are generated with EBU-R128, which does not use peak meters. And the
+ // values are stored as a Q7.8 fixed point number in dB.
+ if (decode_gain(log, tags, "R128_TRACK_GAIN", &rg.track_gain) >= 0) {
+ if (decode_gain(log, tags, "R128_ALBUM_GAIN", &rg.album_gain) < 0) {
+ // Album gain is undefined; fall back to track gain.
+ rg.album_gain = rg.track_gain;
+ }
+ rg.track_gain /= 256.;
+ rg.album_gain /= 256.;
+
+ // Add 5dB to compensate for the different reference levels between
+ // our reference of ReplayGain 2 (-18 LUFS) and EBU R128 (-23 LUFS).
+ rg.track_gain += 5.;
+ rg.album_gain += 5.;
+ return talloc_dup(NULL, &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);
+ }
+ }
+}
+
+// Copy some fields from src to dst (for initialization).
+static void demux_copy(struct demuxer *dst, struct demuxer *src)
+{
+ // 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->is_streaming = src->is_streaming;
+ dst->stream_origin = src->stream_origin;
+ dst->priv = src->priv;
+ dst->metadata = mp_tags_dup(dst, src->metadata);
+}
+
+// Update metadata after initialization. If sh==NULL, it's global metadata,
+// otherwise it's bound to the stream. If pts==NOPTS, use the highest known pts
+// in the stream. Caller retains ownership of tags ptr. Called locked.
+static void add_timed_metadata(struct demux_internal *in, struct mp_tags *tags,
+ struct sh_stream *sh, double pts)
+{
+ struct demux_cached_range *r = in->current_range;
+ if (!r)
+ return;
+
+ // We don't expect this, nor do we find it useful.
+ if (sh && sh != in->metadata_stream)
+ return;
+
+ if (pts == MP_NOPTS_VALUE) {
+ for (int n = 0; n < r->num_streams; n++)
+ pts = MP_PTS_MAX(pts, r->streams[n]->last_ts);
+
+ // Tends to happen when doing the initial icy update.
+ if (pts == MP_NOPTS_VALUE)
+ pts = in->d_thread->start_time;
+ }
+
+ struct timed_metadata *tm = talloc_zero(NULL, struct timed_metadata);
+ *tm = (struct timed_metadata){
+ .pts = pts,
+ .tags = mp_tags_dup(tm, tags),
+ .from_stream = !!sh,
+ };
+ MP_TARRAY_APPEND(r, r->metadata, r->num_metadata, tm);
+}
+
+// This is called by demuxer implementations if sh->tags changed. Note that
+// sh->tags itself is never actually changed (it's immutable, because sh->tags
+// can be accessed by the playback thread, and there is no synchronization).
+// pts is the time at/after which the metadata becomes effective. You're
+// supposed to call this ordered by time, and only while a packet is being
+// read.
+// Ownership of tags goes to the function.
+void demux_stream_tags_changed(struct demuxer *demuxer, struct sh_stream *sh,
+ struct mp_tags *tags, double pts)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_thread);
+ struct demux_stream *ds = sh ? sh->ds : NULL;
+ assert(!sh || ds); // stream must have been added
+
+ mp_mutex_lock(&in->lock);
+
+ if (pts == MP_NOPTS_VALUE) {
+ MP_WARN(in, "Discarding timed metadata without timestamp.\n");
+ } else {
+ add_timed_metadata(in, tags, sh, pts);
+ }
+ talloc_free(tags);
+
+ mp_mutex_unlock(&in->lock);
+}
+
+// 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;
+
+ mp_mutex_lock(&in->lock);
+ add_timed_metadata(in, demuxer->metadata, NULL, MP_NOPTS_VALUE);
+ mp_mutex_unlock(&in->lock);
+}
+
+// Called locked, with user demuxer.
+static void update_final_metadata(demuxer_t *demuxer, struct timed_metadata *tm)
+{
+ assert(demuxer == demuxer->in->d_user);
+ struct demux_internal *in = demuxer->in;
+
+ struct mp_tags *dyn_tags = NULL;
+
+ // Often useful for 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).
+ int astreams = 0;
+ int astream_id = -1;
+ int vstreams = 0;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct sh_stream *sh = in->streams[n];
+ if (sh->type == STREAM_VIDEO && !sh->attached_picture)
+ vstreams += 1;
+ if (sh->type == STREAM_AUDIO) {
+ astreams += 1;
+ astream_id = n;
+ }
+ }
+
+ // Use the metadata_stream tags only if this really seems to be an audio-
+ // only stream. Otherwise it will happen too often that "uninteresting"
+ // stream metadata will trash the actual file tags.
+ if (vstreams == 0 && astreams == 1 &&
+ in->streams[astream_id] == in->metadata_stream)
+ {
+ dyn_tags = in->metadata_stream->tags;
+ if (tm && tm->from_stream)
+ dyn_tags = tm->tags;
+ }
+
+ // Global metadata updates.
+ if (tm && !tm->from_stream)
+ dyn_tags = tm->tags;
+
+ if (dyn_tags)
+ mp_tags_merge(demuxer->metadata, dyn_tags);
+}
+
+static struct timed_metadata *lookup_timed_metadata(struct demux_internal *in,
+ double pts)
+{
+ struct demux_cached_range *r = in->current_range;
+
+ if (!r || !r->num_metadata || pts == MP_NOPTS_VALUE)
+ return NULL;
+
+ int start = 1;
+ int i = in->cached_metadata_index;
+ if (i >= 0 && i < r->num_metadata && r->metadata[i]->pts <= pts)
+ start = i + 1;
+
+ in->cached_metadata_index = r->num_metadata - 1;
+ for (int n = start; n < r->num_metadata; n++) {
+ if (r->metadata[n]->pts >= pts) {
+ in->cached_metadata_index = n - 1;
+ break;
+ }
+ }
+
+ return r->metadata[in->cached_metadata_index];
+}
+
+// Called by the user thread (i.e. player) to update metadata and other things
+// from the demuxer thread.
+// The pts parameter is the current playback position.
+void demux_update(demuxer_t *demuxer, double pts)
+{
+ assert(demuxer == demuxer->in->d_user);
+ struct demux_internal *in = demuxer->in;
+
+ mp_mutex_lock(&in->lock);
+
+ if (!in->threading)
+ update_cache(in);
+
+ // This implies this function is actually called from "the" user thread.
+ in->d_user->filesize = in->stream_size;
+
+ pts = MP_ADD_PTS(pts, -in->ts_offset);
+
+ struct timed_metadata *prev = lookup_timed_metadata(in, in->last_playback_pts);
+ struct timed_metadata *cur = lookup_timed_metadata(in, pts);
+ if (prev != cur || in->force_metadata_update) {
+ in->force_metadata_update = false;
+ update_final_metadata(demuxer, cur);
+ demuxer->events |= DEMUX_EVENT_METADATA;
+ }
+
+ in->last_playback_pts = pts;
+
+ demuxer->events |= in->events;
+ in->events = 0;
+ if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
+ demux_update_replaygain(demuxer);
+ if (demuxer->events & DEMUX_EVENT_DURATION)
+ demuxer->duration = in->duration;
+
+ mp_mutex_unlock(&in->lock);
+}
+
+static void demux_init_cuesheet(struct demuxer *demuxer)
+{
+ char *cue = mp_tags_get_str(demuxer->metadata, "cuesheet");
+ if (cue && !demuxer->num_chapters) {
+ struct cue_file *f = mp_parse_cue(bstr0(cue));
+ if (f) {
+ if (mp_check_embedded_cue(f) < 0) {
+ MP_WARN(demuxer, "Embedded cue sheet references more than one file. "
+ "Ignoring it.\n");
+ } else {
+ for (int n = 0; n < f->num_tracks; n++) {
+ struct cue_track *t = &f->tracks[n];
+ int idx = demuxer_add_chapter(demuxer, "", t->start, -1);
+ mp_tags_merge(demuxer->chapters[idx].metadata, t->tags);
+ }
+ }
+ }
+ talloc_free(f);
+ }
+}
+
+// A demuxer can use this during opening if all data was read from the stream.
+// Calling this after opening was completed is not allowed. Also, if opening
+// failed, this must not be called (or trying another demuxer would fail).
+// Useful so that e.g. subtitles don't keep the file or socket open.
+// If there's ever the situation where we can't allow the demuxer to close
+// the stream, this function could ignore the request.
+void demux_close_stream(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(!in->threading && demuxer == in->d_thread);
+
+ if (!demuxer->stream || !in->owns_stream)
+ return;
+
+ MP_VERBOSE(demuxer, "demuxer read all data; closing stream\n");
+ free_stream(demuxer->stream);
+ demuxer->stream = NULL;
+ in->d_user->stream = NULL;
+}
+
+static void demux_init_ccs(struct demuxer *demuxer, struct demux_opts *opts)
+{
+ struct demux_internal *in = demuxer->in;
+ if (!opts->create_ccs)
+ return;
+ mp_mutex_lock(&in->lock);
+ for (int n = 0; n < in->num_streams; n++) {
+ struct sh_stream *sh = in->streams[n];
+ if (sh->type == STREAM_VIDEO && !sh->attached_picture)
+ demuxer_get_cc_track_locked(sh);
+ }
+ mp_mutex_unlock(&in->lock);
+}
+
+// Return whether "heavy" caching on this stream is enabled. By default, this
+// corresponds to whether the source stream is considered in the network. The
+// only effect should be adjusting display behavior (of cache stats etc.), and
+// possibly switching between which set of options influence cache settings.
+bool demux_is_network_cached(demuxer_t *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ bool r = in->using_network_cache_opts;
+ mp_mutex_unlock(&in->lock);
+ return r;
+}
+
+struct parent_stream_info {
+ bool seekable;
+ bool is_network;
+ bool is_streaming;
+ int stream_origin;
+ struct mp_cancel *cancel;
+ char *filename;
+};
+
+static struct demuxer *open_given_type(struct mpv_global *global,
+ struct mp_log *log,
+ const struct demuxer_desc *desc,
+ struct stream *stream,
+ struct parent_stream_info *sinfo,
+ struct demuxer_params *params,
+ enum demux_check check)
+{
+ if (mp_cancel_test(sinfo->cancel))
+ return NULL;
+
+ struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
+ struct m_config_cache *opts_cache =
+ m_config_cache_alloc(demuxer, global, &demux_conf);
+ struct demux_opts *opts = opts_cache->opts;
+ *demuxer = (struct demuxer) {
+ .desc = desc,
+ .stream = stream,
+ .cancel = sinfo->cancel,
+ .seekable = sinfo->seekable,
+ .filepos = -1,
+ .global = global,
+ .log = mp_log_new(demuxer, log, desc->name),
+ .glog = log,
+ .filename = talloc_strdup(demuxer, sinfo->filename),
+ .is_network = sinfo->is_network,
+ .is_streaming = sinfo->is_streaming,
+ .stream_origin = sinfo->stream_origin,
+ .access_references = opts->access_references,
+ .opts = opts,
+ .opts_cache = opts_cache,
+ .events = DEMUX_EVENT_ALL,
+ .duration = -1,
+ };
+
+ struct demux_internal *in = demuxer->in = talloc_ptrtype(demuxer, in);
+ *in = (struct demux_internal){
+ .global = global,
+ .log = demuxer->log,
+ .stats = stats_ctx_create(in, global, "demuxer"),
+ .can_cache = params && params->is_top_level,
+ .can_record = params && params->stream_record,
+ .d_thread = talloc(demuxer, struct demuxer),
+ .d_user = demuxer,
+ .after_seek = true, // (assumed identical to initial demuxer state)
+ .after_seek_to_start = true,
+ .highest_av_pts = MP_NOPTS_VALUE,
+ .seeking_in_progress = MP_NOPTS_VALUE,
+ .demux_ts = MP_NOPTS_VALUE,
+ .owns_stream = !params->external_stream,
+ };
+ mp_mutex_init(&in->lock);
+ mp_cond_init(&in->wakeup);
+
+ *in->d_thread = *demuxer;
+
+ in->d_thread->metadata = talloc_zero(in->d_thread, struct mp_tags);
+
+ mp_dbg(log, "Trying demuxer: %s (force-level: %s)\n",
+ desc->name, d_level(check));
+
+ if (stream)
+ stream_seek(stream, 0);
+
+ in->d_thread->params = params; // temporary during open()
+ int ret = demuxer->desc->open(in->d_thread, check);
+ if (ret >= 0) {
+ in->d_thread->params = NULL;
+ if (in->d_thread->filetype)
+ mp_verbose(log, "Detected file format: %s (%s)\n",
+ in->d_thread->filetype, desc->desc);
+ else
+ mp_verbose(log, "Detected file format: %s\n", desc->desc);
+ if (!in->d_thread->seekable)
+ mp_verbose(log, "Stream is not seekable.\n");
+ if (!in->d_thread->seekable && opts->force_seekable) {
+ mp_warn(log, "Not seekable, but enabling seeking on user request.\n");
+ in->d_thread->seekable = true;
+ in->d_thread->partially_seekable = true;
+ }
+ demux_init_cuesheet(in->d_thread);
+ demux_init_ccs(demuxer, opts);
+ demux_convert_tags_charset(in->d_thread);
+ demux_copy(in->d_user, in->d_thread);
+ in->duration = in->d_thread->duration;
+ demuxer_sort_chapters(demuxer);
+ in->events = DEMUX_EVENT_ALL;
+
+ struct demuxer *sub = NULL;
+ if (!(params && params->disable_timeline)) {
+ struct timeline *tl = timeline_load(global, log, demuxer);
+ if (tl) {
+ struct demuxer_params params2 = {0};
+ params2.timeline = tl;
+ params2.is_top_level = params && params->is_top_level;
+ params2.stream_record = params && params->stream_record;
+ sub =
+ open_given_type(global, log, &demuxer_desc_timeline,
+ NULL, sinfo, &params2, DEMUX_CHECK_FORCE);
+ if (sub) {
+ in->can_cache = false;
+ in->can_record = false;
+ } else {
+ timeline_destroy(tl);
+ }
+ }
+ }
+
+ switch_to_fresh_cache_range(in);
+
+ update_opts(demuxer);
+
+ demux_update(demuxer, MP_NOPTS_VALUE);
+
+ demuxer = sub ? sub : demuxer;
+ return demuxer;
+ }
+
+ demuxer->stream = NULL;
+ demux_free(demuxer);
+ return NULL;
+}
+
+static const int d_normal[] = {DEMUX_CHECK_NORMAL, DEMUX_CHECK_UNSAFE, -1};
+static const int d_request[] = {DEMUX_CHECK_REQUEST, -1};
+static const int d_force[] = {DEMUX_CHECK_FORCE, -1};
+
+// params can be NULL
+// This may free the stream parameter on success.
+static struct demuxer *demux_open(struct stream *stream,
+ struct mp_cancel *cancel,
+ struct demuxer_params *params,
+ struct mpv_global *global)
+{
+ const int *check_levels = d_normal;
+ const struct demuxer_desc *check_desc = NULL;
+ struct mp_log *log = mp_log_new(NULL, global->log, "!demux");
+ struct demuxer *demuxer = NULL;
+ char *force_format = params ? params->force_format : NULL;
+
+ struct parent_stream_info sinfo = {
+ .seekable = stream->seekable,
+ .is_network = stream->is_network,
+ .is_streaming = stream->streaming,
+ .stream_origin = stream->stream_origin,
+ .cancel = cancel,
+ .filename = talloc_strdup(NULL, stream->url),
+ };
+
+ if (!force_format)
+ force_format = stream->demuxer;
+
+ if (force_format && force_format[0] && !stream->is_directory) {
+ check_levels = d_request;
+ if (force_format[0] == '+') {
+ force_format += 1;
+ check_levels = d_force;
+ }
+ for (int n = 0; demuxer_list[n]; n++) {
+ if (strcmp(demuxer_list[n]->name, force_format) == 0) {
+ check_desc = demuxer_list[n];
+ break;
+ }
+ }
+ if (!check_desc) {
+ mp_err(log, "Demuxer %s does not exist.\n", force_format);
+ goto done;
+ }
+ }
+
+ // Test demuxers from first to last, one pass for each check_levels[] entry
+ for (int pass = 0; check_levels[pass] != -1; pass++) {
+ enum demux_check level = check_levels[pass];
+ mp_verbose(log, "Trying demuxers for level=%s.\n", d_level(level));
+ for (int n = 0; demuxer_list[n]; n++) {
+ const struct demuxer_desc *desc = demuxer_list[n];
+ if (!check_desc || desc == check_desc) {
+ demuxer = open_given_type(global, log, desc, stream, &sinfo,
+ params, level);
+ if (demuxer) {
+ talloc_steal(demuxer, log);
+ log = NULL;
+ goto done;
+ }
+ }
+ }
+ }
+
+done:
+ talloc_free(sinfo.filename);
+ talloc_free(log);
+ return demuxer;
+}
+
+static struct stream *create_webshit_concat_stream(struct mpv_global *global,
+ struct mp_cancel *c,
+ bstr init, struct stream *real)
+{
+ struct stream *mem = stream_memory_open(global, init.start, init.len);
+ assert(mem);
+
+ struct stream *streams[2] = {mem, real};
+ struct stream *concat = stream_concat_open(global, c, streams, 2);
+ if (!concat) {
+ free_stream(mem);
+ free_stream(real);
+ }
+ return concat;
+}
+
+// Convenience function: open the stream, enable the cache (according to params
+// and global opts.), open the demuxer.
+// Also for some reason may close the opened stream if it's not needed.
+// demuxer->cancel is not the cancel parameter, but is its own object that will
+// be a slave (mp_cancel_set_parent()) to provided cancel object.
+// demuxer->cancel is automatically freed.
+struct demuxer *demux_open_url(const char *url,
+ struct demuxer_params *params,
+ struct mp_cancel *cancel,
+ struct mpv_global *global)
+{
+ if (!params)
+ return NULL;
+ struct mp_cancel *priv_cancel = mp_cancel_new(NULL);
+ if (cancel)
+ mp_cancel_set_parent(priv_cancel, cancel);
+ struct stream *s = params->external_stream;
+ if (!s) {
+ s = stream_create(url, STREAM_READ | params->stream_flags,
+ priv_cancel, global);
+ if (s && params->init_fragment.len) {
+ s = create_webshit_concat_stream(global, priv_cancel,
+ params->init_fragment, s);
+ }
+ }
+ if (!s) {
+ talloc_free(priv_cancel);
+ return NULL;
+ }
+ struct demuxer *d = demux_open(s, priv_cancel, params, global);
+ if (d) {
+ talloc_steal(d->in, priv_cancel);
+ assert(d->cancel);
+ } else {
+ params->demuxer_failed = true;
+ if (!params->external_stream)
+ free_stream(s);
+ talloc_free(priv_cancel);
+ }
+ return d;
+}
+
+// clear the packet queues
+void demux_flush(demuxer_t *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ mp_mutex_lock(&in->lock);
+ clear_reader_state(in, true);
+ for (int n = 0; n < in->num_ranges; n++)
+ clear_cached_range(in, in->ranges[n]);
+ free_empty_cached_ranges(in);
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ ds->refreshing = false;
+ ds->eof = false;
+ }
+ in->eof = false;
+ in->seeking = false;
+ mp_mutex_unlock(&in->lock);
+}
+
+// Does some (but not all) things for switching to another range.
+static void switch_current_range(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ struct demux_cached_range *old = in->current_range;
+ assert(old != range);
+
+ set_current_range(in, range);
+
+ if (old) {
+ // Remove packets which can't be used when seeking back to the range.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_queue *queue = old->streams[n];
+
+ // Remove all packets which cannot be involved in seeking.
+ while (queue->head && !queue->head->keyframe)
+ remove_head_packet(queue);
+ }
+
+ // Exclude weird corner cases that break resuming.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ // This is needed to resume or join the range at all.
+ if (ds->selected && !(ds->global_correct_dts ||
+ ds->global_correct_pos))
+ {
+ MP_VERBOSE(in, "discarding unseekable range due to stream %d\n", n);
+ clear_cached_range(in, old);
+ break;
+ }
+ }
+ }
+
+ // Set up reading from new range (as well as writing to it).
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ ds->queue = range->streams[n];
+ ds->refreshing = false;
+ ds->eof = false;
+ }
+
+ // No point in keeping any junk (especially if old current_range is empty).
+ free_empty_cached_ranges(in);
+
+ // The change detection doesn't work across ranges.
+ in->force_metadata_update = true;
+}
+
+// Search for the entry with the highest index with entry.pts <= pts true.
+static struct demux_packet *search_index(struct demux_queue *queue, double pts)
+{
+ size_t a = 0;
+ size_t b = queue->num_index;
+
+ while (a < b) {
+ size_t m = a + (b - a) / 2;
+ struct index_entry *e = &QUEUE_INDEX_ENTRY(queue, m);
+
+ bool m_ok = e->pts <= pts;
+
+ if (a + 1 == b)
+ return m_ok ? e->pkt : NULL;
+
+ if (m_ok) {
+ a = m;
+ } else {
+ b = m;
+ }
+ }
+
+ return NULL;
+}
+
+static struct demux_packet *find_seek_target(struct demux_queue *queue,
+ double pts, int flags)
+{
+ pts -= queue->ds->sh->seek_preroll;
+
+ struct demux_packet *start = search_index(queue, pts);
+ if (!start)
+ start = queue->head;
+
+ struct demux_packet *target = NULL;
+ struct demux_packet *next = NULL;
+ for (struct demux_packet *dp = start; dp; dp = next) {
+ next = dp->next;
+ if (!dp->keyframe)
+ continue;
+
+ double range_pts;
+ next = compute_keyframe_times(dp, &range_pts, NULL);
+
+ if (range_pts == MP_NOPTS_VALUE)
+ continue;
+
+ if (flags & SEEK_FORWARD) {
+ // Stop on the first packet that is >= pts.
+ if (target)
+ break;
+ if (range_pts < pts)
+ continue;
+ } else {
+ // Stop before the first packet that is > pts.
+ // This still returns a packet with > pts if there's no better one.
+ if (target && range_pts > pts)
+ break;
+ }
+
+ target = dp;
+ }
+
+ return target;
+}
+
+// Return a cache range for the given pts/flags, or NULL if none available.
+// must be called locked
+static struct demux_cached_range *find_cache_seek_range(struct demux_internal *in,
+ double pts, int flags)
+{
+ // Note about queued low level seeks: in->seeking can be true here, and it
+ // might come from a previous resume seek to the current range. If we end
+ // up seeking into the current range (i.e. just changing time offset), the
+ // seek needs to continue. Otherwise, we override the queued seek anyway.
+ if ((flags & SEEK_FACTOR) || !in->seekable_cache)
+ return NULL;
+
+ struct demux_cached_range *res = NULL;
+
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *r = in->ranges[n];
+ if (r->seek_start != MP_NOPTS_VALUE) {
+ MP_VERBOSE(in, "cached range %d: %f <-> %f (bof=%d, eof=%d)\n",
+ n, r->seek_start, r->seek_end, r->is_bof, r->is_eof);
+
+ if ((pts >= r->seek_start || r->is_bof) &&
+ (pts <= r->seek_end || r->is_eof))
+ {
+ MP_VERBOSE(in, "...using this range for in-cache seek.\n");
+ res = r;
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+// Adjust the seek target to the found video key frames. Otherwise the
+// video will undershoot the seek target, while audio will be closer to it.
+// The player frontend will play the additional video without audio, so
+// you get silent audio for the amount of "undershoot". Adjusting the seek
+// target will make the audio seek to the video target or before.
+// (If hr-seeks are used, it's better to skip this, as it would only mean
+// that more audio data than necessary would have to be decoded.)
+static void adjust_cache_seek_target(struct demux_internal *in,
+ struct demux_cached_range *range,
+ double *pts, int *flags)
+{
+ if (*flags & SEEK_HR)
+ return;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ struct demux_queue *queue = range->streams[n];
+ if (ds->selected && ds->type == STREAM_VIDEO) {
+ struct demux_packet *target = find_seek_target(queue, *pts, *flags);
+ if (target) {
+ double target_pts;
+ compute_keyframe_times(target, &target_pts, NULL);
+ if (target_pts != MP_NOPTS_VALUE) {
+ MP_VERBOSE(in, "adjust seek target %f -> %f\n",
+ *pts, target_pts);
+ // (We assume the find_seek_target() call will return
+ // the same target for the video stream.)
+ *pts = target_pts;
+ *flags &= ~SEEK_FORWARD;
+ }
+ }
+ break;
+ }
+ }
+}
+
+// must be called locked
+// range must be non-NULL and from find_cache_seek_range() using the same pts
+// and flags, before any other changes to the cached state
+static void execute_cache_seek(struct demux_internal *in,
+ struct demux_cached_range *range,
+ double pts, int flags)
+{
+ adjust_cache_seek_target(in, range, &pts, &flags);
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ struct demux_queue *queue = range->streams[n];
+
+ struct demux_packet *target = find_seek_target(queue, pts, flags);
+ ds->reader_head = target;
+ ds->skip_to_keyframe = !target;
+ if (ds->reader_head)
+ ds->base_ts = MP_PTS_OR_DEF(ds->reader_head->pts, ds->reader_head->dts);
+
+ MP_VERBOSE(in, "seeking stream %d (%s) to ",
+ n, stream_type_name(ds->type));
+
+ if (target) {
+ MP_VERBOSE(in, "packet %f/%f\n", target->pts, target->dts);
+ } else {
+ MP_VERBOSE(in, "nothing\n");
+ }
+ }
+
+ // If we seek to another range, we want to seek the low level demuxer to
+ // there as well, because reader and demuxer queue must be the same.
+ if (in->current_range != range) {
+ switch_current_range(in, range);
+
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = range->seek_end - 1.0;
+
+ // When new packets are being appended, they could overlap with the old
+ // range due to demuxer seek imprecisions, or because the queue contains
+ // packets past the seek target but before the next seek target. Don't
+ // append them twice, instead skip them until new packets are found.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ ds->refreshing = ds->selected;
+ }
+
+ MP_VERBOSE(in, "resuming demuxer to end of cached range\n");
+ }
+}
+
+// Create a new blank cache range, and backup the old one. If the seekable
+// demuxer cache is disabled, merely reset the current range to a blank state.
+static void switch_to_fresh_cache_range(struct demux_internal *in)
+{
+ if (!in->seekable_cache && in->current_range) {
+ clear_cached_range(in, in->current_range);
+ return;
+ }
+
+ struct demux_cached_range *range = talloc_ptrtype(NULL, range);
+ *range = (struct demux_cached_range){
+ .seek_start = MP_NOPTS_VALUE,
+ .seek_end = MP_NOPTS_VALUE,
+ };
+ MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, range);
+ add_missing_streams(in, range);
+
+ switch_current_range(in, range);
+}
+
+int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ mp_mutex_lock(&in->lock);
+
+ if (!(flags & SEEK_FACTOR))
+ seek_pts = MP_ADD_PTS(seek_pts, -in->ts_offset);
+
+ int res = queue_seek(in, seek_pts, flags, true);
+
+ mp_cond_signal(&in->wakeup);
+ mp_mutex_unlock(&in->lock);
+
+ return res;
+}
+
+static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
+ bool clear_back_state)
+{
+ if (seek_pts == MP_NOPTS_VALUE)
+ return false;
+
+ MP_VERBOSE(in, "queuing seek to %f%s\n", seek_pts,
+ in->seeking ? " (cascade)" : "");
+
+ bool require_cache = flags & SEEK_CACHED;
+ flags &= ~(unsigned)SEEK_CACHED;
+
+ bool set_backwards = flags & SEEK_SATAN;
+ flags &= ~(unsigned)SEEK_SATAN;
+
+ bool force_seek = flags & SEEK_FORCE;
+ flags &= ~(unsigned)SEEK_FORCE;
+
+ bool block = flags & SEEK_BLOCK;
+ flags &= ~(unsigned)SEEK_BLOCK;
+
+ struct demux_cached_range *cache_target =
+ find_cache_seek_range(in, seek_pts, flags);
+
+ if (!cache_target) {
+ if (require_cache) {
+ MP_VERBOSE(in, "Cached seek not possible.\n");
+ return false;
+ }
+ if (!in->d_thread->seekable && !force_seek) {
+ MP_WARN(in, "Cannot seek in this file.\n");
+ return false;
+ }
+ }
+
+ in->eof = false;
+ in->reading = false;
+ in->back_demuxing = set_backwards;
+
+ clear_reader_state(in, clear_back_state);
+
+ in->blocked = block;
+
+ if (cache_target) {
+ execute_cache_seek(in, cache_target, seek_pts, flags);
+ } else {
+ switch_to_fresh_cache_range(in);
+
+ in->seeking = true;
+ in->seek_flags = flags;
+ in->seek_pts = seek_pts;
+ }
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ if (in->back_demuxing) {
+ if (ds->back_seek_pos == MP_NOPTS_VALUE)
+ ds->back_seek_pos = seek_pts;
+ // Process possibly cached packets.
+ back_demux_see_packets(in->streams[n]->ds);
+ }
+
+ wakeup_ds(ds);
+ }
+
+ if (!in->threading && in->seeking)
+ execute_seek(in);
+
+ return true;
+}
+
+struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
+ enum stream_type t, int id)
+{
+ if (id < 0)
+ return NULL;
+ int num = demux_get_num_stream(d);
+ for (int n = 0; n < num; n++) {
+ struct sh_stream *s = demux_get_stream(d, n);
+ if (s->type == t && s->demuxer_id == id)
+ return s;
+ }
+ return NULL;
+}
+
+// An obscure mechanism to get stream switching to be executed "faster" (as
+// perceived by the user), by making the stream return packets from the
+// current position
+// 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 initiate_refresh_seek(struct demux_internal *in,
+ struct demux_stream *stream,
+ double start_ts)
+{
+ struct demuxer *demux = in->d_thread;
+ bool seekable = demux->desc->seek && demux->seekable &&
+ !demux->partially_seekable;
+
+ 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);
+
+ // If there were no other streams selected, we can use a normal seek.
+ normal_seek &= stream == ds;
+
+ refresh_possible &= ds->queue->correct_dts || ds->queue->correct_pos;
+ }
+
+ if (start_ts == MP_NOPTS_VALUE || !seekable)
+ return;
+
+ if (!normal_seek) {
+ if (!refresh_possible) {
+ MP_VERBOSE(in, "can't issue refresh seek\n");
+ return;
+ }
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ bool correct_pos = ds->queue->correct_pos;
+ bool correct_dts = ds->queue->correct_dts;
+
+ // We need to re-read all packets anyway, so discard the buffered
+ // data. (In theory, we could keep the packets, and be able to use
+ // it for seeking if partially read streams are deselected again,
+ // but this causes other problems like queue overflows when
+ // selecting a new stream.)
+ ds_clear_reader_queue_state(ds);
+ clear_queue(ds->queue);
+
+ // Streams which didn't have any packets yet will return all packets,
+ // other streams return packets only starting from the last position.
+ if (ds->selected && (ds->last_ret_pos != -1 ||
+ ds->last_ret_dts != MP_NOPTS_VALUE))
+ {
+ ds->refreshing = true;
+ ds->queue->correct_dts = correct_dts;
+ ds->queue->correct_pos = correct_pos;
+ ds->queue->last_pos = ds->last_ret_pos;
+ ds->queue->last_dts = ds->last_ret_dts;
+ }
+
+ update_seek_ranges(in->current_range);
+ }
+
+ start_ts -= 1.0; // small offset to get correct overlap
+ }
+
+ MP_VERBOSE(in, "refresh seek to %f\n", start_ts);
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = start_ts;
+}
+
+// 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,
+ double ref_pts, bool selected)
+{
+ struct demux_internal *in = demuxer->in;
+ struct demux_stream *ds = stream->ds;
+ mp_mutex_lock(&in->lock);
+ ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
+ // don't flush buffers if stream is already selected / unselected
+ if (ds->selected != selected) {
+ MP_VERBOSE(in, "%sselect track %d\n", selected ? "" : "de", stream->index);
+ ds->selected = selected;
+ update_stream_selection_state(in, ds);
+ in->tracks_switched = true;
+ if (ds->selected) {
+ if (in->back_demuxing)
+ ds->back_seek_pos = ref_pts;
+ if (!in->after_seek)
+ initiate_refresh_seek(in, ds, ref_pts);
+ }
+ if (in->threading) {
+ mp_cond_signal(&in->wakeup);
+ } else {
+ execute_trackswitch(in);
+ }
+ }
+ mp_mutex_unlock(&in->lock);
+}
+
+// Execute a refresh seek on the given stream.
+// ref_pts has the same meaning as with demuxer_select_track()
+void demuxer_refresh_track(struct demuxer *demuxer, struct sh_stream *stream,
+ double ref_pts)
+{
+ struct demux_internal *in = demuxer->in;
+ struct demux_stream *ds = stream->ds;
+ mp_mutex_lock(&in->lock);
+ ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
+ if (ds->selected) {
+ MP_VERBOSE(in, "refresh track %d\n", stream->index);
+ update_stream_selection_state(in, ds);
+ if (in->back_demuxing)
+ ds->back_seek_pos = ref_pts;
+ if (!in->after_seek)
+ initiate_refresh_seek(in, ds, ref_pts);
+ }
+ mp_mutex_unlock(&in->lock);
+}
+
+// This is for demuxer implementations only. demuxer_select_track() sets the
+// logical state, while this function returns the actual state (in case the
+// demuxer attempts to cache even unselected packets for track switching - this
+// will potentially be done in the future).
+bool demux_stream_is_selected(struct sh_stream *stream)
+{
+ if (!stream)
+ return false;
+ bool r = false;
+ mp_mutex_lock(&stream->ds->in->lock);
+ r = stream->ds->selected;
+ mp_mutex_unlock(&stream->ds->in->lock);
+ return r;
+}
+
+void demux_set_stream_wakeup_cb(struct sh_stream *sh,
+ void (*cb)(void *ctx), void *ctx)
+{
+ mp_mutex_lock(&sh->ds->in->lock);
+ sh->ds->wakeup_cb = cb;
+ sh->ds->wakeup_cb_ctx = ctx;
+ sh->ds->need_wakeup = true;
+ mp_mutex_unlock(&sh->ds->in->lock);
+}
+
+int demuxer_add_attachment(demuxer_t *demuxer, char *name, char *type,
+ void *data, size_t data_size)
+{
+ if (!(demuxer->num_attachments % 32))
+ demuxer->attachments = talloc_realloc(demuxer, demuxer->attachments,
+ struct demux_attachment,
+ demuxer->num_attachments + 32);
+
+ struct demux_attachment *att = &demuxer->attachments[demuxer->num_attachments];
+ att->name = talloc_strdup(demuxer->attachments, name);
+ att->type = talloc_strdup(demuxer->attachments, type);
+ att->data = talloc_memdup(demuxer->attachments, data, data_size);
+ att->data_size = data_size;
+
+ return demuxer->num_attachments++;
+}
+
+static int chapter_compare(const void *p1, const void *p2)
+{
+ struct demux_chapter *c1 = (void *)p1;
+ struct demux_chapter *c2 = (void *)p2;
+
+ if (c1->pts > c2->pts)
+ return 1;
+ else if (c1->pts < c2->pts)
+ return -1;
+ return c1->original_index > c2->original_index ? 1 :-1; // never equal
+}
+
+static void demuxer_sort_chapters(demuxer_t *demuxer)
+{
+ if (demuxer->num_chapters) {
+ qsort(demuxer->chapters, demuxer->num_chapters,
+ sizeof(struct demux_chapter), chapter_compare);
+ }
+}
+
+int demuxer_add_chapter(demuxer_t *demuxer, char *name,
+ double pts, uint64_t demuxer_id)
+{
+ struct demux_chapter new = {
+ .original_index = demuxer->num_chapters,
+ .pts = pts,
+ .metadata = talloc_zero(demuxer, struct mp_tags),
+ .demuxer_id = demuxer_id,
+ };
+ mp_tags_set_str(new.metadata, "TITLE", name);
+ MP_TARRAY_APPEND(demuxer, demuxer->chapters, demuxer->num_chapters, new);
+ return demuxer->num_chapters - 1;
+}
+
+// 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);
+
+ mp_mutex_lock(&in->lock);
+ in->blocked = block;
+ for (int n = 0; n < in->num_streams; n++) {
+ in->streams[n]->ds->need_wakeup = true;
+ wakeup_ds(in->streams[n]->ds);
+ }
+ mp_cond_signal(&in->wakeup);
+ mp_mutex_unlock(&in->lock);
+}
+
+static void update_bytes_read(struct demux_internal *in)
+{
+ struct demuxer *demuxer = in->d_thread;
+
+ int64_t new = in->slave_unbuffered_read_bytes;
+ in->slave_unbuffered_read_bytes = 0;
+
+ int64_t new_seeks = 0;
+
+ struct stream *stream = demuxer->stream;
+ if (stream) {
+ new += stream->total_unbuffered_read_bytes;
+ stream->total_unbuffered_read_bytes = 0;
+ new_seeks += stream->total_stream_seeks;
+ stream->total_stream_seeks = 0;
+ }
+
+ in->cache_unbuffered_read_bytes += new;
+ in->hack_unbuffered_read_bytes += new;
+ in->byte_level_seeks += new_seeks;
+}
+
+// must be called locked, temporarily unlocks
+static void update_cache(struct demux_internal *in)
+{
+ struct demuxer *demuxer = in->d_thread;
+ struct stream *stream = demuxer->stream;
+
+ int64_t now = mp_time_ns();
+ int64_t diff = now - in->last_speed_query;
+ bool do_update = diff >= MP_TIME_S_TO_NS(1) || !in->last_speed_query;
+
+ // Don't lock while querying the stream.
+ mp_mutex_unlock(&in->lock);
+
+ int64_t stream_size = -1;
+ struct mp_tags *stream_metadata = NULL;
+ if (stream) {
+ if (do_update)
+ stream_size = stream_get_size(stream);
+ stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata);
+ }
+
+ mp_mutex_lock(&in->lock);
+
+ update_bytes_read(in);
+
+ if (do_update)
+ in->stream_size = stream_size;
+ if (stream_metadata) {
+ add_timed_metadata(in, stream_metadata, NULL, MP_NOPTS_VALUE);
+ talloc_free(stream_metadata);
+ }
+
+ in->next_cache_update = INT64_MAX;
+
+ if (do_update) {
+ uint64_t bytes = in->cache_unbuffered_read_bytes;
+ in->cache_unbuffered_read_bytes = 0;
+ in->last_speed_query = now;
+ double speed = bytes / (diff / (double)MP_TIME_S_TO_NS(1));
+ in->bytes_per_second = 0.5 * in->speed_query_prev_sample +
+ 0.5 * speed;
+ in->speed_query_prev_sample = speed;
+ }
+ // The idea is to update as long as there is "activity".
+ if (in->bytes_per_second)
+ in->next_cache_update = now + MP_TIME_S_TO_NS(1) + MP_TIME_US_TO_NS(1);
+}
+
+static void dumper_close(struct demux_internal *in)
+{
+ if (in->dumper)
+ mp_recorder_destroy(in->dumper);
+ in->dumper = NULL;
+ if (in->dumper_status == CONTROL_TRUE)
+ in->dumper_status = CONTROL_FALSE; // make abort equal to success
+}
+
+static int range_time_compare(const void *p1, const void *p2)
+{
+ struct demux_cached_range *r1 = *((struct demux_cached_range **)p1);
+ struct demux_cached_range *r2 = *((struct demux_cached_range **)p2);
+
+ if (r1->seek_start == r2->seek_start)
+ return 0;
+ return r1->seek_start < r2->seek_start ? -1 : 1;
+}
+
+static void dump_cache(struct demux_internal *in, double start, double end)
+{
+ in->dumper_status = in->dumper ? CONTROL_TRUE : CONTROL_ERROR;
+ if (!in->dumper)
+ return;
+
+ // (only in pathological cases there might be more ranges than allowed)
+ struct demux_cached_range *ranges[MAX_SEEK_RANGES];
+ int num_ranges = 0;
+ for (int n = 0; n < MPMIN(MP_ARRAY_SIZE(ranges), in->num_ranges); n++)
+ ranges[num_ranges++] = in->ranges[n];
+ qsort(ranges, num_ranges, sizeof(ranges[0]), range_time_compare);
+
+ for (int n = 0; n < num_ranges; n++) {
+ struct demux_cached_range *r = ranges[n];
+ if (r->seek_start == MP_NOPTS_VALUE)
+ continue;
+ if (r->seek_end <= start)
+ continue;
+ if (end != MP_NOPTS_VALUE && r->seek_start >= end)
+ continue;
+
+ mp_recorder_mark_discontinuity(in->dumper);
+
+ double pts = start;
+ int flags = 0;
+ adjust_cache_seek_target(in, r, &pts, &flags);
+
+ for (int i = 0; i < r->num_streams; i++) {
+ struct demux_queue *q = r->streams[i];
+ struct demux_stream *ds = q->ds;
+
+ ds->dump_pos = find_seek_target(q, pts, flags);
+ }
+
+ // We need to reinterleave the separate streams somehow, which makes
+ // everything more complex.
+ while (1) {
+ struct demux_packet *next = NULL;
+ double next_dts = MP_NOPTS_VALUE;
+
+ for (int i = 0; i < r->num_streams; i++) {
+ struct demux_stream *ds = r->streams[i]->ds;
+ struct demux_packet *dp = ds->dump_pos;
+
+ if (!dp)
+ continue;
+ assert(dp->stream == ds->index);
+
+ double pdts = MP_PTS_OR_DEF(dp->dts, dp->pts);
+
+ // Check for stream EOF. Note that we don't try to EOF
+ // streams at the same point (e.g. video can take longer
+ // to finish than audio, so the output file will have no
+ // audio for the last part of the video). Too much effort.
+ if (pdts != MP_NOPTS_VALUE && end != MP_NOPTS_VALUE &&
+ pdts >= end && dp->keyframe)
+ {
+ ds->dump_pos = NULL;
+ continue;
+ }
+
+ if (pdts == MP_NOPTS_VALUE || next_dts == MP_NOPTS_VALUE ||
+ pdts < next_dts)
+ {
+ next_dts = pdts;
+ next = dp;
+ }
+ }
+
+ if (!next)
+ break;
+
+ struct demux_stream *ds = in->streams[next->stream]->ds;
+ ds->dump_pos = next->next;
+
+ struct demux_packet *dp = read_packet_from_cache(in, next);
+ if (!dp) {
+ in->dumper_status = CONTROL_ERROR;
+ break;
+ }
+
+ write_dump_packet(in, dp);
+
+ talloc_free(dp);
+ }
+
+ if (in->dumper_status != CONTROL_OK)
+ break;
+ }
+
+ // (strictly speaking unnecessary; for clarity)
+ for (int n = 0; n < in->num_streams; n++)
+ in->streams[n]->ds->dump_pos = NULL;
+
+ // If dumping (in end==NOPTS mode) doesn't continue at the range that
+ // was written last, we have a discontinuity.
+ if (num_ranges && ranges[num_ranges - 1] != in->current_range)
+ mp_recorder_mark_discontinuity(in->dumper);
+
+ // end=NOPTS means the demuxer output continues to be written to the
+ // dump file.
+ if (end != MP_NOPTS_VALUE || in->dumper_status != CONTROL_OK)
+ dumper_close(in);
+}
+
+// Set the current cache dumping mode. There is only at most 1 dump process
+// active, so calling this aborts the previous dumping. Passing file==NULL
+// stops dumping.
+// This is synchronous with demux_cache_dump_get_status() (i.e. starting or
+// aborting is not asynchronous). On status change, the demuxer wakeup callback
+// is invoked (except for this call).
+// Returns whether dumping was logically started.
+bool demux_cache_dump_set(struct demuxer *demuxer, double start, double end,
+ char *file)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ bool res = false;
+
+ mp_mutex_lock(&in->lock);
+
+ start = MP_ADD_PTS(start, -in->ts_offset);
+ end = MP_ADD_PTS(end, -in->ts_offset);
+
+ dumper_close(in);
+
+ if (file && file[0] && start != MP_NOPTS_VALUE) {
+ res = true;
+
+ in->dumper = recorder_create(in, file);
+
+ // This is not asynchronous and will freeze the shit for a while if the
+ // user is unlucky. It could be moved to a thread with some effort.
+ // General idea: iterate over all cache ranges, dump what intersects.
+ // After that, and if the user requested it, make it dump all newly
+ // received packets, even if it's awkward (consider the case if the
+ // current range is not the last range).
+ dump_cache(in, start, end);
+ }
+
+ mp_mutex_unlock(&in->lock);
+
+ return res;
+}
+
+// Returns one of CONTROL_*. CONTROL_TRUE means dumping is in progress.
+int demux_cache_dump_get_status(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ mp_mutex_lock(&in->lock);
+ int status = in->dumper_status;
+ mp_mutex_unlock(&in->lock);
+ return status;
+}
+
+// Return what range demux_cache_dump_set() would (probably) yield. This is a
+// conservative amount (in addition to internal consistency of this code, it
+// depends on what a player will do with the resulting file).
+// Use for_end==true to get the end of dumping, other the start.
+// Returns NOPTS if nothing was found.
+double demux_probe_cache_dump_target(struct demuxer *demuxer, double pts,
+ bool for_end)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ double res = MP_NOPTS_VALUE;
+ if (pts == MP_NOPTS_VALUE)
+ return pts;
+
+ mp_mutex_lock(&in->lock);
+
+ pts = MP_ADD_PTS(pts, -in->ts_offset);
+
+ // (When determining the end, look before the keyframe at pts, so subtract
+ // an arbitrary amount to round down.)
+ double seek_pts = for_end ? pts - 0.001 : pts;
+ int flags = 0;
+ struct demux_cached_range *r = find_cache_seek_range(in, seek_pts, flags);
+ if (r) {
+ if (!for_end)
+ adjust_cache_seek_target(in, r, &pts, &flags);
+
+ double t[STREAM_TYPE_COUNT];
+ for (int n = 0; n < STREAM_TYPE_COUNT; n++)
+ t[n] = MP_NOPTS_VALUE;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ struct demux_queue *q = r->streams[n];
+
+ struct demux_packet *dp = find_seek_target(q, pts, flags);
+ if (dp) {
+ if (for_end) {
+ while (dp) {
+ double pdts = MP_PTS_OR_DEF(dp->dts, dp->pts);
+
+ if (pdts != MP_NOPTS_VALUE && pdts >= pts && dp->keyframe)
+ break;
+
+ t[ds->type] = MP_PTS_MAX(t[ds->type], pdts);
+
+ dp = dp->next;
+ }
+ } else {
+ double start;
+ compute_keyframe_times(dp, &start, NULL);
+ start = MP_PTS_MAX(start, r->seek_start);
+ t[ds->type] = MP_PTS_MAX(t[ds->type], start);
+ }
+ }
+ }
+
+ res = t[STREAM_VIDEO];
+ if (res == MP_NOPTS_VALUE)
+ res = t[STREAM_AUDIO];
+ if (res == MP_NOPTS_VALUE) {
+ for (int n = 0; n < STREAM_TYPE_COUNT; n++) {
+ res = t[n];
+ if (res != MP_NOPTS_VALUE)
+ break;
+ }
+ }
+ }
+
+ res = MP_ADD_PTS(res, in->ts_offset);
+
+ mp_mutex_unlock(&in->lock);
+
+ return res;
+}
+
+// Used by demuxers to report the amount of transferred bytes. This is for
+// streams which circumvent demuxer->stream (stream statistics are handled by
+// demux.c itself).
+void demux_report_unbuffered_read_bytes(struct demuxer *demuxer, int64_t new)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_thread);
+
+ in->slave_unbuffered_read_bytes += new;
+}
+
+// Return bytes read since last query. It's a hack because it works only if
+// the demuxer thread is disabled.
+int64_t demux_get_bytes_read_hack(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+
+ // Required because demuxer==in->d_user, and we access in->d_thread.
+ // Locking won't solve this, because we also need to access struct stream.
+ assert(!in->threading);
+
+ update_bytes_read(in);
+
+ int64_t res = in->hack_unbuffered_read_bytes;
+ in->hack_unbuffered_read_bytes = 0;
+ return res;
+}
+
+void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ mp_mutex_lock(&in->lock);
+
+ for (int n = 0; n < STREAM_TYPE_COUNT; n++)
+ rates[n] = -1;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (ds->selected && ds->bitrate >= 0)
+ rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
+ }
+
+ mp_mutex_unlock(&in->lock);
+}
+
+void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ mp_mutex_lock(&in->lock);
+
+ *r = (struct demux_reader_state){
+ .eof = in->eof,
+ .ts_reader = MP_NOPTS_VALUE,
+ .ts_end = MP_NOPTS_VALUE,
+ .ts_duration = -1,
+ .total_bytes = in->total_bytes,
+ .seeking = in->seeking_in_progress,
+ .low_level_seeks = in->low_level_seeks,
+ .ts_last = in->demux_ts,
+ .bytes_per_second = in->bytes_per_second,
+ .byte_level_seeks = in->byte_level_seeks,
+ .file_cache_bytes = in->cache ? demux_cache_get_size(in->cache) : -1,
+ };
+ bool any_packets = false;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (ds->eager && !(!ds->queue->head && ds->eof) && !ds->ignore_eof) {
+ r->underrun |= !ds->reader_head && !ds->eof && !ds->still_image;
+ r->ts_reader = MP_PTS_MAX(r->ts_reader, ds->base_ts);
+ r->ts_end = MP_PTS_MAX(r->ts_end, ds->queue->last_ts);
+ any_packets |= !!ds->reader_head;
+ }
+ r->fw_bytes += get_forward_buffered_bytes(ds);
+ }
+ r->idle = (!in->reading && !r->underrun) || r->eof;
+ r->underrun &= !r->idle && in->threading;
+ r->ts_reader = MP_ADD_PTS(r->ts_reader, in->ts_offset);
+ r->ts_end = MP_ADD_PTS(r->ts_end, in->ts_offset);
+ if (r->ts_reader != MP_NOPTS_VALUE && r->ts_reader <= r->ts_end)
+ r->ts_duration = r->ts_end - r->ts_reader;
+ if (in->seeking || !any_packets)
+ r->ts_duration = 0;
+ for (int n = 0; n < MPMIN(in->num_ranges, MAX_SEEK_RANGES); n++) {
+ struct demux_cached_range *range = in->ranges[n];
+ if (range->seek_start != MP_NOPTS_VALUE) {
+ r->seek_ranges[r->num_seek_ranges++] =
+ (struct demux_seek_range){
+ .start = MP_ADD_PTS(range->seek_start, in->ts_offset),
+ .end = MP_ADD_PTS(range->seek_end, in->ts_offset),
+ };
+ r->bof_cached |= range->is_bof;
+ r->eof_cached |= range->is_eof;
+ }
+ }
+
+ mp_mutex_unlock(&in->lock);
+}
+
+bool demux_cancel_test(struct demuxer *demuxer)
+{
+ return mp_cancel_test(demuxer->cancel);
+}
+
+struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
+{
+ struct demux_chapter *new = talloc_array(NULL, struct demux_chapter, num);
+ for (int n = 0; n < num; n++) {
+ new[n] = c[n];
+ new[n].metadata = mp_tags_dup(new, new[n].metadata);
+ }
+ return new;
+}
+
+static void visit_tags(void *ctx, void (*visit)(void *ctx, void *ta, char **s),
+ struct mp_tags *tags)
+{
+ for (int n = 0; n < (tags ? tags->num_keys : 0); n++)
+ visit(ctx, tags, &tags->values[n]);
+}
+
+static void visit_meta(struct demuxer *demuxer, void *ctx,
+ void (*visit)(void *ctx, void *ta, char **s))
+{
+ struct demux_internal *in = demuxer->in;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct sh_stream *sh = in->streams[n];
+
+ visit(ctx, sh, &sh->title);
+ visit_tags(ctx, visit, sh->tags);
+ }
+
+ for (int n = 0; n < demuxer->num_chapters; n++)
+ visit_tags(ctx, visit, demuxer->chapters[n].metadata);
+
+ visit_tags(ctx, visit, demuxer->metadata);
+}
+
+
+static void visit_detect(void *ctx, void *ta, char **s)
+{
+ char **all = ctx;
+
+ if (*s)
+ *all = talloc_asprintf_append_buffer(*all, "%s\n", *s);
+}
+
+static void visit_convert(void *ctx, void *ta, char **s)
+{
+ struct demuxer *demuxer = ctx;
+ struct demux_internal *in = demuxer->in;
+
+ if (!*s)
+ return;
+
+ bstr data = bstr0(*s);
+ bstr conv = mp_iconv_to_utf8(in->log, data, in->meta_charset,
+ MP_ICONV_VERBOSE);
+ if (conv.start && conv.start != data.start) {
+ char *ns = conv.start; // 0-termination is guaranteed
+ // (The old string might not be an alloc, but if it is, it's a talloc
+ // child, and will not leak, even if it stays allocated uselessly.)
+ *s = ns;
+ talloc_steal(ta, *s);
+ }
+}
+
+static void demux_convert_tags_charset(struct demuxer *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+
+ char *cp = demuxer->opts->meta_cp;
+ if (!cp || mp_charset_is_utf8(cp))
+ return;
+
+ char *data = talloc_strdup(NULL, "");
+ visit_meta(demuxer, &data, visit_detect);
+
+ in->meta_charset = (char *)mp_charset_guess(in, in->log, bstr0(data), cp, 0);
+ if (in->meta_charset && !mp_charset_is_utf8(in->meta_charset)) {
+ MP_INFO(demuxer, "Using tag charset: %s\n", in->meta_charset);
+ visit_meta(demuxer, demuxer, visit_convert);
+ }
+
+ talloc_free(data);
+}
+
+static bool get_demux_sub_opts(int index, const struct m_sub_options **sub)
+{
+ if (!demuxer_list[index])
+ return false;
+ *sub = demuxer_list[index]->options;
+ return true;
+}
diff --git a/demux/demux.h b/demux/demux.h
new file mode 100644
index 0000000..08904f2
--- /dev/null
+++ b/demux/demux.h
@@ -0,0 +1,361 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_DEMUXER_H
+#define MPLAYER_DEMUXER_H
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "misc/bstr.h"
+#include "common/common.h"
+#include "common/tags.h"
+#include "packet.h"
+#include "stheader.h"
+
+#define MAX_SEEK_RANGES 10
+
+struct demux_seek_range {
+ double start, end;
+};
+
+struct demux_reader_state {
+ bool eof, underrun, idle;
+ bool bof_cached, eof_cached;
+ double ts_duration;
+ double ts_reader; // approx. timerstamp of decoder position
+ double ts_end; // approx. timestamp of end of buffered range
+ int64_t total_bytes;
+ int64_t fw_bytes;
+ int64_t file_cache_bytes;
+ double seeking; // current low level seek target, or NOPTS
+ int low_level_seeks; // number of started low level seeks
+ uint64_t byte_level_seeks; // number of byte stream level seeks
+ double ts_last; // approx. timestamp of demuxer position
+ uint64_t bytes_per_second; // low level statistics
+ // Positions that can be seeked to without incurring the latency of a low
+ // level seek.
+ int num_seek_ranges;
+ struct demux_seek_range seek_ranges[MAX_SEEK_RANGES];
+};
+
+extern const struct m_sub_options demux_conf;
+
+struct demux_opts {
+ int enable_cache;
+ bool disk_cache;
+ int64_t max_bytes;
+ int64_t max_bytes_bw;
+ bool donate_fw;
+ double min_secs;
+ double hyst_secs;
+ bool force_seekable;
+ double min_secs_cache;
+ bool access_references;
+ int seekable_cache;
+ int index_mode;
+ double mf_fps;
+ char *mf_type;
+ bool create_ccs;
+ char *record_file;
+ int video_back_preroll;
+ int audio_back_preroll;
+ int back_batch[STREAM_TYPE_COUNT];
+ double back_seek_size;
+ char *meta_cp;
+ bool force_retry_eof;
+};
+
+#define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
+#define SEEK_FORWARD (1 << 2) // prefer later time if not exact
+ // (if unset, prefer earlier time)
+#define SEEK_CACHED (1 << 3) // allow packet cache seeks only
+#define SEEK_SATAN (1 << 4) // enable backward demuxing
+#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
+#define SEEK_FORCE (1 << 6) // ignore unseekable flag
+#define SEEK_BLOCK (1 << 7) // upon successfully queued seek, block readers
+ // (simplifies syncing multiple reader threads)
+
+// Strictness of the demuxer open format check.
+// demux.c will try by default: NORMAL, UNSAFE (in this order)
+// Using "-demuxer format" will try REQUEST
+// Using "-demuxer +format" will try FORCE
+// REQUEST can be used as special value for raw demuxers which have no file
+// header check; then they should fail if check!=FORCE && check!=REQUEST.
+//
+// In general, the list is sorted from weakest check to normal check.
+// You can use relation operators to compare the check level.
+enum demux_check {
+ DEMUX_CHECK_FORCE, // force format if possible
+ DEMUX_CHECK_UNSAFE, // risky/fuzzy detection
+ DEMUX_CHECK_REQUEST,// requested by user or stream implementation
+ DEMUX_CHECK_NORMAL, // normal, safe detection
+};
+
+enum demux_event {
+ DEMUX_EVENT_INIT = 1 << 0, // complete (re-)initialization
+ DEMUX_EVENT_STREAMS = 1 << 1, // a stream was added
+ DEMUX_EVENT_METADATA = 1 << 2, // metadata or stream_metadata changed
+ DEMUX_EVENT_DURATION = 1 << 3, // duration updated
+ DEMUX_EVENT_ALL = 0xFFFF,
+};
+
+struct demuxer;
+struct timeline;
+
+/**
+ * Demuxer description structure
+ */
+typedef struct demuxer_desc {
+ const char *name; // Demuxer name, used with -demuxer switch
+ const char *desc; // Displayed to user
+
+ // If non-NULL, these are added to the global option list.
+ const struct m_sub_options *options;
+
+ // Return 0 on success, otherwise -1
+ int (*open)(struct demuxer *demuxer, enum demux_check check);
+ // The following functions are all optional
+ // Try to read a packet. Return false on EOF. If true is returned, the
+ // demuxer may set *pkt to a new packet (the reference goes to the caller).
+ // If *pkt is NULL (the value when this function is called), the call
+ // will be repeated.
+ bool (*read_packet)(struct demuxer *demuxer, struct demux_packet **pkt);
+ void (*close)(struct demuxer *demuxer);
+ void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
+ void (*switched_tracks)(struct demuxer *demuxer);
+ // See timeline.c
+ void (*load_timeline)(struct timeline *tl);
+} demuxer_desc_t;
+
+typedef struct demux_chapter
+{
+ int original_index;
+ double pts;
+ struct mp_tags *metadata;
+ uint64_t demuxer_id; // for mapping to internal demuxer data structures
+} demux_chapter_t;
+
+struct demux_edition {
+ uint64_t demuxer_id;
+ bool default_edition;
+ struct mp_tags *metadata;
+};
+
+struct matroska_segment_uid {
+ unsigned char segment[16];
+ uint64_t edition;
+};
+
+struct matroska_data {
+ struct matroska_segment_uid uid;
+ // Ordered chapter information if any
+ struct matroska_chapter {
+ uint64_t start;
+ uint64_t end;
+ bool has_segment_uid;
+ struct matroska_segment_uid uid;
+ char *name;
+ } *ordered_chapters;
+ int num_ordered_chapters;
+};
+
+struct replaygain_data {
+ float track_gain;
+ float track_peak;
+ float album_gain;
+ float album_peak;
+};
+
+typedef struct demux_attachment
+{
+ char *name;
+ char *type;
+ void *data;
+ unsigned int data_size;
+} demux_attachment_t;
+
+struct demuxer_params {
+ bool is_top_level; // if true, it's not a sub-demuxer (enables cache etc.)
+ char *force_format;
+ int matroska_num_wanted_uids;
+ struct matroska_segment_uid *matroska_wanted_uids;
+ int matroska_wanted_segment;
+ bool *matroska_was_valid;
+ struct timeline *timeline;
+ bool disable_timeline;
+ bstr init_fragment;
+ bool skip_lavf_probing;
+ bool stream_record; // if true, enable stream recording if option is set
+ int stream_flags;
+ struct stream *external_stream; // if set, use this, don't open or close streams
+ // result
+ bool demuxer_failed;
+};
+
+typedef struct demuxer {
+ const demuxer_desc_t *desc; ///< Demuxer description structure
+ const char *filetype; // format name when not identified by demuxer (libavformat)
+ int64_t filepos; // input stream current pos.
+ int64_t filesize;
+ char *filename; // same as stream->url
+ bool seekable;
+ bool partially_seekable; // true if _maybe_ seekable; implies seekable=true
+ double start_time;
+ double duration; // -1 if unknown
+ // File format allows PTS resets (even if the current file is without)
+ bool ts_resets_possible;
+ // The file data was fully read, and there is no need to keep the stream
+ // open, keep the cache active, or to run the demuxer thread. Generating
+ // packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
+ // Typical examples: text subtitles, playlists
+ bool fully_read;
+ bool is_network; // opened directly from a network stream
+ bool is_streaming; // implies a "slow" input, such as network or FUSE
+ int stream_origin; // any STREAM_ORIGIN_* (set from source stream)
+ bool access_references; // allow opening other files/URLs
+
+ struct demux_opts *opts;
+ struct m_config_cache *opts_cache;
+
+ // Bitmask of DEMUX_EVENT_*
+ int events;
+
+ struct demux_edition *editions;
+ int num_editions;
+ int edition;
+
+ struct demux_chapter *chapters;
+ int num_chapters;
+
+ struct demux_attachment *attachments;
+ int num_attachments;
+
+ struct matroska_data matroska_data;
+
+ // If the file is a playlist file
+ struct playlist *playlist;
+
+ struct mp_tags *metadata;
+
+ void *priv; // demuxer-specific internal data
+ struct mpv_global *global;
+ struct mp_log *log, *glog;
+ struct demuxer_params *params;
+
+ // internal to demux.c
+ struct demux_internal *in;
+
+ // Triggered when ending demuxing forcefully. Usually bound to the stream too.
+ struct mp_cancel *cancel;
+
+ // Since the demuxer can run in its own thread, and the stream is not
+ // thread-safe, only the demuxer is allowed to access the stream directly.
+ // Also note that the stream can get replaced if fully_read is set.
+ struct stream *stream;
+} demuxer_t;
+
+void demux_free(struct demuxer *demuxer);
+void demux_cancel_and_free(struct demuxer *demuxer);
+
+struct demux_free_async_state;
+struct demux_free_async_state *demux_free_async(struct demuxer *demuxer);
+void demux_free_async_force(struct demux_free_async_state *state);
+bool demux_free_async_finish(struct demux_free_async_state *state);
+
+void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp);
+
+int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
+int demux_read_packet_async_until(struct sh_stream *sh, double min_pts,
+ struct demux_packet **out_pkt);
+bool demux_stream_is_selected(struct sh_stream *stream);
+void demux_set_stream_wakeup_cb(struct sh_stream *sh,
+ void (*cb)(void *ctx), void *ctx);
+struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
+
+struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index);
+int demux_get_num_stream(struct demuxer *demuxer);
+
+struct sh_stream *demux_alloc_sh_stream(enum stream_type type);
+void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh);
+
+struct mp_cancel;
+struct demuxer *demux_open_url(const char *url,
+ struct demuxer_params *params,
+ struct mp_cancel *cancel,
+ struct mpv_global *global);
+
+void demux_start_thread(struct demuxer *demuxer);
+void demux_stop_thread(struct demuxer *demuxer);
+void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
+void demux_start_prefetch(struct demuxer *demuxer);
+
+bool demux_cancel_test(struct demuxer *demuxer);
+
+void demux_flush(struct demuxer *demuxer);
+int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
+void demux_set_ts_offset(struct demuxer *demuxer, double offset);
+
+void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates);
+void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r);
+
+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 demuxer_refresh_track(struct demuxer *demuxer, struct sh_stream *stream,
+ double ref_pts);
+
+int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name);
+
+int demuxer_add_attachment(struct demuxer *demuxer, char *name,
+ char *type, void *data, size_t data_size);
+int demuxer_add_chapter(demuxer_t *demuxer, char *name,
+ double pts, uint64_t demuxer_id);
+void demux_stream_tags_changed(struct demuxer *demuxer, struct sh_stream *sh,
+ struct mp_tags *tags, double pts);
+void demux_close_stream(struct demuxer *demuxer);
+
+void demux_metadata_changed(demuxer_t *demuxer);
+void demux_update(demuxer_t *demuxer, double playback_pts);
+
+bool demux_cache_dump_set(struct demuxer *demuxer, double start, double end,
+ char *file);
+int demux_cache_dump_get_status(struct demuxer *demuxer);
+
+double demux_probe_cache_dump_target(struct demuxer *demuxer, double pts,
+ bool for_end);
+
+bool demux_is_network_cached(demuxer_t *demuxer);
+
+void demux_report_unbuffered_read_bytes(struct demuxer *demuxer, int64_t new);
+int64_t demux_get_bytes_read_hack(struct demuxer *demuxer);
+
+struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
+ enum stream_type t, int id);
+
+struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num);
+
+bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
+ struct matroska_segment_uid *b);
+
+const char *stream_type_name(enum stream_type type);
+
+#endif /* MPLAYER_DEMUXER_H */
diff --git a/demux/demux_cue.c b/demux/demux_cue.c
new file mode 100644
index 0000000..4937ec9
--- /dev/null
+++ b/demux/demux_cue.c
@@ -0,0 +1,304 @@
+/*
+ * Original author: Uoti Urpala
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <inttypes.h>
+
+#include "osdep/io.h"
+
+#include "mpv_talloc.h"
+
+#include "misc/bstr.h"
+#include "misc/charset_conv.h"
+#include "common/msg.h"
+#include "demux/demux.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "options/path.h"
+#include "common/common.h"
+#include "stream/stream.h"
+#include "timeline.h"
+
+#include "cue.h"
+
+#define PROBE_SIZE 512
+
+const struct m_sub_options demux_cue_conf = {
+ .opts = (const m_option_t[]) {
+ {"codepage", OPT_REPLACED("metadata-codepage")},
+ {0}
+ },
+};
+
+struct priv {
+ struct cue_file *f;
+};
+
+static void add_source(struct timeline *tl, struct demuxer *d)
+{
+ MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
+}
+
+static bool try_open(struct timeline *tl, char *filename)
+{
+ struct bstr bfilename = bstr0(filename);
+ // Avoid trying to open itself or another .cue file. Best would be
+ // to check the result of demuxer auto-detection, but the demuxer
+ // API doesn't allow this without opening a full demuxer.
+ if (bstr_case_endswith(bfilename, bstr0(".cue"))
+ || bstrcasecmp(bstr0(tl->demuxer->filename), bfilename) == 0)
+ return false;
+
+ struct demuxer_params p = {
+ .stream_flags = tl->stream_origin,
+ };
+
+ struct demuxer *d = demux_open_url(filename, &p, tl->cancel, tl->global);
+ // Since .bin files are raw PCM data with no headers, we have to explicitly
+ // open them. Also, try to avoid to open files that are most likely not .bin
+ // files, as that would only play noise. Checking the file extension is
+ // fragile, but it's about the only way we have.
+ // TODO: maybe also could check if the .bin file is a multiple of the Audio
+ // CD sector size (2352 bytes)
+ if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
+ MP_WARN(tl, "CUE: Opening as BIN file!\n");
+ p.force_format = "rawaudio";
+ d = demux_open_url(filename, &p, tl->cancel, tl->global);
+ }
+ if (d) {
+ add_source(tl, d);
+ return true;
+ }
+ MP_ERR(tl, "Could not open source '%s'!\n", filename);
+ return false;
+}
+
+static bool open_source(struct timeline *tl, char *filename)
+{
+ void *ctx = talloc_new(NULL);
+ bool res = false;
+
+ struct bstr dirname = mp_dirname(tl->demuxer->filename);
+
+ struct bstr base_filename = bstr0(mp_basename(filename));
+ if (!base_filename.len) {
+ MP_WARN(tl, "CUE: Invalid audio filename in .cue file!\n");
+ } else {
+ char *fullname = mp_path_join_bstr(ctx, dirname, base_filename);
+ if (try_open(tl, fullname)) {
+ res = true;
+ goto out;
+ }
+ }
+
+ // Try an audio file with the same name as the .cue file (but different
+ // extension).
+ // Rationale: this situation happens easily if the audio file or both files
+ // are renamed.
+
+ struct bstr cuefile =
+ bstr_strip_ext(bstr0(mp_basename(tl->demuxer->filename)));
+
+ DIR *d = opendir(bstrdup0(ctx, dirname));
+ if (!d)
+ goto out;
+ struct dirent *de;
+ while ((de = readdir(d))) {
+ char *dename0 = de->d_name;
+ struct bstr dename = bstr0(dename0);
+ if (bstr_case_startswith(dename, cuefile)) {
+ MP_WARN(tl, "CUE: No useful audio filename "
+ "in .cue file found, trying with '%s' instead!\n",
+ dename0);
+ if (try_open(tl, mp_path_join_bstr(ctx, dirname, dename))) {
+ res = true;
+ break;
+ }
+ }
+ }
+ closedir(d);
+
+out:
+ talloc_free(ctx);
+ if (!res)
+ MP_ERR(tl, "CUE: Could not open audio file!\n");
+ return res;
+}
+
+static void build_timeline(struct timeline *tl)
+{
+ struct priv *p = tl->demuxer->priv;
+
+ void *ctx = talloc_new(NULL);
+
+ add_source(tl, tl->demuxer);
+
+ struct cue_track *tracks = NULL;
+ size_t track_count = 0;
+
+ for (size_t n = 0; n < p->f->num_tracks; n++) {
+ struct cue_track *track = &p->f->tracks[n];
+ if (track->filename) {
+ MP_TARRAY_APPEND(ctx, tracks, track_count, *track);
+ } else {
+ MP_WARN(tl->demuxer, "No file specified for track entry %zd. "
+ "It will be removed\n", n + 1);
+ }
+ }
+
+ if (track_count == 0) {
+ MP_ERR(tl, "CUE: no tracks found!\n");
+ goto out;
+ }
+
+ // Remove duplicate file entries. This might be too sophisticated, since
+ // CUE files usually use either separate files for every single track, or
+ // only one file for all tracks.
+
+ char **files = 0;
+ size_t file_count = 0;
+
+ for (size_t n = 0; n < track_count; n++) {
+ struct cue_track *track = &tracks[n];
+ track->source = -1;
+ for (size_t file = 0; file < file_count; file++) {
+ if (strcmp(files[file], track->filename) == 0) {
+ track->source = file;
+ break;
+ }
+ }
+ if (track->source == -1) {
+ file_count++;
+ files = talloc_realloc(ctx, files, char *, file_count);
+ files[file_count - 1] = track->filename;
+ track->source = file_count - 1;
+ }
+ }
+
+ for (size_t i = 0; i < file_count; i++) {
+ if (!open_source(tl, files[i]))
+ goto out;
+ }
+
+ struct timeline_part *timeline = talloc_array_ptrtype(tl, timeline,
+ track_count + 1);
+ struct demux_chapter *chapters = talloc_array_ptrtype(tl, chapters,
+ track_count);
+ double starttime = 0;
+ for (int i = 0; i < track_count; i++) {
+ struct demuxer *source = tl->sources[1 + tracks[i].source];
+ double duration;
+ if (i + 1 < track_count && tracks[i].source == tracks[i + 1].source) {
+ duration = tracks[i + 1].start - tracks[i].start;
+ } else {
+ duration = source->duration;
+ // Two cases: 1) last track of a single-file cue, or 2) any track of
+ // a multi-file cue. We need to do this for 1) only because the
+ // timeline needs to be terminated with the length of the last
+ // track.
+ duration -= tracks[i].start;
+ }
+ if (duration < 0) {
+ MP_WARN(tl, "CUE: Can't get duration of source file!\n");
+ // xxx: do something more reasonable
+ duration = 0.0;
+ }
+ timeline[i] = (struct timeline_part) {
+ .start = starttime,
+ .end = starttime + duration,
+ .source_start = tracks[i].start,
+ .source = source,
+ };
+ chapters[i] = (struct demux_chapter) {
+ .pts = timeline[i].start,
+ .metadata = mp_tags_dup(tl, tracks[i].tags),
+ };
+ starttime = timeline[i].end;
+ }
+
+ struct timeline_par *par = talloc_ptrtype(tl, par);
+ *par = (struct timeline_par){
+ .parts = timeline,
+ .num_parts = track_count,
+ .track_layout = timeline[0].source,
+ };
+
+ tl->chapters = chapters;
+ tl->num_chapters = track_count;
+ MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par);
+ tl->meta = par->track_layout;
+ tl->format = "cue";
+
+out:
+ talloc_free(ctx);
+}
+
+static int try_open_file(struct demuxer *demuxer, enum demux_check check)
+{
+ if (!demuxer->access_references)
+ return -1;
+
+ struct stream *s = demuxer->stream;
+ if (check >= DEMUX_CHECK_UNSAFE) {
+ char probe[PROBE_SIZE];
+ int len = stream_read_peek(s, probe, sizeof(probe));
+ if (len < 1 || !mp_probe_cue((bstr){probe, len}))
+ return -1;
+ }
+ struct priv *p = talloc_zero(demuxer, struct priv);
+ demuxer->priv = p;
+ demuxer->fully_read = true;
+ bstr data = stream_read_complete(s, p, 1000000);
+ if (data.start == NULL)
+ return -1;
+
+ struct demux_opts *opts = mp_get_config_group(p, demuxer->global, &demux_conf);
+ const char *charset = mp_charset_guess(p, demuxer->log, data, opts->meta_cp, 0);
+ if (charset && !mp_charset_is_utf8(charset)) {
+ MP_INFO(demuxer, "Using CUE charset: %s\n", charset);
+ bstr utf8 = mp_iconv_to_utf8(demuxer->log, data, charset, MP_ICONV_VERBOSE);
+ if (utf8.start && utf8.start != data.start) {
+ ta_steal(data.start, utf8.start);
+ data = utf8;
+ }
+ }
+ talloc_free(opts);
+
+ p->f = mp_parse_cue(data);
+ talloc_steal(p, p->f);
+ if (!p->f) {
+ MP_ERR(demuxer, "error parsing input file!\n");
+ return -1;
+ }
+
+ demux_close_stream(demuxer);
+
+ mp_tags_merge(demuxer->metadata, p->f->tags);
+ return 0;
+}
+
+const struct demuxer_desc demuxer_desc_cue = {
+ .name = "cue",
+ .desc = "CUE sheet",
+ .open = try_open_file,
+ .load_timeline = build_timeline,
+};
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
new file mode 100644
index 0000000..3dfff45
--- /dev/null
+++ b/demux/demux_disc.c
@@ -0,0 +1,360 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+
+#include "stream/stream.h"
+#include "video/mp_image.h"
+#include "demux.h"
+#include "stheader.h"
+
+#include "video/csputils.h"
+
+struct priv {
+ struct demuxer *slave;
+ // streams[slave_stream_index] == our_stream
+ struct sh_stream **streams;
+ int num_streams;
+ // This contains each DVD sub stream, or NULL. Needed because DVD packets
+ // can come arbitrarily late in the MPEG stream, so the slave demuxer
+ // might add the streams only later.
+ struct sh_stream *dvd_subs[32];
+ // Used to rewrite the raw MPEG timestamps to playback time.
+ double base_time; // playback display start time of current segment
+ double base_dts; // packet DTS that maps to base_time
+ double last_dts; // DTS of previously demuxed packet
+ bool seek_reinit; // needs reinit after seek
+
+ bool is_dvd, is_cdda;
+};
+
+// If the timestamp difference between subsequent packets is this big, assume
+// a reset. It should be big enough to account for 1. low video framerates and
+// large audio frames, and 2. bad interleaving.
+#define DTS_RESET_THRESHOLD 5.0
+
+static void reselect_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ int num_slave = demux_get_num_stream(p->slave);
+ for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
+ if (p->streams[n]) {
+ demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
+ MP_NOPTS_VALUE, demux_stream_is_selected(p->streams[n]));
+ }
+ }
+}
+
+static void get_disc_lang(struct stream *stream, struct sh_stream *sh, bool dvd)
+{
+ struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
+ if (dvd && sh->type == STREAM_SUB)
+ req.id = req.id & 0x1F; // mpeg ID to index
+ stream_control(stream, STREAM_CTRL_GET_LANG, &req);
+ if (req.name[0])
+ sh->lang = talloc_strdup(sh, req.name);
+}
+
+static void add_dvd_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ struct stream *stream = demuxer->stream;
+ if (!p->is_dvd)
+ return;
+ struct stream_dvd_info_req info;
+ if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
+ for (int n = 0; n < MPMIN(32, info.num_subs); n++) {
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
+ sh->demuxer_id = n + 0x20;
+ sh->codec->codec = "dvd_subtitle";
+ get_disc_lang(stream, sh, true);
+ // p->streams _must_ match with p->slave->streams, so we can't add
+ // it yet - it has to be done when the real stream appears, which
+ // could be right on start, or any time later.
+ p->dvd_subs[n] = sh;
+
+ // emulate the extradata
+ struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
+ struct mp_cmat cmatrix;
+ mp_get_csp_matrix(&csp, &cmatrix);
+
+ char *s = talloc_strdup(sh, "");
+ s = talloc_asprintf_append(s, "palette: ");
+ for (int i = 0; i < 16; i++) {
+ int color = info.palette[i];
+ int y[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
+ int c[3];
+ mp_map_fixp_color(&cmatrix, 8, y, 8, c);
+ color = (c[2] << 16) | (c[1] << 8) | c[0];
+
+ if (i != 0)
+ s = talloc_asprintf_append(s, ", ");
+ s = talloc_asprintf_append(s, "%06x", color);
+ }
+ s = talloc_asprintf_append(s, "\n");
+
+ sh->codec->extradata = s;
+ sh->codec->extradata_size = strlen(s);
+
+ demux_add_sh_stream(demuxer, sh);
+ }
+ }
+}
+
+static void add_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) {
+ struct sh_stream *src = demux_get_stream(p->slave, n);
+ if (src->type == STREAM_SUB) {
+ struct sh_stream *sub = NULL;
+ if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F)
+ sub = p->dvd_subs[src->demuxer_id - 0x20];
+ if (sub) {
+ assert(p->num_streams == n); // directly mapped
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
+ continue;
+ }
+ }
+ struct sh_stream *sh = demux_alloc_sh_stream(src->type);
+ assert(p->num_streams == n); // directly mapped
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh);
+ // Copy all stream fields that might be relevant
+ *sh->codec = *src->codec;
+ sh->demuxer_id = src->demuxer_id;
+ if (src->type == STREAM_VIDEO) {
+ double ar;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
+ == STREAM_OK)
+ {
+ struct mp_image_params f = {.w = src->codec->disp_w,
+ .h = src->codec->disp_h};
+ mp_image_params_set_dsize(&f, 1728 * ar, 1728);
+ sh->codec->par_w = f.p_w;
+ sh->codec->par_h = f.p_h;
+ }
+ }
+ get_disc_lang(demuxer->stream, sh, p->is_dvd);
+ demux_add_sh_stream(demuxer, sh);
+ }
+ reselect_streams(demuxer);
+}
+
+static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ if (p->is_cdda) {
+ demux_seek(p->slave, seek_pts, flags);
+ return;
+ }
+
+ if (flags & SEEK_FACTOR) {
+ double tmp = 0;
+ stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp);
+ seek_pts *= tmp;
+ }
+
+ MP_VERBOSE(demuxer, "seek to: %f\n", seek_pts);
+
+ // Supposed to induce a seek reset. Does it even work? I don't know.
+ // It will log some bogus error messages, since the demuxer will try a
+ // low level seek, which will obviously not work. But it will probably
+ // clear its internal buffers.
+ demux_seek(p->slave, 0, SEEK_FACTOR | SEEK_FORCE);
+ stream_drop_buffers(demuxer->stream);
+
+ double seek_arg[] = {seek_pts, flags};
+ stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, seek_arg);
+
+ p->seek_reinit = true;
+}
+
+static void reset_pts(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ double base;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1)
+ base = 0;
+
+ MP_VERBOSE(demuxer, "reset to time: %f\n", base);
+
+ p->base_dts = p->last_dts = MP_NOPTS_VALUE;
+ p->base_time = base;
+ p->seek_reinit = false;
+}
+
+static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
+{
+ struct priv *p = demuxer->priv;
+
+ struct demux_packet *pkt = demux_read_any_packet(p->slave);
+ if (!pkt)
+ return false;
+
+ demux_update(p->slave, MP_NOPTS_VALUE);
+
+ if (p->seek_reinit)
+ reset_pts(demuxer);
+
+ add_streams(demuxer);
+ if (pkt->stream >= p->num_streams) { // out of memory?
+ talloc_free(pkt);
+ return true;
+ }
+
+ struct sh_stream *sh = p->streams[pkt->stream];
+ if (!demux_stream_is_selected(sh)) {
+ talloc_free(pkt);
+ return true;
+ }
+
+ pkt->stream = sh->index;
+
+ if (p->is_cdda) {
+ *out_pkt = pkt;
+ return true;
+ }
+
+ MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
+
+ if (sh->type == STREAM_SUB) {
+ if (p->base_dts == MP_NOPTS_VALUE)
+ MP_WARN(demuxer, "subtitle packet along PTS reset\n");
+ } else if (pkt->dts != MP_NOPTS_VALUE) {
+ // Use the very first DTS to rebase the start time of the MPEG stream
+ // to the playback time.
+ if (p->base_dts == MP_NOPTS_VALUE)
+ p->base_dts = pkt->dts;
+
+ if (p->last_dts == MP_NOPTS_VALUE)
+ p->last_dts = pkt->dts;
+
+ if (fabs(p->last_dts - pkt->dts) >= DTS_RESET_THRESHOLD) {
+ MP_WARN(demuxer, "PTS discontinuity: %f->%f\n", p->last_dts, pkt->dts);
+ p->base_time += p->last_dts - p->base_dts;
+ p->base_dts = pkt->dts - pkt->duration;
+ }
+ p->last_dts = pkt->dts;
+ }
+
+ if (p->base_dts != MP_NOPTS_VALUE) {
+ double delta = -p->base_dts + p->base_time;
+ if (pkt->pts != MP_NOPTS_VALUE)
+ pkt->pts += delta;
+ if (pkt->dts != MP_NOPTS_VALUE)
+ pkt->dts += delta;
+ }
+
+ MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
+
+ *out_pkt = pkt;
+ return 1;
+}
+
+static void add_stream_chapters(struct demuxer *demuxer)
+{
+ int num = 0;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1)
+ return;
+ for (int n = 0; n < num; n++) {
+ double p = n;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
+ continue;
+ demuxer_add_chapter(demuxer, "", p, 0);
+ }
+}
+
+static int d_open(demuxer_t *demuxer, enum demux_check check)
+{
+ struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
+
+ if (check != DEMUX_CHECK_FORCE)
+ return -1;
+
+ struct demuxer_params params = {
+ .force_format = "+lavf",
+ .external_stream = demuxer->stream,
+ .stream_flags = demuxer->stream_origin,
+ };
+
+ struct stream *cur = demuxer->stream;
+ const char *sname = "";
+ if (cur->info)
+ sname = cur->info->name;
+
+ p->is_cdda = strcmp(sname, "cdda") == 0;
+ p->is_dvd = strcmp(sname, "dvd") == 0 ||
+ strcmp(sname, "ifo") == 0 ||
+ strcmp(sname, "dvdnav") == 0 ||
+ strcmp(sname, "ifo_dvdnav") == 0;
+
+ if (p->is_cdda)
+ params.force_format = "+rawaudio";
+
+ char *t = NULL;
+ stream_control(demuxer->stream, STREAM_CTRL_GET_DISC_NAME, &t);
+ if (t) {
+ mp_tags_set_str(demuxer->metadata, "TITLE", t);
+ talloc_free(t);
+ }
+
+ // Initialize the playback time. We need to read _some_ data to get the
+ // correct stream-layer time (at least with libdvdnav).
+ stream_read_peek(demuxer->stream, &(char){0}, 1);
+ reset_pts(demuxer);
+
+ p->slave = demux_open_url("-", &params, demuxer->cancel, demuxer->global);
+ if (!p->slave)
+ return -1;
+
+ // Can be seekable even if the stream isn't.
+ demuxer->seekable = true;
+
+ add_dvd_streams(demuxer);
+ add_streams(demuxer);
+ add_stream_chapters(demuxer);
+
+ double len;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) >= 1)
+ demuxer->duration = len;
+
+ return 0;
+}
+
+static void d_close(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ demux_free(p->slave);
+}
+
+const demuxer_desc_t demuxer_desc_disc = {
+ .name = "disc",
+ .desc = "CD/DVD/BD wrapper",
+ .read_packet = d_read_packet,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .switched_tracks = reselect_streams,
+};
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
new file mode 100644
index 0000000..356b7ee
--- /dev/null
+++ b/demux/demux_edl.c
@@ -0,0 +1,651 @@
+/*
+ * Original author: Uoti Urpala
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+
+#include "mpv_talloc.h"
+
+#include "demux.h"
+#include "timeline.h"
+#include "common/msg.h"
+#include "options/path.h"
+#include "misc/bstr.h"
+#include "common/common.h"
+#include "common/tags.h"
+#include "stream/stream.h"
+
+#define HEADER "# mpv EDL v0\n"
+
+struct tl_part {
+ char *filename; // what is stream_open()ed
+ double offset; // offset into the source file
+ bool offset_set;
+ bool chapter_ts;
+ bool is_layout;
+ double length; // length of the part (-1 if rest of the file)
+ char *title;
+};
+
+struct tl_parts {
+ bool disable_chapters;
+ bool dash, no_clip, delay_open;
+ char *init_fragment_url;
+ struct sh_stream **sh_meta;
+ int num_sh_meta;
+ struct tl_part *parts;
+ int num_parts;
+ struct tl_parts *next;
+};
+
+struct tl_root {
+ struct tl_parts **pars;
+ int num_pars;
+ struct mp_tags *tags;
+};
+
+struct priv {
+ bstr data;
+};
+
+// Static allocation out of laziness.
+#define NUM_MAX_PARAMS 20
+
+struct parse_ctx {
+ struct mp_log *log;
+ bool error;
+ bstr param_vals[NUM_MAX_PARAMS];
+ bstr param_names[NUM_MAX_PARAMS];
+ int num_params;
+};
+
+// This returns a value with bstr.start==NULL if nothing found. If the parameter
+// was specified, bstr.str!=NULL, even if the string is empty (bstr.len==0).
+// The parameter is removed from the list if found.
+static bstr get_param(struct parse_ctx *ctx, const char *name)
+{
+ bstr bname = bstr0(name);
+ for (int n = 0; n < ctx->num_params; n++) {
+ if (bstr_equals(ctx->param_names[n], bname)) {
+ bstr res = ctx->param_vals[n];
+ int count = ctx->num_params;
+ MP_TARRAY_REMOVE_AT(ctx->param_names, count, n);
+ count = ctx->num_params;
+ MP_TARRAY_REMOVE_AT(ctx->param_vals, count, n);
+ ctx->num_params -= 1;
+ if (!res.start)
+ res = bstr0(""); // keep guarantees
+ return res;
+ }
+ }
+ return (bstr){0};
+}
+
+// Same as get_param(), but return C string. Return NULL if missing.
+static char *get_param0(struct parse_ctx *ctx, void *ta_ctx, const char *name)
+{
+ return bstrdup0(ta_ctx, get_param(ctx, name));
+}
+
+// Optional int parameter. Returns the parsed integer, or def if the parameter
+// is missing or on error (sets ctx.error on error).
+static int get_param_int(struct parse_ctx *ctx, const char *name, int def)
+{
+ bstr val = get_param(ctx, name);
+ if (val.start) {
+ bstr rest;
+ long long ival = bstrtoll(val, &rest, 0);
+ if (!val.len || rest.len || ival < INT_MIN || ival > INT_MAX) {
+ MP_ERR(ctx, "Invalid integer: '%.*s'\n", BSTR_P(val));
+ ctx->error = true;
+ return def;
+ }
+ return ival;
+ }
+ return def;
+}
+
+// Optional time parameter. Currently a number.
+// Returns true: parameter was present and valid, *t is set
+// Returns false: parameter was not present (or broken => ctx.error set)
+static bool get_param_time(struct parse_ctx *ctx, const char *name, double *t)
+{
+ bstr val = get_param(ctx, name);
+ if (val.start) {
+ bstr rest;
+ double time = bstrtod(val, &rest);
+ if (!val.len || rest.len || !isfinite(time)) {
+ MP_ERR(ctx, "Invalid time string: '%.*s'\n", BSTR_P(val));
+ ctx->error = true;
+ return false;
+ }
+ *t = time;
+ return true;
+ }
+ return false;
+}
+
+static struct tl_parts *add_part(struct tl_root *root)
+{
+ struct tl_parts *tl = talloc_zero(root, struct tl_parts);
+ MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
+ return tl;
+}
+
+static struct sh_stream *get_meta(struct tl_parts *tl, int index)
+{
+ for (int n = 0; n < tl->num_sh_meta; n++) {
+ if (tl->sh_meta[n]->index == index)
+ return tl->sh_meta[n];
+ }
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_TYPE_COUNT);
+ talloc_steal(tl, sh);
+ MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta, sh);
+ return sh;
+}
+
+/* Returns a list of parts, or NULL on parse error.
+ * Syntax (without file header or URI prefix):
+ * url ::= <entry> ( (';' | '\n') <entry> )*
+ * entry ::= <param> ( <param> ',' )*
+ * param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>)
+ */
+static struct tl_root *parse_edl(bstr str, struct mp_log *log)
+{
+ struct tl_root *root = talloc_zero(NULL, struct tl_root);
+ root->tags = talloc_zero(root, struct mp_tags);
+ struct tl_parts *tl = add_part(root);
+ while (str.len) {
+ if (bstr_eatstart0(&str, "#")) {
+ bstr_split_tok(str, "\n", &(bstr){0}, &str);
+ continue;
+ }
+ if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
+ continue;
+ bool is_header = bstr_eatstart0(&str, "!");
+ struct parse_ctx ctx = { .log = log };
+ int nparam = 0;
+ while (1) {
+ bstr name, val;
+ // Check if it's of the form "name=..."
+ int next = bstrcspn(str, "=%,;\n");
+ if (next > 0 && next < str.len && str.start[next] == '=') {
+ name = bstr_splice(str, 0, next);
+ str = bstr_cut(str, next + 1);
+ } else if (is_header) {
+ const char *names[] = {"type"}; // implied name
+ name = bstr0(nparam < 1 ? names[nparam] : "-");
+ } else {
+ const char *names[] = {"file", "start", "length"}; // implied name
+ name = bstr0(nparam < 3 ? names[nparam] : "-");
+ }
+ if (bstr_eatstart0(&str, "%")) {
+ int len = bstrtoll(str, &str, 0);
+ if (!bstr_startswith0(str, "%") || (len > str.len - 1))
+ goto error;
+ val = bstr_splice(str, 1, len + 1);
+ str = bstr_cut(str, len + 1);
+ } else {
+ next = bstrcspn(str, ",;\n");
+ val = bstr_splice(str, 0, next);
+ str = bstr_cut(str, next);
+ }
+ if (ctx.num_params >= NUM_MAX_PARAMS) {
+ mp_err(log, "Too many parameters, ignoring '%.*s'.\n",
+ BSTR_P(name));
+ } else {
+ ctx.param_names[ctx.num_params] = name;
+ ctx.param_vals[ctx.num_params] = val;
+ ctx.num_params += 1;
+ }
+ nparam++;
+ if (!bstr_eatstart0(&str, ","))
+ break;
+ }
+ if (is_header) {
+ bstr f_type = get_param(&ctx, "type");
+ if (bstr_equals0(f_type, "mp4_dash")) {
+ tl->dash = true;
+ tl->init_fragment_url = get_param0(&ctx, tl, "init");
+ } else if (bstr_equals0(f_type, "no_clip")) {
+ tl->no_clip = true;
+ } else if (bstr_equals0(f_type, "new_stream")) {
+ // (Special case: ignore "redundant" headers at the start for
+ // general symmetry.)
+ if (root->num_pars > 1 || tl->num_parts)
+ tl = add_part(root);
+ } else if (bstr_equals0(f_type, "no_chapters")) {
+ tl->disable_chapters = true;
+ } else if (bstr_equals0(f_type, "track_meta")) {
+ int index = get_param_int(&ctx, "index", -1);
+ struct sh_stream *sh = index < 0 && tl->num_sh_meta
+ ? tl->sh_meta[tl->num_sh_meta - 1]
+ : get_meta(tl, index);
+ sh->lang = get_param0(&ctx, sh, "lang");
+ sh->title = get_param0(&ctx, sh, "title");
+ sh->hls_bitrate = get_param_int(&ctx, "byterate", 0) * 8;
+ bstr flags = get_param(&ctx, "flags");
+ bstr flag;
+ while (bstr_split_tok(flags, "+", &flag, &flags) || flag.len) {
+ if (bstr_equals0(flag, "default")) {
+ sh->default_track = true;
+ } else if (bstr_equals0(flag, "forced")) {
+ sh->forced_track = true;
+ } else {
+ mp_warn(log, "Unknown flag: '%.*s'\n", BSTR_P(flag));
+ }
+ }
+ } else if (bstr_equals0(f_type, "delay_open")) {
+ struct sh_stream *sh = get_meta(tl, tl->num_sh_meta);
+ bstr mt = get_param(&ctx, "media_type");
+ if (bstr_equals0(mt, "video")) {
+ sh->type = sh->codec->type = STREAM_VIDEO;
+ } else if (bstr_equals0(mt, "audio")) {
+ sh->type = sh->codec->type = STREAM_AUDIO;
+ } else if (bstr_equals0(mt, "sub")) {
+ sh->type = sh->codec->type = STREAM_SUB;
+ } else {
+ mp_err(log, "Invalid or missing !delay_open media type.\n");
+ goto error;
+ }
+ sh->codec->codec = get_param0(&ctx, sh, "codec");
+ if (!sh->codec->codec)
+ sh->codec->codec = "null";
+ sh->codec->disp_w = get_param_int(&ctx, "w", 0);
+ sh->codec->disp_h = get_param_int(&ctx, "h", 0);
+ sh->codec->fps = get_param_int(&ctx, "fps", 0);
+ sh->codec->samplerate = get_param_int(&ctx, "samplerate", 0);
+ tl->delay_open = true;
+ } else if (bstr_equals0(f_type, "global_tags")) {
+ for (int n = 0; n < ctx.num_params; n++) {
+ mp_tags_set_bstr(root->tags, ctx.param_names[n],
+ ctx.param_vals[n]);
+ }
+ ctx.num_params = 0;
+ } else {
+ mp_err(log, "Unknown header: '%.*s'\n", BSTR_P(f_type));
+ goto error;
+ }
+ } else {
+ struct tl_part p = { .length = -1 };
+ p.filename = get_param0(&ctx, tl, "file");
+ p.offset_set = get_param_time(&ctx, "start", &p.offset);
+ get_param_time(&ctx, "length", &p.length);
+ bstr ts = get_param(&ctx, "timestamps");
+ if (bstr_equals0(ts, "chapters")) {
+ p.chapter_ts = true;
+ } else if (ts.start && !bstr_equals0(ts, "seconds")) {
+ mp_warn(log, "Unknown timestamp type: '%.*s'\n", BSTR_P(ts));
+ }
+ p.title = get_param0(&ctx, tl, "title");
+ bstr layout = get_param(&ctx, "layout");
+ if (layout.start) {
+ if (bstr_equals0(layout, "this")) {
+ p.is_layout = true;
+ } else {
+ mp_warn(log, "Unknown layout param: '%.*s'\n", BSTR_P(layout));
+ }
+ }
+ if (!p.filename) {
+ mp_err(log, "Missing filename in segment.'\n");
+ goto error;
+ }
+ MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
+ }
+ if (ctx.error)
+ goto error;
+ for (int n = 0; n < ctx.num_params; n++) {
+ mp_warn(log, "Unknown or duplicate parameter: '%.*s'\n",
+ BSTR_P(ctx.param_names[n]));
+ }
+ }
+ assert(root->num_pars);
+ for (int n = 0; n < root->num_pars; n++) {
+ if (root->pars[n]->num_parts < 1) {
+ mp_err(log, "EDL specifies no segments.'\n");
+ goto error;
+ }
+ }
+ return root;
+error:
+ mp_err(log, "EDL parsing failed.\n");
+ talloc_free(root);
+ return NULL;
+}
+
+static struct demuxer *open_source(struct timeline *root,
+ struct timeline_par *tl, char *filename)
+{
+ for (int n = 0; n < tl->num_parts; n++) {
+ struct demuxer *d = tl->parts[n].source;
+ if (d && d->filename && strcmp(d->filename, filename) == 0)
+ return d;
+ }
+ struct demuxer_params params = {
+ .init_fragment = tl->init_fragment,
+ .stream_flags = root->stream_origin,
+ };
+ struct demuxer *d = demux_open_url(filename, &params, root->cancel,
+ root->global);
+ if (d) {
+ MP_TARRAY_APPEND(root, root->sources, root->num_sources, d);
+ } else {
+ MP_ERR(root, "EDL: Could not open source file '%s'.\n", filename);
+ }
+ return d;
+}
+
+static double demuxer_chapter_time(struct demuxer *demuxer, int n)
+{
+ if (n < 0 || n >= demuxer->num_chapters)
+ return -1;
+ return demuxer->chapters[n].pts;
+}
+
+// Append all chapters from src to the chapters array.
+// Ignore chapters outside of the given time range.
+static void copy_chapters(struct demux_chapter **chapters, int *num_chapters,
+ struct demuxer *src, double start, double len,
+ double dest_offset)
+{
+ for (int n = 0; n < src->num_chapters; n++) {
+ double time = demuxer_chapter_time(src, n);
+ if (time >= start && time <= start + len) {
+ struct demux_chapter ch = {
+ .pts = dest_offset + time - start,
+ .metadata = mp_tags_dup(*chapters, src->chapters[n].metadata),
+ };
+ MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch);
+ }
+ }
+}
+
+static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
+{
+ if (part->chapter_ts) {
+ double start = demuxer_chapter_time(demuxer, part->offset);
+ double length = part->length;
+ double end = length;
+ if (end >= 0)
+ end = demuxer_chapter_time(demuxer, part->offset + part->length);
+ if (end >= 0 && start >= 0)
+ length = end - start;
+ part->offset = start;
+ part->length = length;
+ }
+ if (!part->offset_set)
+ part->offset = demuxer->start_time;
+}
+
+static struct timeline_par *build_timeline(struct timeline *root,
+ struct tl_root *edl_root,
+ struct tl_parts *parts)
+{
+ struct timeline_par *tl = talloc_zero(root, struct timeline_par);
+ MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
+
+ tl->track_layout = NULL;
+ tl->dash = parts->dash;
+ tl->no_clip = parts->no_clip;
+ tl->delay_open = parts->delay_open;
+
+ // There is no copy function for sh_stream, so just steal it.
+ for (int n = 0; n < parts->num_sh_meta; n++) {
+ MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta,
+ talloc_steal(tl, parts->sh_meta[n]));
+ parts->sh_meta[n] = NULL;
+ }
+ parts->num_sh_meta = 0;
+
+ if (parts->init_fragment_url && parts->init_fragment_url[0]) {
+ MP_VERBOSE(root, "Opening init fragment...\n");
+ stream_t *s = stream_create(parts->init_fragment_url,
+ STREAM_READ | root->stream_origin,
+ root->cancel, root->global);
+ if (s) {
+ root->is_network |= s->is_network;
+ root->is_streaming |= s->streaming;
+ tl->init_fragment = stream_read_complete(s, tl, 1000000);
+ }
+ free_stream(s);
+ if (!tl->init_fragment.len) {
+ MP_ERR(root, "Could not read init fragment.\n");
+ goto error;
+ }
+ struct demuxer_params params = {
+ .init_fragment = tl->init_fragment,
+ .stream_flags = root->stream_origin,
+ };
+ tl->track_layout = demux_open_url("memory://", &params, root->cancel,
+ root->global);
+ if (!tl->track_layout) {
+ MP_ERR(root, "Could not demux init fragment.\n");
+ goto error;
+ }
+ MP_TARRAY_APPEND(root, root->sources, root->num_sources, tl->track_layout);
+ }
+
+ tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts);
+ double starttime = 0;
+ for (int n = 0; n < parts->num_parts; n++) {
+ struct tl_part *part = &parts->parts[n];
+ struct demuxer *source = NULL;
+
+ if (tl->dash) {
+ part->offset = starttime;
+ if (part->length <= 0)
+ MP_WARN(root, "Segment %d has unknown duration.\n", n);
+ if (part->offset_set)
+ MP_WARN(root, "Offsets are ignored.\n");
+
+ if (!tl->track_layout)
+ tl->track_layout = open_source(root, tl, part->filename);
+ } else if (tl->delay_open) {
+ if (n == 0 && !part->offset_set) {
+ part->offset = starttime;
+ part->offset_set = true;
+ }
+ if (part->chapter_ts || (part->length < 0 && !tl->no_clip)) {
+ MP_ERR(root, "Invalid specification for delay_open stream.\n");
+ goto error;
+ }
+ } else {
+ MP_VERBOSE(root, "Opening segment %d...\n", n);
+
+ source = open_source(root, tl, part->filename);
+ if (!source)
+ goto error;
+
+ resolve_timestamps(part, source);
+
+ double end_time = source->duration;
+ if (end_time >= 0)
+ end_time += source->start_time;
+
+ // Unknown length => use rest of the file. If duration is unknown, make
+ // something up.
+ if (part->length < 0) {
+ if (end_time < 0) {
+ MP_WARN(root, "EDL: source file '%s' has unknown duration.\n",
+ part->filename);
+ end_time = 1;
+ }
+ part->length = end_time - part->offset;
+ } else if (end_time >= 0) {
+ double end_part = part->offset + part->length;
+ if (end_part > end_time) {
+ MP_WARN(root, "EDL: entry %d uses %f "
+ "seconds, but file has only %f seconds.\n",
+ n, end_part, end_time);
+ }
+ }
+
+ if (!parts->disable_chapters) {
+ // Add a chapter between each file.
+ struct demux_chapter ch = {
+ .pts = starttime,
+ .metadata = talloc_zero(tl, struct mp_tags),
+ };
+ mp_tags_set_str(ch.metadata, "title",
+ part->title ? part->title : part->filename);
+ MP_TARRAY_APPEND(root, root->chapters, root->num_chapters, ch);
+
+ // Also copy the source file's chapters for the relevant parts
+ copy_chapters(&root->chapters, &root->num_chapters, source,
+ part->offset, part->length, starttime);
+ }
+ }
+
+ tl->parts[n] = (struct timeline_part) {
+ .start = starttime,
+ .end = starttime + part->length,
+ .source_start = part->offset,
+ .source = source,
+ .url = talloc_strdup(tl, part->filename),
+ };
+
+ starttime = tl->parts[n].end;
+
+ if (source && !tl->track_layout && part->is_layout)
+ tl->track_layout = source;
+
+ tl->num_parts++;
+ }
+
+ if (tl->no_clip && tl->num_parts > 1)
+ MP_WARN(root, "Multiple parts with no_clip. Undefined behavior ahead.\n");
+
+ if (!tl->track_layout) {
+ // Use a heuristic to select the "broadest" part as layout.
+ for (int n = 0; n < parts->num_parts; n++) {
+ struct demuxer *s = tl->parts[n].source;
+ if (!s)
+ continue;
+ if (!tl->track_layout ||
+ demux_get_num_stream(s) > demux_get_num_stream(tl->track_layout))
+ tl->track_layout = s;
+ }
+ }
+
+ if (!tl->track_layout && !tl->delay_open)
+ goto error;
+ if (!root->meta)
+ root->meta = tl->track_layout;
+
+ // Not very sane, since demuxer fields are supposed to be treated read-only
+ // from outside, but happens to work in this case, so who cares.
+ if (root->meta)
+ mp_tags_merge(root->meta->metadata, edl_root->tags);
+
+ assert(tl->num_parts == parts->num_parts);
+ return tl;
+
+error:
+ root->num_pars = 0;
+ return NULL;
+}
+
+static void fix_filenames(struct tl_parts *parts, char *source_path)
+{
+ if (bstr_equals0(mp_split_proto(bstr0(source_path), NULL), "edl"))
+ return;
+ struct bstr dirname = mp_dirname(source_path);
+ for (int n = 0; n < parts->num_parts; n++) {
+ struct tl_part *part = &parts->parts[n];
+ if (!mp_is_url(bstr0(part->filename))) {
+ part->filename =
+ mp_path_join_bstr(parts, dirname, bstr0(part->filename));
+ }
+ }
+}
+
+static void build_mpv_edl_timeline(struct timeline *tl)
+{
+ struct priv *p = tl->demuxer->priv;
+
+ struct tl_root *root = parse_edl(p->data, tl->log);
+ if (!root) {
+ MP_ERR(tl, "Error in EDL.\n");
+ return;
+ }
+
+ bool all_dash = true;
+ bool all_no_clip = true;
+ bool all_single = true;
+
+ for (int n = 0; n < root->num_pars; n++) {
+ struct tl_parts *parts = root->pars[n];
+ fix_filenames(parts, tl->demuxer->filename);
+ struct timeline_par *par = build_timeline(tl, root, parts);
+ if (!par)
+ break;
+ all_dash &= par->dash;
+ all_no_clip &= par->no_clip;
+ all_single &= par->num_parts == 1;
+ }
+
+ if (all_dash) {
+ tl->format = "dash";
+ } else if (all_no_clip && all_single) {
+ tl->format = "multi";
+ } else {
+ tl->format = "edl";
+ }
+
+ talloc_free(root);
+}
+
+static int try_open_file(struct demuxer *demuxer, enum demux_check check)
+{
+ if (!demuxer->access_references)
+ return -1;
+
+ struct priv *p = talloc_zero(demuxer, struct priv);
+ demuxer->priv = p;
+ demuxer->fully_read = true;
+
+ struct stream *s = demuxer->stream;
+ if (s->info && strcmp(s->info->name, "edl") == 0) {
+ p->data = bstr0(s->path);
+ return 0;
+ }
+ if (check >= DEMUX_CHECK_UNSAFE) {
+ char header[sizeof(HEADER) - 1];
+ int len = stream_read_peek(s, header, sizeof(header));
+ if (len != strlen(HEADER) || memcmp(header, HEADER, len) != 0)
+ return -1;
+ }
+ p->data = stream_read_complete(s, demuxer, 1000000);
+ if (p->data.start == NULL)
+ return -1;
+ bstr_eatstart0(&p->data, HEADER);
+ demux_close_stream(demuxer);
+ return 0;
+}
+
+const struct demuxer_desc demuxer_desc_edl = {
+ .name = "edl",
+ .desc = "Edit decision list",
+ .open = try_open_file,
+ .load_timeline = build_mpv_edl_timeline,
+};
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
new file mode 100644
index 0000000..663fab4
--- /dev/null
+++ b/demux/demux_lavf.c
@@ -0,0 +1,1448 @@
+/*
+ * Copyright (C) 2004 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/avutil.h>
+#include <libavutil/avstring.h>
+#include <libavutil/mathematics.h>
+#include <libavutil/replaygain.h>
+#include <libavutil/display.h>
+#include <libavutil/opt.h>
+
+#include <libavutil/dovi_meta.h>
+
+#include "audio/chmap_avchannel.h"
+
+#include "common/msg.h"
+#include "common/tags.h"
+#include "common/av_common.h"
+#include "misc/bstr.h"
+#include "misc/charset_conv.h"
+#include "misc/thread_tools.h"
+
+#include "stream/stream.h"
+#include "demux.h"
+#include "stheader.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "options/path.h"
+
+#ifndef AV_DISPOSITION_TIMED_THUMBNAILS
+#define AV_DISPOSITION_TIMED_THUMBNAILS 0
+#endif
+#ifndef AV_DISPOSITION_STILL_IMAGE
+#define AV_DISPOSITION_STILL_IMAGE 0
+#endif
+
+#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
+#define PROBE_BUF_SIZE (10 * 1024 * 1024)
+
+
+// Should correspond to IO_BUFFER_SIZE in libavformat/aviobuf.c (not public)
+// libavformat (almost) always reads data in blocks of this size.
+#define BIO_BUFFER_SIZE 32768
+
+#define OPT_BASE_STRUCT struct demux_lavf_opts
+struct demux_lavf_opts {
+ int probesize;
+ int probeinfo;
+ int probescore;
+ float analyzeduration;
+ int buffersize;
+ bool allow_mimetype;
+ char *format;
+ char **avopts;
+ bool hacks;
+ char *sub_cp;
+ int rtsp_transport;
+ int linearize_ts;
+ bool propagate_opts;
+};
+
+const struct m_sub_options demux_lavf_conf = {
+ .opts = (const m_option_t[]) {
+ {"demuxer-lavf-probesize", OPT_INT(probesize), M_RANGE(32, INT_MAX)},
+ {"demuxer-lavf-probe-info", OPT_CHOICE(probeinfo,
+ {"no", 0}, {"yes", 1}, {"auto", -1}, {"nostreams", -2})},
+ {"demuxer-lavf-format", OPT_STRING(format)},
+ {"demuxer-lavf-analyzeduration", OPT_FLOAT(analyzeduration),
+ M_RANGE(0, 3600)},
+ {"demuxer-lavf-buffersize", OPT_INT(buffersize),
+ M_RANGE(1, 10 * 1024 * 1024), OPTDEF_INT(BIO_BUFFER_SIZE)},
+ {"demuxer-lavf-allow-mimetype", OPT_BOOL(allow_mimetype)},
+ {"demuxer-lavf-probescore", OPT_INT(probescore),
+ M_RANGE(1, AVPROBE_SCORE_MAX)},
+ {"demuxer-lavf-hacks", OPT_BOOL(hacks)},
+ {"demuxer-lavf-o", OPT_KEYVALUELIST(avopts)},
+ {"sub-codepage", OPT_STRING(sub_cp)},
+ {"rtsp-transport", OPT_CHOICE(rtsp_transport,
+ {"lavf", 0},
+ {"udp", 1},
+ {"tcp", 2},
+ {"http", 3},
+ {"udp_multicast", 4})},
+ {"demuxer-lavf-linearize-timestamps", OPT_CHOICE(linearize_ts,
+ {"no", 0}, {"auto", -1}, {"yes", 1})},
+ {"demuxer-lavf-propagate-opts", OPT_BOOL(propagate_opts)},
+ {0}
+ },
+ .size = sizeof(struct demux_lavf_opts),
+ .defaults = &(const struct demux_lavf_opts){
+ .probeinfo = -1,
+ .allow_mimetype = true,
+ .hacks = true,
+ // AVPROBE_SCORE_MAX/4 + 1 is the "recommended" limit. Below that, the
+ // user is supposed to retry with larger probe sizes until a higher
+ // value is reached.
+ .probescore = AVPROBE_SCORE_MAX/4 + 1,
+ .sub_cp = "auto",
+ .rtsp_transport = 2,
+ .linearize_ts = -1,
+ .propagate_opts = true,
+ },
+};
+
+struct format_hack {
+ const char *ff_name;
+ const char *mime_type;
+ int probescore;
+ float analyzeduration;
+ bool skipinfo : 1; // skip avformat_find_stream_info()
+ unsigned int if_flags; // additional AVInputFormat.flags flags
+ bool max_probe : 1; // use probescore only if max. probe size reached
+ bool ignore : 1; // blacklisted
+ bool no_stream : 1; // do not wrap struct stream as AVIOContext
+ bool use_stream_ids : 1; // has a meaningful native stream IDs (export it)
+ bool fully_read : 1; // set demuxer.fully_read flag
+ bool detect_charset : 1; // format is a small text file, possibly not UTF8
+ // 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 linearize_audio_ts : 1;// compensate timestamp resets (audio only)
+ bool fix_editlists : 1;
+ bool is_network : 1;
+ bool no_seek : 1;
+ bool no_pcm_seek : 1;
+ bool no_seek_on_no_duration : 1;
+ bool readall_on_no_streamseek : 1;
+};
+
+#define BLACKLIST(fmt) {fmt, .ignore = true}
+#define TEXTSUB(fmt) {fmt, .fully_read = true, .detect_charset = true}
+#define TEXTSUB_UTF8(fmt) {fmt, .fully_read = true}
+
+static const struct format_hack format_hacks[] = {
+ // for webradios
+ {"aac", "audio/aacp", 25, 0.5},
+ {"aac", "audio/aac", 25, 0.5},
+
+ // some mp3 files don't detect correctly (usually id3v2 too large)
+ {"mp3", "audio/mpeg", 24, 0.5},
+ {"mp3", NULL, 24, .max_probe = true},
+
+ {"hls", .no_stream = true, .clear_filepos = true},
+ {"dash", .no_stream = true, .clear_filepos = true},
+ {"sdp", .clear_filepos = true, .is_network = true, .no_seek = true},
+ {"mpeg", .use_stream_ids = true},
+ {"mpegts", .use_stream_ids = true},
+ {"mxf", .use_stream_ids = true},
+ {"avi", .use_stream_ids = true},
+ {"asf", .use_stream_ids = true},
+ {"mp4", .skipinfo = true, .fix_editlists = true, .no_pcm_seek = true,
+ .use_stream_ids = true},
+ {"matroska", .skipinfo = true, .no_pcm_seek = true, .use_stream_ids = true},
+
+ {"v4l2", .no_seek = true},
+ {"rtsp", .no_seek_on_no_duration = true},
+
+ // In theory, such streams might contain timestamps, but virtually none do.
+ {"h264", .if_flags = AVFMT_NOTIMESTAMPS },
+ {"hevc", .if_flags = AVFMT_NOTIMESTAMPS },
+
+ // Some Ogg shoutcast streams are essentially concatenated OGG files. They
+ // reset timestamps, which causes all sorts of problems.
+ {"ogg", .linearize_audio_ts = true, .use_stream_ids = true},
+
+ // At some point, FFmpeg lost the ability to read gif from unseekable
+ // streams.
+ {"gif", .readall_on_no_streamseek = true},
+
+ TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
+ TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
+ TEXTSUB("sami"), TEXTSUB("srt"), TEXTSUB("stl"), TEXTSUB("subviewer"),
+ TEXTSUB("subviewer1"), TEXTSUB("vplayer"), TEXTSUB("ass"),
+
+ TEXTSUB_UTF8("webvtt"),
+
+ // Useless non-sense, sometimes breaks MLP2 subreader.c fallback
+ BLACKLIST("tty"),
+ // Let's open files with extremely generic extensions (.bin) with a
+ // demuxer that doesn't have a probe function! NO.
+ BLACKLIST("bin"),
+ // Useless, does not work with custom streams.
+ BLACKLIST("image2"),
+ {0}
+};
+
+struct nested_stream {
+ AVIOContext *id;
+ int64_t last_bytes;
+};
+
+struct stream_info {
+ struct sh_stream *sh;
+ double last_key_pts;
+ double highest_pts;
+ double ts_offset;
+};
+
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 10, 100)
+ #define HAVE_IO_CLOSE2 1
+#else
+ #define HAVE_IO_CLOSE2 0
+#endif
+
+typedef struct lavf_priv {
+ struct stream *stream;
+ bool own_stream;
+ char *filename;
+ struct format_hack format_hack;
+ const AVInputFormat *avif;
+ int avif_flags;
+ AVFormatContext *avfc;
+ AVIOContext *pb;
+ struct stream_info **streams; // NULL for unknown streams
+ int num_streams;
+ char *mime_type;
+ double seek_delay;
+
+ struct demux_lavf_opts *opts;
+
+ bool pcm_seek_hack_disabled;
+ AVStream *pcm_seek_hack;
+ int pcm_seek_hack_packet_size;
+
+ int linearize_ts;
+ bool any_ts_fixed;
+
+ int retry_counter;
+
+ AVDictionary *av_opts;
+
+ // Proxying nested streams.
+ struct nested_stream *nested;
+ int num_nested;
+ int (*default_io_open)(struct AVFormatContext *s, AVIOContext **pb,
+ const char *url, int flags, AVDictionary **options);
+#if HAVE_IO_CLOSE2
+ int (*default_io_close2)(struct AVFormatContext *s, AVIOContext *pb);
+#else
+ void (*default_io_close)(struct AVFormatContext *s, AVIOContext *pb);
+#endif
+} lavf_priv_t;
+
+static void update_read_stats(struct demuxer *demuxer)
+{
+ lavf_priv_t *priv = demuxer->priv;
+
+ for (int n = 0; n < priv->num_nested; n++) {
+ struct nested_stream *nest = &priv->nested[n];
+
+ int64_t cur = nest->id->bytes_read;
+ int64_t new = cur - nest->last_bytes;
+ nest->last_bytes = cur;
+ demux_report_unbuffered_read_bytes(demuxer, new);
+ }
+}
+
+// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
+// on "," in general.
+static bool matches_avinputformat_name(struct lavf_priv *priv,
+ const char *name)
+{
+ const char *avifname = priv->avif->name;
+ while (1) {
+ const char *next = strchr(avifname, ',');
+ if (!next)
+ return !strcmp(avifname, name);
+ int len = next - avifname;
+ if (len == strlen(name) && !memcmp(avifname, name, len))
+ return true;
+ avifname = next + 1;
+ }
+}
+
+static int mp_read(void *opaque, uint8_t *buf, int size)
+{
+ struct demuxer *demuxer = opaque;
+ lavf_priv_t *priv = demuxer->priv;
+ struct stream *stream = priv->stream;
+ if (!stream)
+ return 0;
+
+ int ret = stream_read_partial(stream, buf, size);
+
+ MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
+ ret, stream, buf, size, stream_tell(stream), stream->eof);
+ return ret ? ret : AVERROR_EOF;
+}
+
+static int64_t mp_seek(void *opaque, int64_t pos, int whence)
+{
+ struct demuxer *demuxer = opaque;
+ lavf_priv_t *priv = demuxer->priv;
+ struct stream *stream = priv->stream;
+ if (!stream)
+ return -1;
+
+ MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
+ whence == SEEK_END ? "end" :
+ whence == SEEK_CUR ? "cur" :
+ whence == SEEK_SET ? "set" : "size");
+ if (whence == SEEK_END || whence == AVSEEK_SIZE) {
+ int64_t end = stream_get_size(stream);
+ if (end < 0)
+ return -1;
+ if (whence == AVSEEK_SIZE)
+ return end;
+ pos += end;
+ } else if (whence == SEEK_CUR) {
+ pos += stream_tell(stream);
+ } else if (whence != SEEK_SET) {
+ return -1;
+ }
+
+ if (pos < 0)
+ return -1;
+
+ int64_t current_pos = stream_tell(stream);
+ if (stream_seek(stream, pos) == 0) {
+ stream_seek(stream, current_pos);
+ return -1;
+ }
+
+ return pos;
+}
+
+static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
+{
+ struct demuxer *demuxer = opaque;
+ lavf_priv_t *priv = demuxer->priv;
+ struct stream *stream = priv->stream;
+
+ struct stream_avseek cmd = {
+ .stream_index = stream_idx,
+ .timestamp = ts,
+ .flags = flags,
+ };
+
+ if (stream && stream_control(stream, STREAM_CTRL_AVSEEK, &cmd) == STREAM_OK) {
+ stream_drop_buffers(stream);
+ return 0;
+ }
+ return AVERROR(ENOSYS);
+}
+
+static void list_formats(struct demuxer *demuxer)
+{
+ MP_INFO(demuxer, "Available lavf input formats:\n");
+ const AVInputFormat *fmt;
+ void *iter = NULL;
+ while ((fmt = av_demuxer_iterate(&iter)))
+ MP_INFO(demuxer, "%15s : %s\n", fmt->name, fmt->long_name);
+}
+
+static void convert_charset(struct demuxer *demuxer)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ char *cp = priv->opts->sub_cp;
+ if (!cp || !cp[0] || mp_charset_is_utf8(cp))
+ return;
+ bstr data = stream_read_complete(priv->stream, NULL, 128 * 1024 * 1024);
+ if (!data.start) {
+ MP_WARN(demuxer, "File too big (or error reading) - skip charset probing.\n");
+ return;
+ }
+ void *alloc = data.start;
+ cp = (char *)mp_charset_guess(priv, demuxer->log, data, cp, 0);
+ if (cp && !mp_charset_is_utf8(cp))
+ MP_INFO(demuxer, "Using subtitle charset: %s\n", cp);
+ // libavformat transparently converts UTF-16 to UTF-8
+ if (!mp_charset_is_utf16(cp) && !mp_charset_is_utf8(cp)) {
+ bstr conv = mp_iconv_to_utf8(demuxer->log, data, cp, MP_ICONV_VERBOSE);
+ if (conv.start && conv.start != data.start)
+ talloc_steal(alloc, conv.start);
+ if (conv.start)
+ data = conv;
+ }
+ if (data.start) {
+ priv->stream = stream_memory_open(demuxer->global, data.start, data.len);
+ priv->own_stream = true;
+ }
+ talloc_free(alloc);
+}
+
+static char *remove_prefix(char *s, const char *const *prefixes)
+{
+ for (int n = 0; prefixes[n]; n++) {
+ int len = strlen(prefixes[n]);
+ if (strncmp(s, prefixes[n], len) == 0)
+ return s + len;
+ }
+ return s;
+}
+
+static const char *const prefixes[] =
+ {"ffmpeg://", "lavf://", "avdevice://", "av://", NULL};
+
+static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ struct demux_lavf_opts *lavfdopts = priv->opts;
+ struct stream *s = priv->stream;
+
+ priv->filename = remove_prefix(s->url, prefixes);
+
+ char *avdevice_format = NULL;
+ if (s->info && strcmp(s->info->name, "avdevice") == 0) {
+ // always require filename in the form "format:filename"
+ char *sep = strchr(priv->filename, ':');
+ if (!sep) {
+ MP_FATAL(demuxer, "Must specify filename in 'format:filename' form\n");
+ return -1;
+ }
+ avdevice_format = talloc_strndup(priv, priv->filename,
+ sep - priv->filename);
+ priv->filename = sep + 1;
+ }
+
+ char *mime_type = s->mime_type;
+ if (!lavfdopts->allow_mimetype || !mime_type)
+ mime_type = "";
+
+ const AVInputFormat *forced_format = NULL;
+ const char *format = lavfdopts->format;
+ if (!format)
+ format = s->lavf_type;
+ if (!format)
+ format = avdevice_format;
+ if (format) {
+ if (strcmp(format, "help") == 0) {
+ list_formats(demuxer);
+ return -1;
+ }
+ forced_format = av_find_input_format(format);
+ if (!forced_format) {
+ MP_FATAL(demuxer, "Unknown lavf format %s\n", format);
+ return -1;
+ }
+ }
+
+ // HLS streams seems to be not well tagged, so matching mime type is not
+ // enough. Strip URL parameters and match extension.
+ bstr ext = bstr_get_ext(bstr_split(bstr0(priv->filename), "?#", NULL));
+ AVProbeData avpd = {
+ // Disable file-extension matching with normal checks, except for HLS
+ .filename = !bstrcasecmp0(ext, "m3u8") || !bstrcasecmp0(ext, "m3u") ||
+ check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
+ .buf_size = 0,
+ .buf = av_mallocz(PROBE_BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE),
+ .mime_type = lavfdopts->allow_mimetype ? mime_type : NULL,
+ };
+ if (!avpd.buf)
+ return -1;
+
+ bool final_probe = false;
+ do {
+ int score = 0;
+
+ if (forced_format) {
+ priv->avif = forced_format;
+ score = AVPROBE_SCORE_MAX;
+ } else {
+ int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
+ PROBE_BUF_SIZE);
+ nsize = stream_read_peek(s, avpd.buf, nsize);
+ if (nsize <= avpd.buf_size)
+ final_probe = true;
+ avpd.buf_size = nsize;
+
+ priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score);
+ }
+
+ if (priv->avif) {
+ MP_VERBOSE(demuxer, "Found '%s' at score=%d size=%d%s.\n",
+ priv->avif->name, score, avpd.buf_size,
+ forced_format ? " (forced)" : "");
+
+ for (int n = 0; lavfdopts->hacks && format_hacks[n].ff_name; n++) {
+ const struct format_hack *entry = &format_hacks[n];
+ if (!matches_avinputformat_name(priv, entry->ff_name))
+ continue;
+ if (entry->mime_type && strcasecmp(entry->mime_type, mime_type) != 0)
+ continue;
+ priv->format_hack = *entry;
+ break;
+ }
+
+ if (score >= lavfdopts->probescore)
+ break;
+
+ if (priv->format_hack.probescore &&
+ score >= priv->format_hack.probescore &&
+ (!priv->format_hack.max_probe || final_probe))
+ break;
+ }
+
+ priv->avif = NULL;
+ priv->format_hack = (struct format_hack){0};
+ } while (!final_probe);
+
+ av_free(avpd.buf);
+
+ if (priv->avif && !forced_format && priv->format_hack.ignore) {
+ MP_VERBOSE(demuxer, "Format blacklisted.\n");
+ priv->avif = NULL;
+ }
+
+ if (!priv->avif) {
+ MP_VERBOSE(demuxer, "No format found, try lowering probescore or forcing the format.\n");
+ return -1;
+ }
+
+ 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)
+ convert_charset(demuxer);
+
+ return 0;
+}
+
+static char *replace_idx_ext(void *ta_ctx, bstr f)
+{
+ if (f.len < 4 || f.start[f.len - 4] != '.')
+ return NULL;
+ char *ext = bstr_endswith0(f, "IDX") ? "SUB" : "sub"; // match case
+ return talloc_asprintf(ta_ctx, "%.*s.%s", BSTR_P(bstr_splice(f, 0, -4)), ext);
+}
+
+static void guess_and_set_vobsub_name(struct demuxer *demuxer, AVDictionary **d)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ if (!matches_avinputformat_name(priv, "vobsub"))
+ return;
+
+ void *tmp = talloc_new(NULL);
+ bstr bfilename = bstr0(priv->filename);
+ char *subname = NULL;
+ if (mp_is_url(bfilename)) {
+ // It might be a http URL, which has additional parameters after the
+ // end of the actual file path.
+ bstr start, end;
+ if (bstr_split_tok(bfilename, "?", &start, &end)) {
+ subname = replace_idx_ext(tmp, start);
+ if (subname)
+ subname = talloc_asprintf(tmp, "%s?%.*s", subname, BSTR_P(end));
+ }
+ }
+ if (!subname)
+ subname = replace_idx_ext(tmp, bfilename);
+ if (!subname)
+ subname = talloc_asprintf(tmp, "%.*s.sub", BSTR_P(bfilename));
+
+ MP_VERBOSE(demuxer, "Assuming associated .sub file: %s\n", subname);
+ av_dict_set(d, "sub_name", subname, 0);
+ talloc_free(tmp);
+}
+
+static void select_tracks(struct demuxer *demuxer, int start)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ for (int n = start; n < priv->num_streams; n++) {
+ struct sh_stream *stream = priv->streams[n]->sh;
+ AVStream *st = priv->avfc->streams[n];
+ bool selected = stream && demux_stream_is_selected(stream) &&
+ !stream->attached_picture;
+ st->discard = selected ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
+ }
+}
+
+static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
+ AVStream *st)
+{
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
+ AVPacketSideData *side_data = st->codecpar->coded_side_data;
+ int nb_side_data = st->codecpar->nb_coded_side_data;
+#else
+ AVPacketSideData *side_data = st->side_data;
+ int nb_side_data = st->nb_side_data;
+#endif
+ for (int i = 0; i < nb_side_data; i++) {
+ AVReplayGain *av_rgain;
+ struct replaygain_data *rgain;
+ AVPacketSideData *src_sd = &side_data[i];
+
+ if (src_sd->type != AV_PKT_DATA_REPLAYGAIN)
+ continue;
+
+ av_rgain = (AVReplayGain*)src_sd->data;
+ rgain = talloc_ptrtype(demuxer, rgain);
+ rgain->track_gain = rgain->album_gain = 0;
+ rgain->track_peak = rgain->album_peak = 1;
+
+ // Set values in *rgain, using track gain as a fallback for album gain
+ // if the latter is not present. This behavior matches that in
+ // demux/demux.c's decode_rgain; if you change this, please make
+ // equivalent changes there too.
+ if (av_rgain->track_gain != INT32_MIN && av_rgain->track_peak != 0.0) {
+ // Track gain is defined.
+ rgain->track_gain = av_rgain->track_gain / 100000.0f;
+ rgain->track_peak = av_rgain->track_peak / 100000.0f;
+
+ if (av_rgain->album_gain != INT32_MIN &&
+ av_rgain->album_peak != 0.0)
+ {
+ // Album gain is also defined.
+ rgain->album_gain = av_rgain->album_gain / 100000.0f;
+ rgain->album_peak = av_rgain->album_peak / 100000.0f;
+ } else {
+ // Album gain is undefined; fall back to track gain.
+ rgain->album_gain = rgain->track_gain;
+ rgain->album_peak = rgain->track_peak;
+ }
+ }
+
+ // This must be run only before the stream was added, otherwise there
+ // will be race conditions with accesses from the user thread.
+ assert(!sh->ds);
+ sh->codec->replaygain_data = rgain;
+ }
+}
+
+// Return a dictionary entry as (decimal) integer.
+static int dict_get_decimal(AVDictionary *dict, const char *entry, int def)
+{
+ AVDictionaryEntry *e = av_dict_get(dict, entry, NULL, 0);
+ if (e && e->value) {
+ char *end = NULL;
+ long int r = strtol(e->value, &end, 10);
+ if (end && !end[0] && r >= INT_MIN && r <= INT_MAX)
+ return r;
+ }
+ return def;
+}
+
+static bool is_image(AVStream *st, bool attached_picture, const AVInputFormat *avif)
+{
+ return st->nb_frames <= 1 && (
+ attached_picture ||
+ bstr_endswith0(bstr0(avif->name), "_pipe") ||
+ strcmp(avif->name, "alias_pix") == 0 ||
+ strcmp(avif->name, "gif") == 0 ||
+ strcmp(avif->name, "image2pipe") == 0 ||
+ (st->codecpar->codec_id == AV_CODEC_ID_AV1 && st->nb_frames == 1)
+ );
+}
+
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
+static inline const uint8_t *mp_av_stream_get_side_data(const AVStream *st,
+ enum AVPacketSideDataType type)
+{
+ const AVPacketSideData *sd;
+ sd = av_packet_side_data_get(st->codecpar->coded_side_data,
+ st->codecpar->nb_coded_side_data,
+ type);
+ return sd ? sd->data : NULL;
+}
+#else
+#define mp_av_stream_get_side_data(st, type) av_stream_get_side_data(st, type, NULL)
+#endif
+
+static void handle_new_stream(demuxer_t *demuxer, int i)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ AVFormatContext *avfc = priv->avfc;
+ AVStream *st = avfc->streams[i];
+ struct sh_stream *sh = NULL;
+ AVCodecParameters *codec = st->codecpar;
+ int lavc_delay = codec->initial_padding;
+
+ switch (codec->codec_type) {
+ case AVMEDIA_TYPE_AUDIO: {
+ sh = demux_alloc_sh_stream(STREAM_AUDIO);
+
+#if !HAVE_AV_CHANNEL_LAYOUT
+ // probably unneeded
+ mp_chmap_set_unknown(&sh->codec->channels, codec->channels);
+ if (codec->channel_layout)
+ mp_chmap_from_lavc(&sh->codec->channels, codec->channel_layout);
+#else
+ if (!mp_chmap_from_av_layout(&sh->codec->channels, &codec->ch_layout)) {
+ char layout[128] = {0};
+ MP_WARN(demuxer,
+ "Failed to convert channel layout %s to mpv one!\n",
+ av_channel_layout_describe(&codec->ch_layout,
+ layout, 128) < 0 ?
+ "undefined" : layout);
+ }
+#endif
+
+ sh->codec->samplerate = codec->sample_rate;
+ sh->codec->bitrate = codec->bit_rate;
+
+ double delay = 0;
+ if (codec->sample_rate > 0)
+ delay = lavc_delay / (double)codec->sample_rate;
+ priv->seek_delay = MPMAX(priv->seek_delay, delay);
+
+ export_replaygain(demuxer, sh, st);
+
+ sh->seek_preroll = delay;
+
+ break;
+ }
+ case AVMEDIA_TYPE_VIDEO: {
+ sh = demux_alloc_sh_stream(STREAM_VIDEO);
+
+ if ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) &&
+ !(st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS))
+ {
+ sh->attached_picture =
+ new_demux_packet_from_avpacket(&st->attached_pic);
+ if (sh->attached_picture) {
+ sh->attached_picture->pts = 0;
+ talloc_steal(sh, sh->attached_picture);
+ sh->attached_picture->keyframe = true;
+ }
+ }
+
+ if (!sh->attached_picture) {
+ // 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;
+ sh->codec->disp_h = codec->height;
+ if (st->avg_frame_rate.num)
+ sh->codec->fps = av_q2d(st->avg_frame_rate);
+ if (is_image(st, sh->attached_picture, priv->avif)) {
+ MP_VERBOSE(demuxer, "Assuming this is an image format.\n");
+ sh->image = true;
+ sh->codec->fps = demuxer->opts->mf_fps;
+ }
+ sh->codec->par_w = st->sample_aspect_ratio.num;
+ sh->codec->par_h = st->sample_aspect_ratio.den;
+
+ const uint8_t *sd = mp_av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX);
+ if (sd) {
+ double r = av_display_rotation_get((int32_t *)sd);
+ if (!isnan(r))
+ sh->codec->rotate = (((int)(-r) % 360) + 360) % 360;
+ }
+
+ if ((sd = mp_av_stream_get_side_data(st, AV_PKT_DATA_DOVI_CONF))) {
+ const AVDOVIDecoderConfigurationRecord *cfg = (void *) sd;
+ MP_VERBOSE(demuxer, "Found Dolby Vision config record: profile "
+ "%d level %d\n", cfg->dv_profile, cfg->dv_level);
+ av_format_inject_global_side_data(avfc);
+ }
+
+ // This also applies to vfw-muxed mkv, but we can't detect these easily.
+ sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
+
+ break;
+ }
+ case AVMEDIA_TYPE_SUBTITLE: {
+ sh = demux_alloc_sh_stream(STREAM_SUB);
+
+ if (codec->extradata_size) {
+ sh->codec->extradata = talloc_size(sh, codec->extradata_size);
+ memcpy(sh->codec->extradata, codec->extradata, codec->extradata_size);
+ sh->codec->extradata_size = codec->extradata_size;
+ }
+
+ if (matches_avinputformat_name(priv, "microdvd")) {
+ AVRational r;
+ if (av_opt_get_q(avfc, "subfps", AV_OPT_SEARCH_CHILDREN, &r) >= 0) {
+ // File headers don't have a FPS set.
+ if (r.num < 1 || r.den < 1)
+ sh->codec->frame_based = 23.976; // default timebase
+ }
+ }
+ break;
+ }
+ case AVMEDIA_TYPE_ATTACHMENT: {
+ AVDictionaryEntry *ftag = av_dict_get(st->metadata, "filename", NULL, 0);
+ char *filename = ftag ? ftag->value : NULL;
+ AVDictionaryEntry *mt = av_dict_get(st->metadata, "mimetype", NULL, 0);
+ char *mimetype = mt ? mt->value : NULL;
+ if (mimetype) {
+ demuxer_add_attachment(demuxer, filename, mimetype,
+ codec->extradata, codec->extradata_size);
+ }
+ break;
+ }
+ default: ;
+ }
+
+ 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);
+
+ if (sh) {
+ sh->ff_index = st->index;
+ sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
+ sh->codec->codec_tag = codec->codec_tag;
+ sh->codec->lav_codecpar = avcodec_parameters_alloc();
+ if (sh->codec->lav_codecpar)
+ avcodec_parameters_copy(sh->codec->lav_codecpar, codec);
+ sh->codec->native_tb_num = st->time_base.num;
+ sh->codec->native_tb_den = st->time_base.den;
+
+ if (st->disposition & AV_DISPOSITION_DEFAULT)
+ sh->default_track = true;
+ if (st->disposition & AV_DISPOSITION_FORCED)
+ sh->forced_track = true;
+ if (st->disposition & AV_DISPOSITION_DEPENDENT)
+ sh->dependent_track = true;
+ if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
+ sh->visual_impaired_track = true;
+ if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
+ sh->hearing_impaired_track = true;
+ if (st->disposition & AV_DISPOSITION_STILL_IMAGE)
+ sh->still_image = true;
+ if (priv->format_hack.use_stream_ids)
+ sh->demuxer_id = st->id;
+ AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
+ if (title && title->value)
+ sh->title = talloc_strdup(sh, title->value);
+ if (!sh->title && st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
+ sh->title = talloc_asprintf(sh, "visual impaired");
+ if (!sh->title && st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
+ sh->title = talloc_asprintf(sh, "hearing impaired");
+ AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
+ if (lang && lang->value && strcmp(lang->value, "und") != 0)
+ sh->lang = talloc_strdup(sh, lang->value);
+ sh->hls_bitrate = dict_get_decimal(st->metadata, "variant_bitrate", 0);
+ AVProgram *prog = av_find_program_from_stream(avfc, NULL, i);
+ if (prog)
+ sh->program_id = prog->id;
+ sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
+ mp_tags_move_from_av_dictionary(sh->tags, &st->metadata);
+ demux_add_sh_stream(demuxer, sh);
+
+ // Unfortunately, there is no better way to detect PCM codecs, other
+ // than listing them all manually. (Or other "frameless" codecs. Or
+ // rather, codecs with frames so small libavformat will put multiple of
+ // them into a single packet, but not preserve these artificial packet
+ // boundaries on seeking.)
+ if (sh->codec->codec && strncmp(sh->codec->codec, "pcm_", 4) == 0 &&
+ codec->block_align && !priv->pcm_seek_hack_disabled &&
+ priv->opts->hacks && !priv->format_hack.no_pcm_seek &&
+ st->time_base.num == 1 && st->time_base.den == codec->sample_rate)
+ {
+ if (priv->pcm_seek_hack) {
+ // More than 1 audio stream => usually doesn't apply.
+ priv->pcm_seek_hack_disabled = true;
+ priv->pcm_seek_hack = NULL;
+ } else {
+ priv->pcm_seek_hack = st;
+ }
+ }
+ }
+
+ select_tracks(demuxer, i);
+}
+
+// Add any new streams that might have been added
+static void add_new_streams(demuxer_t *demuxer)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ while (priv->num_streams < priv->avfc->nb_streams)
+ handle_new_stream(demuxer, priv->num_streams);
+}
+
+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_move_from_av_dictionary(demuxer->metadata, &priv->avfc->metadata);
+ priv->avfc->event_flags = 0;
+ demux_metadata_changed(demuxer);
+ }
+}
+
+static int interrupt_cb(void *ctx)
+{
+ struct demuxer *demuxer = ctx;
+ return mp_cancel_test(demuxer->cancel);
+}
+
+static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
+ const char *url, int flags, AVDictionary **options)
+{
+ struct demuxer *demuxer = s->opaque;
+ MP_ERR(demuxer, "Not opening '%s' due to --access-references=no.\n", url);
+ return AVERROR(EACCES);
+}
+
+static int nested_io_open(struct AVFormatContext *s, AVIOContext **pb,
+ const char *url, int flags, AVDictionary **options)
+{
+ struct demuxer *demuxer = s->opaque;
+ lavf_priv_t *priv = demuxer->priv;
+
+ if (priv->opts->propagate_opts) {
+ // Copy av_opts to options, but only entries that are not present in
+ // options. (Hope this will break less by not overwriting important
+ // settings.)
+ AVDictionaryEntry *cur = NULL;
+ while ((cur = av_dict_get(priv->av_opts, "", cur, AV_DICT_IGNORE_SUFFIX)))
+ {
+ if (!*options || !av_dict_get(*options, cur->key, NULL, 0)) {
+ MP_TRACE(demuxer, "Nested option: '%s'='%s'\n",
+ cur->key, cur->value);
+ av_dict_set(options, cur->key, cur->value, 0);
+ } else {
+ MP_TRACE(demuxer, "Skipping nested option: '%s'\n", cur->key);
+ }
+ }
+ }
+
+ int r = priv->default_io_open(s, pb, url, flags, options);
+ if (r >= 0) {
+ if (options)
+ mp_avdict_print_unset(demuxer->log, MSGL_TRACE, *options);
+ struct nested_stream nest = {
+ .id = *pb,
+ };
+ MP_TARRAY_APPEND(priv, priv->nested, priv->num_nested, nest);
+ }
+ return r;
+}
+
+#if HAVE_IO_CLOSE2
+static int nested_io_close2(struct AVFormatContext *s, AVIOContext *pb)
+#else
+static void nested_io_close(struct AVFormatContext *s, AVIOContext *pb)
+#endif
+{
+ struct demuxer *demuxer = s->opaque;
+ lavf_priv_t *priv = demuxer->priv;
+
+ for (int n = 0; n < priv->num_nested; n++) {
+ if (priv->nested[n].id == pb) {
+ MP_TARRAY_REMOVE_AT(priv->nested, priv->num_nested, n);
+ break;
+ }
+ }
+
+#if HAVE_IO_CLOSE2
+ return priv->default_io_close2(s, pb);
+#else
+ priv->default_io_close(s, pb);
+#endif
+}
+
+static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
+{
+ AVFormatContext *avfc = NULL;
+ AVDictionaryEntry *t = NULL;
+ float analyze_duration = 0;
+ lavf_priv_t *priv = talloc_zero(NULL, lavf_priv_t);
+ AVDictionary *dopts = NULL;
+
+ demuxer->priv = priv;
+ priv->stream = demuxer->stream;
+
+ priv->opts = mp_get_config_group(priv, demuxer->global, &demux_lavf_conf);
+ struct demux_lavf_opts *lavfdopts = priv->opts;
+
+ if (lavf_check_file(demuxer, check) < 0)
+ goto fail;
+
+ avfc = avformat_alloc_context();
+ if (!avfc)
+ goto fail;
+
+ if (demuxer->opts->index_mode != 1)
+ avfc->flags |= AVFMT_FLAG_IGNIDX;
+
+ if (lavfdopts->probesize) {
+ if (av_opt_set_int(avfc, "probesize", lavfdopts->probesize, 0) < 0)
+ MP_ERR(demuxer, "couldn't set option probesize to %u\n",
+ lavfdopts->probesize);
+ }
+
+ if (priv->format_hack.analyzeduration)
+ analyze_duration = priv->format_hack.analyzeduration;
+ if (lavfdopts->analyzeduration)
+ analyze_duration = lavfdopts->analyzeduration;
+ if (analyze_duration > 0) {
+ if (av_opt_set_int(avfc, "analyzeduration",
+ analyze_duration * AV_TIME_BASE, 0) < 0)
+ MP_ERR(demuxer, "demux_lavf, couldn't set option "
+ "analyzeduration to %f\n", analyze_duration);
+ }
+
+ if ((priv->avif_flags & AVFMT_NOFILE) || priv->format_hack.no_stream) {
+ mp_setup_av_network_options(&dopts, priv->avif->name,
+ demuxer->global, demuxer->log);
+ // This might be incorrect.
+ demuxer->seekable = true;
+ } else {
+ void *buffer = av_malloc(lavfdopts->buffersize);
+ if (!buffer)
+ goto fail;
+ priv->pb = avio_alloc_context(buffer, lavfdopts->buffersize, 0,
+ demuxer, mp_read, NULL, mp_seek);
+ if (!priv->pb) {
+ av_free(buffer);
+ goto fail;
+ }
+ priv->pb->read_seek = mp_read_seek;
+ priv->pb->seekable = demuxer->seekable ? AVIO_SEEKABLE_NORMAL : 0;
+ avfc->pb = priv->pb;
+ if (stream_control(priv->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
+ demuxer->seekable = true;
+ demuxer->seekable |= priv->format_hack.fully_read;
+ }
+
+ if (matches_avinputformat_name(priv, "rtsp")) {
+ const char *transport = NULL;
+ switch (lavfdopts->rtsp_transport) {
+ case 1: transport = "udp"; break;
+ case 2: transport = "tcp"; break;
+ case 3: transport = "http"; break;
+ case 4: transport = "udp_multicast"; break;
+ }
+ if (transport)
+ av_dict_set(&dopts, "rtsp_transport", transport, 0);
+ }
+
+ guess_and_set_vobsub_name(demuxer, &dopts);
+
+ if (priv->format_hack.fix_editlists)
+ av_dict_set(&dopts, "advanced_editlist", "0", 0);
+
+ avfc->interrupt_callback = (AVIOInterruptCB){
+ .callback = interrupt_cb,
+ .opaque = demuxer,
+ };
+
+ avfc->opaque = demuxer;
+ if (demuxer->access_references) {
+ priv->default_io_open = avfc->io_open;
+ avfc->io_open = nested_io_open;
+#if HAVE_IO_CLOSE2
+ priv->default_io_close2 = avfc->io_close2;
+ avfc->io_close2 = nested_io_close2;
+#else
+ priv->default_io_close = avfc->io_close;
+ avfc->io_close = nested_io_close;
+#endif
+ } else {
+ avfc->io_open = block_io_open;
+ }
+
+ mp_set_avdict(&dopts, lavfdopts->avopts);
+
+ if (av_dict_copy(&priv->av_opts, dopts, 0) < 0) {
+ MP_ERR(demuxer, "av_dict_copy() failed\n");
+ goto fail;
+ }
+
+ if (priv->format_hack.readall_on_no_streamseek && priv->pb &&
+ !priv->pb->seekable)
+ {
+ MP_VERBOSE(demuxer, "Non-seekable demuxer pre-read hack...\n");
+ // Read incremental to avoid unnecessary large buffer sizes.
+ int r = 0;
+ for (int n = 16; n < 29; n++) {
+ r = stream_peek(priv->stream, 1 << n);
+ if (r < (1 << n))
+ break;
+ }
+ MP_VERBOSE(demuxer, "...did read %d bytes.\n", r);
+ }
+
+ if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
+ MP_ERR(demuxer, "avformat_open_input() failed\n");
+ goto fail;
+ }
+
+ mp_avdict_print_unset(demuxer->log, MSGL_V, dopts);
+ av_dict_free(&dopts);
+
+ priv->avfc = avfc;
+
+ bool probeinfo = lavfdopts->probeinfo != 0;
+ switch (lavfdopts->probeinfo) {
+ case -2: probeinfo = priv->avfc->nb_streams == 0; break;
+ case -1: probeinfo = !priv->format_hack.skipinfo; break;
+ }
+ if (demuxer->params && demuxer->params->skip_lavf_probing)
+ probeinfo = false;
+ if (probeinfo) {
+ if (avformat_find_stream_info(avfc, NULL) < 0) {
+ MP_ERR(demuxer, "av_find_stream_info() failed\n");
+ goto fail;
+ }
+
+ MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
+ " bytes.\n", stream_tell(priv->stream));
+ }
+
+ for (int i = 0; i < avfc->nb_chapters; i++) {
+ AVChapter *c = avfc->chapters[i];
+ t = av_dict_get(c->metadata, "title", NULL, 0);
+ int index = demuxer_add_chapter(demuxer, t ? t->value : "",
+ c->start * av_q2d(c->time_base), i);
+ mp_tags_move_from_av_dictionary(demuxer->chapters[index].metadata, &c->metadata);
+ }
+
+ add_new_streams(demuxer);
+
+ mp_tags_move_from_av_dictionary(demuxer->metadata, &avfc->metadata);
+
+ demuxer->ts_resets_possible =
+ priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS);
+
+ 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;
+
+#ifdef AVFMTCTX_UNSEEKABLE
+ if (avfc->ctx_flags & AVFMTCTX_UNSEEKABLE)
+ demuxer->seekable = false;
+#endif
+
+ demuxer->is_network |= priv->format_hack.is_network;
+ demuxer->seekable &= !priv->format_hack.no_seek;
+
+ // We initially prefer track durations over container durations because they
+ // have a higher degree of precision over the container duration which are
+ // only accurate to the 6th decimal place. This is probably a lavf bug.
+ double total_duration = -1;
+ double av_duration = -1;
+ for (int n = 0; n < priv->avfc->nb_streams; n++) {
+ AVStream *st = priv->avfc->streams[n];
+ if (st->duration <= 0)
+ continue;
+ double f_duration = st->duration * av_q2d(st->time_base);
+ total_duration = MPMAX(total_duration, f_duration);
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ||
+ st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ av_duration = MPMAX(av_duration, f_duration);
+ }
+ double duration = av_duration > 0 ? av_duration : total_duration;
+ if (duration <= 0 && priv->avfc->duration > 0)
+ duration = (double)priv->avfc->duration / AV_TIME_BASE;
+ demuxer->duration = duration;
+
+ if (demuxer->duration < 0 && priv->format_hack.no_seek_on_no_duration)
+ demuxer->seekable = false;
+
+ // In some cases, libavformat will export bogus bullshit timestamps anyway,
+ // such as with mjpeg.
+ if (priv->avif_flags & AVFMT_NOTIMESTAMPS) {
+ MP_WARN(demuxer,
+ "This format is marked by FFmpeg as having no timestamps!\n"
+ "FFmpeg will likely make up its own broken timestamps. For\n"
+ "video streams you can correct this with:\n"
+ " --no-correct-pts --container-fps-override=VALUE\n"
+ "with VALUE being the real framerate of the stream. You can\n"
+ "expect seeking and buffering estimation to be generally\n"
+ "broken as well.\n");
+ }
+
+ if (demuxer->fully_read) {
+ demux_close_stream(demuxer);
+ if (priv->own_stream)
+ free_stream(priv->stream);
+ priv->own_stream = false;
+ priv->stream = NULL;
+ }
+
+ return 0;
+
+fail:
+ if (!priv->avfc)
+ avformat_free_context(avfc);
+ av_dict_free(&dopts);
+
+ return -1;
+}
+
+static bool demux_lavf_read_packet(struct demuxer *demux,
+ struct demux_packet **mp_pkt)
+{
+ lavf_priv_t *priv = demux->priv;
+
+ AVPacket *pkt = &(AVPacket){0};
+ int r = av_read_frame(priv->avfc, pkt);
+ update_read_stats(demux);
+ if (r < 0) {
+ av_packet_unref(pkt);
+ if (r == AVERROR_EOF)
+ return false;
+ MP_WARN(demux, "error reading packet: %s.\n", av_err2str(r));
+ if (priv->retry_counter >= 10) {
+ MP_ERR(demux, "...treating it as fatal error.\n");
+ return false;
+ }
+ priv->retry_counter += 1;
+ return true;
+ }
+ priv->retry_counter = 0;
+
+ add_new_streams(demux);
+ update_metadata(demux);
+
+ assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
+ 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)) {
+ av_packet_unref(pkt);
+ return true; // don't signal EOF if skipping a packet
+ }
+
+ struct demux_packet *dp = new_demux_packet_from_avpacket(pkt);
+ if (!dp) {
+ av_packet_unref(pkt);
+ return true;
+ }
+
+ if (priv->pcm_seek_hack == st && !priv->pcm_seek_hack_packet_size)
+ priv->pcm_seek_hack_packet_size = pkt->size;
+
+ dp->pts = mp_pts_from_av(pkt->pts, &st->time_base);
+ dp->dts = mp_pts_from_av(pkt->dts, &st->time_base);
+ dp->duration = pkt->duration * av_q2d(st->time_base);
+ dp->pos = pkt->pos;
+ dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
+ if (pkt->flags & AV_PKT_FLAG_DISCARD)
+ MP_ERR(demux, "Edit lists are not correctly supported (FFmpeg issue).\n");
+ av_packet_unref(pkt);
+
+ if (priv->format_hack.clear_filepos)
+ dp->pos = -1;
+
+ 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);
+ }
+ }
+
+ if (st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
+ st->event_flags = 0;
+ struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
+ mp_tags_move_from_av_dictionary(tags, &st->metadata);
+ double pts = MP_PTS_OR_DEF(dp->pts, dp->dts);
+ demux_stream_tags_changed(demux, stream, tags, pts);
+ }
+
+ *mp_pkt = dp;
+ return true;
+}
+
+static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ int avsflags = 0;
+ int64_t seek_pts_av = 0;
+ int seek_stream = -1;
+
+ 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 (priv->linearize_ts < 0)
+ priv->linearize_ts = 0;
+
+ if (!(flags & SEEK_FORWARD))
+ avsflags = AVSEEK_FLAG_BACKWARD;
+
+ if (flags & SEEK_FACTOR) {
+ struct stream *s = priv->stream;
+ int64_t end = s ? stream_get_size(s) : -1;
+ if (end > 0 && demuxer->ts_resets_possible &&
+ !(priv->avif_flags & AVFMT_NO_BYTE_SEEK))
+ {
+ avsflags |= AVSEEK_FLAG_BYTE;
+ seek_pts_av = end * seek_pts;
+ } else if (priv->avfc->duration != 0 &&
+ priv->avfc->duration != AV_NOPTS_VALUE)
+ {
+ seek_pts_av = seek_pts * priv->avfc->duration;
+ }
+ } else {
+ if (!(flags & SEEK_FORWARD))
+ seek_pts -= priv->seek_delay;
+ seek_pts_av = seek_pts * AV_TIME_BASE;
+ }
+
+ // Hack to make wav seeking "deterministic". Without this, features like
+ // backward playback won't work.
+ if (priv->pcm_seek_hack && !priv->pcm_seek_hack_packet_size) {
+ // This might for example be the initial seek. Fuck it up like the
+ // bullshit it is.
+ AVPacket *pkt = av_packet_alloc();
+ MP_HANDLE_OOM(pkt);
+ if (av_read_frame(priv->avfc, pkt) >= 0)
+ priv->pcm_seek_hack_packet_size = pkt->size;
+ av_packet_free(&pkt);
+ add_new_streams(demuxer);
+ }
+ if (priv->pcm_seek_hack && priv->pcm_seek_hack_packet_size &&
+ !(avsflags & AVSEEK_FLAG_BYTE))
+ {
+ int samples = priv->pcm_seek_hack_packet_size /
+ priv->pcm_seek_hack->codecpar->block_align;
+ if (samples > 0) {
+ MP_VERBOSE(demuxer, "using bullshit libavformat PCM seek hack\n");
+ double pts = seek_pts_av / (double)AV_TIME_BASE;
+ seek_pts_av = pts / av_q2d(priv->pcm_seek_hack->time_base);
+ int64_t align = seek_pts_av % samples;
+ seek_pts_av -= align;
+ seek_stream = priv->pcm_seek_hack->index;
+ }
+ }
+
+ int r = av_seek_frame(priv->avfc, seek_stream, seek_pts_av, avsflags);
+ if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
+ // When seeking before the beginning of the file, and seeking fails,
+ // try again without the backwards flag to make it seek to the
+ // beginning.
+ avsflags &= ~AVSEEK_FLAG_BACKWARD;
+ r = av_seek_frame(priv->avfc, seek_stream, seek_pts_av, avsflags);
+ }
+
+ if (r < 0) {
+ char buf[180];
+ av_strerror(r, buf, sizeof(buf));
+ MP_VERBOSE(demuxer, "Seek failed (%s)\n", buf);
+ }
+
+ update_read_stats(demuxer);
+}
+
+static void demux_lavf_switched_tracks(struct demuxer *demuxer)
+{
+ select_tracks(demuxer, 0);
+}
+
+static void demux_close_lavf(demuxer_t *demuxer)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ if (priv) {
+ // This will be a dangling pointer; but see below.
+ AVIOContext *leaking = priv->avfc ? priv->avfc->pb : NULL;
+ avformat_close_input(&priv->avfc);
+ // The ffmpeg garbage breaks its own API yet again: hls.c will call
+ // io_open on the main playlist, but never calls io_close. This happens
+ // to work out for us (since we don't really use custom I/O), but it's
+ // still weird. Compensate.
+ if (priv->num_nested == 1 && priv->nested[0].id == leaking)
+ priv->num_nested = 0;
+ if (priv->num_nested) {
+ MP_WARN(demuxer, "Leaking %d nested connections (FFmpeg bug).\n",
+ priv->num_nested);
+ }
+ if (priv->pb)
+ av_freep(&priv->pb->buffer);
+ av_freep(&priv->pb);
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct stream_info *info = priv->streams[n];
+ if (info->sh)
+ avcodec_parameters_free(&info->sh->codec->lav_codecpar);
+ }
+ if (priv->own_stream)
+ free_stream(priv->stream);
+ if (priv->av_opts)
+ av_dict_free(&priv->av_opts);
+ talloc_free(priv);
+ demuxer->priv = NULL;
+ }
+}
+
+
+const demuxer_desc_t demuxer_desc_lavf = {
+ .name = "lavf",
+ .desc = "libavformat",
+ .read_packet = demux_lavf_read_packet,
+ .open = demux_open_lavf,
+ .close = demux_close_lavf,
+ .seek = demux_seek_lavf,
+ .switched_tracks = demux_lavf_switched_tracks,
+};
diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c
new file mode 100644
index 0000000..ec50498
--- /dev/null
+++ b/demux/demux_libarchive.c
@@ -0,0 +1,120 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <archive.h>
+#include <archive_entry.h>
+
+#include "common/common.h"
+#include "common/playlist.h"
+#include "options/m_config.h"
+#include "stream/stream.h"
+#include "misc/natural_sort.h"
+#include "demux.h"
+
+#include "stream/stream_libarchive.h"
+
+struct demux_libarchive_opts {
+ bool rar_list_all_volumes;
+};
+
+static int cmp_filename(const void *a, const void *b)
+{
+ return mp_natural_sort_cmp(*(char **)a, *(char **)b);
+}
+
+static int open_file(struct demuxer *demuxer, enum demux_check check)
+{
+ if (!demuxer->access_references)
+ return -1;
+
+ int flags = 0;
+ int probe_size = STREAM_BUFFER_SIZE;
+ if (check <= DEMUX_CHECK_REQUEST) {
+ flags |= MP_ARCHIVE_FLAG_UNSAFE;
+ probe_size *= 100;
+ }
+
+ void *probe = ta_alloc_size(NULL, probe_size);
+ if (!probe)
+ return -1;
+ int probe_got = stream_read_peek(demuxer->stream, probe, probe_size);
+ struct stream *probe_stream =
+ stream_memory_open(demuxer->global, probe, probe_got);
+ struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags, 0);
+ bool ok = !!mpa;
+ free_stream(probe_stream);
+ mp_archive_free(mpa);
+ ta_free(probe);
+ if (!ok)
+ return -1;
+
+ struct demux_libarchive_opts *opts =
+ mp_get_config_group(demuxer, demuxer->global, demuxer->desc->options);
+
+ if (!opts->rar_list_all_volumes)
+ flags |= MP_ARCHIVE_FLAG_NO_VOLUMES;
+
+ mpa = mp_archive_new(demuxer->log, demuxer->stream, flags, 0);
+ if (!mpa)
+ return -1;
+
+ struct playlist *pl = talloc_zero(demuxer, struct playlist);
+ demuxer->playlist = pl;
+
+ char *prefix = mp_url_escape(mpa, demuxer->stream->url, "~|");
+
+ char **files = NULL;
+ int num_files = 0;
+
+ while (mp_archive_next_entry(mpa)) {
+ // stream_libarchive.c does the real work
+ char *f = talloc_asprintf(mpa, "archive://%s|/%s", prefix,
+ mpa->entry_filename);
+ MP_TARRAY_APPEND(mpa, files, num_files, f);
+ }
+
+ if (files)
+ qsort(files, num_files, sizeof(files[0]), cmp_filename);
+
+ for (int n = 0; n < num_files; n++)
+ playlist_add_file(pl, files[n]);
+
+ playlist_set_stream_flags(pl, demuxer->stream_origin);
+
+ demuxer->filetype = "archive";
+ demuxer->fully_read = true;
+
+ mp_archive_free(mpa);
+ demux_close_stream(demuxer);
+
+ return 0;
+}
+
+#define OPT_BASE_STRUCT struct demux_libarchive_opts
+
+const struct demuxer_desc demuxer_desc_libarchive = {
+ .name = "libarchive",
+ .desc = "libarchive wrapper",
+ .open = open_file,
+ .options = &(const struct m_sub_options){
+ .opts = (const struct m_option[]) {
+ {"rar-list-all-volumes", OPT_BOOL(rar_list_all_volumes)},
+ {0}
+ },
+ .size = sizeof(OPT_BASE_STRUCT),
+ },
+};
diff --git a/demux/demux_mf.c b/demux/demux_mf.c
new file mode 100644
index 0000000..8f7cb70
--- /dev/null
+++ b/demux/demux_mf.c
@@ -0,0 +1,373 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "osdep/io.h"
+
+#include "mpv_talloc.h"
+#include "common/msg.h"
+#include "options/options.h"
+#include "options/m_config.h"
+#include "options/path.h"
+#include "misc/ctype.h"
+
+#include "stream/stream.h"
+#include "demux.h"
+#include "stheader.h"
+#include "codec_tags.h"
+
+#define MF_MAX_FILE_SIZE (1024 * 1024 * 256)
+
+typedef struct mf {
+ struct mp_log *log;
+ struct sh_stream *sh;
+ int curr_frame;
+ int nr_of_files;
+ char **names;
+ // optional
+ struct stream **streams;
+} mf_t;
+
+
+static void mf_add(mf_t *mf, const char *fname)
+{
+ char *entry = talloc_strdup(mf, fname);
+ MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
+}
+
+static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename)
+{
+ struct mp_log *log = d->log;
+ int error_count = 0;
+ int count = 0;
+
+ mf_t *mf = talloc_zero(talloc_ctx, mf_t);
+ mf->log = log;
+
+ if (filename[0] == '@') {
+ struct stream *s = stream_create(filename + 1,
+ d->stream_origin | STREAM_READ, d->cancel, d->global);
+ if (s) {
+ while (1) {
+ char buf[512];
+ int len = stream_read_peek(s, buf, sizeof(buf));
+ if (!len)
+ break;
+ bstr data = (bstr){buf, len};
+ int pos = bstrchr(data, '\n');
+ data = bstr_splice(data, 0, pos < 0 ? data.len : pos + 1);
+ bstr fname = bstr_strip(data);
+ if (fname.len) {
+ if (bstrchr(fname, '\0') >= 0) {
+ mp_err(log, "invalid filename\n");
+ break;
+ }
+ char *entry = bstrto0(mf, fname);
+ if (!mp_path_exists(entry)) {
+ mp_verbose(log, "file not found: '%s'\n", entry);
+ } else {
+ MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
+ }
+ }
+ stream_seek_skip(s, stream_tell(s) + data.len);
+ }
+ free_stream(s);
+
+ mp_info(log, "number of files: %d\n", mf->nr_of_files);
+ goto exit_mf;
+ }
+ mp_info(log, "%s is not indirect filelist\n", filename + 1);
+ }
+
+ if (strchr(filename, ',')) {
+ mp_info(log, "filelist: %s\n", filename);
+ bstr bfilename = bstr0(filename);
+
+ while (bfilename.len) {
+ bstr bfname;
+ bstr_split_tok(bfilename, ",", &bfname, &bfilename);
+ char *fname2 = bstrdup0(mf, bfname);
+
+ if (!mp_path_exists(fname2))
+ mp_verbose(log, "file not found: '%s'\n", fname2);
+ else {
+ mf_add(mf, fname2);
+ }
+ talloc_free(fname2);
+ }
+ mp_info(log, "number of files: %d\n", mf->nr_of_files);
+
+ goto exit_mf;
+ }
+
+ size_t fname_avail = strlen(filename) + 32;
+ char *fname = talloc_size(mf, fname_avail);
+
+#if HAVE_GLOB
+ if (!strchr(filename, '%')) {
+ // append * if none present
+ snprintf(fname, fname_avail, "%s%c", filename,
+ strchr(filename, '*') ? 0 : '*');
+ mp_info(log, "search expr: %s\n", fname);
+
+ glob_t gg;
+ if (glob(fname, 0, NULL, &gg)) {
+ talloc_free(mf);
+ return NULL;
+ }
+
+ for (int i = 0; i < gg.gl_pathc; i++) {
+ if (mp_path_isdir(gg.gl_pathv[i]))
+ continue;
+ mf_add(mf, gg.gl_pathv[i]);
+ }
+ mp_info(log, "number of files: %d\n", mf->nr_of_files);
+ globfree(&gg);
+ goto exit_mf;
+ }
+#endif
+
+ // We're using arbitrary user input as printf format with 1 int argument.
+ // Any format which uses exactly 1 int argument would be valid, but for
+ // simplicity we reject all conversion specifiers except %% and simple
+ // integer specifier: %[.][NUM]d where NUM is 1-3 digits (%.d is valid)
+ const char *f = filename;
+ int MAXDIGS = 3, nspec = 0, c;
+ bool bad_spec = false;
+
+ while (nspec < 2 && (c = *f++)) {
+ if (c != '%')
+ continue;
+
+ if (*f == '%') {
+ // '%%', which ends up as an explicit % in the output.
+ // Skipping forwards as it doesn't require further attention.
+ f++;
+ continue;
+ }
+
+ // Now c == '%' and *f != '%', thus we have entered territory of format
+ // specifiers which we are interested in.
+ nspec++;
+
+ if (*f == '.')
+ f++;
+
+ for (int ndig = 0; mp_isdigit(*f) && ndig < MAXDIGS; ndig++, f++)
+ /* no-op */;
+
+ if (*f != 'd') {
+ bad_spec = true; // not int, or beyond our validation capacity
+ break;
+ }
+
+ // *f is 'd'
+ f++;
+ }
+
+ // nspec==0 (zero specifiers) is rejected because fname wouldn't advance.
+ if (bad_spec || nspec != 1) {
+ mp_err(log, "unsupported expr format: '%s'\n", filename);
+ goto exit_mf;
+ }
+
+ mp_info(log, "search expr: %s\n", filename);
+
+ while (error_count < 5) {
+ if (snprintf(fname, fname_avail, filename, count++) >= fname_avail) {
+ mp_err(log, "format result too long: '%s'\n", filename);
+ goto exit_mf;
+ }
+ if (!mp_path_exists(fname)) {
+ error_count++;
+ mp_verbose(log, "file not found: '%s'\n", fname);
+ } else {
+ mf_add(mf, fname);
+ }
+ }
+
+ mp_info(log, "number of files: %d\n", mf->nr_of_files);
+
+exit_mf:
+ return mf;
+}
+
+static mf_t *open_mf_single(void *talloc_ctx, struct mp_log *log, char *filename)
+{
+ mf_t *mf = talloc_zero(talloc_ctx, mf_t);
+ mf->log = log;
+ mf_add(mf, filename);
+ return mf;
+}
+
+static void demux_seek_mf(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ mf_t *mf = demuxer->priv;
+ double newpos = seek_pts * mf->sh->codec->fps;
+ if (flags & SEEK_FACTOR)
+ newpos = seek_pts * (mf->nr_of_files - 1);
+ if (flags & SEEK_FORWARD) {
+ newpos = ceil(newpos);
+ } else {
+ newpos = MPMIN(floor(newpos), mf->nr_of_files - 1);
+ }
+ mf->curr_frame = MPCLAMP((int)newpos, 0, mf->nr_of_files);
+}
+
+static bool demux_mf_read_packet(struct demuxer *demuxer,
+ struct demux_packet **pkt)
+{
+ mf_t *mf = demuxer->priv;
+ if (mf->curr_frame >= mf->nr_of_files)
+ return false;
+ bool ok = false;
+
+ struct stream *entry_stream = NULL;
+ if (mf->streams)
+ entry_stream = mf->streams[mf->curr_frame];
+ struct stream *stream = entry_stream;
+ if (!stream) {
+ char *filename = mf->names[mf->curr_frame];
+ if (filename) {
+ stream = stream_create(filename, demuxer->stream_origin | STREAM_READ,
+ demuxer->cancel, demuxer->global);
+ }
+ }
+
+ if (stream) {
+ stream_seek(stream, 0);
+ bstr data = stream_read_complete(stream, NULL, MF_MAX_FILE_SIZE);
+ if (data.len) {
+ demux_packet_t *dp = new_demux_packet(data.len);
+ if (dp) {
+ memcpy(dp->buffer, data.start, data.len);
+ dp->pts = mf->curr_frame / mf->sh->codec->fps;
+ dp->keyframe = true;
+ dp->stream = mf->sh->index;
+ *pkt = dp;
+ ok = true;
+ }
+ }
+ talloc_free(data.start);
+ }
+
+ if (stream && stream != entry_stream)
+ free_stream(stream);
+
+ mf->curr_frame++;
+
+ if (!ok)
+ MP_ERR(demuxer, "error reading image file\n");
+
+ return true;
+}
+
+static const char *probe_format(mf_t *mf, char *type, enum demux_check check)
+{
+ if (check > DEMUX_CHECK_REQUEST)
+ return NULL;
+ char *org_type = type;
+ if (!type || !type[0]) {
+ char *p = strrchr(mf->names[0], '.');
+ if (p)
+ type = p + 1;
+ }
+ const char *codec = mp_map_type_to_image_codec(type);
+ if (codec)
+ return codec;
+ if (check == DEMUX_CHECK_REQUEST) {
+ if (!org_type) {
+ MP_ERR(mf, "file type was not set! (try --mf-type=ext)\n");
+ } else {
+ MP_ERR(mf, "--mf-type set to an unknown codec!\n");
+ }
+ }
+ return NULL;
+}
+
+static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
+{
+ mf_t *mf;
+
+ if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
+ demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
+ {
+ mf = open_mf_pattern(demuxer, demuxer, demuxer->stream->url + 5);
+ } else {
+ mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
+ int bog = 0;
+ MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream);
+ }
+
+ if (!mf || mf->nr_of_files < 1)
+ goto error;
+
+ const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type);
+ if (!codec || (demuxer->opts->mf_type && demuxer->opts->mf_type[0]))
+ codec = probe_format(mf, demuxer->opts->mf_type, check);
+ if (!codec)
+ goto error;
+
+ mf->curr_frame = 0;
+
+ // create a new video stream header
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
+ if (mf->nr_of_files == 1) {
+ MP_VERBOSE(demuxer, "Assuming this is an image format.\n");
+ sh->image = true;
+ }
+
+ struct mp_codec_params *c = sh->codec;
+ c->codec = codec;
+ c->disp_w = 0;
+ c->disp_h = 0;
+ c->fps = demuxer->opts->mf_fps;
+ c->reliable_fps = true;
+
+ demux_add_sh_stream(demuxer, sh);
+
+ mf->sh = sh;
+ demuxer->priv = (void *)mf;
+ demuxer->seekable = true;
+ demuxer->duration = mf->nr_of_files / mf->sh->codec->fps;
+
+ return 0;
+
+error:
+ return -1;
+}
+
+static void demux_close_mf(demuxer_t *demuxer)
+{
+}
+
+const demuxer_desc_t demuxer_desc_mf = {
+ .name = "mf",
+ .desc = "image files (mf)",
+ .read_packet = demux_mf_read_packet,
+ .open = demux_open_mf,
+ .close = demux_close_mf,
+ .seek = demux_seek_mf,
+};
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
new file mode 100644
index 0000000..41226c5
--- /dev/null
+++ b/demux/demux_mkv.c
@@ -0,0 +1,3392 @@
+/*
+ * Matroska demuxer
+ * Copyright (C) 2004 Aurelien Jacobs <aurel@gnuage.org>
+ * Based on the one written by Ronald Bultje for gstreamer
+ * and on demux_mkv.cpp from Moritz Bunkus.
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <float.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+#include <libavutil/lzo.h>
+#include <libavutil/intreadwrite.h>
+#include <libavutil/avstring.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavcodec/version.h>
+
+#include "config.h"
+
+#if HAVE_ZLIB
+#include <zlib.h>
+#endif
+
+#include "mpv_talloc.h"
+#include "common/av_common.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "options/options.h"
+#include "misc/bstr.h"
+#include "stream/stream.h"
+#include "video/csputils.h"
+#include "video/mp_image.h"
+#include "demux.h"
+#include "stheader.h"
+#include "ebml.h"
+#include "matroska.h"
+#include "codec_tags.h"
+
+#include "common/msg.h"
+
+static const unsigned char sipr_swaps[38][2] = {
+ {0,63},{1,22},{2,44},{3,90},{5,81},{7,31},{8,86},{9,58},{10,36},{12,68},
+ {13,39},{14,73},{15,53},{16,69},{17,57},{19,88},{20,34},{21,71},{24,46},
+ {25,94},{26,54},{28,75},{29,50},{32,70},{33,92},{35,74},{38,85},{40,56},
+ {42,87},{43,65},{45,59},{48,79},{49,93},{51,89},{55,95},{61,76},{67,83},
+ {77,80}
+};
+
+// Map flavour to bytes per second
+#define SIPR_FLAVORS 4
+#define ATRC_FLAVORS 8
+#define COOK_FLAVORS 34
+static const int sipr_fl2bps[SIPR_FLAVORS] = { 813, 1062, 625, 2000 };
+static const int atrc_fl2bps[ATRC_FLAVORS] = {
+ 8269, 11714, 13092, 16538, 18260, 22050, 33075, 44100 };
+static const int cook_fl2bps[COOK_FLAVORS] = {
+ 1000, 1378, 2024, 2584, 4005, 5513, 8010, 4005, 750, 2498,
+ 4048, 5513, 8010, 11973, 8010, 2584, 4005, 2067, 2584, 2584,
+ 4005, 4005, 5513, 5513, 8010, 12059, 1550, 8010, 12059, 5513,
+ 12016, 16408, 22911, 33506
+};
+
+enum {
+ MAX_NUM_LACES = 256,
+};
+
+typedef struct mkv_content_encoding {
+ uint64_t order, type, scope;
+ uint64_t comp_algo;
+ uint8_t *comp_settings;
+ int comp_settings_len;
+} mkv_content_encoding_t;
+
+typedef struct mkv_track {
+ int tnum;
+ uint64_t uid;
+ char *name;
+ struct sh_stream *stream;
+
+ char *codec_id;
+ char *language;
+
+ int type;
+
+ uint32_t v_width, v_height, v_dwidth, v_dheight;
+ bool v_dwidth_set, v_dheight_set;
+ double v_frate;
+ uint32_t colorspace;
+ int stereo_mode;
+ struct mp_colorspace color;
+ uint32_t v_crop_top, v_crop_left, v_crop_right, v_crop_bottom;
+ float v_projection_pose_roll;
+ bool v_projection_pose_roll_set;
+
+ uint32_t a_channels, a_bps;
+ float a_sfreq;
+ float a_osfreq;
+
+ double default_duration;
+ double codec_delay;
+
+ int default_track;
+ int forced_track;
+
+ unsigned char *private_data;
+ unsigned int private_size;
+
+ bool parse;
+ int64_t parse_timebase;
+ void *parser_tmp;
+ AVCodecParserContext *av_parser;
+ AVCodecContext *av_parser_codec;
+
+ bool require_keyframes;
+
+ /* stuff for realaudio braincancer */
+ double ra_pts; /* previous audio timestamp */
+ uint32_t sub_packet_size; ///< sub packet size, per stream
+ uint32_t sub_packet_h; ///< number of coded frames per block
+ uint32_t coded_framesize; ///< coded frame size, per stream
+ uint32_t audiopk_size; ///< audio packet size
+ unsigned char *audio_buf; ///< place to store reordered audio data
+ double *audio_timestamp; ///< timestamp for each audio packet
+ uint32_t sub_packet_cnt; ///< number of subpacket already received
+
+ /* generic content encoding support */
+ mkv_content_encoding_t *encodings;
+ int num_encodings;
+
+ /* latest added index entry for this track */
+ size_t last_index_entry;
+} mkv_track_t;
+
+typedef struct mkv_index {
+ int tnum;
+ int64_t timecode, duration;
+ uint64_t filepos; // position of the cluster which contains the packet
+} mkv_index_t;
+
+struct block_info {
+ uint64_t duration, discardpadding;
+ bool simple, keyframe, duration_known;
+ int64_t timecode;
+ mkv_track_t *track;
+ // Actual packet data.
+ AVBufferRef *laces[MAX_NUM_LACES];
+ int num_laces;
+ int64_t filepos;
+ struct ebml_block_additions *additions;
+};
+
+typedef struct mkv_demuxer {
+ struct demux_mkv_opts *opts;
+
+ int64_t segment_start, segment_end;
+
+ double duration;
+
+ mkv_track_t **tracks;
+ int num_tracks;
+
+ struct ebml_tags *tags;
+
+ int64_t tc_scale, cluster_tc;
+
+ uint64_t cluster_start;
+ uint64_t cluster_end;
+
+ mkv_index_t *indexes;
+ size_t num_indexes;
+ bool index_complete;
+
+ int edition_id;
+
+ struct header_elem {
+ int32_t id;
+ int64_t pos;
+ bool parsed;
+ } *headers;
+ int num_headers;
+
+ int64_t skip_to_timecode;
+ int v_skip_to_keyframe, a_skip_to_keyframe;
+ int a_skip_preroll;
+ int subtitle_preroll;
+
+ bool index_has_durations;
+
+ bool eof_warning, keyframe_warning;
+
+ // Small queue of read but not yet returned packets. This is mostly
+ // temporary data, and not normally larger than 0 or 1 elements.
+ struct block_info *blocks;
+ int num_blocks;
+
+ // Packets to return.
+ struct demux_packet **packets;
+ int num_packets;
+
+ bool probably_webm_dash_init;
+} mkv_demuxer_t;
+
+#define OPT_BASE_STRUCT struct demux_mkv_opts
+struct demux_mkv_opts {
+ int subtitle_preroll;
+ double subtitle_preroll_secs;
+ double subtitle_preroll_secs_index;
+ int probe_duration;
+ bool probe_start_time;
+};
+
+const struct m_sub_options demux_mkv_conf = {
+ .opts = (const m_option_t[]) {
+ {"subtitle-preroll", OPT_CHOICE(subtitle_preroll,
+ {"no", 0}, {"yes", 1}, {"index", 2})},
+ {"subtitle-preroll-secs", OPT_DOUBLE(subtitle_preroll_secs),
+ M_RANGE(0, DBL_MAX)},
+ {"subtitle-preroll-secs-index", OPT_DOUBLE(subtitle_preroll_secs_index),
+ M_RANGE(0, DBL_MAX)},
+ {"probe-video-duration", OPT_CHOICE(probe_duration,
+ {"no", 0}, {"yes", 1}, {"full", 2})},
+ {"probe-start-time", OPT_BOOL(probe_start_time)},
+ {0}
+ },
+ .size = sizeof(struct demux_mkv_opts),
+ .defaults = &(const struct demux_mkv_opts){
+ .subtitle_preroll = 2,
+ .subtitle_preroll_secs = 1.0,
+ .subtitle_preroll_secs_index = 10.0,
+ .probe_start_time = true,
+ },
+};
+
+#define REALHEADER_SIZE 16
+#define RVPROPERTIES_SIZE 34
+#define RAPROPERTIES4_SIZE 56
+#define RAPROPERTIES5_SIZE 70
+
+// Maximum number of subtitle packets that are accepted for pre-roll.
+// (Subtitle packets added before first A/V keyframe packet is found with seek.)
+#define NUM_SUB_PREROLL_PACKETS 500
+
+static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos);
+static void probe_first_timestamp(struct demuxer *demuxer);
+static int read_next_block_into_queue(demuxer_t *demuxer);
+static void free_block(struct block_info *block);
+
+static void add_packet(struct demuxer *demuxer, struct sh_stream *stream,
+ struct demux_packet *pkt)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ if (!pkt)
+ return;
+
+ pkt->stream = stream->index;
+ MP_TARRAY_APPEND(mkv_d, mkv_d->packets, mkv_d->num_packets, pkt);
+}
+
+#define AAC_SYNC_EXTENSION_TYPE 0x02b7
+static int aac_get_sample_rate_index(uint32_t sample_rate)
+{
+ static const int srates[] = {
+ 92017, 75132, 55426, 46009, 37566, 27713,
+ 23004, 18783, 13856, 11502, 9391, 0
+ };
+ int i = 0;
+ while (sample_rate < srates[i])
+ i++;
+ return i;
+}
+
+static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
+ bstr data, uint32_t type)
+{
+ uint8_t *src = data.start;
+ uint8_t *orig_src = src;
+ uint8_t *dest = src;
+ uint32_t size = data.len;
+
+ for (int i = 0; i < track->num_encodings; i++) {
+ struct mkv_content_encoding *enc = track->encodings + i;
+ if (!(enc->scope & type))
+ continue;
+
+ if (src != dest && src != orig_src)
+ talloc_free(src);
+ src = dest; // output from last iteration is new source
+
+ if (enc->comp_algo == 0) {
+#if HAVE_ZLIB
+ /* zlib encoded track */
+
+ if (size == 0)
+ continue;
+
+ z_stream zstream;
+
+ zstream.zalloc = (alloc_func) 0;
+ zstream.zfree = (free_func) 0;
+ zstream.opaque = (voidpf) 0;
+ if (inflateInit(&zstream) != Z_OK) {
+ mp_warn(log, "zlib initialization failed.\n");
+ goto error;
+ }
+ zstream.next_in = (Bytef *) src;
+ zstream.avail_in = size;
+
+ dest = NULL;
+ zstream.avail_out = size;
+ int result;
+ do {
+ if (size >= INT_MAX - 4000) {
+ talloc_free(dest);
+ dest = NULL;
+ inflateEnd(&zstream);
+ goto error;
+ }
+ size += 4000;
+ dest = talloc_realloc_size(track->parser_tmp, dest, size);
+ zstream.next_out = (Bytef *) (dest + zstream.total_out);
+ result = inflate(&zstream, Z_NO_FLUSH);
+ if (result != Z_OK && result != Z_STREAM_END) {
+ mp_warn(log, "zlib decompression failed.\n");
+ talloc_free(dest);
+ dest = NULL;
+ inflateEnd(&zstream);
+ goto error;
+ }
+ zstream.avail_out += 4000;
+ } while (zstream.avail_out == 4000 && zstream.avail_in != 0
+ && result != Z_STREAM_END);
+
+ size = zstream.total_out;
+ inflateEnd(&zstream);
+#endif
+ } else if (enc->comp_algo == 2) {
+ /* lzo encoded track */
+ int out_avail;
+ int maxlen = INT_MAX - AV_LZO_OUTPUT_PADDING;
+ if (size >= maxlen / 3)
+ goto error;
+ int dstlen = size * 3;
+
+ dest = NULL;
+ while (1) {
+ int srclen = size;
+ dest = talloc_realloc_size(track->parser_tmp, dest,
+ dstlen + AV_LZO_OUTPUT_PADDING);
+ out_avail = dstlen;
+ int result = av_lzo1x_decode(dest, &out_avail, src, &srclen);
+ if (result == 0)
+ break;
+ if (!(result & AV_LZO_OUTPUT_FULL)) {
+ mp_warn(log, "lzo decompression failed.\n");
+ talloc_free(dest);
+ dest = NULL;
+ goto error;
+ }
+ mp_trace(log, "lzo decompression buffer too small.\n");
+ if (dstlen >= maxlen / 2) {
+ talloc_free(dest);
+ dest = NULL;
+ goto error;
+ }
+ dstlen = MPMAX(1, 2 * dstlen);
+ }
+ size = dstlen - out_avail;
+ } else if (enc->comp_algo == 3) {
+ dest = talloc_size(track->parser_tmp, size + enc->comp_settings_len);
+ memcpy(dest, enc->comp_settings, enc->comp_settings_len);
+ memcpy(dest + enc->comp_settings_len, src, size);
+ size += enc->comp_settings_len;
+ }
+ }
+
+ error:
+ if (src != dest && src != orig_src)
+ talloc_free(src);
+ if (!size)
+ dest = NULL;
+ return (bstr){dest, size};
+}
+
+
+static int demux_mkv_read_info(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ stream_t *s = demuxer->stream;
+ int res = 0;
+
+ MP_DBG(demuxer, "|+ segment information...\n");
+
+ mkv_d->tc_scale = 1000000;
+ mkv_d->duration = 0;
+
+ struct ebml_info info = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &info, &ebml_info_desc) < 0)
+ return -1;
+ if (info.muxing_app)
+ MP_DBG(demuxer, "| + muxing app: %s\n", info.muxing_app);
+ if (info.writing_app)
+ MP_DBG(demuxer, "| + writing app: %s\n", info.writing_app);
+ if (info.n_timecode_scale) {
+ mkv_d->tc_scale = info.timecode_scale;
+ MP_DBG(demuxer, "| + timecode scale: %"PRId64"\n", mkv_d->tc_scale);
+ if (mkv_d->tc_scale < 1 || mkv_d->tc_scale > INT_MAX) {
+ res = -1;
+ goto out;
+ }
+ }
+ if (info.n_duration) {
+ mkv_d->duration = info.duration * mkv_d->tc_scale / 1e9;
+ MP_DBG(demuxer, "| + duration: %.3fs\n",
+ mkv_d->duration);
+ demuxer->duration = mkv_d->duration;
+ }
+ if (info.title) {
+ mp_tags_set_str(demuxer->metadata, "TITLE", info.title);
+ }
+ if (info.n_segment_uid) {
+ size_t len = info.segment_uid.len;
+ if (len != sizeof(demuxer->matroska_data.uid.segment)) {
+ MP_INFO(demuxer, "segment uid invalid length %zu\n", len);
+ } else {
+ memcpy(demuxer->matroska_data.uid.segment, info.segment_uid.start,
+ len);
+ MP_DBG(demuxer, "| + segment uid");
+ for (size_t i = 0; i < len; i++)
+ MP_DBG(demuxer, " %02x",
+ demuxer->matroska_data.uid.segment[i]);
+ MP_DBG(demuxer, "\n");
+ }
+ }
+ if (demuxer->params && demuxer->params->matroska_wanted_uids) {
+ if (info.n_segment_uid) {
+ for (int i = 0; i < demuxer->params->matroska_num_wanted_uids; i++) {
+ struct matroska_segment_uid *uid = demuxer->params->matroska_wanted_uids + i;
+ if (!memcmp(info.segment_uid.start, uid->segment, 16)) {
+ demuxer->matroska_data.uid.edition = uid->edition;
+ goto out;
+ }
+ }
+ }
+ MP_VERBOSE(demuxer, "This is not one of the wanted files. "
+ "Stopping attempt to open.\n");
+ res = -2;
+ }
+ out:
+ talloc_free(parse_ctx.talloc_ctx);
+ return res;
+}
+
+static void parse_trackencodings(struct demuxer *demuxer,
+ struct mkv_track *track,
+ struct ebml_content_encodings *encodings)
+{
+ // initial allocation to be a non-NULL context before realloc
+ mkv_content_encoding_t *ce = talloc_size(track, 1);
+
+ for (int n_enc = 0; n_enc < encodings->n_content_encoding; n_enc++) {
+ struct ebml_content_encoding *enc = encodings->content_encoding + n_enc;
+ struct mkv_content_encoding e = {0};
+ e.order = enc->content_encoding_order;
+ if (enc->n_content_encoding_scope)
+ e.scope = enc->content_encoding_scope;
+ else
+ e.scope = 1;
+ e.type = enc->content_encoding_type;
+
+ if (enc->n_content_compression) {
+ struct ebml_content_compression *z = &enc->content_compression;
+ e.comp_algo = z->content_comp_algo;
+ if (z->n_content_comp_settings) {
+ int sz = z->content_comp_settings.len;
+ e.comp_settings = talloc_size(ce, sz);
+ memcpy(e.comp_settings, z->content_comp_settings.start, sz);
+ e.comp_settings_len = sz;
+ }
+ }
+
+ if (e.type == 1) {
+ MP_WARN(demuxer, "Track "
+ "number %d has been encrypted and "
+ "decryption has not yet been\n"
+ "implemented. Skipping track.\n",
+ track->tnum);
+ } else if (e.type != 0) {
+ MP_WARN(demuxer, "Unknown content encoding type for "
+ "track %u. Skipping track.\n",
+ track->tnum);
+ } else if (e.comp_algo != 0 && e.comp_algo != 2 && e.comp_algo != 3) {
+ MP_WARN(demuxer, "Track %d has been compressed with "
+ "an unknown/unsupported compression\n"
+ "algorithm (%"PRIu64"). Skipping track.\n",
+ track->tnum, e.comp_algo);
+ }
+#if !HAVE_ZLIB
+ else if (e.comp_algo == 0) {
+ MP_WARN(demuxer, "Track %d was compressed with zlib "
+ "but mpv has not been compiled\n"
+ "with support for zlib compression. "
+ "Skipping track.\n",
+ track->tnum);
+ }
+#endif
+ int i;
+ for (i = 0; i < n_enc; i++) {
+ if (e.order >= ce[i].order)
+ break;
+ }
+ ce = talloc_realloc(track, ce, mkv_content_encoding_t, n_enc + 1);
+ memmove(ce + i + 1, ce + i, (n_enc - i) * sizeof(*ce));
+ memcpy(ce + i, &e, sizeof(e));
+ }
+
+ track->encodings = ce;
+ track->num_encodings = encodings->n_content_encoding;
+}
+
+static void parse_trackaudio(struct demuxer *demuxer, struct mkv_track *track,
+ struct ebml_audio *audio)
+{
+ if (audio->n_sampling_frequency) {
+ track->a_sfreq = audio->sampling_frequency;
+ MP_DBG(demuxer, "| + Sampling frequency: %f\n", track->a_sfreq);
+ } else {
+ track->a_sfreq = 8000;
+ }
+ if (audio->n_output_sampling_frequency) {
+ track->a_osfreq = audio->output_sampling_frequency;
+ MP_DBG(demuxer, "| + Output sampling frequency: %f\n", track->a_osfreq);
+ } else {
+ track->a_osfreq = track->a_sfreq;
+ }
+ if (audio->n_bit_depth) {
+ track->a_bps = audio->bit_depth;
+ MP_DBG(demuxer, "| + Bit depth: %"PRIu32"\n", track->a_bps);
+ }
+ if (audio->n_channels) {
+ track->a_channels = audio->channels;
+ MP_DBG(demuxer, "| + Channels: %"PRIu32"\n", track->a_channels);
+ } else {
+ track->a_channels = 1;
+ }
+}
+
+static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
+ struct ebml_colour *colour)
+{
+ // Note: As per matroska spec, the order is consistent with ISO/IEC
+ // 23001-8:2013/DCOR1, which is the same order used by libavutil/pixfmt.h,
+ // so we can just re-use our avcol_ conversion functions.
+ if (colour->n_matrix_coefficients) {
+ track->color.space = avcol_spc_to_mp_csp(colour->matrix_coefficients);
+ MP_DBG(demuxer, "| + Matrix: %s\n",
+ m_opt_choice_str(mp_csp_names, track->color.space));
+ }
+ if (colour->n_primaries) {
+ track->color.primaries = avcol_pri_to_mp_csp_prim(colour->primaries);
+ MP_DBG(demuxer, "| + Primaries: %s\n",
+ m_opt_choice_str(mp_csp_prim_names, track->color.primaries));
+ }
+ if (colour->n_transfer_characteristics) {
+ track->color.gamma = avcol_trc_to_mp_csp_trc(colour->transfer_characteristics);
+ MP_DBG(demuxer, "| + Gamma: %s\n",
+ m_opt_choice_str(mp_csp_trc_names, track->color.gamma));
+ }
+ if (colour->n_range) {
+ track->color.levels = avcol_range_to_mp_csp_levels(colour->range);
+ MP_DBG(demuxer, "| + Levels: %s\n",
+ m_opt_choice_str(mp_csp_levels_names, track->color.levels));
+ }
+ if (colour->n_max_cll) {
+ track->color.hdr.max_cll = colour->max_cll;
+ MP_DBG(demuxer, "| + MaxCLL: %"PRIu64"\n", colour->max_cll);
+ }
+ if (colour->n_max_fall) {
+ track->color.hdr.max_fall = colour->max_fall;
+ MP_DBG(demuxer, "| + MaxFALL: %"PRIu64"\n", colour->max_cll);
+ }
+ if (colour->n_mastering_metadata) {
+ struct ebml_mastering_metadata *mastering = &colour->mastering_metadata;
+
+ if (mastering->n_primary_r_chromaticity_x) {
+ track->color.hdr.prim.red.x = mastering->primary_r_chromaticity_x;
+ MP_DBG(demuxer, "| + PrimaryRChromaticityX: %f\n", track->color.hdr.prim.red.x);
+ }
+ if (mastering->n_primary_r_chromaticity_y) {
+ track->color.hdr.prim.red.y = mastering->primary_r_chromaticity_y;
+ MP_DBG(demuxer, "| + PrimaryRChromaticityY: %f\n", track->color.hdr.prim.red.y);
+ }
+ if (mastering->n_primary_g_chromaticity_x) {
+ track->color.hdr.prim.green.x = mastering->primary_g_chromaticity_x;
+ MP_DBG(demuxer, "| + PrimaryGChromaticityX: %f\n", track->color.hdr.prim.green.x);
+ }
+ if (mastering->n_primary_g_chromaticity_y) {
+ track->color.hdr.prim.green.y = mastering->primary_g_chromaticity_y;
+ MP_DBG(demuxer, "| + PrimaryGChromaticityY: %f\n", track->color.hdr.prim.green.y);
+ }
+ if (mastering->n_primary_b_chromaticity_x) {
+ track->color.hdr.prim.blue.x = mastering->primary_b_chromaticity_x;
+ MP_DBG(demuxer, "| + PrimaryBChromaticityX: %f\n", track->color.hdr.prim.blue.x);
+ }
+ if (mastering->n_primary_b_chromaticity_y) {
+ track->color.hdr.prim.blue.y = mastering->primary_b_chromaticity_y;
+ MP_DBG(demuxer, "| + PrimaryBChromaticityY: %f\n", track->color.hdr.prim.blue.y);
+ }
+ if (mastering->n_white_point_chromaticity_x) {
+ track->color.hdr.prim.white.x = mastering->white_point_chromaticity_x;
+ MP_DBG(demuxer, "| + WhitePointChromaticityX: %f\n", track->color.hdr.prim.white.x);
+ }
+ if (mastering->n_white_point_chromaticity_y) {
+ track->color.hdr.prim.white.y = mastering->white_point_chromaticity_y;
+ MP_DBG(demuxer, "| + WhitePointChromaticityY: %f\n", track->color.hdr.prim.white.y);
+ }
+ if (mastering->n_luminance_min) {
+ track->color.hdr.min_luma = mastering->luminance_min;
+ MP_DBG(demuxer, "| + LuminanceMin: %f\n", track->color.hdr.min_luma);
+ }
+ if (mastering->n_luminance_max) {
+ track->color.hdr.max_luma = mastering->luminance_max;
+ MP_DBG(demuxer, "| + LuminanceMax: %f\n", track->color.hdr.max_luma);
+ }
+ }
+}
+
+static void parse_trackprojection(struct demuxer *demuxer, struct mkv_track *track,
+ struct ebml_projection *projection)
+{
+ if (projection->n_projection_pose_yaw || projection->n_projection_pose_pitch)
+ MP_WARN(demuxer, "Projection pose yaw/pitch not supported!\n");
+
+ if (projection->n_projection_pose_roll) {
+ track->v_projection_pose_roll = projection->projection_pose_roll;
+ track->v_projection_pose_roll_set = true;
+ MP_DBG(demuxer, "| + Projection pose roll: %f\n",
+ track->v_projection_pose_roll);
+ }
+}
+
+static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
+ struct ebml_video *video)
+{
+ if (video->n_frame_rate) {
+ MP_DBG(demuxer, "| + Frame rate: %f (ignored)\n", video->frame_rate);
+ }
+ if (video->n_display_width) {
+ track->v_dwidth = video->display_width;
+ track->v_dwidth_set = true;
+ MP_DBG(demuxer, "| + Display width: %"PRIu32"\n", track->v_dwidth);
+ }
+ if (video->n_display_height) {
+ track->v_dheight = video->display_height;
+ track->v_dheight_set = true;
+ MP_DBG(demuxer, "| + Display height: %"PRIu32"\n", track->v_dheight);
+ }
+ if (video->n_pixel_width) {
+ track->v_width = video->pixel_width;
+ MP_DBG(demuxer, "| + Pixel width: %"PRIu32"\n", track->v_width);
+ }
+ if (video->n_pixel_height) {
+ track->v_height = video->pixel_height;
+ MP_DBG(demuxer, "| + Pixel height: %"PRIu32"\n", track->v_height);
+ }
+ if (video->n_colour_space && video->colour_space.len == 4) {
+ uint8_t *d = (uint8_t *)&video->colour_space.start[0];
+ track->colorspace = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
+ MP_DBG(demuxer, "| + Colorspace: %#"PRIx32"\n", track->colorspace);
+ }
+ if (video->n_stereo_mode) {
+ const char *name = MP_STEREO3D_NAME(video->stereo_mode);
+ if (name) {
+ track->stereo_mode = video->stereo_mode;
+ MP_DBG(demuxer, "| + StereoMode: %s\n", name);
+ } else {
+ MP_WARN(demuxer, "Unknown StereoMode: %"PRIu64"\n",
+ video->stereo_mode);
+ }
+ }
+ if (video->n_pixel_crop_top) {
+ track->v_crop_top = video->pixel_crop_top;
+ MP_DBG(demuxer, "| + Crop top: %"PRIu32"\n", track->v_crop_top);
+ }
+ if (video->n_pixel_crop_left) {
+ track->v_crop_left = video->pixel_crop_left;
+ MP_DBG(demuxer, "| + Crop left: %"PRIu32"\n", track->v_crop_left);
+ }
+ if (video->n_pixel_crop_right) {
+ track->v_crop_right = video->pixel_crop_right;
+ MP_DBG(demuxer, "| + Crop right: %"PRIu32"\n", track->v_crop_right);
+ }
+ if (video->n_pixel_crop_bottom) {
+ track->v_crop_bottom = video->pixel_crop_bottom;
+ MP_DBG(demuxer, "| + Crop bottom: %"PRIu32"\n", track->v_crop_bottom);
+ }
+ if (video->n_colour)
+ parse_trackcolour(demuxer, track, &video->colour);
+ if (video->n_projection)
+ parse_trackprojection(demuxer, track, &video->projection);
+}
+
+/**
+ * \brief free any data associated with given track
+ * \param track track of which to free data
+ */
+static void demux_mkv_free_trackentry(mkv_track_t *track)
+{
+ talloc_free(track->parser_tmp);
+ talloc_free(track);
+}
+
+static void parse_trackentry(struct demuxer *demuxer,
+ struct ebml_track_entry *entry)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ struct mkv_track *track = talloc_zero(NULL, struct mkv_track);
+ track->last_index_entry = (size_t)-1;
+ track->parser_tmp = talloc_new(track);
+
+ track->tnum = entry->track_number;
+ if (track->tnum) {
+ MP_DBG(demuxer, "| + Track number: %d\n", track->tnum);
+ } else {
+ MP_ERR(demuxer, "Missing track number!\n");
+ }
+ track->uid = entry->track_uid;
+
+ if (entry->name) {
+ track->name = talloc_strdup(track, entry->name);
+ MP_DBG(demuxer, "| + Name: %s\n", track->name);
+ }
+
+ track->type = entry->track_type;
+ MP_DBG(demuxer, "| + Track type: ");
+ switch (track->type) {
+ case MATROSKA_TRACK_AUDIO:
+ MP_DBG(demuxer, "Audio\n");
+ break;
+ case MATROSKA_TRACK_VIDEO:
+ MP_DBG(demuxer, "Video\n");
+ break;
+ case MATROSKA_TRACK_SUBTITLE:
+ MP_DBG(demuxer, "Subtitle\n");
+ break;
+ default:
+ MP_DBG(demuxer, "unknown\n");
+ break;
+ }
+
+ if (entry->n_audio) {
+ MP_DBG(demuxer, "| + Audio track\n");
+ parse_trackaudio(demuxer, track, &entry->audio);
+ }
+
+ if (entry->n_video) {
+ MP_DBG(demuxer, "| + Video track\n");
+ parse_trackvideo(demuxer, track, &entry->video);
+ }
+
+ if (entry->codec_id) {
+ track->codec_id = talloc_strdup(track, entry->codec_id);
+ MP_DBG(demuxer, "| + Codec ID: %s\n", track->codec_id);
+ } else {
+ MP_ERR(demuxer, "Missing codec ID!\n");
+ track->codec_id = "";
+ }
+
+ if (entry->n_codec_private && entry->codec_private.len <= 0x10000000) {
+ int len = entry->codec_private.len;
+ track->private_data = talloc_size(track, len + AV_LZO_INPUT_PADDING);
+ memcpy(track->private_data, entry->codec_private.start, len);
+ track->private_size = len;
+ MP_DBG(demuxer, "| + CodecPrivate, length %u\n", track->private_size);
+ }
+
+ if (entry->language) {
+ track->language = talloc_strdup(track, entry->language);
+ MP_DBG(demuxer, "| + Language: %s\n", track->language);
+ } else {
+ track->language = talloc_strdup(track, "eng");
+ }
+
+ if (entry->n_flag_default) {
+ track->default_track = entry->flag_default;
+ MP_DBG(demuxer, "| + Default flag: %d\n", track->default_track);
+ } else {
+ track->default_track = 1;
+ }
+
+ if (entry->n_flag_forced) {
+ track->forced_track = entry->flag_forced;
+ MP_DBG(demuxer, "| + Forced flag: %d\n", track->forced_track);
+ }
+
+ if (entry->n_default_duration) {
+ track->default_duration = entry->default_duration / 1e9;
+ if (entry->default_duration == 0) {
+ MP_DBG(demuxer, "| + Default duration: 0");
+ } else {
+ track->v_frate = 1e9 / entry->default_duration;
+ MP_DBG(demuxer, "| + Default duration: %.3fms ( = %.3f fps)\n",
+ entry->default_duration / 1000000.0, track->v_frate);
+ }
+ }
+
+ if (entry->n_content_encodings)
+ parse_trackencodings(demuxer, track, &entry->content_encodings);
+
+ if (entry->n_codec_delay)
+ track->codec_delay = entry->codec_delay / 1e9;
+
+ mkv_d->tracks[mkv_d->num_tracks++] = track;
+}
+
+static int demux_mkv_read_tracks(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ stream_t *s = demuxer->stream;
+
+ MP_DBG(demuxer, "|+ segment tracks...\n");
+
+ struct ebml_tracks tracks = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &tracks, &ebml_tracks_desc) < 0)
+ return -1;
+
+ mkv_d->tracks = talloc_zero_array(mkv_d, struct mkv_track*,
+ tracks.n_track_entry);
+ for (int i = 0; i < tracks.n_track_entry; i++) {
+ MP_DBG(demuxer, "| + a track...\n");
+ parse_trackentry(demuxer, &tracks.track_entry[i]);
+ }
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+}
+
+static void cue_index_add(demuxer_t *demuxer, int track_id, uint64_t filepos,
+ int64_t timecode, int64_t duration)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+
+ MP_TARRAY_GROW(mkv_d, mkv_d->indexes, mkv_d->num_indexes);
+
+ mkv_d->indexes[mkv_d->num_indexes] = (mkv_index_t) {
+ .tnum = track_id,
+ .filepos = filepos,
+ .timecode = timecode,
+ .duration = duration,
+ };
+
+ mkv_d->num_indexes++;
+}
+
+static void add_block_position(demuxer_t *demuxer, struct mkv_track *track,
+ uint64_t filepos,
+ int64_t timecode, int64_t duration)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+
+ if (mkv_d->index_complete || !track)
+ return;
+
+ mkv_d->index_has_durations = true;
+
+ if (track->last_index_entry != (size_t)-1) {
+ mkv_index_t *index = &mkv_d->indexes[track->last_index_entry];
+ // Never add blocks which are already covered by the index.
+ if (index->timecode >= timecode)
+ return;
+ }
+ cue_index_add(demuxer, track->tnum, filepos, timecode, duration);
+ track->last_index_entry = mkv_d->num_indexes - 1;
+}
+
+static int demux_mkv_read_cues(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ stream_t *s = demuxer->stream;
+
+ if (demuxer->opts->index_mode != 1 || mkv_d->index_complete) {
+ ebml_read_skip(demuxer->log, -1, s);
+ return 0;
+ }
+
+ MP_VERBOSE(demuxer, "Parsing cues...\n");
+ struct ebml_cues cues = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &cues, &ebml_cues_desc) < 0)
+ return -1;
+
+ for (int i = 0; i < cues.n_cue_point; i++) {
+ struct ebml_cue_point *cuepoint = &cues.cue_point[i];
+ if (cuepoint->n_cue_time != 1 || !cuepoint->n_cue_track_positions) {
+ MP_WARN(demuxer, "Malformed CuePoint element\n");
+ goto done;
+ }
+ if (cuepoint->cue_time / 1e9 > mkv_d->duration / mkv_d->tc_scale * 10 &&
+ mkv_d->duration != 0)
+ goto done;
+ }
+ if (cues.n_cue_point <= 3) // probably too sparse and will just break seeking
+ goto done;
+
+ // Discard incremental index. (Keep the first entry, which must be the
+ // start of the file - helps with files that miss the first index entry.)
+ mkv_d->num_indexes = MPMIN(1, mkv_d->num_indexes);
+ mkv_d->index_has_durations = false;
+
+ for (int i = 0; i < cues.n_cue_point; i++) {
+ struct ebml_cue_point *cuepoint = &cues.cue_point[i];
+ uint64_t time = cuepoint->cue_time;
+ for (int c = 0; c < cuepoint->n_cue_track_positions; c++) {
+ struct ebml_cue_track_positions *trackpos =
+ &cuepoint->cue_track_positions[c];
+ uint64_t pos = mkv_d->segment_start + trackpos->cue_cluster_position;
+ cue_index_add(demuxer, trackpos->cue_track, pos,
+ time, trackpos->cue_duration);
+ mkv_d->index_has_durations |= trackpos->n_cue_duration > 0;
+ MP_TRACE(demuxer, "|+ found cue point for track %"PRIu64": "
+ "timecode %"PRIu64", filepos: %"PRIu64" "
+ "offset %"PRIu64", duration %"PRIu64"\n",
+ trackpos->cue_track, time, pos,
+ trackpos->cue_relative_position, trackpos->cue_duration);
+ }
+ }
+
+ // Do not attempt to create index on the fly.
+ mkv_d->index_complete = true;
+
+done:
+ if (!mkv_d->index_complete)
+ MP_WARN(demuxer, "Discarding potentially broken or useless index.\n");
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+}
+
+static int demux_mkv_read_chapters(struct demuxer *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ stream_t *s = demuxer->stream;
+ int wanted_edition = mkv_d->edition_id;
+ uint64_t wanted_edition_uid = demuxer->matroska_data.uid.edition;
+
+ /* A specific edition UID was requested; ignore the user option which is
+ * only applicable to the top-level file. */
+ if (wanted_edition_uid)
+ wanted_edition = -1;
+
+ MP_DBG(demuxer, "Parsing chapters...\n");
+ struct ebml_chapters file_chapters = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &file_chapters,
+ &ebml_chapters_desc) < 0)
+ return -1;
+
+ int selected_edition = -1;
+ int num_editions = file_chapters.n_edition_entry;
+ struct ebml_edition_entry *editions = file_chapters.edition_entry;
+ for (int i = 0; i < num_editions; i++) {
+ struct demux_edition new = {
+ .demuxer_id = editions[i].edition_uid,
+ .default_edition = editions[i].edition_flag_default,
+ .metadata = talloc_zero(demuxer, struct mp_tags),
+ };
+ MP_TARRAY_APPEND(demuxer, demuxer->editions, demuxer->num_editions, new);
+ }
+ if (wanted_edition >= 0 && wanted_edition < num_editions) {
+ selected_edition = wanted_edition;
+ MP_VERBOSE(demuxer, "User-specified edition: %d\n", selected_edition);
+ } else {
+ for (int i = 0; i < num_editions; i++) {
+ if (wanted_edition_uid &&
+ editions[i].edition_uid == wanted_edition_uid) {
+ selected_edition = i;
+ break;
+ } else if (editions[i].edition_flag_default) {
+ selected_edition = i;
+ MP_VERBOSE(demuxer, "Default edition: %d\n", i);
+ break;
+ }
+ }
+ }
+ if (selected_edition < 0) {
+ if (wanted_edition_uid) {
+ MP_ERR(demuxer, "Unable to find expected edition uid: %"PRIu64"\n",
+ wanted_edition_uid);
+ talloc_free(parse_ctx.talloc_ctx);
+ return -1;
+ } else {
+ selected_edition = 0;
+ }
+ }
+
+ for (int idx = 0; idx < num_editions; idx++) {
+ MP_VERBOSE(demuxer, "New edition %d\n", idx);
+ int warn_level = idx == selected_edition ? MSGL_WARN : MSGL_V;
+ if (editions[idx].n_edition_flag_default)
+ MP_VERBOSE(demuxer, "Default edition flag: %"PRIu64"\n",
+ editions[idx].edition_flag_default);
+ if (editions[idx].n_edition_flag_ordered)
+ MP_VERBOSE(demuxer, "Ordered chapter flag: %"PRIu64"\n",
+ editions[idx].edition_flag_ordered);
+
+ int chapter_count = editions[idx].n_chapter_atom;
+
+ struct matroska_chapter *m_chapters = NULL;
+ if (idx == selected_edition && editions[idx].edition_flag_ordered) {
+ m_chapters = talloc_array_ptrtype(demuxer, m_chapters, chapter_count);
+ demuxer->matroska_data.ordered_chapters = m_chapters;
+ demuxer->matroska_data.num_ordered_chapters = chapter_count;
+ }
+
+ for (int i = 0; i < chapter_count; i++) {
+ struct ebml_chapter_atom *ca = editions[idx].chapter_atom + i;
+ struct matroska_chapter chapter = {0};
+ char *name = "(unnamed)";
+
+ chapter.start = ca->chapter_time_start;
+ chapter.end = ca->chapter_time_end;
+
+ if (!ca->n_chapter_time_start)
+ MP_MSG(demuxer, warn_level, "Chapter lacks start time\n");
+ if (!ca->n_chapter_time_start || !ca->n_chapter_time_end) {
+ if (demuxer->matroska_data.ordered_chapters) {
+ MP_MSG(demuxer, warn_level, "Chapter lacks start or end "
+ "time, disabling ordered chapters.\n");
+ demuxer->matroska_data.ordered_chapters = NULL;
+ demuxer->matroska_data.num_ordered_chapters = 0;
+ }
+ }
+
+ if (ca->n_chapter_display) {
+ if (ca->n_chapter_display > 1)
+ MP_MSG(demuxer, warn_level, "Multiple chapter "
+ "names not supported, picking first\n");
+ if (!ca->chapter_display[0].chap_string)
+ MP_MSG(demuxer, warn_level, "Malformed chapter name entry\n");
+ else
+ name = ca->chapter_display[0].chap_string;
+ }
+
+ if (ca->n_chapter_segment_uid) {
+ chapter.has_segment_uid = true;
+ int len = ca->chapter_segment_uid.len;
+ if (len != sizeof(chapter.uid.segment))
+ MP_MSG(demuxer, warn_level,
+ "Chapter segment uid bad length %d\n", len);
+ else {
+ memcpy(chapter.uid.segment, ca->chapter_segment_uid.start,
+ len);
+ if (ca->n_chapter_segment_edition_uid)
+ chapter.uid.edition = ca->chapter_segment_edition_uid;
+ else
+ chapter.uid.edition = 0;
+ MP_DBG(demuxer, "Chapter segment uid ");
+ for (int n = 0; n < len; n++)
+ MP_DBG(demuxer, "%02x ",
+ chapter.uid.segment[n]);
+ MP_DBG(demuxer, "\n");
+ }
+ }
+
+ MP_DBG(demuxer, "Chapter %u from %02d:%02d:%02d.%03d "
+ "to %02d:%02d:%02d.%03d, %s\n", i,
+ (int) (chapter.start / 60 / 60 / 1000000000),
+ (int) ((chapter.start / 60 / 1000000000) % 60),
+ (int) ((chapter.start / 1000000000) % 60),
+ (int) (chapter.start % 1000000000),
+ (int) (chapter.end / 60 / 60 / 1000000000),
+ (int) ((chapter.end / 60 / 1000000000) % 60),
+ (int) ((chapter.end / 1000000000) % 60),
+ (int) (chapter.end % 1000000000),
+ name);
+
+ if (idx == selected_edition) {
+ demuxer_add_chapter(demuxer, name, chapter.start / 1e9,
+ ca->chapter_uid);
+ }
+ if (m_chapters) {
+ chapter.name = talloc_strdup(m_chapters, name);
+ m_chapters[i] = chapter;
+ }
+ }
+ }
+
+ demuxer->num_editions = num_editions;
+ demuxer->edition = selected_edition;
+
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+}
+
+static int demux_mkv_read_tags(demuxer_t *demuxer)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ stream_t *s = demuxer->stream;
+
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ struct ebml_tags tags = {0};
+ if (ebml_read_element(s, &parse_ctx, &tags, &ebml_tags_desc) < 0)
+ return -1;
+
+ mkv_d->tags = talloc_dup(mkv_d, &tags);
+ talloc_steal(mkv_d->tags, parse_ctx.talloc_ctx);
+ return 0;
+}
+
+static void process_tags(demuxer_t *demuxer)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ struct ebml_tags *tags = mkv_d->tags;
+
+ if (!tags)
+ return;
+
+ for (int i = 0; i < tags->n_tag; i++) {
+ struct ebml_tag tag = tags->tag[i];
+ struct mp_tags *dst = NULL;
+
+ if (tag.targets.target_chapter_uid) {
+ for (int n = 0; n < demuxer->num_chapters; n++) {
+ if (demuxer->chapters[n].demuxer_id ==
+ tag.targets.target_chapter_uid)
+ {
+ dst = demuxer->chapters[n].metadata;
+ break;
+ }
+ }
+ } else if (tag.targets.target_edition_uid) {
+ for (int n = 0; n < demuxer->num_editions; n++) {
+ if (demuxer->editions[n].demuxer_id ==
+ tag.targets.target_edition_uid)
+ {
+ dst = demuxer->editions[n].metadata;
+ break;
+ }
+ }
+ } else if (tag.targets.target_track_uid) {
+ for (int n = 0; n < mkv_d->num_tracks; n++) {
+ if (mkv_d->tracks[n]->uid ==
+ tag.targets.target_track_uid)
+ {
+ struct sh_stream *sh = mkv_d->tracks[n]->stream;
+ if (sh)
+ dst = sh->tags;
+ break;
+ }
+ }
+ } else if (tag.targets.target_attachment_uid) {
+ /* ignore */
+ } else {
+ dst = demuxer->metadata;
+ }
+
+ if (dst) {
+ for (int j = 0; j < tag.n_simple_tag; j++) {
+ if (tag.simple_tag[j].tag_name && tag.simple_tag[j].tag_string) {
+ char *name = tag.simple_tag[j].tag_name;
+ char *val = tag.simple_tag[j].tag_string;
+ char *old = mp_tags_get_str(dst, name);
+ if (old)
+ val = talloc_asprintf(NULL, "%s / %s", old, val);
+ mp_tags_set_str(dst, name, val);
+ if (old)
+ talloc_free(val);
+ }
+ }
+ }
+ }
+}
+
+static int demux_mkv_read_attachments(demuxer_t *demuxer)
+{
+ stream_t *s = demuxer->stream;
+
+ MP_DBG(demuxer, "Parsing attachments...\n");
+
+ struct ebml_attachments attachments = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &attachments,
+ &ebml_attachments_desc) < 0)
+ return -1;
+
+ for (int i = 0; i < attachments.n_attached_file; i++) {
+ struct ebml_attached_file *attachment = &attachments.attached_file[i];
+ if (!attachment->file_name || !attachment->file_mime_type
+ || !attachment->n_file_data) {
+ MP_WARN(demuxer, "Malformed attachment\n");
+ continue;
+ }
+ char *name = attachment->file_name;
+ char *mime = attachment->file_mime_type;
+ demuxer_add_attachment(demuxer, name, mime, attachment->file_data.start,
+ attachment->file_data.len);
+ MP_DBG(demuxer, "Attachment: %s, %s, %zu bytes\n",
+ name, mime, attachment->file_data.len);
+ }
+
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+}
+
+static struct header_elem *get_header_element(struct demuxer *demuxer,
+ uint32_t id,
+ int64_t element_filepos)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+
+ // Note that some files in fact contain a SEEKHEAD with a list of all
+ // clusters - we have no use for that.
+ if (!ebml_is_mkv_level1_id(id) || id == MATROSKA_ID_CLUSTER)
+ return NULL;
+
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+ // SEEKHEAD is the only element that can happen multiple times.
+ // Other elements might be duplicated (or attempted to be read twice,
+ // even if it's only once in the file), but only the first is used.
+ if (elem->id == id && (id != MATROSKA_ID_SEEKHEAD ||
+ elem->pos == element_filepos))
+ return elem;
+ }
+ struct header_elem elem = { .id = id, .pos = element_filepos };
+ MP_TARRAY_APPEND(mkv_d, mkv_d->headers, mkv_d->num_headers, elem);
+ return &mkv_d->headers[mkv_d->num_headers - 1];
+}
+
+// Mark the level 1 element with the given id as read. Return whether it
+// was marked read before (e.g. for checking whether it was already read).
+// element_filepos refers to the file position of the element ID.
+static bool test_header_element(struct demuxer *demuxer, uint32_t id,
+ int64_t element_filepos)
+{
+ struct header_elem *elem = get_header_element(demuxer, id, element_filepos);
+ if (!elem)
+ return false;
+ if (elem->parsed)
+ return true;
+ elem->parsed = true;
+ return false;
+}
+
+static int demux_mkv_read_seekhead(demuxer_t *demuxer)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ struct stream *s = demuxer->stream;
+ int res = 0;
+ struct ebml_seek_head seekhead = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+
+ MP_DBG(demuxer, "Parsing seek head...\n");
+ if (ebml_read_element(s, &parse_ctx, &seekhead, &ebml_seek_head_desc) < 0) {
+ res = -1;
+ goto out;
+ }
+ for (int i = 0; i < seekhead.n_seek; i++) {
+ struct ebml_seek *seek = &seekhead.seek[i];
+ if (seek->n_seek_id != 1 || seek->n_seek_position != 1) {
+ MP_WARN(demuxer, "Invalid SeekHead entry\n");
+ continue;
+ }
+ uint64_t pos = seek->seek_position + mkv_d->segment_start;
+ MP_TRACE(demuxer, "Element 0x%"PRIx32" at %"PRIu64".\n",
+ seek->seek_id, pos);
+ get_header_element(demuxer, seek->seek_id, pos);
+ }
+ out:
+ talloc_free(parse_ctx.talloc_ctx);
+ return res;
+}
+
+static int read_header_element(struct demuxer *demuxer, uint32_t id,
+ int64_t start_filepos)
+{
+ if (id == EBML_ID_INVALID)
+ return 0;
+
+ if (test_header_element(demuxer, id, start_filepos))
+ goto skip;
+
+ switch(id) {
+ case MATROSKA_ID_INFO:
+ return demux_mkv_read_info(demuxer);
+ case MATROSKA_ID_TRACKS:
+ return demux_mkv_read_tracks(demuxer);
+ case MATROSKA_ID_CUES:
+ return demux_mkv_read_cues(demuxer);
+ case MATROSKA_ID_TAGS:
+ return demux_mkv_read_tags(demuxer);
+ case MATROSKA_ID_SEEKHEAD:
+ return demux_mkv_read_seekhead(demuxer);
+ case MATROSKA_ID_CHAPTERS:
+ return demux_mkv_read_chapters(demuxer);
+ case MATROSKA_ID_ATTACHMENTS:
+ return demux_mkv_read_attachments(demuxer);
+ }
+skip:
+ ebml_read_skip(demuxer->log, -1, demuxer->stream);
+ return 0;
+}
+
+static int read_deferred_element(struct demuxer *demuxer,
+ struct header_elem *elem)
+{
+ stream_t *s = demuxer->stream;
+
+ if (elem->parsed)
+ return 0;
+ elem->parsed = true;
+ MP_VERBOSE(demuxer, "Seeking to %"PRIu64" to read header element "
+ "0x%"PRIx32".\n",
+ elem->pos, elem->id);
+ if (!stream_seek(s, elem->pos)) {
+ MP_WARN(demuxer, "Failed to seek when reading header element.\n");
+ return 0;
+ }
+ if (ebml_read_id(s) != elem->id) {
+ MP_ERR(demuxer, "Expected element 0x%"PRIx32" not found\n",
+ elem->id);
+ return 0;
+ }
+ elem->parsed = false; // don't make read_header_element skip it
+ return read_header_element(demuxer, elem->id, elem->pos);
+}
+
+static void read_deferred_cues(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ if (mkv_d->index_complete || demuxer->opts->index_mode != 1)
+ return;
+
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+
+ if (elem->id == MATROSKA_ID_CUES)
+ read_deferred_element(demuxer, elem);
+ }
+}
+
+static void add_coverart(struct demuxer *demuxer)
+{
+ for (int n = 0; n < demuxer->num_attachments; n++) {
+ struct demux_attachment *att = &demuxer->attachments[n];
+ const char *codec = mp_map_mimetype_to_video_codec(att->type);
+ if (!codec)
+ continue;
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
+ sh->codec->codec = codec;
+ sh->attached_picture = new_demux_packet_from(att->data, att->data_size);
+ if (sh->attached_picture) {
+ sh->attached_picture->pts = 0;
+ talloc_steal(sh, sh->attached_picture);
+ sh->attached_picture->keyframe = true;
+ sh->image = true;
+ }
+ sh->title = att->name;
+ demux_add_sh_stream(demuxer, sh);
+ }
+}
+
+static void init_track(demuxer_t *demuxer, mkv_track_t *track,
+ struct sh_stream *sh)
+{
+ track->stream = sh;
+
+ if (track->language && (strcmp(track->language, "und") != 0))
+ sh->lang = track->language;
+
+ sh->demuxer_id = track->tnum;
+ sh->title = track->name;
+ sh->default_track = track->default_track;
+ sh->forced_track = track->forced_track;
+}
+
+static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track);
+static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track);
+static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track);
+
+static void display_create_tracks(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+
+ for (int i = 0; i < mkv_d->num_tracks; i++) {
+ switch (mkv_d->tracks[i]->type) {
+ case MATROSKA_TRACK_VIDEO:
+ demux_mkv_open_video(demuxer, mkv_d->tracks[i]);
+ break;
+ case MATROSKA_TRACK_AUDIO:
+ demux_mkv_open_audio(demuxer, mkv_d->tracks[i]);
+ break;
+ case MATROSKA_TRACK_SUBTITLE:
+ demux_mkv_open_sub(demuxer, mkv_d->tracks[i]);
+ break;
+ }
+ }
+}
+
+static const char *const mkv_video_tags[][2] = {
+ {"V_MJPEG", "mjpeg"},
+ {"V_MPEG1", "mpeg1video"},
+ {"V_MPEG2", "mpeg2video"},
+ {"V_MPEG4/ISO/SP", "mpeg4"},
+ {"V_MPEG4/ISO/ASP", "mpeg4"},
+ {"V_MPEG4/ISO/AP", "mpeg4"},
+ {"V_MPEG4/ISO/AVC", "h264"},
+ {"V_MPEG4/MS/V3", "msmpeg4v3"},
+ {"V_THEORA", "theora"},
+ {"V_VP8", "vp8"},
+ {"V_VP9", "vp9"},
+ {"V_DIRAC", "dirac"},
+ {"V_PRORES", "prores"},
+ {"V_MPEGH/ISO/HEVC", "hevc"},
+ {"V_SNOW", "snow"},
+ {"V_AV1", "av1"},
+ {"V_PNG", "png"},
+ {"V_AVS2", "avs2"},
+ {"V_AVS3", "avs3"},
+ {0}
+};
+
+static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
+{
+ unsigned char *extradata = NULL;
+ unsigned int extradata_size = 0;
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
+ init_track(demuxer, track, sh);
+ struct mp_codec_params *sh_v = sh->codec;
+
+ sh_v->bits_per_coded_sample = 24;
+
+ if (!strcmp(track->codec_id, "V_MS/VFW/FOURCC")) { /* AVI compatibility mode */
+ // The private_data contains a BITMAPINFOHEADER struct
+ if (track->private_data == NULL || track->private_size < 40)
+ goto done;
+
+ unsigned char *h = track->private_data;
+ if (track->v_width == 0)
+ track->v_width = AV_RL32(h + 4); // biWidth
+ if (track->v_height == 0)
+ track->v_height = AV_RL32(h + 8); // biHeight
+ sh_v->bits_per_coded_sample = AV_RL16(h + 14); // biBitCount
+ sh_v->codec_tag = AV_RL32(h + 16); // biCompression
+
+ extradata = track->private_data + 40;
+ extradata_size = track->private_size - 40;
+ mp_set_codec_from_tag(sh_v);
+ sh_v->avi_dts = true;
+ } else if (track->private_size >= RVPROPERTIES_SIZE
+ && (!strcmp(track->codec_id, "V_REAL/RV10")
+ || !strcmp(track->codec_id, "V_REAL/RV20")
+ || !strcmp(track->codec_id, "V_REAL/RV30")
+ || !strcmp(track->codec_id, "V_REAL/RV40")))
+ {
+ unsigned char *src;
+ unsigned int cnt;
+
+ src = (uint8_t *) track->private_data + RVPROPERTIES_SIZE;
+
+ cnt = track->private_size - RVPROPERTIES_SIZE;
+ uint32_t t2 = AV_RB32(src - 4);
+ switch (t2 == 0x10003000 || t2 == 0x10003001 ? '1' : track->codec_id[9]) {
+ case '1': sh_v->codec = "rv10"; break;
+ case '2': sh_v->codec = "rv20"; break;
+ case '3': sh_v->codec = "rv30"; break;
+ case '4': sh_v->codec = "rv40"; break;
+ }
+ // copy type1 and type2 info from rv properties
+ extradata_size = cnt + 8;
+ extradata = src - 8;
+ track->parse = true;
+ track->parse_timebase = 1e3;
+ } else if (strcmp(track->codec_id, "V_UNCOMPRESSED") == 0) {
+ // raw video, "like AVI" - this is a FourCC
+ sh_v->codec_tag = track->colorspace;
+ sh_v->codec = "rawvideo";
+ } else if (strcmp(track->codec_id, "V_QUICKTIME") == 0) {
+ uint32_t fourcc1 = 0, fourcc2 = 0;
+ if (track->private_size >= 8) {
+ fourcc1 = AV_RL32(track->private_data + 0);
+ fourcc2 = AV_RL32(track->private_data + 4);
+ }
+ if (fourcc1 == MKTAG('S', 'V', 'Q', '3') ||
+ fourcc2 == MKTAG('S', 'V', 'Q', '3'))
+ {
+ sh_v->codec = "svq3";
+ extradata = track->private_data;
+ extradata_size = track->private_size;
+ }
+ } else {
+ for (int i = 0; mkv_video_tags[i][0]; i++) {
+ if (!strcmp(mkv_video_tags[i][0], track->codec_id)) {
+ sh_v->codec = mkv_video_tags[i][1];
+ break;
+ }
+ }
+ if (track->private_data && track->private_size > 0) {
+ extradata = track->private_data;
+ extradata_size = track->private_size;
+ }
+ }
+
+ const char *codec = sh_v->codec ? sh_v->codec : "";
+ if (mp_codec_is_image(codec)) {
+ sh->still_image = true;
+ sh->image = true;
+ }
+ if (!strcmp(codec, "mjpeg")) {
+ sh_v->codec_tag = MKTAG('m', 'j', 'p', 'g');
+ track->require_keyframes = true;
+ }
+
+ if (extradata_size > 0x1000000) {
+ MP_WARN(demuxer, "Invalid CodecPrivate\n");
+ goto done;
+ }
+
+ sh_v->extradata = talloc_memdup(sh_v, extradata, extradata_size);
+ sh_v->extradata_size = extradata_size;
+ if (!sh_v->codec) {
+ MP_WARN(demuxer, "Unknown/unsupported CodecID (%s) or missing/bad "
+ "CodecPrivate data (track %d).\n",
+ track->codec_id, track->tnum);
+ }
+ sh_v->fps = track->v_frate;
+ sh_v->disp_w = track->v_width;
+ sh_v->disp_h = track->v_height;
+
+ // Keep the codec crop rect as 0s if we have no cropping since the
+ // file may have broken width/height tags.
+ if (track->v_crop_left || track->v_crop_top ||
+ track->v_crop_right || track->v_crop_bottom)
+ {
+ sh_v->crop.x0 = track->v_crop_left;
+ sh_v->crop.y0 = track->v_crop_top;
+ sh_v->crop.x1 = track->v_width - track->v_crop_right;
+ sh_v->crop.y1 = track->v_height - track->v_crop_bottom;
+ }
+
+ int dw = track->v_dwidth_set ? track->v_dwidth : track->v_width;
+ int dh = track->v_dheight_set ? track->v_dheight : track->v_height;
+ struct mp_image_params p = {.w = track->v_width, .h = track->v_height};
+ mp_image_params_set_dsize(&p, dw, dh);
+ sh_v->par_w = p.p_w;
+ sh_v->par_h = p.p_h;
+
+ sh_v->stereo_mode = track->stereo_mode;
+ sh_v->color = track->color;
+
+ if (track->v_projection_pose_roll_set) {
+ int rotate = lrintf(fmodf(fmodf(track->v_projection_pose_roll, 360) + 360, 360));
+ sh_v->rotate = rotate;
+ }
+
+done:
+ demux_add_sh_stream(demuxer, sh);
+
+ return 0;
+}
+
+// Parse VorbisComment and look for WAVEFORMATEXTENSIBLE_CHANNEL_MASK.
+// Do not change *channels if nothing found or an error happens.
+static void parse_vorbis_chmap(struct mp_chmap *channels, unsigned char *data,
+ int size)
+{
+ // Skip the useless vendor string.
+ if (size < 4)
+ return;
+ uint32_t vendor_length = AV_RL32(data);
+ if (vendor_length + 4 > size) // also check for the next AV_RB32 below
+ return;
+ size -= vendor_length + 4;
+ data += vendor_length + 4;
+ uint32_t num_headers = AV_RL32(data);
+ size -= 4;
+ data += 4;
+ for (int n = 0; n < num_headers; n++) {
+ if (size < 4)
+ return;
+ uint32_t len = AV_RL32(data);
+ size -= 4;
+ data += 4;
+ if (len > size)
+ return;
+ if (len > 34 && !memcmp(data, "WAVEFORMATEXTENSIBLE_CHANNEL_MASK=", 34)) {
+ char smask[80];
+ snprintf(smask, sizeof(smask), "%.*s", (int)(len - 34), data + 34);
+ char *end = NULL;
+ uint32_t mask = strtol(smask, &end, 0);
+ if (!end || end[0])
+ mask = 0;
+ struct mp_chmap chmask = {0};
+ mp_chmap_from_waveext(&chmask, mask);
+ if (mp_chmap_is_valid(&chmask))
+ *channels = chmask;
+ }
+ size -= len;
+ data += len;
+ }
+}
+
+// Parse VorbisComment-in-FLAC and look for WAVEFORMATEXTENSIBLE_CHANNEL_MASK.
+// Do not change *channels if nothing found or an error happens.
+static void parse_flac_chmap(struct mp_chmap *channels, unsigned char *data,
+ int size)
+{
+ // Skip FLAC header.
+ if (size < 4)
+ return;
+ data += 4;
+ size -= 4;
+ // Parse FLAC blocks...
+ while (size >= 4) {
+ unsigned btype = data[0] & 0x7F;
+ unsigned bsize = AV_RB24(data + 1);
+ data += 4;
+ size -= 4;
+ if (bsize > size)
+ return;
+ if (btype == 4) // VORBIS_COMMENT
+ parse_vorbis_chmap(channels, data, bsize);
+ data += bsize;
+ size -= bsize;
+ }
+}
+
+static const char *const mkv_audio_tags[][2] = {
+ { "A_MPEG/L2", "mp2" },
+ { "A_MPEG/L3", "mp3" },
+ { "A_AC3", "ac3" },
+ { "A_EAC3", "eac3" },
+ { "A_DTS", "dts" },
+ { "A_AAC", "aac" },
+ { "A_VORBIS", "vorbis" },
+ { "A_OPUS", "opus" },
+ { "A_OPUS/EXPERIMENTAL", "opus" },
+ { "A_QUICKTIME/QDMC", "qdmc" },
+ { "A_QUICKTIME/QDM2", "qdm2" },
+ { "A_WAVPACK4", "wavpack" },
+ { "A_TRUEHD", "truehd" },
+ { "A_FLAC", "flac" },
+ { "A_ALAC", "alac" },
+ { "A_TTA1", "tta" },
+ { "A_MLP", "mlp" },
+ { NULL },
+};
+
+static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
+{
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
+ init_track(demuxer, track, sh);
+ struct mp_codec_params *sh_a = sh->codec;
+
+ if (track->private_size > 0x1000000)
+ goto error;
+
+ unsigned char *extradata = track->private_data;
+ unsigned int extradata_len = track->private_size;
+
+ if (!track->a_osfreq)
+ track->a_osfreq = track->a_sfreq;
+ sh_a->bits_per_coded_sample = track->a_bps ? track->a_bps : 16;
+ sh_a->samplerate = (uint32_t) track->a_osfreq;
+ mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
+
+ for (int i = 0; mkv_audio_tags[i][0]; i++) {
+ if (!strcmp(mkv_audio_tags[i][0], track->codec_id)) {
+ sh_a->codec = mkv_audio_tags[i][1];
+ break;
+ }
+ }
+
+ if (!strcmp(track->codec_id, "A_MS/ACM")) { /* AVI compatibility mode */
+ // The private_data contains a WAVEFORMATEX struct
+ if (track->private_size < 18)
+ goto error;
+ MP_DBG(demuxer, "track with MS compat audio.\n");
+ unsigned char *h = track->private_data;
+ sh_a->codec_tag = AV_RL16(h + 0); // wFormatTag
+ if (track->a_channels == 0)
+ track->a_channels = AV_RL16(h + 2); // nChannels
+ if (sh_a->samplerate == 0)
+ sh_a->samplerate = AV_RL32(h + 4); // nSamplesPerSec
+ sh_a->bitrate = AV_RL32(h + 8) * 8; // nAvgBytesPerSec
+ sh_a->block_align = AV_RL16(h + 12); // nBlockAlign
+ if (track->a_bps == 0)
+ track->a_bps = AV_RL16(h + 14); // wBitsPerSample
+ extradata = track->private_data + 18;
+ extradata_len = track->private_size - 18;
+ sh_a->bits_per_coded_sample = track->a_bps;
+ sh_a->extradata = extradata;
+ sh_a->extradata_size = extradata_len;
+ mp_set_codec_from_tag(sh_a);
+ extradata = sh_a->extradata;
+ extradata_len = sh_a->extradata_size;
+ } else if (!strcmp(track->codec_id, "A_PCM/INT/LIT")) {
+ bool sign = sh_a->bits_per_coded_sample > 8;
+ mp_set_pcm_codec(sh_a, sign, false, sh_a->bits_per_coded_sample, false);
+ } else if (!strcmp(track->codec_id, "A_PCM/INT/BIG")) {
+ bool sign = sh_a->bits_per_coded_sample > 8;
+ mp_set_pcm_codec(sh_a, sign, false, sh_a->bits_per_coded_sample, true);
+ } else if (!strcmp(track->codec_id, "A_PCM/FLOAT/IEEE")) {
+ sh_a->codec = sh_a->bits_per_coded_sample == 64 ? "pcm_f64le" : "pcm_f32le";
+ } else if (!strncmp(track->codec_id, "A_REAL/", 7)) {
+ if (track->private_size < RAPROPERTIES4_SIZE)
+ goto error;
+ /* Common initialization for all RealAudio codecs */
+ unsigned char *src = track->private_data;
+
+ int version = AV_RB16(src + 4);
+ unsigned int flavor = AV_RB16(src + 22);
+ track->coded_framesize = AV_RB32(src + 24);
+ track->sub_packet_h = AV_RB16(src + 40);
+ sh_a->block_align = track->audiopk_size = AV_RB16(src + 42);
+ track->sub_packet_size = AV_RB16(src + 44);
+ int offset = 0;
+ if (version == 4) {
+ offset += RAPROPERTIES4_SIZE;
+ if (offset + 1 > track->private_size)
+ goto error;
+ offset += (src[offset] + 1) * 2 + 3;
+ } else {
+ offset += RAPROPERTIES5_SIZE + 3 + (version == 5 ? 1 : 0);
+ }
+
+ if (track->audiopk_size == 0 || track->sub_packet_size == 0 ||
+ track->sub_packet_h == 0 || track->coded_framesize == 0)
+ goto error;
+ if (track->coded_framesize > 0x40000000)
+ goto error;
+
+ if (offset + 4 > track->private_size)
+ goto error;
+ uint32_t codecdata_length = AV_RB32(src + offset);
+ offset += 4;
+ if (offset > track->private_size ||
+ codecdata_length > track->private_size - offset)
+ goto error;
+ extradata_len = codecdata_length;
+ extradata = src + offset;
+
+ if (!strcmp(track->codec_id, "A_REAL/ATRC")) {
+ sh_a->codec = "atrac3";
+ if (flavor >= MP_ARRAY_SIZE(atrc_fl2bps))
+ goto error;
+ sh_a->bitrate = atrc_fl2bps[flavor] * 8;
+ sh_a->block_align = track->sub_packet_size;
+ } else if (!strcmp(track->codec_id, "A_REAL/COOK")) {
+ sh_a->codec = "cook";
+ if (flavor >= MP_ARRAY_SIZE(cook_fl2bps))
+ goto error;
+ sh_a->bitrate = cook_fl2bps[flavor] * 8;
+ sh_a->block_align = track->sub_packet_size;
+ } else if (!strcmp(track->codec_id, "A_REAL/SIPR")) {
+ sh_a->codec = "sipr";
+ if (flavor >= MP_ARRAY_SIZE(sipr_fl2bps))
+ goto error;
+ sh_a->bitrate = sipr_fl2bps[flavor] * 8;
+ sh_a->block_align = track->coded_framesize;
+ } else if (!strcmp(track->codec_id, "A_REAL/28_8")) {
+ sh_a->codec = "ra_288";
+ sh_a->bitrate = 3600 * 8;
+ sh_a->block_align = track->coded_framesize;
+ } else if (!strcmp(track->codec_id, "A_REAL/DNET")) {
+ sh_a->codec = "ac3";
+ } else {
+ goto error;
+ }
+
+ track->audio_buf =
+ talloc_array_size(track, track->sub_packet_h, track->audiopk_size);
+ track->audio_timestamp =
+ talloc_array(track, double, track->sub_packet_h);
+ } else if (!strncmp(track->codec_id, "A_AAC/", 6)) {
+ sh_a->codec = "aac";
+
+ /* Recreate the 'private data' (not needed for plain A_AAC) */
+ int srate_idx = aac_get_sample_rate_index(track->a_sfreq);
+ const char *tail = "";
+ if (strlen(track->codec_id) >= 12)
+ tail = &track->codec_id[12];
+ int profile = 3;
+ if (!strncmp(tail, "MAIN", 4))
+ profile = 0;
+ else if (!strncmp(tail, "LC", 2))
+ profile = 1;
+ else if (!strncmp(tail, "SSR", 3))
+ profile = 2;
+ extradata = talloc_size(sh_a, 5);
+ extradata[0] = ((profile + 1) << 3) | ((srate_idx & 0xE) >> 1);
+ extradata[1] = ((srate_idx & 0x1) << 7) | (track->a_channels << 3);
+
+ if (strstr(track->codec_id, "SBR") != NULL) {
+ /* HE-AAC (aka SBR AAC) */
+ extradata_len = 5;
+
+ srate_idx = aac_get_sample_rate_index(sh_a->samplerate);
+ extradata[2] = AAC_SYNC_EXTENSION_TYPE >> 3;
+ extradata[3] = ((AAC_SYNC_EXTENSION_TYPE & 0x07) << 5) | 5;
+ extradata[4] = (1 << 7) | (srate_idx << 3);
+ track->default_duration = 1024.0 / (sh_a->samplerate / 2);
+ } else {
+ extradata_len = 2;
+ track->default_duration = 1024.0 / sh_a->samplerate;
+ }
+ } else if (!strncmp(track->codec_id, "A_AC3/", 6)) {
+ sh_a->codec = "ac3";
+ } else if (!strncmp(track->codec_id, "A_EAC3/", 7)) {
+ sh_a->codec = "eac3";
+ }
+
+ if (!sh_a->codec)
+ goto error;
+
+ const char *codec = sh_a->codec;
+ if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") ||
+ !strcmp(codec, "truehd") || !strcmp(codec, "eac3"))
+ {
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ int64_t segment_timebase = (1e9 / mkv_d->tc_scale);
+
+ track->parse = true;
+ track->parse_timebase = MPMAX(sh_a->samplerate, segment_timebase);
+ } else if (!strcmp(codec, "flac")) {
+ unsigned char *ptr = extradata;
+ unsigned int size = extradata_len;
+ if (size < 4 || ptr[0] != 'f' || ptr[1] != 'L' || ptr[2] != 'a'
+ || ptr[3] != 'C') {
+ extradata = talloc_size(sh_a, 4);
+ extradata_len = 4;
+ memcpy(extradata, "fLaC", 4);
+ }
+ parse_flac_chmap(&sh_a->channels, extradata, extradata_len);
+ } else if (!strcmp(codec, "alac")) {
+ if (track->private_size) {
+ extradata_len = track->private_size + 12;
+ extradata = talloc_size(sh_a, extradata_len);
+ char *data = extradata;
+ AV_WB32(data + 0, extradata_len);
+ memcpy(data + 4, "alac", 4);
+ AV_WB32(data + 8, 0);
+ memcpy(data + 12, track->private_data, track->private_size);
+ }
+ } else if (!strcmp(codec, "tta")) {
+ extradata_len = 30;
+ extradata = talloc_zero_size(sh_a, extradata_len);
+ if (!extradata)
+ goto error;
+ char *data = extradata;
+ memcpy(data + 0, "TTA1", 4);
+ AV_WL16(data + 4, 1);
+ AV_WL16(data + 6, sh_a->channels.num);
+ AV_WL16(data + 8, sh_a->bits_per_coded_sample);
+ AV_WL32(data + 10, track->a_osfreq);
+ // Bogus: last frame won't be played.
+ AV_WL32(data + 14, 0);
+ } else if (!strcmp(codec, "opus")) {
+ // Hardcode the rate libavcodec's opus decoder outputs, so that
+ // AV_PKT_DATA_SKIP_SAMPLES actually works. The Matroska header only
+ // has an arbitrary "input" samplerate, while libavcodec is fixed to
+ // output 48000.
+ sh_a->samplerate = 48000;
+ }
+
+ // Some files have broken default DefaultDuration set, which will lead to
+ // audio packets with incorrect timestamps. This follows FFmpeg commit
+ // 6158a3b, sample see FFmpeg ticket 2508.
+ if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
+ track->default_duration = 0;
+
+ // Deal with some FFmpeg-produced garbage, and assume all audio codecs can
+ // start decoding from anywhere.
+ if (strcmp(codec, "truehd") != 0)
+ track->require_keyframes = true;
+
+ sh_a->extradata = extradata;
+ sh_a->extradata_size = extradata_len;
+
+ sh->seek_preroll = track->codec_delay;
+
+ demux_add_sh_stream(demuxer, sh);
+
+ return 0;
+
+ error:
+ MP_WARN(demuxer, "Unknown/unsupported audio "
+ "codec ID '%s' for track %u or missing/faulty\n"
+ "private codec data.\n", track->codec_id, track->tnum);
+ demux_add_sh_stream(demuxer, sh); // add it anyway
+ return 1;
+}
+
+static const char *const mkv_sub_tag[][2] = {
+ { "S_VOBSUB", "dvd_subtitle" },
+ { "S_TEXT/SSA", "ass"},
+ { "S_TEXT/ASS", "ass"},
+ { "S_SSA", "ass"},
+ { "S_ASS", "ass"},
+ { "S_TEXT/ASCII", "subrip"},
+ { "S_TEXT/UTF8", "subrip"},
+ { "S_HDMV/PGS", "hdmv_pgs_subtitle"},
+ { "D_WEBVTT/SUBTITLES", "webvtt-webm"},
+ { "D_WEBVTT/CAPTIONS", "webvtt-webm"},
+ { "S_TEXT/WEBVTT", "webvtt"},
+ { "S_DVBSUB", "dvb_subtitle"},
+ { "S_ARIBSUB", "arib_caption"},
+ {0}
+};
+
+static void avcodec_par_destructor(void *p)
+{
+ avcodec_parameters_free(p);
+}
+
+static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
+{
+ const char *subtitle_type = NULL;
+ for (int n = 0; mkv_sub_tag[n][0]; n++) {
+ if (strcmp(track->codec_id, mkv_sub_tag[n][0]) == 0) {
+ subtitle_type = mkv_sub_tag[n][1];
+ break;
+ }
+ }
+
+ if (track->private_size > 0x10000000)
+ return 1;
+
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
+ init_track(demuxer, track, sh);
+
+ sh->codec->codec = subtitle_type;
+ bstr in = (bstr){track->private_data, track->private_size};
+ bstr buffer = demux_mkv_decode(demuxer->log, track, in, 2);
+ if (buffer.start && buffer.start != track->private_data) {
+ talloc_free(track->private_data);
+ talloc_steal(track, buffer.start);
+ track->private_data = buffer.start;
+ track->private_size = buffer.len;
+ }
+ sh->codec->extradata = track->private_data;
+ sh->codec->extradata_size = track->private_size;
+
+ if (!strcmp(sh->codec->codec, "arib_caption") && track->private_size >= 3) {
+ struct AVCodecParameters **lavp = talloc_ptrtype(track, lavp);
+
+ talloc_set_destructor(lavp, avcodec_par_destructor);
+
+ struct AVCodecParameters *lav = *lavp = sh->codec->lav_codecpar = avcodec_parameters_alloc();
+ MP_HANDLE_OOM(lav);
+
+ lav->codec_type = AVMEDIA_TYPE_SUBTITLE;
+ lav->codec_id = AV_CODEC_ID_ARIB_CAPTION;
+
+ int component_tag = track->private_data[0];
+ int data_component_id = AV_RB16(track->private_data + 1);
+ switch (data_component_id) {
+ case 0x0008:
+ // [0x30..0x37] are component tags utilized for
+ // non-mobile captioning service ("profile A").
+ if (component_tag >= 0x30 && component_tag <= 0x37)
+ lav->profile = FF_PROFILE_ARIB_PROFILE_A;
+ break;
+ case 0x0012:
+ // component tag 0x87 signifies a mobile/partial reception
+ // (1seg) captioning service ("profile C").
+ if (component_tag == 0x87)
+ lav->profile = FF_PROFILE_ARIB_PROFILE_C;
+ break;
+ }
+ if (lav->profile == FF_PROFILE_UNKNOWN)
+ MP_WARN(demuxer, "ARIB caption profile %02x / %04x not supported.\n",
+ component_tag, data_component_id);
+ }
+
+ demux_add_sh_stream(demuxer, sh);
+
+ if (!subtitle_type)
+ MP_ERR(demuxer, "Subtitle type '%s' is not supported.\n", track->codec_id);
+
+ return 0;
+}
+
+static void probe_x264_garbage(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ for (int n = 0; n < mkv_d->num_tracks; n++) {
+ mkv_track_t *track = mkv_d->tracks[n];
+ struct sh_stream *sh = track->stream;
+
+ if (!sh || sh->type != STREAM_VIDEO)
+ continue;
+
+ if (sh->codec->codec && strcmp(sh->codec->codec, "h264") != 0)
+ continue;
+
+ struct block_info *block = NULL;
+
+ // Find first block for this track.
+ // Restrict reading number of total packets. (Arbitrary to avoid bloat.)
+ for (int i = 0; i < 100; i++) {
+ if (i >= mkv_d->num_blocks && read_next_block_into_queue(demuxer) < 1)
+ break;
+ if (mkv_d->blocks[i].track == track) {
+ block = &mkv_d->blocks[i];
+ break;
+ }
+ }
+
+ if (!block || block->num_laces < 1)
+ continue;
+
+ bstr sblock = {block->laces[0]->data, block->laces[0]->size};
+ bstr nblock = demux_mkv_decode(demuxer->log, track, sblock, 1);
+
+ sh->codec->first_packet = new_demux_packet_from(nblock.start, nblock.len);
+ talloc_steal(mkv_d, sh->codec->first_packet);
+
+ if (nblock.start != sblock.start)
+ talloc_free(nblock.start);
+ }
+}
+
+static int read_ebml_header(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ stream_t *s = demuxer->stream;
+
+ if (ebml_read_id(s) != EBML_ID_EBML)
+ return 0;
+ struct ebml_ebml ebml_master = {0};
+ struct ebml_parse_ctx parse_ctx = { demuxer->log, .no_error_messages = true };
+ if (ebml_read_element(s, &parse_ctx, &ebml_master, &ebml_ebml_desc) < 0)
+ return 0;
+ bool is_matroska = false, is_webm = false;
+ if (!ebml_master.doc_type) {
+ MP_VERBOSE(demuxer, "File has EBML header but no doctype. "
+ "Assuming \"matroska\".\n");
+ is_matroska = true;
+ } else if (strcmp(ebml_master.doc_type, "matroska") == 0) {
+ is_matroska = true;
+ } else if (strcmp(ebml_master.doc_type, "webm") == 0) {
+ is_webm = true;
+ }
+ if (!is_matroska && !is_webm) {
+ MP_TRACE(demuxer, "no head found\n");
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+ }
+ mkv_d->probably_webm_dash_init &= is_webm;
+ if (ebml_master.doc_type_read_version > 2) {
+ MP_WARN(demuxer, "This looks like a Matroska file, "
+ "but we don't support format version %"PRIu64"\n",
+ ebml_master.doc_type_read_version);
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+ }
+ if ((ebml_master.n_ebml_read_version
+ && ebml_master.ebml_read_version != EBML_VERSION)
+ || (ebml_master.n_ebml_max_size_length
+ && ebml_master.ebml_max_size_length > 8)
+ || (ebml_master.n_ebml_max_id_length
+ && ebml_master.ebml_max_id_length != 4))
+ {
+ MP_WARN(demuxer, "This looks like a Matroska file, "
+ "but the header has bad parameters\n");
+ talloc_free(parse_ctx.talloc_ctx);
+ return 0;
+ }
+ talloc_free(parse_ctx.talloc_ctx);
+
+ return 1;
+}
+
+static int read_mkv_segment_header(demuxer_t *demuxer, int64_t *segment_end)
+{
+ stream_t *s = demuxer->stream;
+ int num_skip = 0;
+ if (demuxer->params)
+ num_skip = demuxer->params->matroska_wanted_segment;
+
+ while (stream_read_peek(s, &(char){0}, 1)) {
+ if (ebml_read_id(s) != MATROSKA_ID_SEGMENT) {
+ MP_VERBOSE(demuxer, "segment not found\n");
+ return 0;
+ }
+ MP_DBG(demuxer, "+ a segment...\n");
+ uint64_t len = ebml_read_length(s);
+ *segment_end = (len == EBML_UINT_INVALID) ? 0 : stream_tell(s) + len;
+ if (num_skip <= 0)
+ return 1;
+ num_skip--;
+ MP_DBG(demuxer, " (skipping)\n");
+ if (*segment_end <= 0)
+ break;
+ if (*segment_end >= stream_get_size(s))
+ return 0;
+ if (!stream_seek(s, *segment_end)) {
+ MP_WARN(demuxer, "Failed to seek in file\n");
+ return 0;
+ }
+ // Segments are like concatenated Matroska files
+ if (!read_ebml_header(demuxer))
+ return 0;
+ }
+
+ MP_VERBOSE(demuxer, "End of file, no further segments.\n");
+ return 0;
+}
+
+static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
+{
+ stream_t *s = demuxer->stream;
+ mkv_demuxer_t *mkv_d;
+ int64_t start_pos;
+ int64_t end_pos;
+
+ mkv_d = talloc_zero(demuxer, struct mkv_demuxer);
+ demuxer->priv = mkv_d;
+ mkv_d->tc_scale = 1000000;
+ mkv_d->a_skip_preroll = 1;
+ mkv_d->skip_to_timecode = INT64_MIN;
+
+ if (demuxer->params)
+ mkv_d->probably_webm_dash_init = demuxer->params->init_fragment.len > 0;
+
+ // Make sure you can seek back after read_ebml_header() if no EBML ID.
+ if (stream_read_peek(s, &(char[4]){0}, 4) != 4)
+ return -1;
+ if (!read_ebml_header(demuxer))
+ return -1;
+ MP_DBG(demuxer, "Found the head...\n");
+
+ if (!read_mkv_segment_header(demuxer, &end_pos))
+ return -1;
+
+ mkv_d->segment_start = stream_tell(s);
+ mkv_d->segment_end = end_pos;
+
+ struct MPOpts *mp_opts = mp_get_config_group(mkv_d, demuxer->global, &mp_opt_root);
+ mkv_d->edition_id = mp_opts->edition_id;
+ talloc_free(mp_opts);
+
+ mkv_d->opts = mp_get_config_group(mkv_d, demuxer->global, &demux_mkv_conf);
+
+ if (demuxer->params && demuxer->params->matroska_was_valid)
+ *demuxer->params->matroska_was_valid = true;
+
+ while (1) {
+ start_pos = stream_tell(s);
+ uint32_t id = ebml_read_id(s);
+ if (s->eof) {
+ if (!mkv_d->probably_webm_dash_init)
+ MP_WARN(demuxer, "Unexpected end of file (no clusters found)\n");
+ break;
+ }
+ if (id == MATROSKA_ID_CLUSTER) {
+ MP_DBG(demuxer, "|+ found cluster\n");
+ mkv_d->cluster_start = start_pos;
+ break;
+ }
+ int res = read_header_element(demuxer, id, start_pos);
+ if (res < 0)
+ return -1;
+ }
+
+ int64_t end = stream_get_size(s);
+
+ // Read headers that come after the first cluster (i.e. require seeking).
+ // Note: reading might increase ->num_headers.
+ // Likewise, ->headers might be reallocated.
+ int only_cue = -1;
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+ if (elem->parsed)
+ continue;
+ // Warn against incomplete files and skip headers outside of range.
+ if (elem->pos >= end || !s->seekable) {
+ elem->parsed = true; // don't bother if file is incomplete
+ if (end < 0 || !s->seekable) {
+ MP_WARN(demuxer, "Stream is not seekable or unknown size, "
+ "not reading mkv metadata at end of file.\n");
+ } else if (!mkv_d->eof_warning &&
+ !(mkv_d->probably_webm_dash_init && elem->pos == end))
+ {
+ MP_WARN(demuxer, "mkv metadata beyond end of file - incomplete "
+ "file?\n");
+ mkv_d->eof_warning = true;
+ }
+ continue;
+ }
+ only_cue = only_cue < 0 && elem->id == MATROSKA_ID_CUES;
+ }
+
+ // If there's only 1 needed element, and it's the cues, defer reading.
+ if (only_cue == 1) {
+ // Read cues when they are needed, to avoid seeking on opening.
+ MP_VERBOSE(demuxer, "Deferring reading cues.\n");
+ } else {
+ // Read them by ascending position to reduce unneeded seeks.
+ // O(n^2) because the number of elements is very low.
+ while (1) {
+ struct header_elem *lowest = NULL;
+ for (int n = 0; n < mkv_d->num_headers; n++) {
+ struct header_elem *elem = &mkv_d->headers[n];
+ if (elem->parsed)
+ continue;
+ if (!lowest || elem->pos < lowest->pos)
+ lowest = elem;
+ }
+
+ if (!lowest)
+ break;
+
+ if (read_deferred_element(demuxer, lowest) < 0)
+ return -1;
+ }
+ }
+
+ if (!stream_seek(s, start_pos)) {
+ MP_ERR(demuxer, "Couldn't seek back after reading headers?\n");
+ return -1;
+ }
+
+ MP_VERBOSE(demuxer, "All headers are parsed!\n");
+
+ display_create_tracks(demuxer);
+ add_coverart(demuxer);
+ process_tags(demuxer);
+
+ probe_first_timestamp(demuxer);
+ if (mkv_d->opts->probe_duration)
+ probe_last_timestamp(demuxer, start_pos);
+ probe_x264_garbage(demuxer);
+
+ return 0;
+}
+
+// Read the laced block data at the current stream position (until endpos as
+// indicated by the block length field) into individual buffers.
+static int demux_mkv_read_block_lacing(struct block_info *block, int type,
+ struct stream *s, uint64_t endpos)
+{
+ int laces;
+ uint32_t lace_size[MAX_NUM_LACES];
+
+
+ if (type == 0) { /* no lacing */
+ laces = 1;
+ lace_size[0] = endpos - stream_tell(s);
+ } else {
+ laces = stream_read_char(s);
+ if (laces < 0 || stream_tell(s) > endpos)
+ goto error;
+ laces += 1;
+
+ switch (type) {
+ case 1: { /* xiph lacing */
+ uint32_t total = 0;
+ for (int i = 0; i < laces - 1; i++) {
+ lace_size[i] = 0;
+ uint8_t t;
+ do {
+ t = stream_read_char(s);
+ if (s->eof || stream_tell(s) >= endpos)
+ goto error;
+ lace_size[i] += t;
+ } while (t == 0xFF);
+ total += lace_size[i];
+ }
+ uint32_t rest_length = endpos - stream_tell(s);
+ lace_size[laces - 1] = rest_length - total;
+ break;
+ }
+
+ case 2: { /* fixed-size lacing */
+ uint32_t full_length = endpos - stream_tell(s);
+ for (int i = 0; i < laces; i++)
+ lace_size[i] = full_length / laces;
+ break;
+ }
+
+ case 3: { /* EBML lacing */
+ uint64_t num = ebml_read_length(s);
+ if (num == EBML_UINT_INVALID || stream_tell(s) >= endpos)
+ goto error;
+
+ uint32_t total = lace_size[0] = num;
+ for (int i = 1; i < laces - 1; i++) {
+ int64_t snum = ebml_read_signed_length(s);
+ if (snum == EBML_INT_INVALID || stream_tell(s) >= endpos)
+ goto error;
+ lace_size[i] = lace_size[i - 1] + snum;
+ total += lace_size[i];
+ }
+ uint32_t rest_length = endpos - stream_tell(s);
+ lace_size[laces - 1] = rest_length - total;
+ break;
+ }
+
+ default:
+ goto error;
+ }
+ }
+
+ for (int i = 0; i < laces; i++) {
+ uint32_t size = lace_size[i];
+ if (stream_tell(s) + size > endpos || size > (1 << 30))
+ goto error;
+ int pad = MPMAX(AV_INPUT_BUFFER_PADDING_SIZE, AV_LZO_INPUT_PADDING);
+ AVBufferRef *buf = av_buffer_alloc(size + pad);
+ if (!buf)
+ goto error;
+ buf->size = size;
+ if (stream_read(s, buf->data, buf->size) != buf->size) {
+ av_buffer_unref(&buf);
+ goto error;
+ }
+ memset(buf->data + buf->size, 0, pad);
+ block->laces[block->num_laces++] = buf;
+ }
+
+ if (stream_tell(s) != endpos)
+ goto error;
+
+ return 0;
+
+ error:
+ return 1;
+}
+
+// Return whether the packet was handled & freed.
+static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
+ struct demux_packet *orig)
+{
+ uint32_t sps = track->sub_packet_size;
+ uint32_t sph = track->sub_packet_h;
+ uint32_t cfs = track->coded_framesize; // restricted to [1,0x40000000]
+ uint32_t w = track->audiopk_size;
+ uint32_t spc = track->sub_packet_cnt;
+ uint8_t *buffer = orig->buffer;
+ uint32_t size = orig->len;
+ demux_packet_t *dp;
+ // track->audio_buf allocation size
+ size_t audiobuf_size = sph * w;
+
+ if (!track->audio_buf || !track->audio_timestamp || !track->stream)
+ return false;
+
+ const char *codec = track->stream->codec->codec ? track->stream->codec->codec : "";
+ if (!strcmp(codec, "ra_288")) {
+ for (int x = 0; x < sph / 2; x++) {
+ uint64_t dst_offset = x * 2 * w + spc * (uint64_t)cfs;
+ if (dst_offset + cfs > audiobuf_size)
+ goto error;
+ uint64_t src_offset = x * (uint64_t)cfs;
+ if (src_offset + cfs > size)
+ goto error;
+ memcpy(track->audio_buf + dst_offset, buffer + src_offset, cfs);
+ }
+ } else if (!strcmp(codec, "cook") || !strcmp(codec, "atrac3")) {
+ for (int x = 0; x < w / sps; x++) {
+ uint32_t dst_offset =
+ sps * (sph * x + ((sph + 1) / 2) * (spc & 1) + (spc >> 1));
+ if (dst_offset + sps > audiobuf_size)
+ goto error;
+ uint32_t src_offset = sps * x;
+ if (src_offset + sps > size)
+ goto error;
+ memcpy(track->audio_buf + dst_offset, buffer + src_offset, sps);
+ }
+ } else if (!strcmp(codec, "sipr")) {
+ if (spc * w + w > audiobuf_size || w > size)
+ goto error;
+ memcpy(track->audio_buf + spc * w, buffer, w);
+ if (spc == sph - 1) {
+ int n;
+ int bs = sph * w * 2 / 96; // nibbles per subpacket
+ // Perform reordering
+ for (n = 0; n < 38; n++) {
+ unsigned int i = bs * sipr_swaps[n][0]; // 77 max
+ unsigned int o = bs * sipr_swaps[n][1]; // 95 max
+ // swap nibbles of block 'i' with 'o'
+ for (int j = 0; j < bs; j++) {
+ if (i / 2 >= audiobuf_size || o / 2 >= audiobuf_size)
+ goto error;
+ uint8_t iv = track->audio_buf[i / 2];
+ uint8_t ov = track->audio_buf[o / 2];
+ int x = (i & 1) ? iv >> 4 : iv & 0x0F;
+ int y = (o & 1) ? ov >> 4 : ov & 0x0F;
+ track->audio_buf[o / 2] = (ov & 0x0F) | (o & 1 ? x << 4 : x);
+ track->audio_buf[i / 2] = (iv & 0x0F) | (i & 1 ? y << 4 : y);
+ i++;
+ o++;
+ }
+ }
+ }
+ } else {
+ // Not a codec that requires reordering
+ return false;
+ }
+
+ track->audio_timestamp[track->sub_packet_cnt] =
+ track->ra_pts == orig->pts ? 0 : orig->pts;
+ track->ra_pts = orig->pts;
+
+ if (++(track->sub_packet_cnt) == sph) {
+ track->sub_packet_cnt = 0;
+ // apk_usize has same range as coded_framesize in worst case
+ uint32_t apk_usize = track->stream->codec->block_align;
+ if (apk_usize > audiobuf_size)
+ goto error;
+ // Release all the audio packets
+ for (int x = 0; x < sph * w / apk_usize; x++) {
+ dp = new_demux_packet_from(track->audio_buf + x * apk_usize,
+ apk_usize);
+ if (!dp)
+ goto error;
+ /* Put timestamp only on packets that correspond to original
+ * audio packets in file */
+ dp->pts = (x * apk_usize % w) ? MP_NOPTS_VALUE :
+ track->audio_timestamp[x * apk_usize / w];
+ dp->pos = orig->pos + x;
+ dp->keyframe = !x; // Mark first packet as keyframe
+ add_packet(demuxer, track->stream, dp);
+ }
+ }
+
+error:
+ talloc_free(orig);
+ return true;
+}
+
+static void mkv_seek_reset(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ for (int i = 0; i < mkv_d->num_tracks; i++) {
+ mkv_track_t *track = mkv_d->tracks[i];
+ if (track->av_parser)
+ av_parser_close(track->av_parser);
+ track->av_parser = NULL;
+ avcodec_free_context(&track->av_parser_codec);
+ }
+
+ for (int n = 0; n < mkv_d->num_blocks; n++)
+ free_block(&mkv_d->blocks[n]);
+ mkv_d->num_blocks = 0;
+
+ for (int n = 0; n < mkv_d->num_packets; n++)
+ talloc_free(mkv_d->packets[n]);
+ mkv_d->num_packets = 0;
+
+ mkv_d->skip_to_timecode = INT64_MIN;
+}
+
+// Copied from libavformat/matroskadec.c (FFmpeg 310f9dd / 2013-05-30)
+// Originally added with Libav commit 9b6f47c
+// License: LGPL v2.1 or later
+// Author header: The FFmpeg Project (this function still came from Libav)
+// Modified to use talloc, removed ffmpeg/libav specific error codes.
+static int libav_parse_wavpack(mkv_track_t *track, uint8_t *src,
+ uint8_t **pdst, int *size)
+{
+ uint8_t *dst = NULL;
+ int dstlen = 0;
+ int srclen = *size;
+ uint32_t samples;
+ uint16_t ver;
+ int offset = 0;
+
+ if (srclen < 12 || track->private_size < 2)
+ return -1;
+
+ ver = AV_RL16(track->private_data);
+
+ samples = AV_RL32(src);
+ src += 4;
+ srclen -= 4;
+
+ while (srclen >= 8) {
+ int multiblock;
+ uint32_t blocksize;
+ uint8_t *tmp;
+
+ uint32_t flags = AV_RL32(src);
+ uint32_t crc = AV_RL32(src + 4);
+ src += 8;
+ srclen -= 8;
+
+ multiblock = (flags & 0x1800) != 0x1800;
+ if (multiblock) {
+ if (srclen < 4)
+ goto fail;
+ blocksize = AV_RL32(src);
+ src += 4;
+ srclen -= 4;
+ } else {
+ blocksize = srclen;
+ }
+
+ if (blocksize > srclen)
+ goto fail;
+
+ if (dstlen > 0x10000000 || blocksize > 0x10000000)
+ goto fail;
+
+ tmp = talloc_realloc(track->parser_tmp, dst, uint8_t,
+ dstlen + blocksize + 32);
+ if (!tmp)
+ goto fail;
+ dst = tmp;
+ dstlen += blocksize + 32;
+
+ AV_WL32(dst + offset, MKTAG('w', 'v', 'p', 'k')); // tag
+ AV_WL32(dst + offset + 4, blocksize + 24); // blocksize - 8
+ AV_WL16(dst + offset + 8, ver); // version
+ AV_WL16(dst + offset + 10, 0); // track/index_no
+ AV_WL32(dst + offset + 12, 0); // total samples
+ AV_WL32(dst + offset + 16, 0); // block index
+ AV_WL32(dst + offset + 20, samples); // number of samples
+ AV_WL32(dst + offset + 24, flags); // flags
+ AV_WL32(dst + offset + 28, crc); // crc
+ memcpy (dst + offset + 32, src, blocksize); // block data
+
+ src += blocksize;
+ srclen -= blocksize;
+ offset += blocksize + 32;
+ }
+
+ *pdst = dst;
+ *size = dstlen;
+
+ return 0;
+
+fail:
+ talloc_free(dst);
+ return -1;
+}
+
+static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
+ struct demux_packet *dp)
+{
+ struct sh_stream *stream = track->stream;
+
+ if (stream->type == STREAM_AUDIO && handle_realaudio(demuxer, track, dp))
+ return;
+
+ if (strcmp(stream->codec->codec, "wavpack") == 0) {
+ int size = dp->len;
+ uint8_t *parsed;
+ if (libav_parse_wavpack(track, dp->buffer, &parsed, &size) >= 0) {
+ struct demux_packet *new = new_demux_packet_from(parsed, size);
+ if (new) {
+ demux_packet_copy_attribs(new, dp);
+ talloc_free(dp);
+ add_packet(demuxer, stream, new);
+ return;
+ }
+ }
+ }
+
+ if (strcmp(stream->codec->codec, "prores") == 0) {
+ size_t newlen = dp->len + 8;
+ struct demux_packet *new = new_demux_packet(newlen);
+ if (new) {
+ AV_WB32(new->buffer + 0, newlen);
+ AV_WB32(new->buffer + 4, MKBETAG('i', 'c', 'p', 'f'));
+ memcpy(new->buffer + 8, dp->buffer, dp->len);
+ demux_packet_copy_attribs(new, dp);
+ talloc_free(dp);
+ add_packet(demuxer, stream, new);
+ return;
+ }
+ }
+
+ if (track->parse && !track->av_parser) {
+ int id = mp_codec_to_av_codec_id(track->stream->codec->codec);
+ const AVCodec *codec = avcodec_find_decoder(id);
+ track->av_parser = av_parser_init(id);
+ if (codec)
+ track->av_parser_codec = avcodec_alloc_context3(codec);
+ }
+
+ if (!track->parse || !track->av_parser || !track->av_parser_codec) {
+ add_packet(demuxer, stream, dp);
+ return;
+ }
+
+ double tb = track->parse_timebase;
+ int64_t pts = dp->pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : dp->pts * tb;
+ int64_t dts = dp->dts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : dp->dts * tb;
+ bool copy_sidedata = true;
+
+ while (dp->len) {
+ uint8_t *data = NULL;
+ int size = 0;
+ int len = av_parser_parse2(track->av_parser, track->av_parser_codec,
+ &data, &size, dp->buffer, dp->len,
+ pts, dts, 0);
+ if (len < 0 || len > dp->len)
+ break;
+ dp->buffer += len;
+ dp->len -= len;
+ dp->pos += len;
+ if (size) {
+ struct demux_packet *new = new_demux_packet_from(data, size);
+ if (!new)
+ break;
+ if (copy_sidedata)
+ av_packet_copy_props(new->avpacket, dp->avpacket);
+ copy_sidedata = false;
+ demux_packet_copy_attribs(new, dp);
+ if (track->parse_timebase) {
+ new->pts = track->av_parser->pts == AV_NOPTS_VALUE
+ ? MP_NOPTS_VALUE : track->av_parser->pts / tb;
+ new->dts = track->av_parser->dts == AV_NOPTS_VALUE
+ ? MP_NOPTS_VALUE : track->av_parser->dts / tb;
+ }
+ add_packet(demuxer, stream, new);
+ }
+ pts = dts = AV_NOPTS_VALUE;
+ }
+
+ if (dp->len) {
+ add_packet(demuxer, stream, dp);
+ } else {
+ talloc_free(dp);
+ }
+}
+
+static void free_block(struct block_info *block)
+{
+ for (int n = 0; n < block->num_laces; n++)
+ av_buffer_unref(&block->laces[n]);
+ block->num_laces = 0;
+ TA_FREEP(&block->additions);
+}
+
+static void index_block(demuxer_t *demuxer, struct block_info *block)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ if (block->keyframe) {
+ add_block_position(demuxer, block->track, mkv_d->cluster_start,
+ block->timecode / mkv_d->tc_scale,
+ block->duration / mkv_d->tc_scale);
+ }
+}
+
+static int read_block(demuxer_t *demuxer, int64_t end, struct block_info *block)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ stream_t *s = demuxer->stream;
+ uint64_t num;
+ int16_t time;
+ uint64_t length;
+
+ free_block(block);
+ length = ebml_read_length(s);
+ if (!length || length > 500000000 || stream_tell(s) + length > (uint64_t)end)
+ return -1;
+
+ uint64_t endpos = stream_tell(s) + length;
+ int res = -1;
+
+ // Parse header of the Block element
+ /* first byte(s): track num */
+ num = ebml_read_length(s);
+ if (num == EBML_UINT_INVALID || stream_tell(s) >= endpos)
+ goto exit;
+
+ /* time (relative to cluster time) */
+ if (stream_tell(s) + 3 > endpos)
+ goto exit;
+ uint8_t c1 = stream_read_char(s);
+ uint8_t c2 = stream_read_char(s);
+ time = c1 << 8 | c2;
+
+ uint8_t header_flags = stream_read_char(s);
+
+ block->filepos = stream_tell(s);
+
+ int lace_type = (header_flags >> 1) & 0x03;
+ if (demux_mkv_read_block_lacing(block, lace_type, s, endpos))
+ goto exit;
+
+ if (block->simple)
+ block->keyframe = header_flags & 0x80;
+ block->timecode = time * mkv_d->tc_scale + mkv_d->cluster_tc;
+ for (int i = 0; i < mkv_d->num_tracks; i++) {
+ if (mkv_d->tracks[i]->tnum == num) {
+ block->track = mkv_d->tracks[i];
+ break;
+ }
+ }
+ if (!block->track) {
+ res = 0;
+ goto exit;
+ }
+
+ if (stream_tell(s) != endpos)
+ goto exit;
+
+ res = 1;
+exit:
+ if (res <= 0)
+ free_block(block);
+ stream_seek_skip(s, endpos);
+ return res;
+}
+
+static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ double current_pts;
+ bool keyframe = block_info->keyframe;
+ uint64_t block_duration = block_info->duration;
+ int64_t tc = block_info->timecode;
+ mkv_track_t *track = block_info->track;
+ struct sh_stream *stream = track->stream;
+ bool use_this_block = tc >= mkv_d->skip_to_timecode;
+
+ if (!demux_stream_is_selected(stream))
+ return 0;
+
+ current_pts = tc / 1e9 - track->codec_delay;
+
+ if (track->require_keyframes && !keyframe) {
+ keyframe = true;
+ if (!mkv_d->keyframe_warning) {
+ MP_WARN(demuxer, "This is a broken file! Packets with incorrect "
+ "keyframe flag found. Enabling workaround.\n");
+ mkv_d->keyframe_warning = true;
+ }
+ }
+
+ if (track->type == MATROSKA_TRACK_AUDIO) {
+ if (mkv_d->a_skip_to_keyframe)
+ use_this_block &= keyframe;
+ } else if (track->type == MATROSKA_TRACK_SUBTITLE) {
+ if (!use_this_block && mkv_d->subtitle_preroll) {
+ int64_t end_time = block_info->timecode + block_info->duration;
+ if (!block_info->duration)
+ end_time = INT64_MAX;
+ use_this_block = end_time > mkv_d->skip_to_timecode;
+ if (use_this_block) {
+ if (mkv_d->subtitle_preroll) {
+ mkv_d->subtitle_preroll--;
+ } else {
+ // This could overflow the demuxer queue.
+ use_this_block = 0;
+ }
+ }
+ }
+ if (use_this_block) {
+ if (block_info->num_laces > 1) {
+ MP_WARN(demuxer, "Subtitles use Matroska "
+ "lacing. This is abnormal and not supported.\n");
+ use_this_block = 0;
+ }
+ }
+ } else if (track->type == MATROSKA_TRACK_VIDEO) {
+ if (mkv_d->v_skip_to_keyframe)
+ use_this_block &= keyframe;
+ }
+
+ if (use_this_block) {
+ uint64_t filepos = block_info->filepos;
+
+ for (int i = 0; i < block_info->num_laces; i++) {
+ AVBufferRef *data = block_info->laces[i];
+ demux_packet_t *dp = NULL;
+
+ bstr block = {data->data, data->size};
+ bstr nblock = demux_mkv_decode(demuxer->log, track, block, 1);
+
+ if (block.start != nblock.start || block.len != nblock.len) {
+ // (avoidable copy of the entire data)
+ dp = new_demux_packet_from(nblock.start, nblock.len);
+ } else {
+ dp = new_demux_packet_from_buf(data);
+ }
+ if (!dp)
+ break;
+
+ dp->pos = filepos;
+ /* If default_duration is 0, assume no pts value is known
+ * for packets after the first one (rather than all pts
+ * values being the same). Also, don't use it for extra
+ * packets resulting from parsing. */
+ if (i == 0 || track->default_duration) {
+ dp->pts = current_pts + i * track->default_duration;
+ dp->keyframe = keyframe;
+ }
+ if (stream->codec->avi_dts)
+ MPSWAP(double, dp->pts, dp->dts);
+ if (i == 0 && block_info->duration_known)
+ dp->duration = block_duration / 1e9;
+ if (stream->type == STREAM_AUDIO) {
+ unsigned int srate = stream->codec->samplerate;
+ demux_packet_set_padding(dp, 0,
+ block_info->discardpadding / 1e9 * srate);
+ mkv_d->a_skip_preroll = 0;
+ }
+ if (block_info->additions) {
+ for (int n = 0; n < block_info->additions->n_block_more; n++) {
+ struct ebml_block_more *add =
+ &block_info->additions->block_more[n];
+ int64_t id = add->n_block_add_id ? add->block_add_id : 1;
+ demux_packet_add_blockadditional(dp, id,
+ add->block_additional.start, add->block_additional.len);
+ }
+ }
+
+ mkv_parse_and_add_packet(demuxer, track, dp);
+ talloc_free_children(track->parser_tmp);
+ filepos += data->size;
+ }
+
+ if (stream->type == STREAM_VIDEO) {
+ mkv_d->v_skip_to_keyframe = 0;
+ mkv_d->skip_to_timecode = INT64_MIN;
+ mkv_d->subtitle_preroll = 0;
+ } else if (stream->type == STREAM_AUDIO) {
+ mkv_d->a_skip_to_keyframe = 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int read_block_group(demuxer_t *demuxer, int64_t end,
+ struct block_info *block)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ stream_t *s = demuxer->stream;
+ *block = (struct block_info){ .keyframe = true };
+
+ while (stream_tell(s) < end) {
+ switch (ebml_read_id(s)) {
+ case MATROSKA_ID_BLOCKDURATION:
+ block->duration = ebml_read_uint(s);
+ if (block->duration == EBML_UINT_INVALID)
+ goto error;
+ block->duration *= mkv_d->tc_scale;
+ block->duration_known = true;
+ break;
+
+ case MATROSKA_ID_DISCARDPADDING:
+ block->discardpadding = ebml_read_uint(s);
+ if (block->discardpadding == EBML_UINT_INVALID)
+ goto error;
+ break;
+
+ case MATROSKA_ID_BLOCK:
+ if (read_block(demuxer, end, block) < 0)
+ goto error;
+ break;
+
+ case MATROSKA_ID_REFERENCEBLOCK:;
+ int64_t num = ebml_read_int(s);
+ if (num == EBML_INT_INVALID)
+ goto error;
+ block->keyframe = false;
+ break;
+
+ case MATROSKA_ID_BLOCKADDITIONS:;
+ struct ebml_block_additions additions = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &additions,
+ &ebml_block_additions_desc) < 0)
+ return -1;
+ if (additions.n_block_more > 0) {
+ block->additions = talloc_dup(NULL, &additions);
+ talloc_steal(block->additions, parse_ctx.talloc_ctx);
+ parse_ctx.talloc_ctx = NULL;
+ }
+ talloc_free(parse_ctx.talloc_ctx);
+ break;
+
+ case MATROSKA_ID_CLUSTER:
+ case EBML_ID_INVALID:
+ goto error;
+
+ default:
+ if (ebml_read_skip(demuxer->log, end, s) != 0)
+ goto error;
+ break;
+ }
+ }
+
+ return block->num_laces ? 1 : 0;
+
+error:
+ free_block(block);
+ return -1;
+}
+
+static int read_next_block_into_queue(demuxer_t *demuxer)
+{
+ mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
+ stream_t *s = demuxer->stream;
+ struct block_info block = {0};
+
+ while (1) {
+ while (stream_tell(s) < mkv_d->cluster_end) {
+ int64_t start_filepos = stream_tell(s);
+ switch (ebml_read_id(s)) {
+ case MATROSKA_ID_TIMECODE: {
+ uint64_t num = ebml_read_uint(s);
+ if (num == EBML_UINT_INVALID)
+ goto find_next_cluster;
+ mkv_d->cluster_tc = num * mkv_d->tc_scale;
+ break;
+ }
+
+ case MATROSKA_ID_BLOCKGROUP: {
+ int64_t end = ebml_read_length(s);
+ end += stream_tell(s);
+ if (end > mkv_d->cluster_end)
+ goto find_next_cluster;
+ int res = read_block_group(demuxer, end, &block);
+ if (res < 0)
+ goto find_next_cluster;
+ if (res > 0)
+ goto add_block;
+ break;
+ }
+
+ case MATROSKA_ID_SIMPLEBLOCK: {
+ block = (struct block_info){ .simple = true };
+ int res = read_block(demuxer, mkv_d->cluster_end, &block);
+ if (res < 0)
+ goto find_next_cluster;
+ if (res > 0)
+ goto add_block;
+ break;
+ }
+
+ case MATROSKA_ID_CLUSTER:
+ mkv_d->cluster_start = start_filepos;
+ goto next_cluster;
+
+ case EBML_ID_INVALID:
+ goto find_next_cluster;
+
+ default: ;
+ if (ebml_read_skip(demuxer->log, mkv_d->cluster_end, s) != 0)
+ goto find_next_cluster;
+ break;
+ }
+ }
+
+ find_next_cluster:
+ mkv_d->cluster_end = 0;
+ for (;;) {
+ mkv_d->cluster_start = stream_tell(s);
+ uint32_t id = ebml_read_id(s);
+ if (id == MATROSKA_ID_CLUSTER)
+ break;
+ if (s->eof)
+ return -1;
+ if (demux_cancel_test(demuxer))
+ return -1;
+ if (id == EBML_ID_EBML && stream_tell(s) >= mkv_d->segment_end) {
+ // Appended segment - don't use its clusters, consider this EOF.
+ stream_seek(s, stream_tell(s) - 4);
+ return -1;
+ }
+ // For the sake of robustness, consider even unknown level 1
+ // elements the same as unknown/broken IDs.
+ if ((!ebml_is_mkv_level1_id(id) && id != EBML_ID_VOID) ||
+ ebml_read_skip(demuxer->log, -1, s) != 0)
+ {
+ stream_seek(s, mkv_d->cluster_start);
+ ebml_resync_cluster(demuxer->log, s);
+ }
+ }
+ next_cluster:
+ mkv_d->cluster_end = ebml_read_length(s);
+ // mkv files for "streaming" can have this legally
+ if (mkv_d->cluster_end != EBML_UINT_INVALID)
+ mkv_d->cluster_end += stream_tell(s);
+ }
+ MP_ASSERT_UNREACHABLE();
+
+add_block:
+ index_block(demuxer, &block);
+ MP_TARRAY_APPEND(mkv_d, mkv_d->blocks, mkv_d->num_blocks, block);
+ return 1;
+}
+
+static int read_next_block(demuxer_t *demuxer, struct block_info *block)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ if (!mkv_d->num_blocks) {
+ int res = read_next_block_into_queue(demuxer);
+ if (res < 1)
+ return res;
+
+ assert(mkv_d->num_blocks);
+ }
+
+ *block = mkv_d->blocks[0];
+ MP_TARRAY_REMOVE_AT(mkv_d->blocks, mkv_d->num_blocks, 0);
+ return 1;
+}
+
+static bool demux_mkv_read_packet(struct demuxer *demuxer,
+ struct demux_packet **pkt)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+
+ for (;;) {
+ if (mkv_d->num_packets) {
+ *pkt = mkv_d->packets[0];
+ MP_TARRAY_REMOVE_AT(mkv_d->packets, mkv_d->num_packets, 0);
+ return true;
+ }
+
+ int res;
+ struct block_info block;
+ res = read_next_block(demuxer, &block);
+ if (res < 0)
+ return false;
+ if (res > 0) {
+ handle_block(demuxer, &block);
+ free_block(&block);
+ }
+ }
+}
+
+static mkv_index_t *get_highest_index_entry(struct demuxer *demuxer)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ assert(!mkv_d->index_complete); // would require separate code
+
+ mkv_index_t *index = NULL;
+ for (int n = 0; n < mkv_d->num_tracks; n++) {
+ int n_index = mkv_d->tracks[n]->last_index_entry;
+ if (n_index >= 0) {
+ mkv_index_t *index2 = &mkv_d->indexes[n_index];
+ if (!index || index2->filepos > index->filepos)
+ index = index2;
+ }
+ }
+ return index;
+}
+
+static int create_index_until(struct demuxer *demuxer, int64_t timecode)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ struct stream *s = demuxer->stream;
+
+ read_deferred_cues(demuxer);
+
+ if (mkv_d->index_complete)
+ return 0;
+
+ mkv_index_t *index = get_highest_index_entry(demuxer);
+
+ if (!index || index->timecode * mkv_d->tc_scale < timecode) {
+ stream_seek(s, index ? index->filepos : mkv_d->cluster_start);
+ MP_VERBOSE(demuxer, "creating index until TC %"PRId64"\n", timecode);
+ for (;;) {
+ int res;
+ struct block_info block;
+ res = read_next_block(demuxer, &block);
+ if (res < 0)
+ break;
+ if (res > 0) {
+ free_block(&block);
+ }
+ index = get_highest_index_entry(demuxer);
+ if (index && index->timecode * mkv_d->tc_scale >= timecode)
+ break;
+ }
+ }
+ if (!mkv_d->indexes) {
+ MP_WARN(demuxer, "no target for seek found\n");
+ return -1;
+ }
+ return 0;
+}
+
+static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
+ int64_t target_timecode, int flags)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ struct mkv_index *index = NULL;
+
+ int64_t min_diff = INT64_MIN;
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
+ int64_t diff =
+ mkv_d->indexes[i].timecode * mkv_d->tc_scale - target_timecode;
+ if (flags & SEEK_FORWARD)
+ diff = -diff;
+ if (min_diff != INT64_MIN) {
+ if (diff <= 0) {
+ if (min_diff <= 0 && diff <= min_diff)
+ continue;
+ } else if (diff >= min_diff)
+ continue;
+ }
+ min_diff = diff;
+ index = mkv_d->indexes + i;
+ }
+ }
+
+ if (index) { /* We've found an entry. */
+ uint64_t seek_pos = index->filepos;
+ if (flags & SEEK_HR) {
+ // Find the cluster with the highest filepos, that has a timestamp
+ // still lower than min_tc.
+ double secs = mkv_d->opts->subtitle_preroll_secs;
+ if (mkv_d->index_has_durations)
+ secs = MPMAX(secs, mkv_d->opts->subtitle_preroll_secs_index);
+ double pre_f = secs * 1e9 / mkv_d->tc_scale;
+ int64_t pre = pre_f >= (double)INT64_MAX ? INT64_MAX : (int64_t)pre_f;
+ int64_t min_tc = pre < index->timecode ? index->timecode - pre : 0;
+ uint64_t prev_target = 0;
+ int64_t prev_tc = 0;
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
+ struct mkv_index *cur = &mkv_d->indexes[i];
+ if (cur->timecode <= min_tc && cur->timecode >= prev_tc) {
+ prev_tc = cur->timecode;
+ prev_target = cur->filepos;
+ }
+ }
+ }
+ if (mkv_d->index_has_durations) {
+ // Find the earliest cluster that is not before prev_target,
+ // but contains subtitle packets overlapping with the cluster
+ // at seek_pos.
+ uint64_t target = seek_pos;
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ struct mkv_index *cur = &mkv_d->indexes[i];
+ if (cur->timecode <= index->timecode &&
+ cur->timecode + cur->duration > index->timecode &&
+ cur->filepos >= prev_target &&
+ cur->filepos < target)
+ {
+ target = cur->filepos;
+ }
+ }
+ prev_target = target;
+ }
+ if (prev_target)
+ seek_pos = prev_target;
+ }
+
+ mkv_d->cluster_end = 0;
+ stream_seek(demuxer->stream, seek_pos);
+ }
+ return index;
+}
+
+static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ int64_t old_pos = stream_tell(demuxer->stream);
+ uint64_t v_tnum = -1;
+ uint64_t a_tnum = -1;
+ bool st_active[STREAM_TYPE_COUNT] = {0};
+ mkv_seek_reset(demuxer);
+ for (int i = 0; i < mkv_d->num_tracks; i++) {
+ mkv_track_t *track = mkv_d->tracks[i];
+ if (demux_stream_is_selected(track->stream)) {
+ st_active[track->stream->type] = true;
+ if (track->type == MATROSKA_TRACK_VIDEO)
+ v_tnum = track->tnum;
+ if (track->type == MATROSKA_TRACK_AUDIO)
+ a_tnum = track->tnum;
+ }
+ }
+
+ mkv_d->subtitle_preroll = NUM_SUB_PREROLL_PACKETS;
+ int preroll_opt = mkv_d->opts->subtitle_preroll;
+ if (preroll_opt == 1 || (preroll_opt == 2 && mkv_d->index_has_durations))
+ flags |= SEEK_HR;
+ if (!st_active[STREAM_SUB])
+ flags &= ~SEEK_HR;
+
+ // Adjust the target a little bit to catch cases where the target position
+ // specifies a keyframe with high, but not perfect, precision.
+ seek_pts += flags & SEEK_FORWARD ? -0.005 : 0.005;
+
+ if (!(flags & SEEK_FACTOR)) { /* time in secs */
+ mkv_index_t *index = NULL;
+
+ seek_pts = MPMAX(seek_pts, 0);
+ int64_t target_timecode = seek_pts * 1e9 + 0.5;
+
+ if (create_index_until(demuxer, target_timecode) >= 0) {
+ int seek_id = st_active[STREAM_VIDEO] ? v_tnum : a_tnum;
+ index = seek_with_cues(demuxer, seek_id, target_timecode, flags);
+ if (!index)
+ index = seek_with_cues(demuxer, -1, target_timecode, flags);
+ }
+
+ if (!index)
+ stream_seek(demuxer->stream, old_pos);
+
+ if (flags & SEEK_FORWARD) {
+ mkv_d->skip_to_timecode = target_timecode;
+ } else {
+ mkv_d->skip_to_timecode = index ? index->timecode * mkv_d->tc_scale
+ : INT64_MIN;
+ }
+ } else {
+ stream_t *s = demuxer->stream;
+
+ read_deferred_cues(demuxer);
+
+ int64_t size = stream_get_size(s);
+ int64_t target_filepos = size * MPCLAMP(seek_pts, 0, 1);
+
+ mkv_index_t *index = NULL;
+ if (mkv_d->index_complete) {
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ if (mkv_d->indexes[i].tnum == v_tnum) {
+ if ((index == NULL)
+ || ((mkv_d->indexes[i].filepos >= target_filepos)
+ && ((index->filepos < target_filepos)
+ || (mkv_d->indexes[i].filepos < index->filepos))))
+ index = &mkv_d->indexes[i];
+ }
+ }
+ }
+
+ mkv_d->cluster_end = 0;
+
+ if (index) {
+ stream_seek(s, index->filepos);
+ mkv_d->skip_to_timecode = index->timecode * mkv_d->tc_scale;
+ } else {
+ stream_seek(s, MPMAX(target_filepos, 0));
+ if (ebml_resync_cluster(mp_null_log, s) < 0) {
+ // Assume EOF
+ mkv_d->cluster_end = size;
+ }
+ }
+ }
+
+ mkv_d->v_skip_to_keyframe = st_active[STREAM_VIDEO];
+ mkv_d->a_skip_to_keyframe = st_active[STREAM_AUDIO];
+ mkv_d->a_skip_preroll = mkv_d->a_skip_to_keyframe;
+}
+
+static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ if (!demuxer->seekable)
+ return;
+
+ // Pick some arbitrary video track
+ int v_tnum = -1;
+ for (int n = 0; n < mkv_d->num_tracks; n++) {
+ if (mkv_d->tracks[n]->type == MATROSKA_TRACK_VIDEO) {
+ v_tnum = mkv_d->tracks[n]->tnum;
+ break;
+ }
+ }
+ if (v_tnum < 0)
+ return;
+
+ // In full mode, we start reading data from the current file position,
+ // which works because this function is called after headers are parsed.
+ if (mkv_d->opts->probe_duration != 2) {
+ read_deferred_cues(demuxer);
+ if (mkv_d->index_complete) {
+ // Find last cluster that still has video packets
+ int64_t target = 0;
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ struct mkv_index *cur = &mkv_d->indexes[i];
+ if (cur->tnum == v_tnum)
+ target = MPMAX(target, cur->filepos);
+ }
+ if (!target)
+ return;
+
+ if (!stream_seek(demuxer->stream, target))
+ return;
+ } else {
+ // No index -> just try to find a random cluster towards file end.
+ int64_t size = stream_get_size(demuxer->stream);
+ stream_seek(demuxer->stream, MPMAX(size - 10 * 1024 * 1024, 0));
+ if (ebml_resync_cluster(mp_null_log, demuxer->stream) < 0)
+ stream_seek(demuxer->stream, start_pos); // full scan otherwise
+ }
+ }
+
+ mkv_seek_reset(demuxer);
+
+ int64_t last_ts[STREAM_TYPE_COUNT] = {0};
+ while (1) {
+ struct block_info block;
+ int res = read_next_block(demuxer, &block);
+ if (res < 0)
+ break;
+ if (res > 0) {
+ if (block.track && block.track->stream) {
+ enum stream_type type = block.track->stream->type;
+ uint64_t endtime = block.timecode + block.duration;
+ if (last_ts[type] < endtime)
+ last_ts[type] = endtime;
+ }
+ free_block(&block);
+ }
+ }
+
+ if (!last_ts[STREAM_VIDEO])
+ last_ts[STREAM_VIDEO] = mkv_d->cluster_tc;
+
+ if (last_ts[STREAM_VIDEO]) {
+ mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9 - demuxer->start_time;
+ demuxer->duration = mkv_d->duration;
+ }
+
+ stream_seek(demuxer->stream, start_pos);
+ mkv_d->cluster_start = mkv_d->cluster_end = 0;
+}
+
+static void probe_first_timestamp(struct demuxer *demuxer)
+{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+
+ if (!mkv_d->opts->probe_start_time)
+ return;
+
+ read_next_block_into_queue(demuxer);
+
+ demuxer->start_time = mkv_d->cluster_tc / 1e9;
+
+ if (demuxer->start_time)
+ MP_VERBOSE(demuxer, "Start PTS: %f\n", demuxer->start_time);
+}
+
+static void mkv_free(struct demuxer *demuxer)
+{
+ struct mkv_demuxer *mkv_d = demuxer->priv;
+ if (!mkv_d)
+ return;
+ mkv_seek_reset(demuxer);
+ for (int i = 0; i < mkv_d->num_tracks; i++)
+ demux_mkv_free_trackentry(mkv_d->tracks[i]);
+}
+
+const demuxer_desc_t demuxer_desc_matroska = {
+ .name = "mkv",
+ .desc = "Matroska",
+ .open = demux_mkv_open,
+ .read_packet = demux_mkv_read_packet,
+ .close = mkv_free,
+ .seek = demux_mkv_seek,
+ .load_timeline = build_ordered_chapter_timeline,
+};
+
+bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
+ struct matroska_segment_uid *b)
+{
+ return (!memcmp(a->segment, b->segment, 16) &&
+ a->edition == b->edition);
+}
diff --git a/demux/demux_mkv_timeline.c b/demux/demux_mkv_timeline.c
new file mode 100644
index 0000000..0c23e27
--- /dev/null
+++ b/demux/demux_mkv_timeline.c
@@ -0,0 +1,642 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <dirent.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <libavutil/common.h>
+
+#include "osdep/io.h"
+
+#include "mpv_talloc.h"
+
+#include "common/msg.h"
+#include "demux/demux.h"
+#include "demux/timeline.h"
+#include "demux/matroska.h"
+#include "options/m_config.h"
+#include "options/options.h"
+#include "options/path.h"
+#include "misc/bstr.h"
+#include "misc/thread_tools.h"
+#include "common/common.h"
+#include "common/playlist.h"
+#include "stream/stream.h"
+
+struct tl_ctx {
+ struct mp_log *log;
+ struct mpv_global *global;
+ struct MPOpts *opts;
+ struct timeline *tl;
+
+ struct demuxer *demuxer;
+
+ struct demuxer **sources;
+ int num_sources;
+
+ struct timeline_part *timeline;
+ int num_parts;
+
+ struct matroska_segment_uid *uids;
+ uint64_t start_time; // When the next part should start on the complete timeline.
+ uint64_t missing_time; // Total missing time so far.
+ uint64_t last_end_time; // When the last part ended on the complete timeline.
+ int num_chapters; // Total number of expected chapters.
+};
+
+struct find_entry {
+ char *name;
+ int matchlen;
+ off_t size;
+};
+
+static int cmp_entry(const void *pa, const void *pb)
+{
+ const struct find_entry *a = pa, *b = pb;
+ // check "similar" filenames first
+ int matchdiff = b->matchlen - a->matchlen;
+ if (matchdiff)
+ return FFSIGN(matchdiff);
+ // check small files first
+ off_t sizediff = a->size - b->size;
+ if (sizediff)
+ return FFSIGN(sizediff);
+ return 0;
+}
+
+static bool test_matroska_ext(const char *filename)
+{
+ static const char *const exts[] = {".mkv", ".mka", ".mks", ".mk3d", NULL};
+ for (int n = 0; exts[n]; n++) {
+ const char *suffix = exts[n];
+ int offset = strlen(filename) - strlen(suffix);
+ // name must end with suffix
+ if (offset > 0 && strcasecmp(filename + offset, suffix) == 0)
+ return true;
+ }
+ return false;
+}
+
+static char **find_files(const char *original_file)
+{
+ void *tmpmem = talloc_new(NULL);
+ char *basename = mp_basename(original_file);
+ struct bstr directory = mp_dirname(original_file);
+ char **results = talloc_size(NULL, 0);
+ char *dir_zero = bstrdup0(tmpmem, directory);
+ DIR *dp = opendir(dir_zero);
+ if (!dp) {
+ talloc_free(tmpmem);
+ return results;
+ }
+ struct find_entry *entries = NULL;
+ struct dirent *ep;
+ int num_results = 0;
+ while ((ep = readdir(dp))) {
+ if (!test_matroska_ext(ep->d_name))
+ continue;
+ // don't list the original name
+ if (!strcmp(ep->d_name, basename))
+ continue;
+
+ char *name = mp_path_join_bstr(results, directory, bstr0(ep->d_name));
+ char *s1 = ep->d_name;
+ char *s2 = basename;
+ int matchlen = 0;
+ while (*s1 && *s1++ == *s2++)
+ matchlen++;
+ // be a bit more fuzzy about matching the filename
+ matchlen = (matchlen + 3) / 5;
+
+ struct stat statbuf;
+ if (stat(name, &statbuf) != 0)
+ continue;
+ off_t size = statbuf.st_size;
+
+ entries = talloc_realloc(tmpmem, entries, struct find_entry,
+ num_results + 1);
+ entries[num_results] = (struct find_entry) { name, matchlen, size };
+ num_results++;
+ }
+ closedir(dp);
+ // NOTE: maybe should make it compare pointers instead
+ if (entries)
+ qsort(entries, num_results, sizeof(struct find_entry), cmp_entry);
+ results = talloc_realloc(NULL, results, char *, num_results);
+ for (int i = 0; i < num_results; i++) {
+ results[i] = entries[i].name;
+ }
+ talloc_free(tmpmem);
+ return results;
+}
+
+static bool has_source_request(struct tl_ctx *ctx,
+ struct matroska_segment_uid *new_uid)
+{
+ for (int i = 0; i < ctx->num_sources; ++i) {
+ if (demux_matroska_uid_cmp(&ctx->uids[i], new_uid))
+ return true;
+ }
+ return false;
+}
+
+// segment = get Nth segment of a multi-segment file
+static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
+{
+ bool was_valid = false;
+ struct demuxer_params params = {
+ .force_format = "mkv",
+ .matroska_num_wanted_uids = ctx->num_sources,
+ .matroska_wanted_uids = ctx->uids,
+ .matroska_wanted_segment = segment,
+ .matroska_was_valid = &was_valid,
+ .disable_timeline = true,
+ .stream_flags = ctx->tl->stream_origin,
+ };
+ struct mp_cancel *cancel = ctx->tl->cancel;
+ if (mp_cancel_test(cancel))
+ return false;
+
+ struct demuxer *d = demux_open_url(filename, &params, cancel, ctx->global);
+ if (!d)
+ return false;
+
+ struct matroska_data *m = &d->matroska_data;
+
+ for (int i = 1; i < ctx->num_sources; i++) {
+ struct matroska_segment_uid *uid = &ctx->uids[i];
+ if (ctx->sources[i])
+ continue;
+ /* Accept the source if the segment uid matches and the edition
+ * either matches or isn't specified. */
+ if (!memcmp(uid->segment, m->uid.segment, 16) &&
+ (!uid->edition || uid->edition == m->uid.edition))
+ {
+ MP_INFO(ctx, "Match for source %d: %s\n", i, d->filename);
+
+ if (!uid->edition) {
+ m->uid.edition = 0;
+ } else {
+ for (int j = 0; j < m->num_ordered_chapters; j++) {
+ struct matroska_chapter *c = m->ordered_chapters + j;
+
+ if (!c->has_segment_uid)
+ continue;
+
+ if (has_source_request(ctx, &c->uid))
+ continue;
+
+ /* Set the requested segment. */
+ MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
+ ctx->uids[ctx->num_sources] = c->uid;
+
+ /* Add a new source slot. */
+ MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
+ }
+ }
+
+ ctx->sources[i] = d;
+ return true;
+ }
+ }
+
+ demux_free(d);
+ return was_valid;
+}
+
+static void check_file(struct tl_ctx *ctx, char *filename, int first)
+{
+ for (int segment = first; ; segment++) {
+ if (!check_file_seg(ctx, filename, segment))
+ break;
+ }
+}
+
+static bool missing(struct tl_ctx *ctx)
+{
+ for (int i = 0; i < ctx->num_sources; i++) {
+ if (!ctx->sources[i])
+ return true;
+ }
+ return false;
+}
+
+static void find_ordered_chapter_sources(struct tl_ctx *ctx)
+{
+ struct MPOpts *opts = ctx->opts;
+ void *tmp = talloc_new(NULL);
+ int num_filenames = 0;
+ char **filenames = NULL;
+ if (ctx->num_sources > 1) {
+ char *main_filename = ctx->demuxer->filename;
+ MP_INFO(ctx, "This file references data from other sources.\n");
+ if (opts->ordered_chapters_files && opts->ordered_chapters_files[0]) {
+ MP_INFO(ctx, "Loading references from '%s'.\n",
+ opts->ordered_chapters_files);
+ struct playlist *pl =
+ playlist_parse_file(opts->ordered_chapters_files,
+ ctx->tl->cancel, ctx->global);
+ talloc_steal(tmp, pl);
+ for (int n = 0; n < pl->num_entries; n++) {
+ MP_TARRAY_APPEND(tmp, filenames, num_filenames,
+ pl->entries[n]->filename);
+ }
+ } else if (!ctx->demuxer->stream->is_local_file) {
+ MP_WARN(ctx, "Playback source is not a "
+ "normal disk file. Will not search for related files.\n");
+ } else {
+ MP_INFO(ctx, "Will scan other files in the "
+ "same directory to find referenced sources.\n");
+ filenames = find_files(main_filename);
+ num_filenames = MP_TALLOC_AVAIL(filenames);
+ talloc_steal(tmp, filenames);
+ }
+ // Possibly get further segments appended to the first segment
+ check_file(ctx, main_filename, 1);
+ }
+
+ int old_source_count;
+ do {
+ old_source_count = ctx->num_sources;
+ for (int i = 0; i < num_filenames; i++) {
+ if (!missing(ctx))
+ break;
+ MP_VERBOSE(ctx, "Checking file %s\n", filenames[i]);
+ check_file(ctx, filenames[i], 0);
+ }
+ } while (old_source_count != ctx->num_sources);
+
+ if (missing(ctx)) {
+ MP_ERR(ctx, "Failed to find ordered chapter part!\n");
+ int j = 1;
+ for (int i = 1; i < ctx->num_sources; i++) {
+ if (ctx->sources[i]) {
+ ctx->sources[j] = ctx->sources[i];
+ ctx->uids[j] = ctx->uids[i];
+ j++;
+ }
+ }
+ ctx->num_sources = j;
+ }
+
+ // Copy attachments from referenced sources so fonts are loaded for sub
+ // rendering.
+ for (int i = 1; i < ctx->num_sources; i++) {
+ for (int j = 0; j < ctx->sources[i]->num_attachments; j++) {
+ struct demux_attachment *att = &ctx->sources[i]->attachments[j];
+ demuxer_add_attachment(ctx->demuxer, att->name, att->type,
+ att->data, att->data_size);
+ }
+ }
+
+ talloc_free(tmp);
+}
+
+struct inner_timeline_info {
+ uint64_t skip; // Amount of time to skip.
+ uint64_t limit; // How much time is expected for the parent chapter.
+};
+
+static int64_t add_timeline_part(struct tl_ctx *ctx,
+ struct demuxer *source,
+ uint64_t start)
+{
+ /* Merge directly adjacent parts. We allow for a configurable fudge factor
+ * because of files which specify chapter end times that are one frame too
+ * early; we don't want to try seeking over a one frame gap. */
+ int64_t join_diff = start - ctx->last_end_time;
+ if (ctx->num_parts == 0
+ || FFABS(join_diff) > ctx->opts->chapter_merge_threshold * 1e6
+ || source != ctx->timeline[ctx->num_parts - 1].source)
+ {
+ struct timeline_part new = {
+ .start = ctx->start_time / 1e9,
+ .source_start = start / 1e9,
+ .source = source,
+ };
+ MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
+ } else if (ctx->num_parts > 0 && join_diff) {
+ // Chapter was merged at an inexact boundary; adjust timestamps to match.
+ MP_VERBOSE(ctx, "Merging timeline part %d with offset %g ms.\n",
+ ctx->num_parts, join_diff / 1e6);
+ ctx->start_time += join_diff;
+ return join_diff;
+ }
+
+ return 0;
+}
+
+static void build_timeline_loop(struct tl_ctx *ctx,
+ struct demux_chapter *chapters,
+ struct inner_timeline_info *info,
+ int current_source)
+{
+ uint64_t local_starttime = 0;
+ struct demuxer *source = ctx->sources[current_source];
+ struct matroska_data *m = &source->matroska_data;
+
+ for (int i = 0; i < m->num_ordered_chapters; i++) {
+ struct matroska_chapter *c = m->ordered_chapters + i;
+ uint64_t chapter_length = c->end - c->start;
+
+ if (!c->has_segment_uid)
+ c->uid = m->uid;
+
+ local_starttime += chapter_length;
+
+ // If we're before the start time for the chapter, skip to the next one.
+ if (local_starttime <= info->skip)
+ continue;
+
+ /* Look for the source for this chapter. */
+ for (int j = 0; j < ctx->num_sources; j++) {
+ struct demuxer *linked_source = ctx->sources[j];
+ struct matroska_data *linked_m = &linked_source->matroska_data;
+
+ if (!demux_matroska_uid_cmp(&c->uid, &linked_m->uid))
+ continue;
+
+ if (!info->limit) {
+ if (i >= ctx->num_chapters)
+ break; // malformed files can cause this to happen.
+
+ chapters[i].pts = ctx->start_time / 1e9;
+ chapters[i].metadata = talloc_zero(chapters, struct mp_tags);
+ mp_tags_set_str(chapters[i].metadata, "title", c->name);
+ }
+
+ /* If we're the source or it's a non-ordered edition reference,
+ * just add a timeline part from the source. */
+ if (current_source == j || !linked_m->uid.edition) {
+ uint64_t source_full_length = linked_source->duration * 1e9;
+ uint64_t source_length = source_full_length - c->start;
+ int64_t join_diff = 0;
+
+ /* If the chapter starts after the end of a source, there's
+ * nothing we can get from it. Instead, mark the entire chapter
+ * as missing and make the chapter length 0. */
+ if (source_full_length <= c->start) {
+ ctx->missing_time += chapter_length;
+ chapter_length = 0;
+ goto found;
+ }
+
+ /* If the source length starting at the chapter start is
+ * shorter than the chapter it is supposed to fill, add the gap
+ * to missing_time. Also, modify the chapter length to be what
+ * we actually have to avoid playing off the end of the file
+ * and not switching to the next source. */
+ if (source_length < chapter_length) {
+ ctx->missing_time += chapter_length - source_length;
+ chapter_length = source_length;
+ }
+
+ join_diff = add_timeline_part(ctx, linked_source, c->start);
+
+ /* If we merged two chapters into a single part due to them
+ * being off by a few frames, we need to change the limit to
+ * avoid chopping the end of the intended chapter (the adding
+ * frames case) or showing extra content (the removing frames
+ * case). Also update chapter_length to incorporate the extra
+ * time. */
+ if (info->limit) {
+ info->limit += join_diff;
+ chapter_length += join_diff;
+ }
+ } else {
+ /* We have an ordered edition as the source. Since this
+ * can jump around all over the place, we need to build up the
+ * timeline parts for each of its chapters, but not add them as
+ * chapters. */
+ struct inner_timeline_info new_info = {
+ .skip = c->start,
+ .limit = c->end
+ };
+ build_timeline_loop(ctx, chapters, &new_info, j);
+ // Already handled by the loop call.
+ chapter_length = 0;
+ }
+ ctx->last_end_time = c->end;
+ goto found;
+ }
+
+ ctx->missing_time += chapter_length;
+ chapter_length = 0;
+ found:;
+ ctx->start_time += chapter_length;
+ /* If we're after the limit on this chapter, stop here. */
+ if (info->limit && local_starttime >= info->limit) {
+ /* Back up the global start time by the overflow. */
+ ctx->start_time -= local_starttime - info->limit;
+ break;
+ }
+ }
+
+ /* If we stopped before the limit, add up the missing time. */
+ if (local_starttime < info->limit)
+ ctx->missing_time += info->limit - local_starttime;
+}
+
+static void check_track_compatibility(struct tl_ctx *tl, struct demuxer *mainsrc)
+{
+ for (int n = 0; n < tl->num_parts; n++) {
+ struct timeline_part *p = &tl->timeline[n];
+ if (p->source == mainsrc)
+ continue;
+
+ int num_source_streams = demux_get_num_stream(p->source);
+ for (int i = 0; i < num_source_streams; i++) {
+ struct sh_stream *s = demux_get_stream(p->source, i);
+ if (s->attached_picture)
+ continue;
+
+ if (!demuxer_stream_by_demuxer_id(mainsrc, s->type, s->demuxer_id)) {
+ MP_WARN(tl, "Source %s has %s stream with TID=%d, which "
+ "is not present in the ordered chapters main "
+ "file. This is a broken file. "
+ "The additional stream is ignored.\n",
+ p->source->filename, stream_type_name(s->type),
+ s->demuxer_id);
+ }
+ }
+
+ int num_main_streams = demux_get_num_stream(mainsrc);
+ for (int i = 0; i < num_main_streams; i++) {
+ struct sh_stream *m = demux_get_stream(mainsrc, i);
+ if (m->attached_picture)
+ continue;
+
+ struct sh_stream *s =
+ demuxer_stream_by_demuxer_id(p->source, m->type, m->demuxer_id);
+ if (s) {
+ // There are actually many more things that in theory have to
+ // match (though mpv's implementation doesn't care).
+ if (strcmp(s->codec->codec, m->codec->codec) != 0)
+ MP_WARN(tl, "Timeline segments have mismatching codec.\n");
+ if (s->codec->extradata_size != m->codec->extradata_size ||
+ (s->codec->extradata_size &&
+ memcmp(s->codec->extradata, m->codec->extradata,
+ s->codec->extradata_size) != 0))
+ MP_WARN(tl, "Timeline segments have mismatching codec info.\n");
+ } else {
+ MP_WARN(tl, "Source %s lacks %s stream with TID=%d, which "
+ "is present in the ordered chapters main "
+ "file. This is a broken file.\n",
+ p->source->filename, stream_type_name(m->type),
+ m->demuxer_id);
+ }
+ }
+ }
+}
+
+void build_ordered_chapter_timeline(struct timeline *tl)
+{
+ struct demuxer *demuxer = tl->demuxer;
+
+ if (!demuxer->matroska_data.ordered_chapters)
+ return;
+
+ struct tl_ctx *ctx = talloc_ptrtype(tl, ctx);
+ *ctx = (struct tl_ctx){
+ .log = tl->log,
+ .global = tl->global,
+ .tl = tl,
+ .demuxer = demuxer,
+ .opts = mp_get_config_group(ctx, tl->global, &mp_opt_root),
+ };
+
+ if (!ctx->opts->ordered_chapters || !demuxer->access_references) {
+ MP_INFO(demuxer, "File uses ordered chapters, but "
+ "you have disabled support for them. Ignoring.\n");
+ talloc_free(ctx);
+ return;
+ }
+
+ MP_INFO(ctx, "File uses ordered chapters, will build edit timeline.\n");
+
+ struct matroska_data *m = &demuxer->matroska_data;
+
+ // +1 because sources/uid_map[0] is original file even if all chapters
+ // actually use other sources and need separate entries
+ ctx->sources = talloc_zero_array(tl, struct demuxer *,
+ m->num_ordered_chapters + 1);
+ ctx->sources[0] = demuxer;
+ ctx->num_sources = 1;
+
+ ctx->uids = talloc_zero_array(NULL, struct matroska_segment_uid,
+ m->num_ordered_chapters + 1);
+ ctx->uids[0] = m->uid;
+ ctx->uids[0].edition = 0;
+
+ for (int i = 0; i < m->num_ordered_chapters; i++) {
+ struct matroska_chapter *c = m->ordered_chapters + i;
+ /* If there isn't a segment uid, we are the source. If the segment uid
+ * is our segment uid and the edition matches. We can't accept the
+ * "don't care" edition value of 0 since the user may have requested a
+ * non-default edition. */
+ if (!c->has_segment_uid || demux_matroska_uid_cmp(&c->uid, &m->uid))
+ continue;
+
+ if (has_source_request(ctx, &c->uid))
+ continue;
+
+ ctx->uids[ctx->num_sources] = c->uid;
+ ctx->sources[ctx->num_sources] = NULL;
+ ctx->num_sources++;
+ }
+
+ find_ordered_chapter_sources(ctx);
+
+ talloc_free(ctx->uids);
+ ctx->uids = NULL;
+
+ struct demux_chapter *chapters =
+ talloc_zero_array(tl, struct demux_chapter, m->num_ordered_chapters);
+
+ ctx->timeline = talloc_array_ptrtype(tl, ctx->timeline, 0);
+ ctx->num_chapters = m->num_ordered_chapters;
+
+ struct inner_timeline_info info = {
+ .skip = 0,
+ .limit = 0
+ };
+ build_timeline_loop(ctx, chapters, &info, 0);
+
+ // Fuck everything: filter out all "unset" chapters.
+ for (int n = m->num_ordered_chapters - 1; n >= 0; n--) {
+ if (!chapters[n].metadata)
+ MP_TARRAY_REMOVE_AT(chapters, m->num_ordered_chapters, n);
+ }
+
+ if (!ctx->num_parts) {
+ // None of the parts come from the file itself???
+ // Broken file, but we need at least 1 valid timeline part - add a dummy.
+ MP_WARN(ctx, "Ordered chapters file with no parts?\n");
+ struct timeline_part new = {
+ .source = demuxer,
+ };
+ MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
+ }
+
+ for (int n = 0; n < ctx->num_parts; n++) {
+ ctx->timeline[n].end = n == ctx->num_parts - 1
+ ? ctx->start_time / 1e9
+ : ctx->timeline[n + 1].start;
+ };
+
+ /* Ignore anything less than a millisecond when reporting missing time. If
+ * users really notice less than a millisecond missing, maybe this can be
+ * revisited. */
+ if (ctx->missing_time >= 1e6) {
+ MP_ERR(ctx, "There are %.3f seconds missing from the timeline!\n",
+ ctx->missing_time / 1e9);
+ }
+
+ // With Matroska, the "master" file usually dictates track layout etc.,
+ // except maybe with playlist-like files.
+ struct demuxer *track_layout = ctx->timeline[0].source;
+ for (int n = 0; n < ctx->num_parts; n++) {
+ if (ctx->timeline[n].source == ctx->demuxer) {
+ track_layout = ctx->demuxer;
+ break;
+ }
+ }
+
+ check_track_compatibility(ctx, track_layout);
+
+ tl->sources = ctx->sources;
+ tl->num_sources = ctx->num_sources;
+
+ struct timeline_par *par = talloc_ptrtype(tl, par);
+ *par = (struct timeline_par){
+ .parts = ctx->timeline,
+ .num_parts = ctx->num_parts,
+ .track_layout = track_layout,
+ };
+ MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par);
+ tl->chapters = chapters;
+ tl->num_chapters = m->num_ordered_chapters;
+ tl->meta = track_layout;
+ tl->format = "mkv_oc";
+}
diff --git a/demux/demux_null.c b/demux/demux_null.c
new file mode 100644
index 0000000..0ce3ac4
--- /dev/null
+++ b/demux/demux_null.c
@@ -0,0 +1,35 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "misc/bstr.h"
+#include "stream/stream.h"
+#include "demux.h"
+
+static int try_open_file(struct demuxer *demux, enum demux_check check)
+{
+ if (!bstr_startswith0(bstr0(demux->filename), "null://") &&
+ check != DEMUX_CHECK_REQUEST)
+ return -1;
+ demux->seekable = true;
+ return 0;
+}
+
+const struct demuxer_desc demuxer_desc_null = {
+ .name = "null",
+ .desc = "null demuxer",
+ .open = try_open_file,
+};
diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c
new file mode 100644
index 0000000..63355be
--- /dev/null
+++ b/demux/demux_playlist.c
@@ -0,0 +1,584 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <dirent.h>
+
+#include <libavutil/common.h>
+
+#include "common/common.h"
+#include "options/options.h"
+#include "options/m_config.h"
+#include "common/msg.h"
+#include "common/playlist.h"
+#include "misc/charset_conv.h"
+#include "misc/thread_tools.h"
+#include "options/path.h"
+#include "stream/stream.h"
+#include "osdep/io.h"
+#include "misc/natural_sort.h"
+#include "demux.h"
+
+#define PROBE_SIZE (8 * 1024)
+
+enum dir_mode {
+ DIR_AUTO,
+ DIR_LAZY,
+ DIR_RECURSIVE,
+ DIR_IGNORE,
+};
+
+#define OPT_BASE_STRUCT struct demux_playlist_opts
+struct demux_playlist_opts {
+ int dir_mode;
+};
+
+struct m_sub_options demux_playlist_conf = {
+ .opts = (const struct m_option[]) {
+ {"directory-mode", OPT_CHOICE(dir_mode,
+ {"auto", DIR_AUTO},
+ {"lazy", DIR_LAZY},
+ {"recursive", DIR_RECURSIVE},
+ {"ignore", DIR_IGNORE})},
+ {0}
+ },
+ .size = sizeof(struct demux_playlist_opts),
+ .defaults = &(const struct demux_playlist_opts){
+ .dir_mode = DIR_AUTO,
+ },
+};
+
+static bool check_mimetype(struct stream *s, const char *const *list)
+{
+ if (s->mime_type) {
+ for (int n = 0; list && list[n]; n++) {
+ if (strcasecmp(s->mime_type, list[n]) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+
+struct pl_parser {
+ struct mpv_global *global;
+ struct mp_log *log;
+ struct stream *s;
+ char buffer[2 * 1024 * 1024];
+ int utf16;
+ struct playlist *pl;
+ bool error;
+ bool probing;
+ bool force;
+ bool add_base;
+ bool line_allocated;
+ enum demux_check check_level;
+ struct stream *real_stream;
+ char *format;
+ char *codepage;
+ struct demux_playlist_opts *opts;
+};
+
+
+static uint16_t stream_read_word_endian(stream_t *s, bool big_endian)
+{
+ unsigned int y = stream_read_char(s);
+ y = (y << 8) | stream_read_char(s);
+ if (!big_endian)
+ y = ((y >> 8) & 0xFF) | (y << 8);
+ return y;
+}
+
+// Read characters until the next '\n' (including), or until the buffer in s is
+// exhausted.
+static int read_characters(stream_t *s, uint8_t *dst, int dstsize, int utf16)
+{
+ if (utf16 == 1 || utf16 == 2) {
+ uint8_t *cur = dst;
+ while (1) {
+ if ((cur - dst) + 8 >= dstsize) // PUT_UTF8 writes max. 8 bytes
+ return -1; // line too long
+ uint32_t c;
+ uint8_t tmp;
+ GET_UTF16(c, stream_read_word_endian(s, utf16 == 2), return -1;)
+ if (s->eof)
+ break; // legitimate EOF; ignore the case of partial reads
+ PUT_UTF8(c, tmp, *cur++ = tmp;)
+ if (c == '\n')
+ break;
+ }
+ return cur - dst;
+ } else {
+ uint8_t buf[1024];
+ int buf_len = stream_read_peek(s, buf, sizeof(buf));
+ uint8_t *end = memchr(buf, '\n', buf_len);
+ int len = end ? end - buf + 1 : buf_len;
+ if (len > dstsize)
+ return -1; // line too long
+ memcpy(dst, buf, len);
+ stream_seek_skip(s, stream_tell(s) + len);
+ return len;
+ }
+}
+
+// On error, or if the line is larger than max-1, return NULL and unset s->eof.
+// On EOF, return NULL, and s->eof will be set.
+// Otherwise, return the line (including \n or \r\n at the end of the line).
+// If the return value is non-NULL, it's always the same as mem.
+// utf16: 0: UTF8 or 8 bit legacy, 1: UTF16-LE, 2: UTF16-BE
+static char *read_line(stream_t *s, char *mem, int max, int utf16)
+{
+ if (max < 1)
+ return NULL;
+ int read = 0;
+ while (1) {
+ // Reserve 1 byte of ptr for terminating \0.
+ int l = read_characters(s, &mem[read], max - read - 1, utf16);
+ if (l < 0 || memchr(&mem[read], '\0', l)) {
+ MP_WARN(s, "error reading line\n");
+ return NULL;
+ }
+ read += l;
+ if (l == 0 || (read > 0 && mem[read - 1] == '\n'))
+ break;
+ }
+ mem[read] = '\0';
+ if (!stream_read_peek(s, &(char){0}, 1) && read == 0) // legitimate EOF
+ return NULL;
+ return mem;
+}
+
+static char *pl_get_line0(struct pl_parser *p)
+{
+ char *res = read_line(p->s, p->buffer, sizeof(p->buffer), p->utf16);
+ if (res) {
+ int len = strlen(res);
+ if (len > 0 && res[len - 1] == '\n')
+ res[len - 1] = '\0';
+ } else {
+ p->error |= !p->s->eof;
+ }
+ return res;
+}
+
+static bstr pl_get_line(struct pl_parser *p)
+{
+ bstr line = bstr_strip(bstr0(pl_get_line0(p)));
+ const char *charset = mp_charset_guess(p, p->log, line, p->codepage, 0);
+ if (charset && !mp_charset_is_utf8(charset)) {
+ bstr utf8 = mp_iconv_to_utf8(p->log, line, charset, 0);
+ if (utf8.start && utf8.start != line.start) {
+ line = utf8;
+ p->line_allocated = true;
+ }
+ }
+ return line;
+}
+
+// Helper in case mp_iconv_to_utf8 allocates memory
+static void pl_free_line(struct pl_parser *p, bstr line)
+{
+ if (p->line_allocated) {
+ talloc_free(line.start);
+ p->line_allocated = false;
+ }
+}
+
+static void pl_add(struct pl_parser *p, bstr entry)
+{
+ char *s = bstrto0(NULL, entry);
+ playlist_add_file(p->pl, s);
+ talloc_free(s);
+}
+
+static bool pl_eof(struct pl_parser *p)
+{
+ return p->error || p->s->eof;
+}
+
+static bool maybe_text(bstr d)
+{
+ for (int n = 0; n < d.len; n++) {
+ unsigned char c = d.start[n];
+ if (c < 32 && c != '\n' && c != '\r' && c != '\t')
+ return false;
+ }
+ return true;
+}
+
+static int parse_m3u(struct pl_parser *p)
+{
+ bstr line = pl_get_line(p);
+ if (p->probing && !bstr_equals0(line, "#EXTM3U")) {
+ // Last resort: if the file extension is m3u, it might be headerless.
+ if (p->check_level == DEMUX_CHECK_UNSAFE) {
+ char *ext = mp_splitext(p->real_stream->url, NULL);
+ char probe[PROBE_SIZE];
+ int len = stream_read_peek(p->real_stream, probe, sizeof(probe));
+ bstr data = {probe, len};
+ if (ext && data.len >= 2 && maybe_text(data)) {
+ const char *exts[] = {"m3u", "m3u8", NULL};
+ for (int n = 0; exts[n]; n++) {
+ if (strcasecmp(ext, exts[n]) == 0)
+ goto ok;
+ }
+ }
+ }
+ pl_free_line(p, line);
+ return -1;
+ }
+
+ok:
+ if (p->probing) {
+ pl_free_line(p, line);
+ return 0;
+ }
+
+ char *title = NULL;
+ while (line.len || !pl_eof(p)) {
+ bstr line_dup = line;
+ if (bstr_eatstart0(&line_dup, "#EXTINF:")) {
+ bstr duration, btitle;
+ if (bstr_split_tok(line_dup, ",", &duration, &btitle) && btitle.len) {
+ talloc_free(title);
+ title = bstrto0(NULL, btitle);
+ }
+ } else if (bstr_startswith0(line_dup, "#EXT-X-")) {
+ p->format = "hls";
+ } else if (line_dup.len > 0 && !bstr_startswith0(line_dup, "#")) {
+ char *fn = bstrto0(NULL, line_dup);
+ struct playlist_entry *e = playlist_entry_new(fn);
+ talloc_free(fn);
+ e->title = talloc_steal(e, title);
+ title = NULL;
+ playlist_add(p->pl, e);
+ }
+ pl_free_line(p, line);
+ line = pl_get_line(p);
+ }
+ pl_free_line(p, line);
+ talloc_free(title);
+ return 0;
+}
+
+static int parse_ref_init(struct pl_parser *p)
+{
+ bstr line = pl_get_line(p);
+ if (!bstr_equals0(line, "[Reference]")) {
+ pl_free_line(p, line);
+ return -1;
+ }
+ pl_free_line(p, line);
+
+ // ASF http streaming redirection - this is needed because ffmpeg http://
+ // and mmsh:// can not automatically switch automatically between each
+ // others. Both protocols use http - MMSH requires special http headers
+ // to "activate" it, and will in other cases return this playlist.
+ static const char *const mmsh_types[] = {"audio/x-ms-wax",
+ "audio/x-ms-wma", "video/x-ms-asf", "video/x-ms-afs", "video/x-ms-wmv",
+ "video/x-ms-wma", "application/x-mms-framed",
+ "application/vnd.ms.wms-hdr.asfv1", NULL};
+ bstr burl = bstr0(p->s->url);
+ if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) {
+ MP_INFO(p, "Redirecting to mmsh://\n");
+ playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl)));
+ return 0;
+ }
+
+ while (!pl_eof(p)) {
+ line = pl_get_line(p);
+ bstr value;
+ if (bstr_case_startswith(line, bstr0("Ref"))) {
+ bstr_split_tok(line, "=", &(bstr){0}, &value);
+ if (value.len)
+ pl_add(p, value);
+ }
+ pl_free_line(p, line);
+ }
+ return 0;
+}
+
+static int parse_ini_thing(struct pl_parser *p, const char *header,
+ const char *entry)
+{
+ bstr line = {0};
+ while (!line.len && !pl_eof(p))
+ line = pl_get_line(p);
+ if (bstrcasecmp0(line, header) != 0) {
+ pl_free_line(p, line);
+ return -1;
+ }
+ if (p->probing) {
+ pl_free_line(p, line);
+ return 0;
+ }
+ pl_free_line(p, line);
+ while (!pl_eof(p)) {
+ line = pl_get_line(p);
+ bstr key, value;
+ if (bstr_split_tok(line, "=", &key, &value) &&
+ bstr_case_startswith(key, bstr0(entry)))
+ {
+ value = bstr_strip(value);
+ if (bstr_startswith0(value, "\"") && bstr_endswith0(value, "\""))
+ value = bstr_splice(value, 1, -1);
+ pl_add(p, value);
+ }
+ pl_free_line(p, line);
+ }
+ return 0;
+}
+
+static int parse_pls(struct pl_parser *p)
+{
+ return parse_ini_thing(p, "[playlist]", "File");
+}
+
+static int parse_url(struct pl_parser *p)
+{
+ return parse_ini_thing(p, "[InternetShortcut]", "URL");
+}
+
+static int parse_txt(struct pl_parser *p)
+{
+ if (!p->force)
+ return -1;
+ if (p->probing)
+ return 0;
+ MP_WARN(p, "Reading plaintext playlist.\n");
+ while (!pl_eof(p)) {
+ bstr line = pl_get_line(p);
+ if (line.len == 0)
+ continue;
+ pl_add(p, line);
+ pl_free_line(p, line);
+ }
+ return 0;
+}
+
+#define MAX_DIR_STACK 20
+
+static bool same_st(struct stat *st1, struct stat *st2)
+{
+ return st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino;
+}
+
+struct pl_dir_entry {
+ char *path;
+ char *name;
+ struct stat st;
+ bool is_dir;
+};
+
+static int cmp_dir_entry(const void *a, const void *b)
+{
+ struct pl_dir_entry *a_entry = (struct pl_dir_entry*) a;
+ struct pl_dir_entry *b_entry = (struct pl_dir_entry*) b;
+ if (a_entry->is_dir == b_entry->is_dir) {
+ return mp_natural_sort_cmp(a_entry->name, b_entry->name);
+ } else {
+ return a_entry->is_dir ? 1 : -1;
+ }
+}
+
+// Return true if this was a readable directory.
+static bool scan_dir(struct pl_parser *p, char *path,
+ struct stat *dir_stack, int num_dir_stack)
+{
+ if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK)
+ return false; // things like mount bind loops
+
+ DIR *dp = opendir(path);
+ if (!dp) {
+ MP_ERR(p, "Could not read directory.\n");
+ return false;
+ }
+
+ struct pl_dir_entry *dir_entries = NULL;
+ int num_dir_entries = 0;
+ int path_len = strlen(path);
+ int dir_mode = p->opts->dir_mode;
+
+ struct dirent *ep;
+ while ((ep = readdir(dp))) {
+ if (ep->d_name[0] == '.')
+ continue;
+
+ if (mp_cancel_test(p->s->cancel))
+ break;
+
+ char *file = mp_path_join(p, path, ep->d_name);
+
+ struct stat st;
+ if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) {
+ if (dir_mode != DIR_IGNORE) {
+ for (int n = 0; n < num_dir_stack; n++) {
+ if (same_st(&dir_stack[n], &st)) {
+ MP_VERBOSE(p, "Skip recursive entry: %s\n", file);
+ goto skip;
+ }
+ }
+
+ struct pl_dir_entry d = {file, &file[path_len], st, true};
+ MP_TARRAY_APPEND(p, dir_entries, num_dir_entries, d);
+ }
+ } else {
+ struct pl_dir_entry f = {file, &file[path_len], .is_dir = false};
+ MP_TARRAY_APPEND(p, dir_entries, num_dir_entries, f);
+ }
+
+ skip: ;
+ }
+ closedir(dp);
+
+ if (dir_entries)
+ qsort(dir_entries, num_dir_entries, sizeof(dir_entries[0]), cmp_dir_entry);
+
+ for (int n = 0; n < num_dir_entries; n++) {
+ if (dir_mode == DIR_RECURSIVE && dir_entries[n].is_dir) {
+ dir_stack[num_dir_stack] = dir_entries[n].st;
+ char *file = dir_entries[n].path;
+ scan_dir(p, file, dir_stack, num_dir_stack + 1);
+ }
+ else {
+ playlist_add_file(p->pl, dir_entries[n].path);
+ }
+ }
+
+ return true;
+}
+
+static int parse_dir(struct pl_parser *p)
+{
+ if (!p->real_stream->is_directory)
+ return -1;
+ if (p->probing)
+ return 0;
+
+ char *path = mp_file_get_path(p, bstr0(p->real_stream->url));
+ if (!path)
+ return -1;
+
+ struct stat dir_stack[MAX_DIR_STACK];
+
+ if (p->opts->dir_mode == DIR_AUTO) {
+ struct MPOpts *opts = mp_get_config_group(NULL, p->global, &mp_opt_root);
+ p->opts->dir_mode = opts->shuffle ? DIR_RECURSIVE : DIR_LAZY;
+ talloc_free(opts);
+ }
+
+ scan_dir(p, path, dir_stack, 0);
+
+ p->add_base = false;
+
+ return p->pl->num_entries > 0 ? 0 : -1;
+}
+
+#define MIME_TYPES(...) \
+ .mime_types = (const char*const[]){__VA_ARGS__, NULL}
+
+struct pl_format {
+ const char *name;
+ int (*parse)(struct pl_parser *p);
+ const char *const *mime_types;
+};
+
+static const struct pl_format formats[] = {
+ {"directory", parse_dir},
+ {"m3u", parse_m3u,
+ MIME_TYPES("audio/mpegurl", "audio/x-mpegurl", "application/x-mpegurl")},
+ {"ini", parse_ref_init},
+ {"pls", parse_pls,
+ MIME_TYPES("audio/x-scpls")},
+ {"url", parse_url},
+ {"txt", parse_txt},
+};
+
+static const struct pl_format *probe_pl(struct pl_parser *p)
+{
+ int64_t start = stream_tell(p->s);
+ for (int n = 0; n < MP_ARRAY_SIZE(formats); n++) {
+ const struct pl_format *fmt = &formats[n];
+ stream_seek(p->s, start);
+ if (check_mimetype(p->s, fmt->mime_types)) {
+ MP_VERBOSE(p, "forcing format by mime-type.\n");
+ p->force = true;
+ return fmt;
+ }
+ if (fmt->parse(p) >= 0)
+ return fmt;
+ }
+ return NULL;
+}
+
+static int open_file(struct demuxer *demuxer, enum demux_check check)
+{
+ if (!demuxer->access_references)
+ return -1;
+
+ bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST;
+
+ struct pl_parser *p = talloc_zero(NULL, struct pl_parser);
+ p->global = demuxer->global;
+ p->log = demuxer->log;
+ p->pl = talloc_zero(p, struct playlist);
+ p->real_stream = demuxer->stream;
+ p->add_base = true;
+
+ struct demux_opts *opts = mp_get_config_group(p, p->global, &demux_conf);
+ p->codepage = opts->meta_cp;
+
+ char probe[PROBE_SIZE];
+ int probe_len = stream_read_peek(p->real_stream, probe, sizeof(probe));
+ p->s = stream_memory_open(demuxer->global, probe, probe_len);
+ p->s->mime_type = demuxer->stream->mime_type;
+ p->utf16 = stream_skip_bom(p->s);
+ p->force = force;
+ p->check_level = check;
+ p->probing = true;
+ const struct pl_format *fmt = probe_pl(p);
+ free_stream(p->s);
+ playlist_clear(p->pl);
+ if (!fmt) {
+ talloc_free(p);
+ return -1;
+ }
+
+ p->probing = false;
+ p->error = false;
+ p->s = demuxer->stream;
+ p->utf16 = stream_skip_bom(p->s);
+ p->opts = mp_get_config_group(demuxer, demuxer->global, &demux_playlist_conf);
+ bool ok = fmt->parse(p) >= 0 && !p->error;
+ if (p->add_base)
+ playlist_add_base_path(p->pl, mp_dirname(demuxer->filename));
+ playlist_set_stream_flags(p->pl, demuxer->stream_origin);
+ demuxer->playlist = talloc_steal(demuxer, p->pl);
+ demuxer->filetype = p->format ? p->format : fmt->name;
+ demuxer->fully_read = true;
+ talloc_free(p);
+ if (ok)
+ demux_close_stream(demuxer);
+ return ok ? 0 : -1;
+}
+
+const demuxer_desc_t demuxer_desc_playlist = {
+ .name = "playlist",
+ .desc = "Playlist file",
+ .open = open_file,
+};
diff --git a/demux/demux_raw.c b/demux/demux_raw.c
new file mode 100644
index 0000000..86b0368
--- /dev/null
+++ b/demux/demux_raw.c
@@ -0,0 +1,326 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/common.h>
+
+#include "common/av_common.h"
+
+#include "options/m_config.h"
+#include "options/m_option.h"
+
+#include "stream/stream.h"
+#include "demux.h"
+#include "stheader.h"
+#include "codec_tags.h"
+
+#include "video/fmt-conversion.h"
+#include "video/img_format.h"
+
+#include "osdep/endian.h"
+
+struct demux_rawaudio_opts {
+ struct m_channels channels;
+ int samplerate;
+ int aformat;
+};
+
+// Ad-hoc schema to systematically encode the format as int
+#define PCM(sign, is_float, bits, is_be) \
+ ((sign) | ((is_float) << 1) | ((is_be) << 2) | ((bits) << 3))
+#define NE (BYTE_ORDER == BIG_ENDIAN)
+
+#define OPT_BASE_STRUCT struct demux_rawaudio_opts
+const struct m_sub_options demux_rawaudio_conf = {
+ .opts = (const m_option_t[]) {
+ {"channels", OPT_CHANNELS(channels), .flags = M_OPT_CHANNELS_LIMITED},
+ {"rate", OPT_INT(samplerate), M_RANGE(1000, 8 * 48000)},
+ {"format", OPT_CHOICE(aformat,
+ {"u8", PCM(0, 0, 8, 0)},
+ {"s8", PCM(1, 0, 8, 0)},
+ {"u16le", PCM(0, 0, 16, 0)}, {"u16be", PCM(0, 0, 16, 1)},
+ {"s16le", PCM(1, 0, 16, 0)}, {"s16be", PCM(1, 0, 16, 1)},
+ {"u24le", PCM(0, 0, 24, 0)}, {"u24be", PCM(0, 0, 24, 1)},
+ {"s24le", PCM(1, 0, 24, 0)}, {"s24be", PCM(1, 0, 24, 1)},
+ {"u32le", PCM(0, 0, 32, 0)}, {"u32be", PCM(0, 0, 32, 1)},
+ {"s32le", PCM(1, 0, 32, 0)}, {"s32be", PCM(1, 0, 32, 1)},
+ {"floatle", PCM(0, 1, 32, 0)}, {"floatbe", PCM(0, 1, 32, 1)},
+ {"doublele",PCM(0, 1, 64, 0)}, {"doublebe", PCM(0, 1, 64, 1)},
+ {"u16", PCM(0, 0, 16, NE)},
+ {"s16", PCM(1, 0, 16, NE)},
+ {"u24", PCM(0, 0, 24, NE)},
+ {"s24", PCM(1, 0, 24, NE)},
+ {"u32", PCM(0, 0, 32, NE)},
+ {"s32", PCM(1, 0, 32, NE)},
+ {"float", PCM(0, 1, 32, NE)},
+ {"double", PCM(0, 1, 64, NE)})},
+ {0}
+ },
+ .size = sizeof(struct demux_rawaudio_opts),
+ .defaults = &(const struct demux_rawaudio_opts){
+ // Note that currently, stream_cdda expects exactly these parameters!
+ .channels = {
+ .set = 1,
+ .chmaps = (struct mp_chmap[]){ MP_CHMAP_INIT_STEREO, },
+ .num_chmaps = 1,
+ },
+ .samplerate = 44100,
+ .aformat = PCM(1, 0, 16, 0), // s16le
+ },
+};
+
+#undef PCM
+#undef NE
+
+struct demux_rawvideo_opts {
+ int vformat;
+ int mp_format;
+ char *codec;
+ int width;
+ int height;
+ float fps;
+ int imgsize;
+};
+
+#undef OPT_BASE_STRUCT
+#define OPT_BASE_STRUCT struct demux_rawvideo_opts
+const struct m_sub_options demux_rawvideo_conf = {
+ .opts = (const m_option_t[]) {
+ {"w", OPT_INT(width), M_RANGE(1, 8192)},
+ {"h", OPT_INT(height), M_RANGE(1, 8192)},
+ {"format", OPT_FOURCC(vformat)},
+ {"mp-format", OPT_IMAGEFORMAT(mp_format)},
+ {"codec", OPT_STRING(codec)},
+ {"fps", OPT_FLOAT(fps), M_RANGE(0.001, 1000)},
+ {"size", OPT_INT(imgsize), M_RANGE(1, 8192 * 8192 * 4)},
+ {0}
+ },
+ .size = sizeof(struct demux_rawvideo_opts),
+ .defaults = &(const struct demux_rawvideo_opts){
+ .vformat = MKTAG('I', '4', '2', '0'),
+ .width = 1280,
+ .height = 720,
+ .fps = 25,
+ },
+};
+
+struct priv {
+ struct sh_stream *sh;
+ int frame_size;
+ int read_frames;
+ double frame_rate;
+};
+
+static int generic_open(struct demuxer *demuxer)
+{
+ struct stream *s = demuxer->stream;
+ struct priv *p = demuxer->priv;
+
+ int64_t end = stream_get_size(s);
+ if (end >= 0)
+ demuxer->duration = (end / p->frame_size) / p->frame_rate;
+
+ return 0;
+}
+
+static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
+{
+ struct demux_rawaudio_opts *opts =
+ mp_get_config_group(demuxer, demuxer->global, &demux_rawaudio_conf);
+
+ if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
+ return -1;
+
+ if (opts->channels.num_chmaps != 1) {
+ MP_ERR(demuxer, "Invalid channels option given.\n");
+ return -1;
+ }
+
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
+ struct mp_codec_params *c = sh->codec;
+ c->channels = opts->channels.chmaps[0];
+ c->force_channels = true;
+ c->samplerate = opts->samplerate;
+
+ c->native_tb_num = 1;
+ c->native_tb_den = c->samplerate;
+
+ int f = opts->aformat;
+ // See PCM(): sign float bits endian
+ mp_set_pcm_codec(sh->codec, f & 1, f & 2, f >> 3, f & 4);
+ int samplesize = ((f >> 3) + 7) / 8;
+
+ demux_add_sh_stream(demuxer, sh);
+
+ struct priv *p = talloc_ptrtype(demuxer, p);
+ demuxer->priv = p;
+ *p = (struct priv) {
+ .sh = sh,
+ .frame_size = samplesize * c->channels.num,
+ .frame_rate = c->samplerate,
+ .read_frames = c->samplerate / 8,
+ };
+
+ return generic_open(demuxer);
+}
+
+static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
+{
+ struct demux_rawvideo_opts *opts =
+ mp_get_config_group(demuxer, demuxer->global, &demux_rawvideo_conf);
+
+ if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
+ return -1;
+
+ int width = opts->width;
+ int height = opts->height;
+
+ if (!width || !height) {
+ MP_ERR(demuxer, "rawvideo: width or height not specified!\n");
+ return -1;
+ }
+
+ const char *decoder = "rawvideo";
+ int imgfmt = opts->vformat;
+ int imgsize = opts->imgsize;
+ int mp_imgfmt = 0;
+ if (opts->mp_format && !IMGFMT_IS_HWACCEL(opts->mp_format)) {
+ mp_imgfmt = opts->mp_format;
+ if (!imgsize) {
+ struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(opts->mp_format);
+ for (int p = 0; p < desc.num_planes; p++) {
+ imgsize += ((width >> desc.xs[p]) * (height >> desc.ys[p]) *
+ desc.bpp[p] + 7) / 8;
+ }
+ }
+ } else if (opts->codec && opts->codec[0])
+ decoder = talloc_strdup(demuxer, opts->codec);
+
+ if (!imgsize) {
+ int bpp = 0;
+ switch (imgfmt) {
+ case MKTAG('Y', 'V', '1', '2'):
+ case MKTAG('I', '4', '2', '0'):
+ case MKTAG('I', 'Y', 'U', 'V'):
+ bpp = 12;
+ break;
+ case MKTAG('U', 'Y', 'V', 'Y'):
+ case MKTAG('Y', 'U', 'Y', '2'):
+ bpp = 16;
+ break;
+ }
+ if (!bpp) {
+ MP_ERR(demuxer, "rawvideo: img size not specified and unknown format!\n");
+ return -1;
+ }
+ imgsize = width * height * bpp / 8;
+ }
+
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
+ struct mp_codec_params *c = sh->codec;
+ c->codec = decoder;
+ c->codec_tag = imgfmt;
+ c->fps = opts->fps;
+ c->reliable_fps = true;
+ c->disp_w = width;
+ c->disp_h = height;
+ if (mp_imgfmt) {
+ c->lav_codecpar = avcodec_parameters_alloc();
+ MP_HANDLE_OOM(c->lav_codecpar);
+ c->lav_codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ c->lav_codecpar->codec_id = mp_codec_to_av_codec_id(decoder);
+ c->lav_codecpar->format = imgfmt2pixfmt(mp_imgfmt);
+ c->lav_codecpar->width = width;
+ c->lav_codecpar->height = height;
+ }
+ demux_add_sh_stream(demuxer, sh);
+
+ struct priv *p = talloc_ptrtype(demuxer, p);
+ demuxer->priv = p;
+ *p = (struct priv) {
+ .sh = sh,
+ .frame_size = imgsize,
+ .frame_rate = c->fps,
+ .read_frames = 1,
+ };
+
+ return generic_open(demuxer);
+}
+
+static bool raw_read_packet(struct demuxer *demuxer, struct demux_packet **pkt)
+{
+ struct priv *p = demuxer->priv;
+
+ if (demuxer->stream->eof)
+ return false;
+
+ struct demux_packet *dp = new_demux_packet(p->frame_size * p->read_frames);
+ if (!dp) {
+ MP_ERR(demuxer, "Can't read packet.\n");
+ return true;
+ }
+
+ dp->keyframe = true;
+ dp->pos = stream_tell(demuxer->stream);
+ dp->pts = (dp->pos / p->frame_size) / p->frame_rate;
+
+ int len = stream_read(demuxer->stream, dp->buffer, dp->len);
+ demux_packet_shorten(dp, len);
+
+ dp->stream = p->sh->index;
+ *pkt = dp;
+
+ return true;
+}
+
+static void raw_seek(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ struct priv *p = demuxer->priv;
+ stream_t *s = demuxer->stream;
+ int64_t end = stream_get_size(s);
+ int64_t frame_nr = seek_pts * p->frame_rate;
+ frame_nr = frame_nr - (frame_nr % p->read_frames);
+ int64_t pos = frame_nr * p->frame_size;
+ if (flags & SEEK_FACTOR)
+ pos = end * seek_pts;
+ if (pos < 0)
+ pos = 0;
+ if (end > 0 && pos > end)
+ pos = end;
+ stream_seek(s, (pos / p->frame_size) * p->frame_size);
+}
+
+const demuxer_desc_t demuxer_desc_rawaudio = {
+ .name = "rawaudio",
+ .desc = "Uncompressed audio",
+ .open = demux_rawaudio_open,
+ .read_packet = raw_read_packet,
+ .seek = raw_seek,
+};
+
+const demuxer_desc_t demuxer_desc_rawvideo = {
+ .name = "rawvideo",
+ .desc = "Uncompressed video",
+ .open = demux_rawvideo_open,
+ .read_packet = raw_read_packet,
+ .seek = raw_seek,
+};
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
new file mode 100644
index 0000000..5572fb5
--- /dev/null
+++ b/demux/demux_timeline.c
@@ -0,0 +1,719 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <limits.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+
+#include "demux.h"
+#include "timeline.h"
+#include "stheader.h"
+#include "stream/stream.h"
+
+struct segment {
+ int index; // index into virtual_source.segments[] (and timeline.parts[])
+ double start, end;
+ double d_start;
+ char *url;
+ bool lazy;
+ struct demuxer *d;
+ // stream_map[sh_stream.index] = virtual_stream, where sh_stream is a stream
+ // from the source d, and virtual_stream is a streamexported by the
+ // timeline demuxer (virtual_stream.sh). It's used to map the streams of the
+ // source onto the set of streams of the virtual timeline.
+ // Uses NULL for streams that do not appear in the virtual timeline.
+ struct virtual_stream **stream_map;
+ int num_stream_map;
+};
+
+// Information for each stream on the virtual timeline. (Mirrors streams
+// exposed by demux_timeline.)
+struct virtual_stream {
+ struct sh_stream *sh; // stream exported by demux_timeline
+ bool selected; // ==demux_stream_is_selected(sh)
+ int eos_packets; // deal with b-frame delay
+ struct virtual_source *src; // group this stream is part of
+};
+
+// This represents a single timeline source. (See timeline.pars[]. For each
+// timeline_par struct there is a virtual_source.)
+struct virtual_source {
+ struct timeline_par *tl;
+
+ bool dash, no_clip, delay_open;
+
+ struct segment **segments;
+ int num_segments;
+ struct segment *current;
+
+ struct virtual_stream **streams;
+ int num_streams;
+
+ // Total number of packets received past end of segment. Used
+ // to be clever about determining when to switch segments.
+ int eos_packets;
+
+ bool eof_reached;
+ double dts; // highest read DTS (or PTS if no DTS available)
+ bool any_selected; // at least one stream is actually selected
+
+ struct demux_packet *next;
+};
+
+struct priv {
+ struct timeline *tl;
+ bool owns_tl;
+
+ double duration;
+
+ // As the demuxer user sees it.
+ struct virtual_stream **streams;
+ int num_streams;
+
+ struct virtual_source **sources;
+ int num_sources;
+};
+
+static void update_slave_stats(struct demuxer *demuxer, struct demuxer *slave)
+{
+ demux_report_unbuffered_read_bytes(demuxer, demux_get_bytes_read_hack(slave));
+}
+
+static bool target_stream_used(struct segment *seg, struct virtual_stream *vs)
+{
+ for (int n = 0; n < seg->num_stream_map; n++) {
+ if (seg->stream_map[n] == vs)
+ return true;
+ }
+ return false;
+}
+
+// Create mapping from segment streams to virtual timeline streams.
+static void associate_streams(struct demuxer *demuxer,
+ struct virtual_source *src,
+ struct segment *seg)
+{
+ if (!seg->d || seg->stream_map)
+ return;
+
+ int num_streams = demux_get_num_stream(seg->d);
+ for (int n = 0; n < num_streams; n++) {
+ struct sh_stream *sh = demux_get_stream(seg->d, n);
+ struct virtual_stream *other = NULL;
+
+ for (int i = 0; i < src->num_streams; i++) {
+ struct virtual_stream *vs = src->streams[i];
+
+ // The stream must always have the same media type. Also, a stream
+ // can't be assigned multiple times.
+ if (sh->type != vs->sh->type || target_stream_used(seg, vs))
+ continue;
+
+ // By default pick the first matching stream.
+ if (!other)
+ other = vs;
+
+ // Matching by demuxer ID is supposedly useful and preferable for
+ // ordered chapters.
+ if (sh->demuxer_id >= 0 && sh->demuxer_id == vs->sh->demuxer_id)
+ other = vs;
+ }
+
+ if (!other) {
+ MP_WARN(demuxer, "Source stream %d (%s) unused and hidden.\n",
+ n, stream_type_name(sh->type));
+ }
+
+ MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map, other);
+ }
+}
+
+static void reselect_streams(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int n = 0; n < p->num_streams; n++) {
+ struct virtual_stream *vs = p->streams[n];
+ vs->selected = demux_stream_is_selected(vs->sh);
+ }
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+
+ if (!seg->d)
+ continue;
+
+ for (int i = 0; i < seg->num_stream_map; i++) {
+ bool selected =
+ seg->stream_map[i] && seg->stream_map[i]->selected;
+
+ // This stops demuxer readahead for inactive segments.
+ if (!src->current || seg->d != src->current->d)
+ selected = false;
+ struct sh_stream *sh = demux_get_stream(seg->d, i);
+ demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
+
+ update_slave_stats(demuxer, seg->d);
+ }
+ }
+
+ bool was_selected = src->any_selected;
+ src->any_selected = false;
+
+ for (int n = 0; n < src->num_streams; n++)
+ src->any_selected |= src->streams[n]->selected;
+
+ if (!was_selected && src->any_selected) {
+ src->eof_reached = false;
+ src->dts = MP_NOPTS_VALUE;
+ TA_FREEP(&src->next);
+ }
+ }
+}
+
+static void close_lazy_segments(struct demuxer *demuxer,
+ struct virtual_source *src)
+{
+ // unload previous segment
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+ if (seg != src->current && seg->d && seg->lazy) {
+ TA_FREEP(&src->next); // might depend on one of the sub-demuxers
+ demux_free(seg->d);
+ seg->d = NULL;
+ }
+ }
+}
+
+static void reopen_lazy_segments(struct demuxer *demuxer,
+ struct virtual_source *src)
+{
+ if (src->current->d)
+ return;
+
+ // Note: in delay_open mode, we must _not_ close segments during demuxing,
+ // because demuxed packets have demux_packet.codec set to objects owned
+ // by the segments. Closing them would create dangling pointers.
+ if (!src->delay_open)
+ close_lazy_segments(demuxer, src);
+
+ struct demuxer_params params = {
+ .init_fragment = src->tl->init_fragment,
+ .skip_lavf_probing = src->tl->dash,
+ .stream_flags = demuxer->stream_origin,
+ };
+ src->current->d = demux_open_url(src->current->url, &params,
+ demuxer->cancel, demuxer->global);
+ if (!src->current->d && !demux_cancel_test(demuxer))
+ MP_ERR(demuxer, "failed to load segment\n");
+ if (src->current->d)
+ update_slave_stats(demuxer, src->current->d);
+ associate_streams(demuxer, src, src->current);
+}
+
+static void switch_segment(struct demuxer *demuxer, struct virtual_source *src,
+ struct segment *new, double start_pts, int flags,
+ bool init)
+{
+ if (!(flags & SEEK_FORWARD))
+ flags |= SEEK_HR;
+
+ MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
+
+ if (src->current && src->current->d)
+ update_slave_stats(demuxer, src->current->d);
+
+ src->current = new;
+ reopen_lazy_segments(demuxer, src);
+ if (!new->d)
+ return;
+ reselect_streams(demuxer);
+ if (!src->no_clip)
+ demux_set_ts_offset(new->d, new->start - new->d_start);
+ if (!src->no_clip || !init)
+ demux_seek(new->d, start_pts, flags);
+
+ for (int n = 0; n < src->num_streams; n++) {
+ struct virtual_stream *vs = src->streams[n];
+ vs->eos_packets = 0;
+ }
+
+ src->eof_reached = false;
+ src->eos_packets = 0;
+}
+
+static void do_read_next_packet(struct demuxer *demuxer,
+ struct virtual_source *src)
+{
+ if (src->next)
+ return;
+
+ struct segment *seg = src->current;
+ if (!seg || !seg->d) {
+ src->eof_reached = true;
+ return;
+ }
+
+ struct demux_packet *pkt = demux_read_any_packet(seg->d);
+ if (!pkt || (!src->no_clip && pkt->pts >= seg->end))
+ src->eos_packets += 1;
+
+ update_slave_stats(demuxer, seg->d);
+
+ // Test for EOF. Do this here to properly run into EOF even if other
+ // streams are disabled etc. If it somehow doesn't manage to reach the end
+ // after demuxing a high (bit arbitrary) number of packets, assume one of
+ // the streams went EOF early.
+ bool eos_reached = src->eos_packets > 0;
+ if (eos_reached && src->eos_packets < 100) {
+ for (int n = 0; n < src->num_streams; n++) {
+ struct virtual_stream *vs = src->streams[n];
+ if (vs->selected) {
+ int max_packets = 0;
+ if (vs->sh->type == STREAM_AUDIO)
+ max_packets = 1;
+ if (vs->sh->type == STREAM_VIDEO)
+ max_packets = 16;
+ eos_reached &= vs->eos_packets >= max_packets;
+ }
+ }
+ }
+
+ src->eof_reached = false;
+
+ if (eos_reached || !pkt) {
+ talloc_free(pkt);
+
+ struct segment *next = NULL;
+ for (int n = 0; n < src->num_segments - 1; n++) {
+ if (src->segments[n] == seg) {
+ next = src->segments[n + 1];
+ break;
+ }
+ }
+ if (!next) {
+ src->eof_reached = true;
+ return;
+ }
+ switch_segment(demuxer, src, next, next->start, 0, true);
+ return; // reader will retry
+ }
+
+ if (pkt->stream < 0 || pkt->stream >= seg->num_stream_map)
+ goto drop;
+
+ if (!src->no_clip || src->delay_open) {
+ pkt->segmented = true;
+ if (!pkt->codec)
+ pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
+ }
+ if (!src->no_clip) {
+ if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
+ pkt->start = seg->start;
+ if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
+ pkt->end = seg->end;
+ }
+
+ struct virtual_stream *vs = seg->stream_map[pkt->stream];
+ if (!vs)
+ goto drop;
+
+ // for refresh seeks, demux.c prefers monotonically increasing packet pos
+ // since the packet pos is meaningless anyway for timeline, use it
+ if (pkt->pos >= 0)
+ pkt->pos |= (seg->index & 0x7FFFULL) << 48;
+
+ if (pkt->pts != MP_NOPTS_VALUE && !src->no_clip && pkt->pts >= seg->end) {
+ // Trust the keyframe flag. Might not always be a good idea, but will
+ // be sufficient at least with mkv. The problem is that this flag is
+ // not well-defined in libavformat and is container-dependent.
+ if (pkt->keyframe || vs->eos_packets == INT_MAX) {
+ vs->eos_packets = INT_MAX;
+ goto drop;
+ } else {
+ vs->eos_packets += 1;
+ }
+ }
+
+ double dts = pkt->dts != MP_NOPTS_VALUE ? pkt->dts : pkt->pts;
+ if (src->dts == MP_NOPTS_VALUE || (dts != MP_NOPTS_VALUE && dts > src->dts))
+ src->dts = dts;
+
+ pkt->stream = vs->sh->index;
+ src->next = pkt;
+ return;
+
+drop:
+ talloc_free(pkt);
+}
+
+static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
+{
+ struct priv *p = demuxer->priv;
+ struct virtual_source *src = NULL;
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *cur = p->sources[x];
+
+ if (!cur->any_selected || cur->eof_reached)
+ continue;
+
+ if (!cur->current)
+ switch_segment(demuxer, cur, cur->segments[0], 0, 0, true);
+
+ if (!cur->any_selected || !cur->current || !cur->current->d)
+ continue;
+
+ if (!src || cur->dts == MP_NOPTS_VALUE ||
+ (src->dts != MP_NOPTS_VALUE && cur->dts < src->dts))
+ src = cur;
+ }
+
+ if (!src)
+ return false;
+
+ do_read_next_packet(demuxer, src);
+ *out_pkt = src->next;
+ src->next = NULL;
+ return true;
+}
+
+static void seek_source(struct demuxer *demuxer, struct virtual_source *src,
+ double pts, int flags)
+{
+ struct segment *new = src->segments[src->num_segments - 1];
+ for (int n = 0; n < src->num_segments; n++) {
+ if (pts < src->segments[n]->end) {
+ new = src->segments[n];
+ break;
+ }
+ }
+
+ switch_segment(demuxer, src, new, pts, flags, false);
+
+ src->dts = MP_NOPTS_VALUE;
+ TA_FREEP(&src->next);
+}
+
+static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ seek_pts = seek_pts * ((flags & SEEK_FACTOR) ? p->duration : 1);
+ flags &= SEEK_FORWARD | SEEK_HR;
+
+ // The intention is to seek audio streams to the same target as video
+ // streams if they are separate streams. Video streams usually have more
+ // coarse keyframe snapping, which could leave video without audio.
+ struct virtual_source *master = NULL;
+ bool has_slaves = false;
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ bool any_audio = false, any_video = false;
+ for (int i = 0; i < src->num_streams; i++) {
+ struct virtual_stream *str = src->streams[i];
+ if (str->selected) {
+ if (str->sh->type == STREAM_VIDEO)
+ any_video = true;
+ if (str->sh->type == STREAM_AUDIO)
+ any_audio = true;
+ }
+ }
+
+ if (any_video)
+ master = src;
+ // A true slave stream is audio-only; this also prevents that the master
+ // stream is considered a slave stream.
+ if (any_audio && !any_video)
+ has_slaves = true;
+ }
+
+ if (!has_slaves)
+ master = NULL;
+
+ if (master) {
+ seek_source(demuxer, master, seek_pts, flags);
+ do_read_next_packet(demuxer, master);
+ if (master->next && master->next->pts != MP_NOPTS_VALUE) {
+ // Assume we got a seek target. Actually apply the heuristic.
+ MP_VERBOSE(demuxer, "adjust seek target from %f to %f\n", seek_pts,
+ master->next->pts);
+ seek_pts = master->next->pts;
+ flags &= ~(unsigned)SEEK_FORWARD;
+ }
+ }
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+ if (src != master && src->any_selected)
+ seek_source(demuxer, src, seek_pts, flags);
+ }
+}
+
+static void print_timeline(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ MP_VERBOSE(demuxer, "Timeline segments:\n");
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ if (x >= 1)
+ MP_VERBOSE(demuxer, " --- new parallel stream ---\n");
+
+ for (int n = 0; n < src->num_segments; n++) {
+ struct segment *seg = src->segments[n];
+ int src_num = n;
+ for (int i = 0; i < n; i++) {
+ if (seg->d && src->segments[i]->d == seg->d) {
+ src_num = i;
+ break;
+ }
+ }
+ MP_VERBOSE(demuxer, " %2d: %12f - %12f [%12f] (",
+ n, seg->start, seg->end, seg->d_start);
+ for (int i = 0; i < seg->num_stream_map; i++) {
+ struct virtual_stream *vs = seg->stream_map[i];
+ MP_VERBOSE(demuxer, "%s%d", i ? " " : "",
+ vs ? vs->sh->index : -1);
+ }
+ MP_VERBOSE(demuxer, ")\n source %d:'%s'\n", src_num, seg->url);
+ }
+
+ if (src->dash)
+ MP_VERBOSE(demuxer, " (Using pseudo-DASH mode.)\n");
+ }
+ MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
+}
+
+// Copy various (not all) metadata fields from src to dst, but try not to
+// overwrite fields in dst that are unset in src.
+// May keep data from src by reference.
+// Imperfect and arbitrary, only suited for EDL stuff.
+static void apply_meta(struct sh_stream *dst, struct sh_stream *src)
+{
+ if (src->demuxer_id >= 0)
+ dst->demuxer_id = src->demuxer_id;
+ if (src->title)
+ dst->title = src->title;
+ if (src->lang)
+ dst->lang = src->lang;
+ dst->default_track = src->default_track;
+ dst->forced_track = src->forced_track;
+ if (src->hls_bitrate)
+ dst->hls_bitrate = src->hls_bitrate;
+ dst->missing_timestamps = src->missing_timestamps;
+ if (src->attached_picture)
+ dst->attached_picture = src->attached_picture;
+ dst->image = src->image;
+}
+
+// This is mostly for EDL user-defined metadata.
+static struct sh_stream *find_matching_meta(struct timeline_par *tl, int index)
+{
+ for (int n = 0; n < tl->num_sh_meta; n++) {
+ struct sh_stream *sh = tl->sh_meta[n];
+ if (sh->index == index || sh->index < 0)
+ return sh;
+ }
+ return NULL;
+}
+
+static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
+{
+ struct priv *p = demuxer->priv;
+
+ struct virtual_source *src = talloc_ptrtype(p, src);
+ *src = (struct virtual_source){
+ .tl = tl,
+ .dash = tl->dash,
+ .delay_open = tl->delay_open,
+ .no_clip = tl->no_clip || tl->dash,
+ .dts = MP_NOPTS_VALUE,
+ };
+
+ if (!tl->num_parts)
+ return false;
+
+ MP_TARRAY_APPEND(p, p->sources, p->num_sources, src);
+
+ p->duration = MPMAX(p->duration, tl->parts[tl->num_parts - 1].end);
+
+ struct demuxer *meta = tl->track_layout;
+
+ // delay_open streams normally have meta==NULL, and 1 virtual stream
+ int num_streams = 0;
+ if (tl->delay_open) {
+ num_streams = tl->num_sh_meta;
+ } else if (meta) {
+ num_streams = demux_get_num_stream(meta);
+ }
+ for (int n = 0; n < num_streams; n++) {
+ struct sh_stream *new = NULL;
+
+ if (tl->delay_open) {
+ struct sh_stream *tsh = tl->sh_meta[n];
+ new = demux_alloc_sh_stream(tsh->type);
+ new->codec = tsh->codec;
+ apply_meta(new, tsh);
+ demuxer->is_network = true;
+ demuxer->is_streaming = true;
+ } else {
+ struct sh_stream *sh = demux_get_stream(meta, n);
+ new = demux_alloc_sh_stream(sh->type);
+ apply_meta(new, sh);
+ new->codec = sh->codec;
+ struct sh_stream *tsh = find_matching_meta(tl, n);
+ if (tsh)
+ apply_meta(new, tsh);
+ }
+
+ demux_add_sh_stream(demuxer, new);
+ struct virtual_stream *vs = talloc_ptrtype(p, vs);
+ *vs = (struct virtual_stream){
+ .src = src,
+ .sh = new,
+ };
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, vs);
+ assert(demux_get_stream(demuxer, p->num_streams - 1) == new);
+ MP_TARRAY_APPEND(src, src->streams, src->num_streams, vs);
+ }
+
+ for (int n = 0; n < tl->num_parts; n++) {
+ struct timeline_part *part = &tl->parts[n];
+
+ // demux_timeline already does caching, doing it for the sub-demuxers
+ // would be pointless and wasteful.
+ if (part->source) {
+ demuxer->is_network |= part->source->is_network;
+ demuxer->is_streaming |= part->source->is_streaming;
+ }
+
+ if (!part->source)
+ assert(tl->dash || tl->delay_open);
+
+ struct segment *seg = talloc_ptrtype(src, seg);
+ *seg = (struct segment){
+ .d = part->source,
+ .url = part->source ? part->source->filename : part->url,
+ .lazy = !part->source,
+ .d_start = part->source_start,
+ .start = part->start,
+ .end = part->end,
+ };
+
+ associate_streams(demuxer, src, seg);
+
+ seg->index = n;
+ MP_TARRAY_APPEND(src, src->segments, src->num_segments, seg);
+ }
+
+ if (tl->track_layout) {
+ demuxer->is_network |= tl->track_layout->is_network;
+ demuxer->is_streaming |= tl->track_layout->is_streaming;
+ }
+ return true;
+}
+
+static int d_open(struct demuxer *demuxer, enum demux_check check)
+{
+ struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
+ p->tl = demuxer->params ? demuxer->params->timeline : NULL;
+ if (!p->tl || p->tl->num_pars < 1)
+ return -1;
+
+ demuxer->chapters = p->tl->chapters;
+ demuxer->num_chapters = p->tl->num_chapters;
+
+ struct demuxer *meta = p->tl->meta;
+ if (meta) {
+ demuxer->metadata = meta->metadata;
+ demuxer->attachments = meta->attachments;
+ demuxer->num_attachments = meta->num_attachments;
+ demuxer->editions = meta->editions;
+ demuxer->num_editions = meta->num_editions;
+ demuxer->edition = meta->edition;
+ }
+
+ for (int n = 0; n < p->tl->num_pars; n++) {
+ if (!add_tl(demuxer, p->tl->pars[n]))
+ return -1;
+ }
+
+ if (!p->num_sources)
+ return -1;
+
+ demuxer->is_network |= p->tl->is_network;
+ demuxer->is_streaming |= p->tl->is_streaming;
+
+ demuxer->duration = p->duration;
+
+ print_timeline(demuxer);
+
+ demuxer->seekable = true;
+ demuxer->partially_seekable = false;
+
+ const char *format_name = "unknown";
+ if (meta)
+ format_name = meta->filetype ? meta->filetype : meta->desc->name;
+ demuxer->filetype = talloc_asprintf(p, "%s/%s", p->tl->format, format_name);
+
+ reselect_streams(demuxer);
+
+ p->owns_tl = true;
+ return 0;
+}
+
+static void d_close(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int x = 0; x < p->num_sources; x++) {
+ struct virtual_source *src = p->sources[x];
+
+ src->current = NULL;
+ TA_FREEP(&src->next);
+ close_lazy_segments(demuxer, src);
+ }
+
+ if (p->owns_tl) {
+ struct demuxer *master = p->tl->demuxer;
+ timeline_destroy(p->tl);
+ demux_free(master);
+ }
+}
+
+static void d_switched_tracks(struct demuxer *demuxer)
+{
+ reselect_streams(demuxer);
+}
+
+const demuxer_desc_t demuxer_desc_timeline = {
+ .name = "timeline",
+ .desc = "timeline segments",
+ .read_packet = d_read_packet,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .switched_tracks = d_switched_tracks,
+};
diff --git a/demux/ebml.c b/demux/ebml.c
new file mode 100644
index 0000000..7f62f1f
--- /dev/null
+++ b/demux/ebml.c
@@ -0,0 +1,619 @@
+/*
+ * native ebml reader for the Matroska demuxer
+ * new parser copyright (c) 2010 Uoti Urpala
+ * copyright (c) 2004 Aurelien Jacobs <aurel@gnuage.org>
+ * based on the one written by Ronald Bultje for gstreamer
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <assert.h>
+
+#include <libavutil/intfloat.h>
+#include <libavutil/common.h>
+#include "mpv_talloc.h"
+#include "ebml.h"
+#include "stream/stream.h"
+#include "common/msg.h"
+
+// Whether the id is a known Matroska level 1 element (allowed as element on
+// global file level, after the level 0 MATROSKA_ID_SEGMENT).
+// This (intentionally) doesn't include "global" elements.
+bool ebml_is_mkv_level1_id(uint32_t id)
+{
+ switch (id) {
+ case MATROSKA_ID_SEEKHEAD:
+ case MATROSKA_ID_INFO:
+ case MATROSKA_ID_CLUSTER:
+ case MATROSKA_ID_TRACKS:
+ case MATROSKA_ID_CUES:
+ case MATROSKA_ID_ATTACHMENTS:
+ case MATROSKA_ID_CHAPTERS:
+ case MATROSKA_ID_TAGS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * Read: the element content data ID.
+ * Return: the ID.
+ */
+uint32_t ebml_read_id(stream_t *s)
+{
+ int i, len_mask = 0x80;
+ uint32_t id;
+
+ for (i = 0, id = stream_read_char(s); i < 4 && !(id & len_mask); i++)
+ len_mask >>= 1;
+ if (i >= 4)
+ return EBML_ID_INVALID;
+ while (i--)
+ id = (id << 8) | stream_read_char(s);
+ return id;
+}
+
+/*
+ * Read: element content length.
+ */
+uint64_t ebml_read_length(stream_t *s)
+{
+ int i, j, num_ffs = 0, len_mask = 0x80;
+ uint64_t len;
+
+ for (i = 0, len = stream_read_char(s); i < 8 && !(len & len_mask); i++)
+ len_mask >>= 1;
+ if (i >= 8)
+ return EBML_UINT_INVALID;
+ j = i + 1;
+ if ((int) (len &= (len_mask - 1)) == len_mask - 1)
+ num_ffs++;
+ while (i--) {
+ len = (len << 8) | stream_read_char(s);
+ if ((len & 0xFF) == 0xFF)
+ num_ffs++;
+ }
+ if (j == num_ffs)
+ return EBML_UINT_INVALID;
+ if (len >= 1ULL<<63) // Can happen if stream_read_char returns EOF
+ return EBML_UINT_INVALID;
+ return len;
+}
+
+
+/*
+ * Read a variable length signed int.
+ */
+int64_t ebml_read_signed_length(stream_t *s)
+{
+ uint64_t unum;
+ int l;
+
+ /* read as unsigned number first */
+ uint64_t offset = stream_tell(s);
+ unum = ebml_read_length(s);
+ if (unum == EBML_UINT_INVALID)
+ return EBML_INT_INVALID;
+ l = stream_tell(s) - offset;
+
+ return unum - ((1LL << ((7 * l) - 1)) - 1);
+}
+
+/*
+ * Read the next element as an unsigned int.
+ */
+uint64_t ebml_read_uint(stream_t *s)
+{
+ uint64_t len, value = 0;
+
+ len = ebml_read_length(s);
+ if (len == EBML_UINT_INVALID || len > 8)
+ return EBML_UINT_INVALID;
+
+ while (len--)
+ value = (value << 8) | stream_read_char(s);
+
+ return value;
+}
+
+/*
+ * Read the next element as a signed int.
+ */
+int64_t ebml_read_int(stream_t *s)
+{
+ uint64_t value = 0;
+ uint64_t len;
+ int l;
+
+ len = ebml_read_length(s);
+ if (len == EBML_UINT_INVALID || len > 8)
+ return EBML_INT_INVALID;
+ if (!len)
+ return 0;
+
+ len--;
+ l = stream_read_char(s);
+ if (l & 0x80)
+ value = -1;
+ value = (value << 8) | l;
+ while (len--)
+ value = (value << 8) | stream_read_char(s);
+
+ return (int64_t)value; // assume complement of 2
+}
+
+/*
+ * Skip the current element.
+ * end: the end of the parent element or -1 (for robust error handling)
+ */
+int ebml_read_skip(struct mp_log *log, int64_t end, stream_t *s)
+{
+ uint64_t len;
+
+ int64_t pos = stream_tell(s);
+
+ len = ebml_read_length(s);
+ if (len == EBML_UINT_INVALID)
+ goto invalid;
+
+ int64_t pos2 = stream_tell(s);
+ if (len >= INT64_MAX - pos2 || (end > 0 && pos2 + len > end))
+ goto invalid;
+
+ if (!stream_seek_skip(s, pos2 + len))
+ goto invalid;
+
+ return 0;
+
+invalid:
+ mp_err(log, "Invalid EBML length at position %"PRId64"\n", pos);
+ stream_seek_skip(s, pos);
+ return 1;
+}
+
+/*
+ * Skip to (probable) next cluster (MATROSKA_ID_CLUSTER) element start position.
+ */
+int ebml_resync_cluster(struct mp_log *log, stream_t *s)
+{
+ int64_t pos = stream_tell(s);
+ uint32_t last_4_bytes = 0;
+ stream_read_peek(s, &(char){0}, 1);
+ if (!s->eof) {
+ mp_err(log, "Corrupt file detected. "
+ "Trying to resync starting from position %"PRId64"...\n", pos);
+ }
+ while (!s->eof) {
+ // Assumes MATROSKA_ID_CLUSTER is 4 bytes, with no 0 bytes.
+ if (last_4_bytes == MATROSKA_ID_CLUSTER) {
+ mp_err(log, "Cluster found at %"PRId64".\n", pos - 4);
+ stream_seek(s, pos - 4);
+ return 0;
+ }
+ last_4_bytes = (last_4_bytes << 8) | stream_read_char(s);
+ pos++;
+ }
+ return -1;
+}
+
+
+
+#define EVALARGS(F, ...) F(__VA_ARGS__)
+#define E(str, N, type) const struct ebml_elem_desc ebml_ ## N ## _desc = { str, type };
+#define E_SN(str, count, N) const struct ebml_elem_desc ebml_ ## N ## _desc = { str, EBML_TYPE_SUBELEMENTS, sizeof(struct ebml_ ## N), count, (const struct ebml_field_desc[]){
+#define E_S(str, count) EVALARGS(E_SN, str, count, N)
+#define FN(id, name, multiple, N) { id, multiple, offsetof(struct ebml_ ## N, name), offsetof(struct ebml_ ## N, n_ ## name), &ebml_##name##_desc},
+#define F(id, name, multiple) EVALARGS(FN, id, name, multiple, N)
+#include "ebml_defs.inc"
+#undef EVALARGS
+#undef SN
+#undef S
+#undef FN
+#undef F
+
+// Used to read/write pointers to different struct types
+struct generic;
+#define generic_struct struct generic
+
+static uint32_t ebml_parse_id(uint8_t *data, size_t data_len, int *length)
+{
+ *length = -1;
+ uint8_t *end = data + data_len;
+ if (data == end)
+ return EBML_ID_INVALID;
+ int len = 1;
+ uint32_t id = *data++;
+ for (int len_mask = 0x80; !(id & len_mask); len_mask >>= 1) {
+ len++;
+ if (len > 4)
+ return EBML_ID_INVALID;
+ }
+ *length = len;
+ while (--len && data < end)
+ id = (id << 8) | *data++;
+ return id;
+}
+
+static uint64_t ebml_parse_length(uint8_t *data, size_t data_len, int *length)
+{
+ *length = -1;
+ uint8_t *end = data + data_len;
+ if (data == end)
+ return -1;
+ uint64_t r = *data++;
+ int len = 1;
+ int len_mask;
+ for (len_mask = 0x80; !(r & len_mask); len_mask >>= 1) {
+ len++;
+ if (len > 8)
+ return -1;
+ }
+ r &= len_mask - 1;
+
+ int num_allones = 0;
+ if (r == len_mask - 1)
+ num_allones++;
+ for (int i = 1; i < len; i++) {
+ if (data == end)
+ return -1;
+ if (*data == 255)
+ num_allones++;
+ r = (r << 8) | *data++;
+ }
+ // According to Matroska specs this means "unknown length"
+ // Could be supported if there are any actual files using it
+ if (num_allones == len)
+ return -1;
+ *length = len;
+ return r;
+}
+
+static uint64_t ebml_parse_uint(uint8_t *data, int length)
+{
+ assert(length >= 0 && length <= 8);
+ uint64_t r = 0;
+ while (length--)
+ r = (r << 8) + *data++;
+ return r;
+}
+
+static int64_t ebml_parse_sint(uint8_t *data, int length)
+{
+ assert(length >= 0 && length <= 8);
+ if (!length)
+ return 0;
+ uint64_t r = 0;
+ if (*data & 0x80)
+ r = -1;
+ while (length--)
+ r = (r << 8) | *data++;
+ return (int64_t)r; // assume complement of 2
+}
+
+static double ebml_parse_float(uint8_t *data, int length)
+{
+ assert(length == 0 || length == 4 || length == 8);
+ uint64_t i = ebml_parse_uint(data, length);
+ if (length == 4)
+ return av_int2float(i);
+ else
+ return av_int2double(i);
+}
+
+
+// target must be initialized to zero
+static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
+ uint8_t *data, int size,
+ const struct ebml_elem_desc *type, int level)
+{
+ assert(type->type == EBML_TYPE_SUBELEMENTS);
+ assert(level < 8);
+ MP_TRACE(ctx, "%.*sParsing element %s\n", level, " ", type->name);
+
+ char *s = target;
+ uint8_t *end = data + size;
+ uint8_t *p = data;
+ int num_elems[MAX_EBML_SUBELEMENTS] = {0};
+ while (p < end) {
+ uint8_t *startp = p;
+ int len;
+ uint32_t id = ebml_parse_id(p, end - p, &len);
+ if (len > end - p)
+ goto past_end_error;
+ if (len < 0) {
+ MP_ERR(ctx, "Error parsing subelement id\n");
+ goto other_error;
+ }
+ p += len;
+ uint64_t length = ebml_parse_length(p, end - p, &len);
+ if (len > end - p)
+ goto past_end_error;
+ if (len < 0) {
+ MP_ERR(ctx, "Error parsing subelement length\n");
+ goto other_error;
+ }
+ p += len;
+
+ int field_idx = -1;
+ for (int i = 0; i < type->field_count; i++)
+ if (type->fields[i].id == id) {
+ field_idx = i;
+ num_elems[i]++;
+ if (num_elems[i] >= 0x70000000) {
+ MP_ERR(ctx, "Too many EBML subelements.\n");
+ goto other_error;
+ }
+ break;
+ }
+
+ if (length > end - p) {
+ if (field_idx >= 0 && type->fields[field_idx].desc->type
+ != EBML_TYPE_SUBELEMENTS) {
+ MP_ERR(ctx, "Subelement content goes "
+ "past end of containing element\n");
+ goto other_error;
+ }
+ // Try to parse what is possible from inside this partial element
+ ctx->has_errors = true;
+ length = end - p;
+ }
+ p += length;
+
+ continue;
+
+ past_end_error:
+ MP_ERR(ctx, "Subelement headers go past end of containing element\n");
+ other_error:
+ ctx->has_errors = true;
+ end = startp;
+ break;
+ }
+
+ for (int i = 0; i < type->field_count; i++) {
+ if (num_elems[i] && type->fields[i].multiple) {
+ char *ptr = s + type->fields[i].offset;
+ switch (type->fields[i].desc->type) {
+ case EBML_TYPE_SUBELEMENTS: {
+ size_t max = 1000000000 / type->fields[i].desc->size;
+ if (num_elems[i] > max) {
+ MP_ERR(ctx, "Too many subelements.\n");
+ num_elems[i] = max;
+ }
+ int sz = num_elems[i] * type->fields[i].desc->size;
+ *(generic_struct **) ptr = talloc_zero_size(ctx->talloc_ctx, sz);
+ break;
+ }
+ case EBML_TYPE_UINT:
+ *(uint64_t **) ptr = talloc_zero_array(ctx->talloc_ctx,
+ uint64_t, num_elems[i]);
+ break;
+ case EBML_TYPE_SINT:
+ *(int64_t **) ptr = talloc_zero_array(ctx->talloc_ctx,
+ int64_t, num_elems[i]);
+ break;
+ case EBML_TYPE_FLOAT:
+ *(double **) ptr = talloc_zero_array(ctx->talloc_ctx,
+ double, num_elems[i]);
+ break;
+ case EBML_TYPE_STR:
+ *(char ***) ptr = talloc_zero_array(ctx->talloc_ctx,
+ char *, num_elems[i]);
+ break;
+ case EBML_TYPE_BINARY:
+ *(struct bstr **) ptr = talloc_zero_array(ctx->talloc_ctx,
+ struct bstr,
+ num_elems[i]);
+ break;
+ case EBML_TYPE_EBML_ID:
+ *(int32_t **) ptr = talloc_zero_array(ctx->talloc_ctx,
+ uint32_t, num_elems[i]);
+ break;
+ default:
+ MP_ASSERT_UNREACHABLE();
+ }
+ }
+ }
+
+ while (data < end) {
+ int len;
+ uint32_t id = ebml_parse_id(data, end - data, &len);
+ if (len < 0 || len > end - data) {
+ MP_ERR(ctx, "Error parsing subelement\n");
+ break;
+ }
+ data += len;
+ uint64_t length = ebml_parse_length(data, end - data, &len);
+ if (len < 0 || len > end - data) {
+ MP_ERR(ctx, "Error parsing subelement length\n");
+ break;
+ }
+ data += len;
+ if (length > end - data) {
+ // Try to parse what is possible from inside this partial element
+ length = end - data;
+ MP_ERR(ctx, "Next subelement content goes "
+ "past end of containing element, will be truncated\n");
+ }
+ int field_idx = -1;
+ for (int i = 0; i < type->field_count; i++)
+ if (type->fields[i].id == id) {
+ field_idx = i;
+ break;
+ }
+ if (field_idx < 0) {
+ if (id == 0xec) {
+ MP_TRACE(ctx, "%.*sIgnoring Void element "
+ "size: %"PRIu64"\n", level+1, " ", length);
+ } else if (id == 0xbf) {
+ MP_TRACE(ctx, "%.*sIgnoring CRC-32 "
+ "element size: %"PRIu64"\n", level+1, " ",
+ length);
+ } else {
+ MP_DBG(ctx, "Ignoring unrecognized "
+ "subelement. ID: %x size: %"PRIu64"\n", id, length);
+ }
+ data += length;
+ continue;
+ }
+ const struct ebml_field_desc *fd = &type->fields[field_idx];
+ const struct ebml_elem_desc *ed = fd->desc;
+ bool multiple = fd->multiple;
+ int *countptr = (int *) (s + fd->count_offset);
+ if (*countptr >= num_elems[field_idx]) {
+ // Shouldn't happen on any sane file without bugs
+ MP_ERR(ctx, "Too many subelements.\n");
+ ctx->has_errors = true;
+ data += length;
+ continue;
+ }
+ if (*countptr > 0 && !multiple) {
+ MP_WARN(ctx, "Another subelement of type "
+ "%x %s (size: %"PRIu64"). Only one allowed. Ignoring.\n",
+ id, ed->name, length);
+ ctx->has_errors = true;
+ data += length;
+ continue;
+ }
+ MP_TRACE(ctx, "%.*sParsing %x %s size: %"PRIu64
+ " value: ", level+1, " ", id, ed->name, length);
+
+ char *fieldptr = s + fd->offset;
+ switch (ed->type) {
+ case EBML_TYPE_SUBELEMENTS:
+ MP_TRACE(ctx, "subelements\n");
+ char *subelptr;
+ if (multiple) {
+ char *array_start = (char *) *(generic_struct **) fieldptr;
+ subelptr = array_start + *countptr * ed->size;
+ } else
+ subelptr = fieldptr;
+ ebml_parse_element(ctx, subelptr, data, length, ed, level + 1);
+ break;
+
+ case EBML_TYPE_UINT:;
+ uint64_t *uintptr;
+#define GETPTR(subelptr, fieldtype) \
+ if (multiple) \
+ subelptr = *(fieldtype **) fieldptr + *countptr; \
+ else \
+ subelptr = (fieldtype *) fieldptr
+ GETPTR(uintptr, uint64_t);
+ if (length < 1 || length > 8) {
+ MP_ERR(ctx, "uint invalid length %"PRIu64"\n", length);
+ goto error;
+ }
+ *uintptr = ebml_parse_uint(data, length);
+ MP_TRACE(ctx, "uint %"PRIu64"\n", *uintptr);
+ break;
+
+ case EBML_TYPE_SINT:;
+ int64_t *sintptr;
+ GETPTR(sintptr, int64_t);
+ if (length > 8) {
+ MP_ERR(ctx, "sint invalid length %"PRIu64"\n", length);
+ goto error;
+ }
+ *sintptr = ebml_parse_sint(data, length);
+ MP_TRACE(ctx, "sint %"PRId64"\n", *sintptr);
+ break;
+
+ case EBML_TYPE_FLOAT:;
+ double *floatptr;
+ GETPTR(floatptr, double);
+ if (length != 0 && length != 4 && length != 8) {
+ MP_ERR(ctx, "float invalid length %"PRIu64"\n", length);
+ goto error;
+ }
+ *floatptr = ebml_parse_float(data, length);
+ MP_DBG(ctx, "float %f\n", *floatptr);
+ break;
+
+ case EBML_TYPE_STR:
+ if (length > 1024 * 1024) {
+ MP_ERR(ctx, "Not reading overly long string element.\n");
+ break;
+ }
+ char **strptr;
+ GETPTR(strptr, char *);
+ *strptr = talloc_strndup(ctx->talloc_ctx, data, length);
+ MP_TRACE(ctx, "string \"%s\"\n", *strptr);
+ break;
+
+ case EBML_TYPE_BINARY:;
+ if (length > 0x80000000) {
+ MP_ERR(ctx, "Not reading overly long EBML element.\n");
+ break;
+ }
+ struct bstr *binptr;
+ GETPTR(binptr, struct bstr);
+ binptr->start = data;
+ binptr->len = length;
+ MP_TRACE(ctx, "binary %zd bytes\n", binptr->len);
+ break;
+
+ case EBML_TYPE_EBML_ID:;
+ uint32_t *idptr;
+ GETPTR(idptr, uint32_t);
+ *idptr = ebml_parse_id(data, end - data, &len);
+ if (len != length) {
+ MP_ERR(ctx, "ebml_id broken value\n");
+ goto error;
+ }
+ MP_TRACE(ctx, "ebml_id %x\n", (unsigned)*idptr);
+ break;
+ default:
+ MP_ASSERT_UNREACHABLE();
+ }
+ *countptr += 1;
+ error:
+ data += length;
+ }
+}
+
+// target must be initialized to zero
+int ebml_read_element(struct stream *s, struct ebml_parse_ctx *ctx,
+ void *target, const struct ebml_elem_desc *desc)
+{
+ ctx->has_errors = false;
+ int msglevel = ctx->no_error_messages ? MSGL_DEBUG : MSGL_WARN;
+ uint64_t length = ebml_read_length(s);
+ if (s->eof) {
+ MP_MSG(ctx, msglevel, "Unexpected end of file "
+ "- partial or corrupt file?\n");
+ return -1;
+ }
+ if (length == EBML_UINT_INVALID) {
+ MP_MSG(ctx, msglevel, "EBML element with unknown length - unsupported\n");
+ return -1;
+ }
+ if (length > 1000000000) {
+ MP_MSG(ctx, msglevel, "Refusing to read element over 100 MB in size\n");
+ return -1;
+ }
+ ctx->talloc_ctx = talloc_size(NULL, length);
+ int read_len = stream_read(s, ctx->talloc_ctx, length);
+ if (read_len < length)
+ MP_MSG(ctx, msglevel, "Unexpected end of file - partial or corrupt file?\n");
+ ebml_parse_element(ctx, target, ctx->talloc_ctx, read_len, desc, 0);
+ if (ctx->has_errors)
+ MP_MSG(ctx, msglevel, "Error parsing element %s\n", desc->name);
+ return 0;
+}
diff --git a/demux/ebml.h b/demux/ebml.h
new file mode 100644
index 0000000..86a4009
--- /dev/null
+++ b/demux/ebml.h
@@ -0,0 +1,93 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_EBML_H
+#define MPLAYER_EBML_H
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "stream/stream.h"
+#include "misc/bstr.h"
+
+struct mp_log;
+
+/* EBML version supported */
+#define EBML_VERSION 1
+
+enum ebml_elemtype {
+ EBML_TYPE_SUBELEMENTS,
+ EBML_TYPE_UINT,
+ EBML_TYPE_SINT,
+ EBML_TYPE_FLOAT,
+ EBML_TYPE_STR,
+ EBML_TYPE_BINARY,
+ EBML_TYPE_EBML_ID,
+};
+
+struct ebml_field_desc {
+ uint32_t id;
+ bool multiple;
+ int offset;
+ int count_offset;
+ const struct ebml_elem_desc *desc;
+};
+
+struct ebml_elem_desc {
+ char *name;
+ enum ebml_elemtype type;
+ int size;
+ int field_count;
+ const struct ebml_field_desc *fields;
+};
+
+struct ebml_parse_ctx {
+ struct mp_log *log;
+ void *talloc_ctx;
+ bool has_errors;
+ bool no_error_messages;
+};
+
+#include "ebml_types.h"
+
+#define EBML_ID_INVALID 0xffffffff
+
+/* matroska track types */
+#define MATROSKA_TRACK_VIDEO 0x01 /* rectangle-shaped pictures aka video */
+#define MATROSKA_TRACK_AUDIO 0x02 /* anything you can hear */
+#define MATROSKA_TRACK_COMPLEX 0x03 /* audio+video in same track used by DV */
+#define MATROSKA_TRACK_LOGO 0x10 /* overlay-pictures displayed over video*/
+#define MATROSKA_TRACK_SUBTITLE 0x11 /* text-subtitles */
+#define MATROSKA_TRACK_CONTROL 0x20 /* control-codes for menu or other stuff*/
+
+#define EBML_UINT_INVALID UINT64_MAX
+#define EBML_INT_INVALID INT64_MAX
+
+bool ebml_is_mkv_level1_id(uint32_t id);
+uint32_t ebml_read_id (stream_t *s);
+uint64_t ebml_read_length (stream_t *s);
+int64_t ebml_read_signed_length(stream_t *s);
+uint64_t ebml_read_uint (stream_t *s);
+int64_t ebml_read_int (stream_t *s);
+int ebml_read_skip(struct mp_log *log, int64_t end, stream_t *s);
+int ebml_resync_cluster(struct mp_log *log, stream_t *s);
+
+int ebml_read_element(struct stream *s, struct ebml_parse_ctx *ctx,
+ void *target, const struct ebml_elem_desc *desc);
+
+#endif /* MPLAYER_EBML_H */
diff --git a/demux/matroska.h b/demux/matroska.h
new file mode 100644
index 0000000..939792c
--- /dev/null
+++ b/demux/matroska.h
@@ -0,0 +1,24 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_MATROSKA_H
+#define MPLAYER_MATROSKA_H
+
+struct timeline;
+void build_ordered_chapter_timeline(struct timeline *tl);
+
+#endif /* MPLAYER_MATROSKA_H */
diff --git a/demux/packet.c b/demux/packet.c
new file mode 100644
index 0000000..ed43729
--- /dev/null
+++ b/demux/packet.c
@@ -0,0 +1,244 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/intreadwrite.h>
+
+#include "common/av_common.h"
+#include "common/common.h"
+#include "demux.h"
+
+#include "packet.h"
+
+// Free any refcounted data dp holds (but don't free dp itself). This does not
+// care about pointers that are _not_ refcounted (like demux_packet.codec).
+// Normally, a user should use talloc_free(dp). This function is only for
+// annoyingly specific obscure use cases.
+void demux_packet_unref_contents(struct demux_packet *dp)
+{
+ if (dp->avpacket) {
+ assert(!dp->is_cached);
+ av_packet_free(&dp->avpacket);
+ dp->buffer = NULL;
+ dp->len = 0;
+ }
+}
+
+static void packet_destroy(void *ptr)
+{
+ struct demux_packet *dp = ptr;
+ demux_packet_unref_contents(dp);
+}
+
+static struct demux_packet *packet_create(void)
+{
+ struct demux_packet *dp = talloc(NULL, struct demux_packet);
+ talloc_set_destructor(dp, packet_destroy);
+ *dp = (struct demux_packet) {
+ .pts = MP_NOPTS_VALUE,
+ .dts = MP_NOPTS_VALUE,
+ .duration = -1,
+ .pos = -1,
+ .start = MP_NOPTS_VALUE,
+ .end = MP_NOPTS_VALUE,
+ .stream = -1,
+ .avpacket = av_packet_alloc(),
+ };
+ MP_HANDLE_OOM(dp->avpacket);
+ return dp;
+}
+
+// This actually preserves only data and side data, not PTS/DTS/pos/etc.
+// It also allows avpkt->data==NULL with avpkt->size!=0 - the libavcodec API
+// does not allow it, but we do it to simplify new_demux_packet().
+struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
+{
+ if (avpkt->size > 1000000000)
+ return NULL;
+ struct demux_packet *dp = packet_create();
+ int r = -1;
+ if (avpkt->data) {
+ // We hope that this function won't need/access AVPacket input padding,
+ // because otherwise new_demux_packet_from() wouldn't work.
+ r = av_packet_ref(dp->avpacket, avpkt);
+ } else {
+ r = av_new_packet(dp->avpacket, avpkt->size);
+ }
+ if (r < 0) {
+ talloc_free(dp);
+ return NULL;
+ }
+ dp->buffer = dp->avpacket->data;
+ dp->len = dp->avpacket->size;
+ return dp;
+}
+
+// (buf must include proper padding)
+struct demux_packet *new_demux_packet_from_buf(struct AVBufferRef *buf)
+{
+ if (!buf)
+ return NULL;
+ if (buf->size > 1000000000)
+ return NULL;
+
+ struct demux_packet *dp = packet_create();
+ dp->avpacket->buf = av_buffer_ref(buf);
+ if (!dp->avpacket->buf) {
+ talloc_free(dp);
+ return NULL;
+ }
+ dp->avpacket->data = dp->buffer = buf->data;
+ dp->avpacket->size = dp->len = buf->size;
+ return dp;
+}
+
+// Input data doesn't need to be padded.
+struct demux_packet *new_demux_packet_from(void *data, size_t len)
+{
+ struct demux_packet *dp = new_demux_packet(len);
+ if (!dp)
+ return NULL;
+ memcpy(dp->avpacket->data, data, len);
+ return dp;
+}
+
+struct demux_packet *new_demux_packet(size_t len)
+{
+ if (len > INT_MAX)
+ return NULL;
+
+ struct demux_packet *dp = packet_create();
+ int r = av_new_packet(dp->avpacket, len);
+ if (r < 0) {
+ talloc_free(dp);
+ return NULL;
+ }
+ dp->buffer = dp->avpacket->data;
+ dp->len = len;
+ return dp;
+}
+
+void demux_packet_shorten(struct demux_packet *dp, size_t len)
+{
+ assert(len <= dp->len);
+ if (dp->len) {
+ dp->len = len;
+ memset(dp->buffer + dp->len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+ }
+}
+
+void free_demux_packet(struct demux_packet *dp)
+{
+ talloc_free(dp);
+}
+
+void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *src)
+{
+ dst->pts = src->pts;
+ dst->dts = src->dts;
+ dst->duration = src->duration;
+ dst->pos = src->pos;
+ dst->segmented = src->segmented;
+ dst->start = src->start;
+ dst->end = src->end;
+ dst->codec = src->codec;
+ dst->back_restart = src->back_restart;
+ dst->back_preroll = src->back_preroll;
+ dst->keyframe = src->keyframe;
+ dst->stream = src->stream;
+}
+
+struct demux_packet *demux_copy_packet(struct demux_packet *dp)
+{
+ struct demux_packet *new = NULL;
+ if (dp->avpacket) {
+ new = new_demux_packet_from_avpacket(dp->avpacket);
+ } else {
+ // Some packets might be not created by new_demux_packet*().
+ new = new_demux_packet_from(dp->buffer, dp->len);
+ }
+ if (!new)
+ return NULL;
+ demux_packet_copy_attribs(new, dp);
+ return new;
+}
+
+#define ROUND_ALLOC(s) MP_ALIGN_UP((s), 16)
+
+// Attempt to estimate the total memory consumption of the given packet.
+// This is important if we store thousands of packets and not to exceed
+// user-provided limits. Of course we can't know how much memory internal
+// fragmentation of the libc memory allocator will waste.
+// Note that this should return a "stable" value - e.g. if a new packet ref
+// is created, this should return the same value with the new ref. (This
+// implies the value is not exact and does not return the actual size of
+// memory wasted due to internal fragmentation.)
+size_t demux_packet_estimate_total_size(struct demux_packet *dp)
+{
+ size_t size = ROUND_ALLOC(sizeof(struct demux_packet));
+ size += 8 * sizeof(void *); // ta overhead
+ size += 10 * sizeof(void *); // additional estimate for ta_ext_header
+ if (dp->avpacket) {
+ assert(!dp->is_cached);
+ size += ROUND_ALLOC(dp->len);
+ size += ROUND_ALLOC(sizeof(AVPacket));
+ size += 8 * sizeof(void *); // ta overhead
+ size += ROUND_ALLOC(sizeof(AVBufferRef));
+ size += ROUND_ALLOC(64); // upper bound estimate on sizeof(AVBuffer)
+ size += ROUND_ALLOC(dp->avpacket->side_data_elems *
+ sizeof(dp->avpacket->side_data[0]));
+ for (int n = 0; n < dp->avpacket->side_data_elems; n++)
+ size += ROUND_ALLOC(dp->avpacket->side_data[n].size);
+ }
+ return size;
+}
+
+int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
+{
+ if (!start && !end)
+ return 0;
+ if (!dp->avpacket)
+ return -1;
+ uint8_t *p = av_packet_new_side_data(dp->avpacket, AV_PKT_DATA_SKIP_SAMPLES, 10);
+ if (!p)
+ return -1;
+
+ AV_WL32(p + 0, start);
+ AV_WL32(p + 4, end);
+ return 0;
+}
+
+int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
+ void *data, size_t size)
+{
+ if (!dp->avpacket)
+ return -1;
+ uint8_t *sd = av_packet_new_side_data(dp->avpacket,
+ AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+ 8 + size);
+ if (!sd)
+ return -1;
+ AV_WB64(sd, id);
+ if (size > 0)
+ memcpy(sd + 8, data, size);
+ return 0;
+}
diff --git a/demux/packet.h b/demux/packet.h
new file mode 100644
index 0000000..cd1183d
--- /dev/null
+++ b/demux/packet.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_DEMUX_PACKET_H
+#define MPLAYER_DEMUX_PACKET_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <inttypes.h>
+
+// Holds one packet/frame/whatever
+typedef struct demux_packet {
+ double pts;
+ double dts;
+ double duration;
+ int64_t pos; // position in source file byte stream
+
+ union {
+ // Normally valid for packets.
+ struct {
+ unsigned char *buffer;
+ size_t len;
+ };
+
+ // Used if is_cached==true, special uses only.
+ struct {
+ uint64_t pos;
+ } cached_data;
+ };
+
+ int stream; // source stream index (typically sh_stream.index)
+
+ bool keyframe;
+
+ // backward playback
+ bool back_restart : 1; // restart point (reverse and return previous frames)
+ bool back_preroll : 1; // initial discarded frame for smooth decoder reinit
+
+ // If true, cached_data is valid, while buffer/len are not.
+ bool is_cached : 1;
+
+ // segmentation (ordered chapters, EDL)
+ bool segmented;
+ struct mp_codec_params *codec; // set to non-NULL iff segmented is set
+ double start, end; // set to non-NOPTS iff segmented is set
+
+ // private
+ struct demux_packet *next;
+ struct AVPacket *avpacket; // keep the buffer allocation and sidedata
+ uint64_t cum_pos; // demux.c internal: cumulative size until _start_ of pkt
+} demux_packet_t;
+
+struct AVBufferRef;
+
+struct demux_packet *new_demux_packet(size_t len);
+struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt);
+struct demux_packet *new_demux_packet_from(void *data, size_t len);
+struct demux_packet *new_demux_packet_from_buf(struct AVBufferRef *buf);
+void demux_packet_shorten(struct demux_packet *dp, size_t len);
+void free_demux_packet(struct demux_packet *dp);
+struct demux_packet *demux_copy_packet(struct demux_packet *dp);
+size_t demux_packet_estimate_total_size(struct demux_packet *dp);
+
+void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *src);
+
+int demux_packet_set_padding(struct demux_packet *dp, int start, int end);
+int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
+ void *data, size_t size);
+
+void demux_packet_unref_contents(struct demux_packet *dp);
+
+#endif /* MPLAYER_DEMUX_PACKET_H */
diff --git a/demux/stheader.h b/demux/stheader.h
new file mode 100644
index 0000000..1bc036d
--- /dev/null
+++ b/demux/stheader.h
@@ -0,0 +1,119 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_STHEADER_H
+#define MPLAYER_STHEADER_H
+
+#include <stdbool.h>
+
+#include "common/common.h"
+#include "audio/chmap.h"
+#include "video/mp_image.h"
+
+struct MPOpts;
+struct demuxer;
+
+// Stream headers:
+
+struct sh_stream {
+ enum stream_type type;
+ // Index into demuxer->streams.
+ int index;
+ // Demuxer/format specific ID. Corresponds to the stream IDs as encoded in
+ // some file formats (e.g. MPEG), or an index chosen by demux.c.
+ int demuxer_id;
+ // FFmpeg stream index (AVFormatContext.streams[index]), or equivalent.
+ int ff_index;
+
+ struct mp_codec_params *codec;
+
+ char *title;
+ char *lang; // language code
+ bool default_track; // container default track flag
+ bool forced_track; // container forced track flag
+ bool dependent_track; // container dependent track flag
+ bool visual_impaired_track; // container flag
+ bool hearing_impaired_track;// container flag
+ bool image; // video stream is an image
+ bool still_image; // video stream contains still images
+ int hls_bitrate;
+ int program_id;
+
+ struct mp_tags *tags;
+
+ bool missing_timestamps;
+
+ double seek_preroll;
+
+ // stream is a picture (such as album art)
+ struct demux_packet *attached_picture;
+
+ // Internal to demux.c
+ struct demux_stream *ds;
+};
+
+struct mp_codec_params {
+ enum stream_type type;
+
+ // E.g. "h264" (usually corresponds to AVCodecDescriptor.name)
+ const char *codec;
+
+ // Usually a FourCC, exact meaning depends on codec.
+ unsigned int codec_tag;
+
+ unsigned char *extradata; // codec specific per-stream header
+ int extradata_size;
+
+ // Codec specific header data (set by demux_lavf.c only)
+ struct AVCodecParameters *lav_codecpar;
+
+ // Timestamp granularity for converting double<->rational timestamps.
+ int native_tb_num, native_tb_den;
+
+ // Used by an obscure bug workaround mechanism. As an exception to the usual
+ // rules, demuxers are allowed to set this after adding the sh_stream, but
+ // only before the demuxer open call returns.
+ struct demux_packet *first_packet;
+
+ // STREAM_AUDIO
+ int samplerate;
+ struct mp_chmap channels;
+ bool force_channels;
+ int bitrate; // compressed bits/sec
+ int block_align;
+ struct replaygain_data *replaygain_data;
+
+ // STREAM_VIDEO
+ bool avi_dts; // use DTS timing; first frame and DTS is 0
+ double fps; // frames per second (set only if constant fps)
+ bool reliable_fps; // the fps field is definitely not broken
+ int par_w, par_h; // pixel aspect ratio (0 if unknown/square)
+ int disp_w, disp_h; // display size
+ int rotate; // intended display rotation, in degrees, [0, 359]
+ int stereo_mode; // mp_stereo3d_mode (0 if none/unknown)
+ struct mp_colorspace color; // colorspace info where available
+ struct mp_rect crop; // crop to be applied
+
+ // STREAM_VIDEO + STREAM_AUDIO
+ int bits_per_coded_sample;
+
+ // STREAM_SUB
+ double frame_based; // timestamps are frame-based (and this is the
+ // fallback framerate used for timestamps)
+};
+
+#endif /* MPLAYER_STHEADER_H */
diff --git a/demux/timeline.c b/demux/timeline.c
new file mode 100644
index 0000000..0e5ff75
--- /dev/null
+++ b/demux/timeline.c
@@ -0,0 +1,41 @@
+#include "common/common.h"
+#include "stream/stream.h"
+#include "demux.h"
+
+#include "timeline.h"
+
+struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,
+ struct demuxer *demuxer)
+{
+ if (!demuxer->desc->load_timeline)
+ return NULL;
+
+ struct timeline *tl = talloc_ptrtype(NULL, tl);
+ *tl = (struct timeline){
+ .global = global,
+ .log = log,
+ .cancel = demuxer->cancel,
+ .demuxer = demuxer,
+ .format = "unknown",
+ .stream_origin = demuxer->stream_origin,
+ };
+
+ demuxer->desc->load_timeline(tl);
+
+ if (tl->num_pars)
+ return tl;
+ timeline_destroy(tl);
+ return NULL;
+}
+
+void timeline_destroy(struct timeline *tl)
+{
+ if (!tl)
+ return;
+ for (int n = 0; n < tl->num_sources; n++) {
+ struct demuxer *d = tl->sources[n];
+ if (d != tl->demuxer)
+ demux_free(d);
+ }
+ talloc_free(tl);
+}
diff --git a/demux/timeline.h b/demux/timeline.h
new file mode 100644
index 0000000..7bc7e9e
--- /dev/null
+++ b/demux/timeline.h
@@ -0,0 +1,72 @@
+#ifndef MP_TIMELINE_H_
+#define MP_TIMELINE_H_
+
+#include "common/common.h"
+#include "misc/bstr.h"
+
+// Single segment in a timeline.
+struct timeline_part {
+ // (end time must match with start time of the next part)
+ double start, end;
+ double source_start;
+ char *url;
+ struct demuxer *source;
+};
+
+// Timeline formed by a single demuxer. Multiple pars are used to get tracks
+// that require a separate opened demuxer, such as separate audio tracks. (For
+// example, for ordered chapters there is only a single par, because all streams
+// demux from the same file at a given time, while for DASH-style video+audio,
+// each track would have its own timeline.)
+// Note that demuxer instances must not be shared across timeline_pars. This
+// would conflict in demux_timeline.c.
+// "par" is short for parallel stream.
+struct timeline_par {
+ bstr init_fragment;
+ bool dash, no_clip, delay_open;
+
+ // Of any of these, _some_ fields are used. If delay_open==true, this
+ // describes each sub-track, and the codec info is used.
+ // In both cases, the metadata is mapped to actual tracks in specific ways.
+ struct sh_stream **sh_meta;
+ int num_sh_meta;
+
+ // Segments to play, ordered by time.
+ struct timeline_part *parts;
+ int num_parts;
+
+ // Which source defines the overall track list (over the full timeline).
+ struct demuxer *track_layout;
+};
+
+struct timeline {
+ struct mpv_global *global;
+ struct mp_log *log;
+ struct mp_cancel *cancel;
+
+ bool is_network, is_streaming;
+ int stream_origin;
+ const char *format;
+
+ // main source, and all other sources (this usually only has special meaning
+ // for memory management; mostly compensates for the lack of refcounting)
+ struct demuxer *demuxer;
+ struct demuxer **sources;
+ int num_sources;
+
+ // Description of timeline ranges, possibly multiple parallel ones.
+ struct timeline_par **pars;
+ int num_pars;
+
+ struct demux_chapter *chapters;
+ int num_chapters;
+
+ // global tags, attachments, editions
+ struct demuxer *meta;
+};
+
+struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,
+ struct demuxer *demuxer);
+void timeline_destroy(struct timeline *tl);
+
+#endif