You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于Python GStreamer的RTSP多客户端接入与流流畅性问题咨询

基于Python GStreamer的RTSP多客户端接入与流流畅性问题咨询

我现在在用Python结合GStreamer实现一个RTSP推流服务,核心是用OpenCV读取本地视频文件,再通过appsrc把帧推成RTSP流。我参考了一个示例代码并做了修改,目前单客户端(比如一个VLC实例)能正常播放,但遇到了两个棘手问题,想请教下大家:


问题1:无法支持多客户端接入

现在当第一个VLC客户端连接后播放正常,但第二个VLC实例再连接的话就完全无法播放,甚至会导致整个服务出现异常。我已经设置了self.factory.set_shared(True),但好像没起作用,不知道哪里出了问题。

问题2:流播放存在卡顿现象

推流过程中会时不时出现冻结,比如播放到第38帧时卡住,然后突然跳到250帧继续播放,流畅性很差。我试过调整appsrcis-liveblock参数,但没有明显改善。另外我对比过用multifilesrc实现的同类型服务,那个既能支持多客户端又流畅,但我不知道怎么把它的特性迁移到我用appsrc的代码里。


我的实现代码

import gi
import cv2
import argparse

# 导入GStreamer相关库
gi.require_version('Gst', '1.0')
gi.require_version('GstRtspServer', '1.0')
from gi.repository import Gst, GstRtspServer, GObject, GLib

# 默认配置
IMAGE_WIDTH = 384
IMAGE_HEIGHT = 288
FPS = 25
INPUT_SOURCE = '/path/to/my/video/file.mp4'

# 自定义RTSP媒体工厂类
class SensorFactory(GstRtspServer.RTSPMediaFactory):
    def __init__(self, args, **properties):
        super(SensorFactory, self).__init__(**properties)
        self.cap = cv2.VideoCapture(args.input_source)
        self.number_frames = 0
        self.fps = args.fps
        self.duration = 1 / self.fps * Gst.SECOND  # 单帧时长(纳秒)
        # GStreamer管道启动字符串
        self.launch_string = 'appsrc name=source is-live=true block=true format=GST_FORMAT_TIME caps=video/x-raw,format=BGR,width={},height={},framerate={}/1 ' \
                             '! videoconvert ! video/x-raw,format=I420 ! x264enc speed-preset=ultrafast tune=zerolatency ! rtph264pay config-interval=1 name=pay0 pt=96' \
                             .format(args.image_width, args.image_height, self.fps)
        self.args = args
        self.frame_id = 0

    # 处理appsrc的need-data信号,推送帧到管道
    def on_need_data(self, src, length):
        if self.cap.isOpened():
            ret, frame = self.cap.read()
            self.frame_id += 1
            if ret:
                # 调整帧尺寸
                frame = cv2.resize(frame, (self.args.image_width, self.args.image_height), interpolation = cv2.INTER_LINEAR)
                # 添加帧计数器
                cv2.putText(frame, str(self.frame_id), (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2)
                # 转换为字节流
                data = frame.tobytes()
                # 创建Gst Buffer
                buf = Gst.Buffer.new_allocate(None, len(data), None)
                buf.fill(0, data)
                buf.duration = self.duration
                timestamp = self.number_frames * self.duration
                buf.pts = buf.dts = int(timestamp)
                buf.offset = timestamp
                self.number_frames += 1
                # 推送Buffer
                retval = src.emit('push-buffer', buf)
                print('已推送帧: {}, 单帧时长: {}ns, 单帧时长: {}s'.format(self.number_frames, self.duration, self.duration / Gst.SECOND))
                if retval != Gst.FlowReturn.OK:
                    print(retval)

    # 重写创建管道元素的方法
    def do_create_element(self, url):
        return Gst.parse_launch(self.launch_string)
    
    # 重写配置媒体的方法
    def do_configure(self, rtsp_media):
        self.number_frames = 0
        appsrc = rtsp_media.get_element().get_child_by_name('source')
        appsrc.connect('need-data', self.on_need_data)

# RTSP服务器类
class GstServer(GstRtspServer.RTSPServer):
    def __init__(self, args, **properties):
        super(GstServer, self).__init__(**properties)
        self.factory = SensorFactory(args)
        self.factory.set_shared(True)  # 已设置为共享工厂
        self.set_service(str(args.port))
        self.get_mount_points().add_factory(args.stream_uri, self.factory)
        self.attach(None)

# 解析命令行参数
def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input_source", default=INPUT_SOURCE, help="视频设备ID或本地视频文件路径")
    parser.add_argument("--fps", default=FPS, help="推流帧率", type=int)
    parser.add_argument("--image_width", default=IMAGE_WIDTH, help="视频帧宽度", type=int)
    parser.add_argument("--image_height", default=IMAGE_HEIGHT, help="视频帧高度", type=int)
    parser.add_argument("--port", default=8554, help="RTSP服务端口", type=int)
    parser.add_argument("--stream_uri", default = "/video_stream", help="RTSP流路径")
    args = parser.parse_args()
    return args

# 主函数
def main():
    args = get_args()
    # 如果输入是数字,视为视频设备ID
    if args.input_source.isdigit():
        args.input_source = int(args.input_source)

    print('RTSP流地址: rtsp://127.0.0.1:{}{}'.format(args.port, args.stream_uri))

    # 初始化GStreamer
    Gst.init(None)
    server = GstServer(args)
    loop = GLib.MainLoop()
    loop.run()

if __name__ == '__main__':
    main()

我已经做过的尝试

  • 调整了appsrcis-liveblock参数,设置过is-live=false或者block=false,但对多客户端和流畅性问题都没有改善;
  • 确认了self.factory.set_shared(True)已经设置,理论上这个参数是支持多客户端共享媒体流的;
  • 对比了multifilesrc的实现,它能正常支持多客户端,但我不清楚它的核心机制怎么迁移到appsrc的场景里。

希望有GStreamer经验的朋友能帮我分析下问题出在哪,或者给些具体的参数调整、代码修改建议,非常感谢!

火山引擎 最新活动