1 /* GStreamer
2 *
3 * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include <gst/check/gstcheck.h>
22 #include <string.h>
23 #include <gst/video/video.h>
24
25 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
26 GST_PAD_SINK,
27 GST_PAD_ALWAYS,
28 GST_STATIC_CAPS_ANY);
29
30 static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("src",
31 GST_PAD_SRC,
32 GST_PAD_ALWAYS,
33 GST_STATIC_CAPS ("video/x-h264")
34 );
35
36 static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE ("src",
37 GST_PAD_SRC,
38 GST_PAD_ALWAYS,
39 GST_STATIC_CAPS ("audio/mpeg")
40 );
41
42 /* For ease of programming we use globals to keep refs for our floating
43 * src and sink pads we create; otherwise we always have to do get_pad,
44 * get_peer, and then remove references in every test function */
45 static GstPad *mysrcpad, *mysinkpad;
46
47 #define AUDIO_CAPS_STRING "audio/mpeg, " \
48 "channels = (int) 1, " \
49 "rate = (int) 8000, " \
50 "mpegversion = (int) 1, "\
51 "parsed = (boolean) true "
52 #define VIDEO_CAPS_STRING "video/x-h264, " \
53 "stream-format = (string) byte-stream, " \
54 "alignment = (string) nal, " \
55 "parsed = (boolean) true "
56
57 #define KEYFRAME_DISTANCE 10
58
59 typedef void (CheckOutputBuffersFunc) (GList * buffers);
60
61 /* setup and teardown needs some special handling for muxer */
62 static GstPad *
setup_src_pad(GstElement * element,GstStaticPadTemplate * template,const gchar * sinkname,gchar ** padname)63 setup_src_pad (GstElement * element,
64 GstStaticPadTemplate * template, const gchar * sinkname, gchar ** padname)
65 {
66 GstPad *srcpad, *sinkpad;
67
68 GST_DEBUG_OBJECT (element, "setting up sending pad");
69 /* sending pad */
70 srcpad = gst_pad_new_from_static_template (template, "src");
71 fail_if (srcpad == NULL, "Could not create a srcpad");
72
73 if (!(sinkpad = gst_element_get_static_pad (element, sinkname)))
74 sinkpad = gst_element_request_pad_simple (element, sinkname);
75 fail_if (sinkpad == NULL, "Could not get sink pad from %s",
76 GST_ELEMENT_NAME (element));
77 /* we can't test the reference count of the sinkpad here because it's either
78 * 2 or 3: 1 by us, 1 by tsmux and potentially another one by the srcpad
79 * task of tsmux if it just happens to iterate over the pads */
80 fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK,
81 "Could not link source and %s sink pads", GST_ELEMENT_NAME (element));
82
83 if (padname)
84 *padname = g_strdup (GST_PAD_NAME (sinkpad));
85
86 gst_object_unref (sinkpad); /* because we got it higher up */
87
88 return srcpad;
89 }
90
91 static void
teardown_src_pad(GstElement * element,const gchar * sinkname)92 teardown_src_pad (GstElement * element, const gchar * sinkname)
93 {
94 GstPad *srcpad, *sinkpad;
95
96 /* clean up floating src pad */
97 if (!(sinkpad = gst_element_get_static_pad (element, sinkname)))
98 sinkpad = gst_element_request_pad_simple (element, sinkname);
99 srcpad = gst_pad_get_peer (sinkpad);
100
101 gst_pad_unlink (srcpad, sinkpad);
102 GST_DEBUG ("src %p", srcpad);
103
104 gst_object_unref (sinkpad);
105 gst_object_unref (srcpad);
106 gst_object_unref (srcpad);
107
108 }
109
110 static GstElement *
setup_tsmux(GstStaticPadTemplate * srctemplate,const gchar * sinkname,gchar ** padname)111 setup_tsmux (GstStaticPadTemplate * srctemplate, const gchar * sinkname,
112 gchar ** padname)
113 {
114 GstElement *mux;
115
116 GST_DEBUG ("setup_tsmux");
117 mux = gst_check_setup_element ("mpegtsmux");
118 mysrcpad = setup_src_pad (mux, srctemplate, sinkname, padname);
119 mysinkpad = gst_check_setup_sink_pad (mux, &sink_template);
120 gst_pad_set_active (mysrcpad, TRUE);
121 gst_pad_set_active (mysinkpad, TRUE);
122
123 return mux;
124 }
125
126 static void
cleanup_tsmux(GstElement * mux,const gchar * sinkname)127 cleanup_tsmux (GstElement * mux, const gchar * sinkname)
128 {
129 GST_DEBUG ("cleanup_mux");
130 gst_element_set_state (mux, GST_STATE_NULL);
131
132 gst_pad_set_active (mysrcpad, FALSE);
133 gst_pad_set_active (mysinkpad, FALSE);
134 teardown_src_pad (mux, sinkname);
135 gst_check_teardown_sink_pad (mux);
136 gst_check_teardown_element (mux);
137 }
138
139 static void
check_tsmux_pad_given_muxer(GstElement * mux,const gchar * src_caps_string,gint pes_id,gint pmt_id,CheckOutputBuffersFunc check_func,guint n_bufs,gssize input_buf_size)140 check_tsmux_pad_given_muxer (GstElement * mux,
141 const gchar * src_caps_string, gint pes_id, gint pmt_id,
142 CheckOutputBuffersFunc check_func, guint n_bufs, gssize input_buf_size)
143 {
144 GstClockTime ts;
145 GstBuffer *inbuffer, *outbuffer;
146 GstCaps *caps;
147 gint num_buffers;
148 gint i;
149 gint pmt_pid = -1, el_pid = -1, pcr_pid = -1, packets = 0;
150 GstQuery *drain;
151
152 caps = gst_caps_from_string (src_caps_string);
153 gst_check_setup_events (mysrcpad, mux, caps, GST_FORMAT_TIME);
154 gst_caps_unref (caps);
155
156 ts = 0;
157 for (i = 0; i < n_bufs; ++i) {
158 GstFlowReturn flow;
159
160 if (input_buf_size >= 0)
161 inbuffer = gst_buffer_new_and_alloc (input_buf_size);
162 else
163 inbuffer = gst_buffer_new_and_alloc (g_random_int_range (1, 49141));
164
165 GST_BUFFER_TIMESTAMP (inbuffer) = ts;
166 ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
167
168 if (i % KEYFRAME_DISTANCE == 0 && pes_id == 0xe0) {
169 GST_TRACE ("input keyframe");
170 GST_BUFFER_FLAG_UNSET (inbuffer, GST_BUFFER_FLAG_DELTA_UNIT);
171 } else {
172 GST_TRACE ("input delta");
173 GST_BUFFER_FLAG_SET (inbuffer, GST_BUFFER_FLAG_DELTA_UNIT);
174 }
175 flow = gst_pad_push (mysrcpad, inbuffer);
176 if (flow != GST_FLOW_OK)
177 fail ("Got %s flow instead of OK", gst_flow_get_name (flow));
178 ts += 40 * GST_MSECOND;
179 }
180
181 drain = gst_query_new_drain ();
182 gst_pad_peer_query (mysrcpad, drain);
183 gst_query_unref (drain);
184
185 if (check_func)
186 check_func (buffers);
187
188 num_buffers = g_list_length (buffers);
189 /* all output might get aggregated */
190 fail_unless (num_buffers >= 1);
191
192 /* collect buffers in adapter for convenience */
193 for (i = 0; i < num_buffers; ++i) {
194 guint8 *odata;
195 gint size;
196 GstMapInfo map;
197
198 outbuffer = GST_BUFFER (buffers->data);
199 fail_if (outbuffer == NULL);
200 buffers = g_list_remove (buffers, outbuffer);
201 ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
202
203 gst_buffer_map (outbuffer, &map, GST_MAP_READ);
204 odata = map.data;
205 size = map.size;
206 fail_unless (size % 188 == 0);
207
208 for (; size; odata += 188, size -= 188) {
209 guint pid, y;
210 guint8 *data = odata;
211
212 /* need sync_byte */
213 fail_unless (*data == 0x47);
214 data++;
215
216 y = GST_READ_UINT16_BE (data);
217 pid = y & (0x1FFF);
218 data += 2;
219 GST_TRACE ("pid: %d", pid);
220
221 y = (y >> 14) & 0x1;
222 /* only check packets with payload_start_indicator == 1 */
223 if (!y) {
224 GST_TRACE ("not at start");
225 continue;
226 }
227
228 y = *data;
229 data++;
230
231 if (y & 0x20) {
232 /* adaptation field */
233 y = *data;
234 data++;
235 data += y;
236 GST_TRACE ("adaptation %d", y);
237 }
238
239 if (pid == 0) {
240 /* look for PAT */
241 /* pointer field */
242 y = *data;
243 data++;
244 data += y;
245 /* table_id */
246 y = *data;
247 data++;
248 fail_unless (y == 0x0);
249 /* skip */
250 data += 5;
251 /* section_number */
252 y = *data;
253 fail_unless (y == 0);
254 data++;
255 /* last_section_number */
256 y = *data;
257 fail_unless (y == 0);
258 data++;
259 /* program_number */
260 y = GST_READ_UINT16_BE (data);
261 fail_unless (y != 0);
262 data += 2;
263 /* program_map_PID */
264 y = GST_READ_UINT16_BE (data);
265 pmt_pid = y & 0x1FFF;
266 fail_unless (pmt_pid > 0x10 && pmt_pid != 0x1FF);
267 } else if (pid == pmt_pid) {
268 /* look for PMT */
269 /* pointer field */
270 y = *data;
271 data++;
272 data += y;
273 /* table_id */
274 y = *data;
275 data++;
276 fail_unless (y == 0x2);
277 /* skip */
278 data += 5;
279 /* section_number */
280 y = *data;
281 fail_unless (y == 0);
282 data++;
283 /* last_section_number */
284 y = *data;
285 fail_unless (y == 0);
286 data++;
287 /* PCR_PID */
288 y = GST_READ_UINT16_BE (data);
289 data += 2;
290 pcr_pid = y & 0x1FFF;
291 /* program_info_length */
292 y = GST_READ_UINT16_BE (data);
293 data += 2;
294 y = y & 0x0FFF;
295 data += y;
296 /* parsing only ES stream */
297 /* stream_type */
298 y = *data;
299 data++;
300 fail_unless (y == pmt_id);
301 /* elementary_PID */
302 y = GST_READ_UINT16_BE (data);
303 data += 2;
304 el_pid = y & 0x1FFF;
305 fail_unless (el_pid > 0x10 && el_pid != 0x1FF);
306 } else if (pid == el_pid) {
307 packets++;
308 /* expect to see a PES packet start */
309 y = GST_READ_UINT32_BE (data);
310 fail_unless (y >> 8 == 0x1);
311 /* stream_id */
312 y = y & 0xFF;
313 fail_unless ((pes_id & 0xF0) == (y & 0xF0));
314 }
315 }
316 gst_buffer_unmap (outbuffer, &map);
317 gst_buffer_unref (outbuffer);
318 outbuffer = NULL;
319 }
320
321 fail_unless (pmt_pid > 0);
322 fail_unless (el_pid > 0);
323 fail_unless (pcr_pid == el_pid);
324 fail_unless (packets > 0);
325
326 g_list_free (buffers);
327 buffers = NULL;
328 }
329
330 static void
check_tsmux_pad(GstStaticPadTemplate * srctemplate,const gchar * src_caps_string,gint pes_id,gint pmt_id,const gchar * sinkname,CheckOutputBuffersFunc check_func,guint n_bufs,gssize input_buf_size,guint alignment)331 check_tsmux_pad (GstStaticPadTemplate * srctemplate,
332 const gchar * src_caps_string, gint pes_id, gint pmt_id,
333 const gchar * sinkname, CheckOutputBuffersFunc check_func, guint n_bufs,
334 gssize input_buf_size, guint alignment)
335 {
336 gchar *padname;
337 GstElement *mux;
338
339 mux = setup_tsmux (srctemplate, sinkname, &padname);
340
341 if (alignment != 0)
342 g_object_set (mux, "alignment", alignment, NULL);
343
344 fail_unless (gst_element_set_state (mux,
345 GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
346 "could not set to playing");
347
348 check_tsmux_pad_given_muxer (mux, src_caps_string, pes_id, pmt_id,
349 check_func, n_bufs, input_buf_size);
350
351 cleanup_tsmux (mux, padname);
352 g_free (padname);
353 }
354
GST_START_TEST(test_reappearing_pad_while_playing)355 GST_START_TEST (test_reappearing_pad_while_playing)
356 {
357 gchar *padname;
358 GstElement *mux;
359 GstPad *pad;
360
361 mux = gst_check_setup_element ("mpegtsmux");
362 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
363 mysinkpad = gst_check_setup_sink_pad (mux, &sink_template);
364 gst_pad_set_active (mysrcpad, TRUE);
365 gst_pad_set_active (mysinkpad, TRUE);
366
367 fail_unless (gst_element_set_state (mux,
368 GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
369 "could not set to playing");
370
371 check_tsmux_pad_given_muxer (mux, VIDEO_CAPS_STRING, 0xE0, 0x1b, NULL, 1, 1);
372
373 pad = gst_element_get_static_pad (mux, padname);
374 gst_pad_set_active (mysrcpad, FALSE);
375 teardown_src_pad (mux, padname);
376 gst_element_release_request_pad (mux, pad);
377 gst_object_unref (pad);
378 g_free (padname);
379
380 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
381 gst_pad_set_active (mysrcpad, TRUE);
382
383 check_tsmux_pad_given_muxer (mux, VIDEO_CAPS_STRING, 0xE0, 0x1b, NULL, 1, 1);
384
385 cleanup_tsmux (mux, padname);
386 g_free (padname);
387 }
388
389 GST_END_TEST;
390
GST_START_TEST(test_reappearing_pad_while_stopped)391 GST_START_TEST (test_reappearing_pad_while_stopped)
392 {
393 gchar *padname;
394 GstElement *mux;
395 GstPad *pad;
396
397 mux = gst_check_setup_element ("mpegtsmux");
398 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
399 mysinkpad = gst_check_setup_sink_pad (mux, &sink_template);
400 gst_pad_set_active (mysrcpad, TRUE);
401 gst_pad_set_active (mysinkpad, TRUE);
402
403 fail_unless (gst_element_set_state (mux,
404 GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
405 "could not set to playing");
406
407 check_tsmux_pad_given_muxer (mux, VIDEO_CAPS_STRING, 0xE0, 0x1b, NULL, 1, 1);
408
409 gst_element_set_state (mux, GST_STATE_NULL);
410
411 pad = gst_element_get_static_pad (mux, padname);
412 gst_pad_set_active (mysrcpad, FALSE);
413 teardown_src_pad (mux, padname);
414 gst_element_release_request_pad (mux, pad);
415 gst_object_unref (pad);
416 g_free (padname);
417
418 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
419 gst_pad_set_active (mysrcpad, TRUE);
420
421 fail_unless (gst_element_set_state (mux,
422 GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
423 "could not set to playing");
424
425 check_tsmux_pad_given_muxer (mux, VIDEO_CAPS_STRING, 0xE0, 0x1b, NULL, 1, 1);
426
427 cleanup_tsmux (mux, padname);
428 g_free (padname);
429 }
430
431 GST_END_TEST;
432
GST_START_TEST(test_unused_pad)433 GST_START_TEST (test_unused_pad)
434 {
435 gchar *padname;
436 GstElement *mux;
437 GstPad *pad;
438
439 mux = gst_check_setup_element ("mpegtsmux");
440 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
441 mysinkpad = gst_check_setup_sink_pad (mux, &sink_template);
442 gst_pad_set_active (mysrcpad, TRUE);
443 gst_pad_set_active (mysinkpad, TRUE);
444
445 fail_unless (gst_element_set_state (mux,
446 GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
447 "could not set to playing");
448
449 pad = gst_element_get_static_pad (mux, padname);
450 gst_pad_set_active (mysrcpad, FALSE);
451 teardown_src_pad (mux, padname);
452 gst_element_release_request_pad (mux, pad);
453 gst_object_unref (pad);
454 g_free (padname);
455
456 mysrcpad = setup_src_pad (mux, &video_src_template, "sink_%d", &padname);
457 gst_pad_set_active (mysrcpad, TRUE);
458
459 cleanup_tsmux (mux, padname);
460 g_free (padname);
461 }
462
463 GST_END_TEST;
464
GST_START_TEST(test_video)465 GST_START_TEST (test_video)
466 {
467 check_tsmux_pad (&video_src_template, VIDEO_CAPS_STRING, 0xE0, 0x1b,
468 "sink_%d", NULL, 1, 1, 0);
469 }
470
471 GST_END_TEST;
472
473
GST_START_TEST(test_audio)474 GST_START_TEST (test_audio)
475 {
476 check_tsmux_pad (&audio_src_template, AUDIO_CAPS_STRING, 0xC0, 0x03,
477 "sink_%d", NULL, 1, 1, 0);
478 }
479
480 GST_END_TEST;
481
GST_START_TEST(test_multiple_state_change)482 GST_START_TEST (test_multiple_state_change)
483 {
484 GstElement *mux;
485 gchar *padname;
486 GstSegment segment;
487 GstCaps *caps;
488 size_t i;
489
490 /* it's just a sample of all possible permutations of all states and their
491 * transitions */
492 GstState states[] = { GST_STATE_PLAYING, GST_STATE_PAUSED, GST_STATE_PLAYING,
493 GST_STATE_READY, GST_STATE_PAUSED, GST_STATE_PLAYING, GST_STATE_NULL
494 };
495
496 size_t num_transitions_to_test = 10;
497
498 mux = setup_tsmux (&video_src_template, "sink_%d", &padname);
499 gst_segment_init (&segment, GST_FORMAT_TIME);
500
501 caps = gst_caps_from_string (VIDEO_CAPS_STRING);
502 gst_check_setup_events (mysrcpad, mux, caps, GST_FORMAT_TIME);
503 gst_caps_unref (caps);
504
505 for (i = 0; i < num_transitions_to_test; ++i) {
506 GstQuery *drain;
507 GstState next_state = states[i % G_N_ELEMENTS (states)];
508 fail_unless (gst_element_set_state (mux,
509 next_state) == GST_STATE_CHANGE_SUCCESS,
510 "could not set to %s", gst_element_state_get_name (next_state));
511
512 /* push some buffers when playing - this triggers a lot of activity */
513 if (GST_STATE_PLAYING == next_state) {
514 GstBuffer *inbuffer;
515
516 fail_unless (gst_pad_push_event (mysrcpad,
517 gst_event_new_segment (&segment)));
518
519 inbuffer = gst_buffer_new_and_alloc (1);
520 ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
521
522 GST_BUFFER_PTS (inbuffer) = 0;
523 fail_unless (GST_FLOW_OK == gst_pad_push (mysrcpad, inbuffer));
524
525 drain = gst_query_new_drain ();
526 gst_pad_peer_query (mysrcpad, drain);
527 gst_query_unref (drain);
528 }
529 }
530
531 cleanup_tsmux (mux, padname);
532 g_free (padname);
533 }
534
535 GST_END_TEST;
536
537 static void
test_align_check_output(GList * bufs)538 test_align_check_output (GList * bufs)
539 {
540 GST_LOG ("%u buffers", g_list_length (bufs));
541 while (bufs != NULL) {
542 GstBuffer *buf = bufs->data;
543 gsize size;
544
545 size = gst_buffer_get_size (buf);
546 GST_LOG ("buffer, size = %5u", (guint) size);
547 fail_unless_equals_int (size, 7 * 188);
548 bufs = bufs->next;
549 }
550 }
551
GST_START_TEST(test_align)552 GST_START_TEST (test_align)
553 {
554 check_tsmux_pad (&video_src_template, VIDEO_CAPS_STRING, 0xE0, 0x1b,
555 "sink_%d", test_align_check_output, 817, -1, 7);
556 }
557
558 GST_END_TEST;
559
560 static void
test_keyframe_propagation_check_output(GList * bufs)561 test_keyframe_propagation_check_output (GList * bufs)
562 {
563 guint keyframe_count = 0;
564
565 GST_LOG ("%u buffers", g_list_length (bufs));
566 while (bufs != NULL) {
567 GstBuffer *buf = bufs->data;
568 gboolean keyunit;
569
570 keyunit = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
571
572 if (keyunit)
573 ++keyframe_count;
574
575 GST_LOG ("buffer, keyframe=%d", keyunit);
576 bufs = bufs->next;
577 }
578 fail_unless_equals_int (keyframe_count, 50 / KEYFRAME_DISTANCE);
579 }
580
GST_START_TEST(test_keyframe_flag_propagation)581 GST_START_TEST (test_keyframe_flag_propagation)
582 {
583 check_tsmux_pad (&video_src_template, VIDEO_CAPS_STRING, 0xE0, 0x1b,
584 "sink_%d", test_keyframe_propagation_check_output, 50, -1, 0);
585 }
586
587 GST_END_TEST;
588
589 static Suite *
mpegtsmux_suite(void)590 mpegtsmux_suite (void)
591 {
592 Suite *s = suite_create ("mpegtsmux");
593 TCase *tc_chain = tcase_create ("general");
594
595 suite_add_tcase (s, tc_chain);
596
597 tcase_add_test (tc_chain, test_audio);
598 tcase_add_test (tc_chain, test_video);
599 tcase_add_test (tc_chain, test_multiple_state_change);
600 tcase_add_test (tc_chain, test_align);
601 tcase_add_test (tc_chain, test_keyframe_flag_propagation);
602 tcase_add_test (tc_chain, test_reappearing_pad_while_playing);
603 tcase_add_test (tc_chain, test_reappearing_pad_while_stopped);
604 tcase_add_test (tc_chain, test_unused_pad);
605
606 return s;
607 }
608
609 GST_CHECK_MAIN (mpegtsmux);
610