RK/RV--NVR网络视频录像机技术方案

2023-11-22 21:40

本文主要是介绍RK/RV--NVR网络视频录像机技术方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

技术分析

一、缘起

​ 小组之前做的一个需要国产化定制NVR(网络视频录像机)需求的项目,最后因为成本原因被弃了,现在做一些技术总结和分享。主要分享方案,项目中的技术难点不多说。小组以前项目的累计主要在STM32方面,不成熟的地方希望多多批评指正。

二、技术需求

简要的技术需求如下:

  • 接收最多16路网络摄像头的视频流
    • 显示:可配置1/2/4/9分屏显示,通过HDMI/VGA 输出
    • 存储:可储存16路视频流数据
    • 支持配置记忆
  • 使用WEB和上位机软件配置
    • 支持可配置的数据回放
    • 支持可选择通道的视频分屏显示
  • 模拟雷达成像软件
    • 通过上位机软件动态模拟雷达成像结果。
    • 仿真输入文件为目标物体的极坐标和时间信息,格式自定义。
    • 目标物体不少于3 个
    • 仿真周期不小于10min,可设置成单次仿真和循环仿真。

与上位机软件和配置相关的技术和接口不宜分享,我主要负责与视频相关的部分(也就是NVR的主要功能),本篇展示了项目前期的代码。

三、技术方案

硬件的结构很简单如下(3.1 结构框图):
请添加图片描述

主要模块使用多线程完成(没有画出来,不涉复杂及同步和通信),软件设计流程图也很简单(3.2 软件流程图):
请添加图片描述

与上位机的通信用的TCP-Modbus协议,小组之前这方面有积累。

设备软件开发及运行环境为:Linux-debian10操作系统,在基础系统配置安装的基础上需要安装配置OPENGL、FFMPEG、MMP、OPENCV(C++)等库文件 ,编译器为aarch64-linux-gnu-g++。

四、技术模块分析

​ 核心为三个过程:视频取流、视频解码、视频输出。存储涉及文件队列,为了节省空间直接存储码流(后续优化)。

1、视频取流

软件上ffmpeg已经有了完整的视频取流、视频编解码方案,我直接使用硬件的解码替代掉原有的软解码就行。

这部分学习的是雷霄骅前辈的总结。

2、视频解码

RK有专门的(H264/265)硬件编解码接口MPP,RK官方有MPP的使用说明,这里就不详诉了。还有对图像进行裁剪、旋转的接口RGA。(4.1 MPP多线程编解码使用流程)

请添加图片描述

这里有几个坑:

  • 就是MPP最大只能6通道1080P@30帧显示,对于我们的项目需求只有降帧情况下才能正常使用,当然我们使用软件+硬件解码的方式处理。
  • MPP多实例时还要求上下文独立。
  • 解码花屏问题https://blog.csdn.net/qq_41117054/article/details/127405013
3、视频输出

视频输出使用的一个笨拙的方法:DRM,有很多好用的接口我并没有使用,当时项目比较赶没时间学。
会出现花屏(后面的版本我用单独开一个显示线程用的环形队列显示)

五、核心代码解读

首先初始化ffmpeg、MMP:

