从主函数开始看起
上一篇定了地图,这篇正式进代码。入口是 rv1126_ffmpeg_main.cpp,整个文件只有 35 行,但我在这 35 行上花的时间比想象中多------因为程序的生命周期全在这里了。
一、先把代码全贴出来
cpp
#include "rkmedia_ffmpeg_config.h"
#include "ffmpeg_video_queue.h"
#include "rkmedia_module_function.h"
#include "rkmedia_assignment_manage.h"
VIDEO_QUEUE * high_video_queue = NULL;
VIDEO_QUEUE * low_video_queue = NULL;
int main(int argc, char *argv[])
{
if(argc < 5)
{
printf("Please Input ./rv1126_ffmpeg_main high_stream_type high_url_address low_stream_type low_url_address. Notice URL_TYPE: 0-->FLV 1-->TS\n");
return -1;
}
int high_protocol_type = atoi(argv[1]);
char * high_network_address = argv[2];
int low_protocol_type = atoi(argv[3]);
char * low_network_address = argv[4];
high_video_queue = new VIDEO_QUEUE(); //初始化所有VIDEO队列
low_video_queue = new VIDEO_QUEUE();
init_rkmedia_module_function(); //初始化所有rkmedia的模块
init_rv1126_first_assignment(high_protocol_type, high_network_address,
low_protocol_type, low_network_address); //开启推流任务
while (1)
{
sleep(20);
}
return 0;
}
二、四个参数:两路流各配一个
bash
./rv1126_ffmpeg_main <high_stream_type> <high_url> <low_stream_type> <low_url>
# 例如
./rv1126_ffmpeg_main 0 rtmp://192.168.1.100/live/high 0 rtmp://192.168.1.100/live/low
参数是"高清 + 低清"两两一组,每组是 协议类型 + 推流地址:
- 协议类型
0→ FLV(走 RTMP) - 协议类型
1→ TS(理论上走 SRT,实际还没搞定,后面说)
第一遍看的时候我有个疑问:为啥要两路流? 后来想明白了,这就是直播行业的标配------观众网速好的看 1080P,网速差的看 720P,服务端按需分发。硬件上就是"一次采集、两路编码"。
三、两个全局队列
cpp
VIDEO_QUEUE * high_video_queue = NULL;
VIDEO_QUEUE * low_video_queue = NULL;
注意这两个是全局变量 ,而且后面 rkmedia_data_process.cpp 里是用 extern 引用的:
cpp
extern VIDEO_QUEUE *high_video_queue;
extern VIDEO_QUEUE *low_video_queue;
这是 C/C++ 里跨文件共享全局变量的经典写法(声明和定义分离)。全局变量当然不算优雅,但在这个项目里它降低了参数在七八个线程之间传递的复杂度------所有线程都直接认识这两个队列。先跑通再优雅,嵌入式项目里挺常见的选择。
队列在这里 new 出来之后,硬件侧的线程往里塞数据,推流侧的线程从里取数据。它是整个项目的"传送带",第 6 篇专门拆。
四、三步初始化
cpp
init_rkmedia_module_function(); // 第一步
init_rv1126_first_assignment(...); // 第二步
while (1) sleep(20); // 第三步
| 步骤 | 函数 | 在哪个文件 | 干的事 |
|---|---|---|---|
| 1 | init_rkmedia_module_function() |
rkmedia_module_function.cpp | 系统初始化 + VI/VENC/RGA 全部配好参数 |
| 2 | init_rv1126_first_assignment() |
rkmedia_assignment_manage.cpp | FFmpeg 初始化 + 通道绑定 + 创建所有工作线程 |
| 3 | 主循环 sleep |
本文件 | 什么都不干,纯活着 |
分工很清楚:第一步把硬件"摆好",第二步把数据流的"管道"接起来并放水,第三步主线程退到一边看着。
这里有个值得咂摸的点:主线程 while(1) sleep(20) 为什么不 join 线程?因为所有工作线程创建时都调了 pthread_detach(pthread_self())(后面篇会看到),分离线程 ------线程自己结束后资源自动回收,不需要别人 join。所以主线程唯一的作用就是"别让进程退出"。嵌入式设备上的常驻程序基本都是这个套路。
五、我卡过的一个点:函数名里的 "assignment"
init_rv1126_first_assignment 这个名字我盯着看了半天。assignment?任务?作业?
看完实现才明白,这里把"一轮推流任务"叫做一个 assignment:给两路流各建一套 FFmpeg 输出上下文,把 VI/VENC/RGA 通道绑起来,再创建 5 个工作线程。名字里的 "first" 暗示设计者本来可能想做多任务管理(跑多个 assignment),但目前只实现了这一个。猜的,没证据,先记着。
六、小结
main 函数虽短,但把三件事定了调:
- 两路码流的架构(参数形式就暴露了)
- 全局队列做 RKMedia 和 FFmpeg 的衔接
- 主线程只负责活着,所有活都在分离线程里干
下一篇进第一站:init_rkmedia_module_function(),看 VI、VENC、RGA 这三个模块的参数是怎么配的------1920x1080、25fps、CBR 码率这些数字都在那里。
本篇对应源码:rv1126_ffmpeg_main.cpp(全文 35 行)