Gstreamer use python gi library save video but can't play

Hi,I tried use gstreamer command run
realtime gmsl video and 1 minute save video at same time,
Use theese code at command line is work:

gst-launch-1.0 \
    nvstreammux width=1920 height=1080 batch-size=1 name=mux ! \
    nvmultistreamtiler rows=2 columns=2 width=1920 height=1080 name=tiler ! \
    nvvideoconvert ! \
    tee name=t ! \
    queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! \
    nveglglessink sync=false \
    t. ! \
    queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! \
    x264enc bitrate=5000 tune=zerolatency ! \
    splitmuxsink location="output_video_$(date +%Y%m%d_%H%M%S)_%09d.mp4" max-size-time=60000000000 \
    v4l2src device=/dev/video0 ! \
    nvvideoconvert ! mux.sink_0 \
    v4l2src device=/dev/video1 ! \
    nvvideoconvert ! mux.sink_1 \
    v4l2src device=/dev/video2 ! \
    nvvideoconvert ! mux.sink_2 \
    v4l2src device=/dev/video3 ! \
    nvvideoconvert ! mux.sink_3

But can’t update date time,
So I tried use python GObject Introspection:

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
import time
from datetime import datetime, timedelta
import sys
import signal

class GstreamerRecorder:
    def __init__(self):
        Gst.init(None)
        self.mainloop = GLib.MainLoop()
        self.increment = 60  # 秒
        self.index = 0
        self.initial_time = 0
        self.pipeline = None

    def create_pipeline(self, filename):
        # 創建 pipeline
        self.pipeline = Gst.Pipeline.new('video-pipeline')

        # 創建元素
        streammux = Gst.ElementFactory.make('nvstreammux', 'mux')
        tiler = Gst.ElementFactory.make('nvmultistreamtiler', 'tiler')
        converter = Gst.ElementFactory.make('nvvideoconvert', 'converter')
        tee = Gst.ElementFactory.make('tee', 'tee')
        queue1 = Gst.ElementFactory.make('queue', 'queue1')
        queue2 = Gst.ElementFactory.make('queue', 'queue2')
        sink = Gst.ElementFactory.make('nveglglessink', 'sink')
        encoder = Gst.ElementFactory.make('x264enc', 'encoder')
        filesink = Gst.ElementFactory.make('splitmuxsink', 'filesink')

        # 創建源和轉換器
        sources = []
        converters = []
        for i in range(4):
            src = Gst.ElementFactory.make('v4l2src', f'src{i}')
            conv = Gst.ElementFactory.make('nvvideoconvert', f'conv{i}')
            src.set_property('device', f'/dev/video{i}')
            sources.append(src)
            converters.append(conv)

        # 設置元素屬性
        streammux.set_property('width', 1920)
        streammux.set_property('height', 1080)
        streammux.set_property('batch-size', 1)

        tiler.set_property('rows', 2)
        tiler.set_property('columns', 2)
        tiler.set_property('width', 1920)
        tiler.set_property('height', 1080)

        queue1.set_property('max-size-buffers', 0)
        queue1.set_property('max-size-time', 0)
        queue1.set_property('max-size-bytes', 0)

        queue2.set_property('max-size-buffers', 0)
        queue2.set_property('max-size-time', 0)
        queue2.set_property('max-size-bytes', 0)

        sink.set_property('sync', False)
        encoder.set_property('bitrate', 5000)
        encoder.set_property('key-int-max', 30)  # 每秒一個關鍵幀
        encoder.set_property('bframes', 0)  # 禁用 B 幀以提高相容性
        encoder.set_property('tune', 'zerolatency')  # 降低延遲
        filesink.set_property('location', filename)
        filesink.set_property('max-size-time', 60000000000)

        # 添加元素到 pipeline
        elements = [streammux, tiler, converter, tee, queue1, queue2, 
                   sink, encoder, filesink] + sources + converters
        for element in elements:
            self.pipeline.add(element)

        # 連接元素
        # 連接源到轉換器,再到 streammux
        for i in range(4):
            sources[i].link(converters[i])
            sink_pad = streammux.get_request_pad(f'sink_{i}')
            src_pad = converters[i].get_static_pad('src')
            src_pad.link(sink_pad)

        # 連接主要 pipeline
        streammux.link(tiler)
        tiler.link(converter)
        converter.link(tee)

        # 連接 tee 的第一個分支到顯示
        tee_video_pad = tee.get_request_pad('src_0')
        queue1_pad = queue1.get_static_pad('sink')
        tee_video_pad.link(queue1_pad)
        queue1.link(sink)

        # 連接 tee 的第二個分支到檔案
        tee_file_pad = tee.get_request_pad('src_1')
        queue2_pad = queue2.get_static_pad('sink')
        tee_file_pad.link(queue2_pad)
        queue2.link(encoder)
        encoder.link(filesink)

        return self.pipeline

    def on_bus_message(self, bus, message, loop):
        t = message.type
        if t == Gst.MessageType.EOS:
            print("Pipeline 結束")
            loop.quit()
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print(f"Error: {err}, {debug}")
            loop.quit()
        return True

    def start_recording(self):
        try:
            while True:
                # 計算下一個檔案的時間戳
                next_time = datetime.now() + timedelta(seconds=self.initial_time)
                timestamp = next_time.strftime("%Y%m%d_%H%M%S")
                filename = f"output_video_{timestamp}_{self.index}.mp4"
                print(f"開始錄製: {filename}")

                # 創建新的 pipeline
                if self.pipeline:
                    self.pipeline.set_state(Gst.State.NULL)
                self.pipeline = self.create_pipeline(filename)
                
                # 設置 bus 監聽
                bus = self.pipeline.get_bus()
                bus.add_signal_watch()
                bus.connect('message', self.on_bus_message, self.mainloop)

                # 啟動 pipeline
                self.pipeline.set_state(Gst.State.PLAYING)

                # 等待指定時間
                time.sleep(self.increment)

                # 更新計數器和時間
                self.initial_time += self.increment
                self.index += 1

        except KeyboardInterrupt:
            print("\n程式停止")
            if self.pipeline:
                self.pipeline.set_state(Gst.State.NULL)
            sys.exit(0)

    def stop_recording(self, signum, frame):
        print("\n接收到停止信號")
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)
        sys.exit(0)