int GetStream::Init()
{//av_register_all();  //函数在ffmpeg4.0以上版本已经被废弃,所以4.0以下版本就需要注册初始函数avformat_network_init();av_dict_set(&options, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值跳到最大av_dict_set(&options, "rtsp_transport", "tcp", 0); //以tcp的方式打开,av_dict_set(&options, "stimeout", "5000000", 0); //设置超时断开链接时间,单位usav_dict_set(&options, "max_delay", "500000", 0); //设置最大时延pFormatCtx = avformat_alloc_context(); //用来申请AVFormatContext类型变量并初始化默认参数,申请的空间if (avformat_open_input(&pFormatCtx, filepath, NULL, &options) != 0){printf("Couldn't open input stream.\n");return 0;}//获取视频文件信息if (avformat_find_stream_info(pFormatCtx, NULL)<0){printf("Couldn't find stream information.\n");return 0;}//查找码流中是否有视频流videoindex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
/*     for (i = 0; i<pFormatCtx->nb_streams; i++)if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){videoindex = i;break;} */if (videoindex < 0){printf("Didn't find a video stream.\n");return 0;}av_packet = (AVPacket *)av_malloc(sizeof(AVPacket)); // 申请空间,存放的每一帧数据 (h264、h265)初始化MPP_RET ret         = MPP_OK;size_t file_size    = 0;MpiCmd mpi_cmd      = MPP_CMD_BASE;MppParam param      = NULL;RK_U32 need_split   = 1;
//    MppPollType timeout = 5;// paramter for resource mallocRK_U32 width        = 2560;RK_U32 height       = 1440;MppCodingType type  = MPP_VIDEO_CodingAVC;mpp_log("mpi_dec_test start\n");memset(&data, 0, sizeof(data));data.fp_output = fopen("./tenoutput.yuv", "wb");if (NULL == data.fp_output) {mpp_err("failed to open output file %s\n", "tenoutput.yuv");DeInit();}mpp_log("mpi_dec_test decoder test start w %d h %d type %d\n", width, height, type);// decoder demoret = mpp_create(&ctx, &mpi);if (MPP_OK != ret) {mpp_err("mpp_create failed\n");DeInit();}// NOTE: decoder split mode need to be set before initmpi_cmd = MPP_DEC_SET_PARSER_SPLIT_MODE;param = &need_split;ret = mpi->control(ctx, mpi_cmd, param);if (MPP_OK != ret) {mpp_err("mpi->control failed\n");DeInit();}
/*mpi_cmd = MPP_SET_INPUT_BLOCK;param = &need_split;ret = mpi->control(ctx, mpi_cmd, param);if (MPP_OK != ret) {mpp_err("mpi->control failed\n");DeInit();}mpi_cmd = MPP_SET_INTPUT_BLOCK_TIMEOUT;param = &need_split;ret = mpi->control(ctx, mpi_cmd, param);if (MPP_OK != ret) {mpp_err("mpi->control failed\n");DeInit();}
*/ret = mpp_init(ctx, MPP_CTX_DEC, type);if (MPP_OK != ret) {mpp_err("mpp_init failed\n");DeInit();}data.ctx            = ctx;data.mpi            = mpi;data.eos            = 0;data.packet_size    = packet_size;data.frame          = frame;data.frame_count    = 0;buffer = (char*)malloc(4*displayObject.rga_align.height*displayObject.rga_align.width);std::cout << "Init finish" << std::endl;return 0;
}

建立ffmpeg的rtsp连接后开启线程,ffmpeg接收到视频流后将流丢到解码函数区。

