1 /* GStreamer unit test for matroskademux
2  * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include <gst/check/gstcheck.h>
21 #include <gst/check/gstharness.h>
22 
23 const gchar mkv_sub_base64[] =
24     "GkXfowEAAAAAAAAUQoKJbWF0cm9za2EAQoeBAkKFgQIYU4BnAQAAAAAAAg0RTZt0AQAAAAAAAIxN"
25     "uwEAAAAAAAASU6uEFUmpZlOsiAAAAAAAAACYTbsBAAAAAAAAElOrhBZUrmtTrIgAAAAAAAABEuya"
26     "AQAAAAAAABJTq4QQQ6dwU6yI///////////smgEAAAAAAAASU6uEHFO7a1OsiP//////////TbsB"
27     "AAAAAAAAElOrhBJUw2dTrIgAAAAAAAAB9xVJqWYBAAAAAAAAbnOkkDylQZJlrLziQo8+gsrZVtUq"
28     "17GDD0JARImIQNGUAAAAAABNgJ9HU3RyZWFtZXIgcGx1Z2luIHZlcnNpb24gMS40LjUAV0GZR1N0"
29     "cmVhbWVyIE1hdHJvc2thIG11eGVyAERhiAZfU0rcEwgAFlSuawEAAAAAAAA0rgEAAAAAAAAr14EB"
30     "g4ERc8WIoWF8pYlELidTbolTdWJ0aXRsZQCGjFNfVEVYVC9VVEY4AB9DtnUBAAAAAAAAmeeCA+ig"
31     "AQAAAAAAAA2bggfQoYeBAAAAZm9voAEAAAAAAAAUm4IH0KGOgQu4ADxpPmJhcjwvaT6gAQAAAAAA"
32     "AA2bggfQoYeBF3AAYmF6oAEAAAAAAAAOm4IH0KGIgScQAGbDtgCgAQAAAAAAABWbggfQoY+BMsgA"
33     "PGk+YmFyPC9pPgCgAQAAAAAAAA6bggfQoYiBPoAAYuR6ABJUw2cBAAAAAAAACnNzAQAAAAAAAAA=";
34 
35 const gchar mkv_toc_base64[] =
36     "GkXfowEAAAAAAAAUQoKJbWF0cm9za2EAQoeBAUKFgQEYU4BnAQAAAAAABUoRTZt0AQAAAAAAAIxN"
37     "uwEAAAAAAAASU6uEFUmpZlOsiAAAAAAAAACYTbsBAAAAAAAAElOrhBZUrmtTrIgAAAAAAAABGk27"
38     "AQAAAAAAABJTq4QQQ6dwU6yIAAAAAAAAAWFNuwEAAAAAAAASU6uEHFO7a1OsiAAAAAAAAANrTbsB"
39     "AAAAAAAAElOrhBJUw2dTrIgAAAAAAAADkxVJqWYBAAAAAAAAdnOkkFdJrZAH7YY5MCvJGPwl5E4q"
40     "17GDD0JARImIP/AAAAAAAABNgKdHU3RyZWFtZXIgbWF0cm9za2FtdXggdmVyc2lvbiAxLjEzLjAu"
41     "MQBXQZlHU3RyZWFtZXIgTWF0cm9za2EgbXV4ZXIARGGIB2iH12N5DgAWVK5rAQAAAAAAADuuAQAA"
42     "AAAAADLXgQGDgQJzxYgJixQa+ZhvPSPjg4MPQkBTboZBdWRpbwDhAQAAAAAAAACGhkFfQUMzABBD"
43     "p3ABAAAAAAAB30W5AQAAAAAAAdVFvIi3DuS4TWeFXUW9gQBF24EARd2BALYBAAAAAAAA1HPEiOV0"
44     "L8eev+wgVlSGdWlkLjEAkYEAkoMehICYgQBFmIEBgAEAAAAAAAAQhYdjaGFwLjEAQ3yEdW5kALYB"
45     "AAAAAAAAQnPEiCW5ajpHRzyzVlSIdWlkLjEuMQCRgQCSgw9CQJiBAEWYgQGAAQAAAAAAABSFi25l"
46     "c3RlZC4xLjEAQ3yEdW5kALYBAAAAAAAARHPEiA9klFqtGkBoVlSIdWlkLjEuMgCRgw9CQJKDHoSA"
47     "mIEARZiBAYABAAAAAAAAFIWLbmVzdGVkLzEuMgBDfIR1bmQAtgEAAAAAAADYc8SIeu4QRrjscdtW"
48     "VIZ1aWQuMgCRgx6EgJKDPQkAmIEARZiBAYABAAAAAAAAEIWHY2hhcC4yAEN8hHVuZAC2AQAAAAAA"
49     "AERzxIik77DMKqRyzFZUiHVpZC4yLjEAkYMehICSgy3GwJiBAEWYgQGAAQAAAAAAABSFi25lc3Rl"
50     "ZC4yLjEAQ3yEdW5kALYBAAAAAAAARHPEiDvwt+5+V1ktVlSIdWlkLjIuMgCRgy3GwJKDPQkAmIEA"
51     "RZiBAYABAAAAAAAAFIWLbmVzdGVkLzIuMgBDfIR1bmQAH0O2dQEAAAAAAAAT54EAoAEAAAAAAAAH"
52     "oYWBAAAAABxTu2sBAAAAAAAAHLsBAAAAAAAAE7OBALcBAAAAAAAAB/eBAfGCA0wSVMNnAQAAAAAA"
53     "AatzcwEAAAAAAAAxY8ABAAAAAAAAC2PJiLcO5LhNZ4VdZ8gBAAAAAAAAEkWjiUNPTU1FTlRTAESH"
54     "g0VkAHNzAQAAAAAAADJjwAEAAAAAAAALY8SI5XQvx56/7CBnyAEAAAAAAAATRaOHQVJUSVNUAESH"
55     "hmFydC4xAHNzAQAAAAAAADRjwAEAAAAAAAALY8SIJblqOkdHPLNnyAEAAAAAAAAVRaOHQVJUSVNU"
56     "AESHiGFydC4xLjEAc3MBAAAAAAAANGPAAQAAAAAAAAtjxIgPZJRarRpAaGfIAQAAAAAAABVFo4dB"
57     "UlRJU1QARIeIYXJ0LjEuMgBzcwEAAAAAAAAyY8ABAAAAAAAAC2PEiHruEEa47HHbZ8gBAAAAAAAA"
58     "E0Wjh0FSVElTVABEh4ZhcnQuMgBzcwEAAAAAAAA0Y8ABAAAAAAAAC2PEiKTvsMwqpHLMZ8gBAAAA"
59     "AAAAFUWjh0FSVElTVABEh4hhcnQuMi4xAHNzAQAAAAAAADRjwAEAAAAAAAALY8SIO/C37n5XWS1n"
60     "yAEAAAAAAAAVRaOHQVJUSVNUAESHiGFydC4yLjIA";
61 
62 static void
pad_added_cb(GstElement * matroskademux,GstPad * pad,gpointer user_data)63 pad_added_cb (GstElement * matroskademux, GstPad * pad, gpointer user_data)
64 {
65   GstHarness *h = user_data;
66 
67   GST_LOG_OBJECT (pad, "got new source pad");
68   gst_harness_add_element_src_pad (h, pad);
69 }
70 
71 static void
pull_and_check_buffer(GstHarness * h,GstClockTime pts,GstClockTime duration,const gchar * output)72 pull_and_check_buffer (GstHarness * h, GstClockTime pts, GstClockTime duration,
73     const gchar * output)
74 {
75   GstMapInfo map;
76   GstBuffer *buf;
77 
78   /* wait for buffer */
79   buf = gst_harness_pull (h);
80 
81   /* Make sure there's no 0-terminator in there */
82   fail_unless (gst_buffer_map (buf, &map, GST_MAP_READ));
83   GST_MEMDUMP ("subtitle buffer", map.data, map.size);
84   fail_unless (map.size > 0);
85   fail_unless (map.data[map.size - 1] != '\0');
86   if (output != NULL && memcmp (map.data, output, map.size) != 0) {
87     g_printerr ("Got:\n");
88     gst_util_dump_mem (map.data, map.size);;
89     g_printerr ("Wanted:\n");
90     gst_util_dump_mem ((guint8 *) output, strlen (output));
91     g_error ("Did not get output expected.");
92   }
93 
94   gst_buffer_unmap (buf, &map);
95 
96   fail_unless_equals_int64 (pts, GST_BUFFER_PTS (buf));
97   fail_unless_equals_int64 (duration, GST_BUFFER_DURATION (buf));
98 
99   gst_buffer_unref (buf);
100 }
101 
GST_START_TEST(test_sub_terminator)102 GST_START_TEST (test_sub_terminator)
103 {
104   GstHarness *h;
105   GstBuffer *buf;
106   guchar *mkv_data;
107   gsize mkv_size;
108 
109   h = gst_harness_new_with_padnames ("matroskademux", "sink", NULL);
110 
111   g_signal_connect (h->element, "pad-added", G_CALLBACK (pad_added_cb), h);
112 
113   mkv_data = g_base64_decode (mkv_sub_base64, &mkv_size);
114   fail_unless (mkv_data != NULL);
115 
116   gst_harness_set_src_caps_str (h, "video/x-matroska");
117 
118   buf = gst_buffer_new_wrapped (mkv_data, mkv_size);
119   GST_BUFFER_OFFSET (buf) = 0;
120 
121   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, buf));
122   gst_harness_push_event (h, gst_event_new_eos ());
123 
124   pull_and_check_buffer (h, 1 * GST_SECOND, 2 * GST_SECOND, "foo");
125   pull_and_check_buffer (h, 4 * GST_SECOND, 2 * GST_SECOND, "<i>bar</i>");
126   pull_and_check_buffer (h, 7 * GST_SECOND, 2 * GST_SECOND, "baz");
127   pull_and_check_buffer (h, 11 * GST_SECOND, 2 * GST_SECOND, "f\303\266");
128   pull_and_check_buffer (h, 14 * GST_SECOND, 2 * GST_SECOND, "<i>bar</i>");
129   /* The input is invalid UTF-8 here, what happens might depend on locale */
130   pull_and_check_buffer (h, 17 * GST_SECOND, 2 * GST_SECOND, NULL);
131 
132   fail_unless (gst_harness_try_pull (h) == NULL);
133 
134   gst_harness_teardown (h);
135 }
136 
137 GST_END_TEST;
138 
139 /* Recursively compare 2 toc entries */
140 static void
check_toc_entries(const GstTocEntry * original,const GstTocEntry * other)141 check_toc_entries (const GstTocEntry * original, const GstTocEntry * other)
142 {
143   gint64 start, stop, other_start, other_stop;
144   GstTocEntryType original_type, other_type;
145   const gchar *original_string_uid = NULL, *other_string_uid = NULL;
146   GstTagList *original_tags, *other_tags;
147   GList *cur, *other_cur;
148 
149   original_type = gst_toc_entry_get_entry_type (original);
150   other_type = gst_toc_entry_get_entry_type (other);
151   fail_unless (original_type == other_type);
152 
153   if (original_type != GST_TOC_ENTRY_TYPE_EDITION) {
154     original_string_uid = gst_toc_entry_get_uid (original);
155     other_string_uid = gst_toc_entry_get_uid (other);
156     fail_unless (g_strcmp0 (original_string_uid, other_string_uid) == 0);
157   }
158 
159   if (original_type != GST_TOC_ENTRY_TYPE_EDITION) {
160     gst_toc_entry_get_start_stop_times (original, &start, &stop);
161     gst_toc_entry_get_start_stop_times (other, &other_start, &other_stop);
162 
163     fail_unless (start == other_start && stop == other_stop);
164   }
165 
166   /* tags */
167   original_tags = gst_toc_entry_get_tags (original);
168   other_tags = gst_toc_entry_get_tags (other);
169   fail_unless (gst_tag_list_is_equal (original_tags, other_tags));
170 
171   other_cur = gst_toc_entry_get_sub_entries (other);
172   for (cur = gst_toc_entry_get_sub_entries (original); cur != NULL;
173       cur = cur->next) {
174     fail_unless (other_cur != NULL);
175 
176     check_toc_entries (cur->data, other_cur->data);
177 
178     other_cur = other_cur->next;
179   }
180 }
181 
182 /* Create a new chapter */
183 static GstTocEntry *
new_chapter(const guint chapter_nb,const gint64 start,const gint64 stop)184 new_chapter (const guint chapter_nb, const gint64 start, const gint64 stop)
185 {
186   GstTocEntry *toc_entry, *toc_sub_entry;
187   GstTagList *tags;
188   gchar title[32];
189   gchar artist[32];
190   gchar str_uid[32];
191 
192   g_snprintf (str_uid, sizeof (str_uid), "uid.%d", chapter_nb);
193   toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
194   gst_toc_entry_set_start_stop_times (toc_entry, start, stop);
195 
196   g_snprintf (title, sizeof (title), "chap.%d", chapter_nb);
197   g_snprintf (artist, sizeof (artist), "art.%d", chapter_nb);
198   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
199   gst_toc_entry_set_tags (toc_entry, tags);
200 
201   g_snprintf (str_uid, sizeof (str_uid), "uid.%d.1", chapter_nb);
202   toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
203   gst_toc_entry_set_start_stop_times (toc_sub_entry, start, (start + stop) / 2);
204 
205   g_snprintf (title, sizeof (title), "nested.%d.1", chapter_nb);
206   g_snprintf (artist, sizeof (artist), "art.%d.1", chapter_nb);
207   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
208   gst_toc_entry_set_tags (toc_sub_entry, tags);
209 
210   gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry);
211 
212   g_snprintf (str_uid, sizeof (str_uid), "uid.%d.2", chapter_nb);
213   toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
214   gst_toc_entry_set_start_stop_times (toc_sub_entry, (start + stop) / 2, stop);
215 
216   g_snprintf (title, sizeof (title), "nested/%d.2", chapter_nb);
217   g_snprintf (artist, sizeof (artist), "art.%d.2", chapter_nb);
218   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
219   gst_toc_entry_set_tags (toc_sub_entry, tags);
220 
221   gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry);
222 
223   return toc_entry;
224 }
225 
226 /* Create a reference toc which matches what is expected in mkv_toc_base64 */
227 static GstToc *
new_reference_toc(void)228 new_reference_toc (void)
229 {
230   GstToc *ref_toc;
231   GstTocEntry *toc_edition_entry, *toc_entry;
232   GstTagList *tags;
233 
234   ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL);
235 
236   toc_edition_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "00");
237   tags = gst_tag_list_new (GST_TAG_COMMENT, "Ed", NULL);
238   gst_toc_entry_set_tags (toc_edition_entry, tags);
239 
240   toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND);
241   gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry);
242 
243   toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND);
244   gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry);
245 
246   gst_toc_append_entry (ref_toc, toc_edition_entry);
247 
248   return ref_toc;
249 }
250 
GST_START_TEST(test_toc_demux)251 GST_START_TEST (test_toc_demux)
252 {
253   GstHarness *h;
254   GstBuffer *buf;
255   guchar *mkv_data;
256   gsize mkv_size;
257   GstEvent *event;
258   gboolean update;
259   GstToc *ref_toc, *demuxed_toc = NULL;
260   GList *ref_cur, *demuxed_cur;
261 
262   h = gst_harness_new_with_padnames ("matroskademux", "sink", NULL);
263 
264   g_signal_connect (h->element, "pad-added", G_CALLBACK (pad_added_cb), h);
265 
266   mkv_data = g_base64_decode (mkv_toc_base64, &mkv_size);
267   fail_unless (mkv_data != NULL);
268 
269   gst_harness_set_src_caps_str (h, "audio/x-matroska");
270 
271   buf = gst_buffer_new_wrapped (mkv_data, mkv_size);
272   GST_BUFFER_OFFSET (buf) = 0;
273 
274   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, buf));
275   gst_harness_push_event (h, gst_event_new_eos ());
276 
277   event = gst_harness_try_pull_event (h);
278   fail_unless (event != NULL);
279 
280   while (event != NULL) {
281     if (event->type == GST_EVENT_TOC) {
282       gst_event_parse_toc (event, &demuxed_toc, &update);
283       gst_event_unref (event);
284       break;
285     }
286     gst_event_unref (event);
287     event = gst_harness_try_pull_event (h);
288   }
289 
290   fail_unless (demuxed_toc != NULL);
291   ref_toc = new_reference_toc ();
292 
293   demuxed_cur = gst_toc_get_entries (demuxed_toc);
294   for (ref_cur = gst_toc_get_entries (ref_toc); ref_cur != NULL;
295       ref_cur = ref_cur->next) {
296     fail_unless (demuxed_cur != NULL);
297 
298     check_toc_entries (ref_cur->data, demuxed_cur->data);
299     demuxed_cur = demuxed_cur->next;
300   }
301 
302   gst_toc_unref (ref_toc);
303   gst_toc_unref (demuxed_toc);
304 
305   gst_harness_teardown (h);
306 }
307 
308 GST_END_TEST;
309 
310 static Suite *
matroskademux_suite(void)311 matroskademux_suite (void)
312 {
313   Suite *s = suite_create ("matroskademux");
314   TCase *tc_chain = tcase_create ("general");
315 
316   suite_add_tcase (s, tc_chain);
317   tcase_add_test (tc_chain, test_sub_terminator);
318   tcase_add_test (tc_chain, test_toc_demux);
319 
320   return s;
321 }
322 
323 GST_CHECK_MAIN (matroskademux);
324