/* GStreamer DVD title source * Copyright (C) 1999 Erik Walthinsen * Copyright (C) 2001 Billy Biggs . * Copyright (C) 2006 Tim-Philipp Müller * * 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 #endif #include #include #include #include #include "dvdreadsrc.h" #include #include 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 "); 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);