| /* GStreamer |
| * Copyright (C) 2008-2009 Jan Schmidt <thaytan@noraisin.net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <gmodule.h> |
| #include <gst/gst.h> |
| #include <gst/glib-compat-private.h> |
| #include <gst/gst-i18n-plugin.h> |
| #include <gst/video/video.h> |
| #include <gst/video/navigation.h> |
| |
| #include "resindvdsrc.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (rsndvdsrc_debug); |
| #define GST_CAT_DEFAULT rsndvdsrc_debug |
| |
| #define DEFAULT_DEVICE "/dev/dvd" |
| #define DEFAULT_FASTSTART TRUE |
| #define DEFAULT_LANG "en" |
| |
| #define GST_FLOW_WOULD_BLOCK GST_FLOW_CUSTOM_SUCCESS |
| |
| #define CLOCK_BASE 9LL |
| #define CLOCK_FREQ CLOCK_BASE * 10000 |
| |
| #define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ |
| GST_MSECOND/10, CLOCK_BASE)) |
| #define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ |
| CLOCK_BASE, GST_MSECOND/10)) |
| |
| typedef enum |
| { |
| RSN_NAV_RESULT_NONE, |
| RSN_NAV_RESULT_HIGHLIGHT, |
| RSN_NAV_RESULT_BRANCH, |
| RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT |
| } RsnNavResult; |
| |
| typedef enum |
| { |
| RSN_BTN_NONE = 0x00, |
| RSN_BTN_LEFT = 0x01, |
| RSN_BTN_RIGHT = 0x02, |
| RSN_BTN_UP = 0x04, |
| RSN_BTN_DOWN = 0x08 |
| } RsnBtnMask; |
| |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0, |
| ARG_DEVICE, |
| ARG_FASTSTART |
| }; |
| |
| typedef struct |
| { |
| GstBuffer *buffer; |
| GstClockTime ts; |
| GstClockTime running_ts; |
| } RsnDvdPendingNav; |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| // GST_STATIC_CAPS ("video/mpeg,mpegversion=2,systemstream=true") |
| GST_STATIC_CAPS ("application/x-resin-dvd") |
| ); |
| |
| /* Private seek format for private flushing */ |
| static GstFormat rsndvd_format; |
| /* Title/chapter formats */ |
| static GstFormat title_format; |
| static GstFormat chapter_format; |
| |
| static void rsn_dvdsrc_register_extra (GType rsn_dvdsrc_type); |
| |
| #define rsn_dvdsrc_parent_class parent_class |
| G_DEFINE_TYPE_EXTENDED (resinDvdSrc, rsn_dvdsrc, GST_TYPE_BASE_SRC, |
| 0, rsn_dvdsrc_register_extra (g_define_type_id)); |
| |
| static gboolean read_vts_info (resinDvdSrc * src); |
| |
| static void rsn_dvdsrc_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void rsn_dvdsrc_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static void rsn_dvdsrc_finalize (GObject * object); |
| |
| static gboolean rsn_dvdsrc_start (GstBaseSrc * bsrc); |
| static gboolean rsn_dvdsrc_stop (GstBaseSrc * bsrc); |
| static gboolean rsn_dvdsrc_unlock (GstBaseSrc * bsrc); |
| static gboolean rsn_dvdsrc_unlock_stop (GstBaseSrc * bsrc); |
| |
| static gboolean rsn_dvdsrc_is_seekable (GstBaseSrc * bsrc); |
| static gboolean rsn_dvdsrc_prepare_seek (GstBaseSrc * bsrc, |
| GstEvent * event, GstSegment * segment); |
| static gboolean rsn_dvdsrc_do_seek (GstBaseSrc * bsrc, GstSegment * segment); |
| static GstStateChangeReturn rsn_dvdsrc_change_state (GstElement * element, |
| GstStateChange transition); |
| |
| static void rsn_dvdsrc_prepare_spu_stream_event (resinDvdSrc * src, |
| guint8 logical_stream, guint8 phys_stream, gboolean forced_only); |
| static void rsn_dvdsrc_prepare_audio_stream_event (resinDvdSrc * src, |
| guint8 logical_stream, guint8 phys_stream); |
| static gboolean rsn_dvdsrc_prepare_streamsinfo_event (resinDvdSrc * src); |
| static void rsn_dvdsrc_prepare_clut_change_event (resinDvdSrc * src, |
| const guint32 * clut); |
| static void rsn_dvdsrc_update_highlight (resinDvdSrc * src); |
| |
| static void rsn_dvdsrc_enqueue_nav_block (resinDvdSrc * src, |
| GstBuffer * nav_buf, GstClockTime ts); |
| static void rsn_dvdsrc_activate_nav_block (resinDvdSrc * src, |
| GstBuffer * nav_buf); |
| static void rsn_dvdsrc_clear_nav_blocks (resinDvdSrc * src); |
| static void rsn_dvdsrc_check_nav_blocks (resinDvdSrc * src); |
| static void rsn_dvdsrc_schedule_nav_cb (resinDvdSrc * src, |
| RsnDvdPendingNav * next_nav); |
| |
| static GstFlowReturn rsn_dvdsrc_create (GstBaseSrc * bsrc, guint64 offset, |
| guint length, GstBuffer ** buf); |
| static gboolean rsn_dvdsrc_src_event (GstBaseSrc * basesrc, GstEvent * event); |
| static gboolean rsn_dvdsrc_src_query (GstBaseSrc * basesrc, GstQuery * query); |
| |
| static GstClockTime ifotime_to_gsttime (dvd_time_t * ifo_time); |
| static void rsn_dvdsrc_send_commands_changed (resinDvdSrc * src); |
| |
| static GstClockTime |
| ifotime_to_gsttime (dvd_time_t * ifo_time) |
| { |
| GstClockTime ts; |
| guint frames; |
| |
| ts = 36000 * GST_SECOND * ((ifo_time->hour & 0xf0) >> 4); |
| ts += 3600 * GST_SECOND * (ifo_time->hour & 0x0f); |
| ts += 600 * GST_SECOND * ((ifo_time->minute & 0xf0) >> 4); |
| ts += 60 * GST_SECOND * (ifo_time->minute & 0x0f); |
| ts += 10 * GST_SECOND * ((ifo_time->second & 0xf0) >> 4); |
| ts += GST_SECOND * (ifo_time->second & 0x0f); |
| |
| frames = ((ifo_time->frame_u >> 4) & 0x3) * 10; |
| frames += (ifo_time->frame_u & 0xf); |
| |
| if (ifo_time->frame_u & 0x80) |
| ts += GST_SECOND * frames / 30; |
| else |
| ts += GST_SECOND * frames / 25; |
| |
| return ts; |
| } |
| |
| static void |
| rsn_dvdsrc_register_extra (GType rsn_dvdsrc_type) |
| { |
| GST_DEBUG_CATEGORY_INIT (rsndvdsrc_debug, "rsndvdsrc", 0, |
| "Resin DVD source element based on libdvdnav"); |
| |
| rsndvd_format = gst_format_register ("rsndvdsrc-internal", |
| "private Resin DVD src format"); |
| |
| title_format = gst_format_register ("title", "DVD title format"); |
| chapter_format = gst_format_register ("chapter", "DVD chapter format"); |
| } |
| |
| static void |
| rsn_dvdsrc_class_init (resinDvdSrcClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| GstBaseSrcClass *gstbasesrc_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| gstbasesrc_class = GST_BASE_SRC_CLASS (klass); |
| |
| gobject_class->finalize = rsn_dvdsrc_finalize; |
| gobject_class->set_property = rsn_dvdsrc_set_property; |
| gobject_class->get_property = rsn_dvdsrc_get_property; |
| |
| gstelement_class->change_state = rsn_dvdsrc_change_state; |
| |
| gstbasesrc_class->start = GST_DEBUG_FUNCPTR (rsn_dvdsrc_start); |
| gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (rsn_dvdsrc_stop); |
| gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (rsn_dvdsrc_unlock); |
| gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (rsn_dvdsrc_unlock_stop); |
| gstbasesrc_class->event = GST_DEBUG_FUNCPTR (rsn_dvdsrc_src_event); |
| gstbasesrc_class->query = GST_DEBUG_FUNCPTR (rsn_dvdsrc_src_query); |
| gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (rsn_dvdsrc_is_seekable); |
| gstbasesrc_class->prepare_seek_segment = |
| GST_DEBUG_FUNCPTR (rsn_dvdsrc_prepare_seek); |
| gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (rsn_dvdsrc_do_seek); |
| |
| gstbasesrc_class->create = GST_DEBUG_FUNCPTR (rsn_dvdsrc_create); |
| |
| g_object_class_install_property (gobject_class, ARG_DEVICE, |
| g_param_spec_string ("device", "Device", "DVD device location", |
| NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, ARG_FASTSTART, |
| g_param_spec_boolean ("fast-start", "Fast start", |
| "Skip straight to the DVD menu on start", DEFAULT_FASTSTART, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&src_factory)); |
| gst_element_class_set_details_simple (gstelement_class, "Resin DVD Src", |
| "Source/DVD", "DVD source element", "Jan Schmidt <thaytan@noraisin.net>"); |
| } |
| |
| static void |
| rsn_dvdsrc_init (resinDvdSrc * rsndvdsrc) |
| { |
| const gchar *envvar; |
| |
| envvar = g_getenv ("DVDFASTSTART"); |
| if (envvar) |
| rsndvdsrc->faststart = (strcmp (envvar, "0") && strcmp (envvar, "no")); |
| else |
| rsndvdsrc->faststart = DEFAULT_FASTSTART; |
| |
| rsndvdsrc->device = g_strdup (DEFAULT_DEVICE); |
| rsndvdsrc->dvd_lock = g_mutex_new (); |
| rsndvdsrc->branch_lock = g_mutex_new (); |
| rsndvdsrc->branching = FALSE; |
| rsndvdsrc->still_cond = g_cond_new (); |
| |
| gst_base_src_set_format (GST_BASE_SRC (rsndvdsrc), GST_FORMAT_TIME); |
| } |
| |
| static void |
| rsn_dvdsrc_finalize (GObject * object) |
| { |
| resinDvdSrc *src = RESINDVDSRC (object); |
| |
| g_mutex_free (src->dvd_lock); |
| g_mutex_free (src->branch_lock); |
| g_cond_free (src->still_cond); |
| g_free (src->device); |
| |
| gst_buffer_replace (&src->alloc_buf, NULL); |
| gst_buffer_replace (&src->next_buf, NULL); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| rsn_dvdsrc_unlock (GstBaseSrc * bsrc) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| |
| g_mutex_lock (src->branch_lock); |
| src->branching = TRUE; |
| g_cond_broadcast (src->still_cond); |
| g_mutex_unlock (src->branch_lock); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_unlock_stop (GstBaseSrc * bsrc) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| |
| g_mutex_lock (src->branch_lock); |
| src->branching = FALSE; |
| g_mutex_unlock (src->branch_lock); |
| |
| return TRUE; |
| } |
| |
| static void |
| rsn_dvdsrc_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| resinDvdSrc *src = RESINDVDSRC (object); |
| |
| switch (prop_id) { |
| case ARG_DEVICE: |
| GST_OBJECT_LOCK (src); |
| g_free (src->device); |
| if (g_value_get_string (value) == NULL) |
| src->device = g_strdup (DEFAULT_DEVICE); |
| else |
| src->device = g_value_dup_string (value); |
| GST_OBJECT_UNLOCK (src); |
| break; |
| case ARG_FASTSTART: |
| GST_OBJECT_LOCK (src); |
| src->faststart = g_value_get_boolean (value); |
| GST_OBJECT_UNLOCK (src); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| rsn_dvdsrc_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| resinDvdSrc *src = RESINDVDSRC (object); |
| |
| switch (prop_id) { |
| case ARG_DEVICE: |
| GST_OBJECT_LOCK (src); |
| g_value_set_string (value, src->device); |
| GST_OBJECT_UNLOCK (src); |
| break; |
| case ARG_FASTSTART: |
| GST_OBJECT_LOCK (src); |
| g_value_set_boolean (value, src->faststart); |
| GST_OBJECT_UNLOCK (src); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| rsn_dvdsrc_start (GstBaseSrc * bsrc) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| const gchar *const *langs, *const *cur; |
| gchar lang[8]; |
| |
| g_mutex_lock (src->dvd_lock); |
| if (!read_vts_info (src)) { |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, |
| (_("Could not read title information for DVD.")), GST_ERROR_SYSTEM); |
| goto fail; |
| } |
| |
| if (dvdnav_open (&src->dvdnav, src->device) != DVDNAV_STATUS_OK) { |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), |
| (_("Failed to open DVD device '%s'."), src->device)); |
| goto fail; |
| } |
| |
| if (dvdnav_set_PGC_positioning_flag (src->dvdnav, 1) != DVDNAV_STATUS_OK) { |
| GST_ELEMENT_ERROR (src, LIBRARY, FAILED, |
| (_("Failed to set PGC based seeking.")), GST_ERROR_SYSTEM); |
| goto fail; |
| } |
| |
| /* Attempt to set DVD menu, audio and spu languages */ |
| langs = g_get_language_names (); |
| strncpy (lang, DEFAULT_LANG, 8); |
| for (cur = langs; *cur != NULL; cur++) { |
| /* Look for a 2 char iso-639 lang */ |
| if (strlen (*cur) == 2) { |
| strncpy (lang, *cur, 8); |
| break; |
| } |
| } |
| /* Set the user's preferred language */ |
| dvdnav_menu_language_select (src->dvdnav, lang); |
| dvdnav_audio_language_select (src->dvdnav, lang); |
| dvdnav_spu_language_select (src->dvdnav, lang); |
| |
| if (src->faststart) { |
| if (dvdnav_title_play (src->dvdnav, 1) != DVDNAV_STATUS_OK || |
| (dvdnav_menu_call (src->dvdnav, DVD_MENU_Title) != DVDNAV_STATUS_OK && |
| dvdnav_menu_call (src->dvdnav, |
| DVD_MENU_Root) != DVDNAV_STATUS_OK)) { |
| /* Fast start failed. Do normal start */ |
| dvdnav_reset (src->dvdnav); |
| } |
| } |
| |
| dvdnav_get_title_string (src->dvdnav, &src->disc_name); |
| |
| src->first_seek = TRUE; |
| src->running = TRUE; |
| src->branching = FALSE; |
| src->discont = TRUE; |
| src->need_segment = TRUE; |
| src->need_tag_update = TRUE; |
| |
| src->cur_position = GST_CLOCK_TIME_NONE; |
| src->pgc_duration = GST_CLOCK_TIME_NONE; |
| src->cur_start_ts = GST_CLOCK_TIME_NONE; |
| src->cur_end_ts = GST_CLOCK_TIME_NONE; |
| src->cur_vobu_base_ts = GST_CLOCK_TIME_NONE; |
| |
| src->vts_n = 0; |
| src->in_menu = FALSE; |
| src->title_n = -1; |
| src->part_n = -1; |
| |
| src->active_button = -1; |
| src->cur_btn_mask = RSN_BTN_NONE; |
| |
| src->angles_changed = FALSE; |
| src->n_angles = 0; |
| src->cur_angle = 0; |
| |
| src->commands_changed = TRUE; |
| |
| src->cur_spu_phys_stream = -1; |
| src->cur_spu_forced_only = FALSE; |
| memset (src->cur_clut, 0, sizeof (guint32) * 16); |
| src->cur_audio_phys_stream = -1; |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| return TRUE; |
| |
| fail: |
| if (src->dvdnav) { |
| dvdnav_close (src->dvdnav); |
| src->dvdnav = NULL; |
| } |
| g_mutex_unlock (src->dvd_lock); |
| return FALSE; |
| } |
| |
| /* Use libdvdread to read and cache info from the IFO file about |
| * streams in each VTS */ |
| static gboolean |
| read_vts_info (resinDvdSrc * src) |
| { |
| gint n_vts; |
| |
| if (src->vts_attrs) { |
| g_array_free (src->vts_attrs, TRUE); |
| src->vts_attrs = NULL; |
| } |
| |
| if (src->dvdread) |
| DVDClose (src->dvdread); |
| |
| src->dvdread = DVDOpen (src->device); |
| if (src->dvdread == NULL) |
| return FALSE; |
| |
| if (!(src->vmg_file = ifoOpen (src->dvdread, 0))) { |
| GST_ERROR ("Can't open VMG ifo"); |
| return FALSE; |
| } |
| if (!src->vmg_file->vts_atrt) { |
| GST_INFO ("No vts_atrt - odd, but apparently OK"); |
| g_array_set_size (src->vts_attrs, 0); |
| src->vts_attrs = NULL; |
| return TRUE; |
| } |
| n_vts = src->vmg_file->vts_atrt->nr_of_vtss; |
| memcpy (&src->vmgm_attr, src->vmg_file->vmgi_mat, sizeof (vmgi_mat_t)); |
| |
| GST_DEBUG ("Reading IFO info for %d VTSs", n_vts); |
| src->vts_attrs = |
| g_array_sized_new (FALSE, TRUE, sizeof (vtsi_mat_t), n_vts + 1); |
| if (!src->vts_attrs) |
| return FALSE; |
| g_array_set_size (src->vts_attrs, n_vts + 1); |
| |
| return TRUE; |
| } |
| |
| static vtsi_mat_t * |
| get_vts_attr (resinDvdSrc * src, gint n) |
| { |
| vtsi_mat_t *vts_attr; |
| |
| if (src->vts_attrs == NULL || n >= src->vts_attrs->len) { |
| if (src->vts_attrs) |
| GST_ERROR_OBJECT (src, "No stream info for VTS %d (have %d)", n, |
| src->vts_attrs->len); |
| else |
| GST_ERROR_OBJECT (src, "No stream info"); |
| return NULL; |
| } |
| |
| vts_attr = &g_array_index (src->vts_attrs, vtsi_mat_t, src->vts_n); |
| |
| /* Check if we have read this VTS ifo yet */ |
| if (vts_attr->vtsm_vobs == 0) { |
| ifo_handle_t *ifo = ifoOpen (src->dvdread, n); |
| |
| if (!ifo) { |
| GST_ERROR ("Can't open VTS %d", n); |
| return NULL; |
| } |
| |
| GST_DEBUG ("VTS %d, Menu has %d audio %d subpictures. " |
| "Title has %d and %d", n, |
| ifo->vtsi_mat->nr_of_vtsm_audio_streams, |
| ifo->vtsi_mat->nr_of_vtsm_subp_streams, |
| ifo->vtsi_mat->nr_of_vts_audio_streams, |
| ifo->vtsi_mat->nr_of_vts_subp_streams); |
| |
| memcpy (&g_array_index (src->vts_attrs, vtsi_mat_t, n), |
| ifo->vtsi_mat, sizeof (vtsi_mat_t)); |
| |
| ifoClose (ifo); |
| }; |
| |
| return vts_attr; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_stop (GstBaseSrc * bsrc) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| gboolean ret = TRUE; |
| GstMessage *mouse_over_msg = NULL; |
| |
| g_mutex_lock (src->dvd_lock); |
| |
| if (src->nav_clock_id) { |
| gst_clock_id_unschedule (src->nav_clock_id); |
| gst_clock_id_unref (src->nav_clock_id); |
| src->nav_clock_id = NULL; |
| } |
| rsn_dvdsrc_clear_nav_blocks (src); |
| src->have_pci = FALSE; |
| |
| if (src->was_mouse_over) { |
| mouse_over_msg = |
| gst_navigation_message_new_mouse_over ((GstObject *) src, FALSE); |
| src->was_mouse_over = FALSE; |
| } |
| |
| /* Clear any allocated output buffer */ |
| gst_buffer_replace (&src->alloc_buf, NULL); |
| gst_buffer_replace (&src->next_buf, NULL); |
| src->running = FALSE; |
| |
| if (src->streams_event) { |
| gst_event_unref (src->streams_event); |
| src->streams_event = NULL; |
| } |
| if (src->clut_event) { |
| gst_event_unref (src->clut_event); |
| src->clut_event = NULL; |
| } |
| if (src->spu_select_event) { |
| gst_event_unref (src->spu_select_event); |
| src->spu_select_event = NULL; |
| } |
| if (src->audio_select_event) { |
| gst_event_unref (src->audio_select_event); |
| src->audio_select_event = NULL; |
| } |
| if (src->highlight_event) { |
| gst_event_unref (src->highlight_event); |
| src->highlight_event = NULL; |
| } |
| |
| src->disc_name = NULL; |
| |
| if (src->dvdnav) { |
| if (dvdnav_close (src->dvdnav) != DVDNAV_STATUS_OK) { |
| GST_ELEMENT_ERROR (src, RESOURCE, CLOSE, (NULL), |
| ("dvdnav_close failed: %s", dvdnav_err_to_string (src->dvdnav))); |
| ret = FALSE; |
| } |
| src->dvdnav = NULL; |
| } |
| |
| if (src->vmg_file) { |
| ifoClose (src->vmg_file); |
| src->vmg_file = NULL; |
| } |
| if (src->vts_file) { |
| ifoClose (src->vts_file); |
| src->vts_file = NULL; |
| } |
| if (src->dvdread) { |
| DVDClose (src->dvdread); |
| src->dvdread = NULL; |
| } |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| if (mouse_over_msg) |
| gst_element_post_message (GST_ELEMENT_CAST (src), mouse_over_msg); |
| return ret; |
| } |
| |
| /* handle still events. Call with dvd_lock */ |
| static gboolean |
| rsn_dvdsrc_do_still (resinDvdSrc * src, int duration) |
| { |
| GstEvent *still_event; |
| GstEvent *hl_event; |
| gboolean cmds_changed; |
| GstEvent *seg_event; |
| GstSegment *segment = &(GST_BASE_SRC (src)->segment); |
| |
| if (src->in_still_state == FALSE) { |
| GST_DEBUG_OBJECT (src, "**** Start STILL FRAME. Duration %d ****", |
| duration); |
| |
| if (duration == 255) |
| src->still_time_remaining = GST_CLOCK_TIME_NONE; |
| else |
| src->still_time_remaining = GST_SECOND * duration; |
| |
| /* Send a close-segment event, and a still-frame start |
| * event, then sleep */ |
| still_event = gst_video_event_new_still_frame (TRUE); |
| |
| segment->stop = segment->position = src->cur_end_ts; |
| |
| seg_event = gst_event_new_segment (segment); |
| |
| /* Grab any pending highlight event to send too */ |
| hl_event = src->highlight_event; |
| src->highlight_event = NULL; |
| cmds_changed = src->commands_changed; |
| src->commands_changed = FALSE; |
| |
| /* Now, send the events. We need to drop the dvd lock while doing so, |
| * and then check after if we got flushed */ |
| g_mutex_unlock (src->dvd_lock); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), still_event); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), seg_event); |
| if (hl_event) { |
| GST_LOG_OBJECT (src, "Sending highlight event before still"); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); |
| } |
| if (cmds_changed) |
| rsn_dvdsrc_send_commands_changed (src); |
| |
| g_mutex_lock (src->dvd_lock); |
| |
| g_mutex_lock (src->branch_lock); |
| |
| src->in_still_state = TRUE; |
| } else { |
| GST_DEBUG_OBJECT (src, |
| "Re-entering still wait with %" GST_TIME_FORMAT " remaining", |
| GST_TIME_ARGS (src->still_time_remaining)); |
| g_mutex_lock (src->branch_lock); |
| } |
| |
| if (src->branching) { |
| GST_INFO_OBJECT (src, "Branching - aborting still"); |
| g_mutex_unlock (src->branch_lock); |
| return TRUE; |
| } |
| |
| if (duration == 255) { |
| /* |
| * The only way to get woken from this still is by a flushing |
| * seek or a user action. Either one will clear the still, so |
| * don't skip it |
| */ |
| src->need_segment = TRUE; |
| |
| g_mutex_unlock (src->dvd_lock); |
| GST_LOG_OBJECT (src, "Entering cond_wait still"); |
| g_cond_wait (src->still_cond, src->branch_lock); |
| GST_LOG_OBJECT (src, "cond_wait still over, branching = %d", |
| src->branching); |
| |
| if (src->branching) { |
| g_mutex_unlock (src->branch_lock); |
| g_mutex_lock (src->dvd_lock); |
| return TRUE; |
| } |
| src->in_still_state = FALSE; |
| |
| g_mutex_unlock (src->branch_lock); |
| g_mutex_lock (src->dvd_lock); |
| } else { |
| GTimeVal end_time; |
| gboolean was_signalled; |
| |
| if (src->still_time_remaining > 0) { |
| g_get_current_time (&end_time); |
| g_time_val_add (&end_time, src->still_time_remaining / GST_USECOND); |
| |
| /* Implement timed stills by sleeping, possibly |
| * in multiple steps if we get paused/unpaused */ |
| g_mutex_unlock (src->dvd_lock); |
| GST_LOG_OBJECT (src, "cond_timed_wait still for %d sec", duration); |
| was_signalled = |
| g_cond_timed_wait (src->still_cond, src->branch_lock, &end_time); |
| was_signalled |= src->branching; |
| |
| g_mutex_unlock (src->branch_lock); |
| g_mutex_lock (src->dvd_lock); |
| |
| if (was_signalled) { |
| /* Signalled - must be flushing */ |
| GTimeVal cur_time; |
| GstClockTimeDiff remain; |
| |
| g_get_current_time (&cur_time); |
| remain = |
| (end_time.tv_sec - cur_time.tv_sec) * GST_SECOND + |
| (end_time.tv_usec - cur_time.tv_usec) * GST_USECOND; |
| if (remain < 0) |
| src->still_time_remaining = 0; |
| else |
| src->still_time_remaining = remain; |
| |
| GST_LOG_OBJECT (src, |
| "cond_timed_wait still aborted by signal with %" GST_TIME_FORMAT |
| " remaining. branching = %d", |
| GST_TIME_ARGS (src->still_time_remaining), src->branching); |
| |
| return TRUE; |
| } |
| } |
| |
| /* Else timed out, end the still */ |
| GST_DEBUG_OBJECT (src, |
| "Timed still of %d secs over, calling dvdnav_still_skip", duration); |
| |
| if (dvdnav_still_skip (src->dvdnav) != DVDNAV_STATUS_OK) { |
| return FALSE; |
| } |
| |
| /* Tell downstream the still is over. |
| * We only do this if the still isn't interrupted: */ |
| still_event = gst_video_event_new_still_frame (FALSE); |
| |
| /* If the segment was too short in a timed still, it may need extending */ |
| if (segment->position < segment->start + GST_SECOND * duration) |
| segment->position = segment->start + (GST_SECOND * duration); |
| |
| g_mutex_unlock (src->dvd_lock); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), still_event); |
| g_mutex_lock (src->dvd_lock); |
| } |
| |
| return TRUE; |
| } |
| |
| static pgc_t * |
| get_current_pgc (resinDvdSrc * src) |
| { |
| gint title, part, pgc_n; |
| gint32 vts_ttn; |
| pgc_t *pgc; |
| |
| if (dvdnav_is_domain_fp (src->dvdnav)) { |
| return src->vmg_file->first_play_pgc; |
| } |
| |
| if (src->vts_n == 0 || src->in_menu) { |
| /* FIXME: look up current menu PGC */ |
| return NULL; |
| } |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, &part) != |
| DVDNAV_STATUS_OK) |
| return NULL; |
| |
| /* To find the right PGC, we need the title number within this VTS (vts_ttn) |
| * from the VMG tt_srpt table... */ |
| if (title < 1 || title > src->vmg_file->tt_srpt->nr_of_srpts) |
| return NULL; |
| |
| /* We must be in the correct VTS for any of this to succeed... */ |
| if (src->vts_n != src->vmg_file->tt_srpt->title[title - 1].title_set_nr) |
| return NULL; |
| |
| /* We must also be in the VTS domain to use the tmap table */ |
| if (src->vts_n == 0) |
| return NULL; |
| |
| vts_ttn = src->vmg_file->tt_srpt->title[title - 1].vts_ttn; |
| |
| if (vts_ttn < 1 || vts_ttn > src->vts_file->vts_ptt_srpt->nr_of_srpts) |
| return NULL; |
| |
| if (src->vts_file->vts_ptt_srpt->title[vts_ttn - 1].nr_of_ptts == 0) |
| return NULL; |
| |
| pgc_n = src->vts_file->vts_ptt_srpt->title[vts_ttn - 1].ptt[0].pgcn; |
| if (pgc_n > src->vts_file->vts_pgcit->nr_of_pgci_srp) |
| return NULL; |
| |
| pgc = src->vts_file->vts_pgcit->pgci_srp[pgc_n - 1].pgc; |
| |
| return pgc; |
| } |
| |
| static GstTagList * |
| update_title_info (resinDvdSrc * src, gboolean force) |
| { |
| gint n_angles, cur_agl; |
| gint title_n, part_n; |
| |
| if (dvdnav_get_angle_info (src->dvdnav, &cur_agl, |
| &n_angles) == DVDNAV_STATUS_OK && src->n_angles != n_angles) { |
| /* Make sure we send an angles-changed message soon */ |
| src->angles_changed = TRUE; |
| } |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title_n, |
| &part_n) != DVDNAV_STATUS_OK) { |
| if (!src->in_menu) |
| return NULL; /* Can't update now */ |
| /* Must be in the first play sequence */ |
| title_n = -1; |
| part_n = 0; |
| } |
| |
| if (title_n != src->title_n || part_n != src->part_n || |
| src->n_angles != n_angles || src->cur_angle != cur_agl || force) { |
| gchar *title_str = NULL; |
| |
| src->title_n = title_n; |
| src->part_n = part_n; |
| src->n_angles = n_angles; |
| src->cur_angle = cur_agl; |
| |
| if (title_n == 0) { |
| /* In a menu */ |
| title_str = g_strdup ("DVD Menu"); |
| } else if (title_n > 0) { |
| /* In a title */ |
| if (n_angles > 1) { |
| title_str = g_strdup_printf ("Title %i, Chapter %i, Angle %i of %i", |
| title_n, part_n, cur_agl, n_angles); |
| |
| } else { |
| title_str = g_strdup_printf ("Title %i, Chapter %i", title_n, part_n); |
| } |
| } |
| |
| if (src->disc_name && src->disc_name[0]) { |
| /* We have a name for this disc, publish it */ |
| if (title_str) { |
| gchar *new_title_str = |
| g_strdup_printf ("%s, %s", title_str, src->disc_name); |
| g_free (title_str); |
| title_str = new_title_str; |
| } else { |
| title_str = g_strdup (src->disc_name); |
| } |
| } |
| if (title_str) { |
| GstTagList *tags = gst_tag_list_new (GST_TAG_TITLE, title_str, NULL); |
| g_free (title_str); |
| return tags; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* we don't cache the result on purpose */ |
| static gboolean |
| rsn_descrambler_available (void) |
| { |
| GModule *module; |
| gpointer sym; |
| gsize res; |
| |
| module = g_module_open ("libdvdcss", 0); |
| if (module != NULL) { |
| res = g_module_symbol (module, "dvdcss_open", &sym); |
| g_module_close (module); |
| } else { |
| res = FALSE; |
| } |
| |
| return res; |
| } |
| |
| static GstFlowReturn |
| rsn_dvdsrc_step (resinDvdSrc * src, gboolean have_dvd_lock) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| dvdnav_status_t dvdnav_ret; |
| gint event, len; |
| GstMapInfo mmap; |
| |
| /* Allocate an output buffer if there isn't a pending one */ |
| if (src->alloc_buf == NULL) |
| src->alloc_buf = gst_buffer_new_allocate (NULL, DVD_VIDEO_LB_LEN, NULL); |
| |
| gst_buffer_map (src->alloc_buf, &mmap, GST_MAP_WRITE); |
| |
| len = DVD_VIDEO_LB_LEN; |
| |
| dvdnav_ret = dvdnav_get_next_block (src->dvdnav, mmap.data, &event, &len); |
| if (dvdnav_ret != DVDNAV_STATUS_OK) |
| goto read_error; |
| g_mutex_lock (src->branch_lock); |
| if (src->branching) |
| goto branching; |
| g_mutex_unlock (src->branch_lock); |
| |
| switch (event) { |
| case DVDNAV_BLOCK_OK: |
| /* Data block that needs outputting */ |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| src->next_buf = src->alloc_buf; |
| src->alloc_buf = NULL; |
| |
| src->next_is_nav_block = FALSE; |
| src->next_nav_ts = GST_CLOCK_TIME_NONE; |
| src->in_still_state = FALSE; |
| break; |
| case DVDNAV_NAV_PACKET: |
| { |
| pci_t *pci = dvdnav_get_current_nav_pci (src->dvdnav); |
| GstClockTime new_start_ptm = MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm); |
| GstClockTime new_end_ptm = MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_e_ptm); |
| GstClockTimeDiff new_base_time = ifotime_to_gsttime (&pci->pci_gi.e_eltm); |
| gboolean discont = FALSE; |
| |
| src->in_still_state = FALSE; |
| |
| if (new_start_ptm != src->cur_end_ts) { |
| /* Hack because libdvdnav seems to lose a NAV packet during |
| * angle block changes, triggering a false discont */ |
| GstClockTimeDiff diff = GST_CLOCK_DIFF (src->cur_end_ts, new_start_ptm); |
| if (src->cur_end_ts == GST_CLOCK_TIME_NONE || diff > 2 * GST_SECOND || |
| diff < 0) { |
| discont = TRUE; |
| GST_DEBUG_OBJECT (src, "Discont NAV packet start TS %" GST_TIME_FORMAT |
| " != end TS %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (new_start_ptm), GST_TIME_ARGS (src->cur_end_ts)); |
| } |
| } |
| |
| GST_LOG_OBJECT (src, "NAV packet start TS %" GST_TIME_FORMAT |
| " end TS %" GST_TIME_FORMAT " base %" G_GINT64_FORMAT " %s", |
| GST_TIME_ARGS (new_start_ptm), GST_TIME_ARGS (new_end_ptm), |
| new_base_time, discont ? "discont" : ""); |
| |
| #if 0 |
| g_print ("NAV packet start TS %" GST_TIME_FORMAT |
| " end TS %" GST_TIME_FORMAT " base %" G_GINT64_FORMAT " %s\n", |
| GST_TIME_ARGS (new_start_ptm), GST_TIME_ARGS (new_end_ptm), |
| new_base_time, discont ? "discont" : ""); |
| #endif |
| |
| if (discont) { |
| GST_DEBUG_OBJECT (src, |
| "NAV packet discont: cur_end_ts %" GST_TIME_FORMAT " != " |
| " vobu_start_ptm: %" GST_TIME_FORMAT " base %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (src->cur_end_ts), |
| GST_TIME_ARGS (new_start_ptm), GST_TIME_ARGS (new_base_time)); |
| src->need_segment = TRUE; |
| } |
| |
| src->cur_start_ts = new_start_ptm; |
| src->cur_end_ts = new_end_ptm; |
| src->cur_vobu_base_ts = new_base_time; |
| |
| /* NAV packet is also a data block that needs sending */ |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| src->next_buf = src->alloc_buf; |
| src->alloc_buf = NULL; |
| |
| if (!src->have_pci || pci->hli.hl_gi.hli_ss != 2) { |
| /* Store the nav packet for activation at the right moment |
| * if we don't have a packet yet or the info has changed (hli_ss != 2) |
| */ |
| if (pci->hli.hl_gi.hli_s_ptm != 0) |
| new_start_ptm = MPEGTIME_TO_GSTTIME (pci->hli.hl_gi.hli_s_ptm); |
| |
| src->next_is_nav_block = TRUE; |
| src->next_nav_ts = new_start_ptm; |
| GST_LOG_OBJECT (src, "Storing NAV pack with TS %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (src->next_nav_ts)); |
| } else { |
| src->next_is_nav_block = FALSE; |
| src->next_nav_ts = GST_CLOCK_TIME_NONE; |
| } |
| break; |
| } |
| case DVDNAV_STOP: |
| /* End of the disc. EOS */ |
| dvdnav_reset (src->dvdnav); |
| ret = GST_FLOW_EOS; |
| break; |
| case DVDNAV_STILL_FRAME: |
| { |
| dvdnav_still_event_t *info = (dvdnav_still_event_t *) mmap.data; |
| |
| if (!have_dvd_lock) { |
| /* At a still frame but can't block, handle it later */ |
| return GST_FLOW_WOULD_BLOCK; |
| } |
| |
| if (!rsn_dvdsrc_do_still (src, info->length)) |
| goto internal_error; |
| |
| g_mutex_lock (src->branch_lock); |
| if (src->branching) |
| goto branching; |
| g_mutex_unlock (src->branch_lock); |
| break; |
| } |
| case DVDNAV_WAIT: |
| /* Drain out the queues so that the info on the screen matches |
| * the VM state */ |
| if (have_dvd_lock) { |
| /* FIXME: Drain out the queues, by sleeping on the clock or something */ |
| GST_LOG_OBJECT (src, "****** FIXME: WAIT for queues to drain *****"); |
| } |
| if (dvdnav_wait_skip (src->dvdnav) != DVDNAV_STATUS_OK) |
| goto internal_error; |
| break; |
| case DVDNAV_CELL_CHANGE:{ |
| dvdnav_cell_change_event_t *event = |
| (dvdnav_cell_change_event_t *) mmap.data; |
| GstMessage *message; |
| |
| src->pgc_duration = MPEGTIME_TO_GSTTIME (event->pgc_length); |
| /* event->cell_start has the wrong time - it doesn't handle |
| * multi-angle correctly (as of libdvdnav 4.1.3). The current_time() |
| * calculates it correctly. */ |
| src->cur_position = |
| MPEGTIME_TO_GSTTIME (dvdnav_get_current_time (src->dvdnav)); |
| |
| GST_DEBUG_OBJECT (src, |
| "CELL change dur now %" GST_TIME_FORMAT " position now %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (src->pgc_duration), |
| GST_TIME_ARGS (src->cur_position)); |
| |
| message = gst_message_new_duration_changed (GST_OBJECT (src)); |
| gst_element_post_message (GST_ELEMENT (src), message); |
| |
| rsn_dvdsrc_prepare_streamsinfo_event (src); |
| src->need_tag_update = TRUE; |
| |
| break; |
| } |
| case DVDNAV_SPU_CLUT_CHANGE: |
| rsn_dvdsrc_prepare_clut_change_event (src, (const guint32 *) mmap.data); |
| break; |
| case DVDNAV_VTS_CHANGE:{ |
| dvdnav_vts_change_event_t *event = |
| (dvdnav_vts_change_event_t *) mmap.data; |
| |
| if (dvdnav_is_domain_vmgm (src->dvdnav)) { |
| src->vts_n = 0; |
| } else { |
| src->vts_n = event->new_vtsN; |
| if (src->vts_file) { |
| ifoClose (src->vts_file); |
| src->vts_file = NULL; |
| } |
| src->vts_file = ifoOpen (src->dvdread, src->vts_n); |
| } |
| |
| src->in_menu = !dvdnav_is_domain_vts (src->dvdnav); |
| |
| break; |
| } |
| case DVDNAV_AUDIO_STREAM_CHANGE:{ |
| dvdnav_audio_stream_change_event_t *event = |
| (dvdnav_audio_stream_change_event_t *) mmap.data; |
| |
| rsn_dvdsrc_prepare_audio_stream_event (src, |
| event->logical, event->physical); |
| GST_DEBUG_OBJECT (src, " physical: %d", event->physical); |
| GST_DEBUG_OBJECT (src, " logical: %d", event->logical); |
| |
| break; |
| } |
| case DVDNAV_SPU_STREAM_CHANGE:{ |
| dvdnav_spu_stream_change_event_t *event = |
| (dvdnav_spu_stream_change_event_t *) mmap.data; |
| gint phys_track = event->physical_wide & 0x1f; |
| gboolean forced_only = (event->physical_wide & 0x80) ? TRUE : FALSE; |
| |
| rsn_dvdsrc_prepare_spu_stream_event (src, event->logical, phys_track, |
| forced_only); |
| |
| GST_DEBUG_OBJECT (src, " physical_wide: %x", event->physical_wide); |
| GST_DEBUG_OBJECT (src, " physical_letterbox: %x", |
| event->physical_letterbox); |
| GST_DEBUG_OBJECT (src, " physical_pan_scan: %x", |
| event->physical_pan_scan); |
| GST_DEBUG_OBJECT (src, " logical: %x", event->logical); |
| break; |
| } |
| case DVDNAV_HIGHLIGHT:{ |
| GST_DEBUG_OBJECT (src, "highlight change event, button %d", |
| ((dvdnav_highlight_event_t *) mmap.data)->buttonN); |
| rsn_dvdsrc_update_highlight (src); |
| break; |
| } |
| case DVDNAV_HOP_CHANNEL: |
| GST_DEBUG_OBJECT (src, "Channel hop - User action"); |
| src->need_segment = TRUE; |
| break; |
| case DVDNAV_NOP: |
| break; |
| default: |
| GST_WARNING_OBJECT (src, "Unknown dvdnav event %d", event); |
| break; |
| } |
| if (src->alloc_buf) { |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| } |
| |
| if (src->highlight_event && have_dvd_lock && src->in_playing) { |
| GstEvent *hl_event = src->highlight_event; |
| |
| src->highlight_event = NULL; |
| g_mutex_unlock (src->dvd_lock); |
| GST_DEBUG_OBJECT (src, "Sending highlight event - button %d", |
| src->active_button); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); |
| g_mutex_lock (src->dvd_lock); |
| } |
| |
| return ret; |
| |
| /* ERRORS */ |
| read_error: |
| { |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| if (!rsn_descrambler_available ()) { |
| GST_ELEMENT_ERROR (src, RESOURCE, READ, |
| (_("Could not read DVD. This may be because the DVD is encrypted " |
| "and a DVD decryption library is not installed.")), |
| ("Failed to read next DVD block. Error: %s", |
| dvdnav_err_to_string (src->dvdnav))); |
| } else { |
| GST_ELEMENT_ERROR (src, RESOURCE, READ, (_("Could not read DVD.")), |
| ("Failed to read next DVD block. Error: %s", |
| dvdnav_err_to_string (src->dvdnav))); |
| } |
| return GST_FLOW_ERROR; |
| } |
| internal_error: |
| { |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| GST_ELEMENT_ERROR (src, RESOURCE, READ, (_("Could not read DVD.")), |
| ("Internal error processing DVD commands. Error: %s", |
| dvdnav_err_to_string (src->dvdnav))); |
| return GST_FLOW_ERROR; |
| } |
| branching: |
| { |
| g_mutex_unlock (src->branch_lock); |
| gst_buffer_unmap (src->alloc_buf, &mmap); |
| return GST_FLOW_FLUSHING; |
| } |
| } |
| |
| /* Send app a bus message that the available commands have changed */ |
| static void |
| rsn_dvdsrc_send_commands_changed (resinDvdSrc * src) |
| { |
| GstMessage *cmds_msg = |
| gst_navigation_message_new_commands_changed (GST_OBJECT_CAST (src)); |
| gst_element_post_message (GST_ELEMENT_CAST (src), cmds_msg); |
| } |
| |
| static gboolean |
| rsn_dvdsrc_handle_cmds_query (resinDvdSrc * src, GstQuery * query) |
| { |
| /* Expand this array if we have more commands in the future: */ |
| GstNavigationCommand cmds[16]; |
| gint n_cmds = 0; |
| |
| /* Fill out the standard set of commands we support */ |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_TITLE_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_ROOT_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_SUBPICTURE_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_AUDIO_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_ANGLE_MENU; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DVD_CHAPTER_MENU; |
| |
| g_mutex_lock (src->dvd_lock); |
| |
| /* Multiple angles available? */ |
| if (src->n_angles > 1) { |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_PREV_ANGLE; |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_NEXT_ANGLE; |
| } |
| |
| /* Add button selection commands if we have them */ |
| if (src->active_button > 0) { |
| /* We have a valid current button */ |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_ACTIVATE; |
| } |
| /* Check for buttons in each direction */ |
| if (src->cur_btn_mask & RSN_BTN_LEFT) |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_LEFT; |
| if (src->cur_btn_mask & RSN_BTN_RIGHT) |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_RIGHT; |
| if (src->cur_btn_mask & RSN_BTN_UP) |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_UP; |
| if (src->cur_btn_mask & RSN_BTN_DOWN) |
| cmds[n_cmds++] = GST_NAVIGATION_COMMAND_DOWN; |
| g_mutex_unlock (src->dvd_lock); |
| |
| gst_navigation_query_set_commandsv (query, n_cmds, cmds); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_handle_angles_query (resinDvdSrc * src, GstQuery * query) |
| { |
| gint cur_agl, n_angles; |
| gboolean res = FALSE; |
| |
| g_mutex_lock (src->dvd_lock); |
| if (dvdnav_get_angle_info (src->dvdnav, &cur_agl, |
| &n_angles) == DVDNAV_STATUS_OK) { |
| gst_navigation_query_set_angles (query, cur_agl, n_angles); |
| res = TRUE; |
| } |
| g_mutex_unlock (src->dvd_lock); |
| |
| return res; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_handle_navigation_query (resinDvdSrc * src, |
| GstNavigationQueryType nq_type, GstQuery * query) |
| { |
| gboolean res; |
| |
| GST_LOG_OBJECT (src, "Have Navigation query of type %d", nq_type); |
| |
| switch (nq_type) { |
| case GST_NAVIGATION_QUERY_COMMANDS: |
| res = rsn_dvdsrc_handle_cmds_query (src, query); |
| break; |
| case GST_NAVIGATION_QUERY_ANGLES: |
| res = rsn_dvdsrc_handle_angles_query (src, query); |
| break; |
| default: |
| res = FALSE; |
| } |
| |
| return res; |
| } |
| |
| static GstFlowReturn |
| rsn_dvdsrc_prepare_next_block (resinDvdSrc * src, gboolean have_dvd_lock) |
| { |
| GstFlowReturn ret; |
| |
| /* If buffer already ready, return */ |
| if (src->next_buf) |
| return GST_FLOW_OK; |
| |
| do { |
| ret = rsn_dvdsrc_step (src, have_dvd_lock); |
| } |
| while (ret == GST_FLOW_OK && src->next_buf == NULL); |
| |
| if (ret == GST_FLOW_WOULD_BLOCK) |
| ret = GST_FLOW_OK; |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| rsn_dvdsrc_create (GstBaseSrc * bsrc, guint64 offset, |
| guint length, GstBuffer ** outbuf) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| GstSegment *segment = &(GST_BASE_SRC (src)->segment); |
| GstFlowReturn ret; |
| GstEvent *streams_event = NULL; |
| GstEvent *clut_event = NULL; |
| GstEvent *spu_select_event = NULL; |
| GstEvent *audio_select_event = NULL; |
| GstEvent *highlight_event = NULL; |
| GstMessage *angles_msg = NULL; |
| GstTagList *tags = NULL; |
| gboolean cmds_changed = FALSE; |
| |
| *outbuf = NULL; |
| |
| g_mutex_lock (src->dvd_lock); |
| ret = rsn_dvdsrc_prepare_next_block (src, TRUE); |
| if (ret != GST_FLOW_OK) { |
| g_mutex_unlock (src->dvd_lock); |
| return ret; |
| } |
| |
| streams_event = src->streams_event; |
| src->streams_event = NULL; |
| |
| spu_select_event = src->spu_select_event; |
| src->spu_select_event = NULL; |
| |
| audio_select_event = src->audio_select_event; |
| src->audio_select_event = NULL; |
| |
| clut_event = src->clut_event; |
| src->clut_event = NULL; |
| |
| if (src->angles_changed) { |
| gint cur, agls; |
| if (dvdnav_get_angle_info (src->dvdnav, &cur, &agls) == DVDNAV_STATUS_OK) { |
| |
| angles_msg = |
| gst_navigation_message_new_angles_changed (GST_OBJECT_CAST (src), |
| cur, agls); |
| } |
| src->angles_changed = FALSE; |
| } |
| |
| cmds_changed = src->commands_changed; |
| src->commands_changed = FALSE; |
| |
| if (src->need_tag_update) { |
| tags = update_title_info (src, FALSE); |
| src->need_tag_update = FALSE; |
| } |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| /* Push in-band events now that we've dropped the dvd_lock, before |
| * we change segment */ |
| if (streams_event) { |
| GST_LOG_OBJECT (src, "Pushing stream layout event"); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), streams_event); |
| } |
| if (clut_event) { |
| GST_LOG_OBJECT (src, "Pushing clut event"); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), clut_event); |
| } |
| /* Out of band events */ |
| if (spu_select_event) { |
| GST_LOG_OBJECT (src, "Pushing spu_select event"); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), spu_select_event); |
| } |
| if (audio_select_event) { |
| GST_LOG_OBJECT (src, "Pushing audio_select event"); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), audio_select_event); |
| } |
| |
| if (src->need_segment) { |
| /* Seamless segment update */ |
| GstClockTime position = 0; |
| |
| if (src->cur_position != GST_CLOCK_TIME_NONE) |
| position += src->cur_position; |
| if (src->cur_vobu_base_ts != GST_CLOCK_TIME_NONE) |
| position += src->cur_vobu_base_ts; |
| |
| GST_DEBUG_OBJECT (src, |
| "Starting seamless segment update to %" GST_TIME_FORMAT " -> %" |
| GST_TIME_FORMAT " VOBU %" GST_TIME_FORMAT " position %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (src->cur_start_ts), GST_TIME_ARGS (src->cur_end_ts), |
| GST_TIME_ARGS (src->cur_vobu_base_ts), GST_TIME_ARGS (position)); |
| |
| gst_base_src_new_seamless_segment (GST_BASE_SRC (src), |
| src->cur_start_ts, -1, position); |
| |
| src->need_segment = FALSE; |
| } |
| |
| if (src->cur_end_ts != GST_CLOCK_TIME_NONE) |
| segment->position = src->cur_end_ts; |
| |
| if (tags) { |
| GstEvent *tag_event = gst_event_new_tag (tags); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), tag_event); |
| tags = NULL; |
| } |
| g_mutex_lock (src->dvd_lock); |
| |
| if (src->next_buf != NULL) { |
| /* Now that we're in the new segment, we can enqueue any nav packet |
| * correctly */ |
| if (src->next_is_nav_block) { |
| rsn_dvdsrc_enqueue_nav_block (src, src->next_buf, src->next_nav_ts); |
| src->next_is_nav_block = FALSE; |
| } |
| |
| *outbuf = src->next_buf; |
| src->next_buf = NULL; |
| |
| if (src->discont) { |
| GST_LOG_OBJECT (src, "Marking discont buffer"); |
| GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DISCONT); |
| src->discont = FALSE; |
| } |
| } |
| |
| if (src->in_playing) { |
| highlight_event = src->highlight_event; |
| src->highlight_event = NULL; |
| } else { |
| highlight_event = NULL; |
| } |
| |
| /* Schedule a clock callback for the any pending nav packet */ |
| rsn_dvdsrc_check_nav_blocks (src); |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| if (highlight_event) { |
| GST_LOG_OBJECT (src, "Pushing highlight event with TS %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_EVENT_TIMESTAMP (highlight_event))); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), highlight_event); |
| } |
| |
| if (angles_msg) { |
| gst_element_post_message (GST_ELEMENT_CAST (src), angles_msg); |
| } |
| |
| if (cmds_changed) |
| rsn_dvdsrc_send_commands_changed (src); |
| |
| return ret; |
| } |
| |
| static RsnNavResult |
| rsn_dvdsrc_perform_button_action (resinDvdSrc * src, |
| GstNavigationCommand action) |
| { |
| pci_t *pci; |
| RsnNavResult result = RSN_NAV_RESULT_NONE; |
| int button = 0; |
| btni_t *btn_info; |
| |
| if (!src->have_pci) |
| return RSN_NAV_RESULT_NONE; |
| pci = &src->cur_pci; |
| |
| if (pci->hli.hl_gi.hli_ss == 0) |
| return RSN_NAV_RESULT_NONE; /* No buttons at the moment */ |
| |
| dvdnav_get_current_highlight (src->dvdnav, &button); |
| |
| if (button > pci->hli.hl_gi.btn_ns || button < 1) |
| return RSN_NAV_RESULT_NONE; /* No valid button */ |
| |
| btn_info = pci->hli.btnit + button - 1; |
| |
| switch (action) { |
| case GST_NAVIGATION_COMMAND_ACTIVATE: |
| if (dvdnav_button_activate (src->dvdnav, pci) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| break; |
| case GST_NAVIGATION_COMMAND_LEFT: |
| if (dvdnav_left_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { |
| if (btn_info->left && |
| pci->hli.btnit[btn_info->left - 1].auto_action_mode) |
| result = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| else |
| result = RSN_NAV_RESULT_HIGHLIGHT; |
| } |
| break; |
| case GST_NAVIGATION_COMMAND_RIGHT: |
| if (dvdnav_right_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { |
| if (btn_info->right && |
| pci->hli.btnit[btn_info->right - 1].auto_action_mode) |
| result = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| else |
| result = RSN_NAV_RESULT_HIGHLIGHT; |
| } |
| break; |
| case GST_NAVIGATION_COMMAND_DOWN: |
| if (dvdnav_lower_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { |
| if (btn_info->down && |
| pci->hli.btnit[btn_info->down - 1].auto_action_mode) |
| result = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| else |
| result = RSN_NAV_RESULT_HIGHLIGHT; |
| } |
| break; |
| case GST_NAVIGATION_COMMAND_UP: |
| if (dvdnav_upper_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { |
| if (btn_info->up && pci->hli.btnit[btn_info->up - 1].auto_action_mode) |
| result = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| else |
| result = RSN_NAV_RESULT_HIGHLIGHT; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (result == RSN_NAV_RESULT_HIGHLIGHT) { |
| /* If we're *only* changing the highlight, wake up the still condition. |
| * If we're branching, that will happen anyway */ |
| g_cond_broadcast (src->still_cond); |
| } |
| |
| return result; |
| } |
| |
| static RsnNavResult |
| rsn_dvdsrc_do_command (resinDvdSrc * src, GstNavigationCommand command) |
| { |
| RsnNavResult result = RSN_NAV_RESULT_NONE; |
| |
| switch (command) { |
| case GST_NAVIGATION_COMMAND_DVD_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Escape) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_TITLE_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Title) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_ROOT_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Root) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_SUBPICTURE_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Subpicture) == |
| DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_AUDIO_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Audio) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_ANGLE_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Angle) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_DVD_CHAPTER_MENU: |
| if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Part) == DVDNAV_STATUS_OK) |
| result = RSN_NAV_RESULT_BRANCH; |
| break; |
| case GST_NAVIGATION_COMMAND_LEFT: |
| case GST_NAVIGATION_COMMAND_RIGHT: |
| case GST_NAVIGATION_COMMAND_UP: |
| case GST_NAVIGATION_COMMAND_DOWN: |
| case GST_NAVIGATION_COMMAND_ACTIVATE: |
| return rsn_dvdsrc_perform_button_action (src, command); |
| |
| case GST_NAVIGATION_COMMAND_PREV_ANGLE:{ |
| gint32 cur, agls; |
| gint new_angle = 0; |
| if (dvdnav_get_angle_info (src->dvdnav, &cur, &agls) == DVDNAV_STATUS_OK) { |
| if (cur > 0 && |
| dvdnav_angle_change (src->dvdnav, cur - 1) == DVDNAV_STATUS_OK) { |
| new_angle = cur - 1; |
| } else if (cur == 1 && |
| dvdnav_angle_change (src->dvdnav, agls) == DVDNAV_STATUS_OK) { |
| new_angle = agls; |
| } |
| /* Angle switches are seamless and involve no branching */ |
| if (new_angle) { |
| src->angles_changed = TRUE; |
| GST_INFO_OBJECT (src, "Switched to angle %d", new_angle); |
| } |
| } |
| break; |
| } |
| case GST_NAVIGATION_COMMAND_NEXT_ANGLE:{ |
| gint32 cur, agls; |
| gint new_angle = 0; |
| if (dvdnav_get_angle_info (src->dvdnav, &cur, &agls) == DVDNAV_STATUS_OK) { |
| if (cur < agls |
| && dvdnav_angle_change (src->dvdnav, cur + 1) == DVDNAV_STATUS_OK) { |
| new_angle = cur + 1; |
| } else if (cur == agls |
| && dvdnav_angle_change (src->dvdnav, 1) == DVDNAV_STATUS_OK) { |
| new_angle = 1; |
| } |
| /* Angle switches are seamless and involve no branching */ |
| if (new_angle) { |
| src->angles_changed = TRUE; |
| GST_INFO_OBJECT (src, "Switched to angle %d", new_angle); |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_handle_navigation_event (resinDvdSrc * src, GstEvent * event) |
| { |
| gboolean have_lock = FALSE; |
| GstEvent *hl_event = NULL; |
| RsnNavResult nav_res = RSN_NAV_RESULT_NONE; |
| GstNavigationEventType etype = gst_navigation_event_get_type (event); |
| GstMessage *mouse_over_msg = NULL; |
| GstMessage *angles_msg = NULL; |
| |
| switch (etype) { |
| case GST_NAVIGATION_EVENT_KEY_PRESS:{ |
| const gchar *key; |
| if (!gst_navigation_event_parse_key_event (event, &key)) |
| return FALSE; |
| |
| GST_DEBUG ("dvdnavsrc got a keypress: %s", key); |
| |
| g_mutex_lock (src->dvd_lock); |
| have_lock = TRUE; |
| if (!src->running) |
| goto not_running; |
| |
| if (g_str_equal (key, "Return")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_ACTIVATE); |
| } else if (g_str_equal (key, "Left")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_LEFT); |
| } else if (g_str_equal (key, "Right")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_RIGHT); |
| } else if (g_str_equal (key, "Up")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_UP); |
| } else if (g_str_equal (key, "Down")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_DOWN); |
| } else if (g_str_equal (key, "m")) { |
| nav_res = rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_DVD_MENU); |
| } else if (g_str_equal (key, "t")) { |
| nav_res = |
| rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_DVD_TITLE_MENU); |
| } else if (g_str_equal (key, "r")) { |
| nav_res = |
| rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_DVD_ROOT_MENU); |
| } else if (g_str_equal (key, "comma")) { |
| gint title = 0; |
| gint part = 0; |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, &part)) { |
| if (title > 0 && part > 1) { |
| dvdnav_prev_pg_search (src->dvdnav); |
| nav_res = RSN_NAV_RESULT_BRANCH; |
| } |
| } |
| } else if (g_str_equal (key, "period")) { |
| dvdnav_next_pg_search (src->dvdnav); |
| nav_res = RSN_NAV_RESULT_BRANCH; |
| } else if (g_str_equal (key, "bracketleft")) { |
| nav_res = |
| rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_PREV_ANGLE); |
| } else if (g_str_equal (key, "bracketright")) { |
| nav_res = |
| rsn_dvdsrc_do_command (src, GST_NAVIGATION_COMMAND_NEXT_ANGLE); |
| } else if (key && key[0] >= '1' && key[0] <= '8') { |
| gint new_stream = key[0] - '1'; |
| GST_INFO_OBJECT (src, "Selecting audio stream %d", new_stream); |
| rsn_dvdsrc_prepare_audio_stream_event (src, new_stream, new_stream); |
| } |
| break; |
| } |
| case GST_NAVIGATION_EVENT_MOUSE_MOVE:{ |
| gdouble x, y; |
| |
| if (!gst_navigation_event_parse_mouse_move_event (event, &x, &y)) |
| return FALSE; |
| |
| g_mutex_lock (src->dvd_lock); |
| have_lock = TRUE; |
| if (!src->running) |
| goto not_running; |
| |
| if (src->have_pci && |
| dvdnav_mouse_select (src->dvdnav, &src->cur_pci, (int) x, (int) y) == |
| DVDNAV_STATUS_OK) { |
| nav_res = RSN_NAV_RESULT_HIGHLIGHT; |
| if (!src->was_mouse_over) { |
| GST_DEBUG_OBJECT (src, "Mouse moved onto a button"); |
| mouse_over_msg = |
| gst_navigation_message_new_mouse_over ((GstObject *) src, TRUE); |
| src->was_mouse_over = TRUE; |
| } |
| } else if (src->was_mouse_over) { |
| GST_DEBUG_OBJECT (src, "Mouse moved out of a button"); |
| mouse_over_msg = |
| gst_navigation_message_new_mouse_over ((GstObject *) src, FALSE); |
| src->was_mouse_over = FALSE; |
| } |
| break; |
| } |
| case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE:{ |
| gdouble x, y; |
| gint button; |
| |
| if (!gst_navigation_event_parse_mouse_button_event (event, &button, &x, |
| &y)) |
| return FALSE; |
| if (button != 1) |
| return FALSE; |
| |
| GST_DEBUG_OBJECT (src, "Got click at %g, %g", x, y); |
| |
| g_mutex_lock (src->dvd_lock); |
| have_lock = TRUE; |
| if (!src->running) |
| goto not_running; |
| |
| if (src->have_pci && dvdnav_mouse_activate (src->dvdnav, &src->cur_pci, |
| (int) x, (int) y) == DVDNAV_STATUS_OK) { |
| nav_res = RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT; |
| } |
| break; |
| } |
| case GST_NAVIGATION_EVENT_COMMAND:{ |
| GstNavigationCommand command; |
| |
| if (!gst_navigation_event_parse_command (event, &command)) |
| return FALSE; |
| if (command == GST_NAVIGATION_COMMAND_INVALID) |
| return FALSE; |
| |
| g_mutex_lock (src->dvd_lock); |
| have_lock = TRUE; |
| if (!src->running) |
| goto not_running; |
| |
| GST_LOG_OBJECT (src, "handling navigation command %d", command); |
| nav_res = rsn_dvdsrc_do_command (src, command); |
| break; |
| } |
| default: |
| return TRUE; |
| } |
| |
| if (have_lock) { |
| gboolean channel_hop = FALSE; |
| gboolean cmds_changed; |
| |
| if (nav_res != RSN_NAV_RESULT_NONE) { |
| if (nav_res == RSN_NAV_RESULT_BRANCH) { |
| channel_hop = TRUE; |
| } else if (nav_res == RSN_NAV_RESULT_BRANCH_AND_HIGHLIGHT) { |
| src->active_highlight = TRUE; |
| channel_hop = TRUE; |
| } |
| |
| rsn_dvdsrc_update_highlight (src); |
| } |
| |
| if (channel_hop) { |
| GstEvent *seek; |
| |
| GST_DEBUG_OBJECT (src, "Processing flush and jump"); |
| g_mutex_lock (src->branch_lock); |
| src->branching = TRUE; |
| g_cond_broadcast (src->still_cond); |
| g_mutex_unlock (src->branch_lock); |
| |
| hl_event = src->highlight_event; |
| src->highlight_event = NULL; |
| src->active_highlight = FALSE; |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| if (hl_event) { |
| GST_DEBUG_OBJECT (src, "Sending highlight change event - button: %d", |
| src->active_button); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); |
| } |
| |
| /* Send ourselves a seek event to wake everything up and flush */ |
| seek = gst_event_new_seek (1.0, rsndvd_format, GST_SEEK_FLAG_FLUSH, |
| GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); |
| src->flushing_seek = TRUE; |
| gst_element_send_event (GST_ELEMENT (src), seek); |
| |
| g_mutex_lock (src->dvd_lock); |
| |
| rsn_dvdsrc_update_highlight (src); |
| } |
| |
| hl_event = src->highlight_event; |
| src->highlight_event = NULL; |
| |
| if (src->angles_changed) { |
| gint cur, agls; |
| if (dvdnav_get_angle_info (src->dvdnav, &cur, &agls) == DVDNAV_STATUS_OK) { |
| |
| angles_msg = |
| gst_navigation_message_new_angles_changed (GST_OBJECT_CAST (src), |
| cur, agls); |
| } |
| src->angles_changed = FALSE; |
| |
| src->need_tag_update = TRUE; |
| } |
| |
| cmds_changed = src->commands_changed; |
| src->commands_changed = FALSE; |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| if (hl_event) { |
| GST_DEBUG_OBJECT (src, "Sending highlight change event - button: %d", |
| src->active_button); |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); |
| } |
| |
| if (cmds_changed) |
| rsn_dvdsrc_send_commands_changed (src); |
| } |
| |
| if (mouse_over_msg) { |
| gst_element_post_message (GST_ELEMENT_CAST (src), mouse_over_msg); |
| } |
| |
| if (angles_msg) { |
| gst_element_post_message (GST_ELEMENT_CAST (src), angles_msg); |
| } |
| |
| return TRUE; |
| not_running: |
| if (have_lock) |
| g_mutex_unlock (src->dvd_lock); |
| GST_DEBUG_OBJECT (src, "Element not started. Ignoring navigation event"); |
| return FALSE; |
| } |
| |
| static void |
| rsn_dvdsrc_prepare_audio_stream_event (resinDvdSrc * src, guint8 logical_stream, |
| guint8 phys_stream) |
| { |
| GstStructure *s; |
| GstEvent *e; |
| |
| if (phys_stream == src->cur_audio_phys_stream) |
| return; |
| src->cur_audio_phys_stream = phys_stream; |
| |
| GST_DEBUG_OBJECT (src, "Preparing audio change, phys %d", phys_stream); |
| |
| s = gst_structure_new ("application/x-gst-dvd", |
| "event", G_TYPE_STRING, "dvd-set-audio-track", |
| "logical-id", G_TYPE_INT, (gint) logical_stream, |
| "physical-id", G_TYPE_INT, (gint) phys_stream, NULL); |
| |
| e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); |
| |
| if (src->audio_select_event) |
| gst_event_unref (src->audio_select_event); |
| src->audio_select_event = e; |
| } |
| |
| static void |
| rsn_dvdsrc_prepare_spu_stream_event (resinDvdSrc * src, guint8 logical_stream, |
| guint8 phys_stream, gboolean forced_only) |
| { |
| GstStructure *s; |
| GstEvent *e; |
| |
| if (phys_stream == src->cur_spu_phys_stream && |
| forced_only == src->cur_spu_forced_only) { |
| return; |
| } |
| src->cur_spu_phys_stream = phys_stream; |
| src->cur_spu_forced_only = forced_only; |
| |
| GST_DEBUG_OBJECT (src, "Preparing SPU change, log %d phys %d forced %d", |
| logical_stream, phys_stream, forced_only); |
| |
| s = gst_structure_new ("application/x-gst-dvd", |
| "event", G_TYPE_STRING, "dvd-set-subpicture-track", |
| "logical-id", G_TYPE_INT, (gint) logical_stream, |
| "physical-id", G_TYPE_INT, (gint) phys_stream, |
| "forced-only", G_TYPE_BOOLEAN, forced_only, NULL); |
| |
| e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); |
| |
| if (src->spu_select_event) |
| gst_event_unref (src->spu_select_event); |
| src->spu_select_event = e; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_prepare_streamsinfo_event (resinDvdSrc * src) |
| { |
| vtsi_mat_t *vts_attr; |
| video_attr_t *v_attr; |
| audio_attr_t *a_attrs; |
| subp_attr_t *s_attrs; |
| gint n_audio, n_subp; |
| int8_t cur_audio; |
| GstStructure *s; |
| GstEvent *e; |
| gint i; |
| gchar lang_code[3] = { '\0', '\0', '\0' }; |
| gchar *t; |
| gboolean is_widescreen; |
| gboolean have_audio; |
| gboolean have_subp; |
| |
| if (src->vts_n == 0 || src->vts_attrs == NULL) { |
| /* VMGM info */ |
| vts_attr = NULL; |
| v_attr = &src->vmgm_attr.vmgm_video_attr; |
| a_attrs = &src->vmgm_attr.vmgm_audio_attr; |
| n_audio = MIN (1, src->vmgm_attr.nr_of_vmgm_audio_streams); |
| s_attrs = &src->vmgm_attr.vmgm_subp_attr; |
| n_subp = MIN (1, src->vmgm_attr.nr_of_vmgm_subp_streams); |
| } else if (src->in_menu) { |
| /* VTSM attrs */ |
| vts_attr = get_vts_attr (src, src->vts_n); |
| v_attr = &vts_attr->vtsm_video_attr; |
| a_attrs = &vts_attr->vtsm_audio_attr; |
| n_audio = vts_attr->nr_of_vtsm_audio_streams; |
| s_attrs = &vts_attr->vtsm_subp_attr; |
| n_subp = vts_attr->nr_of_vtsm_subp_streams; |
| } else { |
| /* VTS domain */ |
| vts_attr = get_vts_attr (src, src->vts_n); |
| v_attr = &vts_attr->vts_video_attr; |
| a_attrs = vts_attr->vts_audio_attr; |
| n_audio = vts_attr->nr_of_vts_audio_streams; |
| s_attrs = vts_attr->vts_subp_attr; |
| n_subp = vts_attr->nr_of_vts_subp_streams; |
| } |
| |
| if (src->vts_n > 0 && vts_attr == NULL) |
| return FALSE; |
| |
| GST_DEBUG_OBJECT (src, "Preparing streamsinfo for %d audio and " |
| "%d subpicture streams", n_audio, n_subp); |
| |
| /* build event */ |
| s = gst_structure_new ("application/x-gst-dvd", |
| "event", G_TYPE_STRING, "dvd-lang-codes", NULL); |
| e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); |
| |
| /* video */ |
| is_widescreen = (v_attr->display_aspect_ratio != 0); |
| gst_structure_set (s, "video-pal-format", G_TYPE_BOOLEAN, |
| (v_attr->video_format != 0), NULL); |
| gst_structure_set (s, "video-widescreen", G_TYPE_BOOLEAN, is_widescreen, |
| NULL); |
| |
| /* audio */ |
| cur_audio = dvdnav_get_active_audio_stream (src->dvdnav); |
| |
| have_audio = FALSE; |
| for (i = 0; i < n_audio; i++) { |
| const audio_attr_t *a = a_attrs + i; |
| gint phys_id = dvdnav_get_audio_logical_stream (src->dvdnav, (guint) i); |
| |
| if (phys_id == -1) { |
| GST_DEBUG_OBJECT (src, "No substream ID in map for audio %d. Skipping.", |
| i); |
| continue; |
| } |
| |
| GST_DEBUG_OBJECT (src, "mapped logical audio %d to MPEG substream %d", |
| i, phys_id); |
| /* Force audio stream reselection in case format changed ... */ |
| if (i == cur_audio) { |
| src->cur_audio_phys_stream = -1; |
| rsn_dvdsrc_prepare_audio_stream_event (src, i, phys_id); |
| } |
| #if 0 |
| /* Old test code: Only output A52 streams */ |
| if (a->audio_format != 0) { |
| GST_DEBUG_OBJECT (src, "Ignoring non-A52 stream %d, format %d", i, |
| (int) a->audio_format); |
| continue; |
| } |
| if (a->audio_format == 0) |
| have_audio = TRUE; |
| #else |
| have_audio = TRUE; |
| #endif |
| |
| GST_DEBUG_OBJECT (src, "Audio stream %d is format %d, substream %d", i, |
| (int) a->audio_format, phys_id); |
| |
| t = g_strdup_printf ("audio-%d-stream", i); |
| gst_structure_set (s, t, G_TYPE_INT, phys_id, NULL); |
| g_free (t); |
| |
| t = g_strdup_printf ("audio-%d-format", i); |
| gst_structure_set (s, t, G_TYPE_INT, (int) a->audio_format, NULL); |
| g_free (t); |
| |
| /* Check that the language code is flagged and at least somewhat valid |
| * before putting it in the output structure */ |
| if (a->lang_type && a->lang_code > 0x100) { |
| t = g_strdup_printf ("audio-%d-language", i); |
| lang_code[0] = (a->lang_code >> 8) & 0xff; |
| lang_code[1] = a->lang_code & 0xff; |
| gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL); |
| g_free (t); |
| |
| GST_DEBUG_OBJECT (src, "Audio stream %d is language %s", i, lang_code); |
| } else |
| GST_DEBUG_OBJECT (src, "Audio stream %d - no language", i); |
| } |
| |
| if (have_audio == FALSE) { |
| /* Always create at least one audio stream of the required type */ |
| gst_structure_set (s, "audio-0-format", G_TYPE_INT, (int) 0, |
| "audio-0-stream", G_TYPE_INT, (int) 0, NULL); |
| } |
| |
| /* subpictures */ |
| have_subp = FALSE; |
| for (i = 0; i < n_subp; i++) { |
| const subp_attr_t *u = s_attrs + i; |
| gint phys_id = dvdnav_get_spu_logical_stream (src->dvdnav, (guint) i); |
| |
| if (phys_id == -1) { |
| GST_DEBUG_OBJECT (src, "No substream ID in map for subpicture %d. " |
| "Skipping", i); |
| continue; |
| } |
| have_subp = TRUE; |
| |
| GST_DEBUG_OBJECT (src, "mapped logical subpicture %d to MPEG substream %d", |
| i, phys_id); |
| |
| t = g_strdup_printf ("subpicture-%d-stream", i); |
| gst_structure_set (s, t, G_TYPE_INT, (int) phys_id, NULL); |
| g_free (t); |
| |
| t = g_strdup_printf ("subpicture-%d-format", i); |
| gst_structure_set (s, t, G_TYPE_INT, (int) 0, NULL); |
| g_free (t); |
| |
| t = g_strdup_printf ("subpicture-%d-language", i); |
| if (u->type && u->lang_code > 0x100) { |
| lang_code[0] = (u->lang_code >> 8) & 0xff; |
| lang_code[1] = u->lang_code & 0xff; |
| gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL); |
| } else { |
| gst_structure_set (s, t, G_TYPE_STRING, "MENU", NULL); |
| } |
| g_free (t); |
| |
| GST_DEBUG_OBJECT (src, "Subpicture stream %d is language %s", i, |
| lang_code[0] ? lang_code : "NONE"); |
| } |
| if (!have_subp) { |
| /* Always create at least one subpicture stream */ |
| gst_structure_set (s, "subpicture-0-format", G_TYPE_INT, (int) 0, |
| "subpicture-0-language", G_TYPE_STRING, "MENU", |
| "subpicture-0-stream", G_TYPE_INT, (int) 0, NULL); |
| } |
| |
| if (src->streams_event) |
| gst_event_unref (src->streams_event); |
| src->streams_event = e; |
| |
| return TRUE; |
| } |
| |
| static void |
| rsn_dvdsrc_prepare_clut_change_event (resinDvdSrc * src, const guint32 * clut) |
| { |
| GstEvent *event; |
| GstStructure *structure; |
| gchar name[16]; |
| int i; |
| |
| if (memcmp (src->cur_clut, clut, sizeof (guint32) * 16) == 0) |
| return; |
| memcpy (src->cur_clut, clut, sizeof (guint32) * 16); |
| |
| structure = gst_structure_new ("application/x-gst-dvd", |
| "event", G_TYPE_STRING, "dvd-spu-clut-change", NULL); |
| |
| /* Create a separate field for each value in the table. */ |
| for (i = 0; i < 16; i++) { |
| sprintf (name, "clut%02d", i); |
| gst_structure_set (structure, name, G_TYPE_INT, (int) clut[i], NULL); |
| } |
| |
| /* Create the DVD event and put the structure into it. */ |
| event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure); |
| |
| GST_LOG_OBJECT (src, "preparing clut change event %" GST_PTR_FORMAT, event); |
| |
| if (src->clut_event) |
| gst_event_unref (src->clut_event); |
| src->clut_event = event; |
| } |
| |
| /* |
| * Check for a new highlighted area, and prepare an spu highlight event if |
| * necessary. |
| */ |
| static void |
| rsn_dvdsrc_update_highlight (resinDvdSrc * src) |
| { |
| int button = 0; |
| pci_t *pci = &src->cur_pci; |
| dvdnav_highlight_area_t area; |
| int mode = src->active_highlight ? 1 : 0; |
| GstEvent *event = NULL; |
| GstStructure *s; |
| |
| if (src->have_pci) { |
| if (dvdnav_get_current_highlight (src->dvdnav, &button) == DVDNAV_STATUS_OK) { |
| GST_LOG_OBJECT (src, "current dvdnav button is %d, we have %d", |
| button, src->active_button); |
| } |
| |
| if (pci->hli.hl_gi.hli_ss == 0 || button < 0) { |
| button = 0; |
| } else if (button > pci->hli.hl_gi.btn_ns) { |
| /* button is out of the range of possible buttons. */ |
| button = pci->hli.hl_gi.btn_ns; |
| dvdnav_button_select (src->dvdnav, &src->cur_pci, button); |
| } |
| |
| if (button > 0 && dvdnav_get_highlight_area (pci, button, mode, |
| &area) != DVDNAV_STATUS_OK) { |
| button = 0; |
| } |
| } |
| |
| if (button == 0) { |
| /* No highlight available, or no button selected - clear the SPU */ |
| if (src->active_button != 0) { |
| src->active_button = 0; |
| |
| s = gst_structure_new ("application/x-gst-dvd", "event", |
| G_TYPE_STRING, "dvd-spu-reset-highlight", NULL); |
| event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s); |
| if (src->highlight_event) |
| gst_event_unref (src->highlight_event); |
| src->highlight_event = event; |
| if (src->cur_btn_mask != RSN_BTN_NONE) { |
| src->cur_btn_mask = RSN_BTN_NONE; |
| src->commands_changed = TRUE; |
| } |
| } |
| return; |
| } |
| |
| /* Check if we have a new button number, or a new highlight region. */ |
| if (button != src->active_button || |
| area.sx != src->area.sx || area.sy != src->area.sy || |
| area.ex != src->area.ex || area.ey != src->area.ey || |
| area.palette != src->area.palette) { |
| btni_t *btn_info = pci->hli.btnit + button - 1; |
| guint32 btn_mask; |
| |
| GST_DEBUG_OBJECT (src, "Setting highlight. Button %d @ %d,%d,%d,%d " |
| "active %d palette 0x%x (from button %d @ %d,%d,%d,%d palette 0x%x)", |
| button, area.sx, area.sy, area.ex, area.ey, |
| mode, area.palette, |
| src->active_button, src->area.sx, src->area.sy, src->area.ex, |
| src->area.ey, src->area.palette); |
| |
| memcpy (&(src->area), &area, sizeof (dvdnav_highlight_area_t)); |
| |
| s = gst_structure_new ("application/x-gst-dvd", "event", |
| G_TYPE_STRING, "dvd-spu-highlight", |
| "button", G_TYPE_INT, (gint) button, |
| "palette", G_TYPE_INT, (gint) area.palette, |
| "sx", G_TYPE_INT, (gint) area.sx, |
| "sy", G_TYPE_INT, (gint) area.sy, |
| "ex", G_TYPE_INT, (gint) area.ex, |
| "ey", G_TYPE_INT, (gint) area.ey, NULL); |
| |
| event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s); |
| |
| if (src->active_button < 1) { |
| /* When setting the button for the first time, take the |
| timestamp into account. */ |
| GST_EVENT_TIMESTAMP (event) = MPEGTIME_TO_GSTTIME (area.pts); |
| } |
| |
| src->active_button = button; |
| |
| if (src->highlight_event) |
| gst_event_unref (src->highlight_event); |
| src->highlight_event = event; |
| |
| /* Calculate whether the available set of button motions is changed */ |
| btn_mask = 0; |
| if (btn_info->left && btn_info->left != button) |
| btn_mask |= RSN_BTN_LEFT; |
| if (btn_info->right && btn_info->right != button) |
| btn_mask |= RSN_BTN_RIGHT; |
| if (btn_info->up && btn_info->up != button) |
| btn_mask |= RSN_BTN_UP; |
| if (btn_info->down && btn_info->down != button) |
| btn_mask |= RSN_BTN_DOWN; |
| |
| if (btn_mask != src->cur_btn_mask) { |
| src->cur_btn_mask = btn_mask; |
| src->commands_changed = TRUE; |
| } |
| } |
| } |
| |
| static void |
| rsn_dvdsrc_enqueue_nav_block (resinDvdSrc * src, GstBuffer * nav_buf, |
| GstClockTime ts) |
| { |
| RsnDvdPendingNav *pend_nav = g_new0 (RsnDvdPendingNav, 1); |
| GstSegment *seg = &(GST_BASE_SRC (src)->segment); |
| |
| pend_nav->buffer = gst_buffer_ref (nav_buf); |
| pend_nav->ts = ts; |
| pend_nav->running_ts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, ts); |
| |
| if (src->pending_nav_blocks == NULL) { |
| src->pending_nav_blocks = src->pending_nav_blocks_end = |
| g_slist_append (src->pending_nav_blocks_end, pend_nav); |
| } else { |
| src->pending_nav_blocks_end = |
| g_slist_append (src->pending_nav_blocks_end, pend_nav); |
| src->pending_nav_blocks_end = g_slist_next (src->pending_nav_blocks_end); |
| } |
| |
| GST_LOG_OBJECT (src, "Enqueued nav with TS %" GST_TIME_FORMAT |
| " with run ts %" GST_TIME_FORMAT ". %d packs pending", |
| GST_TIME_ARGS (ts), GST_TIME_ARGS (pend_nav->running_ts), |
| g_slist_length (src->pending_nav_blocks)); |
| } |
| |
| static void |
| rsn_dvdsrc_activate_nav_block (resinDvdSrc * src, GstBuffer * nav_buf) |
| { |
| int32_t forced_button; |
| |
| { |
| GstMapInfo mmap; |
| gst_buffer_map (nav_buf, &mmap, GST_MAP_READ); |
| |
| navRead_PCI (&src->cur_pci, mmap.data + 0x2d); |
| |
| gst_buffer_unmap (nav_buf, &mmap); |
| } |
| |
| src->have_pci = TRUE; |
| |
| forced_button = src->cur_pci.hli.hl_gi.fosl_btnn & 0x3f; |
| if (forced_button != 0) { |
| GST_DEBUG_OBJECT (src, "Selecting button %d based on nav packet command", |
| forced_button); |
| dvdnav_button_select (src->dvdnav, &src->cur_pci, forced_button); |
| } |
| /* highlight might change, let's check */ |
| rsn_dvdsrc_update_highlight (src); |
| |
| if (src->highlight_event && src->in_still_state) { |
| GST_LOG_OBJECT (src, "Signalling still condition due to highlight change"); |
| g_cond_broadcast (src->still_cond); |
| } |
| } |
| |
| static void |
| rsn_dvdsrc_clear_nav_blocks (resinDvdSrc * src) |
| { |
| GST_DEBUG_OBJECT (src, "Clearing %d pending navpacks", |
| g_slist_length (src->pending_nav_blocks)); |
| |
| while (src->pending_nav_blocks) { |
| RsnDvdPendingNav *cur = (RsnDvdPendingNav *) src->pending_nav_blocks->data; |
| |
| gst_buffer_unref (cur->buffer); |
| g_free (cur); |
| |
| src->pending_nav_blocks = |
| g_slist_delete_link (src->pending_nav_blocks, src->pending_nav_blocks); |
| } |
| |
| src->pending_nav_blocks_end = NULL; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_nav_clock_cb (GstClock * clock, GstClockTime time, GstClockID id, |
| gpointer user_data) |
| { |
| resinDvdSrc *src = (resinDvdSrc *) user_data; |
| GstClockTime base_time = gst_element_get_base_time (GST_ELEMENT (src)); |
| |
| GST_LOG_OBJECT (src, "NAV pack callback for TS %" GST_TIME_FORMAT " at ts %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (time), |
| GST_TIME_ARGS (gst_clock_get_time (clock) - base_time)); |
| |
| g_mutex_lock (src->dvd_lock); |
| |
| /* Destroy the clock id that caused this callback */ |
| if (src->nav_clock_id) { |
| gst_clock_id_unref (src->nav_clock_id); |
| src->nav_clock_id = NULL; |
| } |
| |
| while (src->pending_nav_blocks) { |
| RsnDvdPendingNav *cur = (RsnDvdPendingNav *) src->pending_nav_blocks->data; |
| |
| if (time < base_time + cur->running_ts) |
| break; /* Next NAV is in the future */ |
| |
| GST_DEBUG_OBJECT (src, "Activating nav pack with TS %" GST_TIME_FORMAT |
| " at running TS %" GST_TIME_FORMAT, GST_TIME_ARGS (cur->ts), |
| GST_TIME_ARGS (cur->running_ts)); |
| rsn_dvdsrc_activate_nav_block (src, cur->buffer); |
| |
| gst_buffer_unref (cur->buffer); |
| g_free (cur); |
| |
| src->pending_nav_blocks = |
| g_slist_delete_link (src->pending_nav_blocks, src->pending_nav_blocks); |
| } |
| |
| if (src->pending_nav_blocks == NULL) |
| src->pending_nav_blocks_end = NULL; |
| else { |
| /* Schedule a next packet, if any */ |
| RsnDvdPendingNav *next_nav = |
| (RsnDvdPendingNav *) src->pending_nav_blocks->data; |
| rsn_dvdsrc_schedule_nav_cb (src, next_nav); |
| } |
| |
| g_mutex_unlock (src->dvd_lock); |
| |
| return TRUE; |
| } |
| |
| /* Called with dvd_lock held. NOTE: Releases dvd_lock briefly */ |
| static void |
| rsn_dvdsrc_schedule_nav_cb (resinDvdSrc * src, RsnDvdPendingNav * next_nav) |
| { |
| GstClock *clock; |
| GstClockTime base_ts; |
| |
| if (!src->in_playing) { |
| GST_LOG_OBJECT (src, "Not scheduling NAV block - state != PLAYING"); |
| return; /* Not in playing state yet */ |
| } |
| |
| GST_OBJECT_LOCK (src); |
| clock = GST_ELEMENT_CLOCK (src); |
| base_ts = GST_ELEMENT (src)->base_time; |
| |
| if (clock == NULL) { |
| GST_LOG_OBJECT (src, "Not scheduling NAV block - no clock yet"); |
| GST_OBJECT_UNLOCK (src); |
| return; |
| } |
| gst_object_ref (clock); |
| |
| src->nav_clock_id = gst_clock_new_single_shot_id (clock, |
| base_ts + next_nav->running_ts); |
| |
| GST_OBJECT_UNLOCK (src); |
| |
| GST_LOG_OBJECT (src, "Schedule nav pack for running TS %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (next_nav->running_ts)); |
| |
| g_mutex_unlock (src->dvd_lock); |
| gst_clock_id_wait_async (src->nav_clock_id, rsn_dvdsrc_nav_clock_cb, src, |
| NULL); |
| gst_object_unref (clock); |
| g_mutex_lock (src->dvd_lock); |
| } |
| |
| /* Called with dvd_lock held */ |
| static void |
| rsn_dvdsrc_check_nav_blocks (resinDvdSrc * src) |
| { |
| RsnDvdPendingNav *next_nav; |
| |
| /* Make sure a callback is scheduled for the first nav packet */ |
| if (src->nav_clock_id != NULL) { |
| return; /* Something already scheduled */ |
| } |
| if (src->pending_nav_blocks == NULL) { |
| return; /* No nav blocks available yet */ |
| } |
| if (!src->in_playing) |
| return; /* Not in playing state yet */ |
| |
| GST_LOG_OBJECT (src, "Installing NAV callback"); |
| next_nav = (RsnDvdPendingNav *) src->pending_nav_blocks->data; |
| |
| rsn_dvdsrc_schedule_nav_cb (src, next_nav); |
| } |
| |
| static gboolean |
| rsn_dvdsrc_src_event (GstBaseSrc * basesrc, GstEvent * event) |
| { |
| resinDvdSrc *src = RESINDVDSRC (basesrc); |
| gboolean res; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_NAVIGATION: |
| res = rsn_dvdsrc_handle_navigation_event (src, event); |
| break; |
| case GST_EVENT_SEEK:{ |
| GstSeekFlags flags; |
| |
| GST_LOG_OBJECT (src, "handling seek event"); |
| |
| gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL); |
| src->flushing_seek = ! !(flags & GST_SEEK_FLAG_FLUSH); |
| GST_DEBUG_OBJECT (src, "%s seek event", |
| src->flushing_seek ? "flushing" : "non-flushing"); |
| |
| res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event); |
| break; |
| } |
| default: |
| GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event)); |
| |
| res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static void |
| rsn_dvdsrc_post_title_info (GstElement * element) |
| { |
| resinDvdSrc *src = RESINDVDSRC (element); |
| GstMessage *message; |
| GstStructure *s; |
| int32_t n, ntitles; |
| int res; |
| GValue array = { 0 }; |
| |
| res = dvdnav_get_number_of_titles (src->dvdnav, &ntitles); |
| if (res != DVDNAV_STATUS_OK) { |
| GST_WARNING_OBJECT (src, "Failed to get number of titles: %d", res); |
| return; |
| } |
| |
| g_value_init (&array, GST_TYPE_ARRAY); |
| |
| s = gst_structure_new ("application/x-gst-dvd", "event", |
| G_TYPE_STRING, "dvd-title-info", NULL); |
| |
| for (n = 0; n < ntitles; ++n) { |
| uint64_t *times, duration; |
| uint32_t nchapters; |
| GValue item = { 0 }; |
| |
| g_value_init (&item, G_TYPE_UINT64); |
| |
| nchapters = |
| dvdnav_describe_title_chapters (src->dvdnav, n, ×, &duration); |
| if (nchapters == 0) { |
| GST_WARNING_OBJECT (src, "Failed to get title %d info", n); |
| g_value_set_uint64 (&item, GST_CLOCK_TIME_NONE); |
| } else { |
| g_value_set_uint64 (&item, gst_util_uint64_scale (duration, GST_SECOND, |
| 90000)); |
| } |
| gst_value_array_append_value (&array, &item); |
| g_value_unset (&item); |
| } |
| gst_structure_set_value (s, "title-durations", &array); |
| g_value_unset (&array); |
| |
| message = gst_message_new_element (GST_OBJECT (src), s); |
| gst_element_post_message (GST_ELEMENT_CAST (src), message); |
| } |
| |
| static GstStateChangeReturn |
| rsn_dvdsrc_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn ret; |
| resinDvdSrc *src = RESINDVDSRC (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| GST_DEBUG_OBJECT (element, "Switching to PAUSED"); |
| /* Unschedule any NAV packet callback */ |
| g_mutex_lock (src->dvd_lock); |
| src->in_playing = FALSE; |
| if (src->nav_clock_id) { |
| gst_clock_id_unschedule (src->nav_clock_id); |
| gst_clock_id_unref (src->nav_clock_id); |
| src->nav_clock_id = NULL; |
| } |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| return ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| GST_DEBUG_OBJECT (element, "Switching to PLAYING"); |
| /* Kick off the NAV packet callback if needed */ |
| g_mutex_lock (src->dvd_lock); |
| src->in_playing = TRUE; |
| rsn_dvdsrc_check_nav_blocks (src); |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| rsn_dvdsrc_post_title_info (element); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_src_query (GstBaseSrc * basesrc, GstQuery * query) |
| { |
| resinDvdSrc *src = RESINDVDSRC (basesrc); |
| gboolean res = FALSE; |
| GstFormat format; |
| gint64 val; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_DURATION: |
| gst_query_parse_duration (query, &format, NULL); |
| g_mutex_lock (src->dvd_lock); |
| if (!src->running) { |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| } |
| |
| if (format == GST_FORMAT_TIME) { |
| if (src->pgc_duration != GST_CLOCK_TIME_NONE) { |
| val = src->pgc_duration; |
| |
| GST_DEBUG_OBJECT (src, "duration : %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (val)); |
| gst_query_set_duration (query, format, val); |
| res = TRUE; |
| } |
| } else if (format == title_format) { |
| gint32 titles; |
| |
| if (dvdnav_get_number_of_titles (src->dvdnav, |
| &titles) == DVDNAV_STATUS_OK) { |
| val = titles; |
| gst_query_set_duration (query, format, val); |
| res = TRUE; |
| } |
| } else if (format == chapter_format) { |
| gint32 title, chapters, x; |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, |
| &x) == DVDNAV_STATUS_OK) { |
| if (dvdnav_get_number_of_parts (src->dvdnav, title, |
| &chapters) == DVDNAV_STATUS_OK) { |
| val = chapters; |
| gst_query_set_duration (query, format, val); |
| res = TRUE; |
| } |
| } |
| } |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| case GST_QUERY_POSITION: |
| gst_query_parse_position (query, &format, NULL); |
| |
| g_mutex_lock (src->dvd_lock); |
| if (!src->running) { |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| } |
| if (format == title_format) { |
| gint32 title, chapter; |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, |
| &chapter) == DVDNAV_STATUS_OK) { |
| val = title; |
| gst_query_set_position (query, format, val); |
| res = TRUE; |
| } |
| } else if (format == chapter_format) { |
| gint32 title, chapter = -1; |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, |
| &chapter) == DVDNAV_STATUS_OK) { |
| val = chapter; |
| gst_query_set_position (query, format, val); |
| res = TRUE; |
| } |
| } |
| g_mutex_unlock (src->dvd_lock); |
| break; |
| case GST_QUERY_CUSTOM: |
| { |
| GstNavigationQueryType nq_type = gst_navigation_query_get_type (query); |
| if (nq_type != GST_NAVIGATION_QUERY_INVALID) |
| res = rsn_dvdsrc_handle_navigation_query (src, nq_type, query); |
| else |
| res = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query); |
| break; |
| } |
| case GST_QUERY_SCHEDULING: |
| { |
| /* Make sure we operate in pull mode */ |
| gst_query_set_scheduling (query, GST_SCHEDULING_FLAG_SEQUENTIAL, 1, -1, |
| 0); |
| gst_query_add_scheduling_mode (query, GST_PAD_MODE_PUSH); |
| |
| res = TRUE; |
| break; |
| } |
| default: |
| res = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_is_seekable (GstBaseSrc * bsrc) |
| { |
| return TRUE; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_prepare_seek (GstBaseSrc * bsrc, GstEvent * event, |
| GstSegment * segment) |
| { |
| GstSeekType cur_type, stop_type; |
| gint64 cur, stop; |
| GstSeekFlags flags; |
| GstFormat seek_format; |
| gdouble rate; |
| gboolean update; |
| gboolean ret; |
| |
| gst_event_parse_seek (event, &rate, &seek_format, &flags, |
| &cur_type, &cur, &stop_type, &stop); |
| |
| /* Don't allow bytes seeks - angle, time, chapter, title only is the plan */ |
| if (seek_format == GST_FORMAT_BYTES) |
| return FALSE; |
| |
| if (seek_format == rsndvd_format || seek_format == title_format || |
| seek_format == chapter_format) { |
| /* Seeks in our internal formats are passed directly through to the do_seek |
| * method. */ |
| gst_segment_init (segment, seek_format); |
| gst_segment_do_seek (segment, rate, seek_format, flags, cur_type, cur, |
| stop_type, stop, &update); |
| |
| return TRUE; |
| } |
| |
| /* Let basesrc handle other formats */ |
| ret = GST_BASE_SRC_CLASS (parent_class)->prepare_seek_segment (bsrc, |
| event, segment); |
| |
| return ret; |
| } |
| |
| /* Find sector from time using time map if available */ |
| static gint |
| rsn_dvdsrc_get_sector_from_time_tmap (resinDvdSrc * src, GstClockTime ts) |
| { |
| vts_tmapt_t *vts_tmapt; |
| vts_tmap_t *title_tmap; |
| gint32 title, part, vts_ttn; |
| guint32 entry, sector, logical_sector; |
| gint cell_n; |
| pgc_t *pgc; |
| |
| if (ts == 0) |
| return 0; |
| |
| if (src->vts_file == NULL) |
| return -1; |
| |
| if (dvdnav_current_title_info (src->dvdnav, &title, &part) != |
| DVDNAV_STATUS_OK) |
| return -1; |
| |
| vts_tmapt = src->vts_file->vts_tmapt; |
| if (vts_tmapt == NULL) |
| return -1; |
| |
| /* To find the right tmap, we need the title number within this VTS (vts_ttn) |
| * from the VMG tt_srpt table... */ |
| if (title < 1 || title > src->vmg_file->tt_srpt->nr_of_srpts) |
| return -1; |
| |
| /* We must be in the correct VTS for any of this to succeed... */ |
| if (src->vts_n != src->vmg_file->tt_srpt->title[title - 1].title_set_nr) |
| return -1; |
| |
| /* We must also be in the VTS domain to use the tmap table */ |
| if (src->vts_n == 0 || src->in_menu) |
| return -1; |
| |
| vts_ttn = src->vmg_file->tt_srpt->title[title - 1].vts_ttn; |
| |
| GST_DEBUG_OBJECT (src, "Seek to time %" GST_TIME_FORMAT |
| " in VTS %d title %d (vts_ttn %d of %d)", |
| GST_TIME_ARGS (ts), src->vts_n, title, vts_ttn, vts_tmapt->nr_of_tmaps); |
| |
| if (vts_ttn < 1 || vts_ttn > vts_tmapt->nr_of_tmaps) |
| return -1; |
| |
| pgc = get_current_pgc (src); |
| if (pgc == NULL) |
| return -1; |
| |
| /* Get the time map */ |
| title_tmap = vts_tmapt->tmap + vts_ttn - 1; |
| if (title_tmap->tmu == 0) |
| return -1; |
| |
| entry = ts / (title_tmap->tmu * GST_SECOND); |
| if (entry == 0) |
| return 0; |
| |
| if (entry < 1 || entry > title_tmap->nr_of_entries) |
| return -1; |
| |
| sector = title_tmap->map_ent[entry - 1] & 0x7fffffff; |
| |
| GST_LOG_OBJECT (src, "Got sector %u for time seek (entry %d of %d)", |
| sector, entry, title_tmap->nr_of_entries); |
| |
| /* Sector is now an absolute sector within the current VTS, but |
| * dvdnav_sector_search expects a logical sector within the current PGC... |
| * which means iterating over the cells of the current PGC until we find |
| * the cell that contains the time and sector we want, accumulating |
| * the logical sector offsets until we find it |
| */ |
| logical_sector = 0; |
| for (cell_n = 0; cell_n < pgc->nr_of_cells; cell_n++) { |
| cell_playback_t *cell = pgc->cell_playback + cell_n; |
| |
| /* This matches how libdvdnav calculates the logical sector |
| * in dvdnav_sector_search(): */ |
| |
| if (sector >= cell->first_sector && sector <= cell->last_sector) { |
| logical_sector += sector - cell->first_sector; |
| break; |
| } |
| |
| if (cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && |
| cell->block_mode != BLOCK_MODE_FIRST_CELL) |
| continue; |
| |
| logical_sector += (cell->last_sector - cell->first_sector + 1); |
| } |
| |
| GST_DEBUG_OBJECT (src, "Mapped sector %u onto PGC relative sector %u", |
| sector, logical_sector); |
| |
| return logical_sector; |
| } |
| |
| /* call with DVD lock held */ |
| static gboolean |
| rsn_dvdsrc_seek_to_time (resinDvdSrc * src, GstClockTime ts) |
| { |
| gint sector; |
| dvdnav_status_t res; |
| |
| GST_DEBUG_OBJECT (src, "Time seek requested to ts %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ts)); |
| |
| sector = rsn_dvdsrc_get_sector_from_time_tmap (src, ts); |
| if (sector < 0) |
| return FALSE; |
| |
| src->discont = TRUE; |
| res = dvdnav_sector_search (src->dvdnav, sector, SEEK_SET); |
| |
| if (res != DVDNAV_STATUS_OK) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| rsn_dvdsrc_do_seek (GstBaseSrc * bsrc, GstSegment * segment) |
| { |
| resinDvdSrc *src = RESINDVDSRC (bsrc); |
| gboolean ret = FALSE; |
| |
| if (segment->format == rsndvd_format || src->first_seek) { |
| /* The internal format has alread served its purpose of waking |
| * everything up and flushing, we just need to step to the next |
| * data block (below) so we know our new position */ |
| ret = TRUE; |
| /* HACK to make initial seek work: */ |
| src->first_seek = FALSE; |
| } else { |
| /* Handle other formats: Time, title, chapter, angle */ |
| if (segment->format == GST_FORMAT_TIME) { |
| g_mutex_lock (src->dvd_lock); |
| src->discont = TRUE; |
| ret = rsn_dvdsrc_seek_to_time (src, segment->start); |
| g_mutex_unlock (src->dvd_lock); |
| } else if (segment->format == title_format) { |
| gint titles; |
| |
| g_mutex_lock (src->dvd_lock); |
| if (src->running && |
| dvdnav_get_number_of_titles (src->dvdnav, |
| &titles) == DVDNAV_STATUS_OK) { |
| if (segment->start > 0 && segment->start <= titles) { |
| dvdnav_title_play (src->dvdnav, segment->start); |
| ret = TRUE; |
| src->discont = TRUE; |
| } |
| } |
| g_mutex_unlock (src->dvd_lock); |
| } else if (segment->format == chapter_format) { |
| g_mutex_lock (src->dvd_lock); |
| if (src->running) { |
| gint32 title, chapters, x; |
| if (dvdnav_current_title_info (src->dvdnav, &title, &x) == |
| DVDNAV_STATUS_OK) { |
| if (segment->start + 1 == x) { |
| /* if already on the first part, don't try to get before it */ |
| if (segment->start == 0) { |
| dvdnav_part_play (src->dvdnav, title, 1); |
| } else { |
| dvdnav_prev_pg_search (src->dvdnav); |
| } |
| ret = TRUE; |
| src->discont = TRUE; |
| } else if (segment->start == x + 1) { |
| dvdnav_next_pg_search (src->dvdnav); |
| ret = TRUE; |
| src->discont = TRUE; |
| } else if (dvdnav_get_number_of_parts (src->dvdnav, title, |
| &chapters) == DVDNAV_STATUS_OK) { |
| if (segment->start > 0 && segment->start <= chapters) { |
| dvdnav_part_play (src->dvdnav, title, segment->start); |
| ret = TRUE; |
| src->discont = TRUE; |
| } |
| } |
| } |
| } |
| g_mutex_unlock (src->dvd_lock); |
| } |
| } |
| |
| if (ret) { |
| /* Force a highlight update */ |
| src->active_button = -1; |
| |
| if (src->flushing_seek) { |
| GstMessage *mouse_over_msg = NULL; |
| g_mutex_lock (src->dvd_lock); |
| src->flushing_seek = FALSE; |
| |
| gst_buffer_replace (&src->next_buf, NULL); |
| src->cur_start_ts = GST_CLOCK_TIME_NONE; |
| src->cur_end_ts = GST_CLOCK_TIME_NONE; |
| src->cur_vobu_base_ts = GST_CLOCK_TIME_NONE; |
| src->have_pci = FALSE; |
| if (src->nav_clock_id) { |
| gst_clock_id_unschedule (src->nav_clock_id); |
| gst_clock_id_unref (src->nav_clock_id); |
| src->nav_clock_id = NULL; |
| } |
| rsn_dvdsrc_clear_nav_blocks (src); |
| if (src->was_mouse_over) { |
| mouse_over_msg = |
| gst_navigation_message_new_mouse_over ((GstObject *) src, FALSE); |
| src->was_mouse_over = FALSE; |
| } |
| g_mutex_unlock (src->dvd_lock); |
| |
| if (mouse_over_msg) |
| gst_element_post_message (GST_ELEMENT_CAST (src), mouse_over_msg); |
| } |
| |
| GST_LOG_OBJECT (src, "Entering prepare_next_block after seek." |
| " Flushing = %d", src->flushing_seek); |
| while (src->cur_start_ts == GST_CLOCK_TIME_NONE) { |
| if (rsn_dvdsrc_prepare_next_block (src, FALSE) != GST_FLOW_OK) |
| goto fail; |
| if (src->cur_start_ts == GST_CLOCK_TIME_NONE) |
| gst_buffer_replace (&src->next_buf, NULL); |
| } |
| GST_LOG_OBJECT (src, "prepare_next_block after seek done"); |
| |
| segment->format = GST_FORMAT_TIME; |
| /* The first TS output: */ |
| segment->position = segment->start = src->cur_start_ts; |
| |
| /* time field = position is the 'logical' stream time here: */ |
| segment->time = 0; |
| if (src->cur_position != GST_CLOCK_TIME_NONE) |
| segment->time += src->cur_position; |
| if (src->cur_vobu_base_ts != GST_CLOCK_TIME_NONE) |
| segment->time += src->cur_vobu_base_ts; |
| |
| segment->stop = -1; |
| segment->duration = -1; |
| |
| GST_DEBUG_OBJECT (src, "seek completed. New start TS %" GST_TIME_FORMAT |
| " pos %" GST_TIME_FORMAT " (offset %" GST_TIME_FORMAT ")", |
| GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->time), |
| GST_TIME_ARGS ((GstClockTimeDiff) (segment->start - segment->time))); |
| |
| src->need_segment = FALSE; |
| } |
| |
| return ret; |
| fail: |
| GST_DEBUG_OBJECT (src, "Seek in format %d failed", segment->format); |
| return FALSE; |
| } |