diff options
Diffstat (limited to 'demux')
-rw-r--r-- | demux/cache.c | 329 | ||||
-rw-r--r-- | demux/cache.h | 16 | ||||
-rw-r--r-- | demux/codec_tags.c | 99 | ||||
-rw-r--r-- | demux/cue.c | 36 | ||||
-rw-r--r-- | demux/demux.c | 3506 | ||||
-rw-r--r-- | demux/demux.h | 112 | ||||
-rw-r--r-- | demux/demux_cue.c | 60 | ||||
-rw-r--r-- | demux/demux_disc.c | 360 | ||||
-rw-r--r-- | demux/demux_edl.c | 478 | ||||
-rw-r--r-- | demux/demux_lavf.c | 659 | ||||
-rw-r--r-- | demux/demux_libarchive.c | 46 | ||||
-rw-r--r-- | demux/demux_mf.c | 178 | ||||
-rw-r--r-- | demux/demux_mkv.c | 585 | ||||
-rw-r--r-- | demux/demux_mkv_timeline.c | 59 | ||||
-rw-r--r-- | demux/demux_playlist.c | 295 | ||||
-rw-r--r-- | demux/demux_raw.c | 91 | ||||
-rw-r--r-- | demux/demux_timeline.c | 646 | ||||
-rw-r--r-- | demux/ebml.c | 13 | ||||
-rw-r--r-- | demux/packet.c | 103 | ||||
-rw-r--r-- | demux/packet.h | 39 | ||||
-rw-r--r-- | demux/stheader.h | 24 | ||||
-rw-r--r-- | demux/timeline.c | 9 | ||||
-rw-r--r-- | demux/timeline.h | 56 |
23 files changed, 5664 insertions, 2135 deletions
diff --git a/demux/cache.c b/demux/cache.c new file mode 100644 index 0000000000..6398f61823 --- /dev/null +++ b/demux/cache.c @@ -0,0 +1,329 @@ +/* + * 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 "misc/io_utils.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 <= 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 <= 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; + + 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 0000000000..95ea9649c0 --- /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 index ea6c8fe19d..d118fbe560 100644 --- a/demux/codec_tags.c +++ b/demux/codec_tags.c @@ -24,24 +24,18 @@ #include "stheader.h" #include "common/av_common.h" -#define HAVE_QT_TAGS (LIBAVFORMAT_VERSION_MICRO >= 100) - 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(); -#if HAVE_QT_TAGS av_tags[1] = avformat_get_mov_video_tags(); -#endif break; } case STREAM_AUDIO: { av_tags[0] = avformat_get_riff_audio_tags(); -#if HAVE_QT_TAGS av_tags[1] = avformat_get_mov_audio_tags(); -#endif break; } } @@ -50,15 +44,69 @@ static const char *lookup_tag(int type, uint32_t tag) return id == AV_CODEC_ID_NONE ? NULL : mp_codec_from_av_codec_id(id); } -// Corresponds to WMMEDIASUBTYPE_Base. -static const unsigned char guid_ext_base[16] = + +/* + * 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}; + 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; @@ -68,9 +116,23 @@ static void map_audio_pcm_tag(struct mp_codec_params *c) // WAVEFORMATEXTENSIBLE.SubFormat unsigned char *subformat = c->extradata + 6; - if (memcmp(subformat + 4, guid_ext_base + 4, 12) == 0) { - c->codec_tag = AV_RL32(subformat); - c->codec = lookup_tag(c->type, c->codec_tag); + 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. @@ -79,6 +141,9 @@ static void map_audio_pcm_tag(struct mp_codec_params *c) } int bits = c->bits_per_coded_sample; + if (!bits) + return; + int bytes = (bits + 7) / 8; switch (c->codec_tag) { case 0x0: // Microsoft PCM @@ -95,8 +160,7 @@ static void map_audio_pcm_tag(struct mp_codec_params *c) 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 && c->bits_per_coded_sample) + if (c->type == STREAM_AUDIO) map_audio_pcm_tag(c); } @@ -117,8 +181,15 @@ void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float, } 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} }; diff --git a/demux/cue.c b/demux/cue.c index 6e1c91df76..104c598a5c 100644 --- a/demux/cue.c +++ b/demux/cue.c @@ -62,20 +62,46 @@ static const struct { { -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 = bstr_lstrip(line); + 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); - if (rest.len && !strchr(WHITESPACE, rest.start[0])) + struct bstr par = lstrip_whitespace(rest); + if (rest.len && par.len == rest.len) continue; if (out_params) - *out_params = bstr_lstrip(rest); + *out_params = par; return cue_command_strings[n].command; } } @@ -94,7 +120,7 @@ static bool eat_char(struct bstr *data, char ch) static char *read_quoted(void *talloc_ctx, struct bstr *data) { - *data = bstr_lstrip(*data); + *data = lstrip_whitespace(*data); if (!eat_char(data, '"')) return NULL; int end = bstrchr(*data, '"'); @@ -118,7 +144,7 @@ static struct bstr strip_quotes(struct bstr data) // Return -1 on failure. static int read_int(struct bstr *data, bool two_digit) { - *data = bstr_lstrip(*data); + *data = lstrip_whitespace(*data); if (data->len && data->start[0] == '-') return -1; struct bstr s = *data; diff --git a/demux/demux.c b/demux/demux.c index 8a1e9b0b82..5997a96ed6 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -15,29 +15,31 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> #include <assert.h> -#include <unistd.h> +#include <float.h> #include <limits.h> -#include <pthread.h> -#include <stdint.h> - #include <math.h> - -#include <sys/types.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/atomic.h" #include "osdep/timer.h" #include "osdep/threads.h" @@ -56,16 +58,14 @@ 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; -/* Please do not add any new demuxers here. If you want to implement a new - * demuxer, add it to libavformat, except for wrappers around external - * libraries and demuxers requiring binary support. */ - -const demuxer_desc_t *const demuxer_list[] = { +static const demuxer_desc_t *const demuxer_list[] = { + &demuxer_desc_disc, &demuxer_desc_edl, &demuxer_desc_cue, &demuxer_desc_rawaudio, @@ -81,39 +81,43 @@ const demuxer_desc_t *const demuxer_list[] = { NULL }; -struct demux_opts { - int enable_cache; - int64_t max_bytes; - int64_t max_bytes_bw; - double min_secs; - int force_seekable; - double min_secs_cache; - int access_references; - int seekable_cache; - int create_ccs; - char *record_file; -}; - #define OPT_BASE_STRUCT struct demux_opts -#define MAX_BYTES MPMIN(INT64_MAX, SIZE_MAX / 2) +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[]){ - OPT_CHOICE("cache", enable_cache, 0, - ({"no", 0}, {"auto", -1}, {"yes", 1})), - OPT_DOUBLE("demuxer-readahead-secs", min_secs, M_OPT_MIN, .min = 0), - // (The MAX_BYTES sizes may not be accurate because the max field is - // of double type.) - OPT_BYTE_SIZE("demuxer-max-bytes", max_bytes, 0, 0, MAX_BYTES), - OPT_BYTE_SIZE("demuxer-max-back-bytes", max_bytes_bw, 0, 0, MAX_BYTES), - OPT_FLAG("force-seekable", force_seekable, 0), - OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0), - OPT_FLAG("access-references", access_references, 0), - OPT_CHOICE("demuxer-seekable-cache", seekable_cache, 0, - ({"auto", -1}, {"no", 0}, {"yes", 1})), - OPT_FLAG("sub-create-cc-track", create_ccs, 0), - OPT_STRING("stream-record", record_file, 0), + {"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), @@ -121,30 +125,43 @@ const struct m_sub_options demux_conf = { .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 = 10.0 * 60 * 60, + .min_secs_cache = 1000.0 * 60 * 60, .seekable_cache = -1, - .access_references = 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; - struct demux_opts *opts; + 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) - bool owns_stream; - // The lock protects the packet queues (struct demux_stream), // and the fields below. - pthread_mutex_t lock; - pthread_cond_t wakeup; - pthread_t thread; + mp_mutex lock; + mp_cond wakeup; + mp_thread thread; // -- All the following fields are protected by lock. @@ -157,33 +174,51 @@ struct demux_internal { struct sh_stream **streams; int num_streams; - // If non-NULL, a _selected_ stream which is used for global (timed) - // metadata. It will be an arbitrary stream that is hopefully not sparse - // (i.e. not a subtitle stream). This is needed because due to variable - // interleaving multiple streams won't agree whether timed metadata is in - // effect yet at the same time position. - struct demux_stream *master_stream; + 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 last_eof; // last actual global EOF status - bool eof; // whether we're in EOF state (reset for retry) - bool idle; - bool autoselect; + 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; - - // At least one decoder actually requested data since init or the last seek. - // Do this to allow the decoder thread to select streams before starting. + 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 know that we are at the start of the file. This is used to + // 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 initial_state; + 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 @@ -198,18 +233,14 @@ struct demux_internal { double ts_offset; // timestamp offset to apply to everything - void (*run_fn)(void *); // if non-NULL, function queued to be run on - void *run_fn_arg; // the thread as run_fn(run_fn_arg) - // (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 - size_t fw_bytes; // sum of forward packet data in current_range - // Range from which decoder is reading, and to which demuxer is appending. - // This is never NULL. This is always ranges[num_ranges - 1]. + // 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 @@ -221,14 +252,33 @@ struct demux_internal { // Cached state. int64_t stream_size; int64_t last_speed_query; + double speed_query_prev_sample; |