ogg muxing of vorbis and theora now has pages ordered correctly again, even with...
authorThomas Vander Stichele <thomas@apestaart.org>
Sun, 5 Mar 2006 22:57:58 +0000 (22:57 +0000)
committerThomas Vander Stichele <thomas@apestaart.org>
Sun, 5 Mar 2006 22:57:58 +0000 (22:57 +0000)
Original commit message from CVS:
ogg muxing of vorbis and theora now has pages ordered correctly again,
even with delays.

* ext/ogg/README:
updated with some examples
* ext/theora/theoraenc.c: (granulepos_to_timestamp),
(granulepos_add), (theora_buffer_from_packet):
* ext/vorbis/vorbisenc.c: (granulepos_to_timestamp_offset),
(granulepos_to_timestamp), (gst_vorbisenc_buffer_from_packet),
(gst_vorbisenc_chain):
implement strategy from ext/ogg/README
* ext/ogg/gstoggmux.c: (gst_ogg_mux_buffer_from_page),
(gst_ogg_mux_push_buffer), (gst_ogg_mux_dequeue_page),
(gst_ogg_mux_pad_queue_page), (gst_ogg_mux_compare_pads),
(gst_ogg_mux_queue_pads), (gst_ogg_mux_collected):
Fix muxer so that oggz-validate is happy with all streams;
except for no eos mark, and the BOS page ordering
* tests/check/pipelines/theoraenc.c: (check_buffer_is_header),
(check_buffer_granulepos):
* tests/check/pipelines/vorbisenc.c: (check_buffer_granulepos):
update tests to check for OFFSET being set as requested
fixed type of granulepos, it's not a ClockTime

ChangeLog
ext/ogg/README
ext/ogg/gstoggmux.c
ext/theora/theoraenc.c
ext/vorbis/vorbisenc.c
tests/check/pipelines/theoraenc.c
tests/check/pipelines/vorbisenc.c

index 88190f46c91601afc66e29d4e112bb1ed2bb52c9..bf6f53fc2fb7976b75c8682725d8ed1919f04ded 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2006-03-05  Thomas Vander Stichele  <thomas at apestaart dot org>
+
+       * ext/ogg/README:
+         updated with some examples
+       * ext/theora/theoraenc.c: (granulepos_to_timestamp),
+       (granulepos_add), (theora_buffer_from_packet):
+       * ext/vorbis/vorbisenc.c: (granulepos_to_timestamp_offset),
+       (granulepos_to_timestamp), (gst_vorbisenc_buffer_from_packet),
+       (gst_vorbisenc_chain):
+         implement strategy from ext/ogg/README
+       * ext/ogg/gstoggmux.c: (gst_ogg_mux_buffer_from_page),
+       (gst_ogg_mux_push_buffer), (gst_ogg_mux_dequeue_page),
+       (gst_ogg_mux_pad_queue_page), (gst_ogg_mux_compare_pads),
+       (gst_ogg_mux_queue_pads), (gst_ogg_mux_collected):
+         Fix muxer so that oggz-validate is happy with all streams;
+         except for no eos mark, and the BOS page ordering
+       * tests/check/pipelines/theoraenc.c: (check_buffer_is_header),
+       (check_buffer_granulepos):
+       * tests/check/pipelines/vorbisenc.c: (check_buffer_granulepos):
+         update tests to check for OFFSET being set as requested
+         fixed type of granulepos, it's not a ClockTime
+
 2006-03-05  Julien MOUTTE  <julien@moutte.net>
 
        * sys/xvimage/xvimagesink.c: (gst_xvimagesink_xvimage_new),
index 79f71e2a770d55d1d35e0d9a09527bd4c4008453..ed783c0ee61c14cb4bd063db29f55ca40f7e0034 100644 (file)
@@ -341,4 +341,27 @@ In practice
 TODO
 ----
 - decide on a proper mechanism for communicating extra per-buffer fields
