Splitmuxsink pipeline generating an EOS with udpsrc, but not with a fully local pipeline

Problem: splitmuxsink functions correctly with a local pipeline, but not in a similar two-pipeline setup split by udpsink & udpsrc.


Hello all, I’m prototyping a video archiving pipeline using this as my starting point.

GStreamer 1.28.2 and gst-plugin-rs tag gstreamer-1.29.2.

The fully local (no network) pipeline works exactly like how I want it to: it produces 10 second fragmented MP4 video files with 2 seconds per fragments.

$ gst-launch-1.0 videotestsrc pattern=0 is-live=true ! video/x-raw,width=3840,height=2160,framerate=25/1 \
! timeoverlay \
! x265enc tune=zerolatency key-int-max=25 \
! video/x-h265,profile=main \
! h265parse \
! splitmuxsink location=test4-%02d.mp4 max-size-time=10000000000 send-keyframe-requests=true async-finalize=true muxer-factory=isofmp4mux muxer-properties="properties,fragment-duration=2000000000,offset-to-zero=true,header-update-mode=1,write-mehd=true,write-mfra=true"

$ gst-discoverer-1.0 test4-02.mp4 
Analyzing file:///home/adrian/test-gstreamer/test4-02.mp4
Done discovering file:///home/adrian/test-gstreamer/test4-02.mp4

Properties:
  Duration: 0:00:10.000000000
  Seekable: yes
  Live: no
  container #0: Quicktime
    video #1: H.265 (Main Profile)
      Stream ID: f73f66652eef94e6053e0dcb163cae67a3194cc98210fcd8a66dc6fd458136cd/001
      Width: 3840
      Height: 2160
      Depth: 24
      Frame rate: 25/1
      Pixel aspect ratio: 1/1
      Interlaced: false
      Bitrate: 2095233
      Max bitrate: 2097152

So my natural next step is to split the above into two pipelines, and add a network in between:

Sender

$ gst-launch-1.0 videotestsrc pattern=0 is-live=true ! video/x-raw,width=3840,height=2160 \
! timeoverlay \
! x265enc tune=zerolatency key-int-max=25 \
! video/x-h265,profile=main \
! rtph265pay pt=96 config-interval=-1 \
! udpsink host=224.0.2.1 port=5050

And the receiver, which is giving me EOS:

$ gst-launch-1.0 udpsrc -v address=224.0.2.1 port=5050 \
! application/x-rtp,media=video,clock-rate=90000,encoding-name=H265 \
! rtpjitterbuffer \
! rtph265depay \
! h265parse \
! splitmuxsink location=testrx-%02d.mp4 max-size-time=10000000000 send-keyframe-requests=true async-finalize=true muxer-factory=isofmp4mux muxer-properties="properties,fragment-duration=2000000000,offset-to-zero=true,header-update-mode=1,write-mehd=true,write-mfra=true"
Setting pipeline to PAUSED ...
Pipeline is live and does not need PREROLL ...
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstFileSink:sink: async = false
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
/GstPipeline:pipeline0/GstCapsFilter:capsfilter0.GstPad:src: caps = application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265
/GstPipeline:pipeline0/GstRtpJitterBuffer:rtpjitterbuffer0.GstPad:sink: caps = application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265
/GstPipeline:pipeline0/GstRtpJitterBuffer:rtpjitterbuffer0.GstPad:src: caps = application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265
/GstPipeline:pipeline0/GstRtpH265Depay:rtph265depay0: extensions = <  >
/GstPipeline:pipeline0/GstRtpH265Depay:rtph265depay0.GstPad:sink: caps = application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265
/GstPipeline:pipeline0/GstRtpH265Depay:rtph265depay0.GstPad:src: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5
/GstPipeline:pipeline0/GstH265Parse:h265parse0.GstPad:sink: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5
Redistribute latency...
/GstPipeline:pipeline0/GstH265Parse:h265parse0.GstPad:src: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0.GstGhostPad:video.GstProxyPad:proxypad0: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video.GstPad:sink: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0.GstGhostPad:video: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video.GstPad:src: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstISOFMP4Mux:muxer.GstFMP4MuxPad:sink_0: caps = video/x-h265, stream-format=(string)hvc1, alignment=(string)au, codec_data=(buffer)01016000000090000000000096f000fcfdf8f800000f03200001001840010c01ffff0160000003009000000300000300969280902100010031420101016000000300900000030000030096a001e020021c5964a924cafff000100016a020202080000003008000000f0422000100074401c172b46240, profile=(string)main, tier=(string)main, level=(string)5, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709, parsed=(boolean)true, pixel-aspect-ratio=(fraction)1/1, lcevc=(boolean)false
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstFileSink:sink: location = testrx-00.mp4
Redistribute latency...
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 6
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 7
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 8
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 9
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 10
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 11
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 12
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 13
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 14
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 15
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 16
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 17
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 18
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 19
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 20
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 21
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 22
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 23
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 24
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 25
/GstPipeline:pipeline0/GstSplitMuxSink:splitmuxsink0/GstQueue:queue_video: max-size-buffers = 26
Redistribute latency...
Got EOS from element "pipeline0".
Execution ended after 0:00:02.180310029
Setting pipeline to NULL ...
Freeing pipeline ...

My suspicion is that the pipeline is overwhelming splitmuxsink’s internal buffer queues. I would gladly learn about anything I’ve missed or done wrong.

Is this something that can be addressed with a proper C/C++ implementation?

Not sure, but it may be because frames received over RTP may not have DTS. Check with:

GST_DEBUG=identity:7 gst-launch-1.0   udpsrc address=224.0.2.1 port=5050 ! application/x-rtp,encoding-name=H265 ! rtpjitterbuffer ! rtph265depay ! h265parse disable-passthrough=true ! identity silent=false dump=false ! fakesink

GST_DEBUG=1,identity:7 gst-launch-1.0 udpsrc address=224.0.2.1 port=5050 ! application/x-rtp,encoding-name=H265 ! rtpjitterbuffer ! rtph265depay ! h265parse ! identity silent=false ! isofmp4mux ! fakesink


Seems that in isofmp4mux subprojects/gst-plugins-rs/mux/isobmff/src/isobmff/fmp4mux/imp.rs:

`if buffer.dts().is_none() && delta_frames.requires_dts() {
    // For gap buffer simply set the missing DTS to PTS.
    if buffer.flags().contains(gst::BufferFlags::GAP)
        && buffer.flags().contains(gst::BufferFlags::DROPPABLE)
        && buffer.size() == 0
    {
        let pts = buffer.pts();
        buffer.make_mut().set_dts(pts);
        return Ok(());
    } else {
        gst::error!(CAT, obj = sinkpad, "Require DTS for video streams");
        return Err(gst::FlowError::Error);
    }
}`

you may end in last case. Someone more skilled would better help or comment.

splitmuxsink and isofmp4mux are not always compatible. You should not use splitmuxsink in this case and instead write an application tracking the relevant buffer flags manually. Some examples of this are available from: mux/isobmff/examples · main · GStreamer / gst-plugins-rs · GitLab

I’ve found a pipeline that (appears to) solve the DTS issues pointed out by @Honey_Patouceul. The solution is to add h265timestamper between h265parse and splitmuxsink in the receiver pipeline.