summaryrefslogtreecommitdiffstats
path: root/demux
diff options
context:
space:
mode:
Diffstat (limited to 'demux')
-rw-r--r--demux/cache.c329
-rw-r--r--demux/cache.h16
-rw-r--r--demux/codec_tags.c99
-rw-r--r--demux/cue.c36
-rw-r--r--demux/demux.c3506
-rw-r--r--demux/demux.h112
-rw-r--r--demux/demux_cue.c60
-rw-r--r--demux/demux_disc.c360
-rw-r--r--demux/demux_edl.c478
-rw-r--r--demux/demux_lavf.c659
-rw-r--r--demux/demux_libarchive.c46
-rw-r--r--demux/demux_mf.c178
-rw-r--r--demux/demux_mkv.c585
-rw-r--r--demux/demux_mkv_timeline.c59
-rw-r--r--demux/demux_playlist.c295
-rw-r--r--demux/demux_raw.c91
-rw-r--r--demux/demux_timeline.c646
-rw-r--r--demux/ebml.c13
-rw-r--r--demux/packet.c103
-rw-r--r--demux/packet.h39
-rw-r--r--demux/stheader.h24
-rw-r--r--demux/timeline.c9
-rw-r--r--demux/timeline.h56
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;