-
+- the ogg muxer sets timestamp and duration on outgoing ogg pages based on
+  timestamp/duration of incoming ogg packets.
+  Note that:
+  - since the ogg muxer *has* to output pages sorted by gp time, representing
+    end time of the page, this means that the buffer's timestamps are not
+    necessarily monotonically increasing
+  - timestamp + duration of buffers don't match up; the duration represents
+    the length of the ogg page *for that stream*.  Hence, for a normal
+    two-stream file, the sum of all durations is twice the length of the
+    muxed file.
+
+TESTING
+-------
+Proper muxing can be tested by generating test files with command lines like:
+- video and audio start from 0:
+gst-launch -v videotestsrc ! theoraenc ! oggmux audiotestsrc ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg
+
+- video starts after audio:
+gst-launch -v videotestsrc timestamp-offset=500000000 ! theoraenc ! oggmux audiotestsrc ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg
+
+- audio starts after video:
+gst-launch -v videotestsrc ! theoraenc ! oggmux audiotestsrc timestamp-offset=500000000 ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg
+
+The resulting files can be verified with oggz-validate for correctness.
index f3fced896fa54490c7035476163e531188fca7df..37d9331a9e6d631122d96c033f5c721cf7173c61 100644 (file)
@@ -73,10 +73,13 @@ typedef struct
   guint64 duration;             /* duration of current page */
   gboolean eos;
   gint64 offset;
-  GstClockTime timestamp;       /* start timestamp of last complete packet on
-                                   this page */
-  GstClockTime timestamp_end;   /* end timestamp of last complete packet on this
-                                   page == granulepos time. */
+  GstClockTime timestamp;       /* timestamp of the first packet on the next
+                                 * page to be dequeued */
+  GstClockTime timestamp_end;   /* end timestamp of last complete packet on
+                                   the next page to be dequeued */
+  GstClockTime gp_time;         /* time corresponding to the gp value of the
+                                   last complete packet on the next page to be
+                                   dequeued */
 
   GstOggPadState state;         /* state of the pad */
 
@@ -489,10 +492,6 @@ gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
   memcpy (GST_BUFFER_DATA (buffer) + page->header_len,
       page->body, page->body_len);
 
-  /* next_ts was the timestamp of the first buffer put in this page */
-  GST_BUFFER_TIMESTAMP (buffer) = mux->next_ts;
-  GST_BUFFER_OFFSET (buffer) = mux->offset;
-  mux->offset += GST_BUFFER_SIZE (buffer);
   /* Here we set granulepos as our OFFSET_END to give easy direct access to
    * this value later. Before we push it, we reset this to OFFSET + SIZE
    * (see gst_ogg_mux_push_buffer). */
@@ -500,14 +499,19 @@ gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
   if (delta)
     GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
 
+  GST_LOG_OBJECT (mux, GST_GP_FORMAT
+      " created buffer %p from ogg page", ogg_page_granulepos (page));
+
   return buffer;
 }
 
 static GstFlowReturn
 gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer)
 {
-  GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET (buffer) +
-      GST_BUFFER_SIZE (buffer);
+  /* fix up OFFSET and OFFSET_END again */
+  GST_BUFFER_OFFSET (buffer) = mux->offset;
+  mux->offset += GST_BUFFER_SIZE (buffer);
+  GST_BUFFER_OFFSET_END (buffer) = mux->offset;
 
   return gst_pad_push (mux->srcpad, buffer);
 }
