海思流媒体项目总结

项目简述:
该项目基于海思3536平台开发实现的新一代的水利检测系统。主要源于水库区域检测人员对水库水位降雨量等环境指标的实时检测以及视频监控的这么一个需求。我们负责的这部分工作主要是将多路视频流拉入海思平台进行解码,对视频进行处理叠加采集来的环境数据osd,然后再将视频数据编码推送至流媒体服务器或者在本地hdmi输出显示以及本地转存。这里面就涉及到了利用ffmpeg对摄像头的rtsp流解封装,利用海思的编解码器进行硬编解码,利用SDL库进行位图的渲染等技术。

移植ffmpeg实现8路摄像头的的拉流:

FFMPEG对RTSP拉流的详细过程:

对ffmpeg进行交叉编译,海思得编译器

我们的开发板上面有8个网口分别接入八个摄像头,利用rtsp实时流媒体传输的网络协议。向网络摄像头发送rtsp请求,建立连接。利用RTP协议传输视频数据到开发板。

海康威视、大华、宇视rtsp实时读取网络摄像头_rtsp协议的摄像头-CSDN博客

关于rtsp协议:rtsp协议属于应用层协议与http不同的是http传输的是html静态页面,而rtsp传输的是多媒体数据。既可以采用TCP连接也可以使用UDP传输。他的体系由rtcp和rtp之上,其中rtcp主要用于控制传输,而rtp主要用于数据传输。

vdec通道

首先是建立vdec编码通道,该通道的创建是利用海思SDK所提供的程序接口实现,我们需要根据视频的长款,码率,帧率还有编码格式设定解码通道的属性,同时还需要根据解码的通道数以及需要解码的视频格式划分解码的缓存池,该缓存池用于接收视频帧数据。我们这里解码的输入视频数据是H.264格式数据。而他的输入数据解码后的YUV格式。该视频数据作为了下一VPSS通道的输入。

VPSS通道

VPSS是海思平台提供针对图像处理的模块,通过建立VPSS通道可以对解码后的视频每一帧的图像数据进行去噪,锐化甚至叠加OSD等处理操作,在该项目中我们主要进行了对水库区域视频流进行叠加OSD等操作,其中OSD是由传感器实时采集的降雨量,水位温度等信息。这里海思提供了两套在图像叠加图像的方式,一个coverlayer,一个是overlayer。coverlayer实现上是由海思的硬件来完成的,他比较适合对于静态内容的叠加。同时对于海思3536这款芯片,他只支持创建两个句柄,可以不同通道使用这同一个叠加句柄。而我们八路摄像头的叠加内容是不一样的同时需要进行动态更新变化,所以我们这里采用了overlayer来实现动态数据的叠加。

其叠加的原理是在图像数据上划分一片区域,并在这片区域上指定需要加载的图像的属性,以及数据,也就是说我们需要有实时数据的图像,然而3536并没有提供生成位图的相关接口。这里我移植了freetype和sdl库。freetype的主要作用是加载我们的字体文件信息,SDL根据我们的字体文件信息将我们所获取的传感器数据生程包含信息的位图。然后将这些位图的加载到我们划分的overlayer句柄,他们对应几路通道各自的VPSS。

我们创建了八个的叠加区域,绑定到对应的八路通道的VPSSGRP上,然后通过句柄来更新每个区域的图像。

有的海思芯片实际是不支持将OSD从VPSS部分进行叠加,而是在VENC阶段进行叠加,但是海思3536是支持的,同时在vpss上进行绑定的话必须将HI_MPI_RGN_AttachToChn中的通道号设置为0,而根据devId区分不同路的视频流。一个vpssGroup中包含了多条通道(3536最多支持四条),在不同的通道中可以根据使用的场景不同设置不同的属性。我们这里在vpss组中建立了两个通道分别用于编码模块的输入和VO模块的输入(VO模块用于本地的hdmi显示)。

vpss主要是做图像处理,经过VPSS模块处理后的图像数据的是可以由多个模块进行读取,例如接下来的VO以及VENC模块

vpss

VO通道

该通道的作用是将解压缩编码后的图像数据根据设置的显示图层输出。打个比方,我们现在要在一个1080*720的图层上输出显示两个通道的1080*720的视频流数据,那么在这一个图层上肯定是放不下的,VO通道可以根据我们划分准则将这些视频流数据进行缩放以及位置的摆放。而这里的图层就对应我们将来在显示器上各部分视频流的显示。

venc通道  

venc是我们的编码通道,这里面我们设置的编码属性基本上是根据原拉取视频流的数据属性保持一样,将只是将处理后的YUV格式的视频数据进行H264的压缩编码。编码后的数据我们主要做了两部分的操作,一是将编码后的数据进行转存到本地,而是将编码后的数据推流到流媒体服务器。推流这部分是我当时写完第一版OSD叠加后,临时过去协助的。这里我们主要遇到的问题就是流推出去后,拉流没有显示,一点显示都没有。

