You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GStreamer预编码H.264流:RTSP服务器CPU优化及空包问题咨询

问题描述

刚接触GStreamer,想用Python搭建一个能流传输MP4或原始H.264块的RTSP服务器。当前方案CPU占用很高,推测是实时H.264编码导致的。由于视频文件已经是H.264编码(未编码的也可以用FFmpeg预编码),想直接解析后送入GStreamer管道,但对启动字符串的运作逻辑不熟悉,遇到了问题。

当前使用的启动字符串(实时编码版本,CPU占用高):

launch_string = 'appsrc name=source block=false format=GST_FORMAT_TIME ' \
                             'caps=video/x-raw,format=BGR,width={},height={},framerate={}/1 ' \
                             '! videoconvert ! video/x-raw,format=I420 ' \
                             '! x264enc speed-preset=veryfast tune=zerolatency ' \
                             '! queue min-threshold-time=300000000 max-size-time=10000000000 max-size-bytes=0 max-size-buffers=0 ' \
                             '! rtph264pay config-interval=1 name=pay0 pt=96 '.format(opt.image_width, opt.image_height, self.fps) 

尝试修改后的启动字符串(想跳过编码,但得到空H.264 RTP数据包):

launch_string = 'appsrc name=source block=false format=GST_FORMAT_TIME ' \
                             'caps=video/x-h264,format=BGR,width={},height={},framerate={}/1 ' \
                             '! h264parse ' \
                             '! queue min-threshold-time=300000000 max-size-time=10000000000 max-size-bytes=0 max-size-buffers=0 ' \
                             '! rtph264pay config-interval=1 name=pay0 pt=96 '.format(opt.image_width, opt.image_height, self.fps)

主循环代码:

def on_need_data(self, src, length):

    if self.number_frames >= (self.max_frame): # Loop the video or exit
        if LOOP:
            self.reset_video()
        else:
            Gloop.quit()

    if self.cap.isOpened():
        ret, frame = self.cap.read()
        if ret:
            if frame.shape[:2] != (self.height, self.width):
                if self.debug >=2:
                    print("Resizing frame")
                    print(frame.shape[:2])
                    print((self.height, self.width))
                frame = cv2.resize(frame, (self.width, self.height))
            data = frame.tobytes()
            buf = Gst.Buffer.new_allocate(None, len(data), None)
            buf.fill(0, data)
            buf.duration = self.duration
            timestamp = self.timestamp_frame * self.duration
            buf.pts = buf.dts = int(timestamp)
            buf.offset = timestamp
            self.number_frames += 1
            self.timestamp_frame += 1
            retval = src.emit('push-buffer', buf)
            if self.debug >= 2:
                print('pushed buffer to {}, frame {}, duration {} ns, durations {} s'.format(self.device_id,
                                                                                   self.timestamp_frame,
                                                                                   self.duration,
                                                                                   self.duration / Gst.SECOND))
            if retval != Gst.FlowReturn.OK:
                print("[INFO]: retval not OK: {}".format(retval))
            if retval == Gst.FlowReturn.FLUSHING:
                print('Offline')
        else:
            if self.debug > 0:
                print("[INFO]: Unable to read frame from cap: ")
                print(self.device_id)
                print(self.number_frames)
                print(self.max_frame)
            Gloop.quit()
问题分析与解决方案

核心问题

你修改后的启动字符串犯了两个关键错误:

  1. caps参数错误:video/x-h264格式不存在format=BGR属性——BGR是原始视频帧的像素格式,而H.264是压缩后的码流格式,两者完全不同。
  2. 数据不匹配:on_need_data函数里用cv2.read()读取的是解码后的原始BGR帧,不是H.264压缩码流,直接把这种数据送入期望接收H.264码流的管道,h264parse无法解析,自然输出空数据包。

正确实现思路

要跳过实时编码、直接复用已有H.264码流,需要做两个核心调整:

  1. 从文件读取原始H.264码流(而非解码后的帧)。
  2. 配置GStreamer管道接收H.264码流,正确解析后打包成RTP。

步骤1:预提取H.264码流(如果是MP4文件)

先用FFmpeg把MP4里的H.264码流提取出来,避免解码再编码:

ffmpeg -i input.mp4 -vcodec copy -an -bsf:v h264_mp4toannexb output.h264

-bsf:v h264_mp4toannexb参数是把MP4封装的H.264转换成Annex-B格式,这是GStreamerh264parse默认支持的格式。

步骤2:修改GStreamer启动字符串

调整appsrc的caps为H.264码流的正确格式,并去掉编码环节:

launch_string = 'appsrc name=source block=false format=GST_FORMAT_TIME ' \
                             'caps=video/x-h264,stream-format=byte-stream,alignment=au,width={},height={},framerate={}/1 ' \
                             '! h264parse ' \
                             '! queue min-threshold-time=300000000 max-size-time=10000000000 max-size-bytes=0 max-size-buffers=0 ' \
                             '! rtph264pay config-interval=1 name=pay0 pt=96 '.format(opt.image_width, opt.image_height, self.fps)
  • stream-format=byte-stream:指定H.264码流是Annex-B格式的字节流。
  • alignment=au:按访问单元(Access Unit)对齐,确保h264parse能正确识别NALU边界。

步骤3:修改on_need_data函数读取H.264码流

不再用cv2.VideoCapture解码帧,而是直接读取H.264文件的二进制数据,按块推送:

def on_need_data(self, src, length):
    if self.number_frames >= (self.max_frame):
        if LOOP:
            self.h264_file.seek(0)
            self.number_frames = 0
            self.timestamp_frame = 0
        else:
            Gloop.quit()
            return

    # 读取H.264码流数据(可根据实际情况调整读取块大小)
    data = self.h264_file.read(4096)
    if not data:
        if LOOP:
            self.h264_file.seek(0)
            data = self.h264_file.read(4096)
        else:
            Gloop.quit()
            return

    buf = Gst.Buffer.new_allocate(None, len(data), None)
    buf.fill(0, data)
    buf.duration = self.duration
    timestamp = self.timestamp_frame * self.duration
    buf.pts = buf.dts = int(timestamp)
    buf.offset = timestamp
    
    self.number_frames += 1
    self.timestamp_frame += 1
    
    retval = src.emit('push-buffer', buf)
    if self.debug >= 2:
        print('pushed buffer to {}, frame {}, duration {} ns, durations {} s'.format(self.device_id,
                                                                                   self.timestamp_frame,
                                                                                   self.duration,
                                                                                   self.duration / Gst.SECOND))
    if retval != Gst.FlowReturn.OK:
        print("[INFO]: retval not OK: {}".format(retval))
    if retval == Gst.FlowReturn.FLUSHING:
        print('Offline')

初始化时要打开H.264文件,比如在类的__init__方法中添加:

self.h264_file = open('output.h264', 'rb')

更简单的替代方案:直接用GStreamer读取MP4文件

如果不需要用appsrc自定义数据推送,直接用GStreamer原生元素读取MP4文件,完全不需要自己处理帧或码流,CPU占用会更低:

launch_string = 'filesrc location=input.mp4 ! qtdemux name=demux ' \
                             'demux.video_0 ! h264parse ' \
                             '! queue min-threshold-time=300000000 max-size-time=10000000000 max-size-bytes=0 max-size-buffers=0 ' \
                             '! rtph264pay config-interval=1 name=pay0 pt=96'

这个方案让GStreamer直接处理MP4解封装、H.264解析和RTP打包,不需要Python介入数据推送,效率最高。


内容的提问来源于stack exchange,提问作者Yelnat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:10:33