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()
问题分析与解决方案
核心问题
你修改后的启动字符串犯了两个关键错误:
- caps参数错误:
video/x-h264格式不存在format=BGR属性——BGR是原始视频帧的像素格式,而H.264是压缩后的码流格式,两者完全不同。 - 数据不匹配:
on_need_data函数里用cv2.read()读取的是解码后的原始BGR帧,不是H.264压缩码流,直接把这种数据送入期望接收H.264码流的管道,h264parse无法解析,自然输出空数据包。
正确实现思路
要跳过实时编码、直接复用已有H.264码流,需要做两个核心调整:
- 从文件读取原始H.264码流(而非解码后的帧)。
- 配置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
相关产品推荐
相关产品推荐

