ext/dvdread/dvdreadsrc.*: Add basic support for time-based seeking; set timestamps...
authorTim-Philipp Müller <tim@centricular.net>
Sun, 6 Aug 2006 19:47:40 +0000 (19:47 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Sun, 6 Aug 2006 19:47:40 +0000 (19:47 +0000)
Original commit message from CVS:
* ext/dvdread/dvdreadsrc.c: (gst_dvd_read_src_stop),
(gst_dvd_read_src_goto_chapter),
(gst_dvd_read_src_get_chapter_starts),
(gst_dvd_read_src_goto_title), (gst_dvd_read_src_get_next_cell),
(gst_dvd_read_src_get_time_for_sector),
(gst_dvd_read_src_get_sector_from_time), (gst_dvd_read_src_read),
(gst_dvd_read_src_handle_seek_event), (gst_dvd_read_src_do_seek),
(gst_dvd_read_src_goto_sector):
* ext/dvdread/dvdreadsrc.h:
Add basic support for time-based seeking; set timestamps on
outgoing buffers if we have them; create table with
chapter to time mapping when opening a title; rename
gst_dvd_read_src_get_next_cell_for() to _get_next_cell() and
make it take an explicit pgc argument; fix up some debugging
messages so that title/chapter numbers are printed as starting
from 1 for easier readability.

ChangeLog
ext/dvdread/dvdreadsrc.c
ext/dvdread/dvdreadsrc.h

index 1173359..5b84b47 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2006-08-06  Tim-Philipp Müller  <tim at centricular dot net>
+
+       * ext/dvdread/dvdreadsrc.c: (gst_dvd_read_src_stop),
+       (gst_dvd_read_src_goto_chapter),
+       (gst_dvd_read_src_get_chapter_starts),
+       (gst_dvd_read_src_goto_title), (gst_dvd_read_src_get_next_cell),
+       (gst_dvd_read_src_get_time_for_sector),
+       (gst_dvd_read_src_get_sector_from_time), (gst_dvd_read_src_read),
+       (gst_dvd_read_src_handle_seek_event), (gst_dvd_read_src_do_seek),
+       (gst_dvd_read_src_goto_sector):
+       * ext/dvdread/dvdreadsrc.h:
+         Add basic support for time-based seeking; set timestamps on
+         outgoing buffers if we have them; create table with
+         chapter to time mapping when opening a title; rename
+         gst_dvd_read_src_get_next_cell_for() to _get_next_cell() and
+         make it take an explicit pgc argument; fix up some debugging
+         messages so that title/chapter numbers are printed as starting
+         from 1 for easier readability.
+
 2006-08-04  Tim-Philipp Müller  <tim at centricular dot net>
 
        * gst/realmedia/Makefile.am:
index 4cc0d09..bc7a38b 100644 (file)
@@ -1,6 +1,7 @@
-/* GStreamer
+/* GStreamer DVD title source
  * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
  * Copyright (C) 2001 Billy Biggs <vektor@dumbterm.net>.
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -86,6 +87,13 @@ 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);
 
 GST_BOILERPLATE_FULL (GstDvdReadSrc, gst_dvd_read_src, GstPushSrc,
     GST_TYPE_PUSH_SRC, gst_dvd_read_src_do_init);
@@ -270,6 +278,7 @@ gst_dvd_read_src_stop (GstBaseSrc * basesrc)
   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;
@@ -278,6 +287,10 @@ gst_dvd_read_src_stop (GstBaseSrc * basesrc)
     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");
 
@@ -342,7 +355,7 @@ gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src, gint chapter)
   cur_title_get_chapter_bounds (src, chapter, &src->start_cell,
       &src->last_cell);
 
-  GST_LOG_OBJECT (src, "Opened chapter %d - cell %d-%d", chapter,
+  GST_LOG_OBJECT (src, "Opened chapter %d - cell %d-%d", chapter + 1,
       src->start_cell, src->last_cell);
 
   /* retrieve position */
@@ -375,6 +388,45 @@ gst_dvd_read_src_goto_chapter (GstDvdReadSrc * src, gint chapter)
   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)
 {
@@ -391,11 +443,12 @@ gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle)
     goto invalid_title;
 
   src->num_chapters = src->tt_srpt->title[title].nr_of_ptts;
