aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar wm4 <wm4@nowhere>2014-07-15 01:49:02 +0200
committerGravatar wm4 <wm4@nowhere>2014-07-15 01:49:02 +0200
commit23a7257cca5982fa44300825ea489ba95a7e4c17 (patch)
tree5c03e6b1e0127d2e6b72fa168ea0dce8294ca122
parent4b93210e0c244a65ef10a566abed2ad25ecaf9a1 (diff)
Revert "Remove DVD and Bluray support"
This reverts commit 4b93210e0c244a65ef10a566abed2ad25ecaf9a1. *shrug*
-rw-r--r--demux/demux.c2
-rw-r--r--demux/demux_disc.c347
-rwxr-xr-xold-configure10
-rw-r--r--options/options.c11
-rw-r--r--options/options.h6
-rw-r--r--player/command.c133
-rw-r--r--player/configfiles.c6
-rw-r--r--player/core.h9
-rw-r--r--player/discnav.c334
-rw-r--r--player/loadfile.c7
-rw-r--r--player/misc.c4
-rw-r--r--player/playloop.c2
-rw-r--r--stream/cache.c30
-rw-r--r--stream/stream.c15
-rw-r--r--stream/stream.h9
-rw-r--r--stream/stream_bluray.c831
-rw-r--r--stream/stream_dvd.c968
-rw-r--r--stream/stream_dvdnav.c755
-rw-r--r--sub/osd.c11
-rw-r--r--sub/osd.h8
-rw-r--r--wscript13
-rw-r--r--wscript_build.py6
22 files changed, 3517 insertions, 0 deletions
diff --git a/demux/demux.c b/demux/demux.c
index b7f184c161..e716194614 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -52,12 +52,14 @@ extern const demuxer_desc_t demuxer_desc_lavf;
extern const demuxer_desc_t demuxer_desc_libass;
extern const demuxer_desc_t demuxer_desc_subreader;
extern const demuxer_desc_t demuxer_desc_playlist;
+extern const demuxer_desc_t demuxer_desc_disc;
/* 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[] = {
+ &demuxer_desc_disc,
&demuxer_desc_edl,
&demuxer_desc_cue,
&demuxer_desc_rawaudio,
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
new file mode 100644
index 0000000000..06cea65d1a
--- /dev/null
+++ b/demux/demux_disc.c
@@ -0,0 +1,347 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+
+#include "stream/stream.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.
+ struct {
+ 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
+ } pts[STREAM_TYPE_COUNT];
+ double seek_pts;
+ bool seek_reinit; // needs reinit after seek
+};
+
+static void reselect_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ for (int n = 0; n < MPMIN(p->slave->num_streams, p->num_streams); n++) {
+ if (p->streams[n]) {
+ demuxer_select_track(p->slave, p->slave->streams[n],
+ demux_stream_is_selected(p->streams[n]));
+ }
+ }
+}
+
+static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
+{
+ struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
+ if (stream->uncached_type == STREAMTYPE_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 (stream->uncached_type != STREAMTYPE_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 = new_sh_stream(demuxer, STREAM_SUB);
+ if (!sh)
+ break;
+ sh->demuxer_id = n + 0x20;
+ sh->codec = "dvd_subtitle";
+ get_disc_lang(stream, sh);
+ // 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;
+ csp.int_bits_in = 8;
+ csp.int_bits_out = 8;
+ float cmatrix[3][4];
+ mp_get_yuv2rgb_coeffs(&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 c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
+ mp_map_int_color(cmatrix, 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->sub->extradata = s;
+ sh->sub->extradata_len = strlen(s);
+ }
+ }
+}
+
+static void add_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int n = p->num_streams; n < p->slave->num_streams; n++) {
+ struct sh_stream *src = p->slave->streams[n];
+ if (src->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 = new_sh_stream(demuxer, 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 = talloc_strdup(sh, src->codec);
+ sh->format = src->format;
+ sh->lav_headers = src->lav_headers;
+ if (sh && src->video) {
+ double ar;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
+ == STREAM_OK)
+ sh->video->aspect = ar;
+ }
+ if (sh && src->audio)
+ sh->audio = src->audio;
+ }
+ reselect_streams(demuxer);
+}
+
+static void d_seek(demuxer_t *demuxer, float rel_seek_secs, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
+ demux_seek(p->slave, rel_seek_secs, flags);
+ return;
+ }
+
+ double pts = p->seek_pts;
+ if (flags & SEEK_ABSOLUTE)
+ pts = 0.0f;
+
+ if (flags & SEEK_FACTOR) {
+ double tmp = 0;
+ stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp);
+ pts += tmp * rel_seek_secs;
+ } else {
+ pts += rel_seek_secs;
+ }
+
+ MP_VERBOSE(demuxer, "seek to: %f\n", pts);
+
+ stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, &pts);
+ demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL);
+
+ p->seek_pts = pts;
+ 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);
+
+ for (int n = 0; n < STREAM_TYPE_COUNT; n++) {
+ p->pts[n].base_dts = p->pts[n].last_dts = MP_NOPTS_VALUE;
+ p->pts[n].base_time = base;
+ }
+
+ p->seek_reinit = false;
+}
+
+static int d_fill_buffer(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ struct demux_packet *pkt = demux_read_any_packet(p->slave);
+ if (!pkt)
+ return 0;
+
+ if (p->seek_reinit)
+ reset_pts(demuxer);
+
+ add_streams(demuxer);
+ if (pkt->stream >= p->num_streams) { // out of memory?
+ talloc_free(pkt);
+ return 0;
+ }
+
+ struct sh_stream *sh = p->streams[pkt->stream];
+ if (!demux_stream_is_selected(sh)) {
+ talloc_free(pkt);
+ return 1;
+ }
+
+ int t = sh->type;
+
+ // Subtitle timestamps are not continuous, so the heuristic below can't be
+ // applied. Instead, use the video stream as reference.
+ if (t == STREAM_SUB)
+ t = STREAM_VIDEO;
+
+ MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
+
+ if (sh->type == STREAM_SUB) {
+ if (p->pts[t].base_dts == MP_NOPTS_VALUE)
+ MP_WARN(demuxer, "subtitle packet along PTS reset, report a bug\n");
+ } else if (pkt->dts != MP_NOPTS_VALUE) {
+ if (p->pts[t].base_dts == MP_NOPTS_VALUE)
+ p->pts[t].base_dts = p->pts[t].last_dts = pkt->dts;
+
+ if (pkt->dts < p->pts[t].last_dts || pkt->dts > p->pts[t].last_dts + 0.5)
+ {
+ MP_VERBOSE(demuxer, "PTS discontinuity on stream %d\n", sh->type);
+ p->pts[t].base_time += p->pts[t].last_dts - p->pts[t].base_dts;
+ p->pts[t].base_dts = p->pts[t].last_dts = pkt->dts - pkt->duration;
+ }
+ p->pts[t].last_dts = pkt->dts;
+ }
+ if (p->pts[t].base_dts != MP_NOPTS_VALUE) {
+ double delta = -p->pts[t].base_dts + p->pts[t].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);
+
+ if (pkt->pts != MP_NOPTS_VALUE)
+ p->seek_pts = pkt->pts;
+
+ demux_add_packet(sh, 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, bstr0(""), p * 1e9, 0, 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;
+
+ char *demux = "+lavf";
+ if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
+ demux = "+rawaudio";
+
+ // 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(demuxer->stream, demux, NULL, demuxer->global);
+ if (!p->slave)
+ return -1;
+
+ // So that we don't miss initial packets of delayed subtitle streams.
+ p->slave->stream_select_default = true;
+
+ // Can be seekable even if the stream isn't.
+ demuxer->seekable = true;
+
+ add_dvd_streams(demuxer);
+ add_streams(demuxer);
+ add_stream_chapters(demuxer);
+
+ return 0;
+}
+
+static void d_close(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ free_demuxer(p->slave);
+}
+
+static int d_control(demuxer_t *demuxer, int cmd, void *arg)
+{
+ struct priv *p = demuxer->priv;
+
+ switch (cmd) {
+ case DEMUXER_CTRL_GET_TIME_LENGTH: {
+ double len;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) < 1)
+ break;
+ *(double *)arg = len;
+ return DEMUXER_CTRL_OK;
+ }
+ case DEMUXER_CTRL_RESYNC:
+ demux_flush(p->slave);
+ break; // relay to slave demuxer
+ case DEMUXER_CTRL_SWITCHED_TRACKS:
+ reselect_streams(demuxer);
+ return DEMUXER_CTRL_OK;
+ }
+ return demux_control(p->slave, cmd, arg);
+}
+
+const demuxer_desc_t demuxer_desc_disc = {
+ .name = "disc",
+ .desc = "CD/DVD/BD wrapper",
+ .fill_buffer = d_fill_buffer,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .control = d_control,
+ .type = DEMUXER_TYPE_DISC,
+};
diff --git a/old-configure b/old-configure
index 8de6ac7eda..a57be03a56 100755
--- a/old-configure
+++ b/old-configure
@@ -179,6 +179,9 @@ options_state_machine() {
opt_yes_no _libquvi4 "libquvi 0.4.x"
opt_yes_no _libquvi9 "libquvi 0.9.x"
opt_yes_no _lcms2 "LCMS2 support"
+ opt_yes_no _bluray "Blu-ray support"
+ opt_yes_no _dvdread "libdvdread"
+ opt_yes_no _dvdnav "libdvdnav"
opt_yes_no _enca "ENCA charset oracle library"
opt_yes_no _libass "subtitle rendering with libass"
opt_yes_no _libpostproc "postprocess filter (vf_pp)"
@@ -727,6 +730,12 @@ check_pkg_config "OpenAL" $_openal OPENAL 'openal >= 1.13'
check_pkg_config "ALSA audio" $_alsa ALSA 'alsa >= 1.0.9'
+check_pkg_config "Blu-ray support" $_bluray LIBBLURAY 'libbluray >= 0.2.1'
+
+check_pkg_config "dvdread" $_dvdread DVDREAD 'dvdread >= 4.1.0'
+
+check_pkg_config "dvdnav" $_dvdnav DVDNAV 'dvdnav >= 4.2.0'
+
check_pkg_config "libcdio" $_libcdio CDDA 'libcdio_paranoia'
_oldass=$_libass
@@ -952,6 +961,7 @@ cat > $TMPC << EOF
#define HAVE_SDL1 0
#define DEFAULT_CDROM_DEVICE "/dev/cdrom"
+#define DEFAULT_DVD_DEVICE "/dev/dvd"
#define PATH_DEV_DSP "/dev/dsp"
#define PATH_DEV_MIXER "/dev/mixer"
diff --git a/options/options.c b/options/options.c
index 21adaf76c4..84eda8db64 100644
--- a/options/options.c
+++ b/options/options.c
@@ -147,9 +147,18 @@ const m_option_t mp_opts[] = {
({"no", 0})),
OPT_INTRANGE("cache-pause-restart", stream_cache_unpause, 0, 0, 0x7fffffff),
+#if HAVE_DVDREAD || HAVE_DVDNAV
+ OPT_STRING("dvd-device", dvd_device, 0),
+ OPT_INT("dvd-speed", dvd_speed, 0),
+ OPT_INTRANGE("dvd-angle", dvd_angle, 0, 1, 99),
+#endif /* HAVE_DVDREAD */
OPT_INTPAIR("chapter", chapterrange, 0),
OPT_CHOICE_OR_INT("edition", edition_id, 0, 0, 8190,
({"auto", -1})),
+#if HAVE_LIBBLURAY
+ OPT_STRING("bluray-device", bluray_device, 0),
+ OPT_INTRANGE("bluray-angle", bluray_angle, 0, 0, 999),
+#endif /* HAVE_LIBBLURAY */
OPT_STRINGLIST("http-header-fields", network_http_header_fields, 0),
OPT_STRING("user-agent", network_useragent, 0),
@@ -631,6 +640,8 @@ const struct MPOpts mp_default_opts = {
.index_mode = 1,
+ .dvd_angle = 1,
+
.mf_fps = 1.0,
};
diff --git a/options/options.h b/options/options.h
index 9cd5f800d9..100fded4a3 100644
--- a/options/options.h
+++ b/options/options.h
@@ -251,6 +251,12 @@ typedef struct MPOpts {
struct dvb_params *stream_dvb_opts;
char *cdrom_device;
+ int dvd_title;
+ int dvd_angle;
+ int dvd_speed;
+ char *dvd_device;
+ int bluray_angle;
+ char *bluray_device;
double mf_fps;
char *mf_type;
diff --git a/player/command.c b/player/command.c
index facc890e8a..fffa63bb70 100644
--- a/player/command.c
+++ b/player/command.c
@@ -232,6 +232,13 @@ static int mp_property_media_title(void *ctx, struct m_property *prop,
name = demux_info_get(mpctx->master_demuxer, "title");
if (name && name[0])
return m_property_strdup_ro(action, arg, name);
+ struct stream *stream = mpctx->master_demuxer->stream;
+ if (stream_control(stream, STREAM_CTRL_GET_DISC_NAME, &name) > 0
+ && name) {
+ int r = m_property_strdup_ro(action, arg, name);
+ talloc_free(name);
+ return r;
+ }
}
return mp_property_filename(ctx, prop, action, arg);
}
@@ -478,6 +485,48 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop,
return property_time(action, arg, get_playback_time(mpctx));
}
+/// Current BD/DVD title (RW)
+static int mp_property_disc_title(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer || !demuxer->stream)
+ return M_PROPERTY_UNAVAILABLE;
+ struct stream *stream = demuxer->stream;
+ unsigned int title = -1;
+ switch (action) {
+ case M_PROPERTY_GET:
+ if (stream_control(stream, STREAM_CTRL_GET_CURRENT_TITLE, &title) <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+ *(int*)arg = title;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = (struct m_option){
+ .type = CONF_TYPE_INT,
+ .flags = M_OPT_MIN,
+ .min = -1,
+ };
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ title = *(int*)arg;
+ if (stream_control(stream, STREAM_CTRL_SET_CURRENT_TITLE, &title) <= 0)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_disc_menu(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ int state = mp_nav_in_menu(mpctx);
+ if (state < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_flag_ro(action, arg, !!state);
+}
+
/// Current chapter (RW)
static int mp_property_chapter(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -757,6 +806,19 @@ static int mp_property_quvi_format(void *ctx, struct m_property *prop,
return mp_property_generic_option(mpctx, prop, action, arg);
}
+/// Number of titles in BD/DVD
+static int mp_property_disc_titles(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ unsigned int num_titles;
+ if (!demuxer || stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_TITLES,
+ &num_titles) < 1)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(action, arg, num_titles);
+}
+
/// Number of chapters in file
static int mp_property_chapters(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -780,6 +842,69 @@ static int mp_property_editions(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, demuxer->num_editions);
}
+/// Current dvd angle (RW)
+static int mp_property_angle(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ int ris, angles = -1, angle = 1;
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_ANGLES, &angles);
+ if (ris == STREAM_UNSUPPORTED)
+ return M_PROPERTY_UNAVAILABLE;
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_GET_ANGLE, &angle);
+ if (ris == STREAM_UNSUPPORTED)
+ return -1;
+
+ if (angle < 0 || angles <= 1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = angle;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ *(char **) arg = talloc_asprintf(NULL, "%d/%d", angle, angles);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ angle = *(int *)arg;
+ if (angle < 0 || angle > angles)
+ return M_PROPERTY_ERROR;
+ demux_flush(demuxer);
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_SET_ANGLE, &angle);
+ if (ris != STREAM_OK)
+ return M_PROPERTY_ERROR;
+
+ demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL);
+
+ if (mpctx->d_video)
+ video_reset_decoding(mpctx->d_video);
+
+ if (mpctx->d_audio)
+ audio_reset_decoding(mpctx->d_audio);
+
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 1,
+ .max = angles,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
static int get_tag_entry(int item, int action, void *arg, void *ctx)
{
struct mp_tags *tags = ctx;
@@ -2525,11 +2650,15 @@ static const struct m_property mp_properties[] = {
{"time-remaining", mp_property_remaining},
{"playtime-remaining", mp_property_playtime_remaining},
{"playback-time", mp_property_playback_time},
+ {"disc-title", mp_property_disc_title},
+ {"disc-menu-active", mp_property_disc_menu},
{"chapter", mp_property_chapter},
{"edition", mp_property_edition},
{"quvi-format", mp_property_quvi_format},
+ {"disc-titles", mp_property_disc_titles},
{"chapters", mp_property_chapters},
{"editions", mp_property_editions},
+ {"angle", mp_property_angle},
{"metadata", mp_property_metadata},
{"chapter-metadata", mp_property_chapter_metadata},
{"vf-metadata", mp_property_vf_metadata},
@@ -3651,6 +3780,10 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
mp_input_disable_section(mpctx->input, cmd->args[0].v.s);
break;
+ case MP_CMD_DISCNAV:
+ mp_nav_user_input(mpctx, cmd->args[0].v.s);
+ break;
+
case MP_CMD_VO_CMDLINE:
if (mpctx->video_out) {
char *s = cmd->args[0].v.s;
diff --git a/player/configfiles.c b/player/configfiles.c
index b05184c5fb..d1c79c9c9d 100644
--- a/player/configfiles.c
+++ b/player/configfiles.c
@@ -173,6 +173,7 @@ void mp_load_auto_profiles(struct MPContext *mpctx)
static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
const char *fname)
{
+ struct MPOpts *opts = global->opts;
char *res = NULL;
void *tmp = talloc_new(NULL);
const char *realpath = fname;
@@ -183,6 +184,11 @@ static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
goto exit;
realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
}
+ if (bstr_startswith0(bfname, "dvd://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->dvd_device);
+ if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") ||
+ bstr_startswith0(bfname, "bluray://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->bluray_device);
uint8_t md5[16];
av_md5_sum(md5, realpath, strlen(realpath));
char *conf = talloc_strdup(tmp, "");
diff --git a/player/core.h b/player/core.h
index 856e970945..84b6123c1c 100644
--- a/player/core.h
+++ b/player/core.h
@@ -350,6 +350,7 @@ typedef struct MPContext {
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct encode_lavc_context *encode_lavc_ctx;
+ struct mp_nav_state *nav_state;
} MPContext;
// audio.c
@@ -370,6 +371,14 @@ void mp_write_watch_later_conf(struct MPContext *mpctx);
struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
struct playlist *playlist);
+// discnav.c
+void mp_nav_init(struct MPContext *mpctx);
+void mp_nav_reset(struct MPContext *mpctx);
+void mp_nav_destroy(struct MPContext *mpctx);
+void mp_nav_user_input(struct MPContext *mpctx, char *command);
+void mp_handle_nav(struct MPContext *mpctx);
+int mp_nav_in_menu(struct MPContext *mpctx);
+
// loadfile.c
void uninit_player(struct MPContext *mpctx, unsigned int mask);
struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename);
diff --git a/player/discnav.c b/player/discnav.c
new file mode 100644
index 0000000000..0b52fed479
--- /dev/null
+++ b/player/discnav.c
@@ -0,0 +1,334 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <limits.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "core.h"
+#include "command.h"
+
+#include "common/msg.h"
+#include "common/common.h"
+#include "input/input.h"
+
+#include "stream/discnav.h"
+
+#include "sub/dec_sub.h"
+#include "sub/osd.h"
+
+#include "video/mp_image.h"
+#include "video/decode/dec_video.h"
+
+struct mp_nav_state {
+ struct mp_log *log;
+
+ int nav_still_frame;
+ bool nav_eof;
+ bool nav_menu;
+ bool nav_draining;
+
+ // Accessed by OSD (possibly separate thread)
+ // Protected by the given lock
+ pthread_mutex_t osd_lock;
+ int hi_visible;
+ int highlight[4]; // x0 y0 x1 y1
+ int vidsize[2];
+ int subsize[2];
+ struct sub_bitmap *hi_elem;
+ struct sub_bitmap *overlays[2];
+ struct sub_bitmap outputs[3];
+};
+
+static inline bool is_valid_size(int size[2])
+{
+ return size[0] >= 1 && size[1] >= 1;
+}
+
+static void update_resolution(struct MPContext *mpctx)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ int size[2] = {0};
+ if (mpctx->d_sub[0])
+ sub_control(mpctx->d_sub[0], SD_CTRL_GET_RESOLUTION, size);
+ if (!is_valid_size(size)) {
+ struct mp_image_params vid = {0};
+ if (mpctx->d_video)
+ vid = mpctx->d_video->decoder_output;
+ size[0] = vid.w;
+ size[1] = vid.h;
+ }
+ pthread_mutex_lock(&nav->osd_lock);
+ nav->vidsize[0] = size[0];
+ nav->vidsize[1] = size[1];
+ pthread_mutex_unlock(&nav->osd_lock);
+}
+
+// Send update events and such.
+static void update_state(struct MPContext *mpctx)
+{
+ mp_notify_property(mpctx, "disc-menu-active");
+}
+
+// Return 1 if in menu, 0 if in video, or <0 if no navigation possible.
+int mp_nav_in_menu(struct MPContext *mpctx)
+{
+ return mpctx->nav_state ? mpctx->nav_state->nav_menu : -1;
+}
+
+// Allocate state and enable navigation features. Must happen before
+// initializing cache, because the cache would read data. Since stream_dvdnav is
+// in a mode which skips all transitions on reading data (before enabling
+// navigation), this would skip some menu screens.
+void mp_nav_init(struct MPContext *mpctx)
+{
+ assert(!mpctx->nav_state);
+
+ // dvdnav is interactive
+ if (mpctx->encode_lavc_ctx)
+ return;
+
+ struct mp_nav_cmd inp = {MP_NAV_CMD_ENABLE};
+ if (stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp) < 1)
+ return;
+
+ mpctx->nav_state = talloc_zero(NULL, struct mp_nav_state);
+ mpctx->nav_state->log = mp_log_new(mpctx->nav_state, mpctx->log, "discnav");
+ pthread_mutex_init(&mpctx->nav_state->osd_lock, NULL);
+
+ MP_VERBOSE(mpctx->nav_state, "enabling\n");
+
+ mp_input_enable_section(mpctx->input, "discnav", 0);
+ mp_input_set_section_mouse_area(mpctx->input, "discnav-menu",
+ INT_MIN, INT_MIN, INT_MAX, INT_MAX);
+
+ update_state(mpctx);
+}
+
+void mp_nav_reset(struct MPContext *mpctx)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ if (!nav)
+ return;
+ struct mp_nav_cmd inp = {MP_NAV_CMD_RESUME};
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ osd_set_nav_highlight(mpctx->osd, NULL);
+ nav->hi_visible = 0;
+ nav->nav_menu = false;
+ nav->nav_draining = false;
+ nav->nav_still_frame = 0;
+ mp_input_disable_section(mpctx->input, "discnav-menu");
+ stream_control(mpctx->stream, STREAM_CTRL_RESUME_CACHE, NULL);
+ update_state(mpctx);
+}
+
+void mp_nav_destroy(struct MPContext *mpctx)
+{
+ osd_set_nav_highlight(mpctx->osd, NULL);
+ if (!mpctx->nav_state)
+ return;
+ mp_input_disable_section(mpctx->input, "discnav");
+ mp_input_disable_section(mpctx->input, "discnav-menu");
+ pthread_mutex_destroy(&mpctx->nav_state->osd_lock);
+ talloc_free(mpctx->nav_state);
+ mpctx->nav_state = NULL;
+ update_state(mpctx);
+}
+
+void mp_nav_user_input(struct MPContext *mpctx, char *command)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ if (!nav)
+ return;
+ if (strcmp(command, "mouse_move") == 0) {
+ struct mp_image_params vid = {0};
+ if (mpctx->d_video)
+ vid = mpctx->d_video->decoder_output;
+ struct mp_nav_cmd inp = {MP_NAV_CMD_MOUSE_POS};
+ int x, y;
+ mp_input_get_mouse_pos(mpctx->input, &x, &y);
+ osd_coords_to_video(mpctx->osd, vid.w, vid.h, &x, &y);
+ inp.u.mouse_pos.x = x;
+ inp.u.mouse_pos.y = y;
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ } else {
+ struct mp_nav_cmd inp = {MP_NAV_CMD_MENU};
+ inp.u.menu.action = command;
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ }
+}
+
+void mp_handle_nav(struct MPContext *mpctx)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ if (!nav)
+ return;
+ while (1) {
+ struct mp_nav_event *ev = NULL;
+ stream_control(mpctx->stream, STREAM_CTRL_GET_NAV_EVENT, &ev);
+ if (!ev)
+ break;
+ switch (ev->event) {
+ case MP_NAV_EVENT_DRAIN: {
+ nav->nav_draining = true;
+ MP_VERBOSE(nav, "drain requested\n");
+ break;
+ }
+ case MP_NAV_EVENT_RESET_ALL: {
+ mpctx->stop_play = PT_RELOAD_DEMUXER;
+ MP_VERBOSE(nav, "reload\n");
+ // return immediately.
+ // other events should be handled after reloaded.
+ talloc_free(ev);
+ return;
+ }
+ case MP_NAV_EVENT_RESET: {
+ nav->nav_still_frame = 0;
+ break;
+ }
+ case MP_NAV_EVENT_EOF:
+ nav->nav_eof = true;
+ break;
+ case MP_NAV_EVENT_STILL_FRAME: {
+ int len = ev->u.still_frame.seconds;
+ MP_VERBOSE(nav, "wait for %d seconds\n", len);
+ if (len > 0 && nav->nav_still_frame == 0)
+ nav->nav_still_frame = len;
+ break;
+ }
+ case MP_NAV_EVENT_MENU_MODE:
+ nav->nav_menu = ev->u.menu_mode.enable;
+ if (nav->nav_menu) {
+ mp_input_enable_section(mpctx->input, "discnav-menu",
+ MP_INPUT_ON_TOP);
+ } else {
+ mp_input_disable_section(mpctx->input, "discnav-menu");
+ }
+ update_state(mpctx);
+ break;
+ case MP_NAV_EVENT_HIGHLIGHT: {
+ pthread_mutex_lock(&nav->osd_lock);
+ MP_VERBOSE(nav, "highlight: %d %d %d - %d %d\n",
+ ev->u.highlight.display,
+ ev->u.highlight.sx, ev->u.highlight.sy,
+ ev->u.highlight.ex, ev->u.highlight.ey);
+ nav->highlight[0] = ev->u.highlight.sx;
+ nav->highlight[1] = ev->u.highlight.sy;
+ nav->highlight[2] = ev->u.highlight.ex;
+ nav->highlight[3] = ev->u.highlight.ey;
+ nav->hi_visible = ev->u.highlight.display;
+ pthread_mutex_unlock(&nav->osd_lock);
+ update_resolution(mpctx);
+ osd_set_nav_highlight(mpctx->osd, mpctx);
+ break;
+ }
+ case MP_NAV_EVENT_OVERLAY: {
+ pthread_mutex_lock(&nav->osd_lock);
+ for (int i = 0; i < 2; i++) {
+ if (nav->overlays[i])
+ talloc_free(nav->overlays[i]);
+ nav->overlays[i] = talloc_steal(nav, ev->u.overlay.images[i]);
+ }
+ pthread_mutex_unlock(&nav->osd_lock);
+ update_resolution(mpctx);
+ osd_set_nav_highlight(mpctx->osd, mpctx);
+ break;
+ }
+ default: ; // ignore
+ }
+ talloc_free(ev);
+ }
+ update_resolution(mpctx);
+ if (mpctx->stop_play == AT_END_OF_FILE) {
+ if (nav->nav_still_frame > 0) {
+ // gross hack
+ mpctx->time_frame += nav->nav_still_frame;
+ mpctx->playing_last_frame = true;
+ nav->nav_still_frame = -2;
+ } else if (nav->nav_still_frame == -2) {
+ struct mp_nav_cmd inp = {MP_NAV_CMD_SKIP_STILL};
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ }
+ }
+ if (nav->nav_draining && mpctx->stop_play == AT_END_OF_FILE) {
+ MP_VERBOSE(nav, "execute drain\n");
+ struct mp_nav_cmd inp = {MP_NAV_CMD_DRAIN_OK};
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ nav->nav_draining = false;
+ stream_control(mpctx->stream, STREAM_CTRL_RESUME_CACHE, NULL);
+ }
+ // E.g. keep displaying still frames
+ if (mpctx->stop_play == AT_END_OF_FILE && !nav->nav_eof)
+ mpctx->stop_play = KEEP_PLAYING;
+}
+
+// Render "fake" highlights, because using actual dvd sub highlight elements
+// is too hard, and would require changes to libavcodec's dvdsub decoder.
+// Note: a proper solution would introduce something like
+// SD_CTRL_APPLY_DVDNAV, which would crop the vobsub frame,
+// and apply the current CLUT.
+void mp_nav_get_highlight(void *priv, struct mp_osd_res res,
+ struct sub_bitmaps *out_imgs)
+{
+ struct MPContext *mpctx = priv;
+ struct mp_nav_state *nav = mpctx->nav_state;
+
+ pthread_mutex_lock(&nav->osd_lock);
+
+ struct sub_bitmap *sub = nav->hi_elem;
+ if (!sub)
+ sub = talloc_zero(nav, struct sub_bitmap);
+
+ nav->hi_elem = sub;
+ if (!is_valid_size(nav->vidsize)) {
+ pthread_mutex_unlock(&nav->osd_lock);
+ return;
+ }
+ int sizes[2] = {nav->vidsize[0], nav->vidsize[1]};
+ if (sizes[0] != nav->subsize[0] || sizes[1] != nav->subsize[1]) {
+ talloc_free(sub->bitmap);
+ sub->bitmap = talloc_array(sub, uint32_t, sizes[0] * sizes[1]);
+ memset(sub->bitmap, 0x80, talloc_get_size(sub->bitmap));
+ nav->subsize[0] = sizes[0];
+ nav->subsize[1] = sizes[1];
+ }
+
+ out_imgs->num_parts = 0;
+
+ if (nav->hi_visible) {
+ sub->x = nav->highlight[0];
+ sub->y = nav->highlight[1];
+ sub->w = MPCLAMP(nav->highlight[2] - sub->x, 0, sizes[0]);
+ sub->h = MPCLAMP(nav->highlight[3] - sub->y, 0, sizes[1]);
+ sub->stride = sub->w * 4;
+ if (sub->w > 0 && sub->h > 0)
+ nav->outputs[out_imgs->num_parts++] = *sub;
+ }
+
+ if (nav->overlays[0])
+ nav->outputs[out_imgs->num_parts++] = *nav->overlays[0];
+ if (nav->overlays[1])
+ nav->outputs[out_imgs->num_parts++] = *nav->overlays[1];
+
+ if (out_imgs->num_parts) {
+ out_imgs->parts = nav->outputs;
+ out_imgs->format = SUBBITMAP_RGBA;
+ osd_rescale_bitmaps(out_imgs, sizes[0], sizes[1], res, -1);
+ }
+
+ pthread_mutex_unlock(&nav->osd_lock);
+}
diff --git a/player/loadfile.c b/player/loadfile.c
index 3f131d1df6..e2a1e6e145 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -1050,6 +1050,9 @@ static void play_current_file(struct MPContext *mpctx)
goto terminate_playback;
}
+ // Must be called before enabling cache.
+ mp_nav_init(mpctx);
+
int res = stream_enable_cache(&mpctx->stream, &opts->stream_cache);
if (res == 0)
if (demux_was_interrupted(mpctx))
@@ -1059,6 +1062,8 @@ static void play_current_file(struct MPContext *mpctx)
goto_reopen_demuxer: ;
+ mp_nav_reset(mpctx);
+
//============ Open DEMUXERS --- DETECT file type =======================
mpctx->audio_delay = opts->audio_delay;
@@ -1266,6 +1271,8 @@ goto_reopen_demuxer: ;
terminate_playback:
+ mp_nav_destroy(mpctx);
+
if (mpctx->stop_play == KEEP_PLAYING)
mpctx->stop_play = AT_END_OF_FILE;
diff --git a/player/misc.c b/player/misc.c
index 3da404f7ea..abaf5b208a 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -112,6 +112,10 @@ double get_start_time(struct MPContext *mpctx)
struct demuxer *demuxer = mpctx->demuxer;
if (!demuxer)
return 0;
+ // We reload the demuxer on menu transitions; don't make it use the first
+ // timestamp it finds as start PTS.
+ if (mpctx->nav_state)
+ return 0;
return demuxer->start_time;
}
diff --git a/player/playloop.c b/player/playloop.c
index f992b0b064..7e9b63995b 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -1224,6 +1224,8 @@ void run_playloop(struct MPContext *mpctx)
mpctx->stop_play = AT_END_OF_FILE;
}
+ mp_handle_nav(mpctx);
+
if (!mpctx->stop_play && !mpctx->restart_playback) {
// If no more video is available, one frame means one playloop iteration.
diff --git a/stream/cache.c b/stream/cache.c
index 8c6a6a63dd..197f7d3391 100644
--- a/stream/cache.c
+++ b/stream/cache.c
@@ -114,6 +114,8 @@ struct priv {
int stream_cache_idle;
int stream_cache_fill;
struct mp_tags *stream_metadata;
+ char *disc_name;
+ double start_pts;
};
enum {
@@ -169,6 +171,7 @@ static void cache_drop_contents(struct priv *s)
{
s->offset = s->min_filepos = s->max_filepos = s->read_filepos;
s->eof = false;
+ s->start_pts = MP_NOPTS_VALUE;
}
// Copy at most dst_size from the cache at the given absolute file position pos.
@@ -268,6 +271,14 @@ static bool cache_fill(struct priv *s)
len = stream_read_partial(s->stream, &s->buffer[pos], space);
pthread_mutex_lock(&s->mutex);
+ // Do this after reading a block, because at least libdvdnav updates the
+ // stream position only after actually reading something after a seek.
+ if (s->start_pts == MP_NOPTS_VALUE) {
+ double pts;
+ if (stream_control(s->stream, STREAM_CTRL_GET_CURRENT_TIME, &pts) > 0)
+ s->start_pts = pts;
+ }
+
s->max_filepos += len;
if (pos + len == s->buffer_size)
s->offset += s->buffer_size; // wrap...
@@ -345,6 +356,7 @@ static void update_cached_controls(struct priv *s)
int64_t i64;
double d;
struct mp_tags *tags;
+ char *t;
s->stream_time_length = 0;
if (stream_control(s->stream, STREAM_CTRL_GET_TIME_LENGTH, &d) == STREAM_OK)
s->stream_time_length = d;
@@ -355,6 +367,11 @@ static void update_cached_controls(struct priv *s)
talloc_free(s->stream_metadata);
s->stream_metadata = talloc_steal(s, tags);
}
+ if (stream_control(s->stream, STREAM_CTRL_GET_DISC_NAME, &t) == STREAM_OK)
+ {
+ talloc_free(s->disc_name);
+ s->disc_name = talloc_steal(s, t);
+ }
s->stream_size = -1;
if (stream_control(s->stream, STREAM_CTRL_GET_SIZE, &i64) == STREAM_OK)
s->stream_size = i64;
@@ -385,6 +402,12 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg)
case STREAM_CTRL_GET_NUM_CHAPTERS:
*(unsigned int *)arg = s->stream_num_chapters;
return STREAM_OK;
+ case STREAM_CTRL_GET_CURRENT_TIME: {
+ if (s->start_pts == MP_NOPTS_VALUE)
+ return STREAM_UNSUPPORTED;
+ *(double *)arg = s->start_pts;
+ return STREAM_OK;
+ }
case STREAM_CTRL_GET_METADATA: {
if (s->stream_metadata) {
ta_set_parent(s->stream_metadata, NULL);
@@ -394,6 +417,12 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg)
}
return STREAM_UNSUPPORTED;
}
+ case STREAM_CTRL_GET_DISC_NAME: {
+ if (!s->disc_name)
+ return STREAM_UNSUPPORTED;
+ *(char **)arg = talloc_strdup(NULL, s->disc_name);
+ return STREAM_OK;
+ }
case STREAM_CTRL_RESUME_CACHE:
s->idle = s->eof = false;
pthread_cond_signal(&s->wakeup);
@@ -406,6 +435,7 @@ static bool control_needs_flush(int stream_ctrl)
{
switch (stream_ctrl) {
case STREAM_CTRL_SEEK_TO_TIME:
+ case STREAM_CTRL_SET_ANGLE:
case STREAM_CTRL_SET_CURRENT_TITLE:
return true;
}
diff --git a/stream/stream.c b/stream/stream.c
index ba35e0f9ea..d600088267 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -64,6 +64,10 @@ extern const stream_info_t stream_info_ffmpeg;
extern const stream_info_t stream_info_avdevice;
extern const stream_info_t stream_info_file;
extern const stream_info_t stream_info_ifo;
+extern const stream_info_t stream_info_dvd;
+extern const stream_info_t stream_info_dvdnav;
+extern const stream_info_t stream_info_bluray;
+extern const stream_info_t stream_info_bdnav;
extern const stream_info_t stream_info_rar_filter;
extern const stream_info_t stream_info_rar_entry;
extern const stream_info_t stream_info_edl;
@@ -86,6 +90,17 @@ static const stream_info_t *const stream_list[] = {
#if HAVE_LIBSMBCLIENT
&stream_info_smb,
#endif
+#if HAVE_DVDREAD
+ &stream_info_ifo,
+ &stream_info_dvd,
+#endif
+#if HAVE_DVDNAV
+ &stream_info_dvdnav,
+#endif
+#if HAVE_LIBBLURAY
+ &stream_info_bluray,
+ &stream_info_bdnav,
+#endif
&stream_info_memory,
&stream_info_null,
diff --git a/stream/stream.h b/stream/stream.h
index c49af132d8..344e8b55ef 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -33,6 +33,8 @@ enum streamtype {
STREAMTYPE_GENERIC = 0,
STREAMTYPE_FILE,
STREAMTYPE_DVB,
+ STREAMTYPE_DVD,
+ STREAMTYPE_BLURAY,
STREAMTYPE_TV,
STREAMTYPE_MF,
STREAMTYPE_EDL,
@@ -66,6 +68,9 @@ enum stream_ctrl {
STREAM_CTRL_SEEK_TO_TIME,
STREAM_CTRL_GET_SIZE,
STREAM_CTRL_GET_ASPECT_RATIO,
+ STREAM_CTRL_GET_NUM_ANGLES,
+ STREAM_CTRL_GET_ANGLE,
+ STREAM_CTRL_SET_ANGLE,
STREAM_CTRL_GET_NUM_TITLES,
STREAM_CTRL_GET_LANG,
STREAM_CTRL_GET_CURRENT_TITLE,
@@ -77,9 +82,13 @@ enum stream_ctrl {
STREAM_CTRL_RESUME_CACHE,
STREAM_CTRL_RECONNECT,
STREAM_CTRL_GET_CHAPTER_TIME,
+ STREAM_CTRL_GET_DVD_INFO,
STREAM_CTRL_SET_CONTENTS,
STREAM_CTRL_GET_METADATA,
STREAM_CTRL_GET_BASE_FILENAME,
+ STREAM_CTRL_GET_NAV_EVENT, // struct mp_nav_event**
+ STREAM_CTRL_NAV_CMD, // struct mp_nav_cmd*
+ STREAM_CTRL_GET_DISC_NAME,
STREAM_CTRL_TV_SET_SCAN,
STREAM_CTRL_SET_TV_FREQ,
STREAM_CTRL_GET_TV_FREQ,
diff --git a/stream/stream_bluray.c b/stream/stream_bluray.c
new file mode 100644
index 0000000000..686541b551
--- /dev/null
+++ b/stream/stream_bluray.c
@@ -0,0 +1,831 @@
+/*
+ * Copyright (C) 2010 Benjamin Zores <ben@geexbox.org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Blu-ray parser/reader using libbluray
+ * Use 'git clone git://git.videolan.org/libbluray' to get it.
+ *
+ * TODO:
+ * - Add descrambled keys database support (KEYDB.cfg)
+ *
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include <libbluray/bluray.h>
+#include <libbluray/meta_data.h>
+#include <libbluray/overlay.h>
+#include <libbluray/keys.h>
+#include <libbluray/bluray-version.h>
+#include <libavutil/common.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "options/m_option.h"
+#include "options/options.h"
+#include "stream.h"
+#include "osdep/timer.h"
+#include "discnav.h"
+#include "sub/osd.h"
+#include "sub/img_convert.h"
+#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
+
+#define BLURAY_SECTOR_SIZE 6144
+
+#define BLURAY_DEFAULT_ANGLE 0
+#define BLURAY_DEFAULT_CHAPTER 0
+#define BLURAY_DEFAULT_TITLE -2
+#define BLURAY_MENU_TITLE -1
+
+// 90khz ticks
+#define BD_TIMEBASE (90000)
+#define BD_TIME_TO_MP(x) ((x) / (double)(BD_TIMEBASE))
+#define BD_TIME_FROM_MP(x) ((uint64_t)(x * BD_TIMEBASE))
+
+// copied from aacs.h in libaacs
+#define AACS_ERROR_CORRUPTED_DISC -1 /* opening or reading of AACS files failed */
+#define AACS_ERROR_NO_CONFIG -2 /* missing config file */
+#define AACS_ERROR_NO_PK -3 /* no matching processing key */
+#define AACS_ERROR_NO_CERT -4 /* no valid certificate */
+#define AACS_ERROR_CERT_REVOKED -5 /* certificate has been revoked */
+#define AACS_ERROR_MMC_OPEN -6 /* MMC open failed (no MMC drive ?) */
+#define AACS_ERROR_MMC_FAILURE -7 /* MMC failed */
+#define AACS_ERROR_NO_DK -8 /* no matching device key */
+
+struct bluray_overlay {
+ struct sub_bitmap *image;
+ bool clean, hidden;
+ int x, y, w, h;
+};
+
+struct bluray_priv_s {
+ BLURAY *bd;
+ BLURAY_TITLE_INFO *title_info;
+ int num_titles;
+ int current_angle;
+ int current_title;
+ int current_playlist;
+
+ int cfg_title;
+ char *cfg_device;
+
+ // overlay stuffs
+ struct bluray_overlay overlays[2], ol_flushed[2];
+ struct mp_image_pool *pool;
+
+ // navigation stuffs
+ uint64_t next_event;
+ uint32_t still_length;
+ int mousex, mousey;
+ bool in_menu, use_nav, nav_enabled, popup_enabled;
+};
+
+static const struct bluray_priv_s bluray_stream_priv_dflts = {
+ .cfg_title = BLURAY_DEFAULT_TITLE,
+};
+
+static const struct bluray_priv_s bdnav_stream_priv_dflts = {
+ .cfg_title = BLURAY_DEFAULT_TITLE,
+ .use_nav = true,
+};
+
+#define OPT_BASE_STRUCT struct bluray_priv_s
+static const m_option_t bluray_stream_opts_fields[] = {
+ OPT_CHOICE_OR_INT("title", cfg_title, 0, 0, 99999,
+ ({"longest", BLURAY_DEFAULT_TITLE})),
+ OPT_STRING("device", cfg_device, 0),
+ {0}
+};
+
+static const m_option_t bdnav_stream_opts_fields[] = {
+ OPT_CHOICE_OR_INT("title", cfg_title, 0, 0, 99999,
+ ({"menu", BLURAY_MENU_TITLE},
+ {"first", BLURAY_DEFAULT_TITLE})),
+ OPT_STRING("device", cfg_device, 0),
+ {0}
+};
+
+static void destruct(struct bluray_priv_s *priv)
+{
+ if (priv->title_info)
+ bd_free_title_info(priv->title_info);
+ bd_close(priv->bd);
+ talloc_free(priv->pool);
+}
+
+inline static int play_title(struct bluray_priv_s *priv, int title)
+{
+ if (priv->use_nav) {
+ if (title == priv->num_titles - 1)
+ title = BLURAY_TITLE_FIRST_PLAY;
+ return bd_play_title(priv->bd, title);
+ } else
+ return bd_select_title(priv->bd, title);
+}
+
+static void overlay_release(struct bluray_overlay *overlay)
+{
+ if (overlay->image)
+ talloc_free(overlay->image);
+ *overlay = (struct bluray_overlay) { .clean = true };
+}
+
+static void overlay_alloc(struct bluray_priv_s *priv,
+ struct bluray_overlay *overlay,
+ int x, int y, int w, int h)
+{
+ assert(overlay->image == NULL);
+ struct sub_bitmap *image = talloc_zero(NULL, struct sub_bitmap);
+ overlay->w = image->w = image->dw = w;
+ overlay->h = image->h = image->dh = h;
+ overlay->x = image->x = x;
+ overlay->y = image->y = y;
+ struct mp_image *mpi = mp_image_pool_get(priv->pool, IMGFMT_RGBA, w, h);
+ mpi = talloc_steal(image, mpi);
+ assert(image->w > 0 && image->h > 0 && mpi != NULL);
+ image->stride = mpi->stride[0];
+ image->bitmap = mpi->planes[0];
+ overlay->image = image;
+ overlay->clean = true;
+ overlay->hidden = false;
+}
+
+static void overlay_close_all(struct bluray_priv_s *priv)
+{
+ for (int i = 0; i < 2; i++)
+ overlay_release(&priv->overlays[i]);
+}
+
+static void overlay_close(struct bluray_priv_s *priv,
+ const BD_OVERLAY *const bo)
+{
+ overlay_release(&priv->overlays[bo->plane]);
+}
+
+static inline uint32_t conv_rgba(const BD_PG_PALETTE_ENTRY *p)
+{
+ uint32_t rgba;
+ uint8_t *out = (uint8_t*)&rgba;
+ const int y = p->Y, cb = (int)p->Cb - 128, cr = (int)p->Cr - 128;
+ // CAUTION: inaccurate but fast, broken in big endian
+#define CONV(a) (MPCLAMP((a), 0, 255)*p->T >> 8)
+ out[0] = CONV(y + cb + (cb >> 1) + (cb >> 2) + (cb >> 6));
+ out[1] = CONV(y - ((cb >> 2) + (cb >> 4) + (cb >> 5))
+ - ((cr >> 3) + (cr >> 4) + (cr >> 5)));
+ out[2] = CONV(y + cr + (cr >> 2) + (cr >> 3) + (cr >> 5));
+ out[3] = p->T;
+#undef CONV
+ return rgba;
+}
+
+static void overlay_process(void *data, const BD_OVERLAY *const bo)
+{
+ stream_t *s = data;
+ struct bluray_priv_s *priv = s->priv;
+ if (!bo) {
+ overlay_close_all(priv);
+ return;
+ }
+ struct bluray_overlay *overlay = &priv->overlays[bo->plane];
+ switch (bo->cmd) {
+ case BD_OVERLAY_INIT:
+ overlay_alloc(priv, overlay, bo->x, bo->y, bo->w, bo->h);
+ break;
+ case BD_OVERLAY_CLOSE:
+ overlay_close(priv, bo);
+ break;
+ case BD_OVERLAY_CLEAR:
+ if (!overlay->clean) {
+ memset(overlay->image->bitmap, 0,
+ overlay->image->stride*overlay->h);
+ overlay->clean = true;
+ }
+ break;
+ case BD_OVERLAY_DRAW: {
+ if (!bo->img)
+ break;
+ overlay->hidden = false;
+ overlay->clean = false;
+ struct sub_bitmap *img = overlay->image;
+ uint32_t *const origin = img->bitmap;
+ const BD_PG_RLE_ELEM *in = bo->img;
+ for (int y = 0; y < bo->h; y++) {
+ uint32_t *out = origin + (img->stride/4) * (y + bo->y) + bo->x;
+ for (int x = 0; x < bo->w; ) {
+ uint32_t c = 0;
+ if (bo->palette[in->color].T) {
+ c = conv_rgba(&bo->palette[in->color]);
+ for (int i = 0; i < in->len; i++)
+ *out++ = c;
+ } else {
+ memset(out, 0, in->len*4);
+ out += in->len;
+ }
+ x += in->len;
+ ++in;
+ }
+ }
+ break;
+ }
+ case BD_OVERLAY_WIPE: {
+ uint32_t *const origin = overlay->image->bitmap;
+ for (int y = 0; y < bo->h; y++)
+ memset(origin + overlay->w * (y + bo->y) + bo->x, 0, 4 * bo->w);
+ break;
+ }
+ case BD_OVERLAY_HIDE:
+ priv->overlays[bo->plane].hidden = true;
+ break;
+ case BD_OVERLAY_FLUSH: {
+ struct bluray_overlay *in = overlay;
+ struct bluray_overlay *out = &priv->ol_flushed[bo->plane];
+ if (out->image && (out->image->stride != in->image->stride ||
+ out->image->h != in->image->h))
+ overlay_release(out);
+ if (!out->image)
+ overlay_alloc(priv, out, in->x, in->y, in->w, in->h);
+ const int len = in->image->stride*in->image->h;
+ memcpy(out->image->bitmap, in->image->bitmap, len);
+ out->clean = in->clean;
+ out->hidden = in->hidden;
+ priv->next_event |= 1 << MP_NAV_EVENT_OVERLAY;
+ break;
+ } default:
+ break;
+ }
+}
+
+static inline bool set_event_type(struct bluray_priv_s *priv, int type,
+ struct mp_nav_event *event)
+{
+ if (!(priv->next_event & (1 << type)))
+ return false;
+ priv->next_event &= ~(1 << type);
+ event->event = type;
+ return true;
+}
+
+static void fill_next_event(stream_t *s, struct mp_nav_event **ret)
+{
+ struct bluray_priv_s *priv = s->priv;
+ struct mp_nav_event e = {0};
+ // this should be checked before any other events
+ if (!set_event_type(priv, MP_NAV_EVENT_RESET_ALL, &e))
+ for (int n = 0; n < 30 && !set_event_type(priv, n, &e); n++) ;
+ switch (e.event) {
+ case MP_NAV_EVENT_NONE:
+ return;
+ case MP_NAV_EVENT_OVERLAY: {
+ for (int i = 0; i < 2; i++) {
+ struct bluray_overlay *o = &priv->ol_flushed[i];
+ e.u.overlay.images[i] = NULL;
+ if (!o->clean && !o->hidden) {
+ e.u.overlay.images[i] = o->image;
+ o->image = NULL;
+ }
+ }
+ break;
+ }
+ case MP_NAV_EVENT_MENU_MODE:
+ e.u.menu_mode.enable = priv->in_menu;
+ break;
+ case MP_NAV_EVENT_STILL_FRAME:
+ e.u.still_frame.seconds = priv->still_length;
+ break;
+ }
+ *ret = talloc(NULL, struct mp_nav_event);
+ **ret = e;
+}
+
+static void bluray_stream_close(stream_t *s)
+{
+ destruct(s->priv);
+}
+
+static void handle_event(stream_t *s, const BD_EVENT *ev)
+{
+ static const int reset_flags = (1 << MP_NAV_EVENT_RESET_ALL)
+ | (1 << MP_NAV_EVENT_RESET);
+ struct bluray_priv_s *b = s->priv;
+ switch (ev->event) {
+ case BD_EVENT_MENU:
+ b->in_menu = ev->param;
+ b->next_event |= 1 << MP_NAV_EVENT_MENU_MODE;
+ break;
+ case BD_EVENT_STILL:
+ b->still_length = ev->param ? -1 : 0;
+ if (b->nav_enabled)
+ b->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
+ break;
+ case BD_EVENT_STILL_TIME:
+ b->still_length = ev->param ? -1 : ev->param*1000;
+ if (b->nav_enabled)
+ b->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
+ else
+ bd_read_skip_still(b->bd);
+ break;
+ case BD_EVENT_END_OF_TITLE:
+ overlay_close_all(b);
+ break;
+ case BD_EVENT_PLAYLIST:
+ b->next_event = reset_flags;
+ b->current_playlist = ev->param;
+ if (!b->use_nav)
+ b->current_title = bd_get_current_title(b->bd);
+ if (b->title_info)
+ bd_free_title_info(b->title_info);
+ b->title_info = bd_get_playlist_info(b->bd, b->current_playlist,
+ b->current_angle);
+ break;
+ case BD_EVENT_TITLE:
+ b->next_event = reset_flags;
+ if (ev->param == BLURAY_TITLE_FIRST_PLAY) {
+ if (b->use_nav)
+ b->current_title = b->num_titles - 1;
+ else
+ b->current_title = bd_get_current_title(b->bd);
+ } else
+ b->current_title = ev->param;
+ if (b->title_info) {
+ bd_free_title_info(b->title_info);
+ b->title_info = NULL;
+ }
+ break;
+ case BD_EVENT_ANGLE:
+ b->current_angle = ev->param;
+ if (b->title_info) {
+ bd_free_title_info(b->title_info);
+ b->title_info = bd_get_playlist_info(b->bd, b->current_playlist,
+ b->current_angle);
+ }
+ break;
+ case BD_EVENT_POPUP:
+ b->popup_enabled = ev->param;
+ break;
+#if BLURAY_VERSION >= BLURAY_VERSION_CODE(0, 5, 0)
+ case BD_EVENT_DISCONTINUITY:
+ b->next_event = reset_flags;
+ break;
+#endif
+ default:
+ MP_TRACE(s, "Unhandled event: %d %d\n", ev->event, ev->param);
+ break;
+ }
+}
+
+static int bluray_stream_fill_buffer(stream_t *s, char *buf, int len)
+{
+ struct bluray_priv_s *b = s->priv;
+ assert(!b->use_nav);
+ BD_EVENT event;
+ while (bd_get_event(b->bd, &event))
+ handle_event(s, &event);
+ return bd_read(b->bd, buf, len);
+}
+
+static int bdnav_stream_fill_buffer(stream_t *s, char *buf, int len)
+{
+ struct bluray_priv_s *b = s->priv;
+ assert(b->use_nav);
+ BD_EVENT event;
+ int read = -1;
+ for (;;) {
+ read = bd_read_ext(b->bd, buf, len, &event);
+ if (read < 0)
+ return read;
+ if (read == 0) {
+ if (event.event == BD_EVENT_NONE)
+ return 0; // end of stream
+ handle_event(s, &event);
+ } else
+ break;
+ }
+ return read;
+}
+
+static bd_vk_key_e translate_nav_menu_action(const char *cmd)
+{
+ if (strcmp(cmd, "mouse") == 0)
+ return BD_VK_MOUSE_ACTIVATE;
+ if (strcmp(cmd, "up") == 0)
+ return BD_VK_UP;
+ if (strcmp(cmd, "down") == 0)
+ return BD_VK_DOWN;
+ if (strcmp(cmd, "left") == 0)
+ return BD_VK_LEFT;
+ if (strcmp(cmd, "right") == 0)
+ return BD_VK_RIGHT;
+ if (strcmp(cmd, "select") == 0)
+ return BD_VK_ENTER;
+ return BD_VK_NONE;
+}
+
+static void handle_nav_command(stream_t *s, struct mp_nav_cmd *ev)
+{
+ struct bluray_priv_s *priv = s->priv;
+ switch (ev->event) {
+ case MP_NAV_CMD_ENABLE:
+ priv->nav_enabled = true;
+ break;
+ case MP_NAV_CMD_MENU: {
+ const int64_t pts = mp_time_us();
+ const char *action = ev->u.menu.action;
+ bd_vk_key_e key = translate_nav_menu_action(action);
+ if (key != BD_VK_NONE) {
+ if (key == BD_VK_MOUSE_ACTIVATE)
+ bd_mouse_select(priv->bd, pts, priv->mousex, priv->mousey);
+ bd_user_input(priv->bd, pts, key);
+ } else if (strcmp(action, "menu") == 0) {
+ if (priv->popup_enabled)
+ bd_user_input(priv->bd, pts, BD_VK_POPUP);
+ else
+ bd_menu_call(priv->bd, pts);
+ }
+ break;
+ } case MP_NAV_CMD_MOUSE_POS:
+ priv->mousex = ev->u.mouse_pos.x;
+ priv->mousey = ev->u.mouse_pos.y;
+ bd_mouse_select(priv->bd, mp_time_us(), priv->mousex, priv->mousey);
+ break;
+ case MP_NAV_CMD_SKIP_STILL:
+ bd_read_skip_still(priv->bd);
+ break;
+ }
+}
+
+static int bluray_stream_control(stream_t *s, int cmd, void *arg)
+{
+ struct bluray_priv_s *b = s->priv;
+
+ switch (cmd) {
+ case STREAM_CTRL_GET_NUM_CHAPTERS: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (!ti)
+ return STREAM_UNSUPPORTED;
+ *((unsigned int *) arg) = ti->chapter_count;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_CHAPTER_TIME: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (!ti)
+ return STREAM_UNSUPPORTED;
+ int chapter = *(double *)arg;
+ double time = MP_NOPTS_VALUE;
+ if (chapter >= 0 || chapter < ti->chapter_count)
+ time = BD_TIME_TO_MP(ti->chapters[chapter].start);
+ if (time == MP_NOPTS_VALUE)
+ return STREAM_ERROR;
+ *(double *)arg = time;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_SET_CURRENT_TITLE: {
+ const uint32_t title = *((unsigned int*)arg);
+ if (title >= b->num_titles || !play_title(b, title))
+ return STREAM_UNSUPPORTED;
+ b->current_title = title;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_CURRENT_TITLE: {
+ *((unsigned int *) arg) = b->current_title;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_NUM_TITLES: {
+ *((unsigned int *)arg) = b->num_titles;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_TIME_LENGTH: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (!ti)
+ return STREAM_UNSUPPORTED;
+ *((double *) arg) = BD_TIME_TO_MP(ti->duration);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_CURRENT_TIME: {
+ *((double *) arg) = BD_TIME_TO_MP(bd_tell_time(b->bd));
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_SEEK_TO_TIME: {
+ double pts = *((double *) arg);
+ bd_seek_time(b->bd, BD_TIME_FROM_MP(pts));
+ stream_drop_buffers(s);
+ // API makes it hard to determine seeking success
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_NUM_ANGLES: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (!ti)
+ return STREAM_UNSUPPORTED;
+ *((int *) arg) = ti->angle_count;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_ANGLE: {
+ *((int *) arg) = b->current_angle;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_SET_ANGLE: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (!ti)
+ return STREAM_UNSUPPORTED;
+ int angle = *((int *) arg);
+ if (angle < 0 || angle > ti->angle_count)
+ return STREAM_UNSUPPORTED;
+ b->current_angle = angle;
+ bd_seamless_angle_change(b->bd, angle);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_LANG: {
+ const BLURAY_TITLE_INFO *ti = b->title_info;
+ if (ti && ti->clip_count) {
+ struct stream_lang_req *req = arg;
+ BLURAY_STREAM_INFO *si = NULL;
+ int count = 0;
+ switch (req->type) {
+ case STREAM_AUDIO:
+ count = ti->clips[0].audio_stream_count;
+ si = ti->clips[0].audio_streams;
+ break;
+ case STREAM_SUB:
+ count = ti->clips[0].pg_stream_count;
+ si = ti->clips[0].pg_streams;
+ break;
+ }
+ for (int n = 0; n < count; n++) {
+ BLURAY_STREAM_INFO *i = &si[n];
+ if (i->pid == req->id) {
+ snprintf(req->name, sizeof(req->name), "%.4s", i->lang);
+ return STREAM_OK;
+ }
+ }
+ }
+ return STREAM_ERROR;
+ }
+ case STREAM_CTRL_GET_DISC_NAME: {
+ const struct meta_dl *meta = bd_get_meta(b->bd);
+ if (!meta || !meta->di_name || !meta->di_name[0])
+ break;
+ *(char**)arg = talloc_strdup(NULL, meta->di_name);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_NAV_CMD:
+ if (!b->use_nav)
+ return STREAM_UNSUPPORTED;
+ handle_nav_command(s, arg);
+ return STREAM_OK;
+ case STREAM_CTRL_GET_NAV_EVENT: {
+ struct mp_nav_event **ev = arg;
+ if (ev)
+ fill_next_event(s, ev);
+ return STREAM_OK;
+ }
+ default:
+ break;
+ }
+
+ return STREAM_UNSUPPORTED;
+}
+
+static const char *aacs_strerr(int err)
+{
+ switch (err) {
+ case AACS_ERROR_CORRUPTED_DISC: return "opening or reading of AACS files failed";
+ case AACS_ERROR_NO_CONFIG: return "missing config file";
+ case AACS_ERROR_NO_PK: return "no matching processing key";
+ case AACS_ERROR_NO_CERT: return "no valid certificate";
+ case AACS_ERROR_CERT_REVOKED: return "certificate has been revoked";
+ case AACS_ERROR_MMC_OPEN: return "MMC open failed (maybe no MMC drive?)";
+ case AACS_ERROR_MMC_FAILURE: return "MMC failed";
+ case AACS_ERROR_NO_DK: return "no matching device key";
+ default: return "unknown error";
+ }
+}
+
+static bool check_disc_info(stream_t *s)
+{
+ struct bluray_priv_s *b = s->priv;
+ const BLURAY_DISC_INFO *info = bd_get_disc_info(b->bd);
+
+ // check Blu-ray
+ if (!info->bluray_detected) {
+ MP_ERR(s, "Given stream is not a Blu-ray.\n");
+ return false;
+ }
+
+ // check AACS
+ if (info->aacs_detected) {
+ if (!info->libaacs_detected) {
+ MP_ERR(s, "AACS encryption detected but cannot find libaacs.\n");
+ return false;
+ }
+ if (!info->aacs_handled) {
+ MP_ERR(s, "AACS error: %s\n", aacs_strerr(info->aacs_error_code));
+ return false;
+ }
+ }
+
+ // check BD+
+ if (info->bdplus_detected) {
+ if (!info->libbdplus_detected) {
+ MP_ERR(s, "BD+ encryption detected but cannot find libbdplus.\n");
+ return false;
+ }
+ if (!info->bdplus_handled) {
+ MP_ERR(s, "Cannot decrypt BD+ encryption.\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void select_initial_title(stream_t *s, int title_guess) {
+ struct bluray_priv_s *b = s->priv;
+
+ int title = -1;
+ if (b->use_nav) {
+ if (b->cfg_title == BLURAY_MENU_TITLE)
+ title = 0; // BLURAY_TITLE_TOP_MENU
+ else if (b->cfg_title == BLURAY_DEFAULT_TITLE)
+ title = b->num_titles - 1;
+ else
+ title = b->cfg_title;
+ } else {
+ if (b->cfg_title != BLURAY_DEFAULT_TITLE )
+ title = b->cfg_title;
+ else
+ title = title_guess;
+ }
+ if (title < 0)
+ return;
+
+ if (play_title(b, title))
+ b->current_title = title;
+ else {
+ MP_WARN(s, "Couldn't start title '%d'.\n", title);
+ if (!b->use_nav) // cannot query title info in nav
+ b->current_title = bd_get_current_title(b->bd);
+ }
+}
+
+static void select_initial_angle(stream_t *s) {
+ struct bluray_priv_s *b = s->priv;
+ if (!b->use_nav) // no way to figure out current title info
+ return;
+ BLURAY_TITLE_INFO *info = bd_get_title_info(b->bd, b->current_title, 0);
+ if (!info)
+ return;
+ /* Select angle */
+ unsigned int angle = s->opts->bluray_angle;
+ if (!angle)
+ angle = BLURAY_DEFAULT_ANGLE;
+ angle = FFMIN(angle, info->angle_count);
+ if (angle)
+ bd_select_angle(b->bd, angle);
+ b->current_angle = bd_get_current_angle(b->bd);
+ bd_free_title_info(info);
+}
+
+static int bluray_stream_open(stream_t *s)
+{
+ struct bluray_priv_s *b = s->priv;
+
+ const char *device = NULL;
+ /* find the requested device */
+ if (b->cfg_device && b->cfg_device[0])
+ device = b->cfg_device;
+ else if (s->opts->bluray_device && s->opts->bluray_device[0])
+ device = s->opts->bluray_device;
+
+ if (!device) {
+ MP_ERR(s, "No Blu-ray device/location was specified ...\n");
+ return STREAM_UNSUPPORTED;
+ }
+
+ /* open device */
+ BLURAY *bd = bd_open(device, NULL);
+ if (!bd) {
+ MP_ERR(s, "Couldn't open Blu-ray device: %s\n", device);
+ return STREAM_UNSUPPORTED;
+ }
+ b->bd = bd;
+
+ if (!check_disc_info(s)) {
+ destruct(b);
+ return STREAM_UNSUPPORTED;
+ }
+
+ int title_guess = BLURAY_DEFAULT_TITLE;
+ if (b->use_nav) {
+ const BLURAY_DISC_INFO *disc_info = bd_get_disc_info(b->bd);
+ b->num_titles = disc_info->num_hdmv_titles + disc_info->num_bdj_titles;
+ ++b->num_titles; // for BLURAY_TITLE_TOP_MENU
+ ++b->num_titles; // for BLURAY_TITLE_FIRST_PLAY
+ } else {
+ /* check for available titles on disc */
+ b->num_titles = bd_get_titles(bd, TITLES_RELEVANT, 0);
+ if (!b->num_titles) {
+ MP_ERR(s, "Can't find any Blu-ray-compatible title here.\n");
+ destruct(b);
+ return STREAM_UNSUPPORTED;
+ }
+
+ /* parse titles information */
+ uint64_t max_duration = 0;
+ for (int i = 0; i < b->num_titles; i++) {
+ BLURAY_TITLE_INFO *ti = bd_get_title_info(bd, i, 0);
+ if (!ti)
+ continue;
+
+ /* try to guess which title may contain the main movie */
+ if (ti->duration > max_duration) {
+ max_duration = ti->duration;
+ title_guess = i;
+ }
+
+ bd_free_title_info(ti);
+ }
+ }
+
+ // these should be set before any callback
+ b->pool = mp_image_pool_new(6);
+ b->current_angle = -1;
+ b->current_title = -1;
+
+ // initialize libbluray event queue
+ bd_get_event(bd, NULL);
+
+ if (b->use_nav) {
+ if (!bd_play(bd)) {
+ destruct(b);
+ return STREAM_ERROR;
+ }
+ bd_register_overlay_proc(bd, s, overlay_process);
+ }
+
+ select_initial_title(s, title_guess);
+ select_initial_angle(s);
+
+ if (b->use_nav)
+ s->fill_buffer = bdnav_stream_fill_buffer;
+ else
+ s->fill_buffer = bluray_stream_fill_buffer;
+ s->close = bluray_stream_close;
+ s->control = bluray_stream_control;
+ s->type = STREAMTYPE_BLURAY;
+ s->end_pos = bd_get_title_size(bd);
+ s->sector_size = BLURAY_SECTOR_SIZE;
+ s->priv = b;
+ s->demuxer = "+disc";
+
+ MP_VERBOSE(s, "Blu-ray successfully opened.\n");
+
+ return STREAM_OK;
+}
+
+const stream_info_t stream_info_bluray = {
+ .name = "bd",
+ .open = bluray_stream_open,
+ .protocols = (const char*const[]){ "bd", "br", "bluray", NULL },
+ .priv_defaults = &bluray_stream_priv_dflts,
+ .priv_size = sizeof(struct bluray_priv_s),
+ .options = bluray_stream_opts_fields,
+ .url_options = (const char*const[]){
+ "hostname=title",
+ "filename=device",
+ NULL
+ },
+};
+
+const stream_info_t stream_info_bdnav = {
+ .name = "bdnav",
+ .open = bluray_stream_open,
+ .protocols = (const char*const[]){ "bdnav", "brnav", "bluraynav", NULL },
+ .priv_defaults = &bdnav_stream_priv_dflts,
+ .priv_size = sizeof(struct bluray_priv_s),
+ .options = bdnav_stream_opts_fields,
+ .url_options = (const char*const[]){
+ "hostname=title",
+ "filename=device",
+ NULL
+ },
+};
diff --git a/stream/stream_dvd.c b/stream/stream_dvd.c
new file mode 100644
index 0000000000..857d144a65
--- /dev/null
+++ b/stream/stream_dvd.c
@@ -0,0 +1,968 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * Original author: Benjamin Zores
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <libgen.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/nav_read.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "common/common.h"
+#include "common/msg.h"
+
+#define FIRST_AC3_AID 128
+#define FIRST_DTS_AID 136
+#define FIRST_MPG_AID 0
+#define FIRST_PCM_AID 160
+
+#include "stream.h"
+#include "options/m_option.h"
+#include "options/options.h"
+
+#include "stream_dvd_common.h"
+
+#define LIBDVDREAD_VERSION(maj,min,micro) ((maj)*10000 + (min)*100 + (micro))
+/*
+ * Try to autodetect the libdvd-0.9.0 library
+ * (0.9.0 removed the <dvdread/dvd_udf.h> header, and moved the two defines
+ * DVD_VIDEO_LB_LEN and MAX_UDF_FILE_NAME_LEN from it to
+ * <dvdread/dvd_reader.h>)
+ */
+#ifndef DVDREAD_VERSION
+#if defined(DVD_VIDEO_LB_LEN) && defined(MAX_UDF_FILE_NAME_LEN)
+#define DVDREAD_VERSION LIBDVDREAD_VERSION(0,9,0)
+#else
+#define DVDREAD_VERSION LIBDVDREAD_VERSION(0,8,0)
+#endif
+#endif
+
+typedef struct {
+ int id; // 0 - 31 mpeg; 128 - 159 ac3; 160 - 191 pcm
+ int language;
+ int type;
+ int channels;
+} stream_language_t;
+
+typedef struct {
+ dvd_reader_t *dvd;
+ dvd_file_t *title;
+ ifo_handle_t *vmg_file;
+ tt_srpt_t *tt_srpt;
+ ifo_handle_t *vts_file;
+ vts_ptt_srpt_t *vts_ptt_srpt;
+ pgc_t *cur_pgc;
+//
+ int cur_title;
+ int cur_cell;
+ int last_cell;
+ int cur_pack;
+ int cell_last_pack;
+ int cur_pgc_idx;
+// Navi:
+ int packs_left;
+ dsi_t dsi_pack;
+ int angle_seek;
+ unsigned int *cell_times_table;
+// audio datas
+ int nr_of_channels;
+ stream_language_t audio_streams[32];
+// subtitles
+ int nr_of_subtitles;
+ stream_language_t subtitles[32];
+
+ int dvd_angle;
+ char *dvd_device_current;
+ int dvd_speed;
+ int dvd_title;
+
+ int cfg_title;
+ char *cfg_device;
+} dvd_priv_t;
+
+static const dvd_priv_t stream_priv_dflts = {
+ .cfg_title = 0,
+};
+
+#define OPT_BASE_STRUCT dvd_priv_t
+/// URL definition
+static const m_option_t stream_opts_fields[] = {
+ OPT_INTRANGE("title", cfg_title, 0, 0, 99),
+ OPT_STRING("device", cfg_device, 0),
+ {0}
+};
+
+static int dvd_lang_from_aid(stream_t *stream, int id) {
+ dvd_priv_t *d;
+ int i;
+ if (!stream) return 0;
+ d = stream->priv;
+ if (!d) return 0;
+ for(i=0;i<d->nr_of_channels;i++) {
+ if(d->audio_streams[i].id==id)
+ return d->audio_streams[i].language;
+ }
+ return 0;
+}
+
+static int dvd_number_of_subs(stream_t *stream) {
+ int i;
+ int maxid = -1;
+ dvd_priv_t *d;
+ if (!stream) return -1;
+ d = stream->priv;
+ if (!d) return -1;
+ for (i = 0; i < d->nr_of_subtitles; i++)
+ if (d->subtitles[i].id > maxid) maxid = d->subtitles[i].id;
+ return maxid + 1;
+}
+
+static int dvd_lang_from_sid(stream_t *stream, int id) {
+ int i;
+ dvd_priv_t *d;
+ if (!stream) return 0;
+ d = stream->priv;
+ if (!d) return 0;
+ for (i = 0; i < d->nr_of_subtitles; i++)
+ if (d->subtitles[i].id == id && d->subtitles[i].language) return d->subtitles[i].language;
+ return 0;
+}
+
+static int dvd_next_cell(stream_t *stream, dvd_priv_t *d) {
+ int next_cell=d->cur_cell;
+
+ MP_DBG(stream, "dvd_next_cell: next1=0x%X \n",next_cell);
+ if( d->cur_pgc->cell_playback[ next_cell ].block_type == BLOCK_TYPE_ANGLE_BLOCK ) {
+ while(next_cell<d->last_cell) {
+ if( d->cur_pgc->cell_playback[next_cell].block_mode == BLOCK_MODE_LAST_CELL )
+ break;
+ ++next_cell;
+ }
+ }
+ MP_DBG(stream, "dvd_next_cell: next2=0x%X \n",next_cell);
+
+ ++next_cell;
+ if(next_cell>=d->last_cell)
+ return -1; // EOF
+ if(d->cur_pgc->cell_playback[next_cell].block_type == BLOCK_TYPE_ANGLE_BLOCK ) {
+ next_cell+=d->dvd_angle-1;
+ if(next_cell>=d->last_cell)
+ return -1; // EOF
+ }
+ MP_DBG(stream, "dvd_next_cell: next3=0x%X \n",next_cell);
+ return next_cell;
+}
+
+static int dvd_read_sector(stream_t *stream, dvd_priv_t *d, unsigned char *data)
+{
+ int len;
+
+ if(d->packs_left==0) {
+ /**
+ * If we're not at the end of this cell, we can determine the next
+ * VOBU to display using the VOBU_SRI information section of the
+ * DSI. Using this value correctly follows the current angle,
+ * avoiding the doubled scenes in The Matrix, and makes our life
+ * really happy.
+ *
+ * Otherwise, we set our next address past the end of this cell to
+ * force the code above to go to the next cell in the program.
+ */
+ if(d->dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL) {
+ d->cur_pack= d->dsi_pack.dsi_gi.nv_pck_lbn + ( d->dsi_pack.vobu_sri.next_vobu & 0x7fffffff );
+ MP_DBG(stream, "Navi new pos=0x%X \n",d->cur_pack);
+ } else {
+ // end of cell! find next cell!
+ MP_VERBOSE(stream, "--- END OF CELL !!! ---\n");
+ d->cur_pack=d->cell_last_pack+1;
+ }
+ }
+
+read_next:
+ if(d->cur_pack>d->cell_last_pack) {
+ // end of cell!
+ int next=dvd_next_cell(stream, d);
+ if(next>=0) {
+ d->cur_cell=next;
+ // if( d->cur_pgc->cell_playback[d->cur_cell].block_type
+ // == BLOCK_TYPE_ANGLE_BLOCK ) d->cur_cell+=dvd_angle-1;
+ d->cur_pack = d->cur_pgc->cell_playback[ d->cur_cell ].first_sector;
+ d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector;
+ MP_VERBOSE(stream, "DVD next cell: %d pack: 0x%X-0x%X \n",d->cur_cell,d->cur_pack,d->cell_last_pack);
+ } else
+ return -1; // EOF
+ }
+
+ len = DVDReadBlocks(d->title, d->cur_pack, 1, data);
+ // only == 0 should indicate an error, but some dvdread version are buggy when used with dvdcss
+ if(len <= 0) return -1; //error
+
+ if(data[38]==0 && data[39]==0 && data[40]==1 && data[41]==0xBF &&
+ data[1024]==0 && data[1025]==0 && data[1026]==1 && data[1027]==0xBF) {
+ // found a Navi packet!!!
+#if DVDREAD_VERSION >= LIBDVDREAD_VERSION(0,9,0)
+ navRead_DSI(&d->dsi_pack, &(data[ DSI_START_BYTE ]));
+#else
+ navRead_DSI(&d->dsi_pack, &(data[ DSI_START_BYTE ]), sizeof(dsi_t));
+#endif
+ if(d->cur_pack != d->dsi_pack.dsi_gi.nv_pck_lbn ) {
+ MP_VERBOSE(stream, "Invalid NAVI packet! lba=0x%X navi=0x%X \n",
+ d->cur_pack,d->dsi_pack.dsi_gi.nv_pck_lbn);
+ } else {
+ // process!
+ d->packs_left = d->dsi_pack.dsi_gi.vobu_ea;
+ MP_DBG(stream, "Found NAVI packet! lba=0x%X len=%d \n",d->cur_pack,d->packs_left);
+ //navPrint_DSI(&d->dsi_pack);
+ MP_TRACE(stream, "\r### CELL %d: Navi: %d/%d IFO: %d/%d \n",d->cur_cell,
+ d->dsi_pack.dsi_gi.vobu_c_idn,d->dsi_pack.dsi_gi.vobu_vob_idn,
+ d->cur_pgc->cell_position[d->cur_cell].cell_nr,
+ d->cur_pgc->cell_position[d->cur_cell].vob_id_nr);
+
+ if(d->angle_seek) {
+ int i,skip=0;
+ for(i=0;i<9;i++) // check if all values zero:
+ if((skip=d->dsi_pack.sml_agli.data[i].address)!=0) break;
+ if(skip && skip!=0x7fffffff) {
+ // sml_agli table has valid data (at least one non-zero):
+ d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+
+ d->dsi_pack.sml_agli.data[d->dvd_angle-1].address;
+ d->angle_seek=0;
+ d->cur_pack--;
+ MP_VERBOSE(stream, "Angle-seek synced using sml_agli map! new_lba=0x%X \n",d->cur_pack);
+ } else {
+ // check if we're in the right cell, jump otherwise:
+ if( (d->dsi_pack.dsi_gi.vobu_c_idn==d->cur_pgc->cell_position[d->cur_cell].cell_nr) &&
+ (d->dsi_pack.dsi_gi.vobu_vob_idn==d->cur_pgc->cell_position[d->cur_cell].vob_id_nr) ){
+ d->angle_seek=0;
+ MP_VERBOSE(stream, "Angle-seek synced by cell/vob IDN search! \n");
+ } else {
+ // wrong angle, skip this vobu:
+ d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+
+ d->dsi_pack.dsi_gi.vobu_ea;
+ d->angle_seek=2; // DEBUG
+ }
+ }
+ }
+ }
+ ++d->cur_pack;
+ goto read_next;
+ }
+
+ ++d->cur_pack;
+ if(d->packs_left>=0) --d->packs_left;
+
+ if(d->angle_seek) {
+ if(d->angle_seek==2) MP_VERBOSE(stream, "!!! warning! reading packet while angle_seek !!!\n");
+ goto read_next; // searching for Navi packet
+ }
+
+ return d->cur_pack-1;
+}
+
+static int fill_buffer(stream_t *s, char *buf, int len)
+{
+ int64_t pos;
+ if (len < 2048)
+ return -1;
+ pos = dvd_read_sector(s, s->priv, buf);
+ if (pos < 0)
+ return -1;
+ return 2048; // full sector
+}
+
+static void stream_dvd_close(stream_t *s) {
+ dvd_priv_t *d = s->priv;
+ ifoClose(d->vts_file);
+ ifoClose(d->vmg_file);
+ DVDCloseFile(d->title);
+ DVDClose(d->dvd);
+ if (d->dvd_speed)
+ dvd_set_speed(s,d->dvd_device_current, -1); /* -1 => restore default */
+}
+
+static int mp_get_titleset_length(ifo_handle_t *vts_file, tt_srpt_t *tt_srpt, int title_no)
+{
+ int vts_ttn; ///< title number within video title set
+ int pgc_no; ///< program chain number
+ int msec; ///< time length in milliseconds
+
+ msec=0;
+ if(!vts_file || !tt_srpt)
+ return 0;
+
+ if(vts_file->vtsi_mat && vts_file->vts_pgcit)
+ {
+ vts_ttn = tt_srpt->title[title_no].vts_ttn - 1;
+ pgc_no = vts_file->vts_ptt_srpt->title[vts_ttn].ptt[0].pgcn - 1;
+ msec = mp_dvdtimetomsec(&vts_file->vts_pgcit->pgci_srp[pgc_no].pgc->playback_time);
+ }
+ return msec;
+}
+
+
+static int get_num_chapter(ifo_handle_t *vts_file, tt_srpt_t *tt_srpt, int title_no)
+{
+ if(!vts_file || !tt_srpt)
+ return 0;
+
+ if(title_no < 0 || title_no >= tt_srpt->nr_of_srpts)
+ return 0;
+
+ // map global title to vts title
+ title_no = tt_srpt->title[title_no].vts_ttn - 1;
+
+ if(title_no < 0 || title_no >= vts_file->vts_ptt_srpt->nr_of_srpts)
+ return 0;
+
+ return vts_file->vts_ptt_srpt->title[title_no].nr_of_ptts;
+}
+
+// p: in=chapter number, out=PTS
+static int get_chapter_time(ifo_handle_t *vts_file, tt_srpt_t *tt_srpt, int title_no, double *p)
+{
+ unsigned int i, cell, last_cell;
+ unsigned int t=0;
+ ptt_info_t *ptt;
+ pgc_t *pgc;
+
+ title_no = tt_srpt->title[title_no].vts_ttn - 1;
+ if(vts_file->vts_ptt_srpt->title[title_no].nr_of_ptts < 2)
+ return 0;
+ ptt = vts_file->vts_ptt_srpt->title[title_no].ptt;
+
+ int cur = 0;
+ for(i=0; i<vts_file->vts_ptt_srpt->title[title_no].nr_of_ptts; i++)
+ {
+ pgc = vts_file->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc;
+ cell = pgc->program_map[ptt[i].pgn-1]; //here the cell is 1-based
+ if(ptt[i].pgn<pgc->nr_of_programs)
+ last_cell = pgc->program_map[ptt[i].pgn];
+ else
+ last_cell = 0;
+ while (cell < last_cell) {
+ if(!(pgc->cell_playback[cell-1].block_type == BLOCK_TYPE_ANGLE_BLOCK &&
+ pgc->cell_playback[cell-1].block_mode != BLOCK_MODE_FIRST_CELL)
+ ) {
+ if (cur == *p) {
+ *p = t / 1000.0;
+ return 1;
+ }
+ t += mp_dvdtimetomsec(&pgc->cell_playback[cell-1].playback_time);
+ cur++;
+ }
+ cell++;
+ }
+ }
+ return 0;
+}
+
+static void list_chapters(stream_t *stream, ifo_handle_t *vts_file, tt_srpt_t *tt_srpt, int title_no)
+{
+ MP_INFO(stream, "CHAPTERS: ");
+ for (int n = 0; ; n++) {
+ double p = n;
+ int r;
+ r = get_chapter_time(vts_file, tt_srpt, title_no, &p);
+ if (!r)
+ break;
+ int t = p * 1000;
+ MP_INFO(stream, "%02d:%02d:%02d.%03d,", t/3600000, (t/60000)%60, (t/1000)%60, t%1000);
+ }
+ MP_INFO(stream, "\n");
+}
+
+static double dvd_get_current_time(stream_t *stream, int cell)
+{
+ int i, tm;
+ dvd_priv_t *d = stream->priv;
+
+ tm=0;
+ if(cell < 0) cell=d->cur_cell;
+ for(i=0; i<cell; i++) {
+ if(d->cur_pgc->cell_playback[i].block_type == BLOCK_TYPE_ANGLE_BLOCK &&
+ d->cur_pgc->cell_playback[i].block_mode != BLOCK_MODE_FIRST_CELL
+ )
+ continue;
+ tm += d->cell_times_table[i];
+ }
+ tm += mp_dvdtimetomsec(&d->dsi_pack.dsi_gi.c_eltm);
+
+ return (double)tm/1000.0;
+}
+
+static void dvd_seek(stream_t *stream, dvd_priv_t *d, int pos)
+{
+ d->packs_left=-1;
+ d->cur_pack=pos;
+
+ // check if we stay in current cell (speedup things, and avoid angle skip)
+ if(d->cur_pack>d->cell_last_pack ||
+ d->cur_pack<d->cur_pgc->cell_playback[ d->cur_cell ].first_sector) {
+
+ // ok, cell change, find the right cell!
+ cell_playback_t *cell;
+ for(d->cur_cell=0; d->cur_cell < d->cur_pgc->nr_of_cells; d->cur_cell++) {
+ cell = &(d->cur_pgc->cell_playback[d->cur_cell]);
+ if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL)
+ continue;
+ d->cell_last_pack=cell->last_sector;
+ if(d->cur_pack<cell->first_sector) {
+ d->cur_pack=cell->first_sector;
+ break;
+ }
+ if(d->cur_pack<=d->cell_last_pack) break; // ok, we find it! :)
+ }
+ }
+
+ MP_VERBOSE(stream, "DVD Seek! lba=0x%X cell=%d packs: 0x%X-0x%X \n",
+ d->cur_pack,d->cur_cell,d->cur_pgc->cell_playback[ d->cur_cell ].first_sector,d->cell_last_pack);
+
+ // if we're in interleaved multi-angle cell, find the right angle chain!
+ // (read Navi block, and use the seamless angle jump table)
+ d->angle_seek=1;
+}
+
+static int do_seek(stream_t *s, int64_t newpos) {
+ stream_drop_buffers(s);
+ dvd_seek(s, s->priv,newpos/2048);
+ return 1;
+}
+
+static int dvd_seek_to_time(stream_t *stream, ifo_handle_t *vts_file, double sec)
+{
+ unsigned int i, j, k, timeunit, ac_time, tmap_sector=0, cell_sector=0, vobu_sector=0;
+ int t=0;
+ double tm, duration;
+ int64_t pos = -1;
+ dvd_priv_t *d = stream->priv;
+ vts_tmapt_t *vts_tmapt = vts_file->vts_tmapt;
+
+ if(!vts_file->vts_tmapt || sec < 0)
+ return 0;
+
+ duration = (double) mp_get_titleset_length(d->vts_file, d->tt_srpt, d->cur_title) / 1000.0f;
+ if(sec > duration)
+ return 0;
+
+ i=d->cur_pgc_idx;
+ timeunit = vts_tmapt->tmap[i].tmu;
+ for(j = 0; j < vts_tmapt->tmap[i].nr_of_entries; j++) {
+ ac_time = timeunit * (j + 1);
+ if(ac_time >= sec)
+ break;
+ tmap_sector = vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff;
+ }
+ //search enclosing cell
+ for(i=0; i<d->cur_pgc->nr_of_cells; i++) {
+ if(tmap_sector >= d->cur_pgc->cell_playback[i].first_sector && tmap_sector <= d->cur_pgc->cell_playback[i].last_sector) {
+ cell_sector = d->cur_pgc->cell_playback[i].first_sector;
+ break;
+ }
+ }
+
+ pos = ((int64_t)cell_sector)<<11;
+ do_seek(stream, pos);
+ do {
+ char buf[2048];
+ if (dvd_read_sector(stream, stream->priv, buf) < 0) // skip
+ break;
+ t = mp_dvdtimetomsec(&d->dsi_pack.dsi_gi.c_eltm);
+ } while(!t);
+ tm = dvd_get_current_time(stream, -1);
+
+ pos = ((int64_t)tmap_sector)<<11;
+ do_seek(stream, pos);
+ //now get current time in terms of the cell+cell time offset
+ memset(&d->dsi_pack.dsi_gi.c_eltm, 0, sizeof(dvd_time_t));
+ while(tm <= sec) {
+ char buf[2048];
+ if (dvd_read_sector(stream, stream->priv, buf) < 0) // skip
+ break;
+ pos += 2048;
+ tm = dvd_get_current_time(stream, -1);
+ };
+ tmap_sector = pos >> 11;
+
+ //search closest VOBU sector
+ k=(vts_file->vts_vobu_admap->last_byte + 1 - VOBU_ADMAP_SIZE)/4; //entries in the vobu admap
+ for(i=1; i<k; i++) {
+ if(vts_file->vts_vobu_admap->vobu_start_sectors[i] > tmap_sector)
+ break;
+ }
+ vobu_sector = vts_file->vts_vobu_admap->vobu_start_sectors[i-1];
+ pos = ((int64_t)vobu_sector) << 11;
+ do_seek(stream, pos);
+
+ return 1;
+}
+
+static int control(stream_t *stream,int cmd,void* arg)
+{
+ dvd_priv_t *d = stream->priv;
+ switch(cmd)
+ {
+ case STREAM_CTRL_GET_TIME_LENGTH:
+ {
+ *((double *)arg) = (double) mp_get_titleset_length(d->vts_file, d->tt_srpt, d->cur_title)/1000.0;
+ return 1;
+ }
+ case STREAM_CTRL_GET_NUM_TITLES:
+ {
+ *((unsigned int *)arg) = d->vmg_file->tt_srpt->nr_of_srpts;
+ return 1;
+ }
+ case STREAM_CTRL_GET_NUM_CHAPTERS:
+ {
+ int r;
+ r = get_num_chapter(d->vts_file, d->tt_srpt, d->cur_title);
+ if(! r) return STREAM_UNSUPPORTED;
+ *((unsigned int *)arg) = r;
+ return 1;
+ }
+ case STREAM_CTRL_GET_CHAPTER_TIME:
+ {
+ int r;
+ r = get_chapter_time(d->vts_file, d->tt_srpt, d->cur_title, (double *)arg);
+ if(! r) return STREAM_UNSUPPORTED;
+ return 1;
+ }
+ case STREAM_CTRL_GET_CURRENT_TITLE:
+ {
+ *((unsigned int *)arg) = d->cur_title;
+ return 1;
+ }
+ case STREAM_CTRL_GET_CURRENT_TIME:
+ {
+ double tm;
+ tm = dvd_get_current_time(stream, -1);
+ if(tm != -1) {
+ *((double *)arg) = tm;
+ return 1;
+ }
+ break;
+ }
+ case STREAM_CTRL_SEEK_TO_TIME:
+ {
+ if(dvd_seek_to_time(stream, d->vts_file, *((double*)arg)))
+ return 1;
+ break;
+ }
+ case STREAM_CTRL_GET_ASPECT_RATIO:
+ {
+ *((double *)arg) = !d->vts_file->vtsi_mat->vts_video_attr.display_aspect_ratio ? 4.0/3.0 : 16.0/9.0;
+ return 1;
+ }
+ case STREAM_CTRL_GET_NUM_ANGLES:
+ {
+ *((int *)arg) = d->vmg_file->tt_srpt->title[d->dvd_title].nr_of_angles;
+ return 1;
+ }
+ case STREAM_CTRL_GET_ANGLE:
+ {
+ *((int *)arg) = d->dvd_angle;
+ return 1;
+ }
+ case STREAM_CTRL_SET_ANGLE:
+ {
+ int ang = *((int *)arg);
+ if(ang>d->vmg_file->tt_srpt->title[d->dvd_title].nr_of_angles || ang<=0)
+ break;
+ d->dvd_angle = ang;
+ d->angle_seek = 1;
+ return 1;
+ }
+ case STREAM_CTRL_GET_LANG:
+ {
+ struct stream_lang_req *req = arg;
+ int lang = 0;
+ switch(req->type) {
+ case STREAM_AUDIO:
+ lang = dvd_lang_from_aid(stream, req->id);
+ break;
+ case STREAM_SUB:
+ lang = dvd_lang_from_sid(stream, req->id);
+ break;
+ }
+ if (!lang)
+ break;
+ snprintf(req->name, sizeof(req->name), "%c%c", lang >> 8, lang);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_DVD_INFO:
+ {
+ struct stream_dvd_info_req *req = arg;
+ memset(req, 0, sizeof(*req));
+ req->num_subs = dvd_number_of_subs(stream);
+ memcpy(req->palette, d->cur_pgc->palette, sizeof(req->palette));
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_DISC_NAME:
+ {
+ char buffer[128];
+ if (DVDUDFVolumeInfo(d->dvd, buffer, sizeof(buffer), NULL, 0) < 0 &&
+ DVDISOVolumeInfo(d->dvd, buffer, sizeof(buffer), NULL, 0) < 0)
+ break;
+ if (!buffer[0])
+ break;
+ *(char**)arg = talloc_strdup(NULL, buffer);
+ return STREAM_OK;
+ }
+ }
+ return STREAM_UNSUPPORTED;
+}
+
+
+static int open_s(stream_t *stream)
+{
+ int k;
+ dvd_priv_t *d = stream->priv;
+
+ d->dvd_angle = stream->opts->dvd_angle;
+
+ MP_VERBOSE(stream, "URL: %s\n", stream->url);
+ d->dvd_title = d->cfg_title + 1;
+ if(1){
+ //int ret,ret2;
+ int ttn,pgc_id,pgn;
+ dvd_reader_t *dvd;
+ dvd_file_t *title;
+ ifo_handle_t *vmg_file;
+ tt_srpt_t *tt_srpt;
+ ifo_handle_t *vts_file;
+ pgc_t *pgc;
+ /**
+ * Open the disc.
+ */
+ if(d->cfg_device && d->cfg_device[0])
+ d->dvd_device_current = d->cfg_device;
+ else if(stream->opts->dvd_device && stream->opts->dvd_device[0])
+ d->dvd_device_current = talloc_strdup(stream, stream->opts->dvd_device);
+ else
+ d->dvd_device_current = DEFAULT_DVD_DEVICE;
+ d->dvd_speed = stream->opts->dvd_speed;
+ dvd_set_speed(stream,d->dvd_device_current, d->dvd_speed);
+#if defined(__APPLE__) || defined(__DARWIN__)
+ /* Dynamic DVD drive selection on Darwin */
+ if(!strcmp(d->dvd_device_current, "/dev/rdiskN")) {
+ int i;
+ size_t len = strlen(d->dvd_device_current)+1;
+ char *temp_device = malloc(len);
+
+ for (i = 1; i < 10; i++) {
+ snprintf(temp_device, len, "/dev/rdisk%d", i);
+ dvd = DVDOpen(temp_device);
+ if(!dvd) {
+ MP_ERR(stream, "Couldn't open DVD device: %s (%s)\n",temp_device, strerror(errno));
+ } else {
+#if DVDREAD_VERSION <= LIBDVDREAD_VERSION(0,9,4)
+ dvd_file_t *dvdfile = DVDOpenFile(dvd,d->dvd_title,DVD_READ_INFO_FILE);
+ if(!dvdfile) {
+ MP_ERR(stream, "Couldn't open DVD device: %s (%s)\n",temp_device, strerror(errno));
+ DVDClose(dvd);
+ continue;
+ }
+ DVDCloseFile(dvdfile);
+#endif
+ break;
+ }
+ }
+ free(temp_device);
+
+ if(!dvd) {
+ return STREAM_UNSUPPORTED;
+ }
+ } else
+#endif /* defined(__APPLE__) || defined(__DARWIN__) */
+ {
+ dvd = DVDOpen(d->dvd_device_current);
+ if(!dvd) {
+ MP_ERR(stream, "Couldn't open DVD device: %s (%s)\n",d->dvd_device_current, strerror(errno));
+ return STREAM_UNSUPPORTED;
+ }
+ }
+
+ MP_VERBOSE(stream, "Reading disc structure, please wait...\n");
+
+ /**
+ * Load the video manager to find out the information about the titles on
+ * this disc.
+ */
+ vmg_file = ifoOpen(dvd, 0);
+ if(!vmg_file) {
+ MP_ERR(stream, "Can't open VMG info!\n");
+ DVDClose( dvd );
+ return STREAM_UNSUPPORTED;
+ }
+ tt_srpt = vmg_file->tt_srpt;
+ /**
+ * Make sure our title number is valid.
+ */
+ MP_INFO(stream, "There are %d titles on this DVD.\n", tt_srpt->nr_of_srpts );
+ if(d->dvd_title < 1 || d->dvd_title > tt_srpt->nr_of_srpts) {
+ MP_ERR(stream, "Invalid DVD title number: %d\n", d->dvd_title);
+ ifoClose( vmg_file );
+ DVDClose( dvd );
+ return STREAM_UNSUPPORTED;
+ }
+ --(d->dvd_title); // remap 1.. -> 0..
+ /**
+ * Make sure the angle number is valid for this title.
+ */
+ MP_INFO(stream, "There are %d angles in this DVD title.\n", tt_srpt->title[d->dvd_title].nr_of_angles);
+ if(d->dvd_angle<1 || d->dvd_angle>tt_srpt->title[d->dvd_title].nr_of_angles) {
+ MP_ERR(stream, "Invalid DVD angle number: %d\n", d->dvd_angle);
+ goto fail;
+ }
+
+ ttn = tt_srpt->title[d->dvd_title].vts_ttn - 1;
+ /**
+ * Load the VTS information for the title set our title is in.
+ */
+ vts_file = ifoOpen( dvd, tt_srpt->title[d->dvd_title].title_set_nr );
+ if(!vts_file) {
+ MP_ERR(stream, "Cannot open the IFO file for DVD title %d.\n", tt_srpt->title[d->dvd_title].title_set_nr );
+ goto fail;
+ }
+ /**
+ * We've got enough info, time to open the title set data.
+ */
+ title = DVDOpenFile(dvd, tt_srpt->title[d->dvd_title].title_set_nr, DVD_READ_TITLE_VOBS);
+ if(!title) {
+ MP_ERR(stream, "Cannot open title VOBS (VTS_%02d_1.VOB).\n", tt_srpt->title[d->dvd_title].title_set_nr);
+ ifoClose( vts_file );
+ goto fail;
+ }
+
+ MP_VERBOSE(stream, "DVD successfully opened.\n");
+ // store data
+ d->dvd=dvd;
+ d->title=title;
+ d->vmg_file=vmg_file;
+ d->tt_srpt=tt_srpt;
+ d->vts_file=vts_file;
+ d->cur_title = d->dvd_title;
+
+ pgc = vts_file->vts_pgcit ? vts_file->vts_pgcit->pgci_srp[ttn].pgc : NULL;
+ /**
+ * Check number of audio channels and types
+ */
+ {
+ d->nr_of_channels=0;
+ if(vts_file->vts_pgcit) {
+ int i;
+ for(i=0;i<8;i++)
+ if(pgc->audio_control[i] & 0x8000) {
+ audio_attr_t * audio = &vts_file->vtsi_mat->vts_audio_attr[i];
+ int language = 0;
+ char tmp[] = "unknown";
+ stream_language_t *audio_stream = &d->audio_streams[d->nr_of_channels];
+
+ if(audio->lang_type == 1) {
+ language=audio->lang_code;
+ tmp[0]=language>>8;
+ tmp[1]=language&0xff;
+ tmp[2]=0;
+ }
+
+ audio_stream->language=language;
+ audio_stream->id=pgc->audio_control[i] >> 8 & 7;
+ switch(audio->audio_format) {
+ case 0: // ac3
+ audio_stream->id+=FIRST_AC3_AID;
+ break;
+ case 6: // dts
+ audio_stream->id+=FIRST_DTS_AID;
+ break;
+ case 2: // mpeg layer 1/2/3
+ case 3: // mpeg2 ext
+ audio_stream->id+=FIRST_MPG_AID;
+ break;
+ case 4: // lpcm
+ audio_stream->id+=FIRST_PCM_AID;
+ break;
+ }
+
+ audio_stream->type=audio->audio_format;
+ // Pontscho: to my mind, tha channels:
+ // 1 - stereo
+ // 5 - 5.1
+ audio_stream->channels=audio->channels;
+ MP_INFO(stream, "audio stream: %d format: %s (%s) language: %s aid: %d.\n",
+ d->nr_of_channels,
+ dvd_audio_stream_types[ audio->audio_format ],
+ dvd_audio_stream_channels[ audio->channels ],
+ tmp,
+ audio_stream->id
+ );
+
+ d->nr_of_channels++;
+ }
+ }
+ MP_INFO(stream, "number of audio channels on disk: %d.\n",d->nr_of_channels );
+ }
+
+ /**
+ * Check number of subtitles and language
+ */
+ {
+ int i;
+
+ d->nr_of_subtitles=0;
+ for(i=0;i<32;i++)
+ if(pgc->subp_control[i] & 0x80000000) {
+ subp_attr_t * subtitle = &vts_file->vtsi_mat->vts_subp_attr[i];
+ video_attr_t *video = &vts_file->vtsi_mat->vts_video_attr;
+ int language = 0;
+ char tmp[] = "unknown";
+ stream_language_t *sub_stream = &d->subtitles[d->nr_of_subtitles];
+
+ if(subtitle->type == 1) {
+ language=subtitle->lang_code;
+ tmp[0]=language>>8;
+ tmp[1]=language&0xff;
+ tmp[2]=0;
+ }
+
+ sub_stream->language=language;
+ sub_stream->id=d->nr_of_subtitles;
+ if(video->display_aspect_ratio == 0) /* 4:3 */
+ sub_stream->id = pgc->subp_control[i] >> 24 & 31;
+ else if(video->display_aspect_ratio == 3) /* 16:9 */
+ sub_stream->id = pgc->subp_control[i] >> 8 & 31;
+
+ MP_INFO(stream, "subtitle ( sid ): %d language: %s\n", sub_stream->id, tmp);
+ d->nr_of_subtitles++;
+ }
+ MP_INFO(stream, "number of subtitles on disk: %d\n",d->nr_of_subtitles);
+ }
+
+ /**
+ * Determine which program chain we want to watch. This is based on the
+ * chapter number.
+ */
+ pgc_id = vts_file->vts_ptt_srpt->title[ttn].ptt[0].pgcn; // local
+ pgn = vts_file->vts_ptt_srpt->title[ttn].ptt[0].pgn; // local
+ d->cur_pgc_idx = pgc_id-1;
+ d->cur_pgc = vts_file->vts_pgcit->pgci_srp[pgc_id-1].pgc;
+ d->cur_cell = d->cur_pgc->program_map[pgn-1] - 1; // start playback here
+ d->packs_left=-1; // for Navi stuff
+ d->angle_seek=0;
+ d->last_cell=d->cur_pgc->nr_of_cells;
+
+ if(d->cur_pgc->cell_playback[d->cur_cell].block_type == BLOCK_TYPE_ANGLE_BLOCK )
+ d->cur_cell+=d->dvd_angle-1;
+ d->cur_pack = d->cur_pgc->cell_playback[ d->cur_cell ].first_sector;
+ d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector;
+ MP_VERBOSE(stream, "DVD start cell: %d pack: 0x%X-0x%X \n",d->cur_cell,d->cur_pack,d->cell_last_pack);
+
+ //assign cell_times_table
+ d->cell_times_table = malloc(sizeof(unsigned int) * d->cur_pgc->nr_of_cells);
+ if(d->cell_times_table == NULL)
+ return STREAM_UNSUPPORTED;
+ for(k=0; k<d->cur_pgc->nr_of_cells; k++)
+ d->cell_times_table[k] = mp_dvdtimetomsec(&d->cur_pgc->cell_playback[k].playback_time);
+ list_chapters(stream, vts_file,tt_srpt,d->dvd_title);
+
+ // ... (unimplemented)
+ // return NULL;
+ stream->type = STREAMTYPE_DVD;
+ stream->demuxer = "+disc";
+ stream->lavf_type = "mpeg";
+ stream->sector_size = 2048;
+ stream->fill_buffer = fill_buffer;
+ stream->control = control;
+ stream->close = stream_dvd_close;
+ stream->end_pos = (int64_t)(d->cur_pgc->cell_playback[d->last_cell-1].last_sector)*2048;
+ MP_VERBOSE(stream, "DVD start=%d end=%d \n",d->cur_pack,d->cur_pgc->cell_playback[d->last_cell-1].last_sector);
+ stream->priv = (void*)d;
+ return STREAM_OK;
+
+fail:
+ ifoClose(vmg_file);
+ DVDClose(dvd);
+ return STREAM_UNSUPPORTED;
+ }
+ MP_ERR(stream, "mpv was compiled without DVD support, exiting.\n");
+ return STREAM_UNSUPPORTED;
+}
+
+static int ifo_stream_open (stream_t *stream)
+{
+ char* filename;
+ dvd_priv_t *priv = talloc_ptrtype(stream, priv);
+ stream->priv = priv;
+ *priv = stream_priv_dflts;
+
+ // "file://" prefix -> decode URL-style escapes
+ if (strlen(stream->url) > strlen(stream->path))
+ mp_url_unescape_inplace(stream->path);
+
+ int len = strlen(stream->path);
+ if (len < 4 || strcasecmp (stream->path + len - 4, ".ifo"))
+ return STREAM_UNSUPPORTED;
+
+ MP_INFO(stream, ".IFO detected. Redirecting to dvd://\n");
+
+ filename = strdup(basename(stream->path));
+
+ talloc_free(priv->cfg_device);
+ priv->cfg_device = talloc_strdup(NULL, dirname(stream->path));
+ if(!strncasecmp(filename,"vts_",4))
+ {
+ if(sscanf(filename+3, "_%02d_", &priv->cfg_title)!=0)
+ priv->cfg_title = 0;
+ }else
+ priv->cfg_title = 0;
+
+ free(filename);
+ stream->url=talloc_strdup(stream, "dvdread://");
+
+ return open_s(stream);
+}
+
+const stream_info_t stream_info_dvd = {
+ .name = "dvd",
+ .open = open_s,
+ .protocols = (const char*const[]){ "dvdread", NULL },
+ .priv_size = sizeof(dvd_priv_t),
+ .priv_defaults = &stream_priv_dflts,
+ .options = stream_opts_fields,
+ .url_options = (const char*const[]){
+ "hostname=title",
+ "filename=device",
+ NULL
+ },
+};
+
+const stream_info_t stream_info_ifo = {
+ .name = "ifo",
+ .open = ifo_stream_open,
+ .protocols = (const char*const[]){ "file", "", NULL },
+};
diff --git a/stream/stream_dvdnav.c b/stream/stream_dvdnav.c
new file mode 100644
index 0000000000..2d9658bc78
--- /dev/null
+++ b/stream/stream_dvdnav.c
@@ -0,0 +1,755 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <libavutil/common.h>
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <dvdnav/dvdnav.h>
+
+#include "options/options.h"
+#include "common/msg.h"
+#include "input/input.h"
+#include "options/m_option.h"
+#include "osdep/timer.h"
+#include "stream.h"
+#include "demux/demux.h"
+#include "discnav.h"
+#include "video/out/vo.h"
+#include "stream_dvd_common.h"
+
+#define TITLE_MENU -1
+#define TITLE_LONGEST -2
+
+struct priv {
+ dvdnav_t *dvdnav; // handle to libdvdnav stuff
+ char *filename; // path
+ unsigned int duration; // in milliseconds
+ int mousex, mousey;
+ int title;
+ uint32_t spu_clut[16];
+ bool spu_clut_valid;
+ dvdnav_highlight_event_t hlev;
+ int still_length; // still frame duration
+ unsigned long next_event; // bitmask of events to return to player
+ bool suspended_read;
+ bool nav_enabled;
+ bool had_initial_vts;
+
+ int dvd_speed;
+
+ int track;
+ char *device;
+};
+
+static const struct priv stream_priv_dflts = {
+ .track = TITLE_LONGEST,
+};
+
+#define OPT_BASE_STRUCT struct priv
+static const m_option_t stream_opts_fields[] = {
+ OPT_CHOICE_OR_INT("title", track, 0, 0, 99,
+ ({"menu", TITLE_MENU},
+ {"longest", TITLE_LONGEST})),
+ OPT_STRING("device", device, 0),
+ {0}
+};
+
+#define DNE(e) [e] = # e
+static const char *const mp_dvdnav_events[] = {
+ DNE(DVDNAV_BLOCK_OK),
+ DNE(DVDNAV_NOP),
+ DNE(DVDNAV_STILL_FRAME),
+ DNE(DVDNAV_SPU_STREAM_CHANGE),
+ DNE(DVDNAV_AUDIO_STREAM_CHANGE),
+ DNE(DVDNAV_VTS_CHANGE),
+ DNE(DVDNAV_CELL_CHANGE),
+ DNE(DVDNAV_NAV_PACKET),
+ DNE(DVDNAV_STOP),
+ DNE(DVDNAV_HIGHLIGHT),
+ DNE(DVDNAV_SPU_CLUT_CHANGE),
+ DNE(DVDNAV_HOP_CHANNEL),
+ DNE(DVDNAV_WAIT),
+};
+
+static const char *const mp_nav_cmd_types[] = {
+ DNE(MP_NAV_CMD_NONE),
+ DNE(MP_NAV_CMD_ENABLE),
+ DNE(MP_NAV_CMD_DRAIN_OK),
+ DNE(MP_NAV_CMD_RESUME),
+ DNE(MP_NAV_CMD_SKIP_STILL),
+ DNE(MP_NAV_CMD_MENU),
+ DNE(MP_NAV_CMD_MOUSE_POS),
+};
+
+static const char *const mp_nav_event_types[] = {
+ DNE(MP_NAV_EVENT_NONE),
+ DNE(MP_NAV_EVENT_RESET),
+ DNE(MP_NAV_EVENT_RESET_CLUT),
+ DNE(MP_NAV_EVENT_RESET_ALL),
+ DNE(MP_NAV_EVENT_DRAIN),
+ DNE(MP_NAV_EVENT_STILL_FRAME),
+ DNE(MP_NAV_EVENT_HIGHLIGHT),
+ DNE(MP_NAV_EVENT_MENU_MODE),
+ DNE(MP_NAV_EVENT_EOF),
+};
+
+#define LOOKUP_NAME(array, i) \
+ (((i) >= 0 && (i) < MP_ARRAY_SIZE(array)) ? array[(i)] : "?")
+
+static void dvdnav_get_highlight(struct priv *priv, int display_mode)
+{
+ pci_t *pnavpci = NULL;
+ dvdnav_highlight_event_t *hlev = &(priv->hlev);
+ int btnum;
+
+ if (!priv || !priv->dvdnav)
+ return;
+
+ pnavpci = dvdnav_get_current_nav_pci(priv->dvdnav);
+ if (!pnavpci) {
+ hlev->display = 0;
+ return;
+ }
+
+ dvdnav_get_current_highlight(priv->dvdnav, &(hlev->buttonN));
+ hlev->display = display_mode; /* show */
+
+ if (hlev->buttonN > 0 && pnavpci->hli.hl_gi.btn_ns > 0 && hlev->display) {
+ for (btnum = 0; btnum < pnavpci->hli.hl_gi.btn_ns; btnum++) {
+ btni_t *btni = &(pnavpci->hli.btnit[btnum]);
+
+ if (hlev->buttonN == btnum + 1) {
+ hlev->sx = FFMIN(btni->x_start, btni->x_end);
+ hlev->ex = FFMAX(btni->x_start, btni->x_end);
+ hlev->sy = FFMIN(btni->y_start, btni->y_end);
+ hlev->ey = FFMAX(btni->y_start, btni->y_end);
+
+ hlev->palette = (btni->btn_coln == 0) ?
+ 0 : pnavpci->hli.btn_colit.btn_coli[btni->btn_coln - 1][0];
+ break;
+ }
+ }
+ } else { /* hide button or no button */
+ hlev->sx = hlev->ex = 0;
+ hlev->sy = hlev->ey = 0;
+ hlev->palette = hlev->buttonN = 0;
+ }
+}
+
+static void handle_menu_input(stream_t *stream, const char *cmd)
+{
+ struct priv *priv = stream->priv;
+ dvdnav_t *nav = priv->dvdnav;
+ dvdnav_status_t status = DVDNAV_STATUS_ERR;
+ pci_t *pci = dvdnav_get_current_nav_pci(nav);
+
+ MP_VERBOSE(stream, "DVDNAV: input '%s'\n", cmd);
+
+ if (!pci)
+ return;
+
+ if (strcmp(cmd, "up") == 0) {
+ status = dvdnav_upper_button_select(nav, pci);
+ } else if (strcmp(cmd, "down") == 0) {
+ status = dvdnav_lower_button_select(nav, pci);
+ } else if (strcmp(cmd, "left") == 0) {
+ status = dvdnav_left_button_select(nav, pci);
+ } else if (strcmp(cmd, "right") == 0) {
+ status = dvdnav_right_button_select(nav, pci);
+ } else if (strcmp(cmd, "menu") == 0) {
+ status = dvdnav_menu_call(nav, DVD_MENU_Root);
+ } else if (strcmp(cmd, "prev") == 0) {
+ int title = 0, part = 0;
+ dvdnav_current_title_info(nav, &title, &part);
+ if (title)
+ status = dvdnav_menu_call(nav, DVD_MENU_Part);
+ if (status != DVDNAV_STATUS_OK)
+ status = dvdnav_menu_call(nav, DVD_MENU_Title);
+ if (status != DVDNAV_STATUS_OK)
+ status = dvdnav_menu_call(nav, DVD_MENU_Root);
+ } else if (strcmp(cmd, "select") == 0) {
+ status = dvdnav_button_activate(nav, pci);
+ } else if (strcmp(cmd, "mouse") == 0) {
+ status = dvdnav_mouse_activate(nav, pci, priv->mousex, priv->mousey);
+ } else {
+ MP_VERBOSE(stream, "Unknown DVDNAV command: '%s'\n", cmd);
+ }
+}
+
+static void handle_mouse_pos(stream_t *stream, int x, int y)
+{
+ struct priv *priv = stream->priv;
+ dvdnav_t *nav = priv->dvdnav;
+ pci_t *pci = dvdnav_get_current_nav_pci(nav);
+
+ if (!pci)
+ return;
+
+ dvdnav_mouse_select(nav, pci, x, y);
+ priv->mousex = x;
+ priv->mousey = y;
+}
+
+/**
+ * \brief mp_dvdnav_lang_from_aid() returns the language corresponding to audio id 'aid'
+ * \param stream: - stream pointer
+ * \param sid: physical subtitle id
+ * \return 0 on error, otherwise language id
+ */
+static int mp_dvdnav_lang_from_aid(stream_t *stream, int aid)
+{
+ uint8_t lg;
+ uint16_t lang;
+ struct priv *priv = stream->priv;
+
+ if (aid < 0)
+ return 0;
+ lg = dvdnav_get_audio_logical_stream(priv->dvdnav, aid & 0x7);
+ if (lg == 0xff)
+ return 0;
+ lang = dvdnav_audio_stream_to_lang(priv->dvdnav, lg);
+ if (lang == 0xffff)
+ return 0;
+ return lang;
+}
+
+/**
+ * \brief mp_dvdnav_lang_from_sid() returns the language corresponding to subtitle id 'sid'
+ * \param stream: - stream pointer
+ * \param sid: physical subtitle id
+ * \return 0 on error, otherwise language id
+ */
+static int mp_dvdnav_lang_from_sid(stream_t *stream, int sid)
+{
+ uint8_t k;
+ uint16_t lang;
+ struct priv *priv = stream->priv;
+ if (sid < 0)
+ return 0;
+ for (k = 0; k < 32; k++)
+ if (dvdnav_get_spu_logical_stream(priv->dvdnav, k) == sid)
+ break;
+ if (k == 32)
+ return 0;
+ lang = dvdnav_spu_stream_to_lang(priv->dvdnav, k);
+ if (lang == 0xffff)
+ return 0;
+ return lang;
+}
+
+/**
+ * \brief mp_dvdnav_number_of_subs() returns the count of available subtitles
+ * \param stream: - stream pointer
+ * \return 0 on error, something meaningful otherwise
+ */
+static int mp_dvdnav_number_of_subs(stream_t *stream)
+{
+ struct priv *priv = stream->priv;
+ uint8_t lg, k, n = 0;
+
+ for (k = 0; k < 32; k++) {
+ lg = dvdnav_get_spu_logical_stream(priv->dvdnav, k);
+ if (lg == 0xff)
+ continue;
+ if (lg >= n)
+ n = lg + 1;
+ }
+ return n;
+}
+
+static void handle_cmd(stream_t *s, struct mp_nav_cmd *ev)
+{
+ struct priv *priv = s->priv;
+ MP_VERBOSE(s, "DVDNAV: input '%s'\n",
+ LOOKUP_NAME(mp_nav_cmd_types, ev->event));
+ switch (ev->event) {
+ case MP_NAV_CMD_ENABLE:
+ priv->nav_enabled = true;
+ break;
+ case MP_NAV_CMD_DRAIN_OK:
+ dvdnav_wait_skip(priv->dvdnav);
+ break;
+ case MP_NAV_CMD_RESUME:
+ priv->suspended_read = false;
+ break;
+ case MP_NAV_CMD_SKIP_STILL:
+ dvdnav_still_skip(priv->dvdnav);
+ break;
+ case MP_NAV_CMD_MENU:
+ handle_menu_input(s, ev->u.menu.action);
+ break;
+ case MP_NAV_CMD_MOUSE_POS:
+ handle_mouse_pos(s, ev->u.mouse_pos.x, ev->u.mouse_pos.y);
+ break;
+ }
+
+}
+
+static inline bool set_event_type(struct priv *priv, int type,
+ struct mp_nav_event *event)
+{
+ if (!(priv->next_event & (1 << type)))
+ return false;
+ priv->next_event &= ~(1 << type);
+ event->event = type;
+ return true;
+}
+
+static void fill_next_event(stream_t *s, struct mp_nav_event **ret)
+{
+ struct priv *priv = s->priv;
+ struct mp_nav_event e = {0};
+ if (!set_event_type(priv, MP_NAV_EVENT_RESET_ALL, &e))
+ for (int n = 0; n < 30 && !set_event_type(priv, n, &e); n++) ;
+ switch (e.event) {
+ case MP_NAV_EVENT_NONE:
+ return;
+ case MP_NAV_EVENT_HIGHLIGHT: {
+ dvdnav_highlight_event_t hlev = priv->hlev;
+ e.u.highlight.display = hlev.display;
+ e.u.highlight.sx = hlev.sx;
+ e.u.highlight.sy = hlev.sy;
+ e.u.highlight.ex = hlev.ex;
+ e.u.highlight.ey = hlev.ey;
+ e.u.highlight.palette = hlev.palette;
+ break;
+ }
+ case MP_NAV_EVENT_MENU_MODE:
+ e.u.menu_mode.enable = !dvdnav_is_domain_vts(priv->dvdnav);
+ break;
+ case MP_NAV_EVENT_STILL_FRAME:
+ e.u.still_frame.seconds = priv->still_length;
+ break;
+ }
+ *ret = talloc(NULL, struct mp_nav_event);
+ **ret = e;
+
+ MP_VERBOSE(s, "DVDNAV: player event '%s'\n",
+ LOOKUP_NAME(mp_nav_event_types, e.event));
+}
+
+static int fill_buffer(stream_t *s, char *buf, int max_len)
+{
+ struct priv *priv = s->priv;
+ dvdnav_t *dvdnav = priv->dvdnav;
+
+ if (max_len < 2048)
+ return -1;
+
+ while (1) {
+ if (priv->suspended_read)
+ return -1;
+
+ int len = -1;
+ int event = DVDNAV_NOP;
+ if (dvdnav_get_next_block(dvdnav, buf, &event, &len) != DVDNAV_STATUS_OK)
+ {
+ MP_ERR(s, "Error getting next block from DVD %d (%s)\n",
+ event, dvdnav_err_to_string(dvdnav));
+ return 0;
+ }
+ if (event != DVDNAV_BLOCK_OK) {
+ const char *name = LOOKUP_NAME(mp_dvdnav_events, event);
+ MP_VERBOSE(s, "DVDNAV: event %s (%d).\n", name, event);
+ dvdnav_get_highlight(priv, 1);
+ }
+ switch (event) {
+ case DVDNAV_BLOCK_OK:
+ return len;
+ case DVDNAV_STOP: {
+ priv->next_event |= 1 << MP_NAV_EVENT_EOF;
+ return 0;
+ }
+ case DVDNAV_NAV_PACKET: {
+ pci_t *pnavpci = dvdnav_get_current_nav_pci(dvdnav);
+ uint32_t start_pts = pnavpci->pci_gi.vobu_s_ptm;
+ MP_TRACE(s, "start pts = %"PRIu32"\n", start_pts);
+ break;
+ }
+ case DVDNAV_STILL_FRAME: {
+ dvdnav_still_event_t *still_event = (dvdnav_still_event_t *) buf;
+ priv->still_length = still_event->length;
+ if (priv->still_length == 255)
+ priv->still_length = -1;
+ MP_VERBOSE(s, "len=%d\n", priv->still_length);
+ /* set still frame duration */
+ if (priv->still_length <= 1) {
+ pci_t *pnavpci = dvdnav_get_current_nav_pci(dvdnav);
+ priv->duration = mp_dvdtimetomsec(&pnavpci->pci_gi.e_eltm);
+ }
+ if (priv->nav_enabled) {
+ priv->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
+ } else {
+ dvdnav_still_skip(dvdnav);
+ }
+ return 0;
+ }
+ case DVDNAV_WAIT: {
+ if (priv->nav_enabled) {
+ priv->next_event |= 1 << MP_NAV_EVENT_DRAIN;
+ } else {
+ dvdnav_wait_skip(dvdnav);
+ }
+ return 0;
+ }
+ case DVDNAV_HIGHLIGHT: {
+ dvdnav_get_highlight(priv, 1);
+ priv->next_event |= 1 << MP_NAV_EVENT_HIGHLIGHT;
+ break;
+ }
+ case DVDNAV_VTS_CHANGE: {
+ int tit = 0, part = 0;
+ dvdnav_vts_change_event_t *vts_event =
+ (dvdnav_vts_change_event_t *)s->buffer;
+ MP_INFO(s, "DVDNAV, switched to title: %d\n",
+ vts_event->new_vtsN);
+ if (!priv->had_initial_vts) {
+ // dvdnav sends an initial VTS change before any data; don't
+ // cause a blocking wait for the player, because the player in
+ // turn can't initialize the demuxer without data.
+ priv->had_initial_vts = true;
+ break;
+ }
+ // clear all previous events
+ priv->next_event = 0;
+ priv->next_event |= 1 << MP_NAV_EVENT_RESET;
+ priv->next_event |= 1 << MP_NAV_EVENT_RESET_ALL;
+ if (dvdnav_current_title_info(dvdnav, &tit, &part) == DVDNAV_STATUS_OK)
+ {
+ MP_VERBOSE(s, "DVDNAV, NEW TITLE %d\n", tit);
+ dvdnav_get_highlight(priv, 0);
+ if (priv->title > 0 && tit != priv->title) {
+ priv->next_event |= 1 << MP_NAV_EVENT_EOF;;
+ MP_WARN(s, "Requested title not found\n");
+ }
+ }
+ if (priv->nav_enabled)
+ priv->suspended_read = true;
+ break;
+ }
+ case DVDNAV_CELL_CHANGE: {
+ dvdnav_cell_change_event_t *ev = (dvdnav_cell_change_event_t *)buf;
+
+ priv->next_event |= 1 << MP_NAV_EVENT_RESET;
+ priv->next_event |= 1 << MP_NAV_EVENT_MENU_MODE;
+ if (ev->pgc_length)
+ priv->duration = ev->pgc_length / 90;
+
+ dvdnav_get_highlight(priv, 1);
+ break;
+ }
+ case DVDNAV_SPU_CLUT_CHANGE: {
+ memcpy(priv->spu_clut, buf, 16 * sizeof(uint32_t));
+ priv->spu_clut_valid = true;
+ priv->next_event |= 1 << MP_NAV_EVENT_RESET_CLUT;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int control(stream_t *stream, int cmd, void *arg)
+{
+ struct priv *priv = stream->priv;
+ dvdnav_t *dvdnav = priv->dvdnav;
+ int tit, part;
+
+ switch (cmd) {
+ case STREAM_CTRL_GET_NUM_CHAPTERS: {
+ if (dvdnav_current_title_info(dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
+ break;
+ if (dvdnav_get_number_of_parts(dvdnav, tit, &part) != DVDNAV_STATUS_OK)
+ break;
+ if (!part)
+ break;
+ *(unsigned int *)arg = part;
+ return 1;
+ }
+ case STREAM_CTRL_GET_CHAPTER_TIME: {
+ double *ch = arg;
+ int chapter = *ch;
+ if (dvdnav_current_title_info(dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
+ break;
+ uint64_t *parts = NULL, duration = 0;
+ int n = dvdnav_describe_title_chapters(dvdnav, tit, &parts, &duration);
+ if (!parts)
+ break;
+ if (chapter < 0 || chapter >= n)
+ break;
+ *ch = parts[chapter] / 90000.0;
+ free(parts);
+ return 1;
+ }
+ case STREAM_CTRL_GET_TIME_LENGTH: {
+ if (priv->duration) {
+ *(double *)arg = (double)priv->duration / 1000.0;
+ return 1;
+ }
+ break;
+ }
+ case STREAM_CTRL_GET_ASPECT_RATIO: {
+ uint8_t ar = dvdnav_get_video_aspect(dvdnav);
+ *(double *)arg = !ar ? 4.0 / 3.0 : 16.0 / 9.0;
+ return 1;
+ }
+ case STREAM_CTRL_GET_CURRENT_TIME: {
+ double tm;
+ tm = dvdnav_get_current_time(dvdnav) / 90000.0f;
+ if (tm != -1) {
+ *(double *)arg = tm;
+ return 1;
+ }
+ break;
+ }
+ case STREAM_CTRL_GET_NUM_TITLES: {
+ int32_t num_titles = 0;
+ if (dvdnav_get_number_of_titles(dvdnav, &num_titles) != DVDNAV_STATUS_OK)
+ break;
+ *((unsigned int*)arg)= num_titles;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_CURRENT_TITLE: {
+ if (dvdnav_current_title_info(dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
+ break;
+ *((unsigned int *) arg) = tit - 1;
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_SET_CURRENT_TITLE: {
+ int title = *((unsigned int *) arg);
+ if (dvdnav_title_play(priv->dvdnav, title + 1) != DVDNAV_STATUS_OK)
+ break;
+ stream_drop_buffers(stream);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_SEEK_TO_TIME: {
+ double d = *(double *)arg;
+ int64_t tm = (int64_t)(d * 90000);
+ if (tm < 0)
+ tm = 0;
+ if (priv->duration && tm >= (priv->duration * 90))
+ tm = priv->duration * 90 - 1;
+ MP_VERBOSE(stream, "seek to PTS %f (%"PRId64")\n", d, tm);
+ if (dvdnav_time_search(dvdnav, tm) != DVDNAV_STATUS_OK)
+ break;
+ stream_drop_buffers(stream);
+ d = dvdnav_get_current_time(dvdnav) / 90000.0f;
+ MP_VERBOSE(stream, "landed at: %f\n", d);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_NUM_ANGLES: {
+ uint32_t curr, angles;
+ if (dvdnav_get_angle_info(dvdnav, &curr, &angles) != DVDNAV_STATUS_OK)
+ break;
+ *(int *)arg = angles;
+ return 1;
+ }
+ case STREAM_CTRL_GET_ANGLE: {
+ uint32_t curr, angles;
+ if (dvdnav_get_angle_info(dvdnav, &curr, &angles) != DVDNAV_STATUS_OK)
+ break;
+ *(int *)arg = curr;
+ return 1;
+ }
+ case STREAM_CTRL_SET_ANGLE: {
+ uint32_t curr, angles;
+ int new_angle = *(int *)arg;
+ if (dvdnav_get_angle_info(dvdnav, &curr, &angles) != DVDNAV_STATUS_OK)
+ break;
+ if (new_angle > angles || new_angle < 1)
+ break;
+ if (dvdnav_angle_change(dvdnav, new_angle) != DVDNAV_STATUS_OK)
+ return 1;
+ }
+ case STREAM_CTRL_GET_LANG: {
+ struct stream_lang_req *req = arg;
+ int lang = 0;
+ switch (req->type) {
+ case STREAM_AUDIO:
+ lang = mp_dvdnav_lang_from_aid(stream, req->id);
+ break;
+ case STREAM_SUB:
+ lang = mp_dvdnav_lang_from_sid(stream, req->id);
+ break;
+ }
+ if (!lang)
+ break;
+ snprintf(req->name, sizeof(req->name), "%c%c", lang >> 8, lang);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_DVD_INFO: {
+ struct stream_dvd_info_req *req = arg;
+ memset(req, 0, sizeof(*req));
+ req->num_subs = mp_dvdnav_number_of_subs(stream);
+ assert(sizeof(uint32_t) == sizeof(unsigned int));
+ memcpy(req->palette, priv->spu_clut, sizeof(req->palette));
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_NAV_EVENT: {
+ struct mp_nav_event **ev = arg;
+ if (ev)
+ fill_next_event(stream, ev);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_NAV_CMD: {
+ handle_cmd(stream, (struct mp_nav_cmd *)arg);
+ return STREAM_OK;
+ }
+ case STREAM_CTRL_GET_DISC_NAME: {
+ const char *volume = NULL;
+ if (dvdnav_get_title_string(dvdnav, &volume) != DVDNAV_STATUS_OK)
+ break;
+ if (!volume || !volume[0])
+ break;
+ *(char**)arg = talloc_strdup(NULL, volume);
+ return STREAM_OK;
+ }
+ }
+
+ return STREAM_UNSUPPORTED;
+}
+
+static void stream_dvdnav_close(stream_t *s)
+{
+ struct priv *priv = s->priv;
+ dvdnav_close(priv->dvdnav);
+ priv->dvdnav = NULL;
+ if (priv->dvd_speed)
+ dvd_set_speed(s, priv->filename, -1);
+ if (priv->filename)
+ free(priv->filename);
+}
+
+static struct priv *new_dvdnav_stream(stream_t *stream, char *filename)
+{
+ struct priv *priv = stream->priv;
+ const char *title_str;
+
+ if (!filename)
+ return NULL;
+
+ if (!(priv->filename = strdup(filename)))
+ return NULL;
+
+ priv->dvd_speed = stream->opts->dvd_speed;
+ dvd_set_speed(stream, priv->filename, priv->dvd_speed);
+
+ if (dvdnav_open(&(priv->dvdnav), priv->filename) != DVDNAV_STATUS_OK) {
+ free(priv->filename);
+ priv->filename = NULL;
+ return NULL;
+ }
+
+ if (!priv->dvdnav)
+ return NULL;
+
+ dvdnav_set_readahead_flag(priv->dvdnav, 1);
+ if (dvdnav_set_PGC_positioning_flag(priv->dvdnav, 1) != DVDNAV_STATUS_OK)
+ MP_ERR(stream, "stream_dvdnav, failed to set PGC positioning\n");
+ /* report the title?! */
+ dvdnav_get_title_string(priv->dvdnav, &title_str);
+
+ return priv;
+}
+
+static int open_s(stream_t *stream)
+{
+ struct priv *priv, *p;
+ priv = p = stream->priv;
+ char *filename;
+
+ if (p->device && p->device[0])
+ filename = p->device;
+ else if (stream->opts->dvd_device && stream->opts->dvd_device[0])
+ filename = stream->opts->dvd_device;
+ else
+ filename = DEFAULT_DVD_DEVICE;
+ if (!new_dvdnav_stream(stream, filename)) {
+ MP_ERR(stream, "Couldn't open DVD device: %s\n",
+ filename);
+ return STREAM_UNSUPPORTED;
+ }
+
+ if (p->track == TITLE_LONGEST) { // longest
+ dvdnav_t *dvdnav = priv->dvdnav;
+ uint64_t best_length = 0;
+ int best_title = -1;
+ int32_t num_titles;
+ if (dvdnav_get_number_of_titles(dvdnav, &num_titles) == DVDNAV_STATUS_OK) {
+ for (int n = 1; n <= num_titles; n++) {
+ uint64_t *parts = NULL, duration = 0;
+ dvdnav_describe_title_chapters(dvdnav, n, &parts, &duration);
+ if (parts) {
+ if (duration > best_length) {
+ best_length = duration;
+ best_title = n;
+ }
+ free(parts);
+ }
+ }
+ }
+ p->track = best_title - 1;
+ MP_INFO(stream, "Selecting title %d.\n", p->track);
+ }
+
+ if (p->track >= 0) {
+ priv->title = p->track;
+ if (dvdnav_title_play(priv->dvdnav, p->track + 1) != DVDNAV_STATUS_OK) {
+ MP_FATAL(stream, "dvdnav_stream, couldn't select title %d, error '%s'\n",
+ p->track, dvdnav_err_to_string(priv->dvdnav));
+ return STREAM_UNSUPPORTED;
+ }
+ } else {
+ if (dvdnav_menu_call(priv->dvdnav, DVD_MENU_Root) != DVDNAV_STATUS_OK)
+ dvdnav_menu_call(priv->dvdnav, DVD_MENU_Title);
+ }
+ if (stream->opts->dvd_angle > 1)
+ dvdnav_angle_change(priv->dvdnav, stream->opts->dvd_angle);
+
+ stream->sector_size = 2048;
+ stream->fill_buffer = fill_buffer;
+ stream->control = control;
+ stream->close = stream_dvdnav_close;
+ stream->type = STREAMTYPE_DVD;
+ stream->demuxer = "+disc";
+ stream->lavf_type = "mpeg";
+ stream->allow_caching = false;
+
+ return STREAM_OK;
+}
+
+const stream_info_t stream_info_dvdnav = {
+ .name = "dvdnav",
+ .open = open_s,
+ .protocols = (const char*const[]){ "dvd", "dvdnav", NULL },
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &stream_priv_dflts,
+ .options = stream_opts_fields,
+ .url_options = (const char*const[]){
+ "hostname=title",
+ "filename=device",
+ NULL
+ },
+};
diff --git a/sub/osd.c b/sub/osd.c
index 3a3c8b6c25..e88df98afe 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -206,6 +206,14 @@ void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs)
pthread_mutex_unlock(&osd->lock);
}
+void osd_set_nav_highlight(struct osd_state *osd, void *priv)
+{
+ pthread_mutex_lock(&osd->lock);
+ osd->objs[OSDTYPE_NAV_HIGHLIGHT]->highlight_priv = priv;
+ osd_changed_unlocked(osd, OSDTYPE_NAV_HIGHLIGHT);
+ pthread_mutex_unlock(&osd->lock);
+}
+
static void render_object(struct osd_state *osd, struct osd_object *obj,
struct mp_osd_res res, double video_pts,
const bool sub_formats[SUBBITMAP_COUNT],
@@ -239,6 +247,9 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
*out_imgs = *obj->external2;
obj->external2->bitmap_id = obj->external2->bitmap_pos_id = 0;
}
+ } else if (obj->type == OSDTYPE_NAV_HIGHLIGHT) {
+ if (obj->highlight_priv)
+ mp_nav_get_highlight(obj->highlight_priv, obj->vo_res, out_imgs);
} else {
osd_object_get_bitmaps(osd, obj, out_imgs);
}
diff --git a/sub/osd.h b/sub/osd.h
index 97bc2f067d..2a5bf71b05 100644
--- a/sub/osd.h
+++ b/sub/osd.h
@@ -84,6 +84,8 @@ enum mp_osdtype {
OSDTYPE_SUB,
OSDTYPE_SUB2,
+ OSDTYPE_NAV_HIGHLIGHT, // dvdnav fake highlights
+
OSDTYPE_PROGBAR,
OSDTYPE_OSD,
@@ -172,6 +174,8 @@ void osd_set_external(struct osd_state *osd, int res_x, int res_y, char *text);
void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs);
+void osd_set_nav_highlight(struct osd_state *osd, void *priv);
+
enum mp_osd_draw_flags {
OSD_DRAW_SUB_FILTER = (1 << 0),
OSD_DRAW_SUB_ONLY = (1 << 1),
@@ -220,4 +224,8 @@ void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function);
void osd_object_get_resolution(struct osd_state *osd, int obj,
int *out_w, int *out_h);
+// defined in player
+void mp_nav_get_highlight(void *priv, struct mp_osd_res res,
+ struct sub_bitmaps *out_imgs);
+
#endif /* MPLAYER_SUB_H */
diff --git a/wscript b/wscript
index 288db5443a..bb55ba411e 100644
--- a/wscript
+++ b/wscript
@@ -281,6 +281,19 @@ If you really mean to compile without libass support use --disable-libass."
'desc' : 'lirc',
'func': check_cc(header_name='lirc/lirc_client.h', lib='lirc_client'),
}, {
+ 'name': '--libbluray',
+ 'desc': 'Bluray support',
+ 'func': check_pkg_config('libbluray', '>= 0.3.0'),
+ }, {
+ 'name': '--dvdread',
+ 'desc': 'dvdread support',
+ 'func': check_pkg_config('dvdread', '>= 4.1.0'),
+ }, {
+ 'name': '--dvdnav',
+ 'desc': 'dvdnav support',
+ 'deps': [ 'dvdread' ],
+ 'func': check_pkg_config('dvdnav', '>= 4.2.0'),
+ }, {
'name': '--cdda',
'desc': 'cdda support (libcdio)',
'func': check_pkg_config('libcdio_paranoia'),
diff --git a/wscript_build.py b/wscript_build.py
index c4d1be440e..62446d1907 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -174,6 +174,7 @@ def build(ctx):
( "demux/codec_tags.c" ),
( "demux/demux.c" ),
( "demux/demux_cue.c" ),
+ ( "demux/demux_disc.c" ),
( "demux/demux_edl.c" ),
( "demux/demux_lavf.c" ),
( "demux/demux_libass.c", "libass"),
@@ -215,6 +216,7 @@ def build(ctx):
( "player/client.c" ),
( "player/command.c" ),
( "player/configfiles.c" ),
+ ( "player/discnav.c" ),
( "player/loadfile.c" ),
( "player/main.c" ),
( "player/misc.c" ),
@@ -242,8 +244,12 @@ def build(ctx):
( "stream/rar.c" ),
( "stream/stream.c" ),
( "stream/stream_avdevice.c" ),
+ ( "stream/stream_bluray.c", "libbluray" ),
( "stream/stream_cdda.c", "cdda" ),
( "stream/stream_dvb.c", "dvbin" ),
+ ( "stream/stream_dvd.c", "dvdread" ),
+ ( "stream/stream_dvd_common.c", "dvdread" ),
+ ( "stream/stream_dvdnav.c", "dvdnav" ),
( "stream/stream_edl.c" ),
( "stream/stream_file.c" ),
( "stream/stream_lavf.c" ),