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\n",
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