工业级RTSP推流+异步MP4录制:双队列架构实现
在实时音视频场景中,RTSP推流需保证低延迟、不卡顿,MP4录制需确保数据不丢失、文件可正常播放。本文将分享一套经过实践验证的工业级架构。
先上结论:用双队列解耦,把摄像头视频播放和MP4录制隔开,两个队列将 摄像头产生的数据 (生产者)和 MP4 封装(消费者) 完全分开。摄像头往队列存数据,不管消费者是否处理完,不阻塞;MP4封装线程从队列拿数据,如果没有数据就阻塞等待,最终效果就是两个线程各自运行,速度不匹配也没关系,有队列作为缓冲。
一、架构背景
核心模块分为A、B两个进程,承担不同职责:A进程运行摄像头采集线程,负责RTSP推流、异步MP4封装线程,管理双队列;B进程通过HTTP请求向A进程发送录制指令,开始/停止MP4保存。
二、架构流程
阶段1:A进程初始化
初始化阶段核心是完成缓冲区预分配与状态重置,为后续推流和录制做好准备:
-
向 ConcurrentQueue 非阻塞队列(生产者)中预填充N个初始化好的缓存buffer,预先分配内存,避免运行时动态申请/释放,控制内存上限,减少内存碎片。这里用了非阻塞队列,因为摄像头线程要实时推流,绝对不能阻塞。
-
BlockingConcurrentQueue阻塞队列(消费者),给MP4封装线程使用,初始化为空队列。阻塞队列存放待封装 H.264 数据,MP4 线程可以安心等着,不耗 CPU。
-
核心状态初始化:bool类型的 is_running(录制开关)设为false,unordered_map 类型的task_map(MP4录制任务)为空;
-
启动摄像头线程,循环执行:从task_map读取任务,此阶段无任务→ 仅采集摄像头数据,向前端推送RTSP流 → 不操作MP4的任何队列,直接进入下一次循环。
阶段2:B进程HTTP请求驱动
录制由B进程通过HTTP请求触发
-
B进程向A进程发送HTTP请求,请求A进程开始保存MP4;
-
A进程接收请求后,更新核心状态:向task_map写入录制任务,将is_running设为true;
-
清理消费者阻塞队列残留数据:循环调用消费者阻塞队列的出队方法,将所有残留数据取出,重新塞回生产者非阻塞队列,确保消费者队列为空,避免旧数据混入新的录制文件;
-
启动异步MP4封装线程,该线程初始阻塞在消费者队列为空上,消费者队列有数据才能出队。
阶段3:开始录制MP4
A进程中task_map有了任务,并且is_running = true
while(true) {
取摄像头数据 → 推RTSP流
if(task_map无任务 || is_running == false)
continue;
// ====================== 录制逻辑 ======================
// 从非阻塞队列producter取空闲buffer
if( producter.TryDequeue(out buf) )
{
//编码H264 写入buffer
consumer.Enqueue(buf); // 入阻塞队列
}
}
MP4保存线程
while(is_running || producter 不为空)
{
buf = consumer.Dequeue(); // 队空为空,阻塞等待,不占CPU
ffmpeg_write_h264(buf); // 写入MP4
// 用完 后归还空闲buffer到 consumer 闭环复用,不申请内存
producter.Enqueue(buffer);
//写MP4文件尾
}
阶段4:停止MP4录制
-
将is_running设为false,从task_map中移除录制任务;
-
检测到is_running为false,停止取buffer、编码、入队操作,仅保留RTSP推流功能;
-
继续消费consumer队列中的剩余数据,直到队空,然后写入MP4文件尾,完成录制;
-
所有buffer最终归还到producter队列,恢复到初始化时的状态,等待下一次录制触发。
三、架构总结(ai)
ConcurrentQueue保障了摄像头线程的无阻塞运行,确保RTSP推流的实时性;BlockingConcurrentQueue优化了MP4线程的性能,实现了高效的数据消费与内存利用;双队列协同配合,加上固定缓冲区复用,最终实现了“实时、可靠、可控”的工业级音视频处理方案。目前该架构已在实际项目中稳定运行,支持高并发推流与录制场景。

浙公网安备 33010602011771号