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;
276 /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
277 * match the timestamp of the end of the last sample in the output buffer.
278 * Note that one cannot go timestamp->granulepos->timestamp and get the
279 * same value due to loss of precision with granulepos. theoraenc does
280 * take care to timestamp correctly based on the offset of the input data
281 * however, so it does do sub-granulepos timestamping. */
282 buffer = gst_buffer_straw_get_buffer (bin, pad);
283 check_buffer_timestamp (buffer, 0);
284 /* plain division because I know the answer is exact */
285 check_buffer_duration (buffer, GST_SECOND / 10);
286 check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT);
287 check_buffer_is_header (buffer, FALSE);
289 next_timestamp = GST_BUFFER_DURATION (buffer);
291 gst_buffer_unref (buffer);
293 /* check continuity with the next buffer */
294 buffer = gst_buffer_straw_get_buffer (bin, pad);
295 check_buffer_timestamp (buffer, next_timestamp);
296 check_buffer_duration (buffer, GST_SECOND / 10);
297 check_buffer_granulepos (buffer, (1 << GRANULEPOS_SHIFT) | 1);
298 check_buffer_is_header (buffer, FALSE);
300 gst_buffer_unref (buffer);
303 gst_buffer_straw_stop_pipeline (bin, pad);
305 gst_object_unref (pad);
306 gst_object_unref (bin);
312 drop_second_data_buffer (GstPad * droppad, GstBuffer * buffer, gpointer unused)
314 return !(GST_BUFFER_OFFSET (buffer) == 1);
317 GST_START_TEST (test_discontinuity)
320 GstPad *pad, *droppad;
323 GError *error = NULL;
326 pipe_str = g_strdup_printf ("videotestsrc num-buffers=10"
327 " ! video/x-raw-yuv,format=(fourcc)I420,framerate=10/1"
328 " ! theoraenc ! fakesink name=fs0");
330 bin = gst_parse_launch (pipe_str, &error);
331 fail_unless (bin != NULL, "Error parsing pipeline: %s",
332 error ? error->message : "(invalid error)");
335 /* the plan: same as test_continuity, but dropping a buffer and seeing if
336 theoraenc correctly notes the discontinuity */
338 /* get the pad to use to drop buffers */
340 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "theoraenc0");
342 fail_unless (sink != NULL, "Could not get theoraenc out of bin");
343 droppad = gst_element_get_static_pad (sink, "sink");
344 fail_unless (droppad != NULL, "Could not get pad out of theoraenc");
345 gst_object_unref (sink);
350 GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
352 fail_unless (sink != NULL, "Could not get fakesink out of bin");
353 pad = gst_element_get_static_pad (sink, "sink");
354 fail_unless (pad != NULL, "Could not get pad out of fakesink");
355 gst_object_unref (sink);
358 drop_id = gst_pad_add_buffer_probe (droppad,
359 G_CALLBACK (drop_second_data_buffer), NULL);
360 gst_buffer_straw_start_pipeline (bin, pad);
362 /* header packets should have timestamp == NONE, granulepos 0 */
363 buffer = gst_buffer_straw_get_buffer (bin, pad);
364 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
365 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
366 check_buffer_granulepos (buffer, 0);
367 check_buffer_is_header (buffer, TRUE);
368 gst_buffer_unref (buffer);
370 buffer = gst_buffer_straw_get_buffer (bin, pad);
371 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
372 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
373 check_buffer_granulepos (buffer, 0);
374 check_buffer_is_header (buffer, TRUE);
375 gst_buffer_unref (buffer);
377 buffer = gst_buffer_straw_get_buffer (bin, pad);
378 check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
379 check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
380 check_buffer_granulepos (buffer, 0);
381 check_buffer_is_header (buffer, TRUE);
382 gst_buffer_unref (buffer);
385 buffer = gst_buffer_straw_get_buffer (bin, pad);
386 check_buffer_timestamp (buffer, 0);
387 /* plain division because I know the answer is exact */
388 check_buffer_duration (buffer, GST_SECOND / 10);
389 check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT);
390 check_buffer_is_header (buffer, FALSE);
391 fail_if (GST_BUFFER_IS_DISCONT (buffer), "expected continuous buffer yo");
392 gst_buffer_unref (buffer);
394 /* check discontinuity with the next buffer */
395 buffer = gst_buffer_straw_get_buffer (bin, pad);
396 check_buffer_duration (buffer, GST_SECOND / 10);
397 /* After a discont, we'll always get a keyframe, so this one should be
398 * 3<<GRANULEPOS_SHIFT */
399 check_buffer_granulepos (buffer, 3 << GRANULEPOS_SHIFT);
400 check_buffer_is_header (buffer, FALSE);
401 fail_unless (GST_BUFFER_IS_DISCONT (buffer),
402 "expected discontinuous buffer yo");
403 gst_buffer_unref (buffer);
405 /* Then the buffer after that should be continuous */
406 buffer = gst_buffer_straw_get_buffer (bin, pad);
407 fail_if (GST_BUFFER_IS_DISCONT (buffer), "expected continuous buffer yo");
408 /* plain division because I know the answer is exact */
409 check_buffer_duration (buffer, GST_SECOND / 10);
410 check_buffer_granulepos (buffer, (3 << GRANULEPOS_SHIFT) | 1);
411 check_buffer_is_header (buffer, FALSE);
412 gst_buffer_unref (buffer);
415 gst_buffer_straw_stop_pipeline (bin, pad);
416 gst_pad_remove_buffer_probe (droppad, drop_id);
418 gst_object_unref (droppad);
419 gst_object_unref (pad);
420 gst_object_unref (bin);
425 #endif /* #ifndef GST_DISABLE_PARSE */
428 theoraenc_suite (void)
430 Suite *s = suite_create ("theoraenc");
431 TCase *tc_chain = tcase_create ("general");
433 suite_add_tcase (s, tc_chain);
437 #ifndef GST_DISABLE_PARSE
438 tcase_add_test (tc_chain, test_granulepos_offset);
439 tcase_add_test (tc_chain, test_continuity);
440 tcase_add_test (tc_chain, test_discontinuity);
447 main (int argc, char **argv)
451 Suite *s = theoraenc_suite ();
452 SRunner *sr = srunner_create (s);
454 gst_check_init (&argc, &argv);
456 srunner_run_all (sr, CK_NORMAL);
457 nf = srunner_ntests_failed (sr);