@@ -584,18 +588,18 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
     if (buf) {
       /* if no oldest buffer yet, take this one */
       if (oldest == GST_CLOCK_TIME_NONE) {
-        GST_LOG_OBJECT (mux, "no oldest yet, taking from pad %"
-            GST_PTR_FORMAT " with timestamp %" GST_TIME_FORMAT,
-            pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
-        oldest = GST_BUFFER_END_TIME (buf);
+        GST_LOG_OBJECT (mux, "no oldest yet, taking buffer %p from pad %"
+            GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
+            buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
+        oldest = GST_BUFFER_OFFSET (buf);
         opad = pad;
       } else {
         /* if we have an oldest, compare with this one */
-        if (GST_BUFFER_END_TIME (buf) < oldest) {
-          GST_LOG_OBJECT (mux, "older buffer, taking from pad %"
-              GST_PTR_FORMAT " with timestamp %" GST_TIME_FORMAT,
-              pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
-          oldest = GST_BUFFER_END_TIME (buf);
+        if (GST_BUFFER_OFFSET (buf) < oldest) {
+          GST_LOG_OBJECT (mux, "older buffer %p, taking from pad %"
+              GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
+              buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
+          oldest = GST_BUFFER_OFFSET (buf);
           opad = pad;
         }
       }
@@ -607,8 +611,9 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
     g_assert (opad);
     buf = g_queue_pop_head (opad->pagebuffers);
     GST_LOG_OBJECT (opad->collect.pad,
-        GST_GP_FORMAT " pushing oldest page (end time %" GST_TIME_FORMAT ")",
-        GST_BUFFER_OFFSET_END (buf), GST_TIME_ARGS (GST_BUFFER_END_TIME (buf)));
+        GST_GP_FORMAT " pushing oldest page buffer %p (granulepos time %"
+        GST_TIME_FORMAT ")", GST_BUFFER_OFFSET_END (buf), buf,
+        GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
     *flowret = gst_ogg_mux_push_buffer (mux, buf);
     ret = TRUE;
   }
@@ -616,26 +621,41 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
   return ret;
 }
 
-/* put the given page on a per-pad queue, timestamping it correctly.
- * after that, dequeue and push as many pages as possible
- * before calling me, make sure that the the pad's timestamp matches
- * the page's granulepos */
+/* put the given ogg page on a per-pad queue, timestamping it correctly.
+ * after that, dequeue and push as many pages as possible.
+ * Caller should make sure:
+ * pad->timestamp     was set with the timestamp of the first packet put
+ *                    on the page
+ * pad->timestamp_end was set with the timestamp + duration of the last packet
+ *                    put on the page
+ * pad->gp_time       was set with the time matching the gp of the last
+ *                    packet put on the page
+ *
+ * will also reset timestamp and timestamp_end, so caller func can restart
+ * counting.
+ */
 static GstFlowReturn
 gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page,
     gboolean delta)
 {
-  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
   GstFlowReturn ret;
+  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
 
-  /* take the timestamp of the last completed packet on this page */
+  /* take the timestamp of the first packet on this page */
   GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
   GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
+  /* take the gp time of the last completed packet on this page */
+  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
 
+  /* the next page will start where the current page's end time leaves off */
   pad->timestamp = pad->timestamp_end;
 
   g_queue_push_tail (pad->pagebuffers, buffer);
-  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT " queued buffer page (time %"
-      GST_TIME_FORMAT "), %d page buffers queued", ogg_page_granulepos (page),
+  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
+      " queued buffer page %p (gp time %"
+      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
+      "), %d page buffers queued", ogg_page_granulepos (page),
+      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
       g_queue_get_length (pad->pagebuffers));
 
@@ -648,43 +668,49 @@ gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page,
 }
 
 /*
- * Given two pads, compare the buffers queued on it and return 0 if they have
- * an equal priority, 1 if the new pad is better, -1 if the old pad is better 
+ * Given two pads, compare the buffers queued on it.
+ * Returns:
+ *  0 if they have an equal priority
+ * -1 if the first is better
+ *  1 if the second is better
+ * Priority decided by: a) validity, b) older timestamp, c) smaller number
+ * of muxed pages
  */
 static gint
-gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * old, GstOggPad * new)
+gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * first,
+    GstOggPad * second)
 {
-  guint64 oldtime, newtime;
+  guint64 firsttime, secondtime;
 
-  /* if the old pad doesn't contain anything or is even NULL, return 
-   * the new pad as best candidate and vice versa */
-  if (old == NULL || old->buffer == NULL)
+  /* if the first pad doesn't contain anything or is even NULL, return
+   * the second pad as best candidate and vice versa */
+  if (first == NULL || first->buffer == NULL)
     return 1;
-  if (new == NULL || new->buffer == NULL)
+  if (second == NULL || second->buffer == NULL)
     return -1;
 
-  /* no timestamp on old buffer, it must go first */
-  oldtime = GST_BUFFER_TIMESTAMP (old->buffer);
-  if (oldtime == GST_CLOCK_TIME_NONE)
+  /* no timestamp on first buffer, it must go first */
+  firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
+  if (firsttime == GST_CLOCK_TIME_NONE)
     return -1;
 
-  /* no timestamp on new buffer, it must go first */
-  newtime = GST_BUFFER_TIMESTAMP (new->buffer);
-  if (newtime == GST_CLOCK_TIME_NONE)
+  /* no timestamp on second buffer, it must go first */
+  secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
+  if (secondtime == GST_CLOCK_TIME_NONE)
     return 1;
 
-  /* old buffer has higher timestamp, new one should go first */
-  if (newtime < oldtime)
+  /* first buffer has higher timestamp, second one should go first */
+  if (secondtime < firsttime)
     return 1;
-  /* new buffer has higher timestamp, old one should go first */
-  else if (newtime > oldtime)
+  /* second buffer has higher timestamp, first one should go first */
+  else if (secondtime > firsttime)
     return -1;
   else {
     /* buffers with equal timestamps, prefer the pad that has the
      * least number of pages muxed */
-    if (new->pageno < old->pageno)
+    if (second->pageno < first->pageno)
       return 1;
-    else if (new->pageno > old->pageno)
+    else if (second->pageno > first->pageno)
       return -1;
   }
 
@@ -712,8 +738,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
 
     walk = g_slist_next (walk);
 
-    GST_LOG_OBJECT (ogg_mux, "looking at pad %" GST_PTR_FORMAT " (oggpad %p)",
-        data->pad, pad);
+    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
 
     /* try to get a new buffer for this pad if needed and possible */
     if (pad->buffer == NULL) {
@@ -721,7 +746,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
       gboolean incaps;
 
       buf = gst_collect_pads_pop (ogg_mux->collect, data);
-      GST_LOG_OBJECT (ogg_mux, "popping buffer %" GST_PTR_FORMAT, buf);
+      GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf);
 
       /* On EOS we get a NULL buffer */
       if (buf != NULL) {
@@ -743,7 +768,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
           }
         }
       } else {
-        GST_DEBUG_OBJECT (ogg_mux, "EOS on pad");
+        GST_DEBUG_OBJECT (data->pad, "EOS on pad");
         pad->eos = TRUE;
       }
 
