/ ai资讯

基于RV1126开发板实现多路网络摄像头取流方案

发布时间:2025-04-21 15:46:47

1. 方案简介

方案设计逻辑流程图

2. 快速上手

2.1 开发环境准备

如果您初次阅读此文档,请阅读《入门指南/开发环境准备/Easy-Eai编译环境准备与更新》,并按照其相关的操作,进行编译环境的部署

在PC端Ubuntu系统中执行run脚本,进入EASY-EAI编译环境,具体如下所示。

cd ~/develop_environment ./run.sh

2.2 源码下载以及实例编译

在EASY-EAI编译环境下创建存放源码仓库的管理目录:

cd /opt mkdir EASY-EAI-Toolkit cd EASY-EAI-Toolkit

通过git工具,在管理目录内克隆远程仓库

git clone https://github.com/EASY-EAI/EASY-EAI-Toolkit-C-Solution.git

注:

* 此处可能会因网络原因造成卡顿,请耐心等待。

* 如果实在要在gitHub网页上下载,也要把整个仓库下载下来,不能单独下载本实例对应的目录

进入到对应的例程目录执行编译操作,具体命令如下所示:

cd EASY-EAI-Toolkit-C-Solution/solu-rtspMulitPlayer/ ./build.sh

注:

* 由于依赖库部署在板卡上,因此交叉编译过程中必须保持adb连接。

注:

* 若build.sh脚本不带任何参数,则仅会拷贝solution编译出来的可执行文件。

* 若build.sh脚本带有cpres参数,则会把Release/目录下的所有资源都拷贝到开发板上。

* 若build.sh脚本带有clear参数,则会把build/目录和Release/目录删除。

2.3 方案部署

然后把编译好的结果部署到板卡中(有两种方法)。

方法一:通过执行以下命令手动部署【推荐】

cp Release/solu-* /mnt/userdata/Solu cp Release/rtspClient.ini /mnt/userdata/Solu

方法二:在编译时加上编译参数自动部署

./build.sh cpres

2.4 示例方案运行

通过按键Ctrl Shift T创建一个新窗口,执行adb shell命令,进入板卡运行环境。

adb shell

进入板卡后,定位到例程部署的位置,如下所示:

cd /userdata/Solu

用户根据自身所在的网络环境,运行以下命令,修改rtspClient.ini配置文件(配置文件的详细说明见本文的4.1配置文件使用说明)。

vi rtspClient.ini

运行例程命令如下所示:

./solu-rtspMulitPlayer Main &

2.5 运行效果

每路网络摄像头的图像会在屏幕中轮询显示。轮询时间为5s。

2.6 开机启动

首先进入板卡环境,执行以下命令,在板卡上创建一个给本例程使用的应用目录:myapp

cd /userdata/apps/ mkdir myapp

然后回到开发环境中,通过使用“2.3方案部署”类似的操作方法,把本例程所需要的全部文件,包含:编译结果,配置文件,模型等。部署到刚刚新建的myapp目录中。

最后在板卡上创建一个run.sh脚本来管控用户所有需要的应用即可,《入门指南/应用程序开机自启动》会详细描述run.sh脚本该如何编写。

3. 代码组成

方案主逻辑代码分为三部分,如下所示。

启动代码位于:EASY-EAI-Toolkit-C-Solution/solu-rtspMulitPlayer/src/main.cpp

取流代码位于:EASY-EAI-Toolkit-C-Solution/solu-rtspMulitPlayer/src/capturer/rtspCapturer.cpp

播放器代码位于:EASY-EAI-Toolkit-C-Solution/solu-rtspMulitPlayer/src/player/player.cpp

3.1 组件库组成

多路网络摄像头方案的实现,需要使用到easyeai-api库的以下组件。

3.1.1 启动部分使用库情况

启动代码main.cpp主要负责读取配置文件,设置本地网络信息等,使用到的easyeai-api库组件如下所示。

启动代码主要使用的模块信息如下所示。

组件 头文件以及库路径 描述
系统操作组件 easyeai-api/common_api/system_opt 提供网络操作函数
ini文件操作组件 easyeai-api/common_api/ini_wrapper 提供ini文件提取操作函数
网络参数配置组件 easyeai-api/peripheral_api/network 提供网络操作函数

