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., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <gst/check/gstcheck.h>
27 #include <gst/check/gstbufferstraw.h>
28 
29 #include <theora/theora.h>
30 
31 #ifndef GST_DISABLE_PARSE
32 
33 #define TIMESTAMP_OFFSET G_GINT64_CONSTANT(3249870963)
34 #define FRAMERATE 10
35 
36 /* I know all of these have a shift of 6 bits */
37 #define GRANULEPOS_SHIFT 6
38 
39 
40 #define check_buffer_is_header(buffer,is_header) \
41   fail_unless (GST_BUFFER_FLAG_IS_SET (buffer,   \
42           GST_BUFFER_FLAG_HEADER) == is_header, \
43       "GST_BUFFER_IN_CAPS is set to %d but expected %d", \
44       GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER), is_header)
45 
46 #define check_buffer_timestamp(buffer,timestamp) \
47   fail_unless (GST_BUFFER_TIMESTAMP (buffer) == timestamp, \
48       "expected timestamp %" GST_TIME_FORMAT \
49       ", but got timestamp %" GST_TIME_FORMAT, \
50       GST_TIME_ARGS (timestamp), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)))
51 
52 #define check_buffer_duration(buffer,duration) \
53   fail_unless (GST_BUFFER_DURATION (buffer) == duration, \
54       "expected duration %" GST_TIME_FORMAT \
55       ", but got duration %" GST_TIME_FORMAT, \
56       GST_TIME_ARGS (duration), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)))
57 
58 static gboolean old_libtheora;
59 
60 static void
check_libtheora(void)61 check_libtheora (void)
62 {
63   old_libtheora = (theora_version_number () <= 0x00030200);
64 }
65 
66 static void
check_buffer_granulepos(GstBuffer * buffer,gint64 granulepos)67 check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos)
68 {
69   GstClockTime clocktime;
70   int framecount;
71 
72   /* With old versions of libtheora, the granulepos represented the
73    * start time, not end time. Adapt for that. */
74   if (old_libtheora) {
75     if (granulepos >> GRANULEPOS_SHIFT)
76       granulepos -= 1 << GRANULEPOS_SHIFT;
77     else if (granulepos)
78       granulepos -= 1;
79   }
80 
81   fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos,
82       "expected granulepos %" G_GUINT64_FORMAT
83       ", but got granulepos %" G_GUINT64_FORMAT,
84       granulepos, GST_BUFFER_OFFSET_END (buffer));
85 
86   /* contrary to what we record as TIMESTAMP, we can use OFFSET to check
87    * the granulepos correctly here */
88   framecount = GST_BUFFER_OFFSET_END (buffer);
89   framecount = granulepos >> GRANULEPOS_SHIFT;
90   framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1);
91   clocktime = gst_util_uint64_scale (framecount, GST_SECOND, FRAMERATE);
92 
93   fail_unless (clocktime == GST_BUFFER_OFFSET (buffer),
94       "expected OFFSET set to clocktime %" GST_TIME_FORMAT
95       ", but got %" GST_TIME_FORMAT,
96       GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)));
97 }
98 
99 /* this check is here to check that the granulepos we derive from the
100    timestamp is about correct. This is "about correct" because you can't
101    precisely go from timestamp to granulepos due to the downward-rounding
102    characteristics of gst_util_uint64_scale, so you check if granulepos is
103    equal to the number, or the number plus one. */
104 /* should be from_endtime, but theora's granulepos mapping is "special" */
105 static void
check_buffer_granulepos_from_starttime(GstBuffer * buffer,GstClockTime starttime)106 check_buffer_granulepos_from_starttime (GstBuffer * buffer,
107     GstClockTime starttime)
108 {
109   gint64 granulepos, expected, framecount;
110 
111   granulepos = GST_BUFFER_OFFSET_END (buffer);
112   /* Now convert to 'granulepos for start time', depending on libtheora
113    * version */
114   if (!old_libtheora) {
115     if (granulepos & ((1 << GRANULEPOS_SHIFT) - 1))
116       granulepos -= 1;
117     else if (granulepos)
118       granulepos -= 1 << GRANULEPOS_SHIFT;
119   }
120 
121   framecount = granulepos >> GRANULEPOS_SHIFT;
122   framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1);
123   expected = gst_util_uint64_scale (starttime, FRAMERATE, GST_SECOND);
124 
125   fail_unless (framecount == expected || framecount == expected + 1,
126       "expected frame count %" G_GUINT64_FORMAT
127       " or %" G_GUINT64_FORMAT
128       ", but got frame count %" G_GUINT64_FORMAT,
129       expected, expected + 1, framecount);
130 }
131 
GST_START_TEST(test_granulepos_offset)132 GST_START_TEST (test_granulepos_offset)
133 {
134   GstElement *bin;
135   GstPad *pad;
136   gchar *pipe_str;
137   GstBuffer *buffer;
138   GError *error = NULL;
139 
140   pipe_str = g_strdup_printf ("videotestsrc timestamp-offset=%" G_GUINT64_FORMAT
141       " num-buffers=10 ! video/x-raw,format=(string)I420,framerate=10/1"
142       " ! theoraenc ! fakesink name=fs0", TIMESTAMP_OFFSET);
143 
144   bin = gst_parse_launch (pipe_str, &error);
145   fail_unless (bin != NULL, "Error parsing pipeline: %s",
146       error ? error->message : "(invalid error)");
147   g_free (pipe_str);
148 
149   /* get the pad */
150   {
151     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
152 
153     fail_unless (sink != NULL, "Could not get fakesink out of bin");
154     pad = gst_element_get_static_pad (sink, "sink");
155     fail_unless (pad != NULL, "Could not get pad out of fakesink");
156     gst_object_unref (sink);
157   }
158 
159   gst_buffer_straw_start_pipeline (bin, pad);
160 
161   /* header packets should have timestamp == NONE, granulepos 0, IN_CAPS */
162   buffer = gst_buffer_straw_get_buffer (bin, pad);
163   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
164   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
165   check_buffer_granulepos (buffer, 0);
166   check_buffer_is_header (buffer, TRUE);
167   gst_buffer_unref (buffer);
168 
169   buffer = gst_buffer_straw_get_buffer (bin, pad);
170   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
171   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
172   check_buffer_granulepos (buffer, 0);
173   check_buffer_is_header (buffer, TRUE);
174   gst_buffer_unref (buffer);
175 
176   buffer = gst_buffer_straw_get_buffer (bin, pad);
177   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
178   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
179   check_buffer_granulepos (buffer, 0);
180   check_buffer_is_header (buffer, TRUE);
181   gst_buffer_unref (buffer);
182 
183   {
184     GstClockTime next_timestamp;
185     gint64 last_granulepos;
186 
187     /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
188      * match the timestamp of the end of the last sample in the output buffer.
189      * Note that one cannot go timestamp->granulepos->timestamp and get the
190      * same value due to loss of precision with granulepos. theoraenc does
191      * take care to timestamp correctly based on the offset of the input data
192      * however, so it does do sub-granulepos timestamping. */
193     buffer = gst_buffer_straw_get_buffer (bin, pad);
194     last_granulepos = GST_BUFFER_OFFSET_END (buffer);
195     check_buffer_timestamp (buffer, TIMESTAMP_OFFSET);
196     /* don't really have a good way of checking duration... */
197     check_buffer_granulepos_from_starttime (buffer, TIMESTAMP_OFFSET);
198     check_buffer_is_header (buffer, FALSE);
199 
200     next_timestamp = TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer);
201 
202     gst_buffer_unref (buffer);
203 
204     /* check continuity with the next buffer */
205     buffer = gst_buffer_straw_get_buffer (bin, pad);
206     check_buffer_timestamp (buffer, next_timestamp);
207     check_buffer_duration (buffer,
208         gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
209             FRAMERATE)
210         - gst_util_uint64_scale (last_granulepos, GST_SECOND, FRAMERATE));
211     check_buffer_granulepos_from_starttime (buffer, next_timestamp);
212     check_buffer_is_header (buffer, FALSE);
213 
214     gst_buffer_unref (buffer);
215   }
216 
217   gst_buffer_straw_stop_pipeline (bin, pad);
218 
219   gst_object_unref (pad);
220   gst_object_unref (bin);
221 }
222 
223 GST_END_TEST;
224 
GST_START_TEST(test_continuity)225 GST_START_TEST (test_continuity)
226 {
227   GstElement *bin;
228   GstPad *pad;
229   gchar *pipe_str;
230   GstBuffer *buffer;
231   GError *error = NULL;
232 
233   pipe_str = g_strdup_printf ("videotestsrc num-buffers=10"
234       " ! video/x-raw,format=(string)I420,framerate=10/1"
235       " ! theoraenc ! fakesink name=fs0");
236 
237   bin = gst_parse_launch (pipe_str, &error);
238   fail_unless (bin != NULL, "Error parsing pipeline: %s",
239       error ? error->message : "(invalid error)");
240   g_free (pipe_str);
241 
242   /* get the pad */
243   {
244     GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fs0");
245 
246     fail_unless (sink != NULL, "Could not get fakesink out of bin");
247     pad = gst_element_get_static_pad (sink, "sink");
248     fail_unless (pad != NULL, "Could not get pad out of fakesink");
249     gst_object_unref (sink);
250   }
251 
252   gst_buffer_straw_start_pipeline (bin, pad);
253 
254   /* header packets should have timestamp == NONE, granulepos 0 */
255   buffer = gst_buffer_straw_get_buffer (bin, pad);
256   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
257   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
258   check_buffer_granulepos (buffer, 0);
259   check_buffer_is_header (buffer, TRUE);
260   gst_buffer_unref (buffer);
261 
262   buffer = gst_buffer_straw_get_buffer (bin, pad);
263   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
264   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
265   check_buffer_granulepos (buffer, 0);
266   check_buffer_is_header (buffer, TRUE);
267   gst_buffer_unref (buffer);
268 
269   buffer = gst_buffer_straw_get_buffer (bin, pad);
270   check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
271   check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
272   check_buffer_granulepos (buffer, 0);
273   check_buffer_is_header (buffer, TRUE);
274   gst_buffer_unref (buffer);
275 
276   {
277     GstClockTime next_timestamp;
278 
279     /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
280      * match the timestamp of the end of the last sample in the output buffer.
281      * Note that one cannot go timestamp->granulepos->timestamp and get the
282      * same value due to loss of precision with granulepos. theoraenc does
283      * take care to timestamp correctly based on the offset of the input data
284      * however, so it does do sub-granulepos timestamping. */
285     buffer = gst_buffer_straw_get_buffer (bin, pad);
286     check_buffer_timestamp (buffer, 0);
287     /* plain division because I know the answer is exact */
288     check_buffer_duration (buffer, GST_SECOND / 10);
289     check_buffer_granulepos (buffer, 1 << GRANULEPOS_SHIFT);
290     check_buffer_is_header (buffer, FALSE);
291 
292     next_timestamp = GST_BUFFER_DURATION (buffer);
293 
294     gst_buffer_unref (buffer);
295 
296     /* check continuity with the next buffer */
297     buffer = gst_buffer_straw_get_buffer (bin, pad);
298     check_buffer_timestamp (buffer, next_timestamp);
299     check_buffer_duration (buffer, GST_SECOND / 10);
300     check_buffer_granulepos (buffer, (1 << GRANULEPOS_SHIFT) | 1);
301     check_buffer_is_header (buffer, FALSE);
302 
303     gst_buffer_unref (buffer);
304   }
305 
306   gst_buffer_straw_stop_pipeline (bin, pad);
307 
308   gst_object_unref (pad);
309   gst_object_unref (bin);
310 }
311 
312 GST_END_TEST;
313 
314 #endif /* #ifndef GST_DISABLE_PARSE */
315 
316 static Suite *
theoraenc_suite(void)317 theoraenc_suite (void)
318 {
319   Suite *s = suite_create ("theoraenc");
320   TCase *tc_chain = tcase_create ("general");
321 
322   suite_add_tcase (s, tc_chain);
323 
324   check_libtheora ();
325 
326 #ifndef GST_DISABLE_PARSE
327   tcase_add_test (tc_chain, test_granulepos_offset);
328   tcase_add_test (tc_chain, test_continuity);
329 #endif
330 
331   return s;
332 }
333 
334 GST_CHECK_MAIN (theoraenc);
335