tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.
authorAndy Wingo <wingo@pobox.com>
Mon, 30 Jan 2006 15:01:28 +0000 (15:01 +0000)
committerAndy Wingo <wingo@pobox.com>
Mon, 30 Jan 2006 15:01:28 +0000 (15:01 +0000)
Original commit message from CVS:
2006-01-30  Andy Wingo  <wingo@pobox.com>

* tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.

* ext/vorbis/vorbisenc.c (gst_vorbisenc_buffer_from_packet): Logic
updated to timestamp from the first sample, not the last.
(gst_vorbisenc_buffer_from_header_packet): New function, takes
special care of granulepos and timestamp for header packets.
(gst_vorbisenc_chain): Reflow, fix some leaks, and handle the case
when the first buffer has a nonzero timestamp.

* ext/vorbis/vorbisenc.h (GstVorbisEnc.granulepos_offset)
(GstVorbisEnc.subgranule_offset): New members. Take care of the
case when the first audio buffer we get has a nonzero timestamp.
(GstVorbisEnc.next_ts): Renamed from prev_ts, because now we
properly timestamp vorbis buffers with the time of the first
sample, not the last.

* ext/vorbis/vorbisenc.c (granulepos_to_clocktime): Renamed from
vorbis_granule_time_copy -- now it takes the granule/subgranule
offset into account.

* tests/check/pipelines/vorbisenc.c: New test for correctness of
timestamps, durations, and granulepos on buffers produced by
vorbisenc.

ChangeLog
ext/vorbis/vorbisenc.c
ext/vorbis/vorbisenc.h
tests/check/Makefile.am
tests/check/pipelines/vorbisenc.c [new file with mode: 0644]

index 7df40d0..012e5a3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2006-01-30  Andy Wingo  <wingo@pobox.com>
+
+       * tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.
+
+       * ext/vorbis/vorbisenc.c (gst_vorbisenc_buffer_from_packet): Logic
+       updated to timestamp from the first sample, not the last.
+       (gst_vorbisenc_buffer_from_header_packet): New function, takes
+       special care of granulepos and timestamp for header packets.
+       (gst_vorbisenc_chain): Reflow, fix some leaks, and handle the case
+       when the first buffer has a nonzero timestamp.
+
+       * ext/vorbis/vorbisenc.h (GstVorbisEnc.granulepos_offset)
+       (GstVorbisEnc.subgranule_offset): New members. Take care of the
+       case when the first audio buffer we get has a nonzero timestamp.
+       (GstVorbisEnc.next_ts): Renamed from prev_ts, because now we
+       properly timestamp vorbis buffers with the time of the first
+       sample, not the last.
+       
+       * ext/vorbis/vorbisenc.c (granulepos_to_clocktime): Renamed from
+       vorbis_granule_time_copy -- now it takes the granule/subgranule
+       offset into account.
+
+       * tests/check/pipelines/vorbisenc.c: New test for correctness of
+       timestamps, durations, and granulepos on buffers produced by
+       vorbisenc.
+
 2006-01-30  Jan Schmidt  <thaytan@mad.scientist.com>
 
        * gst/ffmpegcolorspace/gstffmpegcodecmap.c:
index 463cd55..b211f4e 100644 (file)
@@ -98,17 +98,14 @@ enum
 
 static GstFlowReturn gst_vorbisenc_output_buffers (GstVorbisEnc * vorbisenc);
 
-/* FIXME:
- * vorbis_granule_time was added between 1.0 and 1.0.1; it's too silly
- * to require a new version for such a simple function, but once we move
- * beyond 1.0 for other reasons we can remove this copy */
-
-static double
-vorbis_granule_time_copy (vorbis_dsp_state * v, ogg_int64_t granulepos)
+static GstClockTime
+granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos)
 {
   if (granulepos >= 0)
-    return ((double) granulepos / v->vi->rate);
-  return (-1);
+    return gst_util_uint64_scale ((guint64) granulepos
+        + vorbisenc->granulepos_offset, GST_SECOND, vorbisenc->frequency)
+        + vorbisenc->subgranule_offset;
+  return GST_CLOCK_TIME_NONE;
 }
 
 #if 0
