当前位置: 首页 > news >正文

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

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
userNamertsp服务器登录用户名。若 rtspUrl 已填,此处为空即可
passwordrtsp服务器登录密码。若 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取流器对象指针
pNodeDescNAL报文描述头
pDataNAL报文内容

        利用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-apialgorithm_api算法组件
common_api通用组件
media_api多媒体组件
netProtocol_api网络协议组件
peripheral_api外设硬件组件

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

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

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.2 CMakeLists.txt文件解析

5.2.1 编译环境配置部分:

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

       配置信息如下所示。

配置项描述
CMake要求版本cmake_minimum_required函数指定,要求的最低版本
CMAKE_SYSTEM_NAMEcmake的系统类型,交叉编译必须
CMAKE_CROSSCOMPILINGcmake是否启动交叉编译
cross.camkecamke_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,内容如下所示:(重新编译,成部署目录,并把资源自动部署进板卡)

相关文章:

  • 设计模式:策略模式 - 消除复杂条件判断的利器
  • 【算法】——会了二分查找,对O(logn)真的很敏感
  • LabVIEW 中 “Flatten To Json String” VI 应用及优势
  • 【C++取经之路】lambda和bind
  • LeetCode 3396 题解
  • 安装vllm
  • 【mllm】——x64模拟htp的后端无法编译debug
  • MySQL深分页问题
  • 【Code】《代码整洁之道》笔记-Chapter11-系统
  • Cuto壁纸 2.6.9 | 解锁所有高清精选壁纸,无广告干扰
  • 单细胞多组学及空间组学数据分析与应用
  • 《系统分析师-浏览试卷(一)总结》
  • 元生代品牌建设:平台实现工作流(comfyui)创建与技术文档说明
  • CVE-2025-32375 | Windows下复现 BentoML runner 服务器远程命令执行漏洞
  • JavaScript:基本语法
  • 电脑的usb端口电压会大于开发板需要的电压吗
  • 【从零开始学习JVM | 第二篇】HotSpot虚拟机对象探秘
  • ai-warp 开源的Platformatic Stackable 与 AI 服务交互
  • 快速idea本地和推送到远程仓库
  • .net 使用笔记
  • 网站开发招标技术规范书/提升seo排名
  • 网站开发流程有几个阶段/百度经验官网首页
  • 百度外推发帖软件/企业seo网络营销
  • asp网站 证书/百度推广需要多少钱
  • 简单的b2c网站/百度网盘app下载安装
  • 苏州网站建设推广/个人网页设计作品模板