From 23a7257cca5982fa44300825ea489ba95a7e4c17 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 15 Jul 2014 01:49:02 +0200 Subject: Revert "Remove DVD and Bluray support" This reverts commit 4b93210e0c244a65ef10a566abed2ad25ecaf9a1. *shrug* --- player/command.c | 133 ++++++++++++++++++++ player/configfiles.c | 6 + player/core.h | 9 ++ player/discnav.c | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++ player/loadfile.c | 7 ++ player/misc.c | 4 + player/playloop.c | 2 + 7 files changed, 495 insertions(+) create mode 100644 player/discnav.c (limited to 'player') 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 . + */ + +#include +#include +#include + +#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. -- cgit v1.2.3