@@ -549,7 +546,6 @@ gst_vorbisenc_init (GstVorbisEnc * vorbisenc)
   vorbisenc->quality = QUALITY_DEFAULT;
   vorbisenc->quality_set = FALSE;
   vorbisenc->last_message = NULL;
-
 }
 
 
@@ -770,7 +766,7 @@ gst_vorbisenc_setup (GstVorbisEnc * vorbisenc)
   vorbis_analysis_init (&vorbisenc->vd, &vorbisenc->vi);
   vorbis_block_init (&vorbisenc->vd, &vorbisenc->vb);
 
-  vorbisenc->prev_ts = 0;
+  vorbisenc->next_ts = 0;
 
   vorbisenc->setup = TRUE;
 
@@ -808,18 +804,40 @@ 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;
-  GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos;
-  GST_BUFFER_TIMESTAMP (outbuf) =
-      vorbis_granule_time_copy (&vorbisenc->vd,
-      packet->granulepos) * GST_SECOND;
+  GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos +
+      vorbisenc->granulepos_offset;
+  GST_BUFFER_TIMESTAMP (outbuf) = vorbisenc->next_ts;
+
+  /* need to pass in an unadjusted granulepos here */
+  vorbisenc->next_ts = granulepos_to_clocktime (vorbisenc, packet->granulepos);
+
   GST_BUFFER_DURATION (outbuf) =
-      GST_BUFFER_TIMESTAMP (outbuf) - vorbisenc->prev_ts;
-  vorbisenc->prev_ts = GST_BUFFER_TIMESTAMP (outbuf);
+      vorbisenc->next_ts - GST_BUFFER_TIMESTAMP (outbuf);
 
   GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
   return outbuf;
 }
 
+/* the same as above, but different logic for setting timestamp and granulepos
+ * */
+static GstBuffer *
+gst_vorbisenc_buffer_from_header_packet (GstVorbisEnc * vorbisenc,
+    ogg_packet * packet)
+{
+  GstBuffer *outbuf;
+
+  outbuf = gst_buffer_new_and_alloc (packet->bytes);
+  memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes);
+  GST_BUFFER_OFFSET (outbuf) = vorbisenc->bytes_out;
+  GST_BUFFER_OFFSET_END (outbuf) = 0;
+  GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
+  GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
+
+  GST_DEBUG ("created header packet buffer, %d bytes",
+      GST_BUFFER_SIZE (outbuf));
+  return outbuf;
+}
+
 /* push out the buffer and do internal bookkeeping */
 static GstFlowReturn
 gst_vorbisenc_push_buffer (GstVorbisEnc * vorbisenc, GstBuffer * buffer)