@@ -754,14 +779,12 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
      * pull on */
     if (pad->buffer) {
       if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
-        GST_LOG_OBJECT (ogg_mux, "best pad now %" GST_PTR_FORMAT
-            " (oggpad %p)", data->pad, pad);
+        GST_LOG_OBJECT (data->pad, "new best pad");
 
         bestpad = pad;
       }
     } else if (!pad->eos) {
-      GST_LOG_OBJECT (ogg_mux, "hungry pad %" GST_PTR_FORMAT
-          " (oggpad %p)", data->pad, pad);
+      GST_LOG_OBJECT (data->pad, "hungry pad");
       still_hungry = pad;
     }
   }
@@ -1079,7 +1102,7 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
   gboolean delta_unit;
   GstFlowReturn ret;
   gint64 granulepos = 0;
-  GstClockTime timestamp, timestamp_end;
+  GstClockTime timestamp, gp_time;
 
   GST_LOG_OBJECT (ogg_mux, "collected");
 
@@ -1098,8 +1121,9 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
     return GST_FLOW_WRONG_STATE;
   }
 
-  GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT " (oggpad %p)"
-      " pulling %" GST_PTR_FORMAT, best->collect.pad, best, ogg_mux->pulling);
+  GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT
+      ", currently pulling from %" GST_PTR_FORMAT, best->collect.pad,
+      ogg_mux->pulling);
 
   /* if we were already pulling from one pad, but the new "best" buffer is
    * from another pad, we need to check if we have reason to flush a page
@@ -1121,6 +1145,8 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
           GST_BUFFER_OFFSET_END (pad->buffer), pad->stream.packetno);
 
       while (ogg_stream_flush (&pad->stream, &page)) {
+        /* end time of this page is the timestamp of the next buffer */
+        ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
         /* Place page into the per-pad queue */
         ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
             pad->first_delta);
