ext/theora/: Same changes as were done to vorbisenc, although theoraenc was timestamp...
authorAndy Wingo <wingo@pobox.com>
Mon, 30 Jan 2006 17:01:54 +0000 (17:01 +0000)
committerAndy Wingo <wingo@pobox.com>
Mon, 30 Jan 2006 17:01:54 +0000 (17:01 +0000)
Original commit message from CVS:
2006-01-30  Andy Wingo  <wingo@pobox.com>

* ext/theora/gsttheoraenc.h:
* ext/theora/theoraenc.c: Same changes as were done to vorbisenc,
although theoraenc was timestamping correctly. Added handling of
streams that start with nonzero timestamps.

* tests/check/Makefile.am:
* tests/check/pipelines/theoraenc.c: New file, basically does same
tests as vorbisenc.

* tests/check/pipelines/vorbisenc.c: I claim these bugs.

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

index 4f3a51c977e7dea936c80dac02d98d060153f7cb..359ac2f270fd6a1df246c9c488760ea5efede166 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2006-01-30  Andy Wingo  <wingo@pobox.com>
+
+       * ext/theora/gsttheoraenc.h:
+       * ext/theora/theoraenc.c: Same changes as were done to vorbisenc,
+       although theoraenc was timestamping correctly. Added handling of
+       streams that start with nonzero timestamps.
+
+       * tests/check/Makefile.am:
+       * tests/check/pipelines/theoraenc.c: New file, basically does same
+       tests as vorbisenc.
+
+       * tests/check/pipelines/vorbisenc.c: I claim these bugs.
+
 2006-01-30  Wim Taymans  <wim@fluendo.com>
 
        * gst-libs/gst/audio/gstaudiosink.c:
index 2dd113ad5df78e0fbe44d433bc0535e4b7d3e3d6..55beb89ee45d1fdbd332bdab5c59a74d6c357d82 100644 (file)
@@ -77,11 +77,13 @@ struct _GstTheoraEnc
   gint info_width, info_height;
   gint width, height;
   gint offset_x, offset_y;
-  gdouble fps;
+  gint fps_n, fps_d;
+  GstClockTime next_ts;
 
   guint packetno;
   guint64 bytes_out;
-  guint64 initial_delay;
+  guint64 granulepos_offset;
+  guint64 timestamp_offset;
 };
 
 struct _GstTheoraEncClass
index 2edea06f7f408762245c3eedb81cbde98db89694..3f7c1219d6e9bb31bf80a5614f86ee025ab39590 100644 (file)
@@ -271,8 +271,8 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps)
   enc->info.offset_x = enc->offset_x;
   enc->info.offset_y = enc->offset_y;
 
-  enc->info.fps_numerator = fps_n;
-  enc->info.fps_denominator = fps_d;
+  enc->info.fps_numerator = enc->fps_n = fps_n;
+  enc->info.fps_denominator = enc->fps_d = fps_d;
   if (par) {
     enc->info.aspect_numerator = gst_value_get_fraction_numerator (par);
     enc->info.aspect_denominator = gst_value_get_fraction_denominator (par);
@@ -318,8 +318,8 @@ theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet,
 
   memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes);
   GST_BUFFER_OFFSET (buf) = enc->bytes_out;
-  GST_BUFFER_OFFSET_END (buf) = packet->granulepos;
-  GST_BUFFER_TIMESTAMP (buf) = timestamp;
+  GST_BUFFER_OFFSET_END (buf) = packet->granulepos + enc->granulepos_offset;
+  GST_BUFFER_TIMESTAMP (buf) = timestamp + enc->timestamp_offset;
   GST_BUFFER_DURATION (buf) = duration;
 
   /* the second most significant bit of the first data byte is cleared
@@ -417,10 +417,12 @@ theora_enc_sink_event (GstPad * pad, GstEvent * event)
     case GST_EVENT_EOS:
       /* push last packet with eos flag */
       while (theora_encode_packetout (&enc->state, 1, &op)) {
-        GstClockTime out_time =
-            theora_granule_time (&enc->state, op.granulepos) * GST_SECOND;
+        /* See comment in the chain function */
+        GstClockTime next_time =
+            theora_granule_time (&enc->state, op.granulepos + 1) * GST_SECOND;
 
-        theora_push_packet (enc, &op, out_time, GST_SECOND / enc->fps);
+        theora_push_packet (enc, &op, enc->next_ts, next_time - enc->next_ts);
+        enc->next_ts = next_time;
       }
       res = gst_pad_push_event (enc->srcpad, event);
       break;
@@ -447,6 +449,9 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
     GstCaps *caps;
     GstBuffer *buf1, *buf2, *buf3;
 
+    enc->granulepos_offset = 0;
+    enc->timestamp_offset = 0;
+
     /* Theora 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
@@ -456,7 +461,8 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
 
     /* first packet will get its own page automatically */
     theora_encode_header (&enc->state, &op);
-    ret = theora_buffer_from_packet (enc, &op, 0, 0, &buf1);
+    ret = theora_buffer_from_packet (enc, &op, GST_CLOCK_TIME_NONE,
+        GST_CLOCK_TIME_NONE, &buf1);
     if (ret != GST_FLOW_OK) {
       goto header_buffer_alloc;
     }
@@ -467,14 +473,16 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
      * portably work around it. Leaks ~50 bytes per encoder instance, so not a
      * huge problem. */
     theora_encode_comment (&enc->comment, &op);
-    ret = theora_buffer_from_packet (enc, &op, 0, 0, &buf2);
+    ret = theora_buffer_from_packet (enc, &op, GST_CLOCK_TIME_NONE,
+        GST_CLOCK_TIME_NONE, &buf2);
     if (ret != GST_FLOW_OK) {
       gst_buffer_unref (buf1);
       goto header_buffer_alloc;
     }
 
     theora_encode_tables (&enc->state, &op);
