飞凌RZ/G2L的开发板试用测评报告三--实时视频编码推流设计与实现 大信(QQ:8125036)
在前面的试用测试基础上,逐渐熟悉了RZ/G2L开发板的SDK,在研究过它的音视频硬件与软件包后,在想进一步利用该开发板做音视频的深度的应用开发。前面已经实现了在开饭上采集视频,采集视频是视频开发的基础,基本熟悉了板子支持V4L2软件工具,其实V4l2还能做很多其它的功能。同样还有很多其它的音视频软件也有很强大的功能,比如ffmpeg ,GStream,VLC等开源的软件库等。 在本次试用中尝试将这些软件移植到该平台上,以便发挥该平台硬件的优势性能。这里就结合该开发板的音视频功能进一步进行音视频高级功能进行开发与测试。
图1
一、实时视频编码推流整体设计
为了完成在RZ/G2L板上的实时视频采集编码与网络发送,需要整体的做设计,设计确定好整体测试的
通信协议,以及配合测试的方式。
实时视频网络推流,是指的是把采集阶段封包好的内容传输到服务器的过程。主流的推送协议和优缺点有:
RTMP是Real
time Messaging Protocol(实时消息传输协议)的缩写,是Adobe公司为Flash/AIR平台和服务器之间音、视频及数据传输开发的实时消息传送协议。RTMP协议基于TCP,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。
RTMP协议中,视频必须是H264编码,音频必须是AAC或MP3编码,且多以flv格式封包。RTMP是目前最主流的流媒体传输协议,对CDN支持良好,实现难度较低,是大多数的直播平台的选择。
不过RTMP有着一个最大的不足——不支持浏览器,且Adobe已不再更新。因此直播服务要支持浏览器的话,需要另外的推送协议支持。
Http Live Streaming是由Apple公司定义的基于HTTP的流媒体实时传输协议。它的原理是将整个流分为多个小的文件来下载,每次只下载若干个。服务器端会将最新的直播数据生成新的小文件,客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。基本上,HLS是以点播的技术实现了直播的体验。因为每个小文件的时长很短,客户端可以很快地切换码率,以适应不同带宽条件下的播放。
分段推送的技术特点,决定了HLS的延迟一般会高于普通的流媒体直播协议。
传输内容包括两部分:一是M3U8描述文件,二是TS媒体文件。TS媒体文件中的视频必须是H264编码,音频必须是AAC或MP3编码。
由于数据通过HTTP协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短。
WebRTC(Web Real-Time Communication),即“源自网页即时通信”。WebRTC是一个支持浏览器进行实时语音、视频对话的开源协议。WebRTC的支持者甚多,Google、Mozilla、Opera推动其成为W3C推荐标准。
WebRTC支持目前的主流浏览器,并且基于SRTP和UDP,即便在网络信号一般的情况下也具备较好的稳定性。
此外,WebRTC可以实现点对点通信,通信双方延时低,此外,WebRTC可以实现点对点通信,通信双方延时低,是实现“连麦”功能比较好的选择。
UDP方式推流,有些直播应用会使用 UDP 做为底层协议开发自己的私有协议,因为 UDP 在弱网环境下的优势通过一些定制化的调优可以达到比较好的弱网优化效果,但同样因为是私有协议也势必与其它系统互联互通有一定问题。
它的优点是,协议简单能够进行定制化优化,能够在网内进行视频流组播,为后续视频滤镜,特效开发形成网络分布式的工作环境,又开发方便,视频传输延时低。能够较快的实现那一套视频实时系统。缺点就是只能在局域网传输,无法把视频流发布到互联网上。
但此次主要目的为了测试开发板的编码与网络实时传输的性能,因此本次方案选择了UDP的技术方案。
本次实验的运行结构模式如下图:
图2
整体分为视频推流端,即在开发板完成视频采集,编码和UDP发送视频流数据。在视频流发送端软件设计如下,主要设计为多线程方式,分为三个线程,分别完成视频采集工作,视频编码工作,网络通讯工作,三个线程通过内存队列块进行数据的转移和存储。这些模块总体运行的设计时序如下图:
图3
接收端,即在Ubuntu上实现UDP数据包接收,解码还原成视频帧并显示到窗口上的过程。视频接收方设计为两个工作线程,一个数据队列,其中一个线程完成数据的接受,因为采用UDP的协议,这里接收方设计为先启动,开发监听端口等待UDP数据包,接收到数据包后,解包,把数据包再放入接收队列中。另外一个线程完成从队列里获取解包后的H264数据,进行解码,生成图像帧内容,然后形成内存图片,然后刷新到UI主线程界面中去,接收端模块设计工作调用和时序如下图所示:
图4
有了整体的设计后,就可以开始开发板上运行的发送端以及Ubuntu上的接收播放端软件。
二、V4l2视频开发介绍
在前一篇视频采集的开发里已经用到了V4L2视频框架,V4L2(Video for Linux2), 是一个专门为 linux 设备设计的视频采集和视频设备管理框架,是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。它使应用与硬件设备接口无关,音视频应用不必关心底层硬件的差异与实现。在不同硬件平台上,V4L2有自己的适配和支持。在RZ/G2L板上是实现了该视频框架,这里先使用V4L2进行一些测试与开发。
图5
l Video capture device :从摄像头等设备上获取视频数据。video capture是V4L2的基本应用。设备名称为/dev/video,主设备号81,子设备号0~63
l Video output device :将视频数据编码为模拟信号输出。与video capture设备名相同。
l Video overlay device :将同步锁相视频数据(如TV)转换为VGA信号,或者将抓取的视频数据直接存放到视频卡的显存中。
l Video output overlay device :也被称为OSD(On-Screen Display)
三、V4l2基本操作流程
l VBI device :提供对VBI(VerticalBlanking Interval)数据的控制,发送VBI数据或抓取VBI数据。设备名/dev/vbi0~vbi31,主设备号81,子设备号224~255
l Radio device :FM/AM发送和接收设备。设备名/dev/radio0~radio63,主设备号81,子设备号64~127
v4l2设备的基本操作流程如下
1、打开设备,例如 fd =open("/dev/video0",0)
2、查询设备能力. 如:
structcapability cap;
ioctl(fd,VIDIOC_QUERYCAP,&cap)
3、设置优先级(可选)
4、配置设备. 包括:
视频输入源的视频标准,VIDIOC_*_STD
视频数据的格式 , VIDIOC_*_FMT
视频输入端口, VIDIOC_*_INPUT
视频输出端口,VIDIOC_*_OUTPUT
5、启动设备开始I/O操作。
V4L2支持一下三种I/O方式:
Read/Write:通过调用设备节点文件的Read/Write函数,与设备交互数据。打开设备后,默认使用的是此方法。
StreamI/O:流操作,只传递数据缓冲区指针,不拷贝数据。使用此方法,需要调用VIDIOC_REQBUFS ioctl来通知设备。流操作I/O有两种方式memory map和user buffer。(具体区别后面章节介绍)
overlay:也可以理解为memory tomemory 传输。将数据从内存拷贝到显存中。overlay设备独有的。
对于Capture device可以以如下方式启动设备:
调用VIDIOC_REQBUFS ioctl来分配缓冲区队列;
调用VIDIOC_STREAMON ioctl通知设备开始stream IO
调用VIDIOC_QBUF ioctl从设备获取一帧视频数据;
使用完数据后,调用VIDIOC_DQBUF将缓冲区还给设备,以便设备填充下一帧数据。
6、释放资源并关闭设备。
图6
在Ubuntu下开发代码如下,主要完成了视频流接收,解包,解码最后送到 QT窗口上不断刷新显示,就完成了UDP实时视频流的接收与播放。
四、X264视频编解码库编译
在实时视频传输中,需要对视频进行压缩,减少数据流的size,才能在网络中传输。因此我们需要使用视频编码,在编码时,我们可以选择系统已经自带的编码库,编码库是开发板随板提供的编码SDK,随板的编码库,支持了板载硬编码方式,方便直接调用。
而在使用中,我们有时需要自己优化或者改造编码,那么使用软编码也是一种方案,这里我们也准备了软编码库的编译,并且在播放端也需要对视频进行解码,那么也需要视频解码。,
首先通过互联网获取 x264 代码库,如下:
git clonehttps://github.com/mirror/x264
图7
在PC上开始进行配置编译
./configure --enable-static --enable-shared --disable-asm
图8
然后进行编译
make
图9
图10
就得到了最新版本的X264编解码库。
同时我们也可以在交叉编译环境下,尝试编译开发板上运行的x264库,编译配置经过尝试,以及修改相关config脚本后,执行配置命令如下:
首先切换环境为交叉编译环境,执行source 命令:
source/home/lutherluo/workspace/G2LD/OKG2L-linux-sdk10/environment-setup-aarch64-smarc-rzg2l-toolchain
然后修改configure,让其支持板上的poky-linux系统,然后使用如下参数,进行makefile 文件的配置生成
./configure --enable-shared --host=aarch64-linux--cross-prefix=aarch64-poky-linux---sysroot=/opt/poky/3.1.5/sysroots/aarch64-poky-linux
最后开始编译
make
图11
图12
最终编译成功过,可以检查一下编译出的x264库,是
ARM64 的动态连接库。
五、实时视频编码推流代码实现
有了上面的支持库的准备工作,就可以着手编写主机端的播放器软件以及开发板上的采集编码推流软件。
主机上采用QT开发包,开发一个简单的桌面应用,使用QTDesigner快速的创建一个窗口,窗体中放置一个wiget控件做为视频帧显示区,下部中间放置几个按钮,连接好信号,槽后,按上面设计的时序图完成,各个线程代码和队列的实现代码,其界面设计和代码设计如下图:
图13
图14
代码开发运行显示如下图,即等待H264的UDP数据中。
图15
核心部分代码如下:
- V4L2_Video::V4L2_Video(QObject *parent) : QObject(parent)
- {
- m_is_start = false;
- pthread_mutex_init(&m_encode_queue_lock, nullptr);
- pthread_mutex_init(&m_send_queue_lock, nullptr);
- }
- int V4L2_Video::init()
- {
- struct v4l2_capability cap;
- struct v4l2_format fmt;
- struct v4l2_streamparm stream_parm;
- struct v4l2_requestbuffers req;
- struct v4l2_buffer buf;
- unsigned int buffer_n;
- pthread_t thread_id;
- int ret;
- fd = open(VIDEO_FILE, O_RDWR);
- if(fd == -1)
- {
- printf("Error opening V4L2 interfacen");
- return -1;
- }
- // 查询设备属性
- ioctl(fd, VIDIOC_QUERYCAP, &cap);
- printf("Driver Name:%snCard Name:%snBus info:%snDriver Version:%u.%u.%un",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF);
- //显示所有支持帧格式
- struct v4l2_fmtdesc fmtdesc;
- fmtdesc.index=0;
- fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
- printf("Support format:n");
- while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
- {
- printf("t%d.%sn",fmtdesc.index+1,fmtdesc.description);
- fmtdesc.index++;
- }
- // 设置帧格式
- fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 传输流类型
- fmt.fmt.pix.width = IMAGEWIDTH; // 宽度
- fmt.fmt.pix.height = IMAGEHEIGHT; // 高度
- fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 采样类型
- // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
- fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; // 采样区域
- ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
- if(ret < 0)
- {
- printf("Unable to set formatn");
- goto label_exit;
- }
- // 设置帧速率,设置采集帧率
- stream_parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- stream_parm.parm.capture.timeperframe.denominator = VIDEO_FPS;
- stream_parm.parm.capture.timeperframe.numerator = 1;
- ret = ioctl(fd, VIDIOC_S_PARM, &stream_parm);
- if(ret < 0)
- {
- printf("Unable to set frame raten");
- goto label_exit;
- }
- // 申请帧缓冲
- req.count = ENCODE_QUEUE_FRAME_NUM;
- req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- req.memory = V4L2_MEMORY_MMAP;
- ret = ioctl(fd, VIDIOC_REQBUFS, &req);
- if(ret < 0)
- {
- printf("request for buffers errorn");
- goto label_exit;
- }
- // 内存映射
- video_buffer = static_cast(malloc(req.count * sizeof(VideoBuffer)));
- for(buffer_n = 0; buffer_n < ENCODE_QUEUE_FRAME_NUM; buffer_n++)
- {
- // memset(&buf, 0, sizeof(buf));
- buf.index = buffer_n;
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- ret = ioctl (fd, VIDIOC_QUERYBUF, &buf);
- if (ret < 0)
- {
- printf("query buffer errorn");
- goto label_exit;
- }
- video_buffer[buffer_n].start = static_cast(mmap(nullptr, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset));
- video_buffer[buffer_n].length = buf.length;
- if(video_buffer[buffer_n].start == MAP_FAILED)
- {
- ret = -1;
- printf("buffer map errorn");
- goto label_exit;
- }
- // 放入缓存队列
- ret = ioctl(fd, VIDIOC_QBUF, &buf);
- if (ret < 0)
- {
- printf("put in frame errorn");
- goto label_exit;
- }
- }
- // 创建帧获取线程
- pthread_create(&thread_id, nullptr, frame_process_thread, this);
- // 创建编码线程
- pthread_create(&thread_id, nullptr, yuyv422_to_H264_thread, this);
- // 创建编码线程
- pthread_create(&thread_id, nullptr, h264_udp_broadcast_thread, this);
- return 0;
- label_exit:
- close(fd);
- return ret;
- }
开发板端程序开发使用V4l2框架,按上面总体设计,完成各个工作线程的代码实现,队列代码的实现,这里很多代码除了窗口部分和主机代码基本一致,因此可以支持copy过来复用,大大减少开发开发时间。
图16
图17
然后编译,检查编译的结果,变成位 ARM aarch64版本,然后传送到开发板上准被执行:
图18
六、实时视频编码推流测试
使用前面获得的摄像头的设备参数以及能够采集的视频规格参数,输入到采集编码端的程序中,就可以运行视频采集编码推流测试程序了,这里测试时,主机地址为192.168.50.212开发板配置在同一路由器下,使用有线网络地址为192.168.50.232。网络配置好后,通过互ping检查两机通讯正常,网络稳定且时延较低时,开始进行启动测试。
图19
首先启动主机端的程序,即在Ubuntu下启动播放器程序,然后在启动Gl2开发板上的推流测试程序,操作结果如下图:
图20
图21
在几秒后在主机端即可成功看到了开发板上推送过来的直播流,视频流很流畅,画面完成,无马赛克等问题。不过视频画质有点低,应该是编写开发板端编码代码时,有关编码码率参数设置的较低有关,这个在后面可以按需进行优化。
随文放了两段视频,一段是是
手机拍摄直播编码推送的摄像头与开发板的连接,主机端的播放程序以及接收到直播后的直播画面。另外一段拍摄了一下对比直播时延的画面,可以测试直播时延。
七、推流传输时延测评
最后使用开发的测试程序,测试一下整体直播实时视频的时延,测试方式是,将一个手机计时器打开放到PC电脑显示的Ubuntu播放器旁边,再将摄像头对准手机和PC电脑屏幕,使手机实时时间和在播放器里播放画面上的时间做对比就可以看出直播的时延。
试验测试图图下,实测视频见文末拍摄的视频画面。
最后多次测试,系统整体时延在 300ms ,左右,这里需要解释下,时延与摄像头设备,采集,编码,传输,解码,播放各个环节都有关,这里的测试的时延并不是开发板硬件的时延,而是这个整体测试的时延,在通过对各个环节的优化和压缩处理流程,可以大幅减少时延,笔者在某款开发板上进过优化软件实现,实现到<50ms的网络视频时延效果。
在实时采集编码时,开发板的系统资源使用情况如下图:
图22
可见板上资源消耗非常小,是因为芯片采用的硬编码的方式,所以能够较少的使用CPU的资源。
从本次测试的效果看,开发板系统采集编码效率较高,能够支持芯片实时的硬编码和网络发送。这为后面产品开发提供很好的基础,在较长时间(>60分钟)的视频编码推送测试中,视频编码推送程序和系统运行非常稳定,没有出现异常中断等现象,并且在持续视频采集中系统稳定运行,这也可以看出该开发板在视频处理时的稳定性。
-----------------------------------干货分割线-----------------------------
附件:
开发板上编译好的可形成测试软件包,感兴趣的可以在开发板上测试一下,播放器可以用 VLC ,windows 或者 Ubuntu下均可以。