-  GST_INFO_OBJECT (src, "Title %d has %d chapters", title, src->num_chapters);
+  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, src->num_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);
@@ -416,7 +469,7 @@ gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle)
   if (src->dvd_title == NULL)
     goto title_open_failed;
 
-  GST_INFO_OBJECT (src, "Opened title %d, angle %d", title, angle);
+  GST_INFO_OBJECT (src, "Opened title %d, angle %d", title + 1, angle);
   src->title = title;
   src->angle = angle;
 
@@ -449,7 +502,7 @@ gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle)
     }
 
     GST_INFO_OBJECT (src, "[%02d] Audio    %02d: lang='%s', format=%d",
-        src->title, i, lang_code, (gint) a->audio_format);
+        src->title + 1, i, lang_code, (gint) a->audio_format);
   }
 
   /* subtitle */
@@ -467,12 +520,42 @@ gst_dvd_read_src_goto_title (GstDvdReadSrc * src, gint title, gint angle)
     }
 
     GST_INFO_OBJECT (src, "[%02d] Subtitle %02d: lang='%s', format=%d",
-        src->title, i, lang_code);
+        src->title + 1, i, lang_code);
   }
 
   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 = 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 */
@@ -500,13 +583,13 @@ title_open_failed:
 
 /* FIXME: double-check this function, compare against original */
 static gint
-gst_dvd_read_src_get_next_cell_for (GstDvdReadSrc * src, gint cell)
+gst_dvd_read_src_get_next_cell (GstDvdReadSrc * src, pgc_t * pgc, gint cell)
 {
   /* Check if we're entering an angle block. */
-  if (src->cur_pgc->cell_playback[cell].block_type != BLOCK_TYPE_ANGLE_BLOCK)
+  if (pgc->cell_playback[cell].block_type != BLOCK_TYPE_ANGLE_BLOCK)
     return (cell + 1);
 
-  while (src->cur_pgc->cell_playback[cell].block_mode == BLOCK_MODE_LAST_CELL)
+  while (pgc->cell_playback[cell].block_mode == BLOCK_MODE_LAST_CELL)
     ++cell;
 
   return cell + 1;              /* really +1? (tpm) */
@@ -540,6 +623,58 @@ gst_dvd_read_src_is_nav_pack (const guint8 * data)
   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 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, i, j;
+
+  if (src->vts_tmapt == NULL || src->vts_tmapt->nr_of_tmaps == 0)
+    return -1;
+
+  sector = 0;
+  for (i = 0; i < src->vts_tmapt->nr_of_tmaps; ++i) {
+    for (j = 0; j < src->vts_tmapt->tmap[i].nr_of_entries; ++j) {
+      GstClockTime entry_time;
+
+      entry_time = src->vts_tmapt->tmap[i].tmu * (j + 1) * GST_SECOND;
+      if (entry_time <= ts) {
+        sector = src->vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff;
+      }
+      if (entry_time >= ts) {
+        return sector;
+      }
+    }
+  }
+
+  if (ts == 0)
+    return 0;
+
+  return -1;
+}
+
 typedef enum
 {
   GST_DVD_READ_OK = 0,
@@ -569,7 +704,8 @@ again:
     if (src->chapter == (src->num_chapters - 1))
       goto eos;
 
-    GST_INFO_OBJECT (src, "end of chapter %d, switch to next", src->chapter);
+    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);
@@ -592,7 +728,8 @@ again:
       src->cur_cell += angle;
 
     /* calculate next cell */
-    src->next_cell = gst_dvd_read_src_get_next_cell_for (src, src->cur_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;
@@ -655,6 +792,8 @@ nav_retry:
 
   GST_BUFFER_SIZE (buf) = 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);
 
   gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (src)));
 
