diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /demux | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'demux')
-rw-r--r-- | demux/cache.c | 331 | ||||
-rw-r--r-- | demux/cache.h | 16 | ||||
-rw-r--r-- | demux/codec_tags.c | 280 | ||||
-rw-r--r-- | demux/codec_tags.h | 35 | ||||
-rw-r--r-- | demux/cue.c | 270 | ||||
-rw-r--r-- | demux/cue.h | 43 | ||||
-rw-r--r-- | demux/demux.c | 4624 | ||||
-rw-r--r-- | demux/demux.h | 361 | ||||
-rw-r--r-- | demux/demux_cue.c | 304 | ||||
-rw-r--r-- | demux/demux_disc.c | 360 | ||||
-rw-r--r-- | demux/demux_edl.c | 651 | ||||
-rw-r--r-- | demux/demux_lavf.c | 1448 | ||||
-rw-r--r-- | demux/demux_libarchive.c | 120 | ||||
-rw-r--r-- | demux/demux_mf.c | 373 | ||||
-rw-r--r-- | demux/demux_mkv.c | 3392 | ||||
-rw-r--r-- | demux/demux_mkv_timeline.c | 642 | ||||
-rw-r--r-- | demux/demux_null.c | 35 | ||||
-rw-r--r-- | demux/demux_playlist.c | 584 | ||||
-rw-r--r-- | demux/demux_raw.c | 326 | ||||
-rw-r--r-- | demux/demux_timeline.c | 719 | ||||
-rw-r--r-- | demux/ebml.c | 619 | ||||
-rw-r--r-- | demux/ebml.h | 93 | ||||
-rw-r--r-- | demux/matroska.h | 24 | ||||
-rw-r--r-- | demux/packet.c | 244 | ||||
-rw-r--r-- | demux/packet.h | 86 | ||||
-rw-r--r-- | demux/stheader.h | 119 | ||||
-rw-r--r-- | demux/timeline.c | 41 | ||||
-rw-r--r-- | demux/timeline.h | 72 |
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, ¶m); + 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(¶m, true); + double time = read_time(¶m); + 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, ¶m); + 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, ¶ms2, 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("-", ¶ms, 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, ¶ms, 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://", ¶ms, 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, ¶ms, 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, ¶ms, + 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 |