/ ai资讯

基于RV1126开发板实现网络摄像头方案

发布时间:2025-04-21 17:46:32

1. 方案简介

本开发实例是一个包含:抓取摄像头数据、图像送入编码器进行编码压缩、以及把压缩后的数据通过RTSP协议发送出去的简单核心示例。

若用户需要通过webServer对图像进行调整,以及配置摄像头的一些工作方式,则需要用户自行研发增加对应的功能。

1.1 接线示意图

摄像头与板卡的连接:

板卡与PC的连接:

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-rtspIPCamera/ ./build.sh

注:

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

注:

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

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

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

2.3 方案部署

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

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

cp Release/solu-* /mnt/userdata/Solu

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

./build.sh cpres

2.4 示例方案运行

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

adb shell

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

cd /userdata/Solu

运行例程前,先通过以下命令查看一下开发板网卡的地址:

ifconfig

运行例程命令如下所示:

./solu-rtspIPCamera Main &

2.5 运行效果

运行打印如下所示:

[1] 3931 root@EASY-EAI-NANO:/userdata/Solu# media get entity by name: rkcif-lvds-subdev is null media get entity by name: rkcif-lite-lvds-subdev is null media get entity by name: rkisp-mpfbc-subdev is null media get entity by name: rkisp_dmapath is null media get entity by name: rkisp-mpfbc-subdev is null media get entity by name: rkisp_dmapath is null media get entity by name: rockchip-mipi-dphy-rx is null [16:45:52.418515][CAMHW]:XCAM ERROR CamHwIsp20.cpp:928: No free isp&ispp needed by fake camera! Rga built version:1.04 94a2c08 2023-04-12 10:23:53 ***************************************** Waiting for the child process terminated! ***************************************** …… mpp[3939]: mpp_rt: NOT found ion allocator mpp[3939]: mpp_rt: found drm allocator mpp[3939]: mpp_info: mpp version: 2cc04fd author: chenhaiman 2023-02-08 update build scripts mpp[3939]: mpp_log: 0x6718c0 mpi_enc_test encoder test start w 720 h 1280 type 7 mpp[3939]: mpp_enc: MPP_ENC_SET_RC_CFG bps 2880000 [180000 : 2880000] fps [30:25] gop 12 mpp[3939]: h264e_api_v2: MPP_ENC_SET_PREP_CFG w:h [720:1280] stride [720:1280] mpp[3939]: mpp_enc: send header for set cfg change input/format mpp[3939]: mpp_enc: mode cbr bps [180000:2880000:2880000] fps flex [30/1] -> fix [25/1] gop i [12] v [0] =====[frame time]: 1970-01-01 08:00:00 [000][0x67][1]------------- dataLen = 38 =====[frame time]: 2023-05-08 16:45:54 [001][0x06][1]------------- dataLen = 86 =====[frame time]: 2023-05-08 16:45:54 [002][0x06][1]------------- dataLen = 90 =====[frame time]: 2023-05-08 16:45:54 [003][0x65][1]------------- dataLen = 13747 =====[frame time]: 2023-05-08 16:45:54 [004][0x41][2]------------- dataLen = 4531 =====[frame time]: 2023-05-08 16:45:54 ……

然后在PC主机(windows系统或者Ubuntu系统都可以),打开VLC播放器。输入rtsp流地址即可通过rtsp协议播放摄像机画面。

rtsp流地址构造规则在下方【4.3.2RTSP服务器--服务器对象】中有详细讲解。

注:若无法播放,请检查网络环境是否正常(如:PC是否与开发板处于同一网段内,是否有IP冲突的情况等)

摄像机画面如下所示:

2.6 开机启动

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

cd /userdata/apps/ mkdir myapp

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

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

3. 代码组成

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

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

画面编码代码位于:EASY-EAI-Toolkit-C-Solution/solu-rtspIPCamera/src/enCoder/enCoder.cpp

RTSP传输代码位于:EASY-EAI-Toolkit-C-Solution/solu-rtspIPCamera/src/rtspServer/rtspServer.cpp

3.1 组件库组成

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

3.1.1 启动部分使用库情况

启动代码main.cpp主要负责启动并守护编码进程和RTSP传输进程,使用到的easyeai-api库组件如下所示。

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

组件 头文件以及库路径 描述
系统操作组件 easyeai-api/common_api/system_opt 提供系统操作函数

3.1.2 画面编码部分使用库情况

画面编码代码enCoder.cpp主要负责从mipi摄像头获取画面、把画面送入编码器、把编码后数据送入流媒体环形队列中等操作,使用到的easyeai-api库组件如下所示。

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

组件 头文件以及库路径 描述
摄像头组件 easyeai-api/peripheral_api/camera 提供时间戳操作函数
编码器操作组件 easyeai-api/media_api/endeCode_api 提供推入编码器操作函数、编码数据输出函数
环形队列组件 easyeai-api/media_api/frame_queue 提供推入编码数据到环形队列操作函数