@@ -1137,12 +1163,13 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
   /* if we don't know which pad to pull on, use the best one */
   if (ogg_mux->pulling == NULL) {
     ogg_mux->pulling = best;
-    GST_LOG_OBJECT (ogg_mux, "pulling now %" GST_PTR_FORMAT " (oggpad %p)",
-        ogg_mux->pulling->collect.pad, ogg_mux->pulling);
+    GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from best pad");
 
-    /* remember timestamp of first buffer for this new pad */
+    /* remember timestamp and gp time of first buffer for this new pad */
     if (ogg_mux->pulling != NULL) {
       ogg_mux->next_ts = GST_BUFFER_TIMESTAMP (ogg_mux->pulling->buffer);
+      GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "updated times, next ts %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (ogg_mux->next_ts));
     } else {
       /* no pad to pull on, send EOS */
       gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
@@ -1165,8 +1192,7 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
     gint64 duration;
     gboolean force_flush;
 
-    GST_LOG_OBJECT (ogg_mux, "pulling now %" GST_PTR_FORMAT " (oggpad %p)",
-        ogg_mux->pulling->collect.pad, ogg_mux->pulling);
+    GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from pad");
 
     /* now see if we have a buffer */
     buf = pad->buffer;
@@ -1179,6 +1205,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
     delta_unit = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
     duration = GST_BUFFER_DURATION (buf);
 
+    /* if the current "next timestamp" on the pad is unset, then this is the
+     * first packet on the new page.  Update our pad's page timestamp */
+    if (ogg_mux->pulling->timestamp == GST_CLOCK_TIME_NONE) {
+      ogg_mux->pulling->timestamp = GST_BUFFER_TIMESTAMP (buf);
+      GST_LOG_OBJECT (ogg_mux->pulling->collect.pad,
+          "updated pad timestamp to %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
+    }
     /* create a packet from the buffer */
     packet.packet = GST_BUFFER_DATA (buf);
     packet.bytes = GST_BUFFER_SIZE (buf);
@@ -1206,12 +1240,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
       }
     }
 
-    /* flush the currently built page if neccesary */
+    /* flush the currently built page if necessary */
     if (force_flush) {
       GST_LOG_OBJECT (pad->collect.pad,
-          GST_GP_FORMAT " forcing flush because of keyframe",
+          GST_GP_FORMAT " forced flush of page before this packet",
           GST_BUFFER_OFFSET_END (pad->buffer));
       while (ogg_stream_flush (&pad->stream, &page)) {
+        /* end time of this page is the timestamp of the next buffer */
+        ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
         ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
             pad->first_delta);
 
@@ -1261,14 +1297,15 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
 
     ogg_stream_packetin (&pad->stream, &packet);
 
+    gp_time = GST_BUFFER_OFFSET (pad->buffer);
     granulepos = GST_BUFFER_OFFSET_END (pad->buffer);
     timestamp = GST_BUFFER_TIMESTAMP (pad->buffer);
-    timestamp_end = GST_BUFFER_END_TIME (pad->buffer);
 
     GST_LOG_OBJECT (pad->collect.pad,
-        GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", time %"
-        GST_TIME_FORMAT ") packetin'd",
-        granulepos, packet.packetno, GST_TIME_ARGS (timestamp));
+        GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", gp time %"
+        GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT " packetin'd",
+        granulepos, packet.packetno, GST_TIME_ARGS (gp_time),
+        GST_TIME_ARGS (timestamp));
     /* don't need the old buffer anymore */
     gst_buffer_unref (pad->buffer);
     /* store new readahead buffer */
@@ -1290,24 +1327,25 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
           pad->stream.pageno);
 
       if (ogg_page_granulepos (&page) == granulepos) {
-        /* the packet we streamed in finishes on the page,
+        /* the packet we streamed in finishes on the current page,
          * because the page's granulepos is the granulepos of the last
          * packet completed on that page,
          * so update the timestamp that we will give to the page */
         GST_LOG_OBJECT (pad->collect.pad,
             GST_GP_FORMAT
-            " packet finishes on new page, updating timestamp to %"
-            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (timestamp));
-        pad->timestamp = timestamp;
-        pad->timestamp_end = timestamp_end;
+            " packet finishes on current page, updating gp time to %"
+            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (gp_time));
+        pad->gp_time = gp_time;
       } else {
         GST_LOG_OBJECT (pad->collect.pad,
             GST_GP_FORMAT
-            " packet spans across new page, keeping old timestamp %"
-            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->timestamp));
+            " packet spans beyond current page, keeping old gp time %"
+            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->gp_time));
       }
 
       /* push the page */
