基于Python GStreamer的RTSP多客户端接入与流流畅性问题咨询
基于Python GStreamer的RTSP多客户端接入与流流畅性问题咨询
我现在在用Python结合GStreamer实现一个RTSP推流服务,核心是用OpenCV读取本地视频文件,再通过appsrc把帧推成RTSP流。我参考了一个示例代码并做了修改,目前单客户端(比如一个VLC实例)能正常播放,但遇到了两个棘手问题,想请教下大家:
问题1:无法支持多客户端接入
现在当第一个VLC客户端连接后播放正常,但第二个VLC实例再连接的话就完全无法播放,甚至会导致整个服务出现异常。我已经设置了self.factory.set_shared(True),但好像没起作用,不知道哪里出了问题。
问题2:流播放存在卡顿现象
推流过程中会时不时出现冻结,比如播放到第38帧时卡住,然后突然跳到250帧继续播放,流畅性很差。我试过调整appsrc的is-live和block参数,但没有明显改善。另外我对比过用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()
我已经做过的尝试
- 调整了
appsrc的is-live和block参数,设置过is-live=false或者block=false,但对多客户端和流畅性问题都没有改善; - 确认了
self.factory.set_shared(True)已经设置,理论上这个参数是支持多客户端共享媒体流的; - 对比了
multifilesrc的实现,它能正常支持多客户端,但我不清楚它的核心机制怎么迁移到appsrc的场景里。
希望有GStreamer经验的朋友能帮我分析下问题出在哪,或者给些具体的参数调整、代码修改建议,非常感谢!




