blob: ab89be709b40eb826e50c97e7c8d80567e225484 [file] [log] [blame]
/* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.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 <gst/tag/tag.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_static_pad_template (gstelement_class, &src_factory);
gst_element_class_set_static_metadata (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);
g_mutex_init (&rsndvdsrc->dvd_lock);
g_mutex_init (&rsndvdsrc->branch_lock);
rsndvdsrc->branching = FALSE;
g_cond_init (&rsndvdsrc->still_cond);
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_clear (&src->dvd_lock);
g_mutex_clear (&src->branch_lock);
g_cond_clear (&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;
const char *disc_name;
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);
}
}
/* Get disc name and convert to UTF-8 */
g_free (src->disc_name);
dvdnav_get_title_string (src->dvdnav, &disc_name);
if (disc_name != NULL && *disc_name != '\0')
src->disc_name = gst_tag_freeform_string_to_utf8 (disc_name, -1, NULL);
else
src->disc_name = NULL;
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;
}
g_free (src->disc_name);
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;
GST_LOG_OBJECT (src, "Segment position now %" GST_TIME_FORMAT,
GST_TIME_ARGS (segment->position));
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 {
gboolean was_signalled;
if (src->still_time_remaining > 0) {
gint64 end_time;
end_time =
g_get_monotonic_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_wait_until (&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 */
gint64 cur_time;
GstClockTimeDiff remain;
cur_time = g_get_monotonic_time ();
remain = (end_time - cur_time) * 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);
if (segment->stop != -1 && segment->position > segment->stop)
segment->stop = segment->position;
GST_LOG_OBJECT (src, "Extended segment position to %" GST_TIME_FORMAT,
GST_TIME_ARGS (segment->position));
}
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 %" GST_STIME_FORMAT " %s",
GST_TIME_ARGS (new_start_ptm), GST_TIME_ARGS (new_end_ptm),
GST_STIME_ARGS (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 elapsed_time = 0;
if (src->cur_position != GST_CLOCK_TIME_NONE)
elapsed_time += src->cur_position;
if (src->cur_vobu_base_ts != GST_CLOCK_TIME_NONE)
elapsed_time += src->cur_vobu_base_ts;
GST_DEBUG_OBJECT (src,
"Starting seamless segment update to %" GST_TIME_FORMAT " -> %"
GST_TIME_FORMAT " VOBU %" GST_TIME_FORMAT " time %" 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 (elapsed_time));
gst_base_src_new_seamless_segment (GST_BASE_SRC (src),
src->cur_start_ts, -1, elapsed_time);
src->need_segment = FALSE;
}
if (src->cur_end_ts != GST_CLOCK_TIME_NONE) {
segment->position = src->cur_end_ts;
if (segment->stop != -1 && segment->position > segment->stop)
segment->stop = segment->position;
GST_LOG_OBJECT (src, "Segment position now %" GST_TIME_FORMAT,
GST_TIME_ARGS (segment->position));
}
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 = MAX (1, vts_attr->nr_of_vtsm_audio_streams);
s_attrs = &vts_attr->vtsm_subp_attr;
n_subp = MAX (1, 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, &times, &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));
free (times);
}
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, </