+      /* end time of this page is the timestamp of the next buffer */
+      pad->timestamp_end = timestamp;
       ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta);
       pad->pageno++;
       /* mark next pages as delta */
@@ -1318,10 +1356,9 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
       while (ogg_stream_pageout (&pad->stream, &page) > 0) {
         if (ogg_page_granulepos (&page) == granulepos) {
           /* the page has taken up the new packet completely, which means
-           * the packet ends the page and we can update the timestamp
+           * the packet ends the page and we can update the gp time
            * before pushing out */
-          pad->timestamp = timestamp;
-          pad->timestamp_end = timestamp_end;
+          pad->gp_time = gp_time;
         }
 
         /* we have a complete page now, we can push the page
@@ -1339,15 +1376,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
       ogg_mux->pulling = NULL;
     }
 
-    /* Update the timestamp, if necessary, since any future page will have at
-     * least this timestamp.
+    /* Update the gp time, if necessary, since any future page will have at
+     * least this gp time.
      */
-    if (pad->timestamp < timestamp_end) {
-      pad->timestamp = timestamp_end;
-      pad->timestamp_end = timestamp_end;
-      GST_LOG_OBJECT (ogg_mux, "Updated timestamp of pad %" GST_PTR_FORMAT
-          " (oggpad %p) to %" GST_TIME_FORMAT, pad->collect.pad, pad,
-          GST_TIME_ARGS (timestamp_end));
+    if (pad->gp_time < gp_time) {
+      pad->gp_time = gp_time;
+      GST_LOG_OBJECT (pad->collect.pad,
+          "Updated running gp time of pad %" GST_PTR_FORMAT
+          " to %" GST_TIME_FORMAT, pad->collect.pad, GST_TIME_ARGS (gp_time));
     }
   }
 
index b7c4c1e7403fb03d594c25086f10f335fce89770..a54a02b658b6f429ad02edc808732524717fb512 100644 (file)
@@ -130,6 +130,25 @@ enum
   /* FILL ME */
 };
 
+/* this function does a straight granulepos -> timestamp conversion */
+static GstClockTime
+granulepos_to_timestamp (GstTheoraEnc * theoraenc, ogg_int64_t granulepos)
+{
+  guint64 iframe, pframe;
+  int shift = theoraenc->granule_shift;
+
+  if (granulepos < 0)
+    return GST_CLOCK_TIME_NONE;
+
+  iframe = granulepos >> shift;
+  pframe = granulepos - (iframe << shift);
+
+  /* num and den are 32 bit, so we can safely multiply with GST_SECOND */
+  return gst_util_uint64_scale ((guint64) (iframe + pframe),
+      GST_SECOND * theoraenc->info.fps_denominator,
+      theoraenc->info.fps_numerator);
+}
+
 static GstElementDetails theora_enc_details = {
   "TheoraEnc",
   "Codec/Encoder/Video",
@@ -365,7 +384,7 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps)
 static guint64
 granulepos_add (guint64 granulepos, guint64 addend, gint shift)
 {
-  GstClockTime iframe, pframe;
+  guint64 iframe, pframe;
 
   iframe = granulepos >> shift;
   pframe = granulepos - (iframe << shift);
@@ -388,10 +407,14 @@ theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet,
     goto no_buffer;
 
   memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes);