@@ -913,97 +931,117 @@ gst_vorbisenc_sink_event (GstPad * pad, GstEvent * event)
 static GstFlowReturn
 gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
 {
-  GstBuffer *buf = GST_BUFFER (buffer);
   GstVorbisEnc *vorbisenc;
   GstFlowReturn ret = GST_FLOW_OK;
+  gfloat *data;
+  gulong size;
+  gulong i, j;
+  float **vorbis_buffer;
 
   vorbisenc = GST_VORBISENC (GST_PAD_PARENT (pad));
 
-  {
-    gfloat *data;
-    gulong size;
-    gulong i, j;
-    float **buffer;
-
-    if (!vorbisenc->setup) {
-      gst_buffer_unref (buf);
-      GST_ELEMENT_ERROR (vorbisenc, CORE, NEGOTIATION, (NULL),
-          ("encoder not initialized (input is not audio?)"));
-      return GST_FLOW_UNEXPECTED;
-    }
-
-    if (!vorbisenc->header_sent) {
-      /* Vorbis streams begin with three headers; the initial header (with
-         most of the codec setup parameters) which is mandated by the Ogg
-         bitstream spec.  The second header holds any comment fields.  The
-         third header holds the bitstream codebook.  We merely need to
-         make the headers, then pass them to libvorbis one at a time;
-         libvorbis handles the additional Ogg bitstream constraints */
-      ogg_packet header;
-      ogg_packet header_comm;
-      ogg_packet header_code;
-      GstBuffer *buf1, *buf2, *buf3;
-      GstCaps *caps;
-
-      GST_DEBUG_OBJECT (vorbisenc, "creating and sending header packets");
-      gst_vorbisenc_set_metadata (vorbisenc);
-      vorbis_analysis_headerout (&vorbisenc->vd, &vorbisenc->vc, &header,
-          &header_comm, &header_code);
-
-      /* create header buffers */
-      buf1 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header);
-      buf2 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header_comm);
-      buf3 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header_code);
-
-      /* mark and put on caps */
-      caps = gst_pad_get_caps (vorbisenc->srcpad);
-      caps = gst_vorbisenc_set_header_on_caps (caps, buf1, buf2, buf3);
-
-      /* negotiate with these caps */
-      GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps);
-      gst_pad_set_caps (vorbisenc->srcpad, caps);
-
-      gst_buffer_set_caps (buf1, caps);
-      gst_buffer_set_caps (buf2, caps);
-      gst_buffer_set_caps (buf3, caps);
-
-      /* push out buffers */
-      if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf1)) != GST_FLOW_OK)
-        goto done;
-      if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf2)) != GST_FLOW_OK)
-        goto done;
-      if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf3)) != GST_FLOW_OK)
-        goto done;
-
-      vorbisenc->header_sent = TRUE;
-    }
+  if (!vorbisenc->setup)
+    goto not_setup;
+
+  if (!vorbisenc->header_sent) {
+    /* Vorbis streams begin with three headers; the initial header (with
+       most of the codec setup parameters) which is mandated by the Ogg
+       bitstream spec.  The second header holds any comment fields.  The
+       third header holds the bitstream codebook.  We merely need to
+       make the headers, then pass them to libvorbis one at a time;
+       libvorbis handles the additional Ogg bitstream constraints */
+    ogg_packet header;
+    ogg_packet header_comm;
+    ogg_packet header_code;
+    GstBuffer *buf1, *buf2, *buf3;
+    GstCaps *caps;
+
+    /* first, make sure header buffers get timestamp == 0 */
+    vorbisenc->next_ts = 0;
+    vorbisenc->granulepos_offset = 0;
+    vorbisenc->subgranule_offset = 0;
+
+    GST_DEBUG_OBJECT (vorbisenc, "creating and sending header packets");
+    gst_vorbisenc_set_metadata (vorbisenc);
+    vorbis_analysis_headerout (&vorbisenc->vd, &vorbisenc->vc, &header,
+        &header_comm, &header_code);
+
+    /* create header buffers */
+    buf1 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header);
+    buf2 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header_comm);
+    buf3 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header_code);
+
+    /* mark and put on caps */
+    caps = gst_pad_get_caps (vorbisenc->srcpad);
+    caps = gst_vorbisenc_set_header_on_caps (caps, buf1, buf2, buf3);
+
+    /* negotiate with these caps */
+    GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps);
+    gst_pad_set_caps (vorbisenc->srcpad, caps);
+
+    gst_buffer_set_caps (buf1, caps);
+    gst_buffer_set_caps (buf2, caps);
+    gst_buffer_set_caps (buf3, caps);
+
+    /* push out buffers */
+    if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf1)) != GST_FLOW_OK)
+      goto failed_header_push;
+    if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf2)) != GST_FLOW_OK)
+      goto failed_header_push;
+    if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf3)) != GST_FLOW_OK)
+      goto failed_header_push;
+
+
+    /* now adjust starting granulepos accordingly if the buffer's timestamp is
+       nonzero */
+    vorbisenc->next_ts = GST_BUFFER_TIMESTAMP (buffer);
+    vorbisenc->granulepos_offset = gst_util_uint64_scale
+        (GST_BUFFER_TIMESTAMP (buffer), vorbisenc->frequency, GST_SECOND);
+    vorbisenc->subgranule_offset = 0;
+    vorbisenc->subgranule_offset =
+        vorbisenc->next_ts - granulepos_to_clocktime (vorbisenc, 0);
+
+    vorbisenc->header_sent = TRUE;
+  }
 