void* decode_pth(void* data_)
{auto *obj = (GetStream*) data_;MpiDecLoopData *data = &obj->data;AVPacket *av_packet = obj->av_packet;void* ret;while (obj->decode_flag){if (av_read_frame(obj->pFormatCtx, av_packet) >= 0){if (av_packet->stream_index == obj->videoindex){mpp_log("--------------\ndata size is: %d\n-------------", av_packet->size);ret = decode_simple(obj);}if (av_packet != NULL)av_packet_unref(av_packet);mpp_log("%d", obj->videoi);}//usleep(5000);}return ret;
}

解码后将解码数据丢到displayObject.display()里面去

void* decode_simple(void* data_)
{auto *obj = (GetStream*) data_;MpiDecLoopData *data = &obj->data;AVPacket *av_packet = obj->av_packet;RK_U32 pkt_done = 0;RK_U32 pkt_eos  = 0;RK_U32 err_info = 0;MPP_RET ret = MPP_OK;MppCtx ctx  = data->ctx;MppApi *mpi = data->mpi;// char   *buf = data->buf;MppPacket packet = NULL;MppFrame  frame  = NULL;size_t read_size = 0;size_t packet_size = data->packet_size;ret = mpp_packet_init(&packet, av_packet->data, av_packet->size);mpp_packet_set_pts(packet, av_packet->pts);clock_t startTime;clock_t endTime;do {RK_S32 times = 5;// send the packet first if packet is not doneif (!pkt_done) {startTime = clock();ret = mpi->decode_put_packet(ctx, packet);if (MPP_OK == ret)pkt_done = 1;}// then get all available frame and releasedo {RK_S32 get_frm = 0;RK_U32 frm_eos = 0;try_again:ret = mpi->decode_get_frame(ctx, &frame);if (MPP_ERR_TIMEOUT == ret) {if (times > 0) {times--;msleep(2);goto try_again;}mpp_err("decode_get_frame failed too much time\n");}if (MPP_OK != ret) {mpp_err("decode_get_frame failed ret %d\n", ret);break;}if (frame) {if (mpp_frame_get_info_change(frame)) {RK_U32 width = mpp_frame_get_width(frame);RK_U32 height = mpp_frame_get_height(frame);RK_U32 hor_stride = mpp_frame_get_hor_stride(frame);RK_U32 ver_stride = mpp_frame_get_ver_stride(frame);RK_U32 buf_size = mpp_frame_get_buf_size(frame);mpp_log("decode_get_frame get info changed found\n");mpp_log("decoder require buffer w:h [%d:%d] stride [%d:%d] buf_size %d",width, height, hor_stride, ver_stride, buf_size);ret = mpp_buffer_group_get_internal(&data->frm_grp, MPP_BUFFER_TYPE_ION);if (ret) {mpp_err("get mpp buffer group  failed ret %d\n", ret);break;}mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, data->frm_grp);mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);} else {err_info = mpp_frame_get_errinfo(frame) | mpp_frame_get_discard(frame);if (err_info) {mpp_log("decoder_get_frame get err info:%d discard:%d.\n",mpp_frame_get_errinfo(frame), mpp_frame_get_discard(frame));}data->frame_count++;mpp_log("decode_get_frame get frame %d\n", data->frame_count);if (!err_info){if(obj->display_mode){MppBuffer buff = mpp_frame_get_buffer(frame);mpp_buffer_handle((char *)mpp_buffer_get_ptr(buff),obj->buffer);//convertdata((char *)mpp_buffer_get_ptr(buff),obj->buffer,&format);displayObject.display(obj->buffer,obj->display_id);}}}frm_eos = mpp_frame_get_eos(frame);mpp_frame_deinit(&frame);frame = NULL;get_frm = 1;}// try get runtime frame memory usageif (data->frm_grp) {size_t usage = mpp_buffer_group_usage(data->frm_grp);if (usage > data->max_usage)data->max_usage = usage;}// if last packet is send but last frame is not found continueif (pkt_eos && pkt_done && !frm_eos) {msleep(10);continue;}if (frm_eos) {mpp_log("found last frame\n");break;}if (data->frame_num > 0 && data->frame_count >= data->frame_num) {data->eos = 1;break;}if (get_frm)continue;break;} while (obj->decode_flag);endTime = clock();std::cout << "id = " << obj->display_id << " decode time = " << endTime - startTime << std::endl;if (pkt_done)break;/** why sleep here:* mpi->decode_put_packet will failed when packet in internal queue is* full,waiting the package is consumed .Usually hardware decode one* frame which resolution is 1080p needs 2 ms,so here we sleep 3ms* * is enough.*/msleep(3);} while (obj->decode_flag);mpp_packet_deinit(&packet);return nullptr;
}

DRM显示:

我的display是用C格式写的如下

int init(int display_num)
{uint32_t conn_id;uint32_t crtc_id;std::cout << "display init start" << std::endl;displayObject.fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);res = drmModeGetResources(displayObject.fd);crtc_id = res->crtcs[0];conn_id = res->connectors[0];conn = drmModeGetConnector(displayObject.fd, conn_id);displayObject.buf.width = conn->modes[0].hdisplay;displayObject.buf.height = conn->modes[0].vdisplay;modeset_create_fb(displayObject.fd, &displayObject.buf);drmModeSetCrtc(displayObject.fd, crtc_id, displayObject.buf.fb_id,0, 0, &conn_id, 1, &conn->modes[0]);displayObject.displayLocation = new display_location[display_num];if(split_screen(display_num) < 0 ){std::cerr << "split_screen error" << std::endl;return -1;}//16字节对齐std::cout << "display init final" << std::endl;return 0;
}static int modeset_create_fb(int fd, struct buffer_object *bo)
{struct drm_mode_create_dumb create = {};struct drm_mode_map_dumb map = {};create.width = bo->width;create.height = bo->height;create.bpp = 32;drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);bo->pitch = create.pitch;bo->size = create.size;bo->handle = create.handle;drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,bo->handle, &bo->fb_id);map.handle = create.handle;drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);bo->vaddr = (uint8_t *)mmap(0, create.size, PROT_READ | PROT_WRITE,MAP_SHARED, fd, map.offset);memset(bo->vaddr, 0xff, bo->size);return 0;
}