3.1.2 取流部分使用库情况

取流代码rtspCapturer.cpp主要负责从网络摄像头RTSP拉流、ini配置文件读取、推进解码器操作、时间戳操作等,使用到的easyeai-api库组件如下所示。

取流代码主要使用的模块信息如下所示。

组件 头文件以及库路径 描述
系统操作组件 easyeai-api/common_api/system_opt 提供时间戳操作函数
ini文件操作组件 easyeai-api/common_api/ini_wrapper 提供ini文件提取操作函数
解码器操作组件 easyeai-api/media_api/frame_queue 提供推入解码器操作函数
rtsp组件 easyeai-api/netProtocol_api/rtsp 提供RTSP拉流操作函数

3.1.3 播放/分析部分使用库情况

播放器代码player.cpp主要负责从解码器获取YUV数据、YUV转RGA操作、解码操作、显示图片等,使用到的easyeai-api库组件如下所示。

播放器代码主要使用模块信息如下所示。

组件 头文件以及库路径 描述
系统操作组件 easyeai-api/common_api/system_opt 提供线程操作函数
ini文件操作组件 easyeai-api/common_api/ini_wrapper 提供ini文件提取操作函数
解码器操作组件 easyeai-api/media_api/frame_queue 提供解码器操作函数
easyeai-api/media_api/endeCode_api 提供创建解码器、绑定回调等函数
显示组件 easyeai-api/peripheral_api/display 提供显示操作函数
RGA数据转换组件 rga/RgaApi.h 非easyeai-api库,提供Rga数据转换操作

4. 逻辑框图

项目的整体逻辑框图如下所示。

4.1 配置文件使用说明

本方案使用ini文件管理配置参数,根据您的网络环境,配置板卡,网络摄像头的参数,路径为EASY-EAI-Toolkit-C-Solution/solu-rtspMulitPlayer/config/rtspClient.ini,内容如下所示。

本机配置节 节名:configInfo
enableChnNum 需要使能的前n个RTSP取流通道。如填2,则[rtspChannel_0]和[rtspChannel_1]的配置被使能
ipAddress 板卡IP地址信息
netMask 板卡网络掩码信息
gateWay 板卡网关信息
网络Camera配置节 节名:rtspChannel_*(星号为摄像头序号,从0开始)
rtspUrl 可仅填流媒体地址,也可以填上完整格式的 url。例如:rtsp://admin:a12346578@192.168.1.69/main
progName
userName rtsp服务器登录用户名。若 rtspUrl 已填,此处为空即可
password rtsp服务器登录密码。若 rtspUrl 已填,此处为空即可
frameRate 流控时参考帧率,此处建议填写0(自动匹配)。
注意事项:
1. 必须大于等于实际帧率。
2. H.265码流暂未实现自适应功能,必须填写一个大于等于实际帧率的数值。

修改好以后进行保存:

:wq

4.2 启动逻辑

启动逻辑代码路径为:src/main.cpp。

4.2.1 启动——配置板卡网络

调用ini库,读取配置文件,设置本地IP地址,操作命令如下。

char ipv4[64]={0}; char netMask[64]={0}; char gateWay[64]={0}; ini_read_string(RTSP_CLIENT_PATH, "configInfo", "ipAddress", ipv4, sizeof(ipv4)); ini_read_string(RTSP_CLIENT_PATH, "configInfo", "netMask", netMask, sizeof(netMask)); ini_read_string(RTSP_CLIENT_PATH, "configInfo", "gateWay", gateWay, sizeof(gateWay)); set_net_ipv4(ipv4, netMask, gateWay);

4.2.2 启动——创建播放器和取流器

主进程根据配置文件,创建1个播放器,创建N个取流器。播放器用于统一接收解码器输出的图像文件,取流器用于获取单个网络摄像头的RTSP流,使用几个摄像头就开启几个取流器。

创建播放器的操作如下所示,播放器会接受各个摄像头解码出来的图像数据,故需要得知摄像头的数量。

ini_read_int(RTSP_CLIENT_PATH, "configInfo", "enableChnNum", &chnNum); Player *pPlayer = new Player(chnNum);

