开篇:这个项目要做什么,我是怎么设计的
系列目录:
- 01 开篇:项目与整体设计(本篇)
- 02 从主函数开始看起
- 03 RKMedia 三大模块初始化(VI / VENC / RGA)
- 04 通道绑定与任务分发
- 05 数据是怎么流动的:三大工作线程
- 06 VIDEO_QUEUE:生产者消费者队列
- 07 FFmpeg 推流侧的初始化
- 08 AVPacket 搬运工与写帧
- 09 Makefile、交叉编译与工程总结
一、这个项目是干嘛的
一句话:RV1126 开发板采集摄像头画面 → 硬件编码成 H264 → 用 FFmpeg 打包推到流媒体服务器(RTMP)。
说白了就是让一块板子当"直播摄像头"用。这是我做的一套嵌入式推流方案:采集、编码全部吃 RV1126 的硬件能力,FFmpeg 只负责封装和网络发送,最后双路码流(1080P 高清 + 720P 低清)同时推向 RTMP 服务器。
设计目标从一开始就定得很清楚:
- 实时性:1080P@25fps 全程硬件链路,CPU 留给业务层;
- 双档清晰度:观众网速好看 1080P,网速差看 720P,一次采集两路输出;
- 结构清晰:每一层职责单一,以后往上叠功能(水印、QP 采集、音频)都有现成的插入点。
二、文件组织:先把"地图"画好
写第一行代码之前,我先定了文件命名规范——项目里那份《文件命名前缀规范说明.md》就是干这个的。前缀即层次:
| 前缀 | 是什么层次 | 干嘛的 |
|---|---|---|
rv1126_ | 硬件层 | 芯片平台相关,比如 ISP 图像处理 |
rkmedia_ | SDK 层 | Rockchip 的 RKMedia API 封装 |
ffmpeg_ | 第三方库层 | 推流相关的数据结构(队列) |
sample_ | 官方示例 | Rockchip 给的参考代码,基本不动 |
我的类比:
rv1126_* = 地基和框架(硬件基础) rkmedia_* = 水电管道(SDK 基础设施) ffmpeg_* = 装修材料(第三方功能库) sample_* = 装修图纸(官方参考方案) main.cpp = 房子整体(你的应用)为什么这么较真?因为嵌入式项目最容易烂在"文件越多越乱"上。前缀定了层次,看文件名就知道该去哪个文件改东西——三个月后的我自己也是受益人。
三、整体架构:一张图说清楚
这是我最先画出来的设计图,整个项目就是照着它落地的:
三个我最得意的设计决策,后面每篇都会展开:
- 两路码流不是两次采集。摄像头只采一次 1080P,高清路直接编码;低清路靠 RGA 硬件缩放成 720P 再进第二个编码通道。一路传感器出两档清晰度,sensor 和 VI 的开销一分钱不多花。
- RKMedia 和 FFmpeg 之间用队列解耦。硬件出数据的节奏和网络消费的节奏天然对不上,中间塞一个带锁的队列,两边各跑各的线程,互不牵连——这是整个架构的"轴承"。
- 编码完全交给硬件,FFmpeg 不编码。VENC 出来的就是 H264 码流,FFmpeg 只干"参数对接 + 封装 + 发网络"。CPU 占用从源头上就压住了。
四、这个系列怎么写
按"从骨架到血肉、顺着数据流方向"的顺序,每篇一个主题:
README.md→ 知道项目干什么的,入口在哪文件命名前缀规范说明.md→ 建立文件地图rv1126_ffmpeg_main.cpp→ 30 多行的程序骨架rkmedia_module_function.cpp→ 所有硬件模块的参数rkmedia_assignment_manage.cpp→ 绑定关系 + 线程创建rkmedia_data_process.cpp→ 线程函数实现,数据流核心ffmpeg_video_queue.cpp→ 队列组件rkmedia_ffmpeg_config.cpp→ FFmpeg 侧初始化Makefile→ 交叉编译链条
后面每篇我会按这个套路写:
- 这部分代码在干嘛(一句话)
- 当时为什么这么设计(设计动机,这个最重要)
- 关键代码贴出来,配注释
- 后续的演进方向
下一篇:从主函数开始看起 —— 一个 30 行的 main 里藏着整个程序的生命周期。