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