@@ -903,12 +1042,12 @@ gst_dvd_read_src_handle_seek_event (GstDvdReadSrc * src, GstEvent * event)
     }
     src->angle = (gint) new_off;
     GST_OBJECT_UNLOCK (src);
-    GST_DEBUG_OBJECT (src, "switched to angle %d", (gint) new_off);
+    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_BYTES && format != GST_FORMAT_TIME) {
     GST_DEBUG_OBJECT (src, "unsupported seek format %d (%s)", format,
         gst_format_get_name (format));
     return FALSE;
@@ -920,6 +1059,10 @@ gst_dvd_read_src_handle_seek_event (GstDvdReadSrc * src, GstEvent * event)
   } 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);
@@ -973,13 +1116,26 @@ gst_dvd_read_src_do_seek (GstBaseSrc * basesrc, GstSegment * s)
   GST_DEBUG_OBJECT (src, "Seeking to %s: %12" G_GINT64_FORMAT,
       gst_format_get_name (s->format), s->last_stop);
 
-  if (s->format == sector_format || s->format == GST_FORMAT_BYTES) {
+  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) {
       src->cur_pack = s->last_stop;
+    } else if (s->format == GST_FORMAT_TIME) {
+      gint sector;
+
+      sector = gst_dvd_read_src_get_sector_from_time (src, s->last_stop);
+
+      GST_DEBUG_OBJECT (src, "Time %" GST_TIME_FORMAT " => sector %d",
+          GST_TIME_ARGS (s->last_stop), 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 */
       src->cur_pack = s->last_stop / DVD_VIDEO_LB_LEN;
@@ -999,10 +1155,11 @@ gst_dvd_read_src_do_seek (GstBaseSrc * basesrc, GstSegment * s)
     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->last_stop)) {
-      GST_DEBUG_OBJECT (src, "seek to chapter %d failed", (gint) s->last_stop);
+      GST_DEBUG_OBJECT (src, "seek to chapter %d failed",
+          (gint) s->last_stop + 1);
       return FALSE;
     }
-    GST_INFO_OBJECT (src, "seek to chapter %d ok", (gint) s->last_stop);
+    GST_INFO_OBJECT (src, "seek to chapter %d ok", (gint) s->last_stop + 1);
     src->chapter = s->last_stop;
   } else if (s->format == title_format) {
     if (!gst_dvd_read_src_goto_title (src, (gint) s->last_stop, src->angle) ||
@@ -1012,7 +1169,7 @@ gst_dvd_read_src_do_seek (GstBaseSrc * basesrc, GstSegment * s)
     }
     src->title = (gint) s->last_stop;
     src->chapter = 0;
-    GST_INFO_OBJECT (src, "seek to title %d ok", src->title);
+    GST_INFO_OBJECT (src, "seek to title %d ok", src->title + 1);
   } else {
     g_return_val_if_reached (FALSE);
   }
@@ -1233,7 +1390,7 @@ gst_dvd_read_src_goto_sector (GstDvdReadSrc * src, int angle)
       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_for (src, cur);
+      next = gst_dvd_read_src_get_next_cell (src, src->cur_pgc, cur);
     }
   }
 
@@ -1246,7 +1403,7 @@ 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, cur);
+        chapter + 1, cur);
 
     gst_dvd_read_src_goto_chapter (src, chapter);
     src->cur_cell = cur;
index b7fcd4a..77e8183 100644 (file)
@@ -1,5 +1,7 @@
-/* GStreamer
+/* GStreamer DVD title source
  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2001> Billy Biggs <vektor@dumbterm.net>.
+ * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -71,10 +73,13 @@ struct _GstDvdReadSrc {
   tt_srpt_t       *tt_srpt;
   ifo_handle_t    *vts_file;
   vts_ptt_srpt_t  *vts_ptt_srpt;
+  vts_tmapt_t     *vts_tmapt;
   dvd_file_t      *dvd_title;
   gint             num_chapters;
   gint             num_angles;
 
+  GstClockTime    *chapter_starts;  /* start time of chapters within title   */
+
   /* which program chain to watch (based on title and chapter number) */
   pgc_t           *cur_pgc;
   gint             pgc_id;