/* GStreamer * Copyright (C) 2008 Jan Schmidt * * 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 #endif #include #include #include #include "resindvdsrc.h" GST_DEBUG_CATEGORY_STATIC (rsndvdsrc_debug); #define GST_CAT_DEFAULT rsndvdsrc_debug #define DEFAULT_DEVICE "/dev/dvd" #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 } RsnNavResult; typedef enum { RSN_NAV_ACTION_ACTIVATE, RSN_NAV_ACTION_LEFT, RSN_NAV_ACTION_RIGHT, RSN_NAV_ACTION_DOWN, RSN_NAV_ACTION_UP } RsnNavAction; enum { /* FILL ME */ LAST_SIGNAL }; enum { ARG_0, ARG_DEVICE }; 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 ("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); GST_BOILERPLATE_FULL (resinDvdSrc, rsn_dvdsrc, RsnPushSrc, RSN_TYPE_PUSH_SRC, rsn_dvdsrc_register_extra); 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 (RsnBaseSrc * bsrc); static gboolean rsn_dvdsrc_stop (RsnBaseSrc * bsrc); static gboolean rsn_dvdsrc_unlock (RsnBaseSrc * bsrc); static gboolean rsn_dvdsrc_unlock_stop (RsnBaseSrc * bsrc); static gboolean rsn_dvdsrc_is_seekable (RsnBaseSrc * bsrc); static gboolean rsn_dvdsrc_prepare_seek (RsnBaseSrc * bsrc, GstEvent * event, GstSegment * segment); static gboolean rsn_dvdsrc_do_seek (RsnBaseSrc * bsrc, GstSegment * segment); static GstStateChangeReturn rsn_dvdsrc_change_state (GstElement * element, GstStateChange transition); static void rsn_dvdsrc_prepare_spu_stream_event (resinDvdSrc * src, guint8 phys_stream, gboolean forced_only); static void rsn_dvdsrc_prepare_audio_stream_event (resinDvdSrc * src, 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 (RsnPushSrc * psrc, GstBuffer ** buf); static gboolean rsn_dvdsrc_src_event (RsnBaseSrc * basesrc, GstEvent * event); static gboolean rsn_dvdsrc_src_query (RsnBaseSrc * basesrc, GstQuery * query); static GstClockTime ifotime_to_gsttime (dvd_time_t * ifo_time); static GstClockTime ifotime_to_gsttime (dvd_time_t * ifo_time) { GstClockTime ts; guint frames; ts = 3600 * GST_SECOND * ifo_time->hour; ts += 60 * GST_SECOND * ifo_time->minute; ts += GST_SECOND * ifo_time->second; 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_base_init (gpointer gclass) { static GstElementDetails element_details = { "Resin DVD Src", "Source/DVD", "DVD source element", "Jan Schmidt " }; GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&src_factory)); gst_element_class_set_details (element_class, &element_details); } static void rsn_dvdsrc_class_init (resinDvdSrcClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; RsnBaseSrcClass *gstbasesrc_class; RsnPushSrcClass *gstpush_src_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesrc_class = GST_BASE_SRC_CLASS (klass); gstpush_src_class = GST_PUSH_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); gstpush_src_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)); } static void rsn_dvdsrc_init (resinDvdSrc * rsndvdsrc, resinDvdSrcClass * gclass) { 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 (); rsn_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); 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 (RsnBaseSrc * 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 (RsnBaseSrc * 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; 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean rsn_dvdsrc_start (RsnBaseSrc * bsrc) { resinDvdSrc *src = RESINDVDSRC (bsrc); 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; } src->running = TRUE; src->branching = FALSE; src->discont = TRUE; src->need_segment = 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->active_button = -1; 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 i; 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; } 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); for (i = 1; i <= n_vts; i++) { ifo_handle_t *ifo = ifoOpen (src->dvdread, i); if (!ifo) { GST_ERROR ("Can't open VTS %d", i); return FALSE; } GST_DEBUG ("VTS %d, Menu has %d audio %d subpictures. " "Title has %d and %d", i, 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, i), ifo->vtsi_mat, sizeof (vtsi_mat_t)); ifoClose (ifo); } return TRUE; } static gboolean rsn_dvdsrc_stop (RsnBaseSrc * bsrc) { resinDvdSrc *src = RESINDVDSRC (bsrc); gboolean ret = TRUE; 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; /* 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; } 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); 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; GstStructure *s; GstEvent *seg_event; GstSegment *segment = &(GST_BASE_SRC (src)->segment); if (src->in_still_state == FALSE) { g_print ("**** STILL FRAME. Duration %d ****\n", duration); /* Send a close-segment event, and a dvd-still start * event, then sleep */ s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "dvd-still", "still-state", G_TYPE_BOOLEAN, TRUE, NULL); still_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); gst_segment_set_last_stop (segment, GST_FORMAT_TIME, src->cur_end_ts); seg_event = gst_event_new_new_segment_full (TRUE, segment->rate, segment->applied_rate, segment->format, segment->start, segment->last_stop, segment->time); /* Grab any pending highlight event to send too */ hl_event = src->highlight_event; src->highlight_event = NULL; /* 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) { g_print ("Sending highlight event before still\n"); gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); } 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"); 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; g_get_current_time (&end_time); g_time_val_add (&end_time, duration * G_USEC_PER_SEC); /* FIXME: Implement timed stills by sleeping on the clock, possibly * in multiple steps if we get paused/unpaused */ g_mutex_unlock (src->dvd_lock); GST_LOG_OBJECT (src, "cond_timed_wait still"); 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 */ GST_LOG_OBJECT (src, "cond_timed_wait still over. Signalled, branching = %d", 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. * Later: We'll only do this if the still isn't interrupted: */ s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "dvd-still", "still-state", G_TYPE_BOOLEAN, FALSE, NULL); still_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); 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 GstFlowReturn rsn_dvdsrc_step (resinDvdSrc * src, gboolean have_dvd_lock) { GstFlowReturn ret = GST_FLOW_OK; dvdnav_status_t dvdnav_ret; guint8 *data; gint event, len; /* Allocate an output buffer if there isn't a pending one */ if (src->alloc_buf == NULL) src->alloc_buf = gst_buffer_new_and_alloc (DVD_VIDEO_LB_LEN); data = GST_BUFFER_DATA (src->alloc_buf); len = DVD_VIDEO_LB_LEN; dvdnav_ret = dvdnav_get_next_block (src->dvdnav, 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 */ src->next_buf = src->alloc_buf; src->next_is_nav_block = FALSE; src->next_nav_ts = GST_CLOCK_TIME_NONE; src->alloc_buf = NULL; 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) discont = TRUE; 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) { g_print ("NAV packet discont: cur_end_ts %" GST_TIME_FORMAT " != " " vobu_start_ptm: %" GST_TIME_FORMAT " base %" GST_TIME_FORMAT "\n", 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 */ 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_UNEXPECTED; break; case DVDNAV_STILL_FRAME: { dvdnav_still_event_t *info = (dvdnav_still_event_t *) 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 */ g_print ("****** FIXME: WAIT *****\n"); } 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 *) data; src->pgc_duration = MPEGTIME_TO_GSTTIME (event->pgc_length); src->cur_position = MPEGTIME_TO_GSTTIME (event->cell_start); 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)); break; } case DVDNAV_SPU_CLUT_CHANGE: rsn_dvdsrc_prepare_clut_change_event (src, (const guint32 *) data); break; case DVDNAV_VTS_CHANGE:{ dvdnav_vts_change_event_t *event = (dvdnav_vts_change_event_t *) data; if (dvdnav_is_domain_vmgm (src->dvdnav)) src->vts_n = 0; else src->vts_n = event->new_vtsN; src->in_menu = !dvdnav_is_domain_vtsm (src->dvdnav); if (!dvdnav_is_domain_fp (src->dvdnav)) rsn_dvdsrc_prepare_streamsinfo_event (src); break; } case DVDNAV_AUDIO_STREAM_CHANGE:{ dvdnav_audio_stream_change_event_t *event = (dvdnav_audio_stream_change_event_t *) data; GST_DEBUG_OBJECT (src, " physical: %d", event->physical); GST_DEBUG_OBJECT (src, " logical: %d", event->logical); rsn_dvdsrc_prepare_audio_stream_event (src, event->physical); break; } case DVDNAV_SPU_STREAM_CHANGE:{ dvdnav_spu_stream_change_event_t *event = (dvdnav_spu_stream_change_event_t *) data; gint phys_track = event->physical_wide & 0x1f; gboolean forced_only = (event->physical_wide & 0x80) ? TRUE : FALSE; rsn_dvdsrc_prepare_spu_stream_event (src, phys_track, forced_only); GST_DEBUG_OBJECT (src, " physical_wide: %d", event->physical_wide); GST_DEBUG_OBJECT (src, " physical_letterbox: %d", event->physical_letterbox); GST_DEBUG_OBJECT (src, " physical_pan_scan: %d", event->physical_pan_scan); GST_DEBUG_OBJECT (src, " logical: %d", event->logical); break; } case DVDNAV_HIGHLIGHT:{ rsn_dvdsrc_update_highlight (src); break; } case DVDNAV_HOP_CHANNEL: g_print ("Channel hop - User action\n"); src->need_segment = TRUE; break; case DVDNAV_NOP: break; default: GST_WARNING_OBJECT (src, "Unknown dvdnav event %d", event); break; } if (src->highlight_event && have_dvd_lock) { GstEvent *hl_event = src->highlight_event; src->highlight_event = NULL; g_mutex_unlock (src->dvd_lock); g_print ("Sending highlight event - button %d\n", src->active_button); gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); g_mutex_lock (src->dvd_lock); } return ret; read_error: GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Failed to read next DVD block. Error: %s", dvdnav_err_to_string (src->dvdnav))); return GST_FLOW_ERROR; internal_error: GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Internal error processing DVD commands. Error: %s", dvdnav_err_to_string (src->dvdnav))); return GST_FLOW_ERROR; branching: g_mutex_unlock (src->branch_lock); return GST_FLOW_WRONG_STATE; } 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 (RsnPushSrc * psrc, GstBuffer ** outbuf) { resinDvdSrc *src = RESINDVDSRC (psrc); 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; *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; 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) { g_print ("Pushing stream event\n"); gst_pad_push_event (GST_BASE_SRC_PAD (src), streams_event); } if (clut_event) { g_print ("Pushing clut event\n"); gst_pad_push_event (GST_BASE_SRC_PAD (src), clut_event); } /* Out of band events */ if (spu_select_event) { g_print ("Pushing spu_select event\n"); gst_pad_push_event (GST_BASE_SRC_PAD (src), spu_select_event); } if (audio_select_event) { g_print ("Pushing audio_select event\n"); gst_pad_push_event (GST_BASE_SRC_PAD (src), audio_select_event); } if (src->need_segment) { /* Seamless segment update */ GstEvent *seek; seek = gst_event_new_seek (segment->rate, rsndvd_format, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); gst_element_send_event (GST_ELEMENT (src), seek); src->need_segment = FALSE; } g_mutex_lock (src->dvd_lock); if (src->cur_end_ts != GST_CLOCK_TIME_NONE) gst_segment_set_last_stop (segment, GST_FORMAT_TIME, src->cur_end_ts); 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) { g_print ("Discont packet\n"); GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DISCONT); src->discont = FALSE; } } highlight_event = src->highlight_event; src->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) { g_print ("Pushing highlight event with TS %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (GST_EVENT_TIMESTAMP (highlight_event))); gst_pad_push_event (GST_BASE_SRC_PAD (src), highlight_event); } return ret; } static RsnNavResult rsn_dvdsrc_perform_button_action (resinDvdSrc * src, RsnNavAction 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 RSN_NAV_ACTION_ACTIVATE: if (dvdnav_button_activate (src->dvdnav, pci) == DVDNAV_STATUS_OK) result = RSN_NAV_RESULT_BRANCH; break; case RSN_NAV_ACTION_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; else result = RSN_NAV_RESULT_HIGHLIGHT; } break; case RSN_NAV_ACTION_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; else result = RSN_NAV_RESULT_HIGHLIGHT; } break; case RSN_NAV_ACTION_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; else result = RSN_NAV_RESULT_HIGHLIGHT; } break; case RSN_NAV_ACTION_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; else result = RSN_NAV_RESULT_HIGHLIGHT; } break; } if (result == RSN_NAV_RESULT_HIGHLIGHT) g_cond_broadcast (src->still_cond); return result; } static gboolean rsn_dvdsrc_handle_navigation_event (resinDvdSrc * src, GstEvent * event) { const GstStructure *s = gst_event_get_structure (event); const gchar *event_type; gboolean channel_hop = FALSE; gboolean have_lock = FALSE; GstEvent *hl_event = NULL; RsnNavResult nav_res = RSN_NAV_RESULT_NONE; if (s == NULL) return FALSE; event_type = gst_structure_get_string (s, "event"); if (event_type == NULL) return FALSE; if (strcmp (event_type, "key-press") == 0) { const gchar *key = gst_structure_get_string (s, "key"); if (key == NULL) 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_perform_button_action (src, RSN_NAV_ACTION_ACTIVATE); } else if (g_str_equal (key, "Left")) { nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_LEFT); } else if (g_str_equal (key, "Right")) { nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_RIGHT); } else if (g_str_equal (key, "Up")) { nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_UP); } else if (g_str_equal (key, "Down")) { nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_DOWN); } else if (g_str_equal (key, "m")) { if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Escape) == DVDNAV_STATUS_OK) channel_hop = TRUE; } else if (g_str_equal (key, "t")) { if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Title) == DVDNAV_STATUS_OK) channel_hop = TRUE; } else if (g_str_equal (key, "r")) { if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Root) == DVDNAV_STATUS_OK) channel_hop = TRUE; } else if (g_str_equal (key, "comma")) { gint title = 0; gint part = 0; if (dvdnav_current_title_info (src->dvdnav, &title, &part) && title > 0 && part > 1) { if (dvdnav_part_play (src->dvdnav, title, part - 1) == DVDNAV_STATUS_ERR) dvdnav_prev_pg_search (src->dvdnav); channel_hop = TRUE; } } else if (g_str_equal (key, "period")) { dvdnav_next_pg_search (src->dvdnav); channel_hop = TRUE; } else { g_print ("Unknown keypress: %s\n", key); } } else if (strcmp (event_type, "mouse-move") == 0) { gdouble x, y; if (!gst_structure_get_double (s, "pointer_x", &x) || !gst_structure_get_double (s, "pointer_y", &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; } } else if (strcmp (event_type, "mouse-button-release") == 0) { gdouble x, y; if (!gst_structure_get_double (s, "pointer_x", &x) || !gst_structure_get_double (s, "pointer_y", &y)) 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; } } if (have_lock) { if (nav_res != RSN_NAV_RESULT_NONE) { if (nav_res == RSN_NAV_RESULT_BRANCH) { src->active_highlight = TRUE; channel_hop = TRUE; } rsn_dvdsrc_update_highlight (src); } if (channel_hop) { GstEvent *seek; g_print ("flush and jump\n"); 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) { g_print ("Highlight change - button: %d\n", 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); 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; g_mutex_unlock (src->dvd_lock); if (hl_event) { g_print ("Highlight change - button: %d\n", src->active_button); gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); } } return TRUE; not_running: 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 phys_stream) { GstStructure *s; GstEvent *e; if (phys_stream == src->cur_audio_phys_stream) return; src->cur_audio_phys_stream = phys_stream; g_print ("Preparing audio change, phys %d\n", phys_stream); s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "dvd-set-audio-track", "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 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; g_print ("Preparing SPU change, phys %d forced %d\n", phys_stream, forced_only); s = gst_structure_new ("application/x-gst-dvd", "event", G_TYPE_STRING, "dvd-set-subpicture-track", "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; GstStructure *s; GstEvent *e; gint i; gchar lang_code[3] = { '\0', '\0', '\0' }; gchar *t; gboolean is_widescreen; gboolean have_audio; if (src->vts_attrs == NULL || src->vts_n >= src->vts_attrs->len) { if (src->vts_attrs) GST_ERROR_OBJECT (src, "No stream info for VTS %d (have %d)", src->vts_n, src->vts_attrs->len); else GST_ERROR_OBJECT (src, "No stream info"); return FALSE; } if (src->vts_n == 0) { /* 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 = &g_array_index (src->vts_attrs, vtsi_mat_t, 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 = &g_array_index (src->vts_attrs, vtsi_mat_t, 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; } /* 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 */ have_audio = FALSE; for (i = 0; i < n_audio; i++) { const audio_attr_t *a = a_attrs + i; #if 1 /* FIXME: Only output A52 streams for now, until the decoder switching * is ready */ if (a->audio_format != 0) { GST_DEBUG_OBJECT (src, "Ignoring non-A52 stream %d, format %d", i, (int) a->audio_format); continue; } #endif have_audio = TRUE; t = g_strdup_printf ("audio-%d-format", i); gst_structure_set (s, t, G_TYPE_INT, (int) a->audio_format, NULL); g_free (t); GST_DEBUG_OBJECT (src, "Audio stream %d is format %d", i, (int) a->audio_format); if (a->lang_type) { 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, lang_code); } if (have_audio == FALSE) { /* Always create at least one audio stream */ gst_structure_set (s, "audio-0-format", G_TYPE_INT, (int) 0, NULL); } /* subpictures */ if (n_subp == 0) { /* Always create at least one subpicture stream */ gst_structure_set (s, "subpicture-0-format", G_TYPE_INT, (int) 0, NULL); gst_structure_set (s, "subpicture-0-language", G_TYPE_STRING, "MENU", NULL); } for (i = 0; i < n_subp; i++) { const subp_attr_t *u = s_attrs + i; 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) { 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 (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, "pushing 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 = 0; GstEvent *event = NULL; GstStructure *s; if (src->have_pci) { if (dvdnav_get_current_highlight (src->dvdnav, &button) != DVDNAV_STATUS_OK) { GST_ELEMENT_ERROR (src, LIBRARY, FAILED, (NULL), ("dvdnav_get_current_highlight: %s", dvdnav_err_to_string (src->dvdnav))); return; } if (pci->hli.hl_gi.hli_ss == 0 || (button > pci->hli.hl_gi.btn_ns) || (button < 1)) { /* button is out of the range of possible buttons. */ 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; } return; } if (src->active_highlight) mode = 1; if (dvdnav_get_highlight_area (pci, button, mode, &area) != DVDNAV_STATUS_OK) { GST_ELEMENT_ERROR (src, LIBRARY, FAILED, (NULL), ("dvdnav_get_highlight_area: %s", dvdnav_err_to_string (src->dvdnav))); 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) { g_print ("Setting highlight. Button %d @ %d,%d active %d palette 0x%x " "(from button %d @ %d,%d palette 0x%x)\n", button, src->area.sx, src->area.sy, mode, src->area.palette, src->active_button, area.sx, area.sy, 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 == 0) { /* 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; } } 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; navRead_PCI (&src->cur_pci, GST_BUFFER_DATA (nav_buf) + 0x2d); src->have_pci = TRUE; forced_button = src->cur_pci.hli.hl_gi.fosl_btnn & 0x3f; if (forced_button != 0) 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) g_cond_broadcast (src->still_cond); } static void rsn_dvdsrc_clear_nav_blocks (resinDvdSrc * src) { 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 for 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; } static void rsn_dvdsrc_schedule_nav_cb (resinDvdSrc * src, RsnDvdPendingNav * next_nav) { GstClock *clock; GstClockTime base_ts; GST_OBJECT_LOCK (src); if (!src->in_playing) { GST_LOG_OBJECT (src, "Not scheduling NAV block - state != PLAYING"); GST_OBJECT_UNLOCK (src); return; /* Not in playing state yet */ } 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)); gst_clock_id_wait_async (src->nav_clock_id, rsn_dvdsrc_nav_clock_cb, src); gst_object_unref (clock); } 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) { GST_LOG_OBJECT (src, "NAV callback already scheduled"); return; /* Something already scheduled */ } if (src->pending_nav_blocks == NULL) { GST_LOG_OBJECT (src, "No NAV blocks to schedule"); return; /* No nav blocks available yet */ } next_nav = (RsnDvdPendingNav *) src->pending_nav_blocks->data; rsn_dvdsrc_schedule_nav_cb (src, next_nav); } /* Use libdvdread to read and cache info from the IFO file about * streams in each VTS */ static gboolean rsn_dvdsrc_src_event (RsnBaseSrc * basesrc, GstEvent * event) { resinDvdSrc *src = RESINDVDSRC (basesrc); gboolean res; GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_NAVIGATION: res = rsn_dvdsrc_handle_navigation_event (src, event); break; default: res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event); break; } return res; } 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; default: break; } return ret; } static gboolean rsn_dvdsrc_src_query (RsnBaseSrc * 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_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; default: res = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query); break; } return res; } static gboolean rsn_dvdsrc_is_seekable (RsnBaseSrc * bsrc) { return TRUE; } static gboolean rsn_dvdsrc_prepare_seek (RsnBaseSrc * bsrc, GstEvent * event, GstSegment * segment) { resinDvdSrc *src = RESINDVDSRC (bsrc); GstSeekType cur_type, stop_type; gint64 cur, stop; GstSeekFlags flags; GstFormat seek_format; gdouble rate; gboolean update; gst_event_parse_seek (event, &rate, &seek_format, &flags, &cur_type, &cur, &stop_type, &stop); 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_set_seek (segment, rate, seek_format, flags, cur_type, cur, stop_type, stop, &update); if (flags & GST_SEEK_FLAG_FLUSH) src->flushing_seek = TRUE; return TRUE; } /* Don't allow bytes seeks - angle, time, chapter, title only is the plan */ if (seek_format == GST_FORMAT_BYTES) return FALSE; /* Let basesrc handle other formats for now. FIXME: Implement angle */ return GST_BASE_SRC_CLASS (parent_class)->prepare_seek_segment (bsrc, event, segment); } static gboolean rsn_dvdsrc_do_seek (RsnBaseSrc * bsrc, GstSegment * segment) { resinDvdSrc *src = RESINDVDSRC (bsrc); gboolean ret = FALSE; if (segment->format == rsndvd_format) { /* 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; } else { /* FIXME: Handle other formats: Time, title, chapter, angle */ /* HACK to make initial seek work: */ if (segment->format == GST_FORMAT_TIME) { ret = TRUE; src->discont = TRUE; } 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) { 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) { 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); g_mutex_unlock (src->dvd_lock); } GST_LOG_OBJECT (src, "Entering prepare_next_block after seek"); if (rsn_dvdsrc_prepare_next_block (src, FALSE) != GST_FLOW_OK) goto fail; GST_LOG_OBJECT (src, "prepare_next_block after seek done"); segment->format = GST_FORMAT_TIME; /* The first TS output: */ segment->last_stop = 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; g_print ("seek completed. New start TS %" GST_TIME_FORMAT " pos %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->time)); src->need_segment = FALSE; } return ret; fail: g_print ("Seek in format %d failed\n", segment->format); return FALSE; } gboolean rsndvdsrc_init (GstPlugin * plugin) { gboolean res; res = gst_element_register (plugin, "rsndvdsrc", GST_RANK_NONE, RESIN_TYPE_DVDSRC); return res; }