-    ret = theora_buffer_from_packet (enc, &op, 0, 0, &buf3);
+    ret = theora_buffer_from_packet (enc, &op, GST_CLOCK_TIME_NONE,
+        GST_CLOCK_TIME_NONE, &buf3);
     if (ret != GST_FLOW_OK) {
       gst_buffer_unref (buf1);
       gst_buffer_unref (buf2);
@@ -504,6 +512,12 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
     if ((ret = theora_push_buffer (enc, buf3)) != GST_FLOW_OK) {
       goto header_push;
     }
+
+    enc->granulepos_offset =
+        gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buffer), enc->fps_n,
+        GST_SECOND * enc->fps_d);
+    enc->timestamp_offset = GST_BUFFER_TIMESTAMP (buffer);
+    enc->next_ts = 0;
   }
 
   {
@@ -655,11 +669,18 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer)
 
     ret = GST_FLOW_OK;
     while (theora_encode_packetout (&enc->state, 0, &op)) {
-      GstClockTime out_time;
-
-      out_time = theora_granule_time (&enc->state, op.granulepos) * GST_SECOND;
-      if ((ret = theora_push_packet (enc, &op, out_time, GST_SECOND / enc->fps))
-          != GST_FLOW_OK)
+      /* This is where we hack around theora's broken idea of what granulepos
+         is -- normally we wouldn't need to add the 1, because granulepos
+         should be the presentation time of the last sample in the packet, but
+         theora starts with 0 instead of 1... */
+      GstClockTime next_time;
+
+      next_time =
+          theora_granule_time (&enc->state, op.granulepos + 1) * GST_SECOND;
+      ret =
+          theora_push_packet (enc, &op, enc->next_ts, next_time - enc->next_ts);
+      enc->next_ts = next_time;
+      if (ret != GST_FLOW_OK)
         goto data_push;
     }
     gst_buffer_unref (buffer);
@@ -705,7 +726,6 @@ theora_enc_change_state (GstElement * element, GstStateChange transition)
       theora_info_init (&enc->info);
       theora_comment_init (&enc->comment);
       enc->packetno = 0;
-      enc->initial_delay = 0;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       break;
index 6c3ab247fc0bf7d23db873db6a2312328451b490..89cc421ccb1a21cbdb9e01447007b8b23a7c705d 100644 (file)
@@ -27,7 +27,14 @@ else
 check_vorbis =
 endif
 
+if USE_THEORA
+check_theora = pipelines/theoraenc
+else
+check_theora =
+endif
+
 check_PROGRAMS = $(check_vorbis) \
+       $(check_theora) \
        elements/audioconvert \
        elements/audioresample \
        elements/audiotestsrc \
diff --git a/tests/check/pipelines/theoraenc.c b/tests/check/pipelines/theoraenc.c
new file mode 100644 (file)
index 0000000..c0888b6
--- /dev/null
@@ -0,0 +1,360 @@
+/* GStreamer
+ *
+ * unit test for theoraenc
+ *
+ * Copyright (C) 2006 Andy Wingo <wingo at pobox.com>
+ *
+ * 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)
+#define FRAMERATE 10
+
+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. */
+/* should be from_endtime, but theora's granulepos mapping is "special" */
+static void
+check_buffer_granulepos_from_starttime (GstBuffer * buffer,
+    GstClockTime starttime)
+{
+  GstClockTime granulepos, expected;
+
+  granulepos = GST_BUFFER_OFFSET_END (buffer);
+  expected = gst_util_uint64_scale (starttime, FRAMERATE, 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 ("videotestsrc timestamp-offset=%" G_GUINT64_FORMAT
+      " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
+      " ! theoraenc ! 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. theoraenc 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_starttime (buffer, TIMESTAMP_OFFSET);
+
+    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,
+            FRAMERATE)
+        - gst_util_uint64_scale (last_granulepos, GST_SECOND, FRAMERATE));
+    check_buffer_granulepos_from_starttime (buffer, next_timestamp);
+
+    gst_buffer_unref (buffer);
+  }
+
+  stop_pipeline (bin, pad);
+
+  gst_object_unref (pad);
+  gst_object_unref (bin);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_continuity)
+{
+  GstElement *bin;
+  GstPad *pad;
+  gchar *pipe_str;
+  GstBuffer *buffer;
+  GError *error = NULL;
+  GstClockTime timestamp;
+
+  pipe_str = g_strdup_printf ("videotestsrc"
+      " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
+      " ! theoraenc ! 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);
+
+  /* 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. theoraenc 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, 0);
+    check_buffer_duration (buffer, GST_SECOND / 10);    /* plain division because I
+                                                           know the answer is exact */
+    check_buffer_granulepos (buffer, 0);
+
+    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_SECOND / 10);
+    check_buffer_granulepos (buffer, 1);
+
+    gst_buffer_unref (buffer);
+  }
+
+  stop_pipeline (bin, pad);
+
+  gst_object_unref (pad);
+  gst_object_unref (bin);
+}
+
+GST_END_TEST;
+
+Suite *
+theoraenc_suite (void)
+{
+  Suite *s = suite_create ("theoraenc");
+  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_continuity);
+
+  return s;
+}
+
+int
+main (int argc, char **argv)
+{
+  int nf;
+
+  Suite *s = theoraenc_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;
+}
index 36ae66b956fc9805733b697cae1815cfd7aa5651..970e7f6222357b2baad4770f5c04069b574ec2ac 100644 (file)
@@ -2,7 +2,7 @@
  *
  * unit test for vorbisenc
  *
- * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
+ * Copyright (C) 2006 Andy Wingo <wingo at pobox.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public