-    /* data to encode */
-    data = (gfloat *) GST_BUFFER_DATA (buf);
-    size = GST_BUFFER_SIZE (buf) / (vorbisenc->channels * sizeof (float));
+  /* data to encode */
+  data = (gfloat *) GST_BUFFER_DATA (buffer);
+  size = GST_BUFFER_SIZE (buffer) / (vorbisenc->channels * sizeof (float));
 
-    /* expose the buffer to submit data */
-    buffer = vorbis_analysis_buffer (&vorbisenc->vd, size);
+  /* expose the buffer to submit data */
+  vorbis_buffer = vorbis_analysis_buffer (&vorbisenc->vd, size);
 
-    /* uninterleave samples */
-    for (i = 0; i < size; i++) {
-      for (j = 0; j < vorbisenc->channels; j++) {
-        buffer[j][i] = *data++;
-      }
+  /* deinterleave samples, write the buffer data */
+  for (i = 0; i < size; i++) {
+    for (j = 0; j < vorbisenc->channels; j++) {
+      vorbis_buffer[j][i] = *data++;
     }
+  }
 
-    /* tell the library how much we actually submitted */
-    vorbis_analysis_wrote (&vorbisenc->vd, size);
+  /* tell the library how much we actually submitted */
+  vorbis_analysis_wrote (&vorbisenc->vd, size);
 
-    vorbisenc->samples_in += size;
+  vorbisenc->samples_in += size;
 
-    gst_buffer_unref (buf);
-  }
+  gst_buffer_unref (buffer);
 
   ret = gst_vorbisenc_output_buffers (vorbisenc);
 
-done:
   return ret;
+
+  /* error cases */
+not_setup:
+  {
+    gst_buffer_unref (buffer);
+    GST_ELEMENT_ERROR (vorbisenc, CORE, NEGOTIATION, (NULL),
+        ("encoder not initialized (input is not audio?)"));
+    return GST_FLOW_UNEXPECTED;
+  }
+failed_header_push:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
 }
 
 static GstFlowReturn
