|
1 /* GStreamer |
|
2 * |
|
3 * unit tests for oggmux |
|
4 * |
|
5 * Copyright (C) 2006 James Livingston <doclivingston at gmail.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 #ifdef HAVE_CONFIG_H |
|
24 #include "config.h" |
|
25 #endif |
|
26 |
|
27 #include <string.h> |
|
28 |
|
29 #include <gst/check/gstcheck.h> |
|
30 #include <ogg/ogg.h> |
|
31 |
|
32 |
|
33 typedef enum |
|
34 { |
|
35 CODEC_UNKNOWN, |
|
36 CODEC_VORBIS, |
|
37 CODEC_THEORA, |
|
38 CODEC_SPEEX, |
|
39 } ChainCodec; |
|
40 |
|
41 typedef struct |
|
42 { |
|
43 gboolean eos; |
|
44 gulong serialno; |
|
45 gint64 last_granule; |
|
46 ChainCodec codec; |
|
47 } ChainState; |
|
48 |
|
49 static ogg_sync_state oggsync; |
|
50 static GHashTable *eos_chain_states; |
|
51 static gulong probe_id; |
|
52 |
|
53 |
|
54 static ChainCodec |
|
55 get_page_codec (ogg_page * page) |
|
56 { |
|
57 ChainCodec codec = CODEC_UNKNOWN; |
|
58 ogg_stream_state state; |
|
59 ogg_packet packet; |
|
60 |
|
61 ogg_stream_init (&state, ogg_page_serialno (page)); |
|
62 if (ogg_stream_pagein (&state, page) == 0) { |
|
63 if (ogg_stream_packetpeek (&state, &packet) > 0) { |
|
64 if (strncmp ((char *) &packet.packet[1], "vorbis", |
|
65 strlen ("vorbis")) == 0) |
|
66 codec = CODEC_VORBIS; |
|
67 else if (strncmp ((char *) &packet.packet[1], "theora", |
|
68 strlen ("theora")) == 0) |
|
69 codec = CODEC_THEORA; |
|
70 else if (strncmp ((char *) &packet.packet[0], "Speex ", |
|
71 strlen ("Speex ")) == 0) |
|
72 codec = CODEC_SPEEX; |
|
73 } |
|
74 } |
|
75 ogg_stream_clear (&state); |
|
76 |
|
77 return codec; |
|
78 } |
|
79 |
|
80 static gboolean |
|
81 check_chain_final_state (gpointer key, ChainState * state, gpointer data) |
|
82 { |
|
83 fail_unless (state->eos, "missing EOS flag on chain %u", state->serialno); |
|
84 |
|
85 /* return TRUE to empty the chain table */ |
|
86 return TRUE; |
|
87 } |
|
88 |
|
89 static void |
|
90 fail_if_audio (gpointer key, ChainState * state, gpointer data) |
|
91 { |
|
92 fail_if (state->codec == CODEC_VORBIS, |
|
93 "vorbis BOS occurred before theora BOS"); |
|
94 fail_if (state->codec == CODEC_SPEEX, "speex BOS occurred before theora BOS"); |
|
95 } |
|
96 |
|
97 static ChainState * |
|
98 validate_ogg_page (ogg_page * page) |
|
99 { |
|
100 gulong serialno; |
|
101 gint64 granule; |
|
102 ChainState *state; |
|
103 |
|
104 serialno = ogg_page_serialno (page); |
|
105 granule = ogg_page_granulepos (page); |
|
106 state = g_hash_table_lookup (eos_chain_states, GINT_TO_POINTER (serialno)); |
|
107 |
|
108 fail_if (ogg_page_packets (page) == 0 && granule != -1, |
|
109 "Must have granulepos -1 when page has no packets, has %" G_GINT64_FORMAT, |
|
110 granule); |
|
111 |
|
112 if (ogg_page_bos (page)) { |
|
113 fail_unless (state == NULL, "Extraneous BOS flag on chain %u", serialno); |
|
114 |
|
115 state = g_new0 (ChainState, 1); |
|
116 g_hash_table_insert (eos_chain_states, GINT_TO_POINTER (serialno), state); |
|
117 state->serialno = serialno; |
|
118 state->last_granule = granule; |
|
119 state->codec = get_page_codec (page); |
|
120 |
|
121 /* check for things like BOS ordering, etc */ |
|
122 switch (state->codec) { |
|
123 case CODEC_THEORA: |
|
124 /* check we have no vorbis/speex chains yet */ |
|
125 g_hash_table_foreach (eos_chain_states, (GHFunc) fail_if_audio, NULL); |
|
126 break; |
|
127 case CODEC_VORBIS: |
|
128 case CODEC_SPEEX: |
|
129 /* no checks (yet) */ |
|
130 break; |
|
131 case CODEC_UNKNOWN: |
|
132 default: |
|
133 break; |
|
134 } |
|
135 } else if (ogg_page_eos (page)) { |
|
136 fail_unless (state != NULL, "Missing BOS flag on chain %u", serialno); |
|
137 state->eos = TRUE; |
|
138 } else { |
|
139 fail_unless (state != NULL, "Missing BOS flag on chain %u", serialno); |
|
140 fail_unless (!state->eos, "Data after EOS flag on chain %u", serialno); |
|
141 } |
|
142 |
|
143 if (granule != -1) { |
|
144 fail_unless (granule >= state->last_granule, |
|
145 "Granulepos out-of-order for chain %u: old=%" G_GINT64_FORMAT ", new=" |
|
146 G_GINT64_FORMAT, serialno, state->last_granule, granule); |
|
147 state->last_granule = granule; |
|
148 } |
|
149 |
|
150 return state; |
|
151 } |
|
152 |
|
153 static void |
|
154 is_video (gpointer key, ChainState * state, gpointer data) |
|
155 { |
|
156 if (state->codec == CODEC_THEORA) |
|
157 *((gboolean *) data) = TRUE; |
|
158 } |
|
159 |
|
160 |
|
161 static gboolean |
|
162 eos_buffer_probe (GstPad * pad, GstBuffer * buffer, gpointer unused) |
|
163 { |
|
164 gint ret; |
|
165 gint size; |
|
166 guint8 *data; |
|
167 gchar *oggbuffer; |
|
168 ChainState *state = NULL; |
|
169 gboolean has_video = FALSE; |
|
170 |
|
171 size = GST_BUFFER_SIZE (buffer); |
|
172 data = GST_BUFFER_DATA (buffer); |
|
173 |
|
174 oggbuffer = ogg_sync_buffer (&oggsync, size); |
|
175 memcpy (oggbuffer, data, size); |
|
176 ogg_sync_wrote (&oggsync, size); |
|
177 |
|
178 do { |
|
179 ogg_page page; |
|
180 |
|
181 ret = ogg_sync_pageout (&oggsync, &page); |
|
182 if (ret > 0) |
|
183 state = validate_ogg_page (&page); |
|
184 } |
|
185 while (ret != 0); |
|
186 |
|
187 if (state) { |
|
188 /* Now, we can do buffer-level checks... |
|
189 * If we have video somewhere, then we should have DELTA_UNIT set on all |
|
190 * non-header (not IN_CAPS), non-video buffers |
|
191 */ |
|
192 g_hash_table_foreach (eos_chain_states, (GHFunc) is_video, &has_video); |
|
193 if (has_video && state->codec != CODEC_THEORA) { |
|
194 if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS)) |
|
195 fail_unless (GST_BUFFER_FLAG_IS_SET (buffer, |
|
196 GST_BUFFER_FLAG_DELTA_UNIT), |
|
197 "Non-video buffer doesn't have DELTA_UNIT in stream with video"); |
|
198 } |
|
199 } |
|
200 |
|
201 return TRUE; |
|
202 } |
|
203 |
|
204 static void |
|
205 start_pipeline (GstElement * bin, GstPad * pad) |
|
206 { |
|
207 GstStateChangeReturn ret; |
|
208 |
|
209 ogg_sync_init (&oggsync); |
|
210 |
|
211 eos_chain_states = |
|
212 g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); |
|
213 probe_id = |
|
214 gst_pad_add_buffer_probe (pad, G_CALLBACK (eos_buffer_probe), NULL); |
|
215 |
|
216 ret = gst_element_set_state (bin, GST_STATE_PLAYING); |
|
217 fail_if (ret == GST_STATE_CHANGE_FAILURE, "Could not start test pipeline"); |
|
218 if (ret == GST_STATE_CHANGE_ASYNC) { |
|
219 ret = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); |
|
220 fail_if (ret != GST_STATE_CHANGE_SUCCESS, "Could not start test pipeline"); |
|
221 } |
|
222 } |
|
223 |
|
224 static void |
|
225 stop_pipeline (GstElement * bin, GstPad * pad) |
|
226 { |
|
227 GstStateChangeReturn ret; |
|
228 |
|
229 ret = gst_element_set_state (bin, GST_STATE_NULL); |
|
230 fail_if (ret == GST_STATE_CHANGE_FAILURE, "Could not stop test pipeline"); |
|
231 if (ret == GST_STATE_CHANGE_ASYNC) { |
|
232 ret = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); |
|
233 fail_if (ret != GST_STATE_CHANGE_SUCCESS, "Could not stop test pipeline"); |
|
234 } |
|
235 |
|
236 gst_pad_remove_buffer_probe (pad, (guint) probe_id); |
|
237 ogg_sync_clear (&oggsync); |
|
238 |
|
239 /* check end conditions, such as EOS flags */ |
|
240 g_hash_table_foreach_remove (eos_chain_states, |
|
241 (GHRFunc) check_chain_final_state, NULL); |
|
242 } |
|
243 |
|
244 static gboolean |
|
245 eos_watch (GstBus * bus, GstMessage * message, GMainLoop * loop) |
|
246 { |
|
247 if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS) { |
|
248 g_main_loop_quit (loop); |
|
249 } |
|
250 return TRUE; |
|
251 } |
|
252 |
|
253 static void |
|
254 test_pipeline (const char *pipeline) |
|
255 { |
|
256 GstElement *bin, *sink; |
|
257 GstPad *pad, *sinkpad; |
|
258 GstBus *bus; |
|
259 GError *error = NULL; |
|
260 GMainLoop *loop; |
|
261 GstPadLinkReturn linkret; |
|
262 |
|
263 bin = gst_parse_launch (pipeline, &error); |
|
264 fail_unless (bin != NULL, "Error parsing pipeline: %s", |
|
265 error ? error->message : "(invalid error)"); |
|
266 pad = gst_bin_find_unconnected_pad (GST_BIN (bin), GST_PAD_SRC); |
|
267 fail_unless (pad != NULL, "Could not locate free src pad"); |
|
268 |
|
269 /* connect the fake sink */ |
|
270 sink = gst_element_factory_make ("fakesink", "fake_sink"); |
|
271 fail_unless (sink != NULL, "Could create fakesink"); |
|
272 fail_unless (gst_bin_add (GST_BIN (bin), sink), "Could not insert fakesink"); |
|
273 sinkpad = gst_element_get_pad (sink, "sink"); |
|
274 fail_unless (sinkpad != NULL, "Could not get fakesink src pad"); |
|
275 |
|
276 linkret = gst_pad_link (pad, sinkpad); |
|
277 fail_unless (GST_PAD_LINK_SUCCESSFUL (linkret), |
|
278 "Could not link to fake sink"); |
|
279 gst_object_unref (sinkpad); |
|
280 |
|
281 /* run until we receive EOS */ |
|
282 loop = g_main_loop_new (NULL, FALSE); |
|
283 bus = gst_element_get_bus (bin); |
|
284 gst_bus_add_watch (bus, (GstBusFunc) eos_watch, loop); |
|
285 gst_object_unref (bus); |
|
286 |
|
287 start_pipeline (bin, pad); |
|
288 g_main_loop_run (loop); |
|
289 stop_pipeline (bin, pad); |
|
290 |
|
291 /* clean up */ |
|
292 g_main_loop_unref (loop); |
|
293 gst_object_unref (pad); |
|
294 gst_object_unref (bin); |
|
295 } |
|
296 |
|
297 GST_START_TEST (test_vorbis) |
|
298 { |
|
299 test_pipeline |
|
300 ("audiotestsrc num-buffers=5 ! audioconvert ! vorbisenc ! oggmux"); |
|
301 } |
|
302 |
|
303 GST_END_TEST; |
|
304 |
|
305 GST_START_TEST (test_theora) |
|
306 { |
|
307 test_pipeline |
|
308 ("videotestsrc num-buffers=5 ! ffmpegcolorspace ! theoraenc ! oggmux"); |
|
309 } |
|
310 |
|
311 GST_END_TEST; |
|
312 |
|
313 GST_START_TEST (test_theora_vorbis) |
|
314 { |
|
315 test_pipeline |
|
316 ("videotestsrc num-buffers=10 ! ffmpegcolorspace ! theoraenc ! queue ! oggmux name=mux " |
|
317 "audiotestsrc num-buffers=2 ! audioconvert ! vorbisenc ! queue ! mux."); |
|
318 } |
|
319 |
|
320 GST_END_TEST; |
|
321 |
|
322 GST_START_TEST (test_vorbis_theora) |
|
323 { |
|
324 test_pipeline |
|
325 ("videotestsrc num-buffers=2 ! ffmpegcolorspace ! theoraenc ! queue ! oggmux name=mux " |
|
326 "audiotestsrc num-buffers=10 ! audioconvert ! vorbisenc ! queue ! mux."); |
|
327 } |
|
328 |
|
329 GST_END_TEST; |
|
330 |
|
331 GST_START_TEST (test_simple_cleanup) |
|
332 { |
|
333 GstElement *oggmux; |
|
334 |
|
335 oggmux = gst_element_factory_make ("oggmux", NULL); |
|
336 gst_object_unref (oggmux); |
|
337 } |
|
338 |
|
339 GST_END_TEST; |
|
340 |
|
341 GST_START_TEST (test_request_pad_cleanup) |
|
342 { |
|
343 GstElement *oggmux; |
|
344 GstPad *pad; |
|
345 |
|
346 oggmux = gst_element_factory_make ("oggmux", NULL); |
|
347 pad = gst_element_get_request_pad (oggmux, "sink_%d"); |
|
348 fail_unless (pad != NULL); |
|
349 gst_object_unref (pad); |
|
350 pad = gst_element_get_request_pad (oggmux, "sink_%d"); |
|
351 fail_unless (pad != NULL); |
|
352 gst_object_unref (pad); |
|
353 gst_object_unref (oggmux); |
|
354 } |
|
355 |
|
356 GST_END_TEST; |
|
357 |
|
358 static Suite * |
|
359 oggmux_suite (void) |
|
360 { |
|
361 Suite *s = suite_create ("oggmux"); |
|
362 TCase *tc_chain = tcase_create ("general"); |
|
363 |
|
364 suite_add_tcase (s, tc_chain); |
|
365 #ifdef HAVE_VORBIS |
|
366 tcase_add_test (tc_chain, test_vorbis); |
|
367 #endif |
|
368 |
|
369 #ifdef HAVE_THEORA |
|
370 tcase_add_test (tc_chain, test_theora); |
|
371 #endif |
|
372 |
|
373 #if (defined (HAVE_THEORA) && defined (HAVE_VORBIS)) |
|
374 tcase_add_test (tc_chain, test_vorbis_theora); |
|
375 tcase_add_test (tc_chain, test_theora_vorbis); |
|
376 #endif |
|
377 |
|
378 tcase_add_test (tc_chain, test_simple_cleanup); |
|
379 tcase_add_test (tc_chain, test_request_pad_cleanup); |
|
380 return s; |
|
381 } |
|
382 |
|
383 GST_CHECK_MAIN (oggmux); |