-  GST_BUFFER_OFFSET (buf) = enc->bytes_out;
+  /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its
+   * time representation */
   GST_BUFFER_OFFSET_END (buf) =
       granulepos_add (packet->granulepos, enc->granulepos_offset,
       enc->granule_shift);
+  GST_BUFFER_OFFSET (buf) = granulepos_to_timestamp (enc,
+      GST_BUFFER_OFFSET_END (buf));
+
   GST_BUFFER_TIMESTAMP (buf) = timestamp + enc->timestamp_offset;
   GST_BUFFER_DURATION (buf) = duration;
 
index 4def90866ad13391cf297503f0001bc8dd65295e..f02439203fc77ceddef811b01e83446c8502eeba 100644 (file)
@@ -93,8 +93,11 @@ enum
 
 static GstFlowReturn gst_vorbisenc_output_buffers (GstVorbisEnc * vorbisenc);
 
+/* this function takes into account the granulepos_offset and the subgranule
+ * time offset */
 static GstClockTime
-granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos)
+granulepos_to_timestamp_offset (GstVorbisEnc * vorbisenc,
+    ogg_int64_t granulepos)
 {
   if (granulepos >= 0)
     return gst_util_uint64_scale ((guint64) granulepos
@@ -103,6 +106,16 @@ granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos)
   return GST_CLOCK_TIME_NONE;
 }
 
+/* this function does a straight granulepos -> timestamp conversion */
+static GstClockTime
+granulepos_to_timestamp (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos)
+{
+  if (granulepos >= 0)
+    return gst_util_uint64_scale ((guint64) granulepos,
+        GST_SECOND, vorbisenc->frequency);
+  return GST_CLOCK_TIME_NONE;
+}
+
 #if 0
 static const GstFormat *
 gst_vorbisenc_get_formats (GstPad * pad)
@@ -807,18 +820,23 @@ gst_vorbisenc_buffer_from_packet (GstVorbisEnc * vorbisenc, ogg_packet * packet)
 
   outbuf = gst_buffer_new_and_alloc (packet->bytes);
   memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes);
-  GST_BUFFER_OFFSET (outbuf) = vorbisenc->bytes_out;
+  /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its
+   * time representation */
   GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos +
       vorbisenc->granulepos_offset;
+  GST_BUFFER_OFFSET (outbuf) = granulepos_to_timestamp (vorbisenc,
+      GST_BUFFER_OFFSET_END (outbuf));
   GST_BUFFER_TIMESTAMP (outbuf) = vorbisenc->next_ts;
 
-  /* need to pass in an unadjusted granulepos here */
-  vorbisenc->next_ts = granulepos_to_clocktime (vorbisenc, packet->granulepos);
-
+  /* update the next timestamp, taking granulepos_offset and subgranule offset
+   * into account */
+  vorbisenc->next_ts =
+      granulepos_to_timestamp_offset (vorbisenc, packet->granulepos);
   GST_BUFFER_DURATION (outbuf) =
       vorbisenc->next_ts - GST_BUFFER_TIMESTAMP (outbuf);
 
-  GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
+  GST_LOG_OBJECT (vorbisenc, "encoded buffer of %d bytes",
+      GST_BUFFER_SIZE (outbuf));
   return outbuf;
 }
 
@@ -1004,7 +1022,7 @@ gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
         (GST_BUFFER_TIMESTAMP (buffer), vorbisenc->frequency, GST_SECOND);
     vorbisenc->subgranule_offset = 0;
     vorbisenc->subgranule_offset =
-        vorbisenc->next_ts - granulepos_to_clocktime (vorbisenc, 0);
+        vorbisenc->next_ts - granulepos_to_timestamp_offset (vorbisenc, 0);
 
     vorbisenc->header_sent = TRUE;
   }
index c69eda345551bc1e9ce1665d8e5727a792231f8c..f59f0095470963d33b959d49b8989ec95720e65e 100644 (file)
@@ -154,12 +154,24 @@ check_buffer_duration (GstBuffer * buffer, GstClockTime duration)
 }
 
 static void
