图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下均可以。
【项目源代码+开发板体验视频,详见作者原文帖子内容】