将获取到的屏幕-buffer进行分割

static int split_screen(int display_num)
{int width,height,display_mode;int screen_w = displayObject.buf.width;int screen_h = displayObject.buf.height;if(display_num>0){if(display_num == 1){width = screen_w;height = screen_h;display_mode=1;} else if(display_num == 2){display_mode=2;width = screen_w/2;height = screen_h;} else if(display_num <= 4){display_mode=4;width = screen_w/2;height = screen_h/2;} else if(display_num <= 6){display_mode=6;width = screen_w/3;height = screen_h/2;} else if(display_num <= 9){display_mode=9;width = screen_w/3;height = screen_h/3;} else{std::cerr << "split_screen error" << std::endl;return -1;}}else{display_mode=0;return 0;}displayObject.width = width;displayObject.height = height;displayObject.display_mode = display_mode;for(int i = 0; i < display_num; ++i){displayObject.displayLocation[i].x0 = (width * i) % screen_w;displayObject.displayLocation[i].y0 = height * (width * i / screen_w);displayObject.displayLocation[i].x1 = displayObject.displayLocation[i].x0 + width - 1;displayObject.displayLocation[i].y1 = displayObject.displayLocation[i].y0 + height - 1;std::cout << "i,x0,y0,x1,y1" << i <<","<< displayObject.displayLocation[i].x0 <<","<< displayObject.displayLocation[i].y0 <<","<< displayObject.displayLocation[i].x1 <<","<< displayObject.displayLocation[i].y1 <<std::endl;}//displayObject.location_y = (screen_h - (height *width)/screen_w )/2;displayObject.rga_align.height = ((height+15)/16)*16;displayObject.rga_align.width = ((width+15)/16)*16;return 0;
}

根据之前的流id和分割的屏显示出来

static void lcd_fill(char *buff,int obj_id)
{int id = obj_id-1;//std::cout << "i,x0,y0,x1,y1" << id <<","<< displayObject.displayLocation[id].x0 <<","<< displayObject.displayLocation[id].y0 <<","<< displayObject.displayLocation[id].x1 <<","<< displayObject.displayLocation[id].y1 <<std::endl;int start_x = displayObject.displayLocation[id].x0;int start_y = displayObject.displayLocation[id].y0;int end_x = displayObject.displayLocation[id].x1;int end_y = displayObject.displayLocation[id].y1;int width = displayObject.buf.width;int height = displayObject.buf.height;int *screen_base = (int *)displayObject.buf.vaddr;int *src_buff = (int*)buff;int i = 0,temp,x;if (end_x >= width){std::cout << "end_x >> screen width : " << end_x << std::endl;end_x = width - 1;}if (end_y >= height){std::cout << "end_y >> screen width : " << end_y << std::endl;end_y = height - 1;}temp = start_y * width; //定位到起点行首for ( ; start_y <= end_y; start_y++, temp+=width) {for (x = start_x; x <= end_x; x++,i++){screen_base[temp + x] = src_buff[i];}}
}

六、效果展示

这是使用的两个摄像头,但是不是读取的同一个流。四分屏的实时性很好,但是九分屏有一个实时性跟不上。
请添加图片描述
请添加图片描述

这篇关于RK/RV--NVR网络视频录像机技术方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/413116

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~