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);
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
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;
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
/* 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;
}
* 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);
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;
}
{
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);
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;
--- /dev/null
+/* 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;
+}