创建多个RTSP取流器如下所示。

char chnId[MAX_CHN_NUM] = {0}; for(int i = 0; i < chnNum; i ){ bzero(&chnId, sizeof(chnId)); sprintf(chnId, "%d", i); CreateSignalProcess(PROCESS_RTSPCLIENT_NAME, &st_TaskInfo, chnId); }

4.3 取流器逻辑

4.3.1 取流器——创建过程

取流器实际是对每个网络摄像头的RTSP拉流,RTSP拉流应用有个特点,一般启动RTSP拉流之后,Camera端会不断返回NAL报文,对于本地应用来说,需要为每个摄像头开启独立进程接收报文,所以进程和Camera一一对应。创建取流器的过程实际就是创建子进程的过程。

创建进程操作如下所示。

static int32_t CreateProcess(const char *pcPara, struct st_SysTask *st_TaskInfo){ pid = fork(); if(pid == -1) {/*创建进程失败操作*/ } else if(pid != 0) {/*父进程操作,只记录*/ } /*子进程操作*/ execlp(“./solu-rtspMulitPlayer”, “solu-rtspMulitPlayer”, pcPara, (char *)0); }

进程的主体为main.cpp的solu-rtspMulitPlayer分支,即以下代码。

4.3.2 取流器——对象描述

创建RTSP取流器的操作实现在rtspCapture/rtspCapture.cpp内的rtspSignalInit()函数内,主要流程如下所示。

RtspCapturer *pRtspCapturer = new RtspCapturer(channelName); pRtspCapturer->init(atoi(argv[2]));

代码创建了一个RtspCapturer对象,并调用了对象的init函数。

4.3.3 取流器初始化——环形队列操作

在src/capture/rtspCapturer.cpp的RtspCapturer初始化init函数中,由于单路取流器只能运行在单条进程中,因此取回来的RTSP流媒体数据,需要通过“流媒体环形队列”送入解码器。

以下是创建流媒体环形队列操作。

create_video_frame_queue_pool(MAX_VIDEO_CHN_NUMBER);

这部分请参考《【多媒体组件】编解码-流媒体环形队列》。

4.3.4 取流器初始化——读取INI配置

取流器需要知道网络摄像头的配置情况,从INI文件读对应配置操作如下所示。在src/capture/rtspCapturer.cpp的RtspCapturer初始化init函数。

ini_read_string(RTSP_CLIENT_PATH, strSection(), "progName", cProgName,sizeof(cProgName)); ini_read_string(RTSP_CLIENT_PATH, strSection(), "rtspUrl", cRtspUrl,sizeof(cRtspUrl)); ini_read_string(RTSP_CLIENT_PATH, strSection(), "userName", cUserName,sizeof(cUserName)); ini_read_string(RTSP_CLIENT_PATH, strSection(), "password", cPassword,sizeof(cPassword)); ini_read_int(RTSP_CLIENT_PATH, strSection(), "frameRate", &frameRate);

4.3.5 取流器初始化——绑定回调函数

设置RTSP取流器的回调函数。

set_rtsp_client_video_callback(VideoHandle, (void *)this);

回调函数原型 如下所示。

int32_t VideoHandle(void *pCapturer, VideoNodeDesc *pNodeDesc, uint8_t *pData)

传入参数如下所示。

参数 描述
pCapturer 取流器对象指针
pNodeDesc NAL报文描述头
pData NAL报文内容

利用RTSP取流器回调,把NALU(NAL单元),即H.264码流组成单元送入流媒体环形队列。

push_node_to_video_channel(pSelf->channelId(), pNodeDesc, pData);

4.3.6 取流器初始化——创建RTSP拉流通道

在src/capture/rtspCapturer.cpp的RtspCapturer初始化init函数内,创建RTSP拉流通道。

create_rtsp_client_channel(&rtspChn);

一旦调用该接口,整条取流进程就会进入RTSP拉流事件循环中,会阻塞在此处,代码不再往下执行。

4.4 播放逻辑

4.4.1 播放器初始化——创建解码器

Player对象的构造函数Player()内,创建解码器操作如下所示。

create_decoder(mChnannelNumber);

4.4.2 播放器初始化——绑定回调函数

