ext/dvdread/dvdreadsrc.*: Rewrite seeking code and make seeking in DVDs work (#337834).
[platform/upstream/gstreamer.git] / ext / dvdread / dvdreadsrc.c
1 /* GStreamer
2  * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) 2001 Billy Biggs <vektor@dumbterm.net>.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "_stdint.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #include "dvdreadsrc.h"
34
35 /* #include <gst/gst-i18n-plugin.h> */
36 /* FIXME: remove once GETTEXT_PACKAGE etc. is set */
37 #define _(s) s
38
39 GST_DEBUG_CATEGORY_STATIC (gstgst_dvd_read_src_debug);
40 #define GST_CAT_DEFAULT (gstgst_dvd_read_src_debug)
41
42 static void gst_dvd_read_src_do_init (GType dvdreadsrc_type);
43
44 enum
45 {
46   ARG_0,
47   ARG_DEVICE,
48   ARG_TITLE,
49   ARG_CHAPTER,
50   ARG_ANGLE
51 };
52
53 static GstElementDetails gst_dvd_read_src_details = {
54   "DVD Source",
55   "Source/File/DVD",
56   "Access a DVD title/chapter/angle using libdvdread",
57   "Erik Walthinsen <omega@cse.ogi.edu>",
58 };
59
60 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
61     GST_PAD_SRC,
62     GST_PAD_ALWAYS,
63     GST_STATIC_CAPS ("video/mpeg, mpegversion=2, systemstream=(boolean)true"));
64
65 static GstFormat title_format;
66 static GstFormat angle_format;
67 static GstFormat sector_format;
68 static GstFormat chapter_format;
69
70 static gboolean gst_dvd_read_src_start (GstBaseSrc * basesrc);
71 static gboolean gst_dvd_read_src_stop (GstBaseSrc * basesrc);
72 static GstFlowReturn gst_dvd_read_src_create (GstPushSrc * pushsrc,
73     GstBuffer ** buf);
74 static gboolean gst_dvd_read_src_src_query (GstBaseSrc * basesrc,
75     GstQuery * query);
76 static gboolean gst_dvd_read_src_src_event (GstBaseSrc * basesrc,
77     GstEvent * event);
78 static gboolean gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title,
79     gint angle);
80 static gboolean gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src,
81     gint chapter);
82 static gboolean gst_dvd_read_src_goto_sector (GstDvdReadSrc * src, gint angle);
83 static void gst_dvd_read_src_set_property (GObject * object, guint prop_id,
84     const GValue * value, GParamSpec * pspec);
85 static void gst_dvd_read_src_get_property (GObject * object, guint prop_id,
86     GValue * value, GParamSpec * pspec);
87 static GstEvent *gst_dvd_read_src_make_clut_change_event (GstDvdReadSrc * src,
88     const guint * clut);
89 static gboolean gst_dvd_read_src_get_size (GstDvdReadSrc * src, gint64 * size);
90 static gboolean gst_dvd_read_src_do_seek (GstBaseSrc * src, GstSegment * s);
91
92 GST_BOILERPLATE_FULL (GstDvdReadSrc, gst_dvd_read_src, GstPushSrc,
93     GST_TYPE_PUSH_SRC, gst_dvd_read_src_do_init);
94
95 static void
96 gst_dvd_read_src_base_init (gpointer g_class)
97 {
98   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
99
100   gst_element_class_add_pad_template (element_class,
101       gst_static_pad_template_get (&srctemplate));
102
103   gst_element_class_set_details (element_class, &gst_dvd_read_src_details);
104 }
105
106 static void
107 gst_dvd_read_src_finalize (GObject * object)
108 {
109   GstDvdReadSrc *src = GST_DVD_READ_SRC (object);
110
111   g_free (src->location);
112   g_free (src->last_uri);
113
114   G_OBJECT_CLASS (parent_class)->finalize (object);
115 }
116
117 static void
118 gst_dvd_read_src_init (GstDvdReadSrc * src, GstDvdReadSrcClass * klass)
119 {
120   src->dvd = NULL;
121   src->vts_file = NULL;
122   src->vmg_file = NULL;
123   src->dvd_title = NULL;
124
125   src->location = g_strdup ("/dev/dvd");
126   src->last_uri = NULL;
127   src->new_seek = TRUE;
128   src->new_cell = TRUE;
129   src->change_cell = FALSE;
130   src->uri_title = 1;
131   src->uri_chapter = 1;
132   src->uri_angle = 1;
133
134   src->title_lang_event_pending = NULL;
135   src->pending_clut_event = NULL;
136
137   gst_pad_use_fixed_caps (GST_BASE_SRC_PAD (src));
138   gst_pad_set_caps (GST_BASE_SRC_PAD (src),
139       gst_static_pad_template_get_caps (&srctemplate));
140 }
141
142 static gboolean
143 gst_dvd_read_src_is_seekable (GstBaseSrc * src)
144 {
145   return TRUE;
146 }
147
148 static void
149 gst_dvd_read_src_class_init (GstDvdReadSrcClass * klass)
150 {
151   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
152   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
153   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
154
155   gobject_class->finalize = gst_dvd_read_src_finalize;
156   gobject_class->set_property = gst_dvd_read_src_set_property;
157   gobject_class->get_property = gst_dvd_read_src_get_property;
158
159   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
160       g_param_spec_string ("device", "Device",
161           "DVD device location", NULL, G_PARAM_READWRITE));
162   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TITLE,
163       g_param_spec_int ("title", "title", "title",
164           1, 999, 1, G_PARAM_READWRITE));
165   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHAPTER,
166       g_param_spec_int ("chapter", "chapter", "chapter",
167           1, 999, 1, G_PARAM_READWRITE));
168   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ANGLE,
169       g_param_spec_int ("angle", "angle", "angle",
170           1, 999, 1, G_PARAM_READWRITE));
171
172   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_dvd_read_src_start);
173   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_dvd_read_src_stop);
174   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_dvd_read_src_src_query);
175   gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_dvd_read_src_src_event);
176   gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_dvd_read_src_do_seek);
177   gstbasesrc_class->is_seekable =
178       GST_DEBUG_FUNCPTR (gst_dvd_read_src_is_seekable);
179
180   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_dvd_read_src_create);
181 }
182
183 static gboolean
184 gst_dvd_read_src_start (GstBaseSrc * basesrc)
185 {
186   GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc);
187
188   g_return_val_if_fail (src->location != NULL, FALSE);
189
190   GST_DEBUG_OBJECT (src, "Opening DVD '%s'", src->location);
191
192   if ((src->dvd = DVDOpen (src->location)) == NULL)
193     goto open_failed;
194
195   /* Load the video manager to find out the information about the titles */
196   GST_DEBUG_OBJECT (src, "Loading VMG info");
197
198   if (!(src->vmg_file = ifoOpen (src->dvd, 0)))
199     goto ifo_open_failed;
200
201   src->tt_srpt = src->vmg_file->tt_srpt;
202
203   src->title = src->uri_title - 1;
204   src->chapter = src->uri_chapter - 1;
205   src->angle = src->uri_angle - 1;
206
207   if (!gst_dvd_read_src_goto_title (src, src->title, src->angle))
208     goto title_open_failed;
209
210   if (!gst_dvd_read_src_goto_chapter (src, src->chapter))
211     goto chapter_open_failed;
212
213   src->new_seek = FALSE;
214   src->change_cell = TRUE;
215
216   return TRUE;
217
218   /* ERRORS */
219 open_failed:
220   {
221     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
222         (_("Could not open DVD")),
223         ("DVDOpen(%s) failed: %s", src->location, g_strerror (errno)));
224     return FALSE;
225   }
226 ifo_open_failed:
227   {
228     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
229         (_("Could not open DVD")),
230         ("ifoOpen() failed: %s", g_strerror (errno)));
231     return FALSE;
232   }
233 title_open_failed:
234   {
235     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
236         (_("Could not open DVD title %d"), src->uri_title), (NULL));
237     return FALSE;
238   }
239 chapter_open_failed:
240   {
241     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
242         (_("Failed to go to chapter %d of DVD title %d"),
243             src->uri_chapter, src->uri_title), (NULL));
244     return FALSE;
245   }
246 }
247
248 static gboolean
249 gst_dvd_read_src_stop (GstBaseSrc * basesrc)
250 {
251   GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc);
252
253   if (src->vts_file) {
254     ifoClose (src->vts_file);
255     src->vts_file = NULL;
256   }
257   if (src->vmg_file) {
258     ifoClose (src->vmg_file);
259     src->vmg_file = NULL;
260   }
261   if (src->dvd_title) {
262     DVDCloseFile (src->dvd_title);
263     src->dvd_title = NULL;
264   }
265   if (src->dvd) {
266     DVDClose (src->dvd);
267     src->dvd = NULL;
268   }
269   src->new_cell = TRUE;
270   src->new_seek = TRUE;
271   src->change_cell = FALSE;
272   src->chapter = 0;
273   src->title = 0;
274   src->need_newsegment = TRUE;
275   if (src->title_lang_event_pending) {
276     gst_event_unref (src->title_lang_event_pending);
277     src->title_lang_event_pending = NULL;
278   }
279   if (src->pending_clut_event) {
280     gst_event_unref (src->pending_clut_event);
281     src->pending_clut_event = NULL;
282   }
283
284   GST_LOG_OBJECT (src, "closed DVD");
285
286   return TRUE;
287 }
288
289 static void
290 cur_title_get_chapter_pgc (GstDvdReadSrc * src, gint chapter, gint * p_pgn,
291     gint * p_pgc_id, pgc_t ** p_pgc)
292 {
293   pgc_t *pgc;
294   gint pgn, pgc_id;
295
296   g_assert (chapter >= 0 && chapter < src->num_chapters);
297
298   pgc_id = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter].pgcn;
299   pgn = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter].pgn;
300   pgc = src->vts_file->vts_pgcit->pgci_srp[pgc_id - 1].pgc;
301
302   *p_pgn = pgn;
303   *p_pgc_id = pgc_id;
304   *p_pgc = pgc;
305 }
306
307 static void
308 cur_title_get_chapter_bounds (GstDvdReadSrc * src, gint chapter,
309     gint * p_first_cell, gint * p_last_cell)
310 {
311   pgc_t *pgc;
312   gint pgn, pgc_id, pgn_next_ch;
313
314   g_assert (chapter >= 0 && chapter < src->num_chapters);
315
316   cur_title_get_chapter_pgc (src, chapter, &pgn, &pgc_id, &pgc);
317
318   *p_first_cell = pgc->program_map[pgn - 1] - 1;
319
320   if (chapter == (src->num_chapters - 1)) {
321     *p_last_cell = pgc->nr_of_cells;
322   } else {
323     pgn_next_ch = src->vts_ptt_srpt->title[src->ttn - 1].ptt[chapter + 1].pgn;
324     *p_last_cell = pgc->program_map[pgn_next_ch - 1] - 1;
325   }
326 }
327
328 static gboolean
329 gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src, gint chapter)
330 {
331   gint i;
332
333   /* make sure the chapter number is valid for this title */
334   if (chapter < 0 || chapter >= src->num_chapters) {
335     GST_WARNING_OBJECT (src, "invalid chapter %d (only %d available)",
336         chapter, src->num_chapters);
337     chapter = CLAMP (chapter, 0, src->num_chapters - 1);
338   }
339
340   /* determine which program chain we want to watch. This is
341    * based on the chapter number */
342   cur_title_get_chapter_pgc (src, chapter, &src->pgn, &src->pgc_id,
343       &src->cur_pgc);
344   cur_title_get_chapter_bounds (src, chapter, &src->start_cell,
345       &src->last_cell);
346
347   GST_LOG_OBJECT (src, "Opened chapter %d - cell %d-%d", chapter,
348       src->start_cell, src->last_cell);
349
350   /* retrieve position */
351   src->cur_pack = 0;
352   for (i = 0; i < chapter; i++) {
353     gint c1, c2;
354
355     cur_title_get_chapter_bounds (src, i, &c1, &c2);
356
357     while (c1 < c2) {
358       src->cur_pack +=
359           src->cur_pgc->cell_playback[c1].last_sector -
360           src->cur_pgc->cell_playback[c1].first_sector;
361       ++c1;
362     }
363   }
364
365   /* prepare reading for new cell */
366   src->new_cell = TRUE;
367   src->next_cell = src->start_cell;
368
369   src->chapter = chapter;
370
371   if (src->pending_clut_event)
372     gst_event_unref (src->pending_clut_event);
373
374   src->pending_clut_event =
375       gst_dvd_read_src_make_clut_change_event (src, src->cur_pgc->palette);
376
377   return TRUE;
378 }
379
380 static gboolean
381 gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle)
382 {
383   GstStructure *s;
384   gchar lang_code[3] = { '\0', '\0', '\0' }, *t;
385   gint title_set_nr;
386   gint num_titles;
387   gint i;
388
389   /* make sure our title number is valid */
390   num_titles = src->tt_srpt->nr_of_srpts;
391   GST_INFO_OBJECT (src, "There are %d titles on this DVD", num_titles);
392   if (title < 0 || title >= num_titles)
393     goto invalid_title;
394
395   src->num_chapters = src->tt_srpt->title[title].nr_of_ptts;
396   GST_INFO_OBJECT (src, "Title %d has %d chapters", title, src->num_chapters);
397
398   /* make sure the angle number is valid for this title */
399   src->num_angles = src->tt_srpt->title[title].nr_of_angles;
400   GST_LOG_OBJECT (src, "Title %d has %d angles", title, src->num_angles);
401   if (angle < 0 || angle >= src->num_angles) {
402     GST_WARNING_OBJECT (src, "Invalid angle %d (only %d available)",
403         angle, src->num_angles);
404     angle = CLAMP (angle, 0, src->num_angles - 1);
405   }
406
407   /* load the VTS information for the title set our title is in */
408   title_set_nr = src->tt_srpt->title[title].title_set_nr;
409   src->vts_file = ifoOpen (src->dvd, title_set_nr);
410   if (src->vts_file == NULL)
411     goto ifo_open_failed;
412
413   src->ttn = src->tt_srpt->title[title].vts_ttn;
414   src->vts_ptt_srpt = src->vts_file->vts_ptt_srpt;
415
416   /* we've got enough info, time to open the title set data */
417   src->dvd_title = DVDOpenFile (src->dvd, title_set_nr, DVD_READ_TITLE_VOBS);
418   if (src->dvd_title == NULL)
419     goto title_open_failed;
420
421   GST_INFO_OBJECT (src, "Opened title %d, angle %d", title, angle);
422   src->title = title;
423   src->angle = angle;
424
425   /* build event */
426
427   if (src->title_lang_event_pending) {
428     gst_event_unref (src->title_lang_event_pending);
429     src->title_lang_event_pending = NULL;
430   }
431
432   s = gst_structure_new ("application/x-gst-dvd",
433       "event", G_TYPE_STRING, "dvd-lang-codes", NULL);
434
435   /* audio */
436   for (i = 0; i < src->vts_file->vtsi_mat->nr_of_vts_audio_streams; i++) {
437     const audio_attr_t *a = &src->vts_file->vtsi_mat->vts_audio_attr[i];
438
439     t = g_strdup_printf ("audio-%d-format", i);
440     gst_structure_set (s, t, G_TYPE_INT, (int) a->audio_format, NULL);
441     g_free (t);
442
443     if (a->lang_type) {
444       t = g_strdup_printf ("audio-%d-language", i);
445       lang_code[0] = (a->lang_code >> 8) & 0xff;
446       lang_code[1] = a->lang_code & 0xff;
447       gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL);
448       g_free (t);
449     } else {
450       lang_code[0] = '\0';
451     }
452
453     GST_INFO_OBJECT (src, "[%02d] Audio    %02d: lang='%s', format=%d",
454         src->title, i, lang_code, (gint) a->audio_format);
455   }
456
457   /* subtitle */
458   for (i = 0; i < src->vts_file->vtsi_mat->nr_of_vts_subp_streams; i++) {
459     const subp_attr_t *u = &src->vts_file->vtsi_mat->vts_subp_attr[i];
460
461     if (u->type) {
462       t = g_strdup_printf ("subtitle-%d-language", i);
463       lang_code[0] = (u->lang_code >> 8) & 0xff;
464       lang_code[1] = u->lang_code & 0xff;
465       gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL);
466       g_free (t);
467     } else {
468       lang_code[0] = '\0';
469     }
470
471     GST_INFO_OBJECT (src, "[%02d] Subtitle %02d: lang='%s', format=%d",
472         src->title, i, lang_code);
473   }
474
475   src->title_lang_event_pending =
476       gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);
477
478   return TRUE;
479
480   /* ERRORS */
481 invalid_title:
482   {
483     GST_WARNING_OBJECT (src, "Invalid title %d (only %d available)",
484         title, num_titles);
485     return FALSE;
486   }
487 ifo_open_failed:
488   {
489     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
490         (_("Could not open DVD title %d"), title_set_nr),
491         ("ifoOpen(%d) failed: %s", title_set_nr, g_strerror (errno)));
492     return FALSE;
493   }
494 title_open_failed:
495   {
496     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
497         (_("Could not open DVD title %d"), title_set_nr),
498         ("Can't open title VOBS (VTS_%02d_1.VOB)", title_set_nr));
499     return FALSE;
500   }
501 }
502
503 /* FIXME: double-check this function, compare against original */
504 static gint
505 gst_dvd_read_src_get_next_cell_for (GstDvdReadSrc * src, gint cell)
506 {
507   /* Check if we're entering an angle block. */
508   if (src->cur_pgc->cell_playback[cell].block_type != BLOCK_TYPE_ANGLE_BLOCK)
509     return (cell + 1);
510
511   while (src->cur_pgc->cell_playback[cell].block_mode == BLOCK_MODE_LAST_CELL)
512     ++cell;
513
514   return cell + 1;              /* really +1? (tpm) */
515 }
516
517 /* Returns true if the pack is a NAV pack */
518 static gboolean
519 gst_dvd_read_src_is_nav_pack (const guint8 * data)
520 {
521   if (GST_READ_UINT32_BE (data + 0x26) != 0x000001BF)
522     return FALSE;
523
524   /* Check that this is substream 0 (PCI) */
525   if (data[0x2c] != 0)
526     return FALSE;
527
528   if (GST_READ_UINT32_BE (data + 0x400) != 0x000001BF)
529     return FALSE;
530
531   /* Check that this is substream 1 (DSI) */
532   if (data[0x406] != 1)
533     return FALSE;
534
535   /* Check sizes of PCI and DSI packets */
536   if (GST_READ_UINT16_BE (data + 0x2a) != 0x03d4)
537     return FALSE;
538
539   if (GST_READ_UINT16_BE (data + 0x404) != 0x03fa)
540     return FALSE;
541
542   return TRUE;
543 }
544
545 typedef enum
546 {
547   GST_DVD_READ_OK = 0,
548   GST_DVD_READ_ERROR = -1,
549   GST_DVD_READ_EOS = -2,
550   GST_DVD_READ_AGAIN = -3
551 } GstDvdReadReturn;
552
553 static GstDvdReadReturn
554 gst_dvd_read_src_read (GstDvdReadSrc * src, gint angle, gint new_seek,
555     GstBuffer ** p_buf)
556 {
557   GstBuffer *buf;
558   guint8 oneblock[DVD_VIDEO_LB_LEN];
559   dsi_t dsi_pack;
560   guint next_vobu, next_ilvu_start, cur_output_size;
561   gint len;
562
563   /* playback by cell in this pgc, starting at the cell for our chapter */
564   if (new_seek)
565     src->cur_cell = src->start_cell;
566
567 again:
568
569   if (src->cur_cell >= src->last_cell) {
570     /* advance to next chapter */
571     if (src->chapter == (src->num_chapters - 1))
572       goto eos;
573
574     GST_INFO_OBJECT (src, "end of chapter %d, switch to next", src->chapter);
575
576     ++src->chapter;
577     gst_dvd_read_src_goto_chapter (src, src->chapter);
578
579     return GST_DVD_READ_AGAIN;
580   }
581
582   if (src->new_cell || new_seek) {
583     if (!new_seek) {
584       src->cur_cell = src->next_cell;
585       if (src->cur_cell >= src->last_cell) {
586         GST_LOG_OBJECT (src, "last cell in chapter");
587         goto again;
588       }
589     }
590
591     /* take angle into account */
592     if (src->cur_pgc->cell_playback[src->cur_cell].block_type
593         == BLOCK_TYPE_ANGLE_BLOCK)
594       src->cur_cell += angle;
595
596     /* calculate next cell */
597     src->next_cell = gst_dvd_read_src_get_next_cell_for (src, src->cur_cell);
598
599     /* we loop until we're out of this cell */
600     src->cur_pack = src->cur_pgc->cell_playback[src->cur_cell].first_sector;
601     src->new_cell = FALSE;
602   }
603
604   if (src->cur_pack >= src->cur_pgc->cell_playback[src->cur_cell].last_sector) {
605     src->new_cell = TRUE;
606     GST_LOG_OBJECT (src, "Beyond last sector, go to next cell");
607     return GST_DVD_READ_AGAIN;
608   }
609
610   /* read NAV packet */
611 nav_retry:
612
613   len = DVDReadBlocks (src->dvd_title, src->cur_pack, 1, oneblock);
614   if (len == 0)
615     goto read_error;
616
617   if (!gst_dvd_read_src_is_nav_pack (oneblock)) {
618     src->cur_pack++;
619     goto nav_retry;
620   }
621
622   /* parse the contained dsi packet */
623   navRead_DSI (&dsi_pack, &oneblock[DSI_START_BYTE]);
624   g_assert (src->cur_pack == dsi_pack.dsi_gi.nv_pck_lbn);
625
626   /* determine where we go next. These values are the ones we
627    * mostly care about */
628   next_ilvu_start = src->cur_pack + dsi_pack.sml_agli.data[angle].address;
629   cur_output_size = dsi_pack.dsi_gi.vobu_ea;
630
631   /* If we're not at the end of this cell, we can determine the next
632    * VOBU to display using the VOBU_SRI information section of the
633    * DSI.  Using this value correctly follows the current angle,
634    * avoiding the doubled scenes in The Matrix, and makes our life
635    * really happy.
636    *
637    * Otherwise, we set our next address past the end of this cell to
638    * force the code above to go to the next cell in the program. */
639   if (dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL) {
640     next_vobu = src->cur_pack + (dsi_pack.vobu_sri.next_vobu & 0x7fffffff);
641   } else {
642     next_vobu = src->cur_pack + cur_output_size + 1;
643   }
644
645   g_assert (cur_output_size < 1024);
646   ++src->cur_pack;
647
648   /* create the buffer (TODO: use buffer pool?) */
649   buf = gst_buffer_new_and_alloc (cur_output_size * DVD_VIDEO_LB_LEN);
650
651   /* read in and output cursize packs */
652   len = DVDReadBlocks (src->dvd_title, src->cur_pack, cur_output_size,
653       GST_BUFFER_DATA (buf));
654
655   if (len != cur_output_size)
656     goto block_read_error;
657
658   GST_BUFFER_SIZE (buf) = cur_output_size * DVD_VIDEO_LB_LEN;
659   /* GST_BUFFER_OFFSET (buf) = priv->cur_pack * DVD_VIDEO_LB_LEN; */
660
661   gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (src)));
662
663   *p_buf = buf;
664
665   src->cur_pack = next_vobu;
666
667   GST_LOG_OBJECT (src, "Read %u sectors", cur_output_size);
668
669   return GST_DVD_READ_OK;
670
671   /* ERRORS */
672 eos:
673   {
674     GST_INFO_OBJECT (src, "last chapter done - EOS");
675     return GST_DVD_READ_EOS;
676   }
677 read_error:
678   {
679     GST_ERROR_OBJECT (src, "Read failed for block %d", src->cur_pack);
680     return GST_DVD_READ_ERROR;
681   }
682 block_read_error:
683   {
684     GST_ERROR_OBJECT (src, "Read failed for %d blocks at %d",
685         cur_output_size, src->cur_pack);
686     gst_buffer_unref (buf);
687     return GST_DVD_READ_ERROR;
688   }
689 }
690
691 static GstFlowReturn
692 gst_dvd_read_src_create (GstPushSrc * pushsrc, GstBuffer ** p_buf)
693 {
694   GstDvdReadSrc *src = GST_DVD_READ_SRC (pushsrc);
695   GstPad *srcpad;
696   gint res;
697
698   g_return_val_if_fail (src->dvd != NULL, GST_FLOW_ERROR);
699
700   srcpad = GST_BASE_SRC (src)->srcpad;
701
702   if (src->need_newsegment) {
703     gst_pad_push_event (srcpad,
704         gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
705             src->cur_pack * DVD_VIDEO_LB_LEN, -1, 0));
706     src->need_newsegment = FALSE;
707   }
708
709   if (src->new_seek) {
710     gst_dvd_read_src_goto_title (src, src->title, src->angle);
711     gst_dvd_read_src_goto_chapter (src, src->chapter);
712
713     src->new_seek = FALSE;
714     src->change_cell = TRUE;
715   }
716
717   if (src->title_lang_event_pending) {
718     gst_pad_push_event (srcpad, src->title_lang_event_pending);
719     src->title_lang_event_pending = NULL;
720   }
721
722   if (src->pending_clut_event) {
723     gst_pad_push_event (srcpad, src->pending_clut_event);
724     src->pending_clut_event = NULL;
725   }
726
727   /* read it in */
728   do {
729     res = gst_dvd_read_src_read (src, src->angle, src->change_cell, p_buf);
730   } while (res == GST_DVD_READ_AGAIN);
731
732   switch (res) {
733     case GST_DVD_READ_ERROR:{
734       GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), (NULL));
735       return GST_FLOW_ERROR;
736     }
737     case GST_DVD_READ_EOS:{
738       GST_INFO_OBJECT (src, "Reached EOS");
739       return GST_FLOW_UNEXPECTED;
740     }
741     case GST_DVD_READ_OK:{
742       src->change_cell = FALSE;
743       return GST_FLOW_OK;
744     }
745     default:
746       break;
747   }
748
749   g_return_val_if_reached (GST_FLOW_UNEXPECTED);
750 }
751
752 static void
753 gst_dvd_read_src_set_property (GObject * object, guint prop_id,
754     const GValue * value, GParamSpec * pspec)
755 {
756   GstDvdReadSrc *src = GST_DVD_READ_SRC (object);
757   gboolean started;
758
759   GST_OBJECT_LOCK (src);
760   started = GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_STARTED);
761
762   switch (prop_id) {
763     case ARG_DEVICE:{
764       if (started) {
765         g_warning ("%s: property '%s' needs to be set before the device is "
766             "opened", GST_ELEMENT_NAME (src), pspec->name);
767         break;;
768       }
769
770       g_free (src->location);
771       /* clear the filename if we get a NULL (is that possible?) */
772       if (g_value_get_string (value) == NULL) {
773         src->location = g_strdup ("/dev/dvd");
774       } else {
775         src->location = g_strdup (g_value_get_string (value));
776       }
777       break;
778     }
779     case ARG_TITLE:
780       src->uri_title = g_value_get_int (value);
781       if (started) {
782         src->title = src->uri_title - 1;
783         src->new_seek = TRUE;
784       }
785       break;
786     case ARG_CHAPTER:
787       src->uri_chapter = g_value_get_int (value);
788       if (started) {
789         src->chapter = src->uri_chapter - 1;
790         src->new_seek = TRUE;
791       }
792       break;
793     case ARG_ANGLE:
794       src->uri_angle = g_value_get_int (value);
795       if (started) {
796         src->angle = src->uri_angle - 1;
797       }
798       break;
799     default:
800       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
801       break;
802   }
803
804   GST_OBJECT_UNLOCK (src);
805 }
806
807 static void
808 gst_dvd_read_src_get_property (GObject * object, guint prop_id, GValue * value,
809     GParamSpec * pspec)
810 {
811   GstDvdReadSrc *src = GST_DVD_READ_SRC (object);
812
813   GST_OBJECT_LOCK (src);
814
815   switch (prop_id) {
816     case ARG_DEVICE:
817       g_value_set_string (value, src->location);
818       break;
819     case ARG_TITLE:
820       g_value_set_int (value, src->uri_title);
821       break;
822     case ARG_CHAPTER:
823       g_value_set_int (value, src->uri_chapter);
824       break;
825     case ARG_ANGLE:
826       g_value_set_int (value, src->uri_angle);
827       break;
828     default:
829       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
830       break;
831   }
832
833   GST_OBJECT_UNLOCK (src);
834 }
835
836 static gboolean
837 gst_dvd_read_src_get_size (GstDvdReadSrc * src, gint64 * size)
838 {
839   gboolean ret = FALSE;
840
841   if (src->dvd_title) {
842     gsize blocks;
843
844     blocks = DVDFileSize (src->dvd_title);
845     if (blocks >= 0) {
846       *size = (gint64) blocks *DVD_VIDEO_LB_LEN;
847
848       ret = TRUE;
849     } else {
850       GST_WARNING_OBJECT (src, "DVDFileSize(%p) failed!", src->dvd_title);
851     }
852   }
853
854   return ret;
855 }
856
857 /*** Querying and seeking ***/
858
859 static gboolean
860 gst_dvd_read_src_handle_seek_event (GstDvdReadSrc * src, GstEvent * event)
861 {
862   GstSeekFlags flags;
863   GstSeekType cur_type, end_type;
864   gint64 new_off, total;
865   GstFormat format;
866   GstPad *srcpad;
867   gboolean query_ok;
868   gdouble rate;
869
870   gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &new_off,
871       &end_type, NULL);
872
873   if (rate <= 0.0) {
874     GST_DEBUG_OBJECT (src, "cannot do backwards playback yet");
875     return FALSE;
876   }
877
878   if ((flags & GST_SEEK_FLAG_SEGMENT) != 0) {
879     GST_DEBUG_OBJECT (src, "segment seek not supported");
880     return FALSE;
881   }
882
883   if ((flags & GST_SEEK_FLAG_FLUSH) == 0) {
884     GST_DEBUG_OBJECT (src, "can only do flushing seeks at the moment");
885     return FALSE;
886   }
887
888   if (end_type != GST_SEEK_TYPE_NONE) {
889     GST_DEBUG_OBJECT (src, "end seek type not supported");
890     return FALSE;
891   }
892
893   if (cur_type != GST_SEEK_TYPE_SET) {
894     GST_DEBUG_OBJECT (src, "only SEEK_TYPE_SET is supported");
895     return FALSE;
896   }
897
898   if (format == angle_format) {
899     GST_OBJECT_LOCK (src);
900     if (new_off < 0 || new_off >= src->num_angles) {
901       GST_OBJECT_UNLOCK (src);
902       GST_DEBUG_OBJECT (src, "invalid angle %d, only %d available",
903           src->num_angles);
904       return FALSE;
905     }
906     src->angle = (gint) new_off;
907     GST_OBJECT_UNLOCK (src);
908     GST_DEBUG_OBJECT (src, "switched to angle %d", (gint) new_off);
909     return TRUE;
910   }
911
912   if (format != chapter_format && format != title_format &&
913       format != GST_FORMAT_BYTES) {
914     GST_DEBUG_OBJECT (src, "unsupported seek format %d (%s)", format,
915         gst_format_get_name (format));
916     return FALSE;
917   }
918
919   if (format == GST_FORMAT_BYTES) {
920     GST_DEBUG_OBJECT (src, "Requested seek to byte %" G_GUINT64_FORMAT,
921         new_off);
922   } else if (format == GST_FORMAT_TIME) {
923     GST_DEBUG_OBJECT (src, "Requested seek to time %" GST_TIME_FORMAT,
924         GST_TIME_ARGS (new_off));
925   }
926
927   srcpad = GST_BASE_SRC_PAD (src);
928
929   /* check whether the seek looks reasonable (ie within possible range) */
930   if (format == GST_FORMAT_BYTES) {
931     GST_OBJECT_LOCK (src);
932     query_ok = gst_dvd_read_src_get_size (src, &total);
933     GST_OBJECT_UNLOCK (src);
934   } else {
935     query_ok = gst_pad_query_duration (srcpad, &format, &total);
936   }
937
938   if (!query_ok) {
939     GST_DEBUG_OBJECT (src, "Failed to query duration in format %s",
940         gst_format_get_name (format));
941     return FALSE;
942   }
943
944   GST_DEBUG_OBJECT (src, "Total      %s: %12" G_GINT64_FORMAT,
945       gst_format_get_name (format), total);
946   GST_DEBUG_OBJECT (src, "Seek to    %s: %12" G_GINT64_FORMAT,
947       gst_format_get_name (format), new_off);
948
949   if (new_off >= total) {
950     GST_DEBUG_OBJECT (src, "Seek position out of range");
951     return FALSE;
952   }
953
954   /* set segment to seek format; this allows us to use the do_seek
955    * virtual function and let the base source handle all the tricky
956    * stuff for us. We don't use the segment internally anyway */
957   /* FIXME: can't take the stream lock here - what to do? */
958   GST_OBJECT_LOCK (src);
959   GST_BASE_SRC (src)->segment.format = format;
960   GST_BASE_SRC (src)->segment.start = 0;
961   GST_BASE_SRC (src)->segment.stop = total;
962   GST_BASE_SRC (src)->segment.duration = total;
963   GST_OBJECT_UNLOCK (src);
964
965   return GST_BASE_SRC_CLASS (parent_class)->event (GST_BASE_SRC (src), event);
966 }
967
968 static gboolean
969 gst_dvd_read_src_do_seek (GstBaseSrc * basesrc, GstSegment * s)
970 {
971   GstDvdReadSrc *src;
972
973   src = GST_DVD_READ_SRC (basesrc);
974
975   GST_DEBUG_OBJECT (src, "Seeking to %s: %12" G_GINT64_FORMAT,
976       gst_format_get_name (s->format), s->last_stop);
977
978   if (s->format == sector_format || s->format == GST_FORMAT_BYTES) {
979     guint old;
980
981     old = src->cur_pack;
982
983     if (s->format == sector_format) {
984       src->cur_pack = s->last_stop;
985     } else {
986       /* byte format */
987       src->cur_pack = s->last_stop / DVD_VIDEO_LB_LEN;
988       if ((src->cur_pack * DVD_VIDEO_LB_LEN) != s->last_stop) {
989         GST_LOG_OBJECT (src, "rounded down offset %" G_GINT64_FORMAT " => %"
990             G_GINT64_FORMAT, s->last_stop,
991             (gint64) src->cur_pack * DVD_VIDEO_LB_LEN);
992       }
993     }
994
995     if (!gst_dvd_read_src_goto_sector (src, src->angle)) {
996       GST_DEBUG_OBJECT (src, "seek to sector 0x%08x failed", src->cur_pack);
997       src->cur_pack = old;
998       return FALSE;
999     }
1000
1001     GST_LOG_OBJECT (src, "seek to sector 0x%08x ok", src->cur_pack);
1002   } else if (s->format == chapter_format) {
1003     if (!gst_dvd_read_src_goto_chapter (src, (gint) s->last_stop)) {
1004       GST_DEBUG_OBJECT (src, "seek to chapter %d failed", (gint) s->last_stop);
1005       return FALSE;
1006     }
1007     GST_INFO_OBJECT (src, "seek to chapter %d ok", (gint) s->last_stop);
1008     src->chapter = s->last_stop;
1009   } else if (s->format == title_format) {
1010     if (!gst_dvd_read_src_goto_title (src, (gint) s->last_stop, src->angle) ||
1011         !gst_dvd_read_src_goto_chapter (src, 0)) {
1012       GST_DEBUG_OBJECT (src, "seek to title %d failed", (gint) s->last_stop);
1013       return FALSE;
1014     }
1015     src->title = (gint) s->last_stop;
1016     src->chapter = 0;
1017     GST_INFO_OBJECT (src, "seek to title %d ok", src->title);
1018   } else {
1019     g_return_val_if_reached (FALSE);
1020   }
1021
1022   src->need_newsegment = TRUE;
1023   return TRUE;
1024 }
1025
1026 static gboolean
1027 gst_dvd_read_src_src_event (GstBaseSrc * basesrc, GstEvent * event)
1028 {
1029   GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc);
1030   gboolean res;
1031
1032   GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event));
1033
1034   switch (GST_EVENT_TYPE (event)) {
1035     case GST_EVENT_SEEK:
1036       res = gst_dvd_read_src_handle_seek_event (src, event);
1037       break;
1038     default:
1039       res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
1040       break;
1041   }
1042
1043   return res;
1044 }
1045
1046 static GstEvent *
1047 gst_dvd_read_src_make_clut_change_event (GstDvdReadSrc * src,
1048     const guint * clut)
1049 {
1050   GstStructure *structure;
1051   gchar name[16];
1052   gint i;
1053
1054   structure = gst_structure_new ("application/x-gst-dvd",
1055       "event", G_TYPE_STRING, "dvd-spu-clut-change", NULL);
1056
1057   /* Create a separate field for each value in the table. */
1058   for (i = 0; i < 16; i++) {
1059     g_snprintf (name, sizeof (name), "clut%02d", i);
1060     gst_structure_set (structure, name, G_TYPE_INT, (int) clut[i], NULL);
1061   }
1062
1063   /* Create the DVD event and put the structure into it. */
1064   return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure);
1065 }
1066
1067 static gint64
1068 gst_dvd_read_src_convert_timecode (dvd_time_t * time)
1069 {
1070   gint64 ret_time;
1071   const gint64 one_hour = 3600 * GST_SECOND;
1072   const gint64 one_min = 60 * GST_SECOND;
1073
1074   g_return_val_if_fail ((time->hour >> 4) < 0xa
1075       && (time->hour & 0xf) < 0xa, -1);
1076   g_return_val_if_fail ((time->minute >> 4) < 0x7
1077       && (time->minute & 0xf) < 0xa, -1);
1078   g_return_val_if_fail ((time->second >> 4) < 0x7
1079       && (time->second & 0xf) < 0xa, -1);
1080
1081   ret_time = ((time->hour >> 4) * 10 + (time->hour & 0xf)) * one_hour;
1082   ret_time += ((time->minute >> 4) * 10 + (time->minute & 0xf)) * one_min;
1083   ret_time += ((time->second >> 4) * 10 + (time->second & 0xf)) * GST_SECOND;
1084
1085   return ret_time;
1086 }
1087
1088 static gboolean
1089 gst_dvd_read_src_do_duration_query (GstDvdReadSrc * src, GstQuery * query)
1090 {
1091   GstFormat format;
1092   gint64 val;
1093
1094   gst_query_parse_duration (query, &format, NULL);
1095
1096   switch (format) {
1097     case GST_FORMAT_TIME:{
1098       if (src->cur_pgc == NULL)
1099         return FALSE;
1100       val = gst_dvd_read_src_convert_timecode (&src->cur_pgc->playback_time);
1101       if (val < 0)
1102         return FALSE;
1103       break;
1104     }
1105     case GST_FORMAT_BYTES:{
1106       if (!gst_dvd_read_src_get_size (src, &val))
1107         return FALSE;
1108       break;
1109     }
1110     default:{
1111       if (format == sector_format) {
1112         val = DVDFileSize (src->dvd_title);
1113       } else if (format == title_format) {
1114         val = src->tt_srpt->nr_of_srpts;
1115       } else if (format == chapter_format) {
1116         val = src->num_chapters;
1117       } else if (format == angle_format) {
1118         val = src->tt_srpt->title[src->title].nr_of_angles;
1119       } else {
1120         GST_DEBUG_OBJECT (src, "Don't know how to handle format %d (%s)",
1121             format, gst_format_get_name (format));
1122         return FALSE;
1123       }
1124       break;
1125     }
1126   }
1127
1128   GST_LOG_OBJECT (src, "duration = %" G_GINT64_FORMAT " %s", val,
1129       gst_format_get_name (format));
1130
1131   gst_query_set_duration (query, format, val);
1132   return TRUE;
1133 }
1134
1135 static gboolean
1136 gst_dvd_read_src_do_position_query (GstDvdReadSrc * src, GstQuery * query)
1137 {
1138   GstFormat format;
1139   gint64 val;
1140
1141   gst_query_parse_position (query, &format, NULL);
1142
1143   switch (format) {
1144     case GST_FORMAT_BYTES:{
1145       val = src->cur_pack * DVD_VIDEO_LB_LEN;
1146       break;
1147     }
1148     default:{
1149       if (format == sector_format) {
1150         val = src->cur_pack;
1151       } else if (format == title_format) {
1152         val = src->title;
1153       } else if (format == chapter_format) {
1154         val = src->chapter;
1155       } else if (format == angle_format) {
1156         val = src->angle;
1157       } else {
1158         GST_DEBUG_OBJECT (src, "Don't know how to handle format %d (%s)",
1159             format, gst_format_get_name (format));
1160         return FALSE;
1161       }
1162       break;
1163     }
1164   }
1165
1166   GST_LOG_OBJECT (src, "position = %" G_GINT64_FORMAT " %s", val,
1167       gst_format_get_name (format));
1168
1169   gst_query_set_position (query, format, val);
1170   return TRUE;
1171 }
1172
1173 static gboolean
1174 gst_dvd_read_src_src_query (GstBaseSrc * basesrc, GstQuery * query)
1175 {
1176   GstDvdReadSrc *src = GST_DVD_READ_SRC (basesrc);
1177   gboolean started;
1178   gboolean res = TRUE;
1179
1180   GST_LOG_OBJECT (src, "handling %s query",
1181       gst_query_type_get_name (GST_QUERY_TYPE (query)));
1182
1183   GST_OBJECT_LOCK (src);
1184   started = (GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_STARTED));
1185   GST_OBJECT_UNLOCK (src);
1186
1187   if (!started) {
1188     GST_DEBUG_OBJECT (src, "query failed: not started");
1189     return FALSE;
1190   }
1191
1192   switch (GST_QUERY_TYPE (query)) {
1193     case GST_QUERY_DURATION:
1194       GST_OBJECT_LOCK (src);
1195       res = gst_dvd_read_src_do_duration_query (src, query);
1196       GST_OBJECT_UNLOCK (src);
1197       break;
1198     case GST_QUERY_POSITION:
1199       GST_OBJECT_LOCK (src);
1200       res = gst_dvd_read_src_do_position_query (src, query);
1201       GST_OBJECT_UNLOCK (src);
1202       break;
1203     default:
1204       res = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
1205       break;
1206   }
1207
1208   return res;
1209 }
1210
1211 static gboolean
1212 gst_dvd_read_src_goto_sector (GstDvdReadSrc * src, int angle)
1213 {
1214   gint seek_to = src->cur_pack;
1215   gint chapter, sectors, next, cur, i;
1216
1217   /* retrieve position */
1218   src->cur_pack = 0;
1219   for (i = 0; i < src->num_chapters; i++) {
1220     gint c1, c2;
1221
1222     cur_title_get_chapter_bounds (src, i, &c1, &c2);
1223
1224     for (next = cur = c1; cur < c2;) {
1225       if (next != cur) {
1226         sectors =
1227             src->cur_pgc->cell_playback[cur].last_sector -
1228             src->cur_pgc->cell_playback[cur].first_sector;
1229         if (src->cur_pack + sectors > seek_to) {
1230           chapter = i;
1231           goto done;
1232         }
1233         src->cur_pack += sectors;
1234       }
1235       cur = next;
1236       if (src->cur_pgc->cell_playback[cur].block_type == BLOCK_TYPE_ANGLE_BLOCK)
1237         cur += angle;
1238       next = gst_dvd_read_src_get_next_cell_for (src, cur);
1239     }
1240   }
1241
1242   GST_DEBUG_OBJECT (src, "Seek to sector %u failed", seek_to);
1243
1244   return FALSE;
1245
1246 done:
1247   {
1248     /* so chapter $chapter and cell $cur contain our sector
1249      * of interest. Let's go there! */
1250     GST_INFO_OBJECT (src, "Seek succeeded, going to chapter %u, cell %u",
1251         chapter, cur);
1252
1253     gst_dvd_read_src_goto_chapter (src, chapter);
1254     src->cur_cell = cur;
1255     src->next_cell = next;
1256     src->new_cell = FALSE;
1257     src->cur_pack = seek_to;
1258
1259     return TRUE;
1260   }
1261 }
1262
1263
1264 /*** URI interface ***/
1265
1266 static GstURIType
1267 gst_dvd_read_src_uri_get_type (void)
1268 {
1269   return GST_URI_SRC;
1270 }
1271
1272 static gchar **
1273 gst_dvd_read_src_uri_get_protocols (void)
1274 {
1275   static gchar *protocols[] = { "dvd", NULL };
1276
1277   return protocols;
1278 }
1279
1280 static const gchar *
1281 gst_dvd_read_src_uri_get_uri (GstURIHandler * handler)
1282 {
1283   GstDvdReadSrc *src = GST_DVD_READ_SRC (handler);
1284
1285   GST_OBJECT_LOCK (src);
1286
1287   g_free (src->last_uri);
1288   src->last_uri = g_strdup_printf ("dvd://%d,%d,%d", src->uri_title,
1289       src->uri_chapter, src->uri_angle);
1290
1291   GST_OBJECT_UNLOCK (src);
1292
1293   return src->last_uri;
1294 }
1295
1296 static gboolean
1297 gst_dvd_read_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
1298 {
1299   GstDvdReadSrc *src = GST_DVD_READ_SRC (handler);
1300   gboolean ret;
1301   gchar *protocol;
1302
1303   protocol = gst_uri_get_protocol (uri);
1304   ret = (protocol != NULL && g_str_equal (protocol, "dvd"));
1305   g_free (protocol);
1306   protocol = NULL;
1307
1308   if (!ret)
1309     return ret;
1310
1311   /* parse out the new t/c/a and seek to them */
1312   {
1313     gchar *location = NULL;
1314     gchar **strs;
1315     gchar **strcur;
1316     gint pos = 0;
1317
1318     location = gst_uri_get_location (uri);
1319
1320     if (!location)
1321       return ret;
1322
1323     GST_OBJECT_LOCK (src);
1324
1325     src->uri_title = 1;
1326     src->uri_chapter = 1;
1327     src->uri_angle = 1;
1328
1329     strcur = strs = g_strsplit (location, ",", 0);
1330     while (strcur && *strcur) {
1331       gint val;
1332
1333       if (!sscanf (*strcur, "%d", &val))
1334         break;
1335
1336       if (val <= 0) {
1337         g_warning ("Invalid value %d in URI '%s'. Must be 1 or greater",
1338             val, location);
1339         break;
1340       }
1341
1342       switch (pos) {
1343         case 0:
1344           src->uri_title = val;
1345           break;
1346         case 1:
1347           src->uri_chapter = val;
1348           break;
1349         case 2:
1350           src->uri_angle = val;
1351           break;
1352       }
1353
1354       strcur++;
1355       pos++;
1356     }
1357
1358     if (pos > 0 && GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_STARTED)) {
1359       src->title = src->uri_title - 1;
1360       src->chapter = src->uri_chapter - 1;
1361       src->angle = src->uri_angle - 1;
1362       src->new_seek = TRUE;
1363     }
1364
1365     GST_OBJECT_UNLOCK (src);
1366
1367     g_strfreev (strs);
1368     g_free (location);
1369   }
1370
1371   return ret;
1372 }
1373
1374 static void
1375 gst_dvd_read_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1376 {
1377   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1378
1379   iface->get_type = gst_dvd_read_src_uri_get_type;
1380   iface->get_protocols = gst_dvd_read_src_uri_get_protocols;
1381   iface->get_uri = gst_dvd_read_src_uri_get_uri;
1382   iface->set_uri = gst_dvd_read_src_uri_set_uri;
1383 }
1384
1385 static void
1386 gst_dvd_read_src_do_init (GType dvdreadsrc_type)
1387 {
1388   static const GInterfaceInfo urihandler_info = {
1389     gst_dvd_read_src_uri_handler_init,
1390     NULL,
1391     NULL
1392   };
1393
1394   g_type_add_interface_static (dvdreadsrc_type, GST_TYPE_URI_HANDLER,
1395       &urihandler_info);
1396
1397   title_format = gst_format_register ("title", "DVD title");
1398   angle_format = gst_format_register ("angle", "DVD angle");
1399   sector_format = gst_format_register ("sector", "DVD sector");
1400   chapter_format = gst_format_register ("chapter", "DVD chapter");
1401 }
1402
1403 static gboolean
1404 plugin_init (GstPlugin * plugin)
1405 {
1406   GST_DEBUG_CATEGORY_INIT (gstgst_dvd_read_src_debug, "dvdreadsrc", 0,
1407       "DVD reader element based on dvdreadsrc");
1408
1409   if (!gst_element_register (plugin, "dvdreadsrc", GST_RANK_SECONDARY,
1410           GST_TYPE_DVD_READ_SRC)) {
1411     return FALSE;
1412   }
1413
1414   return TRUE;
1415 }
1416
1417 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1418     GST_VERSION_MINOR,
1419     "dvdread",
1420     "Access a DVD with dvdread",
1421     plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);