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

解决相机录制线程锁致丢帧问题的合适设计模式问询

解决方案:双缓冲区(Double Buffer)设计模式

问题根源

场景二中的丢帧问题源于单个共享缓冲区的长时间锁定:写入线程需要锁定缓冲区并完成读取、清空操作,这段时间内相机无法写入新帧,最终导致丢帧。

双缓冲区模式的核心思路

维护两个独立的内存缓冲区:

  • 相机主线程始终向活跃缓冲区写入帧数据
  • 磁盘写入线程处理非活跃缓冲区的数据并写入磁盘
  • 当满足切换条件(如固定时间间隔、缓冲区达到阈值)时,仅需短暂锁定完成缓冲区切换,几乎不会阻塞相机写入

优化后的代码实现

import io
import time
import os
from threading import Thread, Event, Lock
from picamera2 import Picamera2
from picamera2.encoders import Encoder as NullEncoder
from picamera2.outputs import FileOutput

def disk_writer(buffers: list[io.BytesIO], bin_fd, write_interval: int, stop_event: Event, lock: Lock):
    while not stop_event.is_set():
        start_loop = time.perf_counter()
        # 短暂锁定,完成活跃/非活跃缓冲区切换
        lock.acquire()
        buffers[0], buffers[1] = buffers[1], buffers[0]
        inactive_buff = buffers[1]
        lock.release()
        
        # 处理非活跃缓冲区(此时相机不会写入该缓冲区,无需锁定)
        curr_buff_pos = inactive_buff.tell()
        if curr_buff_pos > 0:
            bin_fd.write(inactive_buff.getvalue())
            inactive_buff.seek(0)
            inactive_buff.truncate(0)
        
        elapsed = time.perf_counter() - start_loop
        if elapsed < write_interval:
            time.sleep(write_interval - elapsed)
    
    # 处理录制结束后剩余的所有缓冲区数据
    lock.acquire()
    for buff in buffers:
        if buff.tell() > 0:
            bin_fd.write(buff.getvalue())
            buff.seek(0)
            buff.truncate(0)
    lock.release()

# 初始化相机
cam = Picamera2()

# 初始化双缓冲区
buff1 = io.BytesIO()
buff2 = io.BytesIO()
# 初始将buff1设为活跃缓冲区,供相机写入
mem_out = FileOutput(buff1)

# 打开磁盘输出文件
out_fpath = "video.bin"
bin_fd = open(out_fpath, "wb")

# 启动相机
cam.start()

# 创建并启动磁盘写入线程
stop_event = Event()
lock = Lock()
write_interval = 5
writer_thread = Thread(target=disk_writer, args=([buff1, buff2], bin_fd, write_interval, stop_event, lock))
writer_thread.start()

# 配置无编码编码器(直接写入BGR888格式帧数据)
encoder = NullEncoder()

# 开始录制
to_record = 10
print(f"开始录制,时长{to_record}秒")
cam.start_recording(encoder, mem_out)
time.sleep(to_record)
cam.stop_recording()
print("录制结束")

# 停止写入线程并等待其完成
stop_event.set()
writer_thread.join()
cam.close()
bin_fd.close()

# 统计视频帧数
totbytes = os.path.getsize(out_fpath)
byte_per_frame = 2304 * 1296 * 3  # 帧宽度 * 帧高度 * 通道数
num_frames = totbytes / byte_per_frame
print(f"视频总帧数:{num_frames}")

额外优化建议

  • 如果磁盘写入速度波动较大,可扩展为多缓冲区队列(生产者-消费者模式):用线程安全队列存储多个缓冲区,相机生产满缓冲区,写入线程消费缓冲区,进一步降低阻塞风险
  • 可根据单帧大小设置缓冲区阈值,当缓冲区达到指定帧数时触发切换,而非固定时间间隔,适配不同帧率的相机

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 06:05:54