3 * unit test for theoraenc
5 * Copyright (C) 2006 Andy Wingo <wingo at pobox.com>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
23 #include <gst/check/gstcheck.h>
24 #include <gst/check/gstbufferstraw.h>
26 #include <theora/theora.h>
28 #ifndef GST_DISABLE_PARSE
30 #define TIMESTAMP_OFFSET G_GINT64_CONSTANT(3249870963)
33 /* I know all of these have a shift of 6 bits */
34 #define GRANULEPOS_SHIFT 6
37 #define check_buffer_is_header(buffer,is_header) \
38 fail_unless (GST_BUFFER_FLAG_IS_SET (buffer, \
39 GST_BUFFER_FLAG_IN_CAPS) == is_header, \
40 "GST_BUFFER_IN_CAPS is set to %d but expected %d", \
41 GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS), is_header)
43 #define check_buffer_timestamp(buffer,timestamp) \
44 fail_unless (GST_BUFFER_TIMESTAMP (buffer) == timestamp, \
45 "expected timestamp %" GST_TIME_FORMAT \
46 ", but got timestamp %" GST_TIME_FORMAT, \
47 GST_TIME_ARGS (timestamp), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)))
49 #define check_buffer_duration(buffer,duration) \
50 fail_unless (GST_BUFFER_DURATION (buffer) == duration, \
51 "expected duration %" GST_TIME_FORMAT \
52 ", but got duration %" GST_TIME_FORMAT, \
53 GST_TIME_ARGS (duration), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)))
55 static gboolean old_libtheora;
58 check_libtheora (void)
60 old_libtheora = (theora_version_number () <= 0x00030200);
64 check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos)
66 GstClockTime clocktime;
69 /* With old versions of libtheora, the granulepos represented the
70 * start time, not end time. Adapt for that. */
72 if (granulepos >> GRANULEPOS_SHIFT)
73 granulepos -= 1 << GRANULEPOS_SHIFT;
78 fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos,
79 "expected granulepos %" G_GUINT64_FORMAT
80 ", but got granulepos %" G_GUINT64_FORMAT,
81 granulepos, GST_BUFFER_OFFSET_END (buffer));
83 /* contrary to what we record as TIMESTAMP, we can use OFFSET to check
84 * the granulepos correctly here */
85 framecount = GST_BUFFER_OFFSET_END (buffer);
86 framecount = granulepos >> GRANULEPOS_SHIFT;
87 framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1);
88 clocktime = gst_util_uint64_scale (framecount, GST_SECOND, FRAMERATE);
90 fail_unless (clocktime == GST_BUFFER_OFFSET (buffer),
91 "expected OFFSET set to clocktime %" GST_TIME_FORMAT
92 ", but got %" GST_TIME_FORMAT,
93 GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)));
96 /* this check is here to check that the granulepos we derive from the
97 timestamp is about correct. This is "about correct" because you can't
98 precisely go from timestamp to granulepos due to the downward-rounding
99 characteristics of gst_util_uint64_scale, so you check if granulepos is
100 equal to the number, or the number plus one. */
101 /* should be from_endtime, but theora's granulepos mapping is "special" */
103 check_buffer_granulepos_from_starttime (GstBuffer * buffer,
104 GstClockTime starttime)
106 gint64 granulepos, expected, framecount;
108 granulepos = GST_BUFFER_OFFSET_END (buffer);
109 /* Now convert to 'granulepos for start time', depending on libtheora
111 if (!old_libtheora) {
112 if (granulepos & ((1 << GRANULEPOS_SHIFT) - 1))
115 granulepos -= 1 << GRANULEPOS_SHIFT;
118 framecount = granulepos >> GRANULEPOS_SHIFT;
119 framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1);
120 expected = gst_util_uint64_scale (starttime, FRAMERATE, GST_SECOND);
122 fail_unless (framecount == expected || framecount == expected + 1,
123 "expected frame count %" G_GUINT64_FORMAT
124 " or %" G_GUINT64_FORMAT
125 ", but got frame count %" G_GUINT64_FORMAT,
126 expected, expected + 1, framecount);
129 GST_START_TEST (test_granulepos_offset)
135 GError *error = NULL;
137 pipe_str = g_strdup_printf ("videotestsrc timestamp-offset=%" G_GUINT64_FORMAT
138 " num-buffers=10 ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
139 " ! theoraenc ! fakesink name=fs0", TIMESTAMP_OFFSET);
141 bin = gst_parse_launch (pipe_str, &error);
142 fail_unless (bin != NULL, "Error parsing pipeline: %s",
143 error ? error->message : "(invalid error)");
148 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
150 fail_unless (sink != NULL, "Could not get fakesink out of bin");
151 pad = gst_element_get_static_pad (sink, "sink");
152 fail_unless (pad != NULL, "Could not get pad out of fakesink");
153 gst_object_unref (sink);
156 gst_buffer_straw_start_pipeline (bin, pad);
158 /* header packets should have timestamp == NONE, granulepos 0, IN_CAPS */
159 buffer = gst_buffer_straw_get_buffer (bin, pad);
160 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
161 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
162 check_buffer_granulepos (buffer, 0);
163 check_buffer_is_header (buffer, TRUE);
164 gst_buffer_unref (buffer);
166 buffer = gst_buffer_straw_get_buffer (bin, pad);
167 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
168 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
169 check_buffer_granulepos (buffer, 0);
170 check_buffer_is_header (buffer, TRUE);
171 gst_buffer_unref (buffer);
173 buffer = gst_buffer_straw_get_buffer (bin, pad);
174 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
175 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
176 check_buffer_granulepos (buffer, 0);
177 check_buffer_is_header (buffer, TRUE);
178 gst_buffer_unref (buffer);
181 GstClockTime next_timestamp;
182 gint64 last_granulepos;
184 /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
185 * match the timestamp of the end of the last sample in the output buffer.
186 * Note that one cannot go timestamp->granulepos->timestamp and get the
187 * same value due to loss of precision with granulepos. theoraenc does
188 * take care to timestamp correctly based on the offset of the input data
189 * however, so it does do sub-granulepos timestamping. */
190 buffer = gst_buffer_straw_get_buffer (bin, pad);
191 last_granulepos = GST_BUFFER_OFFSET_END (buffer);
192 check_buffer_timestamp (buffer, TIMESTAMP_OFFSET);
193 /* don't really have a good way of checking duration... */
194 check_buffer_granulepos_from_starttime (buffer, TIMESTAMP_OFFSET);
195 check_buffer_is_header (buffer, FALSE);
197 next_timestamp = TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer);
199 gst_buffer_unref (buffer);
201 /* check continuity with the next buffer */
202 buffer = gst_buffer_straw_get_buffer (bin, pad);
203 check_buffer_timestamp (buffer, next_timestamp);
204 check_buffer_duration (buffer,
205 gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
207 - gst_util_uint64_scale (last_granulepos, GST_SECOND, FRAMERATE));
208 check_buffer_granulepos_from_starttime (buffer, next_timestamp);
209 check_buffer_is_header (buffer, FALSE);
211 gst_buffer_unref (buffer);
214 gst_buffer_straw_stop_pipeline (bin, pad);
216 gst_object_unref (pad);
217 gst_object_unref (bin);
222 GST_START_TEST (test_continuity)
228 GError *error = NULL;
230 pipe_str = g_strdup_printf ("videotestsrc num-buffers=10"
231 " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
232 " ! theoraenc ! fakesink name=fs0");
234 bin = gst_parse_launch (pipe_str, &error);
235 fail_unless (bin != NULL, "Error parsing pipeline: %s",
236 error ? error->message : "(invalid error)");
241 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
243 fail_unless (sink != NULL, "Could not get fakesink out of bin");
244 pad = gst_element_get_static_pad (sink, "sink");
245 fail_unless (pad != NULL, "Could not get pad out of fakesink");
246 gst_object_unref (sink);
249 gst_buffer_straw_start_pipeline (bin, pad);
251 /* header packets should have timestamp == NONE, granulepos 0 */
252 buffer = gst_buffer_straw_get_buffer (bin, pad);
253 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
254 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
255 check_buffer_granulepos (buffer, 0);
256 check_buffer_is_header (buffer, TRUE);
257 gst_buffer_unref (buffer);
259 buffer = gst_buffer_straw_get_buffer (bin, pad);
260 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
261 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
262 check_buffer_granulepos (buffer, 0);
263 check_buffer_is_header (buffer, TRUE);
264 gst_buffer_unref (buffer);
266 buffer = gst_buffer_straw_get_buffer (bin, pad);
267 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
268 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
269 check_buffer_granulepos (buffer, 0);
270 check_buffer_is_header (buffer, TRUE);
271 gst_buffer_unref (buffer);
274 GstClockTime next_timestamp;
275 gint64 last_granulepos;
277 /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
278 * match the timestamp of the end of the last sample in the output buffer.
279 * Note that one cannot go timestamp->granulepos->timestamp and get the
280 * same value due to loss of precision with granulepos. theoraenc does
281 * take care to timestamp correctly based on the offset of the input data
282 * however, so it does do sub-granulepos timestamping. */
283 buffer = gst_buffer_straw_get_buffer (bin, pad);
284 last_granulepos = GST_BUFFER_OFFSET_END (buffer);
285 check_buffer_timestamp (buffer, 0);
286 /* plain division because I know the answer is exact */
287 check_buffer_duration (buffer, GST_SECOND / 10);
288 check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT);
289 check_buffer_is_header (buffer, FALSE);
291 next_timestamp = GST_BUFFER_DURATION (buffer);
293 gst_buffer_unref (buffer);
295 /* check continuity with the next buffer */
296 buffer = gst_buffer_straw_get_buffer (bin, pad);
297 check_buffer_timestamp (buffer, next_timestamp);
298 check_buffer_duration (buffer, GST_SECOND / 10);
299 check_buffer_granulepos (buffer, (1 << GRANULEPOS_SHIFT) | 1);
300 check_buffer_is_header (buffer, FALSE);
302 gst_buffer_unref (buffer);
305 gst_buffer_straw_stop_pipeline (bin, pad);
307 gst_object_unref (pad);
308 gst_object_unref (bin);
314 drop_second_data_buffer (GstPad * droppad, GstBuffer * buffer, gpointer unused)
316 return !(GST_BUFFER_OFFSET (buffer) == 1);
319 GST_START_TEST (test_discontinuity)
322 GstPad *pad, *droppad;
325 GError *error = NULL;
328 pipe_str = g_strdup_printf ("videotestsrc num-buffers=10"
329 " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
330 " ! theoraenc ! fakesink name=fs0");
332 bin = gst_parse_launch (pipe_str, &error);
333 fail_unless (bin != NULL, "Error parsing pipeline: %s",
334 error ? error->message : "(invalid error)");
337 /* the plan: same as test_continuity, but dropping a buffer and seeing if
338 theoraenc correctly notes the discontinuity */
340 /* get the pad to use to drop buffers */
342 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "theoraenc0");
344 fail_unless (sink != NULL, "Could not get theoraenc out of bin");
345 droppad = gst_element_get_static_pad (sink, "sink");
346 fail_unless (droppad != NULL, "Could not get pad out of theoraenc");
347 gst_object_unref (sink);
352 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
354 fail_unless (sink != NULL, "Could not get fakesink out of bin");
355 pad = gst_element_get_static_pad (sink, "sink");
356 fail_unless (pad != NULL, "Could not get pad out of fakesink");
357 gst_object_unref (sink);
360 drop_id = gst_pad_add_buffer_probe (droppad,
361 G_CALLBACK (drop_second_data_buffer), NULL);
362 gst_buffer_straw_start_pipeline (bin, pad);
364 /* header packets should have timestamp == NONE, granulepos 0 */
365 buffer = gst_buffer_straw_get_buffer (bin, pad);
366 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
367 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
368 check_buffer_granulepos (buffer, 0);
369 check_buffer_is_header (buffer, TRUE);
370 gst_buffer_unref (buffer);
372 buffer = gst_buffer_straw_get_buffer (bin, pad);
373 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
374 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
375 check_buffer_granulepos (buffer, 0);
376 check_buffer_is_header (buffer, TRUE);
377 gst_buffer_unref (buffer);
379 buffer = gst_buffer_straw_get_buffer (bin, pad);
380 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
381 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
382 check_buffer_granulepos (buffer, 0);
383 check_buffer_is_header (buffer, TRUE);
384 gst_buffer_unref (buffer);
387 buffer = gst_buffer_straw_get_buffer (bin, pad);
388 check_buffer_timestamp (buffer, 0);
389 /* plain division because I know the answer is exact */
390 check_buffer_duration (buffer, GST_SECOND / 10);
391 check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT);
392 check_buffer_is_header (buffer, FALSE);
393 fail_if (GST_BUFFER_IS_DISCONT (buffer), "expected continuous buffer yo");
394 gst_buffer_unref (buffer);
396 /* check discontinuity with the next buffer */
397 buffer = gst_buffer_straw_get_buffer (bin, pad);
398 check_buffer_duration (buffer, GST_SECOND / 10);
399 /* After a discont, we'll always get a keyframe, so this one should be
400 * 3<<GRANULEPOS_SHIFT */
401 check_buffer_granulepos (buffer, 3 << GRANULEPOS_SHIFT);
402 check_buffer_is_header (buffer, FALSE);
403 fail_unless (GST_BUFFER_IS_DISCONT (buffer),
404 "expected discontinuous buffer yo");
405 gst_buffer_unref (buffer);
407 /* Then the buffer after that should be continuous */
408 buffer = gst_buffer_straw_get_buffer (bin, pad);
409 fail_if (GST_BUFFER_IS_DISCONT (buffer), "expected continuous buffer yo");
410 /* plain division because I know the answer is exact */
411 check_buffer_duration (buffer, GST_SECOND / 10);
412 check_buffer_granulepos (buffer, (3 << GRANULEPOS_SHIFT) | 1);
413 check_buffer_is_header (buffer, FALSE);
414 gst_buffer_unref (buffer);
417 gst_buffer_straw_stop_pipeline (bin, pad);
418 gst_pad_remove_buffer_probe (droppad, drop_id);
420 gst_object_unref (droppad);
421 gst_object_unref (pad);
422 gst_object_unref (bin);
427 #endif /* #ifndef GST_DISABLE_PARSE */
430 theoraenc_suite (void)
432 Suite *s = suite_create ("theoraenc");
433 TCase *tc_chain = tcase_create ("general");
435 suite_add_tcase (s, tc_chain);
439 #ifndef GST_DISABLE_PARSE
440 tcase_add_test (tc_chain, test_granulepos_offset);
441 tcase_add_test (tc_chain, test_continuity);
442 tcase_add_test (tc_chain, test_discontinuity);
449 main (int argc, char **argv)
453 Suite *s = theoraenc_suite ();
454 SRunner *sr = srunner_create (s);
456 gst_check_init (&argc, &argv);
458 srunner_run_all (sr, CK_NORMAL);
459 nf = srunner_ntests_failed (sr);