我们通过ffmpeg打开rtsp流,先获取到视频流上下文信息,这些上下文信息包括流的类型、编解码器、时间戳、时长、帧率、码率等等。利用这些信息我们开始读取视频。这里我们事先将保存视频帧的ffmpeg结构体指针的指向地址传递给了海思解码通道的输入结构体并发送至实现创建好的海思解码器。这里我们创建了八个线程针对不同路的视频流进行拉去传递,并且单独创建了一个线程来管理这八个线程,以实现热插拔或者连接意外中断时的重连机制,当其中一个线程意外结束或者创建失败,我们的监听线程就会根据具体的情况选择重启线程或者等待连接无异常后再重启拉流解码线程。

遇见的问题1:在开始我们的策略是在主线程当中循环对八路视频流获取的上下文信息,也就是串行建立连接再传递到八个线程中进行视频帧的读取,但是这会导致其中几路随机读取失败,同时程序容易崩溃。这个问题是我当时解决的,我先是尝试了在减少几路的情况下进行拉取,就发现除了一路的情况,其他不管几路都会出现这种问题,那我们想的就是建立连接获取的上下文受到了影响,而且这种影响还是相互之间的影响,后面我们将尝试了下在子线程分别进行获取连接上下文拉流,就没有出现这种问题了。

关于这个连接上下文,我们分析他并非是线程安全的,也就是说一个线程只能由该上下文的实体,如果我们在线程中循环建立了多个连接上下文,那么有可能就导致资源竞争,在创建上下文的时候导致上下文信息错误。

为什么不使用GB28181:

GB28181是国家发布的关于视频监控安防领域的网络视频监控系统技术规范。他是基于sip信令会话通过传输层rtp传输视频数据,rtcp进行传输流控制。主要适用于大规模得城市安防交通监控等场景。而我们得拉流与推流都是基于一个轻量化得边缘设备,rtsp相较于硬件和网络资源得需求较低,同时也更简易实现,具有较高的灵活性。

rtsp/rtmp:

海康rtsp流地址i格式:

rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream

H264编码解码原理是怎么样的:

在传输过程中,视频数据的一帧往往是被划分成了多个RTP包进行传输,ffmpeg与服务端建立连接时会获取上下文信息,包括传输协议文件格式以及接收数据流的缓冲区地址等信息。客户端在开始接收视频时,服务端发送来的视频帧数据会被放入ffmpeg管理的缓冲区中。当我们需要读取一帧视频数据,ffmpeg会根据时间戳以及序号将同一帧数据的rtp包合成一帧数据交由我们读取。在H264格式的数据是一种经过压缩后的视频格式,我们正常在显示器上播放的是一张一张的rgb图像,这类数据如果直接传输那将是是特别大的,如果视频格式是30帧,1080*720,那么也就意味着我们需要在一秒中内传输30*1080*720bit的数据,还没算上各种协议头,这对我们的传输资源的占用是极大的。那么h264呢就是通过视频数帧之间的相似程度来将我们的YUV这类可用于播放的视频数据的视频帧划分成了I帧,P帧,B帧。其中I帧数据是一帧完整的图像数据可以直接进行转换显示,而P帧依靠前一帧的数据经过相似度等计算得到,B帧则是根据他的前向帧以及后向帧的数据得到。一般H264编码后的数据帧被分成了很多组称为Gop,一个组的第一个视频帧一定是I帧,而每个组之间间隔的P,B帧越多说明压缩率越大。但是PB帧的显示效果依赖于当前序列的I帧的质量,所以GOP的长度不易过大。

YUV视频数据是通过人眼对亮度变化的敏感性,将我们的视频数据以亮度分布大小以及颜色分量的形式进行标识,其中Y指的就是亮度,VU指的就是两个色度分量。为什么不直接采用RGB格式呢,首先是适用范围更广,兼容性更强,RGB对于黑白电视的场景的显示并不方便还需要经过多余的计算得到亮度,而YUV直接可以将UV部分的数据进行丢弃即可,这也方便了很多场景下的视频图像处理的计算量。同时UV的在对RGB颜色空间表示的时候可以进行在保证图像质量不明显下降的同时进行降采样,丫UV444与yuv420与yuv422代表了其采样比例 ,这大大减少了视频存储所需要的空间。而我们需要在显示器上进行图像显示时也只需要做一个转换得到RGB的显示数据。

海思推流无图片显示问题:

在海思进行编码的过程中I帧的数据结构中会包含sps与pps等信息。海思编码默认使用的是多包模式也就是在获取包的结构体中的packet实际不止一个,而是四个。常出现的问题是在进行推流过程中只是将第一个packet的数据进行推流,导致I帧的数据包只发送了sps部分,后面的信息丢失。注意这里的packet不管是不是夺宝模式其中的图像数据都不一定是一帧数据,而是I帧的某一分片数据。I帧只做帧内预测。

关于叠加的图像没法背景透明显示:

我当时采用了RGB8888的格式保存的图像,也就是32位拥有透明通道的RGBA图像但是保存的图像的背景并不是透明的,我在多个阶段打印了生成的图像数据,数据是没问题的,但是当保存程位图的时候他始终就是变成非透明的格式。后面我尝试不采用SDL库提供的保存函数,而是写一个利用标准IO里的fopen这种原生的库函数去写入数据,自己根据BMP图像的格式加上信息头保存,但还是无法背景透明。因为当时整个项目还是初期主要是将流程走通,所以我也是走了一个捷径,保存的位图不是无法透明嘛,那在将数据加载进内存中时他的阿尔法通道对应的背景像素手动修改成0,这里算是暂时解决了问题。但是这种是非常占用运算资源,这相当于要将位图的每个像素都要做一次判断。

然而在后期优化降程序负载的时候我们就不得不去考虑这个问题了,我尝试在WIN上运行同样的代码,在win上保存的就是透明的,,然后又在我自己的虚拟机上也尝试过,也是没问题。同时我们将这些生成没问题的图片放到了我们的开发板上进行叠加,并且不再进行手动修改像素值,那实际显示出来没问题,也就是说SDL生成的图像格式没问题,加载也是支持包含阿尔法通道的位图的,那就只能是保存过程没能将我们的透明通道保存。这个时候我就想到了我在实现这段代码的时候就考虑到的一类优化方案,因为我们最终的目的都是为了降负载,按照之前的方法保存图像后,再读取图像这就涉及到两次系统调用,我们的数据更新周期是很快的,同时还是八路,这就导致了频繁的用户态与内核态特权模式之间的切换,所以这本身不是明智之举,加之现在保存这部分无法保存包含透明通道的图像,但生成和加载这部分是没问题的,那我何不直接省去保存这部分,直接将生成的位图对接至相应的叠加层的句柄中,这是就一石二鸟。后面事实证明这个方案是可行的,而且避免了循环调用后,CPU负载也是下载了很多。

关于抓取图片的接口返回的地址没有实际图片数据的问题?

海思的mpp提供了一个用于抓取视频流图片的接口函数,可以用于解码后与编码之前对视频数据进行抓取一帧图像,然而实际在利用该函数进行抓取图像后会返回一个指针,但是对该指针指向的内存进行保存是一直是发生段错误或者保存没有实际数据。后面经过查阅开发文档发现该函数实际返回的是一个物理内存地址。因为当前流媒体数据的处理实际是在专门的图像处理硬件上进行处理,要抓取当前时刻的图片就得从该硬件输出的混村中(或许是通过DMA引擎直接读取至内存)读取一帧图像放入内存,同时返回了该图像的物理内存地址,利用该地址需要进一步使用MMU单元映射至进程的虚拟地址空间。

至于海思为什么要将该API设计成直接返回物理地址进行返回应该是为了方便当前进程将抓取的数据共享给其他进程,直接返回虚拟空间地址又得采用共享内存或者管道通信将抓取的数据拷贝,不如直接给其他进程发送自己获取数据的物理地址,自己在进行一次物理内存到虚拟空间的映射。

海思3536的功能框图:

海思3536处理器内部主要硬件单元模块有:

ARM核心:包含一个四核A17cpu,主频1.4GHZ 。一个单核A7cpu主频900Mhz。

视频编/解码器

GPU单元

智能加速引擎:用于移动侦测。

图形处理模块:vpss,HDMI。

在海思3536中只有2级缓存,但是他在核心外部还有一个高速存储器SRAM连接着各部分硬件模块(应该是由各部分硬件模块共享使用)。

海思芯片在启动Uboot会为mpp与CPP划分物理内存,分别是mmz内存与OS内存。可以在/proc/media-mem文件夹下查看当前mmz内存的使用情况。

在uboot的命令模式下可以修改os内存与MMZ内存的分配:

setenv bootargs 'mem=512M console=ttyAMA0,115200 rw root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1M(boot),4M(kernel),27M(rootfs)'
saveenv

叠加区域的优化:

叠加区域经过压缩编码后可能画质会受到影响,可以通过设置QP保护,针对叠加区域单独设置压缩特性。

编码参考帧列表优化 

设置合适的参考帧表长度或者数目,越多编码后的的画质可能会更好,但是相应的延时以及所占的内存空间也会更大,根据开发手册建议设置成2.

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值