-check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos)
+check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos)
 {
+  GstClockTime clocktime;
+
   fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos,
       "expected granulepos %" G_GUINT64_FORMAT
       ", but got granulepos %" G_GUINT64_FORMAT,
       granulepos, GST_BUFFER_OFFSET_END (buffer));
+
+  /* contrary to what we record as TIMESTAMP, we can use OFFSET to check
+   * the granulepos correctly here */
+  clocktime = gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
+      FRAMERATE);
+
+  fail_unless (clocktime == GST_BUFFER_OFFSET (buffer),
+      "expected OFFSET set to clocktime %" GST_TIME_FORMAT
+      ", but got %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)));
 }
 
 /* this check is here to check that the granulepos we derive from the
@@ -172,7 +184,7 @@ static void
 check_buffer_granulepos_from_starttime (GstBuffer * buffer,
     GstClockTime starttime)
 {
-  GstClockTime granulepos, expected, framecount;
+  gint64 granulepos, expected, framecount;
 
   granulepos = GST_BUFFER_OFFSET_END (buffer);
   framecount = granulepos >> GRANULEPOS_SHIFT;
@@ -239,7 +251,8 @@ GST_START_TEST (test_granulepos_offset)
   gst_buffer_unref (buffer);
 
   {
-    GstClockTime next_timestamp, last_granulepos;
+    GstClockTime next_timestamp;
+    gint64 last_granulepos;
 
     /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
      * match the timestamp of the end of the last sample in the output buffer.
@@ -332,7 +345,8 @@ GST_START_TEST (test_continuity)
   gst_buffer_unref (buffer);
 
   {
-    GstClockTime next_timestamp, last_granulepos;
+    GstClockTime next_timestamp;
+    gint64 last_granulepos;
 
     /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
      * match the timestamp of the end of the last sample in the output buffer.
index 6d4e041f2e1fe8c73fe7d5595cda0bae8f00abcb..5914447140fb7ff3365616849927b3d05f57637b 100644 (file)
@@ -124,12 +124,24 @@ check_buffer_duration (GstBuffer * buffer, GstClockTime duration)
 }
 
 static void
-check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos)
+check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos)
 {
+  GstClockTime clocktime;
+
   fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos,
       "expected granulepos %" G_GUINT64_FORMAT
       ", but got granulepos %" G_GUINT64_FORMAT,
       granulepos, GST_BUFFER_OFFSET_END (buffer));
+
+  /* contrary to what we record as TIMESTAMP, we can use OFFSET to check
+   * the granulepos correctly here */
+  clocktime = gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), 44100,
+      GST_SECOND);
+
+  fail_unless (clocktime == GST_BUFFER_OFFSET (buffer),
+      "expected OFFSET set to clocktime %" GST_TIME_FORMAT
+      ", but got %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)));
 }
 
 /* this check is here to check that the granulepos we derive from the timestamp
@@ -140,7 +152,7 @@ check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos)
 static void
 check_buffer_granulepos_from_endtime (GstBuffer * buffer, GstClockTime endtime)
 {
-  GstClockTime granulepos, expected;
+  gint64 granulepos, expected;
 
   granulepos = GST_BUFFER_OFFSET_END (buffer);
   expected = gst_util_uint64_scale (endtime, 44100, GST_SECOND);
@@ -202,7 +214,8 @@ GST_START_TEST (test_granulepos_offset)
   gst_buffer_unref (buffer);
 
   {
-    GstClockTime next_timestamp, last_granulepos;
+    GstClockTime next_timestamp;
+    gint64 last_granulepos;
 
     /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
      * match the timestamp of the end of the last sample in the output buffer.
@@ -291,7 +304,8 @@ GST_START_TEST (test_timestamps)
   gst_buffer_unref (buffer);
 
   {
-    GstClockTime next_timestamp, last_granulepos;
+    GstClockTime next_timestamp;
+    gint64 last_granulepos;
 
     /* first buffer has timestamp 0 */
     buffer = get_buffer (bin, pad);