index 613d43a..b8e41d2 100644 (file)
@@ -69,7 +69,9 @@ struct _GstVorbisEnc {
 
   guint64          samples_in;
   guint64          bytes_out;
-  GstClockTime     prev_ts;
+  GstClockTime     next_ts;
+  guint64          granulepos_offset;
+  gint64           subgranule_offset;
 
   GstTagList *     tags;
 
index b3b3556..6c3ab24 100644 (file)
@@ -22,7 +22,7 @@ $(CHECK_REGISTRY):
 TESTS = $(check_PROGRAMS)
 
 if USE_VORBIS
-check_vorbis = elements/vorbisdec
+check_vorbis = elements/vorbisdec pipelines/vorbisenc
 else
 check_vorbis =
 endif
diff --git a/tests/check/pipelines/vorbisenc.c b/tests/check/pipelines/vorbisenc.c
new file mode 100644 (file)
index 0000000..36ae66b
--- /dev/null
@@ -0,0 +1,356 @@
+/* GStreamer
+ *
+ * unit test for vorbisenc
+ *
+ * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gst/check/gstcheck.h>
+
+#define TIMESTAMP_OFFSET G_GUINT64_CONSTANT(3249870963)
+
+static GCond *cond = NULL;
+static GMutex *lock = NULL;
+static GstBuffer *buf = NULL;
+static gulong id;
+
+static gboolean
+buffer_probe (GstPad * pad, GstBuffer * buffer, gpointer unused)
+{
+  g_mutex_lock (lock);
+
+  while (buf != NULL)
+    g_cond_wait (cond, lock);
+
+  buf = gst_buffer_ref (buffer);
+
+  g_cond_signal (cond);
+
+  g_mutex_unlock (lock);
+
+  return TRUE;
+}
+
+static void
+start_pipeline (GstElement * bin, GstPad * pad)
+{
+  id = gst_pad_add_buffer_probe (pad, G_CALLBACK (buffer_probe), NULL);
+
+  cond = g_cond_new ();
+  lock = g_mutex_new ();
+
+  gst_element_set_state (bin, GST_STATE_PLAYING);
+
+}
+
+static GstBuffer *
+get_buffer (GstElement * bin, GstPad * pad)
+{
+  GstBuffer *ret;
+
+  g_mutex_lock (lock);
+
+  while (buf == NULL)
+    g_cond_wait (cond, lock);
+
+  ret = buf;
+  buf = NULL;
+
+  g_cond_signal (cond);
+
+  g_mutex_unlock (lock);
+
+  return ret;
+}
+
+static void
+stop_pipeline (GstElement * bin, GstPad * pad)
+{
+  g_mutex_lock (lock);
+  if (buf)
+    gst_buffer_unref (buf);
+  buf = NULL;
+  gst_pad_remove_buffer_probe (pad, (guint) id);
+  id = 0;
+  g_cond_signal (cond);
+  g_mutex_unlock (lock);
+
+  gst_element_set_state (bin, GST_STATE_NULL);
+
+  g_mutex_lock (lock);
+  if (buf)
+    gst_buffer_unref (buf);
+  buf = NULL;
+  g_mutex_unlock (lock);
+
+  g_mutex_free (lock);
+  g_cond_free (cond);
+
+  lock = NULL;
+  cond = NULL;
+}
+
+static void
+check_buffer_timestamp (GstBuffer * buffer, GstClockTime timestamp)
+{
+  fail_unless (GST_BUFFER_TIMESTAMP (buffer) == timestamp,
+      "expected timestamp %" GST_TIME_FORMAT
+      ", but got timestamp %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (timestamp), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
+}
+
+static void
+check_buffer_duration (GstBuffer * buffer, GstClockTime duration)
+{
+  fail_unless (GST_BUFFER_DURATION (buffer) == duration,
+      "expected duration %" GST_TIME_FORMAT
+      ", but got duration %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (duration), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
+}
+
+static void
+check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos)
+{
+  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));
+}
+
+/* this check is here to check that the granulepos we derive from the timestamp
+   is about correct. This is "about correct" because you can't precisely go from
+   timestamp to granulepos due to the downward-rounding characteristics of
+   gst_util_uint64_scale, so you check if granulepos is equal to the number, or
+   the number plus one. */
+static void
+check_buffer_granulepos_from_endtime (GstBuffer * buffer, GstClockTime endtime)
+{
+  GstClockTime granulepos, expected;
+
+  granulepos = GST_BUFFER_OFFSET_END (buffer);
+  expected = gst_util_uint64_scale (endtime, 44100, GST_SECOND);
+
+  fail_unless (granulepos == expected || granulepos == expected + 1,
+      "expected granulepos %" G_GUINT64_FORMAT
+      " or %" G_GUINT64_FORMAT
+      ", but got granulepos %" G_GUINT64_FORMAT,
+      expected, expected + 1, granulepos);
+}
+
+GST_START_TEST (test_granulepos_offset)
+{
+  GstElement *bin;
+  GstPad *pad;
+  gchar *pipe_str;
+  GstBuffer *buffer;
+  GError *error = NULL;
+  GstClockTime timestamp;
+
+  pipe_str = g_strdup_printf ("audiotestsrc timestamp-offset=%" G_GUINT64_FORMAT
+      " ! audio/x-raw-int,rate=44100"
+      " ! audioconvert ! vorbisenc ! fakesink", TIMESTAMP_OFFSET);
+
+  bin = gst_parse_launch (pipe_str, &error);
+  fail_unless (bin != NULL, "Error parsing pipeline: %s",
+      error ? error->message : "(invalid error)");
+  g_free (pipe_str);
+
+  /* get the pad */
+  {
+    GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fakesink0");
+
+    fail_unless (sink != NULL, "Could not get fakesink out of bin");
+    pad = gst_element_get_pad (sink, "sink");
+    fail_unless (pad != NULL, "Could not get pad out of fakesink");
+    gst_object_unref (sink);
+  }
+
+  start_pipeline (bin, pad);
+
+  /* header packets should have timestamp == NONE, granulepos 0 */
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  {
+    GstClockTime next_timestamp, 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.
+     * Note that one cannot go timestamp->granulepos->timestamp and get the same
+     * value due to loss of precision with granulepos. vorbisenc does take care
+     * to timestamp correctly based on the offset of the input data however, so
+     * it does do sub-granulepos timestamping. */
+    buffer = get_buffer (bin, pad);
+    last_granulepos = GST_BUFFER_OFFSET_END (buffer);
+    check_buffer_timestamp (buffer, TIMESTAMP_OFFSET);
+    /* don't really have a good way of checking duration... */
+    check_buffer_granulepos_from_endtime (buffer,
+        TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer));
+
+    next_timestamp = TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer);
+
+    gst_buffer_unref (buffer);
+
+    /* check continuity with the next buffer */
+    buffer = get_buffer (bin, pad);
+    check_buffer_timestamp (buffer, next_timestamp);
+    check_buffer_duration (buffer,
+        gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
+            44100)
+        - gst_util_uint64_scale (last_granulepos, GST_SECOND, 44100));
+    check_buffer_granulepos_from_endtime (buffer,
+        next_timestamp + GST_BUFFER_DURATION (buffer));
+
+    gst_buffer_unref (buffer);
+  }
+
+  stop_pipeline (bin, pad);
+
+  gst_object_unref (pad);
+  gst_object_unref (bin);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_timestamps)
+{
+  GstElement *bin;
+  GstPad *pad;
+  gchar *pipe_str;
+  GstBuffer *buffer;
+  GError *error = NULL;
+  GstClockTime timestamp;
+
+  pipe_str = g_strdup_printf ("audiotestsrc"
+      " ! audio/x-raw-int,rate=44100" " ! audioconvert ! vorbisenc ! fakesink");
+
+  bin = gst_parse_launch (pipe_str, &error);
+  fail_unless (bin != NULL, "Error parsing pipeline: %s",
+      error ? error->message : "(invalid error)");
+  g_free (pipe_str);
+
+  /* get the pad */
+  {
+    GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fakesink0");
+
+    fail_unless (sink != NULL, "Could not get fakesink out of bin");
+    pad = gst_element_get_pad (sink, "sink");
+    fail_unless (pad != NULL, "Could not get pad out of fakesink");
+    gst_object_unref (sink);
+  }
+
+  start_pipeline (bin, pad);
+
+  /* check header packets */
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  buffer = get_buffer (bin, pad);
+  check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
+  check_buffer_granulepos (buffer, 0);
+  gst_buffer_unref (buffer);
+
+  {
+    GstClockTime next_timestamp, last_granulepos;
+
+    /* first buffer has timestamp 0 */
+    buffer = get_buffer (bin, pad);
+    last_granulepos = GST_BUFFER_OFFSET_END (buffer);
+    check_buffer_timestamp (buffer, 0);
+    /* don't really have a good way of checking duration... */
+    check_buffer_granulepos_from_endtime (buffer, GST_BUFFER_DURATION (buffer));
+
+    next_timestamp = GST_BUFFER_DURATION (buffer);
+
+    gst_buffer_unref (buffer);
+
+    /* check continuity with the next buffer */
+    buffer = get_buffer (bin, pad);
+    check_buffer_timestamp (buffer, next_timestamp);
+    check_buffer_duration (buffer,
+        gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
+            44100)
+        - gst_util_uint64_scale (last_granulepos, GST_SECOND, 44100));
+    check_buffer_granulepos_from_endtime (buffer,
+        next_timestamp + GST_BUFFER_DURATION (buffer));
+
+    gst_buffer_unref (buffer);
+  }
+
+  stop_pipeline (bin, pad);
+
+  gst_object_unref (pad);
+  gst_object_unref (bin);
+}
+
+GST_END_TEST;
+
+Suite *
+vorbisenc_suite (void)
+{
+  Suite *s = suite_create ("vorbisenc");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+  tcase_add_test (tc_chain, test_granulepos_offset);
+  tcase_add_test (tc_chain, test_timestamps);
+
+  return s;
+}
+
+int
+main (int argc, char **argv)
+{
+  int nf;
+
+  Suite *s = vorbisenc_suite ();
+  SRunner *sr = srunner_create (s);
+
+  gst_check_init (&argc, &argv);
+
+  srunner_run_all (sr, CK_NORMAL);
+  nf = srunner_ntests_failed (sr);
+  srunner_free (sr);
+
+  return nf;
+}