| /* GStreamer DVD title source | |
| * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> | |
| * Copyright (C) 2001 Billy Biggs <vektor@dumbterm.net>. | |
| * Copyright (C) 2006 Tim-Philipp Müller <tim centricular 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 | |
| #ifdef HAVE_STDINT_H | |
| #include <stdint.h> | |
| #endif | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include "dvdreadsrc.h" | |
| #include <gmodule.h> | |
| #include <glib/gi18n-lib.h> | |
| GST_DEBUG_CATEGORY_STATIC (gstgst_dvd_read_src_debug); | |
| #define GST_CAT_DEFAULT (gstgst_dvd_read_src_debug) | |
| enum | |
| { | |
| ARG_0, | |
| ARG_DEVICE, | |
| ARG_TITLE, | |
| ARG_CHAPTER, | |
| ARG_ANGLE | |
| }; | |
| static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", | |
| GST_PAD_SRC, | |
| GST_PAD_ALWAYS, | |
| GST_STATIC_CAPS ("video/mpeg, mpegversion=2, systemstream=(boolean)true")); | |
| static GstFormat title_format; | |
| static GstFormat angle_format; | |
| static GstFormat sector_format; | |
| static GstFormat chapter_format; | |
| static gboolean gst_dvd_read_src_start (GstBaseSrc * basesrc); | |
| static gboolean gst_dvd_read_src_stop (GstBaseSrc * basesrc); | |
| static GstFlowReturn gst_dvd_read_src_create (GstPushSrc * pushsrc, | |
| GstBuffer ** buf); | |
| static gboolean gst_dvd_read_src_src_query (GstBaseSrc * basesrc, | |
| GstQuery * query); | |
| static gboolean gst_dvd_read_src_src_event (GstBaseSrc * basesrc, | |
| GstEvent * event); | |
| static gboolean gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, | |
| gint angle); | |
| static gboolean gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src, | |
| gint chapter); | |
| static gboolean gst_dvd_read_src_goto_sector (GstDvdReadSrc * src, gint angle); | |
| static void gst_dvd_read_src_set_property (GObject * object, guint prop_id, | |
| const GValue * value, GParamSpec * pspec); | |
| static void gst_dvd_read_src_get_property (GObject * object, guint prop_id, | |
| GValue * value, GParamSpec * pspec); | |
| static GstEvent *gst_dvd_read_src_make_clut_change_event (GstDvdReadSrc * src, | |
| const guint32 * clut); | |
| static gboolean gst_dvd_read_src_get_size (GstDvdReadSrc * src, gint64 * size); | |
| static gboolean gst_dvd_read_src_do_seek (GstBaseSrc * src, GstSegment * s); | |
| static gint64 gst_dvd_read_src_convert_timecode (dvd_time_t * time); | |
| static gint gst_dvd_read_src_get_next_cell (GstDvdReadSrc * src, | |
| pgc_t * pgc, gint cell); | |
| static GstClockTime gst_dvd_read_src_get_time_for_sector (GstDvdReadSrc * src, | |
| guint sector); | |
| static gint gst_dvd_read_src_get_sector_from_time (GstDvdReadSrc * src, | |
| GstClockTime ts); | |
| static void gst_dvd_read_src_uri_handler_init (gpointer g_iface, | |
| gpointer iface_data); | |
| static gboolean dvdread_element_init (GstPlugin * plugin); | |
| #define gst_dvd_read_src_parent_class parent_class | |
| G_DEFINE_TYPE_WITH_CODE (GstDvdReadSrc, gst_dvd_read_src, GST_TYPE_PUSH_SRC, | |
| G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, | |
| gst_dvd_read_src_uri_handler_init)); | |
| GST_ELEMENT_REGISTER_DEFINE_CUSTOM (dvdreadsrc, dvdread_element_init); | |
| static void | |
| gst_dvd_read_src_finalize (GObject * object) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (object); | |
| g_free (src->location); | |
| G_OBJECT_CLASS (parent_class)->finalize (object); | |
| } | |
| static void | |
| gst_dvd_read_src_init (GstDvdReadSrc * src) | |
| { | |
| src->dvd = NULL; | |
| src->vts_file = NULL; | |
| src->vmg_file = NULL; | |
| src->dvd_title = NULL; | |
| src->location = g_strdup ("/dev/dvd"); | |
| src->first_seek = TRUE; | |
| src->new_seek = TRUE; | |
| src->new_cell = TRUE; | |
| src->change_cell = FALSE; | |
| src->uri_title = 1; | |
| src->uri_chapter = 1; | |
| src->uri_angle = 1; | |
| src->title_lang_event_pending = NULL; | |
| src->pending_clut_event = NULL; | |
| gst_pad_use_fixed_caps (GST_BASE_SRC_PAD (src)); | |
| gst_pad_set_caps (GST_BASE_SRC_PAD (src), | |
| gst_static_pad_template_get_caps (&srctemplate)); | |
| } | |
| static gboolean | |
| gst_dvd_read_src_is_seekable (GstBaseSrc * src) | |
| { | |
| return TRUE; | |
| } | |
| static void | |
| gst_dvd_read_src_class_init (GstDvdReadSrcClass * klass) | |
| { | |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); | |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); | |
| GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass); | |
| GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass); | |
| gobject_class->finalize = gst_dvd_read_src_finalize; | |
| gobject_class->set_property = gst_dvd_read_src_set_property; | |
| gobject_class->get_property = gst_dvd_read_src_get_property; | |
| g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE, | |
| g_param_spec_string ("device", "Device", | |
| "DVD device location", NULL, | |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TITLE, | |
| g_param_spec_int ("title", "title", "title", | |
| 1, 999, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHAPTER, | |
| g_param_spec_int ("chapter", "chapter", "chapter", | |
| 1, 999, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ANGLE, | |
| g_param_spec_int ("angle", "angle", "angle", | |
| 1, 999, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); | |
| gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); | |
| gst_element_class_set_static_metadata (gstelement_class, "DVD Source", | |
| "Source/File/DVD", | |
| "Access a DVD title/chapter/angle using libdvdread", | |
| "Erik Walthinsen <omega@cse.ogi.edu>"); | |
| gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_dvd_read_src_start); | |
| gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_dvd_read_src_stop); | |
| gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_dvd_read_src_src_query); | |
| gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_dvd_read_src_src_event); | |
| gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_dvd_read_src_do_seek); | |
| gstbasesrc_class->is_seekable = | |
| GST_DEBUG_FUNCPTR (gst_dvd_read_src_is_seekable); | |
| gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_dvd_read_src_create); | |
| title_format = gst_format_register ("title", "DVD title"); | |
| angle_format = gst_format_register ("angle", "DVD angle"); | |
| sector_format = gst_format_register ("sector", "DVD sector"); | |
| chapter_format = gst_format_register ("chapter", "DVD chapter"); | |
| } | |
| static gboolean | |
| gst_dvd_read_src_start (GstBaseSrc * basesrc) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc); | |
| g_return_val_if_fail (src->location != NULL, FALSE); | |
| GST_DEBUG_OBJECT (src, "Opening DVD '%s'", src->location); | |
| if ((src->dvd = DVDOpen (src->location)) == NULL) | |
| goto open_failed; | |
| /* Load the video manager to find out the information about the titles */ | |
| GST_DEBUG_OBJECT (src, "Loading VMG info"); | |
| if (!(src->vmg_file = ifoOpen (src->dvd, 0))) | |
| goto ifo_open_failed; | |
| src->tt_srpt = src->vmg_file->tt_srpt; | |
| src->title = src->uri_title - 1; | |
| src->chapter = src->uri_chapter - 1; | |
| src->angle = src->uri_angle - 1; | |
| if (!gst_dvd_read_src_goto_title (src, src->title, src->angle)) | |
| goto title_open_failed; | |
| if (!gst_dvd_read_src_goto_chapter (src, src->chapter)) | |
| goto chapter_open_failed; | |
| src->new_seek = FALSE; | |
| src->change_cell = TRUE; | |
| src->first_seek = TRUE; | |
| return TRUE; | |
| /* ERRORS */ | |
| open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD")), | |
| ("DVDOpen(%s) failed: %s", src->location, g_strerror (errno))); | |
| return FALSE; | |
| } | |
| ifo_open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD")), | |
| ("ifoOpen() failed: %s", g_strerror (errno))); | |
| return FALSE; | |
| } | |
| title_open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD title %d"), src->uri_title), (NULL)); | |
| return FALSE; | |
| } | |
| chapter_open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Failed to go to chapter %d of DVD title %d"), | |
| src->uri_chapter, src->uri_title), (NULL)); | |
| return FALSE; | |
| } | |
| } | |
| static gboolean | |
| gst_dvd_read_src_stop (GstBaseSrc * basesrc) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc); | |
| if (src->vts_file) { | |
| ifoClose (src->vts_file); | |
| src->vts_file = NULL; | |
| } | |
| if (src->vmg_file) { | |
| ifoClose (src->vmg_file); | |
| src->vmg_file = NULL; | |
| } | |
| if (src->dvd_title) { | |
| DVDCloseFile (src->dvd_title); | |
| src->dvd_title = NULL; | |
| } | |
| if (src->dvd) { | |
| DVDClose (src->dvd); | |
| src->dvd = NULL; | |
| } | |
| src->new_cell = TRUE; | |
| src->new_seek = TRUE; | |
| src->change_cell = FALSE; | |
| src->chapter = 0; | |
| src->title = 0; | |
| src->need_newsegment = TRUE; | |
| src->vts_tmapt = NULL; | |
| if (src->title_lang_event_pending) { | |
| gst_event_unref (src->title_lang_event_pending); | |
| src->title_lang_event_pending = NULL; | |
| } | |
| if (src->pending_clut_event) { | |
| gst_event_unref (src->pending_clut_event); | |
| src->pending_clut_event = NULL; | |
| } | |
| if (src->chapter_starts) { | |
| g_free (src->chapter_starts); | |
| src->chapter_starts = NULL; | |
| } | |
| GST_LOG_OBJECT (src, "closed DVD"); | |
| return TRUE; | |
| } | |
| static void | |
| cur_title_get_chapter_pgc (GstDvdReadSrc * src, gint chapter, gint * p_pgn, | |
| gint * p_pgc_id, pgc_t ** p_pgc) | |
| { | |
| pgc_t *pgc; | |
| gint pgn, pgc_id; | |
| g_assert (chapter >= 0 && chapter < src->num_chapters); | |
| pgc_id = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter].pgcn; | |
| pgn = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter].pgn; | |
| pgc = src->vts_file->vts_pgcit->pgci_srp[pgc_id - 1].pgc; | |
| *p_pgn = pgn; | |
| *p_pgc_id = pgc_id; | |
| *p_pgc = pgc; | |
| } | |
| static void | |
| cur_title_get_chapter_bounds (GstDvdReadSrc * src, gint chapter, | |
| gint * p_first_cell, gint * p_last_cell) | |
| { | |
| pgc_t *pgc; | |
| gint pgn, pgc_id, pgn_next_ch; | |
| g_assert (chapter >= 0 && chapter < src->num_chapters); | |
| cur_title_get_chapter_pgc (src, chapter, &pgn, &pgc_id, &pgc); | |
| *p_first_cell = pgc->program_map[pgn - 1] - 1; | |
| /* last cell is used as a 'up to boundary', not 'up to and including', | |
| * i.e. it is the first cell not included in the chapter range */ | |
| if (chapter == (src->num_chapters - 1)) { | |
| *p_last_cell = pgc->nr_of_cells; | |
| } else { | |
| pgn_next_ch = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter + 1].pgn; | |
| *p_last_cell = pgc->program_map[pgn_next_ch - 1] - 1; | |
| } | |
| GST_DEBUG_OBJECT (src, "Chapter %d bounds: %d %d (within %d cells)", | |
| chapter, *p_first_cell, *p_last_cell, pgc->nr_of_cells); | |
| } | |
| static gboolean | |
| gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src, gint chapter) | |
| { | |
| gint i; | |
| const guint8 *palette; | |
| /* make sure the chapter number is valid for this title */ | |
| if (chapter < 0 || chapter >= src->num_chapters) { | |
| GST_WARNING_OBJECT (src, "invalid chapter %d (only %d available)", | |
| chapter, src->num_chapters); | |
| chapter = CLAMP (chapter, 0, src->num_chapters - 1); | |
| } | |
| /* determine which program chain we want to watch. This is | |
| * based on the chapter number */ | |
| cur_title_get_chapter_pgc (src, chapter, &src->pgn, &src->pgc_id, | |
| &src->cur_pgc); | |
| cur_title_get_chapter_bounds (src, chapter, &src->start_cell, | |
| &src->last_cell); | |
| GST_LOG_OBJECT (src, "Opened chapter %d - cell %d-%d", chapter + 1, | |
| src->start_cell, src->last_cell); | |
| /* retrieve position */ | |
| src->cur_pack = 0; | |
| for (i = 0; i < chapter; i++) { | |
| gint c1, c2; | |
| cur_title_get_chapter_bounds (src, i, &c1, &c2); | |
| while (c1 < c2) { | |
| src->cur_pack += | |
| src->cur_pgc->cell_playback[c1].last_sector - | |
| src->cur_pgc->cell_playback[c1].first_sector; | |
| ++c1; | |
| } | |
| } | |
| /* prepare reading for new cell */ | |
| src->new_cell = TRUE; | |
| src->next_cell = src->start_cell; | |
| src->chapter = chapter; | |
| if (src->pending_clut_event) | |
| gst_event_unref (src->pending_clut_event); | |
| /* Work around GCC 9 compiler warning here about taking address of packed | |
| * member, which may result in an unaligned pointer access */ | |
| palette = (const guint8 *) src->cur_pgc->palette; | |
| src->pending_clut_event = | |
| gst_dvd_read_src_make_clut_change_event (src, (const guint32 *) palette); | |
| return TRUE; | |
| } | |
| static void | |
| gst_dvd_read_src_get_chapter_starts (GstDvdReadSrc * src) | |
| { | |
| GstClockTime uptohere; | |
| guint c; | |
| g_free (src->chapter_starts); | |
| src->chapter_starts = g_new (GstClockTime, src->num_chapters); | |
| uptohere = (GstClockTime) 0; | |
| for (c = 0; c < src->num_chapters; ++c) { | |
| GstClockTime chapter_duration = 0; | |
| gint cell_start, cell_end, cell; | |
| gint pgn, pgc_id; | |
| pgc_t *pgc; | |
| cur_title_get_chapter_pgc (src, c, &pgn, &pgc_id, &pgc); | |
| cur_title_get_chapter_bounds (src, c, &cell_start, &cell_end); | |
| cell = cell_start; | |
| while (cell < cell_end) { | |
| dvd_time_t *cell_duration; | |
| cell_duration = &pgc->cell_playback[cell].playback_time; | |
| chapter_duration += gst_dvd_read_src_convert_timecode (cell_duration); | |
| cell = gst_dvd_read_src_get_next_cell (src, pgc, cell); | |
| } | |
| src->chapter_starts[c] = uptohere; | |
| GST_INFO_OBJECT (src, "[%02u] Chapter %02u starts at %" GST_TIME_FORMAT | |
| ", dur = %" GST_TIME_FORMAT ", cells %d-%d", src->title + 1, c + 1, | |
| GST_TIME_ARGS (uptohere), GST_TIME_ARGS (chapter_duration), | |
| cell_start, cell_end); | |
| uptohere += chapter_duration; | |
| } | |
| } | |
| static gboolean | |
| gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle) | |
| { | |
| GstStructure *s; | |
| gchar lang_code[3] = { '\0', '\0', '\0' }, *t; | |
| pgc_t *pgc0; | |
| gint title_set_nr; | |
| gint num_titles; | |
| gint pgn0, pgc0_id; | |
| gint i; | |
| /* make sure our title number is valid */ | |
| num_titles = src->tt_srpt->nr_of_srpts; | |
| GST_INFO_OBJECT (src, "There are %d titles on this DVD", num_titles); | |
| if (title < 0 || title >= num_titles) | |
| goto invalid_title; | |
| src->num_chapters = src->tt_srpt->title[title].nr_of_ptts; | |
| GST_INFO_OBJECT (src, "Title %d has %d chapters", title + 1, | |
| src->num_chapters); | |
| /* make sure the angle number is valid for this title */ | |
| src->num_angles = src->tt_srpt->title[title].nr_of_angles; | |
| GST_LOG_OBJECT (src, "Title %d has %d angles", title + 1, src->num_angles); | |
| if (angle < 0 || angle >= src->num_angles) { | |
| GST_WARNING_OBJECT (src, "Invalid angle %d (only %d available)", | |
| angle, src->num_angles); | |
| angle = CLAMP (angle, 0, src->num_angles - 1); | |
| } | |
| /* load the VTS information for the title set our title is in */ | |
| title_set_nr = src->tt_srpt->title[title].title_set_nr; | |
| src->vts_file = ifoOpen (src->dvd, title_set_nr); | |
| if (src->vts_file == NULL) | |
| goto ifo_open_failed; | |
| src->ttn = src->tt_srpt->title[title].vts_ttn; | |
| src->vts_ptt_srpt = src->vts_file->vts_ptt_srpt; | |
| /* interactive title? */ | |
| if (src->num_chapters > 0 && | |
| src->vts_ptt_srpt->title[src->ttn - 1].ptt[0].pgn == 0) { | |
| goto commands_only_pgc; | |
| } | |
| /* we've got enough info, time to open the title set data */ | |
| src->dvd_title = DVDOpenFile (src->dvd, title_set_nr, DVD_READ_TITLE_VOBS); | |
| if (src->dvd_title == NULL) | |
| goto title_open_failed; | |
| GST_INFO_OBJECT (src, "Opened title %d, angle %d", title + 1, angle); | |
| src->title = title; | |
| src->angle = angle; | |
| /* build event */ | |
| if (src->title_lang_event_pending) { | |
| gst_event_unref (src->title_lang_event_pending); | |
| src->title_lang_event_pending = NULL; | |
| } | |
| s = gst_structure_new ("application/x-gst-dvd", | |
| "event", G_TYPE_STRING, "dvd-lang-codes", NULL); | |
| /* so we can filter out invalid/unused streams (same for all chapters) */ | |
| cur_title_get_chapter_pgc (src, 0, &pgn0, &pgc0_id, &pgc0); | |
| /* audio */ | |
| for (i = 0; i < src->vts_file->vtsi_mat->nr_of_vts_audio_streams; i++) { | |
| const audio_attr_t *a; | |
| /* audio stream present? */ | |
| if (pgc0 != NULL && (pgc0->audio_control[i] & 0x8000) == 0) | |
| continue; | |
| a = &src->vts_file->vtsi_mat->vts_audio_attr[i]; | |
| t = g_strdup_printf ("audio-%d-format", i); | |
| gst_structure_set (s, t, G_TYPE_INT, (int) a->audio_format, NULL); | |
| g_free (t); | |
| t = g_strdup_printf ("audio-%d-stream", i); | |
| gst_structure_set (s, t, G_TYPE_INT, (int) i, NULL); | |
| g_free (t); | |
| 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); | |
| } else { | |
| lang_code[0] = '\0'; | |
| } | |
| GST_INFO_OBJECT (src, "[%02d] Audio %02d: lang='%s', format=%d", | |
| src->title + 1, i, lang_code, (gint) a->audio_format); | |
| } | |
| /* subtitle */ | |
| for (i = 0; i < src->vts_file->vtsi_mat->nr_of_vts_subp_streams; i++) { | |
| const subp_attr_t *u; | |
| const video_attr_t *v; | |
| gint sid; | |
| /* subpicture stream present? */ | |
| if (pgc0 != NULL && (pgc0->subp_control[i] & 0x80000000) == 0) | |
| continue; | |
| u = &src->vts_file->vtsi_mat->vts_subp_attr[i]; | |
| v = &src->vts_file->vtsi_mat->vts_video_attr; | |
| sid = i; | |
| if (pgc0 != NULL) { | |
| if (v->display_aspect_ratio == 0) /* 4:3 */ | |
| sid = (pgc0->subp_control[i] >> 24) & 0x1f; | |
| else if (v->display_aspect_ratio == 3) /* 16:9 */ | |
| sid = (pgc0->subp_control[i] >> 8) & 0x1f; | |
| } | |
| if (u->type) { | |
| t = g_strdup_printf ("subpicture-%d-language", i); | |
| 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); | |
| g_free (t); | |
| t = g_strdup_printf ("subpicture-%d-stream", i); | |
| gst_structure_set (s, t, G_TYPE_INT, (int) sid, 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); | |
| } else { | |
| lang_code[0] = '\0'; | |
| } | |
| GST_INFO_OBJECT (src, "[%02d] Subtitle %02d: lang='%s', type=%d", | |
| src->title + 1, sid, lang_code, u->type); | |
| } | |
| src->title_lang_event_pending = | |
| gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); | |
| /* dump seek tables */ | |
| src->vts_tmapt = src->vts_file->vts_tmapt; | |
| if (src->vts_tmapt) { | |
| gint i, j; | |
| GST_LOG_OBJECT (src, "nr_of_tmaps = %d", src->vts_tmapt->nr_of_tmaps); | |
| for (i = 0; i < src->vts_tmapt->nr_of_tmaps; ++i) { | |
| GST_LOG_OBJECT (src, "======= Table %d ===================", i); | |
| GST_LOG_OBJECT (src, "Offset relative to VTS_TMAPTI: %d", | |
| src->vts_tmapt->tmap_offset[i]); | |
| GST_LOG_OBJECT (src, "Time unit (seconds) : %d", | |
| src->vts_tmapt->tmap[i].tmu); | |
| GST_LOG_OBJECT (src, "Number of entries : %d", | |
| src->vts_tmapt->tmap[i].nr_of_entries); | |
| for (j = 0; j < src->vts_tmapt->tmap[i].nr_of_entries; j++) { | |
| guint64 time; | |
| time = (guint64) src->vts_tmapt->tmap[i].tmu * (j + 1) * GST_SECOND; | |
| GST_LOG_OBJECT (src, "Time: %" GST_TIME_FORMAT " VOBU " | |
| "Sector: 0x%08x %s", GST_TIME_ARGS (time), | |
| src->vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff, | |
| (src->vts_tmapt->tmap[i].map_ent[j] >> 31) ? "discontinuity" : ""); | |
| } | |
| } | |
| } else { | |
| GST_WARNING_OBJECT (src, "no vts_tmapt - seeking will suck"); | |
| } | |
| gst_dvd_read_src_get_chapter_starts (src); | |
| return TRUE; | |
| /* ERRORS */ | |
| invalid_title: | |
| { | |
| GST_WARNING_OBJECT (src, "Invalid title %d (only %d available)", | |
| title, num_titles); | |
| return FALSE; | |
| } | |
| ifo_open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD title %d"), title_set_nr), | |
| ("ifoOpen(%d) failed: %s", title_set_nr, g_strerror (errno))); | |
| return FALSE; | |
| } | |
| title_open_failed: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD title %d"), title_set_nr), | |
| ("Can't open title VOBS (VTS_%02d_1.VOB)", title_set_nr)); | |
| return FALSE; | |
| } | |
| commands_only_pgc: | |
| { | |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, | |
| (_("Could not open DVD title %d. Interactive titles are not supported " | |
| "by this element"), title_set_nr), | |
| ("Commands-only PGC, not supported, use rsndvdbin")); | |
| return FALSE; | |
| } | |
| } | |
| /* FIXME: double-check this function, compare against original */ | |
| static gint | |
| gst_dvd_read_src_get_next_cell (GstDvdReadSrc * src, pgc_t * pgc, gint cell) | |
| { | |
| /* Check if we're entering an angle block. */ | |
| if (pgc->cell_playback[cell].block_type != BLOCK_TYPE_ANGLE_BLOCK) | |
| return (cell + 1); | |
| while (pgc->cell_playback[cell].block_mode != BLOCK_MODE_LAST_CELL) | |
| ++cell; | |
| return cell + 1; | |
| } | |
| /* Returns true if the pack is a NAV pack */ | |
| static gboolean | |
| gst_dvd_read_src_is_nav_pack (const guint8 * data, gint lbn, dsi_t * dsi_pack) | |
| { | |
| if (GST_READ_UINT32_BE (data + 0x26) != 0x000001BF) | |
| return FALSE; | |
| /* Check that this is substream 0 (PCI) */ | |
| if (data[0x2c] != 0) | |
| return FALSE; | |
| if (GST_READ_UINT32_BE (data + 0x400) != 0x000001BF) | |
| return FALSE; | |
| /* Check that this is substream 1 (DSI) */ | |
| if (data[0x406] != 1) | |
| return FALSE; | |
| /* Check sizes of PCI and DSI packets */ | |
| if (GST_READ_UINT16_BE (data + 0x2a) != 0x03d4) | |
| return FALSE; | |
| if (GST_READ_UINT16_BE (data + 0x404) != 0x03fa) | |
| return FALSE; | |
| /* Read the DSI packet into the provided struct and check it */ | |
| navRead_DSI (dsi_pack, (unsigned char *) data + DSI_START_BYTE); | |
| if (lbn != dsi_pack->dsi_gi.nv_pck_lbn) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| /* find time for sector from index, returns NONE if there is no exact match */ | |
| static GstClockTime | |
| gst_dvd_read_src_get_time_for_sector (GstDvdReadSrc * src, guint sector) | |
| { | |
| gint i, j; | |
| if (src->vts_tmapt == NULL || src->vts_tmapt->nr_of_tmaps == 0) | |
| return GST_CLOCK_TIME_NONE; | |
| for (i = 0; i < src->vts_tmapt->nr_of_tmaps; ++i) { | |
| for (j = 0; j < src->vts_tmapt->tmap[i].nr_of_entries; ++j) { | |
| if ((src->vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff) == sector) | |
| return (guint64) src->vts_tmapt->tmap[i].tmu * (j + 1) * GST_SECOND; | |
| } | |
| } | |
| if (sector == 0) | |
| return (GstClockTime) 0; | |
| return GST_CLOCK_TIME_NONE; | |
| } | |
| /* returns the sector in the index at (or before) the given time, or -1 */ | |
| static gint | |
| gst_dvd_read_src_get_sector_from_time (GstDvdReadSrc * src, GstClockTime ts) | |
| { | |
| gint sector, j; | |
| if (src->vts_tmapt == NULL || src->vts_tmapt->nr_of_tmaps < src->ttn) | |
| return -1; | |
| sector = src->vts_tmapt->tmap[src->ttn - 1].map_ent[0] & 0x7fffffff; | |
| for (j = 0; j < src->vts_tmapt->tmap[src->ttn - 1].nr_of_entries; ++j) { | |
| GstClockTime entry_time; | |
| entry_time = | |
| (guint64) src->vts_tmapt->tmap[src->ttn - 1].tmu * (j + 1) * GST_SECOND; | |
| if (entry_time <= ts) { | |
| sector = src->vts_tmapt->tmap[src->ttn - 1].map_ent[j] & 0x7fffffff; | |
| } | |
| if (entry_time >= ts) { | |
| return sector; | |
| } | |
| } | |
| if (ts == 0) | |
| return 0; | |
| return -1; | |
| } | |
| typedef enum | |
| { | |
| GST_DVD_READ_OK = 0, | |
| GST_DVD_READ_ERROR = -1, | |
| GST_DVD_READ_EOS = -2, | |
| GST_DVD_READ_AGAIN = -3 | |
| } GstDvdReadReturn; | |
| static GstDvdReadReturn | |
| gst_dvd_read_src_read (GstDvdReadSrc * src, gint angle, gint new_seek, | |
| GstBuffer ** p_buf) | |
| { | |
| GstBuffer *buf; | |
| GstSegment *seg; | |
| guint8 oneblock[DVD_VIDEO_LB_LEN]; | |
| dsi_t dsi_pack; | |
| guint next_vobu, cur_output_size; | |
| gint len; | |
| gint retries; | |
| gint64 next_time; | |
| GstMapInfo map; | |
| seg = &(GST_BASE_SRC (src)->segment); | |
| /* playback by cell in this pgc, starting at the cell for our chapter */ | |
| if (new_seek) | |
| src->cur_cell = src->start_cell; | |
| again: | |
| if (src->cur_cell >= src->last_cell) { | |
| /* advance to next chapter */ | |
| if (src->chapter == (src->num_chapters - 1) || | |
| (seg->format == chapter_format && seg->stop != -1 && | |
| src->chapter == (seg->stop - 1))) { | |
| GST_DEBUG_OBJECT (src, "end of chapter segment"); | |
| goto eos; | |
| } | |
| GST_INFO_OBJECT (src, "end of chapter %d, switch to next", | |
| src->chapter + 1); | |
| ++src->chapter; | |
| gst_dvd_read_src_goto_chapter (src, src->chapter); | |
| return GST_DVD_READ_AGAIN; | |
| } | |
| if (src->new_cell || new_seek) { | |
| if (!new_seek) { | |
| src->cur_cell = src->next_cell; | |
| if (src->cur_cell >= src->last_cell) { | |
| GST_LOG_OBJECT (src, "last cell in chapter"); | |
| goto again; | |
| } | |
| } | |
| /* take angle into account */ | |
| if (src->cur_pgc->cell_playback[src->cur_cell].block_type | |
| == BLOCK_TYPE_ANGLE_BLOCK) | |
| src->cur_cell += angle; | |
| /* calculate next cell */ | |
| src->next_cell = | |
| gst_dvd_read_src_get_next_cell (src, src->cur_pgc, src->cur_cell); | |
| /* we loop until we're out of this cell */ | |
| src->cur_pack = src->cur_pgc->cell_playback[src->cur_cell].first_sector; | |
| src->new_cell = FALSE; | |
| GST_DEBUG_OBJECT (src, "Starting new cell %d @ pack %d", src->cur_cell, | |
| src->cur_pack); | |
| } | |
| if (src->cur_pack >= src->cur_pgc->cell_playback[src->cur_cell].last_sector) { | |
| src->new_cell = TRUE; | |
| GST_LOG_OBJECT (src, "Beyond last sector for cell %d, going to next cell", | |
| src->cur_cell); | |
| return GST_DVD_READ_AGAIN; | |
| } | |
| /* read NAV packet */ | |
| retries = 0; | |
| nav_retry: | |
| retries++; | |
| len = DVDReadBlocks (src->dvd_title, src->cur_pack, 1, oneblock); | |
| if (len != 1) | |
| goto read_error; | |
| if (!gst_dvd_read_src_is_nav_pack (oneblock, src->cur_pack, &dsi_pack)) { | |
| GST_LOG_OBJECT (src, "Skipping nav packet @ pack %d", src->cur_pack); | |
| src->cur_pack++; | |
| if (retries < 2000) { | |
| goto nav_retry; | |
| } else { | |
| GST_LOG_OBJECT (src, "No nav packet @ pack %d after 2000 blocks", | |
| src->cur_pack); | |
| goto read_error; | |
| } | |
| } | |
| /* determine where we go next. These values are the ones we | |
| * mostly care about */ | |
| cur_output_size = dsi_pack.dsi_gi.vobu_ea + 1; | |
| /* If we're not at the end of this cell, we can determine the next | |
| * VOBU to display using the VOBU_SRI information section of the | |
| * DSI. Using this value correctly follows the current angle, | |
| * avoiding the doubled scenes in The Matrix, and makes our life | |
| * really happy. | |
| * | |
| * Otherwise, we set our next address past the end of this cell to | |
| * force the code above to go to the next cell in the program. */ | |
| if (dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL) { | |
| next_vobu = src->cur_pack + (dsi_pack.vobu_sri.next_vobu & 0x7fffffff); | |
| } else { | |
| next_vobu = src->cur_pgc->cell_playback[src->cur_cell].last_sector + 1; | |
| } | |
| g_assert (cur_output_size < 1024); | |
| /* create the buffer (TODO: use buffer pool?) */ | |
| buf = | |
| gst_buffer_new_allocate (NULL, cur_output_size * DVD_VIDEO_LB_LEN, NULL); | |
| GST_LOG_OBJECT (src, "Going to read %u sectors @ pack %d", cur_output_size, | |
| src->cur_pack); | |
| gst_buffer_map (buf, &map, GST_MAP_WRITE); | |
| /* read in and output cursize packs */ | |
| len = | |
| DVDReadBlocks (src->dvd_title, src->cur_pack, cur_output_size, map.data); | |
| if (len != cur_output_size) | |
| goto block_read_error; | |
| gst_buffer_unmap (buf, &map); | |
| gst_buffer_resize (buf, 0, cur_output_size * DVD_VIDEO_LB_LEN); | |
| /* GST_BUFFER_OFFSET (buf) = priv->cur_pack * DVD_VIDEO_LB_LEN; */ | |
| GST_BUFFER_TIMESTAMP (buf) = | |
| gst_dvd_read_src_get_time_for_sector (src, src->cur_pack); | |
| *p_buf = buf; | |
| GST_LOG_OBJECT (src, "Read %u sectors", cur_output_size); | |
| src->cur_pack = next_vobu; | |
| next_time = GST_BUFFER_TIMESTAMP (buf); | |
| if (GST_CLOCK_TIME_IS_VALID (next_time) && seg->format == GST_FORMAT_TIME && | |
| GST_CLOCK_TIME_IS_VALID (seg->stop) && | |
| next_time > seg->stop + 5 * GST_SECOND) { | |
| GST_DEBUG_OBJECT (src, "end of TIME segment"); | |
| goto eos; | |
| } | |
| return GST_DVD_READ_OK; | |
| /* ERRORS */ | |
| eos: | |
| { | |
| GST_INFO_OBJECT (src, "Reached end-of-segment/stream - EOS"); | |
| return GST_DVD_READ_EOS; | |
| } | |
| read_error: | |
| { | |
| GST_ERROR_OBJECT (src, "Read failed for block %d", src->cur_pack); | |
| return GST_DVD_READ_ERROR; | |
| } | |
| block_read_error: | |
| { | |
| GST_ERROR_OBJECT (src, "Read failed for %d blocks at %d", | |
| cur_output_size, src->cur_pack); | |
| gst_buffer_unmap (buf, &map); | |
| gst_buffer_unref (buf); | |
| return GST_DVD_READ_ERROR; | |
| } | |
| } | |
| /* we don't cache the result on purpose */ | |
| static gboolean | |
| gst_dvd_read_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 | |
| gst_dvd_read_src_create (GstPushSrc * pushsrc, GstBuffer ** p_buf) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (pushsrc); | |
| GstPad *srcpad; | |
| gint res; | |
| g_return_val_if_fail (src->dvd != NULL, GST_FLOW_ERROR); | |
| srcpad = GST_BASE_SRC (src)->srcpad; | |
| if (src->need_newsegment) { | |
| GstSegment seg; | |
| gst_segment_init (&seg, GST_FORMAT_BYTES); | |
| seg.start = src->cur_pack * DVD_VIDEO_LB_LEN; | |
| seg.stop = -1; | |
| seg.time = 0; | |
| gst_pad_push_event (srcpad, gst_event_new_segment (&seg)); | |
| src->need_newsegment = FALSE; | |
| } | |
| if (src->new_seek) { | |
| gst_dvd_read_src_goto_title (src, src->title, src->angle); | |
| gst_dvd_read_src_goto_chapter (src, src->chapter); | |
| src->new_seek = FALSE; | |
| src->change_cell = TRUE; | |
| } | |
| if (src->title_lang_event_pending) { | |
| gst_pad_push_event (srcpad, src->title_lang_event_pending); | |
| src->title_lang_event_pending = NULL; | |
| } | |
| if (src->pending_clut_event) { | |
| gst_pad_push_event (srcpad, src->pending_clut_event); | |
| src->pending_clut_event = NULL; | |
| } | |
| /* read it in */ | |
| do { | |
| res = gst_dvd_read_src_read (src, src->angle, src->change_cell, p_buf); | |
| } while (res == GST_DVD_READ_AGAIN); | |
| switch (res) { | |
| case GST_DVD_READ_ERROR:{ | |
| /* FIXME: figure out a way to detect if scrambling is the problem */ | |
| if (!gst_dvd_read_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.")), (NULL)); | |
| } else { | |
| GST_ELEMENT_ERROR (src, RESOURCE, READ, (_("Could not read DVD.")), | |
| (NULL)); | |
| } | |
| return GST_FLOW_ERROR; | |
| } | |
| case GST_DVD_READ_EOS:{ | |
| return GST_FLOW_EOS; | |
| } | |
| case GST_DVD_READ_OK:{ | |
| src->change_cell = FALSE; | |
| return GST_FLOW_OK; | |
| } | |
| default: | |
| break; | |
| } | |
| g_return_val_if_reached (GST_FLOW_EOS); | |
| } | |
| static void | |
| gst_dvd_read_src_set_property (GObject * object, guint prop_id, | |
| const GValue * value, GParamSpec * pspec) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (object); | |
| gboolean started; | |
| GST_OBJECT_LOCK (src); | |
| started = GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_FLAG_STARTED); | |
| switch (prop_id) { | |
| case ARG_DEVICE:{ | |
| if (started) { | |
| g_warning ("%s: property '%s' needs to be set before the device is " | |
| "opened", GST_ELEMENT_NAME (src), pspec->name); | |
| break; | |
| } | |
| g_free (src->location); | |
| /* clear the filename if we get a NULL (is that possible?) */ | |
| if (g_value_get_string (value) == NULL) { | |
| src->location = g_strdup ("/dev/dvd"); | |
| } else { | |
| src->location = g_value_dup_string (value); | |
| } | |
| break; | |
| } | |
| case ARG_TITLE: | |
| src->uri_title = g_value_get_int (value); | |
| if (started) { | |
| src->title = src->uri_title - 1; | |
| src->new_seek = TRUE; | |
| } | |
| break; | |
| case ARG_CHAPTER: | |
| src->uri_chapter = g_value_get_int (value); | |
| if (started) { | |
| src->chapter = src->uri_chapter - 1; | |
| src->new_seek = TRUE; | |
| } | |
| break; | |
| case ARG_ANGLE: | |
| src->uri_angle = g_value_get_int (value); | |
| if (started) { | |
| src->angle = src->uri_angle - 1; | |
| } | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| GST_OBJECT_UNLOCK (src); | |
| } | |
| static void | |
| gst_dvd_read_src_get_property (GObject * object, guint prop_id, GValue * value, | |
| GParamSpec * pspec) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (object); | |
| GST_OBJECT_LOCK (src); | |
| switch (prop_id) { | |
| case ARG_DEVICE: | |
| g_value_set_string (value, src->location); | |
| break; | |
| case ARG_TITLE: | |
| g_value_set_int (value, src->uri_title); | |
| break; | |
| case ARG_CHAPTER: | |
| g_value_set_int (value, src->uri_chapter); | |
| break; | |
| case ARG_ANGLE: | |
| g_value_set_int (value, src->uri_angle); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| GST_OBJECT_UNLOCK (src); | |
| } | |
| static gboolean | |
| gst_dvd_read_src_get_size (GstDvdReadSrc * src, gint64 * size) | |
| { | |
| gboolean ret = FALSE; | |
| if (src->dvd_title) { | |
| gssize blocks; | |
| blocks = DVDFileSize (src->dvd_title); | |
| if (blocks >= 0) { | |
| *size = (gint64) blocks *DVD_VIDEO_LB_LEN; | |
| ret = TRUE; | |
| } else { | |
| GST_WARNING_OBJECT (src, "DVDFileSize(%p) failed!", src->dvd_title); | |
| } | |
| } | |
| return ret; | |
| } | |
| /*** Querying and seeking ***/ | |
| static gboolean | |
| gst_dvd_read_src_handle_seek_event (GstDvdReadSrc * src, GstEvent * event) | |
| { | |
| GstSeekFlags flags; | |
| GstSeekType cur_type, end_type; | |
| gint64 new_off, total; | |
| GstFormat format; | |
| GstPad *srcpad; | |
| gboolean query_ok; | |
| gdouble rate; | |
| gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &new_off, | |
| &end_type, NULL); | |
| if (rate <= 0.0) { | |
| GST_DEBUG_OBJECT (src, "cannot do backwards playback yet"); | |
| return FALSE; | |
| } | |
| if (end_type != GST_SEEK_TYPE_NONE) { | |
| if ((format != chapter_format && format != GST_FORMAT_TIME) || | |
| end_type != GST_SEEK_TYPE_SET) { | |
| GST_DEBUG_OBJECT (src, "end seek type not supported"); | |
| return FALSE; | |
| } | |
| } | |
| if (cur_type != GST_SEEK_TYPE_SET) { | |
| GST_DEBUG_OBJECT (src, "only SEEK_TYPE_SET is supported"); | |
| return FALSE; | |
| } | |
| if (format == angle_format) { | |
| GST_OBJECT_LOCK (src); | |
| if (new_off < 0 || new_off >= src->num_angles) { | |
| GST_OBJECT_UNLOCK (src); | |
| GST_DEBUG_OBJECT (src, "invalid angle %d, only %d available", | |
| src->num_angles, src->num_angles); | |
| return FALSE; | |
| } | |
| src->angle = (gint) new_off; | |
| GST_OBJECT_UNLOCK (src); | |
| GST_DEBUG_OBJECT (src, "switched to angle %d", (gint) new_off + 1); | |
| return TRUE; | |
| } | |
| if (format != chapter_format && format != title_format && | |
| format != GST_FORMAT_BYTES && format != GST_FORMAT_TIME) { | |
| GST_DEBUG_OBJECT (src, "unsupported seek format %d (%s)", format, | |
| gst_format_get_name (format)); | |
| return FALSE; | |
| } | |
| if (format == GST_FORMAT_BYTES) { | |
| GST_DEBUG_OBJECT (src, "Requested seek to byte %" G_GUINT64_FORMAT, | |
| new_off); | |
| } else if (format == GST_FORMAT_TIME) { | |
| GST_DEBUG_OBJECT (src, "Requested seek to time %" GST_TIME_FORMAT, | |
| GST_TIME_ARGS (new_off)); | |
| if (gst_dvd_read_src_get_sector_from_time (src, new_off) < 0) { | |
| GST_DEBUG_OBJECT (src, "Can't find sector for requested time"); | |
| return FALSE; | |
| } | |
| } | |
| srcpad = GST_BASE_SRC_PAD (src); | |
| /* check whether the seek looks reasonable (ie within possible range) */ | |
| if (format == GST_FORMAT_BYTES) { | |
| GST_OBJECT_LOCK (src); | |
| query_ok = gst_dvd_read_src_get_size (src, &total); | |
| GST_OBJECT_UNLOCK (src); | |
| } else { | |
| query_ok = gst_pad_query_duration (srcpad, format, &total); | |
| } | |
| if (!query_ok) { | |
| GST_DEBUG_OBJECT (src, "Failed to query duration in format %s", | |
| gst_format_get_name (format)); | |
| return FALSE; | |
| } | |
| GST_DEBUG_OBJECT (src, "Total %s: %12" G_GINT64_FORMAT, | |
| gst_format_get_name (format), total); | |
| GST_DEBUG_OBJECT (src, "Seek to %s: %12" G_GINT64_FORMAT, | |
| gst_format_get_name (format), new_off); | |
| if (new_off >= total) { | |
| GST_DEBUG_OBJECT (src, "Seek position out of range"); | |
| return FALSE; | |
| } | |
| /* set segment to seek format; this allows us to use the do_seek | |
| * virtual function and let the base source handle all the tricky | |
| * stuff for us. We don't use the segment internally anyway */ | |
| /* FIXME: can't take the stream lock here - what to do? */ | |
| GST_OBJECT_LOCK (src); | |
| GST_BASE_SRC (src)->segment.format = format; | |
| GST_BASE_SRC (src)->segment.start = 0; | |
| GST_BASE_SRC (src)->segment.stop = total; | |
| GST_BASE_SRC (src)->segment.duration = total; | |
| GST_OBJECT_UNLOCK (src); | |
| return GST_BASE_SRC_CLASS (parent_class)->event (GST_BASE_SRC (src), event); | |
| } | |
| static void | |
| gst_dvd_read_src_get_sector_bounds (GstDvdReadSrc * src, gint * first, | |
| gint * last) | |
| { | |
| gint c1, c2, tmp; | |
| cur_title_get_chapter_bounds (src, 0, &c1, &tmp); | |
| cur_title_get_chapter_bounds (src, src->num_chapters - 1, &tmp, &c2); | |
| *first = src->cur_pgc->cell_playback[c1].first_sector; | |
| *last = src->cur_pgc->cell_playback[c2].last_sector; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_do_seek (GstBaseSrc * basesrc, GstSegment * s) | |
| { | |
| GstDvdReadSrc *src; | |
| src = GST_DVD_READ_SRC (basesrc); | |
| GST_DEBUG_OBJECT (src, "Seeking to %s: %12" G_GINT64_FORMAT, | |
| gst_format_get_name (s->format), s->position); | |
| /* Ignore the first seek to 0, as it breaks starting playback | |
| * from another chapter by seeking back to sector 0 */ | |
| if (src->first_seek && s->format == GST_FORMAT_BYTES && s->start == 0) { | |
| src->first_seek = FALSE; | |
| return TRUE; | |
| } | |
| if (s->format == sector_format || s->format == GST_FORMAT_BYTES | |
| || s->format == GST_FORMAT_TIME) { | |
| guint old; | |
| old = src->cur_pack; | |
| if (s->format == sector_format) { | |
| gint first, last; | |
| gst_dvd_read_src_get_sector_bounds (src, &first, &last); | |
| GST_DEBUG_OBJECT (src, "Format is sector, seeking to %" G_GINT64_FORMAT, | |
| s->position); | |
| src->cur_pack = s->position; | |
| if (src->cur_pack < first) | |
| src->cur_pack = first; | |
| if (src->cur_pack > last) | |
| src->cur_pack = last; | |
| } else if (s->format == GST_FORMAT_TIME) { | |
| gint sector; | |
| GST_DEBUG_OBJECT (src, "Format is time"); | |
| sector = gst_dvd_read_src_get_sector_from_time (src, s->position); | |
| GST_DEBUG_OBJECT (src, "Time %" GST_TIME_FORMAT " => sector %d", | |
| GST_TIME_ARGS (s->position), sector); | |
| /* really shouldn't happen, we've checked this earlier ... */ | |
| g_return_val_if_fail (sector >= 0, FALSE); | |
| src->cur_pack = sector; | |
| } else { | |
| /* byte format */ | |
| gint first, last; | |
| gst_dvd_read_src_get_sector_bounds (src, &first, &last); | |
| GST_DEBUG_OBJECT (src, "Format is byte"); | |
| src->cur_pack = s->position / DVD_VIDEO_LB_LEN; | |
| if (((gint64) src->cur_pack * DVD_VIDEO_LB_LEN) != s->position) { | |
| GST_LOG_OBJECT (src, "rounded down offset %" G_GINT64_FORMAT " => %" | |
| G_GINT64_FORMAT, s->position, | |
| (gint64) src->cur_pack * DVD_VIDEO_LB_LEN); | |
| } | |
| src->cur_pack += first; | |
| } | |
| if (!gst_dvd_read_src_goto_sector (src, src->angle)) { | |
| GST_DEBUG_OBJECT (src, "seek to sector 0x%08x failed", src->cur_pack); | |
| src->cur_pack = old; | |
| return FALSE; | |
| } | |
| GST_LOG_OBJECT (src, "seek to sector 0x%08x ok", src->cur_pack); | |
| } else if (s->format == chapter_format) { | |
| if (!gst_dvd_read_src_goto_chapter (src, (gint) s->position)) { | |
| GST_DEBUG_OBJECT (src, "seek to chapter %d failed", | |
| (gint) s->position + 1); | |
| return FALSE; | |
| } | |
| GST_INFO_OBJECT (src, "seek to chapter %d ok", (gint) s->position + 1); | |
| src->chapter = s->position; | |
| } else if (s->format == title_format) { | |
| if (!gst_dvd_read_src_goto_title (src, (gint) s->position, src->angle) || | |
| !gst_dvd_read_src_goto_chapter (src, 0)) { | |
| GST_DEBUG_OBJECT (src, "seek to title %d failed", (gint) s->position); | |
| return FALSE; | |
| } | |
| src->title = (gint) s->position; | |
| src->chapter = 0; | |
| GST_INFO_OBJECT (src, "seek to title %d ok", src->title + 1); | |
| } else { | |
| g_return_val_if_reached (FALSE); | |
| } | |
| src->need_newsegment = TRUE; | |
| return TRUE; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_src_event (GstBaseSrc * basesrc, GstEvent * event) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc); | |
| gboolean res; | |
| GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event)); | |
| switch (GST_EVENT_TYPE (event)) { | |
| case GST_EVENT_SEEK: | |
| res = gst_dvd_read_src_handle_seek_event (src, event); | |
| break; | |
| default: | |
| res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event); | |
| break; | |
| } | |
| return res; | |
| } | |
| static GstEvent * | |
| gst_dvd_read_src_make_clut_change_event (GstDvdReadSrc * src, | |
| const guint32 * clut) | |
| { | |
| GstStructure *structure; | |
| gchar name[16]; | |
| gint i; | |
| 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++) { | |
| g_snprintf (name, sizeof (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. */ | |
| return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, structure); | |
| } | |
| static gint64 | |
| gst_dvd_read_src_convert_timecode (dvd_time_t * time) | |
| { | |
| gint64 ret_time; | |
| const gint64 one_hour = 3600 * GST_SECOND; | |
| const gint64 one_min = 60 * GST_SECOND; | |
| g_return_val_if_fail ((time->hour >> 4) < 0xa | |
| && (time->hour & 0xf) < 0xa, -1); | |
| g_return_val_if_fail ((time->minute >> 4) < 0x7 | |
| && (time->minute & 0xf) < 0xa, -1); | |
| g_return_val_if_fail ((time->second >> 4) < 0x7 | |
| && (time->second & 0xf) < 0xa, -1); | |
| ret_time = ((time->hour >> 4) * 10 + (time->hour & 0xf)) * one_hour; | |
| ret_time += ((time->minute >> 4) * 10 + (time->minute & 0xf)) * one_min; | |
| ret_time += ((time->second >> 4) * 10 + (time->second & 0xf)) * GST_SECOND; | |
| return ret_time; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_do_duration_query (GstDvdReadSrc * src, GstQuery * query) | |
| { | |
| GstFormat format; | |
| gint64 val = 0; | |
| gst_query_parse_duration (query, &format, NULL); | |
| switch (format) { | |
| case GST_FORMAT_TIME:{ | |
| if (src->cur_pgc == NULL) | |
| return FALSE; | |
| val = gst_dvd_read_src_convert_timecode (&src->cur_pgc->playback_time); | |
| if (val < 0) | |
| return FALSE; | |
| break; | |
| } | |
| case GST_FORMAT_BYTES:{ | |
| if (!gst_dvd_read_src_get_size (src, &val)) | |
| return FALSE; | |
| break; | |
| } | |
| default:{ | |
| if (format == sector_format) { | |
| val = DVDFileSize (src->dvd_title); | |
| } else if (format == title_format) { | |
| val = src->tt_srpt->nr_of_srpts; | |
| } else if (format == chapter_format) { | |
| val = src->num_chapters; | |
| } else if (format == angle_format) { | |
| val = src->tt_srpt->title[src->title].nr_of_angles; | |
| } else { | |
| GST_DEBUG_OBJECT (src, "Don't know how to handle format %d (%s)", | |
| format, gst_format_get_name (format)); | |
| return FALSE; | |
| } | |
| break; | |
| } | |
| } | |
| GST_LOG_OBJECT (src, "duration = %" G_GINT64_FORMAT " %s", val, | |
| gst_format_get_name (format)); | |
| gst_query_set_duration (query, format, val); | |
| return TRUE; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_do_position_query (GstDvdReadSrc * src, GstQuery * query) | |
| { | |
| GstFormat format; | |
| gint64 val; | |
| gst_query_parse_position (query, &format, NULL); | |
| switch (format) { | |
| case GST_FORMAT_BYTES:{ | |
| val = (gint64) src->cur_pack * DVD_VIDEO_LB_LEN; | |
| break; | |
| } | |
| default:{ | |
| if (format == sector_format) { | |
| val = src->cur_pack; | |
| } else if (format == title_format) { | |
| val = src->title; | |
| } else if (format == chapter_format) { | |
| val = src->chapter; | |
| } else if (format == angle_format) { | |
| val = src->angle; | |
| } else { | |
| GST_DEBUG_OBJECT (src, "Don't know how to handle format %d (%s)", | |
| format, gst_format_get_name (format)); | |
| return FALSE; | |
| } | |
| break; | |
| } | |
| } | |
| GST_LOG_OBJECT (src, "position = %" G_GINT64_FORMAT " %s", val, | |
| gst_format_get_name (format)); | |
| gst_query_set_position (query, format, val); | |
| return TRUE; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_do_convert_query (GstDvdReadSrc * src, GstQuery * query) | |
| { | |
| GstFormat src_format, dest_format; | |
| gboolean ret = FALSE; | |
| gint64 src_val, dest_val = -1; | |
| gst_query_parse_convert (query, &src_format, &src_val, &dest_format, NULL); | |
| if (src_format == dest_format) { | |
| dest_val = src_val; | |
| ret = TRUE; | |
| goto done; | |
| } | |
| /* Formats to consider: TIME, DEFAULT, BYTES, title, chapter, sector. | |
| * Note: title and chapter are counted as starting from 0 here, just like | |
| * in the context of seek events. Another note: DEFAULT format is undefined */ | |
| if (src_format == GST_FORMAT_BYTES) { | |
| src_format = sector_format; | |
| src_val /= DVD_VIDEO_LB_LEN; | |
| } | |
| if (src_format == sector_format) { | |
| /* SECTOR => xyz */ | |
| if (dest_format == GST_FORMAT_TIME && src_val < G_MAXUINT) { | |
| dest_val = gst_dvd_read_src_get_time_for_sector (src, (guint) src_val); | |
| ret = (dest_val >= 0); | |
| } else if (dest_format == GST_FORMAT_BYTES) { | |
| dest_val = src_val * DVD_VIDEO_LB_LEN; | |
| ret = TRUE; | |
| } else { | |
| ret = FALSE; | |
| } | |
| } else if (src_format == title_format) { | |
| /* TITLE => xyz */ | |
| if (dest_format == GST_FORMAT_TIME) { | |
| /* not really true, but we use this to trick the base source into | |
| * handling seeks in title-format for us (the source won't know that | |
| * we changed the title in this case) (changing titles should really | |
| * be done with an interface rather than a seek, but for now we're | |
| * stuck with this mechanism. Fix in 0.11) */ | |
| dest_val = (GstClockTime) 0; | |
| ret = TRUE; | |
| } else { | |
| ret = FALSE; | |
| } | |
| } else if (src_format == chapter_format) { | |
| /* CHAPTER => xyz */ | |
| if (dest_format == GST_FORMAT_TIME) { | |
| if (src->num_chapters >= 0 && src_val < src->num_chapters) { | |
| dest_val = src->chapter_starts[src_val]; | |
| ret = TRUE; | |
| } | |
| } else if (dest_format == sector_format) { | |
| } else { | |
| ret = FALSE; | |
| } | |
| } else if (src_format == GST_FORMAT_TIME) { | |
| /* TIME => xyz */ | |
| if (dest_format == sector_format || dest_format == GST_FORMAT_BYTES) { | |
| dest_val = gst_dvd_read_src_get_sector_from_time (src, src_val); | |
| ret = (dest_val >= 0); | |
| if (dest_format == GST_FORMAT_BYTES) | |
| dest_val *= DVD_VIDEO_LB_LEN; | |
| } else if (dest_format == chapter_format) { | |
| if (src->chapter_starts != NULL) { | |
| gint i; | |
| for (i = src->num_chapters - 1; i >= 0; --i) { | |
| if (src->chapter_starts && src->chapter_starts[i] >= src_val) { | |
| dest_val = i; | |
| ret = TRUE; | |
| break; | |
| } | |
| } | |
| } else { | |
| ret = FALSE; | |
| } | |
| } else { | |
| ret = FALSE; | |
| } | |
| } else { | |
| ret = FALSE; | |
| } | |
| done: | |
| if (ret) { | |
| gst_query_set_convert (query, src_format, src_val, dest_format, dest_val); | |
| } | |
| return ret; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_src_query (GstBaseSrc * basesrc, GstQuery * query) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc); | |
| gboolean res = TRUE; | |
| GST_LOG_OBJECT (src, "handling %s query", GST_QUERY_TYPE_NAME (query)); | |
| switch (GST_QUERY_TYPE (query)) { | |
| case GST_QUERY_DURATION: | |
| GST_OBJECT_LOCK (src); | |
| if (GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_FLAG_STARTED)) { | |
| res = gst_dvd_read_src_do_duration_query (src, query); | |
| } else { | |
| GST_DEBUG_OBJECT (src, "query failed: not started"); | |
| res = FALSE; | |
| } | |
| GST_OBJECT_UNLOCK (src); | |
| break; | |
| case GST_QUERY_POSITION: | |
| GST_OBJECT_LOCK (src); | |
| if (GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_FLAG_STARTED)) { | |
| res = gst_dvd_read_src_do_position_query (src, query); | |
| } else { | |
| GST_DEBUG_OBJECT (src, "query failed: not started"); | |
| res = FALSE; | |
| } | |
| GST_OBJECT_UNLOCK (src); | |
| break; | |
| case GST_QUERY_CONVERT: | |
| GST_OBJECT_LOCK (src); | |
| if (GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_FLAG_STARTED)) { | |
| res = gst_dvd_read_src_do_convert_query (src, query); | |
| } else { | |
| GST_DEBUG_OBJECT (src, "query failed: not started"); | |
| res = FALSE; | |
| } | |
| GST_OBJECT_UNLOCK (src); | |
| break; | |
| default: | |
| res = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query); | |
| break; | |
| } | |
| return res; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_goto_sector (GstDvdReadSrc * src, int angle) | |
| { | |
| gint seek_to = src->cur_pack; | |
| gint chapter, next, cur, i; | |
| /* retrieve position */ | |
| src->cur_pack = 0; | |
| GST_DEBUG_OBJECT (src, "Goto sector %d, angle %d, within %d chapters", | |
| seek_to, angle, src->num_chapters); | |
| for (i = 0; i < src->num_chapters; i++) { | |
| gint c1, c2; | |
| cur_title_get_chapter_bounds (src, i, &c1, &c2); | |
| GST_DEBUG_OBJECT (src, " Looking in chapter %d, bounds: %d %d", i, c1, c2); | |
| for (next = cur = c1; cur < c2;) { | |
| gint first = src->cur_pgc->cell_playback[cur].first_sector; | |
| gint last = src->cur_pgc->cell_playback[cur].last_sector; | |
| GST_DEBUG_OBJECT (src, "Cell %d sector bounds: %d %d", cur, first, last); | |
| cur = next; | |
| if (src->cur_pgc->cell_playback[cur].block_type == BLOCK_TYPE_ANGLE_BLOCK) | |
| cur += angle; | |
| next = gst_dvd_read_src_get_next_cell (src, src->cur_pgc, cur); | |
| /* seeking to 0 should end up at first chapter in any case */ | |
| if ((seek_to >= first && seek_to <= last) || (seek_to == 0 && i == 0)) { | |
| GST_DEBUG_OBJECT (src, "Seek target found in chapter %d", i); | |
| chapter = i; | |
| goto done; | |
| } | |
| } | |
| } | |
| GST_DEBUG_OBJECT (src, "Seek to sector %u failed", seek_to); | |
| return FALSE; | |
| done: | |
| { | |
| /* so chapter $chapter and cell $cur contain our sector | |
| * of interest. Let's go there! */ | |
| GST_INFO_OBJECT (src, "Seek succeeded, going to chapter %u, cell %u", | |
| chapter + 1, cur); | |
| gst_dvd_read_src_goto_chapter (src, chapter); | |
| src->cur_cell = cur; | |
| src->next_cell = next; | |
| src->new_cell = FALSE; | |
| src->cur_pack = seek_to; | |
| return TRUE; | |
| } | |
| } | |
| /*** URI interface ***/ | |
| static GstURIType | |
| gst_dvd_read_src_uri_get_type (GType type) | |
| { | |
| return GST_URI_SRC; | |
| } | |
| static const gchar *const * | |
| gst_dvd_read_src_uri_get_protocols (GType type) | |
| { | |
| static const gchar *protocols[] = { "dvd", NULL }; | |
| return protocols; | |
| } | |
| static gchar * | |
| gst_dvd_read_src_uri_get_uri (GstURIHandler * handler) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (handler); | |
| gchar *uri; | |
| GST_OBJECT_LOCK (src); | |
| uri = g_strdup_printf ("dvd://%d,%d,%d", src->uri_title, src->uri_chapter, | |
| src->uri_angle); | |
| GST_OBJECT_UNLOCK (src); | |
| return uri; | |
| } | |
| static gboolean | |
| gst_dvd_read_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, | |
| GError ** error) | |
| { | |
| GstDvdReadSrc *src = GST_DVD_READ_SRC (handler); | |
| /* parse out the new t/c/a and seek to them */ | |
| { | |
| gchar *location = NULL; | |
| gchar **strs; | |
| gchar **strcur; | |
| gint pos = 0; | |
| location = gst_uri_get_location (uri); | |
| GST_OBJECT_LOCK (src); | |
| src->uri_title = 1; | |
| src->uri_chapter = 1; | |
| src->uri_angle = 1; | |
| if (!location) | |
| goto empty_location; | |
| strcur = strs = g_strsplit (location, ",", 0); | |
| while (strcur && *strcur) { | |
| gint val; | |
| if (!sscanf (*strcur, "%d", &val)) | |
| break; | |
| if (val <= 0) { | |
| g_warning ("Invalid value %d in URI '%s'. Must be 1 or greater", | |
| val, location); | |
| break; | |
| } | |
| switch (pos) { | |
| case 0: | |
| src->uri_title = val; | |
| break; | |
| case 1: | |
| src->uri_chapter = val; | |
| break; | |
| case 2: | |
| src->uri_angle = val; | |
| break; | |
| } | |
| strcur++; | |
| pos++; | |
| } | |
| if (pos > 0 && GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_FLAG_STARTED)) { | |
| src->title = src->uri_title - 1; | |
| src->chapter = src->uri_chapter - 1; | |
| src->angle = src->uri_angle - 1; | |
| src->new_seek = TRUE; | |
| } | |
| g_strfreev (strs); | |
| g_free (location); | |
| empty_location: | |
| GST_OBJECT_UNLOCK (src); | |
| } | |
| return TRUE; | |
| } | |
| static void | |
| gst_dvd_read_src_uri_handler_init (gpointer g_iface, gpointer iface_data) | |
| { | |
| GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; | |
| iface->get_type = gst_dvd_read_src_uri_get_type; | |
| iface->get_protocols = gst_dvd_read_src_uri_get_protocols; | |
| iface->get_uri = gst_dvd_read_src_uri_get_uri; | |
| iface->set_uri = gst_dvd_read_src_uri_set_uri; | |
| } | |
| static gboolean | |
| dvdread_element_init (GstPlugin * plugin) | |
| { | |
| GST_DEBUG_CATEGORY_INIT (gstgst_dvd_read_src_debug, "dvdreadsrc", 0, | |
| "DVD reader element based on dvdreadsrc"); | |
| #ifdef ENABLE_NLS | |
| GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, | |
| LOCALEDIR); | |
| bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); | |
| bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); | |
| #endif /* ENABLE_NLS */ | |
| return gst_element_register (plugin, "dvdreadsrc", GST_RANK_NONE, | |
| GST_TYPE_DVD_READ_SRC); | |
| } | |
| static gboolean | |
| plugin_init (GstPlugin * plugin) | |
| { | |
| return GST_ELEMENT_REGISTER (dvdreadsrc, plugin); | |
| } | |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, | |
| GST_VERSION_MINOR, | |
| dvdread, | |
| "Access a DVD with dvdread", | |
| plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); |