def main():
    recorder = GstreamerRecorder()
    # 註冊信號處理
    signal.signal(signal.SIGINT, recorder.stop_recording)
    signal.signal(signal.SIGTERM, recorder.stop_recording)
    
    # 開始錄製
    recorder.start_recording()

if __name__ == "__main__":
    main()

The video really save to local and split 1 minute to file,
But can’t play on vlc or other media player,
How can I fix it?
Thanks!!!

Could you try to add the h264parse plugin after the x264enc plugin?

I tried to link h264parser,but still can’t play.
and use link mp4mux the display and save video will be stop work.
How can I modify ?
Thanks!!!

class GstreamerRecorder:
    def __init__(self,find_cameras,config_camera_port_list):
        Gst.init(None)
        self.mainloop = GLib.MainLoop()
        self.config_camera_port_list=config_camera_port_list
        self.find_cameras=find_cameras
        self.increment = 60  # 秒
        self.index = 0
        self.initial_time = 0
        self.pipeline = None

    def create_pipeline(self, filename):
        # 創建 pipeline
        self.pipeline = Gst.Pipeline.new('video-pipeline')

        # 創建元素
        streammux = Gst.ElementFactory.make('nvstreammux', 'mux')
        tiler = Gst.ElementFactory.make('nvmultistreamtiler', 'tiler')
        converter = Gst.ElementFactory.make('nvvideoconvert', 'converter')
        tee = Gst.ElementFactory.make('tee', 'tee')
        queue1 = Gst.ElementFactory.make('queue', 'queue1')
        queue2 = Gst.ElementFactory.make('queue', 'queue2')
        sink = Gst.ElementFactory.make('nveglglessink', 'sink')
        encoder = Gst.ElementFactory.make('x264enc', 'encoder')
        h264parser = Gst.ElementFactory.make('h264parse', 'h264parser')  # Add h264parse element
        filesink = Gst.ElementFactory.make('splitmuxsink', 'filesink')
        mp4mux = Gst.ElementFactory.make("mp4mux", "mp4-muxer")
        # 創建源和轉換器
        sources = []
        converters = []
        for i, camera in enumerate(self.find_cameras.values()):
            if camera == "black":
                # Add a black screen when the camera is not found
                src = Gst.ElementFactory.make('videotestsrc', f'src{i}')
                src.set_property('pattern', 6)  # 6 = blue screen pattern
            else:
                src = Gst.ElementFactory.make('v4l2src', f'src{i}')
                src.set_property('device', camera)
            
            conv = Gst.ElementFactory.make('nvvideoconvert', f'conv{i}')
            sources.append(src)
            converters.append(conv)

        # 設置元素屬性
        config_camera_quantity=len(self.config_camera_port_list)
        streammux.set_property('width', 1920)
        streammux.set_property('height', 1080)
        
        # 依config camera數量動態調整顯示畫面格數
        rows, columns = 0, 0
        if config_camera_quantity == 1:
            rows, columns = 1, 1
        elif config_camera_quantity <= 2:
            rows, columns = 1, 2
        elif config_camera_quantity <= 4:
            rows, columns = 2, 2
        elif config_camera_quantity <= 6:
            rows, columns = 2, 3
        elif config_camera_quantity <= 8:
            rows, columns = 2, 4
        batch_size=rows*columns
        streammux.set_property('batch-size',batch_size)  # Adjust batch size based on number of sources
        tiler.set_property('rows', rows)
        tiler.set_property('columns', columns)
            
        tiler.set_property('width', 1920)
        tiler.set_property('height', 1080)

        queue1.set_property('max-size-buffers', 0)
        queue1.set_property('max-size-time', 0)
        queue1.set_property('max-size-bytes', 0)

        queue2.set_property('max-size-buffers', 0)
        queue2.set_property('max-size-time', 0)
        queue2.set_property('max-size-bytes', 0)

        sink.set_property('sync', False)
        encoder.set_property('bitrate', 5000)
        encoder.set_property('key-int-max', 30)  # 每秒一個關鍵幀
        encoder.set_property('bframes', 0)  # 禁用 B 幀以提高相容性
        encoder.set_property('tune', 'zerolatency')  # 降低延遲
        h264parser.set_property("disable-passthrough", True)
        filesink.set_property('location', filename)
        filesink.set_property('max-size-time', 60000000000)
        mp4mux.set_property("fragment-duration", 1000)  # 每秒分段
        mp4mux.set_property("streamable", True)         # 支援即時播放
        mp4mux.set_property("faststart", True)          # 提升點播速度
        # 添加元素到 pipeline
        elements = [streammux, tiler, converter, tee, queue1, queue2, 
                    sink, encoder,h264parser,mp4mux ,filesink] + sources + converters
        for element in elements:
            self.pipeline.add(element)

        # 連接源到轉換器,再到 streammux
        for i, find_camera in enumerate(self.find_cameras):
            sources[i].link(converters[i])
            sink_pad = streammux.get_request_pad(f'sink_{i}')
            src_pad = converters[i].get_static_pad('src')
            src_pad.link(sink_pad)

        # 連接主要 pipeline
        streammux.link(tiler)
        tiler.link(converter)
        converter.link(tee)

        # 連接 tee 的第一個分支到顯示
        tee_video_pad = tee.get_request_pad('src_0')
        queue1_pad = queue1.get_static_pad('sink')
        tee_video_pad.link(queue1_pad)
        queue1.link(sink)

        # 連接 tee 的第二個分支到檔案
        tee_file_pad = tee.get_request_pad('src_1')
        queue2_pad = queue2.get_static_pad('sink')
        tee_file_pad.link(queue2_pad)
        queue2.link(encoder)
        encoder.link(h264parser)
        h264parser.link(filesink)

        return self.pipeline

    def on_bus_message(self, bus, message, loop):
        t = message.type
        if t == Gst.MessageType.EOS:
            print("Pipeline 結束")
            loop.quit()
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print(f"Error: {err}, {debug}")
            loop.quit()
        return True
    
    def start_recording(self):
        try:
            while True:
                # 計算下一個檔案的時間戳
                next_time = datetime.now() + timedelta(seconds=self.initial_time)
                timestamp = next_time.strftime("%Y%m%d_%H%M%S")
                filename = f"output_video_{timestamp}_{self.index}.mp4"
                print(f"開始錄製: {filename}")

                # 創建新的 pipeline
                if self.pipeline:
                    self.pipeline.set_state(Gst.State.NULL)
                self.pipeline = self.create_pipeline(filename)
                
                # 設置 bus 監聽
                bus = self.pipeline.get_bus()
                bus.add_signal_watch()
                bus.connect('message', self.on_bus_message, self.mainloop)

                # 啟動 pipeline
                self.pipeline.set_state(Gst.State.PLAYING)

                # 等待指定時間
                time.sleep(self.increment)

                # 更新計數器和時間
                self.initial_time += self.increment
                self.index += 1

        except KeyboardInterrupt:
            print("\n程式停止")
            if self.pipeline:
                self.pipeline.set_state(Gst.State.NULL)
            sys.exit(0)

    def stop_recording(self, signum, frame):
        print("\n接收到停止信號")
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)
        sys.exit(0)

Since you’re using x264enc encoder, can you try to play it with ffmpeg?

Looks not element link problem,
I try use same element

        streammux = Gst.ElementFactory.make('nvstreammux', 'mux')
        tiler = Gst.ElementFactory.make('nvmultistreamtiler', 'tiler')
        converter = Gst.ElementFactory.make('nvvideoconvert', 'converter')
        tee = Gst.ElementFactory.make('tee', 'tee')
        queue1 = Gst.ElementFactory.make('queue', 'queue1')
        queue2 = Gst.ElementFactory.make('queue', 'queue2')
        sink = Gst.ElementFactory.make('nveglglessink', 'sink')
        encoder = Gst.ElementFactory.make('x264enc', 'encoder')
        
        # 設置 splitmuxsink 使用時間自動分割檔案
        filesink = Gst.ElementFactory.make('splitmuxsink', 'filesink')
    

And up the queue buffer to fix this problem:

queue1.set_property('max-size-buffers', 4)
queue2.set_property('max-size-buffers', 4)

I guess the video not normal save,
So can’t play.