Python分割H.264流为10秒分段报错求助(树莓派环境)
问题根源分析
- 流分割时机错误:你依赖libcamera-vid的日志行(
# ... fps)触发分割,但H.264是带结构的编码流,必须在关键帧(I帧)处分割。如果新的ffmpeg进程启动后接收到的不是起始的SPS/PPS+关键帧,就会报Invalid data found when processing input,因为无法解码非起始帧的流。 - 管道处理不规范:直接关闭旧ffmpeg的stdin后,没有确保libcamera-vid的流数据被正确过渡到新进程,可能导致中间数据丢失或错位;同时没有等待旧ffmpeg进程退出,容易引发资源冲突。
- libcamera-vid参数冗余:
--listen是用于网络RTSP监听的参数,本地管道输出不需要,会干扰正常流输出。
解决方案
1. 调整libcamera-vid参数,确保关键帧间隔可控
添加--key 30(每秒生成1个关键帧,方便10秒分割时总能在关键帧处切换),移除--listen:
libcamera-vid -t 0 --width 1920 --height 1080 --codec h264 --inline --key 30 -o -
2. 重构流处理逻辑,基于关键帧+时间触发分割
- 识别H.264流中的关键帧(通过NALU头判断:H.264 NALU起始码是
0x000001或0x00000001,关键帧的NALU类型是5) - 当时间达到10秒时,等待下一个关键帧到来再切换ffmpeg进程,确保新进程从完整的SPS/PPS+关键帧开始接收数据。
3. 规范ffmpeg进程管理
- 关闭旧进程stdin后,调用
wait()等待进程退出,释放资源 - 新ffmpeg启动时,确保只在关键帧到来后开始写入数据
修改后的完整代码
import subprocess import time recentStreamProcesses = [] streamInfo = { "lastStreamStart": -1, "process": None, "wait_for_keyframe": False # 标记是否需要等待关键帧来切换进程 } def is_h264_keyframe(data): # 判断是否是H.264关键帧NALU # NALU起始码:0x000001 或 0x00000001 start_pos = 0 if data.startswith(b'\x00\x00\x01'): start_pos = 3 elif data.startswith(b'\x00\x00\x00\x01'): start_pos = 4 else: return False # NALU类型(第1字节的低5位),5是关键帧(I帧) nalu_type = data[start_pos] & 0x1F return nalu_type == 5 def processStreamData(data): # 过滤libcamera的日志信息 if data.startswith(b"[") or data == b"Preview window unavailable" or data.startswith(b"#"): return # 如果正在等待关键帧切换,先判断当前是否是关键帧 if streamInfo["wait_for_keyframe"]: if is_h264_keyframe(data): print("Got keyframe, switching to new stream...") exportStream() createNewStream() streamInfo["wait_for_keyframe"] = False # 写入当前关键帧到新进程 streamInfo["process"].stdin.write(data) return # 正常写入数据到当前ffmpeg进程 try: streamInfo["process"].stdin.write(data) except BrokenPipeError: print("Current ffmpeg process exited unexpectedly, creating new one...") createNewStream() streamInfo["process"].stdin.write(data) # 检查是否到了10秒,标记需要等待关键帧切换 if time.time() - streamInfo["lastStreamStart"] >= 10: print("10 seconds passed, waiting for keyframe to split...") streamInfo["wait_for_keyframe"] = True def createNewStream(): timestamp = round(time.time()) output_path = f"/home/survpi-camera/{timestamp}.mp4" streamInfo["lastStreamStart"] = time.time() # ffmpeg添加-hide_banner减少日志,同时确保输入是H.264流 streamInfo["process"] = subprocess.Popen([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "h264", # 指定输入格式为H.264,避免ffmpeg自动探测出错 "-r", "30", "-i", "-", "-c", "copy", output_path ], stdin=subprocess.PIPE, stderr=subprocess.STDOUT) print(f"Created new stream: {output_path}") def exportStream(): print("Finishing current stream...") old_process = streamInfo["process"] old_process.stdin.close() # 等待进程退出,确保文件写入完成 old_process.wait() recentStreamProcesses.append(old_process) print("Current stream exported.") # 启动libcamera-vid,移除--listen,添加--key 30 cameraProcess = subprocess.Popen([ "libcamera-vid", "-t", "0", "--width", "1920", "--height", "1080", "--codec", "h264", "--inline", "--key", "30", # 每秒1个关键帧 "-o", "-" ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) createNewStream() # 注意:不要用readline(),因为H.264流是二进制数据,不是行文本,用read()按块读取 while True: # 每次读取1024字节的二进制数据 readData = cameraProcess.stdout.read(1024) if not readData: # 相机进程退出,结束循环 print("Camera process exited.") break processStreamData(readData)
关键改动说明
- 替换readline()为read(1024):H.264流是二进制数据,不是行结构文本,readline()会破坏流的完整性,导致ffmpeg无法解析。
- 关键帧检测:确保每次分割都从关键帧开始,新ffmpeg进程能正确初始化解码上下文。
- 等待旧ffmpeg进程退出:调用
wait()确保文件写入完成,避免资源泄漏。 - ffmpeg指定输入格式:添加
-f h264让ffmpeg明确知道输入是H.264流,减少自动探测的错误。 - 移除libcamera-vid的--listen参数:本地管道输出不需要该参数,避免异常。
内容的提问来源于stack exchange,提问作者BillPlayz
相关产品推荐
相关产品推荐

