summaryrefslogtreecommitdiffstats
path: root/demux
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2019-10-03 00:22:18 +0200
committerwm4 <wm4@nowhere>2019-10-03 00:22:18 +0200
commit1c63869d0ae01fd8e720353719e4c8b8da12e0b5 (patch)
tree942771789b91389636d2d65baa1238ab9b992a86 /demux
parent8c58375dbd968fff22c9ef5a473c456657c5fc78 (diff)
downloadmpv-1c63869d0ae01fd8e720353719e4c8b8da12e0b5.tar.bz2
mpv-1c63869d0ae01fd8e720353719e4c8b8da12e0b5.tar.xz
demux: restore some of the DVD/BD/CDDA interaction layers
This partially reverts commit a9d83eac40c94f44d19fab7b6955331f10efe301 ("Remove optical disc fancification layers"). Mostly due to the timestamp crap, this was never really going to work. The playback layer is sensitive to timestamps, and derives the playback time directly from the low level packet timestamps. DVD/BD works differently, and libdvdnav/libbluray do not make it easy at all to compensate for this. Which is why it never worked well, but not doing it at all is even more awful. demux_disc.c tried this and rewrote packet timestamps from low level TS to playback time. So restore demux_disc.c, which should bring behavior back to the old often non-working but slightly better state. I did not revert anything that affects components above the demuxer layer. For example, the properties for switching DVD angles or listing disc titles are still gone. (Disc titles could be reimplemented as editions. But not by me.) This commit modifies the reverted code a bit; this can't be avoided, because the internal API changed quite a bit. The old seek resync in demux_lavf.c (which was a hack) is replaced with a hack. SEEK_FORCE and demux_params.external_stream are new additions. Some of this could/should be further cleaned up. If you don't want "proper" DVD/BD support to disappear, you should probably volunteer. Now why am I wasting my time for this? Just because some idiot users are too lazy to rip their ever-wearing out shitty physical discs? Then why should I not be lazy and drop support completely? They won't even be thankful for me maintaining this horrible garbage for no compensation.
Diffstat (limited to 'demux')
-rw-r--r--demux/demux.c31
-rw-r--r--demux/demux.h2
-rw-r--r--demux/demux_disc.c359
-rw-r--r--demux/demux_lavf.c15
4 files changed, 383 insertions, 24 deletions
diff --git a/demux/demux.c b/demux/demux.c
index 3d3c1d0f19..01f15de496 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -58,12 +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;
static const demuxer_desc_t *const demuxer_list[] = {
+ &demuxer_desc_disc,
&demuxer_desc_edl,
&demuxer_desc_cue,
&demuxer_desc_rawaudio,
@@ -263,6 +265,8 @@ struct demux_internal {
struct mp_recorder *dumper;
int dumper_status;
+ bool owns_stream;
+
// -- Access from demuxer thread only
bool enable_recording;
struct mp_recorder *recorder;
@@ -1084,7 +1088,8 @@ static void demux_shutdown(struct demux_internal *in)
talloc_free(in->cache);
in->cache = NULL;
- free_stream(demuxer->stream);
+ if (in->owns_stream)
+ free_stream(demuxer->stream);
demuxer->stream = NULL;
}
@@ -3105,7 +3110,7 @@ void demux_close_stream(struct demuxer *demuxer)
struct demux_internal *in = demuxer->in;
assert(!in->threading && demuxer == in->d_thread);
- if (!demuxer->stream)
+ if (!demuxer->stream || !in->owns_stream)
return;
MP_VERBOSE(demuxer, "demuxer read all data; closing stream\n");
@@ -3197,6 +3202,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.highest_av_pts = MP_NOPTS_VALUE,
.seeking_in_progress = MP_NOPTS_VALUE,
.demux_ts = MP_NOPTS_VALUE,
+ .owns_stream = !params->external_stream,
};
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
@@ -3369,11 +3375,14 @@ struct demuxer *demux_open_url(const char *url,
struct mp_cancel *priv_cancel = mp_cancel_new(NULL);
if (cancel)
mp_cancel_set_parent(priv_cancel, cancel);
- struct stream *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);
+ 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);
@@ -3385,7 +3394,8 @@ struct demuxer *demux_open_url(const char *url,
assert(d->cancel);
} else {
params->demuxer_failed = true;
- free_stream(s);
+ if (!params->external_stream)
+ free_stream(s);
talloc_free(priv_cancel);
}
return d;
@@ -3694,6 +3704,9 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
bool set_backwards = flags & SEEK_SATAN;
flags &= ~(unsigned)SEEK_SATAN;
+ bool force_seek = flags & SEEK_FORCE;
+ flags &= ~(unsigned)SEEK_FORCE;
+
struct demux_cached_range *cache_target =
find_cache_seek_range(in, seek_pts, flags);
@@ -3702,7 +3715,7 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
MP_VERBOSE(in, "Cached seek not possible.\n");
return false;
}
- if (!in->d_thread->seekable) {
+ if (!in->d_thread->seekable && !force_seek) {
MP_WARN(in, "Cannot seek in this file.\n");
return false;
}
diff --git a/demux/demux.h b/demux/demux.h
index 6ffd5587ec..3e3abbc84f 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -61,6 +61,7 @@ struct demux_reader_state {
#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
// Strictness of the demuxer open format check.
// demux.c will try by default: NORMAL, UNSAFE (in this order)
@@ -171,6 +172,7 @@ struct demuxer_params {
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;
};
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
new file mode 100644
index 0000000000..13e73caa39
--- /dev/null
+++ b/demux/demux_disc.c
@@ -0,0 +1,359 @@
+/*
+ * 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,
+ };
+
+ 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_peek(demuxer->stream, 1);
+ reset_pts(demuxer);
+
+ p->slave = demux_open_url("-", &params, demuxer->cancel, demuxer->global);
+ if (!p->slave)
+ return -1;
+
+ // Can be seekable even if the stream isn't.
+ demuxer->seekable = true;
+
+ add_dvd_streams(demuxer);
+ add_streams(demuxer);
+ add_stream_chapters(demuxer);
+
+ double len;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) >= 1)
+ demuxer->duration = len;
+
+ return 0;
+}
+
+static void d_close(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ demux_free(p->slave);
+}
+
+const demuxer_desc_t demuxer_desc_disc = {
+ .name = "disc",
+ .desc = "CD/DVD/BD wrapper",
+ .read_packet = d_read_packet,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .switched_tracks = reselect_streams,
+};
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index 4696927ef9..281259a15e 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -222,7 +222,6 @@ typedef struct lavf_priv {
int num_streams;
char *mime_type;
double seek_delay;
- bool optical_crap_hack;
struct demux_lavf_opts *opts;
double mf_fps;
@@ -1080,13 +1079,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
"broken as well.\n");
}
- double len = -1;
- if (stream_control(priv->stream, STREAM_CTRL_OPTICAL_CRAP_HACK1, &len) > 0) {
- priv->optical_crap_hack = true;
- demuxer->duration = len;
- demuxer->seekable = true;
- }
-
if (demuxer->fully_read) {
demux_close_stream(demuxer);
if (priv->own_stream)
@@ -1197,13 +1189,6 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
int64_t seek_pts_av = 0;
int seek_stream = -1;
- if (priv->optical_crap_hack) {
- if (flags & SEEK_FACTOR)
- seek_pts = seek_pts * demuxer->duration;
- stream_control(priv->stream, STREAM_CTRL_OPTICAL_CRAP_HACK2, &seek_pts);
- return;
- }
-
if (priv->any_ts_fixed) {
// helpful message to piss of users
MP_WARN(demuxer, "Some timestamps returned by the demuxer were linearized. "