| /* 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 <gst/gst-i18n-plugin.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 guint * 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); |
| |
| #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)); |
| |
| 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; |
| |
| /* 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); |
| |
| src->pending_clut_event = |
| gst_dvd_read_src_make_clut_change_event (src, src->cur_pgc->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); |
| 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_strdup (g_value_get_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 guint * 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, 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; |
| |
| 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 |
| plugin_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 */ |
| |
| if (!gst_element_register (plugin, "dvdreadsrc", GST_RANK_NONE, |
| GST_TYPE_DVD_READ_SRC)) { |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| 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); |