splitmuxsink: Don't crash on EOS without buffer
authorSeungha Yang <seungha@centricular.com>
Wed, 13 Apr 2022 16:19:51 +0000 (01:19 +0900)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Tue, 5 Jul 2022 11:33:35 +0000 (11:33 +0000)
Fix a case where upstream pushed EOS without buffers.

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

subprojects/gst-plugins-good/gst/multifile/gstsplitmuxsink.c
subprojects/gst-plugins-good/tests/check/elements/splitmuxsink.c

index 8fa6f1b..948ca35 100644 (file)
@@ -2805,14 +2805,34 @@ handle_mq_input (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
           splitmux->input_state = SPLITMUX_INPUT_STATE_WAITING_GOP_COLLECT;
           /* Wake up other input pads to collect this GOP */
           GST_SPLITMUX_BROADCAST_INPUT (splitmux);
-          check_completed_gop (splitmux, ctx);
+          if (g_queue_is_empty (&splitmux->pending_input_gops)) {
+            GST_WARNING_OBJECT (splitmux,
+                "EOS with no buffers received on the reference pad");
+
+            /* - child muxer and sink might be still locked state
+             *   (see gst_splitmux_reset_elements()) so should be unlocked
+             *   for state change of splitmuxsink to be applied to child
+             * - would need to post async done message
+             * - location on sink element is still null then it will post
+             *   error message on bus (muxer will produce something, header
+             *   data for example)
+             *
+             * Calls start_next_fragment() here, the method will address
+             * everything the above mentioned one */
+            ret = start_next_fragment (splitmux, ctx);
+            if (ret != GST_FLOW_OK)
+              goto beach;
+          } else {
+            check_completed_gop (splitmux, ctx);
+          }
         } else if (splitmux->input_state ==
             SPLITMUX_INPUT_STATE_WAITING_GOP_COLLECT) {
           /* If we are waiting for a GOP to be completed (ie, for aux
            * pads to catch up), then this pad is complete, so check
            * if the whole GOP is.
            */
-          check_completed_gop (splitmux, ctx);
+          if (!g_queue_is_empty (&splitmux->pending_input_gops))
+            check_completed_gop (splitmux, ctx);
         }
         GST_SPLITMUX_UNLOCK (splitmux);
         break;
@@ -3178,6 +3198,15 @@ handle_mq_input (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
 
         GST_LOG_OBJECT (pad,
             "Collected last packet of GOP. Checking other pads");
+
+        if (g_queue_is_empty (&splitmux->pending_input_gops)) {
+          GST_WARNING_OBJECT (pad,
+              "Reference was closed without GOP, dropping");
+          GST_SPLITMUX_UNLOCK (splitmux);
+          GST_PAD_PROBE_INFO_FLOW_RETURN (info) = GST_FLOW_EOS;
+          return GST_PAD_PROBE_DROP;
+        }
+
         check_completed_gop (splitmux, ctx);
         break;
       }
index 93c86e7..dba0c0a 100644 (file)
@@ -663,6 +663,55 @@ GST_START_TEST (test_splitmuxsink_muxer_pad_map)
 
 GST_END_TEST;
 
+static void
+run_eos_pipeline (guint num_video_buf, guint num_audio_buf,
+    gboolean configure_audio)
+{
+  GstMessage *msg;
+  GstElement *pipeline;
+  gchar *dest_pattern;
+  gchar *pipeline_str;
+  gchar *audio_branch = NULL;
+
+  dest_pattern = g_build_filename (tmpdir, "out%05d.mp4", NULL);
+
+  if (configure_audio) {
+    audio_branch = g_strdup_printf ("audiotestsrc num-buffers=%d ! "
+        "splitsink.audio_0", num_audio_buf);
+  }
+
+  pipeline_str = g_strdup_printf ("splitmuxsink name=splitsink location=%s "
+      "muxer-factory=qtmux videotestsrc num-buffers=%d ! jpegenc ! splitsink. "
+      "%s", dest_pattern, num_video_buf, audio_branch ? audio_branch : "");
+  pipeline = gst_parse_launch (pipeline_str, NULL);
+  g_free (dest_pattern);
+  g_free (audio_branch);
+  g_free (pipeline_str);
+
+  fail_if (pipeline == NULL);
+
+  msg = run_pipeline (pipeline);
+
+  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR)
+    dump_error (msg);
+  fail_unless (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS);
+  gst_message_unref (msg);
+
+  gst_object_unref (pipeline);
+}
+
+GST_START_TEST (test_splitmuxsink_eos_without_buffer)
+{
+  /* below pipelines will create non-playable files but at least we should not
+   * crash */
+  run_eos_pipeline (0, 0, FALSE);
+  run_eos_pipeline (0, 0, TRUE);
+  run_eos_pipeline (1, 0, TRUE);
+  run_eos_pipeline (0, 1, TRUE);
+}
+
+GST_END_TEST;
+
 static GstPadProbeReturn
 count_upstrea_fku (GstPad * pad, GstPadProbeInfo * info,
     guint * upstream_fku_count)
@@ -863,6 +912,7 @@ splitmuxsink_suite (void)
     tcase_add_checked_fixture (tc_chain_mp4_jpeg, tempdir_setup,
         tempdir_cleanup);
     tcase_add_test (tc_chain_mp4_jpeg, test_splitmuxsink_muxer_pad_map);
+    tcase_add_test (tc_chain_mp4_jpeg, test_splitmuxsink_eos_without_buffer);
   } else {
     GST_INFO ("Skipping tests, missing plugins: jpegenc or mp4mux");
   }