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

Python分割H.264流为10秒分段报错求助(树莓派环境)

问题根源分析
  1. 流分割时机错误:你依赖libcamera-vid的日志行(# ... fps)触发分割,但H.264是带结构的编码流,必须在关键帧(I帧)处分割。如果新的ffmpeg进程启动后接收到的不是起始的SPS/PPS+关键帧,就会报Invalid data found when processing input,因为无法解码非起始帧的流。
  2. 管道处理不规范:直接关闭旧ffmpeg的stdin后,没有确保libcamera-vid的流数据被正确过渡到新进程,可能导致中间数据丢失或错位;同时没有等待旧ffmpeg进程退出,容易引发资源冲突。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:45:18