tizen 2.3 release
[framework/multimedia/gst-plugins-base0.10.git] / tests / check / pipelines / theoraenc.c
1 /* GStreamer
2  *
3  * unit test for theoraenc
4  *
5  * Copyright (C) 2006 Andy Wingo <wingo at pobox.com>
6  *
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.
11  *
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.
16  *
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.
21  */
22
23 #include <gst/check/gstcheck.h>
24 #include <gst/check/gstbufferstraw.h>
25
26 #include <theora/theora.h>
27
28 #ifndef GST_DISABLE_PARSE
29
30 #define TIMESTAMP_OFFSET G_GINT64_CONSTANT(3249870963)
31 #define FRAMERATE 10
32
33 /* I know all of these have a shift of 6 bits */
34 #define GRANULEPOS_SHIFT 6
35
36
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)
42
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)))
48
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)))
54
55 static gboolean old_libtheora;
56
57 static void
58 check_libtheora (void)
59 {
60   old_libtheora = (theora_version_number () <= 0x00030200);
61 }
62
63 static void
64 check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos)
65 {
66   GstClockTime clocktime;
67   int framecount;
68
69   /* With old versions of libtheora, the granulepos represented the
70    * start time, not end time. Adapt for that. */
71   if (old_libtheora) {
72     if (granulepos >> GRANULEPOS_SHIFT)
73       granulepos -= 1 << GRANULEPOS_SHIFT;
74     else if (granulepos)
75       granulepos -= 1;
76   }
77
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));
82
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);
89
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)));
94 }
95
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" */
102 static void
103 check_buffer_granulepos_from_starttime (GstBuffer * buffer,
104     GstClockTime starttime)
105 {
106   gint64 granulepos, expected, framecount;
107
108   granulepos = GST_BUFFER_OFFSET_END (buffer);
109   /* Now convert to 'granulepos for start time', depending on libtheora 
110    * version */
111   if (!old_libtheora) {
112     if (granulepos & ((1 << GRANULEPOS_SHIFT) - 1))
113       granulepos -= 1;
114     else if (granulepos)
115       granulepos -= 1 << GRANULEPOS_SHIFT;
116   }
117
118   framecount = granulepos >> GRANULEPOS_SHIFT;
119   framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1);
120   expected = gst_util_uint64_scale (starttime, FRAMERATE, GST_SECOND);
121
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);
127 }
128
129 GST_START_TEST (test_granulepos_offset)
130 {
131   GstElement *bin;
132   GstPad *pad;
133   gchar *pipe_str;
134   GstBuffer *buffer;
135   GError *error = NULL;
136
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);
140
141   bin = gst_parse_launch (pipe_str, &error);
142   fail_unless (bin != NULL, "Error parsing pipeline: %s",
143       error ? error->message : "(invalid error)");
144   g_free (pipe_str);
145
146   /* get the pad */
147   {
148     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
149
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);
154   }
155
156   gst_buffer_straw_start_pipeline (bin, pad);
157
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);
165
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);
172
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);
179
180   {
181     GstClockTime next_timestamp;
182     gint64 last_granulepos;
183
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);
196
197     next_timestamp = TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer);
198
199     gst_buffer_unref (buffer);
200
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,
206             FRAMERATE)
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);
210
211     gst_buffer_unref (buffer);
212   }
213
214   gst_buffer_straw_stop_pipeline (bin, pad);
215
216   gst_object_unref (pad);
217   gst_object_unref (bin);
218 }
219
220 GST_END_TEST;
221
222 GST_START_TEST (test_continuity)
223 {
224   GstElement *bin;
225   GstPad *pad;
226   gchar *pipe_str;
227   GstBuffer *buffer;
228   GError *error = NULL;
229
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");
233
234   bin = gst_parse_launch (pipe_str, &error);
235   fail_unless (bin != NULL, "Error parsing pipeline: %s",
236       error ? error->message : "(invalid error)");
237   g_free (pipe_str);
238
239   /* get the pad */
240   {
241     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
242
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);
247   }
248
249   gst_buffer_straw_start_pipeline (bin, pad);
250
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);
258
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);
265
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);
272
273   {
274     GstClockTime next_timestamp;
275
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);
288
289     next_timestamp = GST_BUFFER_DURATION (buffer);
290
291     gst_buffer_unref (buffer);
292
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);
299
300     gst_buffer_unref (buffer);
301   }
302
303   gst_buffer_straw_stop_pipeline (bin, pad);
304
305   gst_object_unref (pad);
306   gst_object_unref (bin);
307 }
308
309 GST_END_TEST;
310
311 static gboolean
312 drop_second_data_buffer (GstPad * droppad, GstBuffer * buffer, gpointer unused)
313 {
314   return !(GST_BUFFER_OFFSET (buffer) == 1);
315 }
316
317 GST_START_TEST (test_discontinuity)
318 {
319   GstElement *bin;
320   GstPad *pad, *droppad;
321   gchar *pipe_str;
322   GstBuffer *buffer;
323   GError *error = NULL;
324   guint drop_id;
325
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");
329
330   bin = gst_parse_launch (pipe_str, &error);
331   fail_unless (bin != NULL, "Error parsing pipeline: %s",
332       error ? error->message : "(invalid error)");
333   g_free (pipe_str);
334
335   /* the plan: same as test_continuity, but dropping a buffer and seeing if
336      theoraenc correctly notes the discontinuity */
337
338   /* get the pad to use to drop buffers */
339   {
340     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "theoraenc0");
341
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);
346   }
347
348   /* get the pad */
349   {
350     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
351
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);
356   }
357
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);
361
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);
369
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);
376
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);
383
384   {
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);
393
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);
404
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);
413   }
414
415   gst_buffer_straw_stop_pipeline (bin, pad);
416   gst_pad_remove_buffer_probe (droppad, drop_id);
417
418   gst_object_unref (droppad);
419   gst_object_unref (pad);
420   gst_object_unref (bin);
421 }
422
423 GST_END_TEST;
424
425 #endif /* #ifndef GST_DISABLE_PARSE */
426
427 static Suite *
428 theoraenc_suite (void)
429 {
430   Suite *s = suite_create ("theoraenc");
431   TCase *tc_chain = tcase_create ("general");
432
433   suite_add_tcase (s, tc_chain);
434
435   check_libtheora ();
436
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);
441 #endif
442
443   return s;
444 }
445
446 int
447 main (int argc, char **argv)
448 {
449   int nf;
450
451   Suite *s = theoraenc_suite ();
452   SRunner *sr = srunner_create (s);
453
454   gst_check_init (&argc, &argv);
455
456   srunner_run_all (sr, CK_NORMAL);
457   nf = srunner_ntests_failed (sr);
458   srunner_free (sr);
459
460   return nf;
461 }