qtdemux: add unit test for edit list regression
authorTim-Philipp Müller <tim@centricular.com>
Thu, 11 May 2023 15:25:11 +0000 (16:25 +0100)
committerTim-Philipp Müller <tim@centricular.com>
Thu, 11 May 2023 17:46:57 +0000 (18:46 +0100)
File is the mp4 file from #2549 with the mdat atom
zeroed out and compressed. We compress twice because
apparently compressing 5MB of zeroes effectively in
one run is too difficult for gzip.

https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2549

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4605>

subprojects/gst-plugins-good/tests/check/elements/qtdemux.c
subprojects/gst-plugins-good/tests/files/editlists.mp4.gz.gz [new file with mode: 0644]

index 4a14c45..60c63aa 100644 (file)
 
 #include "qtdemux.h"
 #include <glib/gprintf.h>
-#include <gst/check/gstharness.h>
+
+#include <gio/gio.h>
+
+#include <gst/check/check.h>
+#include <gst/app/app.h>
+
+#define TEST_FILE_PREFIX GST_TEST_FILES_PATH G_DIR_SEPARATOR_S
+
+static gboolean
+load_file (const gchar * fn, guint8 ** p_data, guint expected_len)
+{
+  gsize read_len = 0;
+
+  if (!g_file_get_contents (fn, (gchar **) p_data, &read_len, NULL))
+    return FALSE;
+
+  g_assert_cmpuint (read_len, ==, expected_len);
+  return TRUE;
+}
 
 typedef struct
 {
@@ -888,6 +906,127 @@ GST_START_TEST (test_qtdemux_pad_names)
 
 GST_END_TEST;
 
+GST_START_TEST (test_qtdemux_editlist)
+{
+  const gsize editlist_mp4_size = 5322593;
+  guint8 *editlist_mp4 = NULL;
+  GstElement *src, *sink, *pipe;
+  GstSample *sample;
+  guint frame_count = 0;
+
+  {
+    GZlibDecompressor *decompress;
+    GConverterResult decomp_res;
+    gsize bytes_read, gz_size, mp4_size;
+    guint8 *gz_gz = NULL;
+    guint8 gz[8705];
+
+    /* read .mp4.gz.gz */
+    g_assert (load_file (TEST_FILE_PREFIX "editlists.mp4.gz.gz", &gz_gz, 3597));
+
+    /* mp4.gz.gz -> mp4.gz */
+    decompress = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+    decomp_res = g_converter_convert (G_CONVERTER (decompress), gz_gz, 3597,
+        gz, 8705, G_CONVERTER_INPUT_AT_END, &bytes_read, &gz_size, NULL);
+    fail_unless_equals_int (decomp_res, G_CONVERTER_FINISHED);
+    fail_unless_equals_int (bytes_read, 3597);
+    fail_unless_equals_int (gz_size, 8705);
+    g_object_unref (decompress);
+    g_clear_pointer (&gz_gz, (GDestroyNotify) g_free);
+
+    editlist_mp4 = g_malloc0 (editlist_mp4_size);
+
+    /* mp4.gz -> mp4 */
+    decompress = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+    decomp_res = g_converter_convert (G_CONVERTER (decompress), gz, 8705,
+        editlist_mp4, editlist_mp4_size, G_CONVERTER_INPUT_AT_END, &bytes_read,
+        &mp4_size, NULL);
+    fail_unless_equals_int (decomp_res, G_CONVERTER_FINISHED);
+    fail_unless_equals_int (bytes_read, 8705);
+    fail_unless_equals_int (mp4_size, editlist_mp4_size);
+    g_object_unref (decompress);
+  }
+
+  fail_unless_equals_int (editlist_mp4[28 + 4], 'm');
+  fail_unless_equals_int (editlist_mp4[28 + 5], 'd');
+  fail_unless_equals_int (editlist_mp4[28 + 6], 'a');
+  fail_unless_equals_int (editlist_mp4[28 + 7], 't');
+
+  pipe = gst_parse_launch ("dataurisrc name=src ! qtdemux name=d "
+      "d.video_0 ! appsink name=sink", NULL);
+
+  fail_unless (pipe != NULL);
+
+  src = gst_bin_get_by_name (GST_BIN (pipe), "src");
+  fail_unless (src != NULL);
+
+  sink = gst_bin_get_by_name (GST_BIN (pipe), "sink");
+  fail_unless (sink != NULL);
+
+  /* Convert to data: URI so we can use dataurisrc. Bit silly of course,
+   * should have a memsrc or somesuch, but does the job for now */
+  {
+    gsize s_alloc_len = 32 + (editlist_mp4_size / 3 + 1) * 4 + 4;
+    gchar *s = g_malloc0 (s_alloc_len);
+    gsize s_len = 0;
+    gsize base64_size;
+    gint state = 0;
+    gint save = 0;
+
+    s_len = g_strlcat (s, "data:video/quicktime;base64,", s_alloc_len);
+
+    base64_size =
+        g_base64_encode_step (editlist_mp4, editlist_mp4_size, FALSE, s + s_len,
+        &state, &save);
+    s_len += base64_size;
+    base64_size = g_base64_encode_close (FALSE, s + s_len, &state, &save);
+    s_len += base64_size;
+    g_clear_pointer (&editlist_mp4, (GDestroyNotify) g_free);
+
+    {
+      GValue v = G_VALUE_INIT;
+
+      /* Avoids at least one of the two string copies */
+      g_value_init (&v, G_TYPE_STRING);
+      g_value_take_string (&v, s);
+      g_object_set_property (G_OBJECT (src), "uri", &v);
+      g_value_reset (&v);
+    }
+  }
+
+  g_object_set (sink, "sync", FALSE, NULL);
+
+  gst_element_set_state (pipe, GST_STATE_PLAYING);
+
+  /* wait for preroll */
+  {
+    GstMessage *msg;
+
+    GST_LOG ("waiting for preroll");
+    msg =
+        gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1,
+        GST_MESSAGE_ASYNC_DONE);
+
+    gst_message_unref (msg);
+  }
+
+  /* pull video frames out of qtdemux */
+  while ((sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)))) {
+    ++frame_count;
+    gst_sample_unref (sample);
+  }
+
+  fail_unless_equals_int (frame_count, 361);
+
+  gst_element_set_state (pipe, GST_STATE_NULL);
+
+  gst_clear_object (&src);
+  gst_clear_object (&sink);
+  gst_clear_object (&pipe);
+}
+
+GST_END_TEST;
+
 static Suite *
 qtdemux_suite (void)
 {
@@ -901,6 +1040,7 @@ qtdemux_suite (void)
   tcase_add_test (tc_chain, test_qtdemux_duplicated_moov);
   tcase_add_test (tc_chain, test_qtdemux_stream_change);
   tcase_add_test (tc_chain, test_qtdemux_pad_names);
+  tcase_add_test (tc_chain, test_qtdemux_editlist);
 
   return s;
 }
diff --git a/subprojects/gst-plugins-good/tests/files/editlists.mp4.gz.gz b/subprojects/gst-plugins-good/tests/files/editlists.mp4.gz.gz
new file mode 100644 (file)
index 0000000..93cafdc
Binary files /dev/null and b/subprojects/gst-plugins-good/tests/files/editlists.mp4.gz.gz differ