splitmuxsink: Fix occasional deadlock when ending file with subtitle
authorXavier Claessens <xavier.claessens@collabora.com>
Fri, 18 Mar 2016 19:45:01 +0000 (15:45 -0400)
committerJan Schmidt <jan@centricular.com>
Thu, 31 Mar 2016 13:48:05 +0000 (00:48 +1100)
Deadlock occurs when splitting files if one stream received no buffer during
the first GOP of the next file. That can happen in that scenario for example:
 1) The first GOP of video is collected, it has a duration of 10s.
    max_in_running_time is set to 10s.
 2) Other streams catchup and we receive the first subtitle buffer at ts=0 and
    has a duration of 1min.
 3) We receive the 2nd subtitle buffer with a ts=1min. in_running_time is set to
    1min. That buffer is blocked in handle_mq_input() because
    max_in_running_time is still 10s.
 4) Since all in_running_time are now > 10s, max_out_running_time is now set to
    10s. That first GOP gets recorded into the file. The muxer pop buffers out
    of the mq, when it tries to pop a 2nd subtitle buffer it blocks because the
    GstDataQueue is empty.
 5) A 2nd GOP of video is collected and has a duration of 10s as well.
    max_in_running_time is now 20s. Since subtitle's in_running_time is already
    1min, that GOP is already complete.
 6) But let's say we overran the max file size, we thus set state to
    SPLITMUX_STATE_ENDING_FILE now. As soon as a buffer with ts > 10s (end of
    previous GOP) arrives in handle_mq_output(), EOS event is sent downstream
    instead. But since the subtitle queue is empty, that's never going to
    happen. Pipeline is now deadlocked.

To fix this situation we have to:
 - Send a dummy event through the queue to wakeup output thread.
 - Update out_running_time to at least max_out_running_time so it sends EOS.
 - Respect time order, so we set out_running_tim=max_in_running_time because
   that's bigger than previous buffer and smaller than next.

https://bugzilla.gnome.org/show_bug.cgi?id=763711

gst/multifile/gstsplitmuxsink.c

index 205a910..10096be 100644 (file)
@@ -627,6 +627,25 @@ handle_mq_output (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
         GST_SPLITMUX_UNLOCK (splitmux);
         break;
       }
+      case GST_EVENT_CUSTOM_DOWNSTREAM:{
+        const GstStructure *s;
+        GstClockTime ts = 0;
+
+        s = gst_event_get_structure (event);
+        if (!gst_structure_has_name (s, "splitmuxsink-unblock"))
+          break;
+
+        gst_structure_get_uint64 (s, "timestamp", &ts);
+
+        GST_SPLITMUX_LOCK (splitmux);
+
+        if (splitmux->state == SPLITMUX_STATE_STOPPED)
+          goto beach;
+        ctx->out_running_time = ts;
+        complete_or_wait_on_out (splitmux, ctx);
+        GST_SPLITMUX_UNLOCK (splitmux);
+        return GST_PAD_PROBE_DROP;
+      }
       default:
         break;
     }
@@ -1125,7 +1144,23 @@ handle_mq_input (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
             "Collected last packet of GOP. Checking other pads");
         check_completed_gop (splitmux, ctx);
         break;
-      case SPLITMUX_STATE_ENDING_FILE:
+      case SPLITMUX_STATE_ENDING_FILE:{
+        GstEvent *event;
+
+        /* If somes streams received no buffer during the last GOP that overran,
+         * because its next buffer has a timestamp bigger than
+         * ctx->max_in_running_time, its queue is empty. In that case the only
+         * way to wakeup the output thread is by injecting an event in the
+         * queue. This usually happen with subtitle streams.
+         * See https://bugzilla.gnome.org/show_bug.cgi?id=763711. */
+        GST_LOG_OBJECT (pad, "Sending splitmuxsink-unblock event");
+        event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM |
+            GST_EVENT_TYPE_SERIALIZED,
+            gst_structure_new ("splitmuxsink-unblock", "timestamp",
+                G_TYPE_UINT64, splitmux->max_in_running_time, NULL));
+        gst_pad_send_event (ctx->sinkpad, event);
+        /* fallthrough */
+      }
       case SPLITMUX_STATE_START_NEXT_FRAGMENT:
         /* A fragment is ending, wait until that's done before continuing */
         GST_DEBUG_OBJECT (pad, "Sleeping for fragment restart");