1 /* GStreamer unit test for HLS demux
2 *
3 * Copyright (c) <2015> YouView TV Ltd
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 "adaptive_demux_common.h"
23
24 #define DEMUX_ELEMENT_NAME "hlsdemux"
25
26 #define TS_PACKET_LEN 188
27
28 typedef struct _GstHlsDemuxTestInputData
29 {
30 const gchar *uri;
31 const guint8 *payload;
32 guint64 size;
33 } GstHlsDemuxTestInputData;
34
35 typedef struct _GstHlsDemuxTestCase
36 {
37 const GstHlsDemuxTestInputData *input;
38 GstStructure *state;
39 } GstHlsDemuxTestCase;
40
41 typedef struct _GstHlsDemuxTestAppendUriContext
42 {
43 GQuark field_id;
44 const gchar *uri;
45 } GstHlsDemuxTestAppendUriContext;
46
47 typedef struct _GstHlsDemuxTestSelectBitrateContext
48 {
49 GstAdaptiveDemuxTestEngine *engine;
50 GstAdaptiveDemuxTestCase *testData;
51 guint select_count;
52 gulong signal_handle;
53 } GstHlsDemuxTestSelectBitrateContext;
54
55 static GByteArray *
generate_transport_stream(guint length)56 generate_transport_stream (guint length)
57 {
58 guint pos;
59 guint cc = 0;
60 GByteArray *mpeg_ts;
61
62 fail_unless ((length % TS_PACKET_LEN) == 0);
63 mpeg_ts = g_byte_array_sized_new (length);
64 if (!mpeg_ts) {
65 return NULL;
66 }
67 memset (mpeg_ts->data, 0xFF, length);
68 for (pos = 0; pos < length; pos += TS_PACKET_LEN) {
69 mpeg_ts->data[pos] = 0x47;
70 mpeg_ts->data[pos + 1] = 0x1F;
71 mpeg_ts->data[pos + 2] = 0xFF;
72 mpeg_ts->data[pos + 3] = cc;
73 cc = (cc + 1) & 0x0F;
74 }
75 return mpeg_ts;
76 }
77
78 static GByteArray *
setup_test_variables(const gchar * funcname,GstHlsDemuxTestInputData * inputTestData,GstAdaptiveDemuxTestExpectedOutput * outputTestData,GstHlsDemuxTestCase * hlsTestCase,GstAdaptiveDemuxTestCase * engineTestData,guint segment_size)79 setup_test_variables (const gchar * funcname,
80 GstHlsDemuxTestInputData * inputTestData,
81 GstAdaptiveDemuxTestExpectedOutput * outputTestData,
82 GstHlsDemuxTestCase * hlsTestCase,
83 GstAdaptiveDemuxTestCase * engineTestData, guint segment_size)
84 {
85 GByteArray *mpeg_ts = NULL;
86
87 if (segment_size) {
88 guint itd, otd;
89
90 mpeg_ts = generate_transport_stream ((segment_size));
91 fail_unless (mpeg_ts != NULL);
92 for (itd = 0; inputTestData[itd].uri; ++itd) {
93 if (g_str_has_suffix (inputTestData[itd].uri, ".ts")) {
94 inputTestData[itd].payload = mpeg_ts->data;
95 }
96 }
97 for (otd = 0; outputTestData[otd].name; ++otd) {
98 outputTestData[otd].expected_data = mpeg_ts->data;
99 engineTestData->output_streams =
100 g_list_append (engineTestData->output_streams, &outputTestData[otd]);
101 }
102 }
103 hlsTestCase->input = inputTestData;
104 hlsTestCase->state = gst_structure_new_empty (funcname);
105 return mpeg_ts;
106 }
107
108 #define TESTCASE_INIT_BOILERPLATE(segment_size) \
109 GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; \
110 GstAdaptiveDemuxTestCallbacks engine_callbacks = { 0 }; \
111 GstAdaptiveDemuxTestCase *engineTestData; \
112 GstHlsDemuxTestCase hlsTestCase = { 0 }; \
113 GByteArray *mpeg_ts=NULL; \
114 engineTestData = gst_adaptive_demux_test_case_new(); \
115 fail_unless (engineTestData!=NULL); \
116 mpeg_ts = setup_test_variables(__FUNCTION__, inputTestData, outputTestData, \
117 &hlsTestCase, engineTestData, segment_size); \
118
119 #define TESTCASE_UNREF_BOILERPLATE do{ \
120 if(engineTestData->signal_context){ \
121 g_slice_free (GstHlsDemuxTestSelectBitrateContext, engineTestData->signal_context); \
122 } \
123 if(mpeg_ts) { g_byte_array_free (mpeg_ts, TRUE); } \
124 gst_structure_free (hlsTestCase.state); \
125 g_object_unref (engineTestData); \
126 } while(0)
127
128 static gboolean
append_request_uri(GQuark field_id,GValue * value,gpointer user_data)129 append_request_uri (GQuark field_id, GValue * value, gpointer user_data)
130 {
131 GstHlsDemuxTestAppendUriContext *context =
132 (GstHlsDemuxTestAppendUriContext *) user_data;
133 GValue uri_val = G_VALUE_INIT;
134
135 if (context->field_id == field_id) {
136 g_value_init (&uri_val, G_TYPE_STRING);
137 g_value_set_string (&uri_val, context->uri);
138 gst_value_array_append_value (value, &uri_val);
139 g_value_unset (&uri_val);
140 }
141 return TRUE;
142 }
143
144 static void
gst_hlsdemux_test_set_input_data(const GstHlsDemuxTestCase * test_case,const GstHlsDemuxTestInputData * input,GstTestHTTPSrcInput * output)145 gst_hlsdemux_test_set_input_data (const GstHlsDemuxTestCase * test_case,
146 const GstHlsDemuxTestInputData * input, GstTestHTTPSrcInput * output)
147 {
148 output->size = input->size;
149 output->context = (gpointer) input;
150 if (output->size == 0) {
151 output->size = strlen ((gchar *) input->payload);
152 }
153 fail_unless (input->uri != NULL);
154 if (g_str_has_suffix (input->uri, ".m3u8")) {
155 output->response_headers = gst_structure_new ("response-headers",
156 "Content-Type", G_TYPE_STRING, "application/vnd.apple.mpegurl", NULL);
157 } else if (g_str_has_suffix (input->uri, ".ts")) {
158 output->response_headers = gst_structure_new ("response-headers",
159 "Content-Type", G_TYPE_STRING, "video/mp2t", NULL);
160 }
161 if (gst_structure_has_field (test_case->state, "requests")) {
162 GstHlsDemuxTestAppendUriContext context =
163 { g_quark_from_string ("requests"), input->uri };
164 gst_structure_map_in_place (test_case->state, append_request_uri, &context);
165 } else {
166 GValue requests = G_VALUE_INIT;
167 GValue uri_val = G_VALUE_INIT;
168
169 g_value_init (&requests, GST_TYPE_ARRAY);
170 g_value_init (&uri_val, G_TYPE_STRING);
171 g_value_set_string (&uri_val, input->uri);
172 gst_value_array_append_value (&requests, &uri_val);
173 gst_structure_set_value (test_case->state, "requests", &requests);
174 g_value_unset (&uri_val);
175 g_value_unset (&requests);
176 }
177 }
178
179 static gboolean
gst_hlsdemux_test_src_start(GstTestHTTPSrc * src,const gchar * uri,GstTestHTTPSrcInput * input_data,gpointer user_data)180 gst_hlsdemux_test_src_start (GstTestHTTPSrc * src,
181 const gchar * uri, GstTestHTTPSrcInput * input_data, gpointer user_data)
182 {
183 const GstHlsDemuxTestCase *test_case =
184 (const GstHlsDemuxTestCase *) user_data;
185 guint fail_count = 0;
186 guint i;
187
188 GST_DEBUG ("src_start %s", uri);
189 for (i = 0; test_case->input[i].uri; ++i) {
190 if (strcmp (test_case->input[i].uri, uri) == 0) {
191 gst_hlsdemux_test_set_input_data (test_case, &test_case->input[i],
192 input_data);
193 GST_DEBUG ("open URI %s", uri);
194 return TRUE;
195 }
196 }
197 gst_structure_get_uint (test_case->state, "failure-count", &fail_count);
198 fail_count++;
199 gst_structure_set (test_case->state, "failure-count", G_TYPE_UINT,
200 fail_count, NULL);
201 return FALSE;
202 }
203
204 static GstFlowReturn
gst_hlsdemux_test_src_create(GstTestHTTPSrc * src,guint64 offset,guint length,GstBuffer ** retbuf,gpointer context,gpointer user_data)205 gst_hlsdemux_test_src_create (GstTestHTTPSrc * src,
206 guint64 offset,
207 guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data)
208 {
209 GstBuffer *buf;
210 /* const GstHlsDemuxTestCase *test_case = (const GstHlsDemuxTestCase *) user_data; */
211 GstHlsDemuxTestInputData *input = (GstHlsDemuxTestInputData *) context;
212
213 buf = gst_buffer_new_allocate (NULL, length, NULL);
214 fail_if (buf == NULL, "Not enough memory to allocate buffer");
215 fail_if (input->payload == NULL);
216 gst_buffer_fill (buf, 0, input->payload + offset, length);
217 *retbuf = buf;
218 return GST_FLOW_OK;
219 }
220
221 static GstFlowReturn
gst_hlsdemux_test_network_error_src_create(GstTestHTTPSrc * src,guint64 offset,guint length,GstBuffer ** retbuf,gpointer context,gpointer user_data)222 gst_hlsdemux_test_network_error_src_create (GstTestHTTPSrc * src,
223 guint64 offset,
224 guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data)
225 {
226 const GstHlsDemuxTestCase *test_case =
227 (const GstHlsDemuxTestCase *) user_data;
228 GstHlsDemuxTestInputData *input = (GstHlsDemuxTestInputData *) context;
229 const gchar *failure_suffix;
230 guint64 failure_position = 0;
231
232 fail_unless (test_case != NULL);
233 fail_unless (input != NULL);
234 fail_unless (input->uri != NULL);
235 failure_suffix =
236 gst_structure_get_string (test_case->state, "failure-suffix");
237 if (!failure_suffix) {
238 failure_suffix = ".ts";
239 }
240 if (!gst_structure_get_uint64 (test_case->state, "failure-position",
241 &failure_position)) {
242 failure_position = 10 * TS_PACKET_LEN;
243 }
244 GST_DEBUG ("network_error %s %s %" G_GUINT64_FORMAT " @ %" G_GUINT64_FORMAT,
245 input->uri, failure_suffix, offset, failure_position);
246 if (g_str_has_suffix (input->uri, failure_suffix)
247 && offset >= failure_position) {
248 GST_DEBUG ("return error");
249 GST_ELEMENT_ERROR (src, RESOURCE, READ,
250 (("A network error occurred, or the server closed the connection unexpectedly.")), ("A network error occurred, or the server closed the connection unexpectedly."));
251 *retbuf = NULL;
252 return GST_FLOW_ERROR;
253 }
254 return gst_hlsdemux_test_src_create (src, offset, length, retbuf, context,
255 user_data);
256 }
257
258 /******************** Test specific code starts here **************************/
259
260 /*
261 * Test a media manifest with a single segment
262 *
263 */
GST_START_TEST(simpleTest)264 GST_START_TEST (simpleTest)
265 {
266 /* segment_size needs to larger than 2K, otherwise gsthlsdemux will
267 not perform a typefind on the buffer */
268 const guint segment_size = 30 * TS_PACKET_LEN;
269 const gchar *manifest =
270 "#EXTM3U \n"
271 "#EXT-X-TARGETDURATION:1\n"
272 "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
273 GstHlsDemuxTestInputData inputTestData[] = {
274 {"http://unit.test/media.m3u8", (guint8 *) manifest, 0},
275 {"http://unit.test/001.ts", NULL, segment_size},
276 {NULL, NULL, 0},
277 };
278 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
279 {"src_0", segment_size, NULL},
280 {NULL, 0, NULL}
281 };
282 TESTCASE_INIT_BOILERPLATE (segment_size);
283
284 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
285 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
286 engine_callbacks.appsink_received_data =
287 gst_adaptive_demux_test_check_received_data;
288 engine_callbacks.appsink_eos =
289 gst_adaptive_demux_test_check_size_of_received_data;
290
291 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
292 gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
293 inputTestData[0].uri, &engine_callbacks, engineTestData);
294 TESTCASE_UNREF_BOILERPLATE;
295 }
296
297 GST_END_TEST;
298
GST_START_TEST(testMasterPlaylist)299 GST_START_TEST (testMasterPlaylist)
300 {
301 const guint segment_size = 30 * TS_PACKET_LEN;
302 const gchar *master_playlist =
303 "#EXTM3U\n"
304 "#EXT-X-VERSION:4\n"
305 "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
306 "1200.m3u8\n";
307 const gchar *media_playlist =
308 "#EXTM3U \n"
309 "#EXT-X-TARGETDURATION:1\n"
310 "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
311 GstHlsDemuxTestInputData inputTestData[] = {
312 {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
313 {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
314 {"http://unit.test/001.ts", NULL, segment_size},
315 {NULL, NULL, 0}
316 };
317 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
318 {"src_0", segment_size, NULL},
319 {NULL, 0, NULL}
320 };
321 const GValue *requests;
322 guint i;
323 TESTCASE_INIT_BOILERPLATE (segment_size);
324
325 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
326 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
327 engine_callbacks.appsink_received_data =
328 gst_adaptive_demux_test_check_received_data;
329 engine_callbacks.appsink_eos =
330 gst_adaptive_demux_test_check_size_of_received_data;
331
332 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
333 gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
334 "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
335
336 requests = gst_structure_get_value (hlsTestCase.state, "requests");
337 fail_unless (requests != NULL);
338 assert_equals_uint64 (gst_value_array_get_size (requests),
339 sizeof (inputTestData) / sizeof (inputTestData[0]) - 1);
340 for (i = 0; inputTestData[i].uri; ++i) {
341 const GValue *uri;
342 uri = gst_value_array_get_value (requests, i);
343 fail_unless (uri != NULL);
344 assert_equals_string (inputTestData[i].uri, g_value_get_string (uri));
345 }
346 TESTCASE_UNREF_BOILERPLATE;
347 }
348
349 GST_END_TEST;
350
351 /*
352 * Test seeking
353 *
354 */
GST_START_TEST(testSeek)355 GST_START_TEST (testSeek)
356 {
357 const guint segment_size = 60 * TS_PACKET_LEN;
358 const gchar *manifest =
359 "#EXTM3U \n"
360 "#EXT-X-TARGETDURATION:1\n"
361 "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
362 GstHlsDemuxTestInputData inputTestData[] = {
363 {"http://unit.test/media.m3u8", (guint8 *) manifest, 0},
364 {"http://unit.test/001.ts", NULL, segment_size},
365 {NULL, NULL, 0},
366 };
367 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
368 {"src_0", segment_size, NULL},
369 {NULL, 0, NULL}
370 };
371 GstTestHTTPSrcCallbacks http_src_callbacks = { 0 };
372 GstAdaptiveDemuxTestCase *engineTestData;
373 GstHlsDemuxTestCase hlsTestCase = { 0 };
374 GByteArray *mpeg_ts = NULL;
375
376 engineTestData = gst_adaptive_demux_test_case_new ();
377 mpeg_ts = setup_test_variables (__FUNCTION__, inputTestData, outputTestData,
378 &hlsTestCase, engineTestData, segment_size);
379
380 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
381 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
382 /* seek to 5ms.
383 * Because there is only one fragment, we expect the whole file to be
384 * downloaded again
385 */
386 engineTestData->threshold_for_seek = 20 * TS_PACKET_LEN;
387 engineTestData->seek_event =
388 gst_event_new_seek (1.0, GST_FORMAT_TIME,
389 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, GST_SEEK_TYPE_SET,
390 5 * GST_MSECOND, GST_SEEK_TYPE_NONE, 0);
391
392 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
393 gst_adaptive_demux_test_seek (DEMUX_ELEMENT_NAME,
394 inputTestData[0].uri, engineTestData);
395
396 TESTCASE_UNREF_BOILERPLATE;
397 }
398
399 GST_END_TEST;
400
401 static void
run_seek_position_test(gdouble rate,GstSeekType start_type,guint64 seek_start,GstSeekType stop_type,guint64 seek_stop,GstSeekFlags flags,guint64 segment_start,guint64 segment_stop,gint segments)402 run_seek_position_test (gdouble rate, GstSeekType start_type,
403 guint64 seek_start, GstSeekType stop_type,
404 guint64 seek_stop, GstSeekFlags flags, guint64 segment_start,
405 guint64 segment_stop, gint segments)
406 {
407 const guint segment_size = 60 * TS_PACKET_LEN;
408 const gchar *manifest =
409 "#EXTM3U \n"
410 "#EXT-X-TARGETDURATION:1\n"
411 "#EXTINF:1,Test\n" "001.ts\n"
412 "#EXTINF:1,Test\n" "002.ts\n"
413 "#EXTINF:1,Test\n" "003.ts\n"
414 "#EXTINF:1,Test\n" "004.ts\n" "#EXT-X-ENDLIST\n";
415 GstHlsDemuxTestInputData inputTestData[] = {
416 {"http://unit.test/media.m3u8", (guint8 *) manifest, 0},
417 {"http://unit.test/001.ts", NULL, segment_size},
418 {"http://unit.test/002.ts", NULL, segment_size},
419 {"http://unit.test/003.ts", NULL, segment_size},
420 {"http://unit.test/004.ts", NULL, segment_size},
421 {NULL, NULL, 0},
422 };
423 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
424 {"src_0", segment_size * segments, NULL},
425 {NULL, 0, NULL}
426 };
427 GstTestHTTPSrcCallbacks http_src_callbacks = { 0 };
428 GstAdaptiveDemuxTestCase *engineTestData;
429 GstHlsDemuxTestCase hlsTestCase = { 0 };
430 GByteArray *mpeg_ts = NULL;
431
432 engineTestData = gst_adaptive_demux_test_case_new ();
433 mpeg_ts = setup_test_variables (__FUNCTION__, inputTestData, outputTestData,
434 &hlsTestCase, engineTestData, segment_size);
435
436 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
437 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
438
439 /* FIXME hack to avoid having a 0 seqnum */
440 gst_util_seqnum_next ();
441
442 /* Seek to 1.5s, expect it to start from 1s */
443 engineTestData->threshold_for_seek = 20 * TS_PACKET_LEN;
444 engineTestData->seek_event =
445 gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
446 seek_start, stop_type, seek_stop);
447 gst_segment_init (&outputTestData[0].post_seek_segment, GST_FORMAT_TIME);
448 outputTestData[0].post_seek_segment.rate = rate;
449 outputTestData[0].post_seek_segment.start = segment_start;
450 outputTestData[0].post_seek_segment.time = segment_start;
451 outputTestData[0].post_seek_segment.stop = segment_stop;
452 outputTestData[0].segment_verification_needed = TRUE;
453
454 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
455 gst_adaptive_demux_test_seek (DEMUX_ELEMENT_NAME,
456 inputTestData[0].uri, engineTestData);
457
458 TESTCASE_UNREF_BOILERPLATE;
459 }
460
461
GST_START_TEST(testSeekKeyUnitPosition)462 GST_START_TEST (testSeekKeyUnitPosition)
463 {
464 /* Seek to 1.5s with key unit, it should go back to 1.0s. 3 segments will be
465 * pushed */
466 run_seek_position_test (1.0, GST_SEEK_TYPE_SET, 1500 * GST_MSECOND,
467 GST_SEEK_TYPE_NONE, 0, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
468 1000 * GST_MSECOND, -1, 3);
469 }
470
471 GST_END_TEST;
472
GST_START_TEST(testSeekPosition)473 GST_START_TEST (testSeekPosition)
474 {
475 /* Seek to 1.5s without key unit, it should keep the 1.5s, but still push
476 * from the 1st segment, so 3 segments will be
477 * pushed */
478 run_seek_position_test (1.0, GST_SEEK_TYPE_SET, 1500 * GST_MSECOND,
479 GST_SEEK_TYPE_NONE, 0, GST_SEEK_FLAG_FLUSH, 1500 * GST_MSECOND, -1, 3);
480 }
481
482 GST_END_TEST;
483
GST_START_TEST(testSeekUpdateStopPosition)484 GST_START_TEST (testSeekUpdateStopPosition)
485 {
486 run_seek_position_test (1.0, GST_SEEK_TYPE_NONE, 1500 * GST_MSECOND,
487 GST_SEEK_TYPE_SET, 3000 * GST_MSECOND, 0, 0, 3000 * GST_MSECOND, 3);
488 }
489
490 GST_END_TEST;
491
GST_START_TEST(testSeekSnapBeforePosition)492 GST_START_TEST (testSeekSnapBeforePosition)
493 {
494 /* Seek to 1.5s, snap before, it go to 1s */
495 run_seek_position_test (1.0, GST_SEEK_TYPE_SET, 1500 * GST_MSECOND,
496 GST_SEEK_TYPE_NONE, 0,
497 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_BEFORE,
498 1000 * GST_MSECOND, -1, 3);
499 }
500
501 GST_END_TEST;
502
503
GST_START_TEST(testSeekSnapAfterPosition)504 GST_START_TEST (testSeekSnapAfterPosition)
505 {
506 /* Seek to 1.5s with snap after, it should move to 2s */
507 run_seek_position_test (1.0, GST_SEEK_TYPE_SET, 1500 * GST_MSECOND,
508 GST_SEEK_TYPE_NONE, 0,
509 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_AFTER,
510 2000 * GST_MSECOND, -1, 2);
511 }
512
513 GST_END_TEST;
514
515
GST_START_TEST(testReverseSeekSnapBeforePosition)516 GST_START_TEST (testReverseSeekSnapBeforePosition)
517 {
518 run_seek_position_test (-1.0, GST_SEEK_TYPE_SET, 1000 * GST_MSECOND,
519 GST_SEEK_TYPE_SET, 2500 * GST_MSECOND,
520 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_BEFORE,
521 1000 * GST_MSECOND, 3000 * GST_MSECOND, 2);
522 }
523
524 GST_END_TEST;
525
526
GST_START_TEST(testReverseSeekSnapAfterPosition)527 GST_START_TEST (testReverseSeekSnapAfterPosition)
528 {
529 run_seek_position_test (-1.0, GST_SEEK_TYPE_SET, 1000 * GST_MSECOND,
530 GST_SEEK_TYPE_SET, 2500 * GST_MSECOND,
531 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_AFTER,
532 1000 * GST_MSECOND, 2000 * GST_MSECOND, 1);
533 }
534
535 GST_END_TEST;
536
537 static void
testDownloadErrorMessageCallback(GstAdaptiveDemuxTestEngine * engine,GstMessage * msg,gpointer user_data)538 testDownloadErrorMessageCallback (GstAdaptiveDemuxTestEngine * engine,
539 GstMessage * msg, gpointer user_data)
540 {
541 GError *err = NULL;
542 gchar *dbg_info = NULL;
543
544 fail_unless (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR);
545 gst_message_parse_error (msg, &err, &dbg_info);
546 GST_DEBUG ("Error from element %s : %s",
547 GST_OBJECT_NAME (msg->src), err->message);
548 fail_unless_equals_string (GST_OBJECT_NAME (msg->src), DEMUX_ELEMENT_NAME);
549 g_error_free (err);
550 g_free (dbg_info);
551 g_main_loop_quit (engine->loop);
552 }
553
554 /* test failing to download the media playlist */
GST_START_TEST(testMediaPlaylistNotFound)555 GST_START_TEST (testMediaPlaylistNotFound)
556 {
557 const gchar *master_playlist =
558 "#EXTM3U\n"
559 "#EXT-X-VERSION:4\n"
560 "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
561 "1200.m3u8\n";
562 GstHlsDemuxTestInputData inputTestData[] = {
563 {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
564 {NULL, NULL, 0}
565 };
566 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
567 {"src_0", 0, NULL},
568 {NULL, 0, NULL}
569 };
570 TESTCASE_INIT_BOILERPLATE (0);
571
572 gst_structure_set (hlsTestCase.state,
573 "failure-count", G_TYPE_UINT, 0,
574 "failure-suffix", G_TYPE_STRING, "1200.m3u8", NULL);
575 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
576 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
577 engine_callbacks.appsink_received_data =
578 gst_adaptive_demux_test_check_received_data;
579 engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
580
581 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
582 gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
583 "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
584
585 TESTCASE_UNREF_BOILERPLATE;
586 }
587
588 GST_END_TEST;
589
590 static void
hlsdemux_test_check_no_data_received(GstAdaptiveDemuxTestEngine * engine,GstAdaptiveDemuxTestOutputStream * stream,gpointer user_data)591 hlsdemux_test_check_no_data_received (GstAdaptiveDemuxTestEngine
592 * engine, GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data)
593 {
594 assert_equals_uint64 (stream->total_received_size, 0);
595 g_main_loop_quit (engine->loop);
596 }
597
598 /* test failing to download a media segment (a 404 error) */
GST_START_TEST(testFragmentNotFound)599 GST_START_TEST (testFragmentNotFound)
600 {
601 const gchar *master_playlist =
602 "#EXTM3U\n"
603 "#EXT-X-VERSION:4\n"
604 "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
605 "1200.m3u8\n";
606 const gchar *media_playlist =
607 "#EXTM3U \n"
608 "#EXT-X-TARGETDURATION:1\n"
609 "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
610 GstHlsDemuxTestInputData inputTestData[] = {
611 {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
612 {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
613 {NULL, NULL, 0}
614 };
615 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
616 {"src_0", 0, NULL},
617 {NULL, 0, NULL}
618 };
619 TESTCASE_INIT_BOILERPLATE (0);
620
621 gst_structure_set (hlsTestCase.state,
622 "failure-count", G_TYPE_UINT, 0,
623 "failure-suffix", G_TYPE_STRING, "001.ts", NULL);
624 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
625 http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
626 engine_callbacks.appsink_received_data =
627 gst_adaptive_demux_test_check_received_data;
628 engine_callbacks.appsink_eos = hlsdemux_test_check_no_data_received;
629 engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
630
631 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
632 gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
633 "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
634
635 TESTCASE_UNREF_BOILERPLATE;
636 }
637
638 GST_END_TEST;
639
640 /* work-around that adaptivedemux is not posting an error message
641 about failure to download a fragment */
642 static void
missing_message_eos_callback(GstAdaptiveDemuxTestEngine * engine,GstAdaptiveDemuxTestOutputStream * stream,gpointer user_data)643 missing_message_eos_callback (GstAdaptiveDemuxTestEngine * engine,
644 GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data)
645 {
646 GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data);
647 GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData;
648
649 fail_unless (stream != NULL);
650 testOutputStreamData =
651 gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL);
652 fail_unless (testOutputStreamData != NULL);
653 /* expect to receive less than file size */
654 fail_unless (stream->total_received_size <
655 testOutputStreamData->expected_size,
656 "size validation failed for %s, expected < %d received %d",
657 testOutputStreamData->name, testOutputStreamData->expected_size,
658 stream->total_received_size);
659 testData->count_of_finished_streams++;
660 GST_DEBUG ("EOS callback %d %d",
661 testData->count_of_finished_streams,
662 g_list_length (testData->output_streams));
663 if (testData->count_of_finished_streams ==
664 g_list_length (testData->output_streams)) {
665 g_main_loop_quit (engine->loop);
666 }
667 }
668
669
670 /*
671 * Test fragment download error
672 * Let the adaptive demux download a few bytes, then instruct the
673 * test soup http src element to generate an error.
674 */
GST_START_TEST(testFragmentDownloadError)675 GST_START_TEST (testFragmentDownloadError)
676 {
677 const guint segment_size = 30 * TS_PACKET_LEN;
678 const gchar *master_playlist =
679 "#EXTM3U\n"
680 "#EXT-X-VERSION:4\n"
681 "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
682 "1200.m3u8\n";
683 const gchar *media_playlist =
684 "#EXTM3U \n"
685 "#EXT-X-VERSION:4\n"
686 "#EXT-X-TARGETDURATION:1\n"
687 "#EXTINF:1,Test\n" "001.ts\n"
688 "#EXTINF:1,Test\n" "002.ts\n" "#EXT-X-ENDLIST\n";
689 GstHlsDemuxTestInputData inputTestData[] = {
690 {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
691 {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
692 {"http://unit.test/001.ts", NULL, segment_size},
693 {"http://unit.test/002.ts", NULL, segment_size},
694 {NULL, NULL, 0}
695 };
696 const guint64 failure_position = 2048;
697 GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
698 /* adaptive demux tries for 4 times (MAX_DOWNLOAD_ERROR_COUNT + 1) before giving up */
699 {"src_0", failure_position * 4, NULL},
700 {NULL, 0, NULL}
701 };
702 TESTCASE_INIT_BOILERPLATE (segment_size);
703
704 /* download in chunks of failure_position size.
705 * This means the first chunk will succeed, the second will generate
706 * error because we already exceeded failure_position bytes.
707 */
708 gst_test_http_src_set_default_blocksize (failure_position);
709
710 http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
711 http_src_callbacks.src_create = gst_hlsdemux_test_network_error_src_create;
712 gst_structure_set (hlsTestCase.state,
713 "failure-suffix", G_TYPE_STRING, "001.ts",
714 "failure-position", G_TYPE_UINT64, failure_position, NULL);
715 engine_callbacks.appsink_received_data =
716 gst_adaptive_demux_test_check_received_data;
717 engine_callbacks.appsink_eos = missing_message_eos_callback;
718 engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
719
720 gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
721 gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
722 inputTestData[0].uri, &engine_callbacks, engineTestData);
723
724 TESTCASE_UNREF_BOILERPLATE;
725 }
726
727 GST_END_TEST;
728
729 static Suite *
hls_demux_suite(void)730 hls_demux_suite (void)
731 {
732 Suite *s = suite_create ("hls_demux");
733 TCase *tc_basicTest = tcase_create ("basicTest");
734
735 tcase_add_test (tc_basicTest, simpleTest);
736 tcase_add_test (tc_basicTest, testMasterPlaylist);
737 tcase_add_test (tc_basicTest, testMediaPlaylistNotFound);
738 tcase_add_test (tc_basicTest, testFragmentNotFound);
739 tcase_add_test (tc_basicTest, testFragmentDownloadError);
740 tcase_add_test (tc_basicTest, testSeek);
741 tcase_add_test (tc_basicTest, testSeekKeyUnitPosition);
742 tcase_add_test (tc_basicTest, testSeekPosition);
743 tcase_add_test (tc_basicTest, testSeekUpdateStopPosition);
744 tcase_add_test (tc_basicTest, testSeekSnapBeforePosition);
745 tcase_add_test (tc_basicTest, testSeekSnapAfterPosition);
746 tcase_add_test (tc_basicTest, testReverseSeekSnapBeforePosition);
747 tcase_add_test (tc_basicTest, testReverseSeekSnapAfterPosition);
748
749 tcase_add_unchecked_fixture (tc_basicTest, gst_adaptive_demux_test_setup,
750 gst_adaptive_demux_test_teardown);
751
752 suite_add_tcase (s, tc_basicTest);
753
754 return s;
755 }
756
757 GST_CHECK_MAIN (hls_demux);
758