首先创建流媒体环形队列。再向解码器申请解码通道,并向通道绑定输出回调,同时分别创建出线程去读取共享内存的NALU,送入编码器对应的通道中。

使用create_decMedia_channel()创建解码通道,然后通过set_decMedia_channel_callback()绑定解码回调处理函数。

解码回调处理函数原型如下所示。

static int32_t VideoPlayerHandle(void *pPlayer, VideoFrameData *pData);

传入参数如下所示。

参数 描述
pPlayer 播放器对象指针
pData 解码输出结果

使用create_video_frame_queue_pool()创建对应数量的流媒体环形队列,然后在sendNALUtoDecoderThread里,通过get_node_from_video_channel()从流媒体环形队列对应的通道取出NALU数据,再通过push_node_in_decMedia_channel();把数据送入对应的解码通道。

4.4.3 播放器初始化——管理线程

创建播放管理线程,如下所示。

CreateNormalThread(cruiseCtrl_thread, pPlayer, &mTid);

播放器的管理线程用于计算切换播放通道的chnId变量,实现每5秒切换一组摄像头的功能,如下所示。

4.4.4 播放器——解码输入线程

sendNALUtoDecoderThread线程的内部实现如下所示:

void *sendNALUtoDecoderThread(void *para) { uint32_t *pChnId = (uint32_t *)para; uint32_t chnId = *pChnId; VideoNodeDesc nodeDesc; uint8_t *pTempBuf = NULL; pTempBuf = (uint8_t *)mpp_malloc(char, MEM_BLOCK_SIZE_5M); while(1) { if(!pTempBuf){ pTempBuf = (uint8_t *)mpp_malloc(char, MEM_BLOCK_SIZE_5M); usleep(20 * 1000); } // 通道合法性校验 if((0 <= chnId) && (chnId < MAX_CHN_NUM)) { if(!pTempBuf){ usleep(20 * 1000); continue; } // 从环形共享内存队列中,取出节点描述信息,以及把帧数据放入临时内存中 if(0 == get_node_from_video_channel(chnId, &nodeDesc, pTempBuf)){ // 把NALU数据送入各自的解码通道 push_node_in_decMedia_channel(chnId, &nodeDesc, pTempBuf); usleep(5*1000); } else { usleep(15*1000); } }else{ usleep(500*1000); } } if(pTempBuf){ mpp_free(pTempBuf); pTempBuf = NULL; } pthread_exit(NULL); }

4.4.5 播放器——解码回调函数

解码回调函数会把每一路解码后的“最后一帧”数据(YUV)通过调用播放器对象的makeCamImg()函数转换成RGB数据,然后缓存到该路对应的camImg中,以便后续处理。

这里需要注意:

1. 由于每一路的解码器都是调用同一个回调函数,因此在此函数内定义并使用静态变量时,一定要注意多通道的影响,即按通道来定义静态变量:

否则每一个通道都会操作“同一个”静态变量。

2. 解码回调函数内不能有耗时过长的操作,否则会导致解码器因堵帧导致的丢帧情况。整个函数调用耗时建议不超过(1000/帧率)ms。

4.4.6 播放器——数据格式化函数

数据格式化函数如下所示。

此函数的目的是把解码后数据转换成1280x720 RGB888的格式,以便显示函数使用。

4.4.7 播放器——播放函数

播放函数如下所示。

5. 开发指南

5.1 示例文件&目录结构

Solution git仓库会随着产品迭代更新,不断新增解决方案代码,当前截图只作参考。

5.1.1 solution git 仓库目录介绍

Solution工程构成如下所示,由功能组件easyeai-api和各个解决方案构成。

功能组件的描述如下所示,easyeai-api是经过高度封装的易用性组件接口,便于用户直接调用板卡资源。

功能 组件目录 组件子目录 描述
功能组件 easyeai-api algorithm_api 算法组件
common_api 通用组件
media_api 多媒体组件
netProtocol_api 网络协议组件
peripheral_api 外设硬件组件

解决方案的描述如下所示,单个“solu-”开头的目录即为一个解决方案案例,代码内调用“EASY EAI-API”来满足某一实际应用场景的需求。

功能 工程目录 描述
解决方案 solu-qrdecode 二维码解决方案
solu-rtspMulitPlayer RTMP推流解决方案
...... 持续更新

5.1.2 解决方案最基本的目录构成

每个解决方案就是一个独立的项目,项目内包含部分如下所示,项目使用cmake构建自动编译部署。

具体介绍如下所示。

组成部分 描述
build.sh 编译脚本,用于管理生成可执行文件后的部署准备工作,用户可自定义shell命令
CMakeLists.txt 工程管理文件,用于组织整个工程结构,指导cmake生成Makefile
include 用于存放第三方应用库、头文件目录等
src 用于存放实现本方案需求的源代码

5.1.3 解决方案可拓展的目录构成

可拓展的目录是指:开发过程中增加某些功能模块,功能代码。增加模式分为两种:

  • 增加已编译的第三方库,在include、libs目录内添加头文件和库文件;
  • 增加用户自定义的功能模块,推荐在src目录内增加;

具体情况如下所示,第三方模块相关的文件由include/3rd_model/xxx.h、libs/3rd_model/xxx.a。自定义的功能模块为src/mySrcCode、src/mySrcCode2。

5.2CMakeLists.txt文件解析

5.2.1 编译环境配置部分:

第一部分为配置部分,配置部分如下所示。(获取当前方案目录、配置工具链、提取方案名称):

配置信息如下所示。

配置项 描述
CMake要求版本 cmake_minimum_required函数指定,要求的最低版本
CMAKE_SYSTEM_NAME cmake的系统类型,交叉编译必须
CMAKE_CROSSCOMPILING cmake是否启动交叉编译
cross.camke camke_host_system_information获取平台信息,发现不是armv7l就导入当前平台的交叉编译配置。
project项目名 由project函数指定

5.2.2 easyeai-api配置部分

第二部分是引入我司的功能组件库(针对当前方案进行:配置EASY EAI API头文件目录、库文件目录以及配置库链接参数):

配置信息如下所示。

配置项 描述
api_inc 最终通过target_include_directories关键字指定目标包含的头文件路径
link_directories 由link_directories关键字指定easyeai-api库所在路径
LINK_LIBRARIES 由LINK_LIBRARIES关键字指定easyeai-api库文件

5.2.3 第三方库配置部分

第三部分配置第三方的库(针对当前方案进行:配置第三方头文件目录、库文件目录、配置第三方库链接参数以及配置源码目录):

配置信息如下所示。

配置项 描述
custom_inc 自定义变量custom_inc,最终通过target_include_directories函数指定目标包含的头文件路径,在源码include目录下
link_directories 由link_directories函数指定第三方库所在路径
custom_libs 自定义变量custom_libs,最终通过target_link_libraries函数指定目标引用的库链接参数
aux_source_directory 自定义变量dir_srcs,用于添加工程代码以及自定义的个人代码

例如添加个人库的目录组成方式如下所示。

aux_source_directory的修改方式为:

aux_source_directory(./src ./src/mySrcCode ./src/mySrcCode2 dir_srcs)

aux_source_directory(./src dir_srcs) aux_source_directory(./src/mySrcCode dir_srcs) aux_source_directory(./src/mySrcCode2 dir_srcs)

5.2.4 本方案配置部分

第四部分配置项目的编译信息,内容如下所示:

配置项如下所示。

配置项 描述
add_executable 编译结果为${CURRENT_FOLDER}指定,即方案目录名;
编译的源文件为${dir_srcs}指定;
target_include_directories 指定头文件的名字,由${api_inc}与${custom_inc}指定;

5.3 build.sh编译脚本:

5.3.1 路径定位部分

第一部分用于提取目录用于编译操作,内容如下所示:(进入build.sh脚本所在目录,并且提取当前目录绝对路径,提取当前目录名称)

5.3.2 清除编译部分

第二部分清除操作,清除目录为build、Release,内容如下所示:(执行build.sh脚本时,带入了参数“clear”,则清空编译输出)

5.3.3 编译操作

第三部分,编译直接调用cmake,内容如下所示:(重新编译,成部署目录,并把资源自动部署进板卡)

免责声明:本文为转载,非本网原创内容,不代表本网观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

如有疑问请发送邮件至:bangqikeconnect@gmail.com