streamsynchronizer: Delay EOS events until all streams are EOS
authorSebastian Dröge <sebastian.droege@collabora.co.uk>
Sat, 24 Jul 2010 16:17:43 +0000 (18:17 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Sat, 24 Jul 2010 16:17:43 +0000 (18:17 +0200)
This fixes a race condition in playbin2's gapless mode, where the
EOS of other streams might arrive in the sinks before the last stream
ends and the switch to the new track happens. The EOS sinks won't
accept any new data then and playback stops.

To prevent this, delay all EOS events until all streams are EOS
and advance the sinks of the EOS streams by filler newsegment
events if necessary.

Fixes bug #625118.

gst/playback/gststreamsynchronizer.c

index d4b8abe..78bf00d 100644 (file)
@@ -66,6 +66,7 @@ typedef struct
   gboolean wait;
   gboolean new_stream;
   gboolean drop_discont;
+  gboolean is_eos;
 
   gint64 running_time_diff;
 } GstStream;
@@ -447,6 +448,40 @@ gst_stream_synchronizer_sink_event (GstPad * pad, GstEvent * event)
       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
       break;
     }
+    case GST_EVENT_EOS:{
+      GstStream *stream;
+      GList *l;
+      gboolean all_eos = TRUE;
+
+      GST_STREAM_SYNCHRONIZER_LOCK (self);
+      stream = gst_pad_get_element_private (pad);
+      if (stream) {
+        GST_DEBUG_OBJECT (pad, "Have EOS for stream %d", stream->stream_number);
+        stream->is_eos = TRUE;
+      }
+
+      for (l = self->streams; l; l = l->next) {
+        GstStream *ostream = l->data;
+
+        all_eos = all_eos && ostream->is_eos;
+        if (!all_eos)
+          break;
+      }
+
+      if (all_eos) {
+        ret = TRUE;
+        GST_DEBUG_OBJECT (self, "All streams are EOS -- forwarding");
+        for (l = self->streams; l; l = l->next) {
+          GstStream *ostream = l->data;
+          ret = ret
+              && gst_pad_push_event (ostream->srcpad, gst_event_new_eos ());
+        }
+      }
+      GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+
+      goto done;
+      break;
+    }
     default:
       break;
   }
@@ -533,6 +568,8 @@ gst_stream_synchronizer_sink_chain (GstPad * pad, GstBuffer * buffer)
 
   GST_LOG_OBJECT (pad, "Push returned: %s", gst_flow_get_name (ret));
   if (ret == GST_FLOW_OK) {
+    GList *l;
+
     GST_STREAM_SYNCHRONIZER_LOCK (self);
     stream = gst_pad_get_element_private (pad);
     if (stream && stream->segment.format == GST_FORMAT_TIME
@@ -544,6 +581,43 @@ gst_stream_synchronizer_sink_chain (GstPad * pad, GstBuffer * buffer)
       gst_segment_set_last_stop (&stream->segment, GST_FORMAT_TIME,
           timestamp_end);
     }
+
+    /* Advance EOS streams if necessary. For non-EOS
+     * streams the demuxers should already do this! */
+    for (l = self->streams; l; l = l->next) {
+      GstStream *ostream = l->data;
+      gint64 last_stop;
+
+      if (!ostream->is_eos || ostream->segment.format != GST_FORMAT_TIME)
+        continue;
+
+      if (ostream->segment.last_stop != -1)
+        last_stop = ostream->segment.last_stop;
+      else
+        last_stop = ostream->segment.start;
+
+      /* Is there a 1 second lag? */
+      if (last_stop != -1 && last_stop + GST_SECOND < timestamp_end) {
+        gint64 new_start;
+
+        new_start = timestamp_end - GST_SECOND;
+
+        GST_DEBUG_OBJECT (ostream->sinkpad,
+            "Advancing stream %d from %" GST_TIME_FORMAT " to %"
+            GST_TIME_FORMAT, ostream->stream_number, last_stop, new_start);
+
+        gst_pad_push_event (ostream->srcpad,
+            gst_event_new_new_segment_full (TRUE, ostream->segment.rate,
+                ostream->segment.applied_rate, ostream->segment.format,
+                new_start, ostream->segment.stop, new_start));
+        gst_segment_set_newsegment_full (&ostream->segment, TRUE,
+            ostream->segment.rate, ostream->segment.applied_rate,
+            ostream->segment.format, new_start, ostream->segment.stop,
+            new_start);
+        gst_segment_set_last_stop (&ostream->segment, GST_FORMAT_TIME,
+            new_start);
+      }
+    }
     GST_STREAM_SYNCHRONIZER_UNLOCK (self);
   }
 
@@ -666,6 +740,15 @@ gst_stream_synchronizer_release_stream (GstStreamSynchronizer * self,
   }
 
   g_slice_free (GstStream, stream);
+
+  /* NOTE: In theory we have to check here if all streams
+   * are EOS but the one that was removed wasn't and then
+   * send EOS downstream. But due to the way how playsink
+   * works this is not necessary and will only cause problems
+   * for gapless playback. playsink will only add/remove pads
+   * when it's reconfigured, which happens when the streams
+   * change
+   */
 }
 
 static void
@@ -742,6 +825,7 @@ gst_stream_synchronizer_change_state (GstElement * element,
         stream->wait = FALSE;
         stream->new_stream = FALSE;
         stream->drop_discont = FALSE;
+        stream->is_eos = FALSE;
       }
       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
       break;