3.1.3 RTSP传输部分使用库情况

Rtsp服务器代码rtspServer.cpp主要负责从环形队列中取出NALU送入RTSP服务等操作,使用到的easyeai-api库组件如下所示。

Rtsp服务器代码主要使用模块信息如下所示。

组件 头文件以及库路径 描述
Rtsp协议组件 easyeai-api/netProtocol_api/rtsp 提供RTSP服务器操作函数
环形队列组件 easyeai-api/media_api/frame_queue 提供从环形队列取出NALU数据操作函数

4. 逻辑框图

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

4.1 启动逻辑

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

主进程会创建1个RTSP服务器和1个编码器。RTSP服务器用于通过RTSP协议发送NALU包;编码器用于获取MIPI摄像头的图像,并且编码成NALU数据,送入流媒体环形队列。然后变成一个守护进程,监视RTSP服务器进程和编码器进程的运行状况,一旦崩溃则把它们重新创建。

创建RTSP服务器代码如下所示。

CreateProcess(PROCESS_RTSPSERVER_NAME, &st_TaskInfo);

创建编码器代码如下所示。

CreateProcess(PROCESS_ENCODER_NAME, &st_TaskInfo);

4.2 编码逻辑

编码逻辑代码路径为:src/enCoder/enCoder.cpp。

4.2.1 编码器——创建编码资源

首先是创建编码资源,包含:编码器、编码通道、输出通道(流媒体环形队列)。

创建编码器代码如下所示,其中MAXCHNNUM代表此编码器的可分配的最大通道数。

create_encoder(MAXCHNNUM);

创建编码通道代码如下所示,其中encodeChn_Id就是编码器自动分配出来的可用通道号,此通道Id的有效取值范围是0~MAXCHNNUM。

create_encMedia_channel(&encodeChn_Id);

创建输出通道代码如下所示,注意这个环形队列的通道数与编码器的通道无直接关系

create_video_frame_queue_pool(DATA_INPUT_CHN 1);

4.2.2 编码器——初始化mipi摄像头

初始化mipi摄像头的代码如下所示,分为两步:第一步初始化摄像头画面的宽高、显示角度;第二步改变摄像头的画面图像格式为nv12。

ret = rgbcamera_init(CAMERA_WIDTH, CAMERA_HEIGHT, 90); rgbcamera_set_format(RK_FORMAT_YCbCr_420_SP);

4.2.3 编码器——初始化编码器

初始化编码包含:向编码通道绑定输出回调函数;通过设置工作参数,指导编码通道的编码工作方式。具体代码如下所示。

set_encMedia_channel_callback(encodeChn_Id, StreamOutpuHandle, NULL); memset(&wp, 0, sizeof(WorkPara)); wp.in_fmt = VFRAME_TYPE_NV12; wp.out_fmt = VCODING_TYPE_AVC; wp.out_fps = 25; wp.width = CAMERA_WIDTH; wp.height = CAMERA_HEIGHT; ret = set_encMedia_channel_workPara(encodeChn_Id, &wp, NULL);

编码通道回调函数,源码如下所示。

int32_t StreamOutpuHandle(void *obj, VideoNodeDesc *pNodeDesc, uint8_t *pNALUData) { push_node_to_video_channel(DATA_INPUT_CHN, pNodeDesc, pNALUData); return 0; }

编码通道回调函数,传入参数说明。

参数 描述
obj 绑定解码回调函数时,传入的对象指针。以便可以在回调函数中操作此对象,此示例为NULL。
pNodeDesc NAL报文描述头
pNALUData NAL报文内容

编码通道普通工作参数,成员变量说明,如下所示:

参数 成员 描述
WorkPara
【必须】
in_fmt 输入数据的数据格式,如VFRAME_TYPE_NV12
out_fmt 输出数据的数据格式,如VCODING_TYPE_AVC
out_fps 输出视频裸流帧率,如25帧
width 输出视频的宽
height 输出视频的高
hor_stride 输出视频的水平步长,通常是宽的16位对齐。
ver_stride 输出视频的垂直步长,通常是高的16位对齐。

编码通道高级工作参数,成员变量说明,如下所示:

参数 成员 描述
AdvanceWorkPara
【非必须】
num_frames
gop_mode 高级GOP模式,默认不需要配置(填0)
gop_len 0:[I帧间隔采用2*fps_out_num], n:[I帧间隔为n(1==n,即每一帧都是I帧)]
vi_len
fps_out_flex 0:[固定的输出帧率, 输出帧率=(fps_out_num/fps_out_den)], 1:[可变的输出帧率,完成帧编码后马上输出]
fps_out_num 0:[使用默认值], n:[使用n值] -- 默认值为30
fps_out_den 0:[使用默认值], n:[使用n值] -- 默认值为1
fps_in_flex 0:[固定的输入帧率, 输入帧率=(fps_in_num/fps_in_den)], 1:[可变的输入帧率]
fps_in_num 0:[使用默认值], n:[使用n值] -- 默认值为30
fps_in_den 0:[使用默认值], n:[使用n值] -- 默认值为1
rc_mode 码率控制模式(CBR、VBR、AVBR、FIXQP)
bps_target 目标码率(CBR模式下使用)
bps_max 码率上限(VBR模式下使用)
bps_min 码率下限(VBR模式下使用)
split_mode slice 切分模式(0:[不切分], 1:[根据slice大小], 2:[根据宏块或CTU个数切分])
split_arg
split_out
osd_enable 不生效
osd_mode 不生效
user_data_enable 不生效
roi_enable 不生效

4.2.4 编码器——摄像头画面送入编码器

此处采用一个死循环,首先从摄像头接口读出数据到pbuf,然后pbuf数据送入编码通道,最后编码通道会自动地从回调函数中输出NALU。

while(1){ ret = rgbcamera_getframe(pbuf); if(ret){ usleep(10*1000); continue; } push_frame_to_encMedia_channel(encodeChn_Id, pbuf, IMAGE_SIZE); usleep(10*1000); }

4.3 传输逻辑

传输逻辑代码路径为:src/rtspServer/rtspServer.cpp。

4.3.1 RTSP服务器——初始化服务器

RTSP服务器通过rtspServerInit()函数进行【流媒体环形队列初始化】和【RTSP服务器初始化】。其中服务器初始化包含:初始化服务端口、对多路码流进行初始化。示例如下所示。

int rtspServerInit(const char *moduleName) { RtspServer_t srv; memset(&srv, 0, sizeof(RtspServer_t)); srv.port = 554; srv.stream[0].bEnable = true; srv.stream[0].videoHooks.pConnectHook = VideoStreamConnect; srv.stream[0].videoHooks.pDataInHook = VideoStreamDataIn; srv.stream[0].audioHooks.pConnectHook = NULL; srv.stream[0].audioHooks.pDataInHook = NULL; strcpy(srv.stream[0].strName, "aabb"); create_video_frame_queue_pool(DATA_INPUT_CHN 1); //初始化流媒体环形队列 //create_audio_frame_queue_pool(DATA_INPUT_CHN 1); create_rtsp_Server(srv); //启动RTSP服务器事件循环 // 正常情况下不会走到这里 PRINT_ERROR("Fatal Error! RtspServer loop exited!"); return -1; }

4.3.2 RTSP服务器——服务器对象

RTSP服务器对象的描述结构体的内容如下所示。

描述结构体:RtspServer_t
目录 easyeai-api/netProtocol_api/rtsp/rtsp_data.h
easyeai-api/netProtocol_api/rtsp/rtsp.h
成员 描述
port 服务器端口号
stream[n] 该服务器所提供的码流服务
bEnable 该码流是否使能
strName 该码流名称。如:strName为"mainStream",
url则为rtsp://username:password@192.168.1.69/mainStream
videoHooks 视频输入的钩子函数组
pConnectHook 在客户端向服务器发起视频流连接时所调用的函数,不使用可填NULL
pDataInHook 服务端会从此函数获取视频流输入数据,不使用可填NULL
audioHooks 音频输入的钩子函数组
pConnectHook 在客户端向服务器发起音频流连接时所调用的函数,不使用可填NULL
pDataInHook 服务端会从此函数获取音频流输入数据,不使用可填NULL

4.3.3 RTSP服务器——视频连接钩子函数

视频连接钩子函数:在客户端向服务器发起视频流连接时所调用的函数,不使用可填NULL。

void VideoStreamConnect() { flush_video_channel(DATA_INPUT_CHN); } …… int rtspServerInit(const char *moduleName) { …… srv.stream[0].videoHooks.pConnectHook = VideoStreamConnect; …… }

4.3.4 RTSP服务器——视频拉流钩子函数

视频连接钩子函数:服务端会从此函数获取视频流输入数据,不使用可填NULL。

int32_t VideoStreamDataIn(RTSPVideoDesc_t *pDesc, uint8_t *pData) { int ret = -1; VideoNodeDesc node; memset(&node, 0, sizeof(node)); ret = get_node_from_video_channel(DATA_INPUT_CHN, &node, pData); if(0 != ret){ return ret; } pDesc->frameType = node.bySubType; pDesc->frameIndex = node.dwFrameIndex; pDesc->dataLen = node.dwDataLen; pDesc->timeStamp = node.ddwTimeStamp; return 0; } …… int rtspServerInit(const char *moduleName) { …… srv.stream[0].videoHooks.pDataInHook = VideoStreamDataIn; …… }

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.2 CMakeLists.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.2 路径定位部分

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

5.3.2 清除编译部分

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

5.3.3 编译操作

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

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

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