前言
之前我们已经实现了摄像头用V4L2框架采集一张图片,现在就是实现用摄像头采集视频流(本质一张图片就是一帧,很多张图片就是很多帧,拼起来就是一个视频)。
本部分需要大家有一点QT相关的知识,整体框架还是非常简单的,和昨天采集一个图片没什么区别,这里就只讲如何采集视频流,至于后面功能的完善就大家自己发挥。
Ubuntu版本的采集,相对比较简单,因为不涉及到交叉编译器等相关知识,在编译过程也比较顺利,稍微注意一点细节就好了。比较难的是嵌入式平台的部署。但本质也都和Ubuntu版本一样,只是用的编译器和链接的库文件有所不同。
Ubuntu下采集
QT代码的编写
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QTimer>
#include <QMainWindow>
//这些是用到的一些头文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define video_width 320
#define video_height 240
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QTimer *timer; //这个是个定时器
unsigned char *userbuf[4]; //保存地址映射后的地址值
int userbuf_length[4];//长度
int fd; //摄像头文件的id
int start; //开始标志位
int v4l2_open(); //摄像头打开操作
int v4l2_close(); //摄像头关闭操作
public slots:
void video_show(); //这是一个槽,由定时器触发,定时器触发一次采集一张图片显示
};
#endif // MAINWINDOW_H
以上便是QT的.h代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "yuv_to_jpeg.h"//这个是用来yuv转jpg的一个C库后面会讲到
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
timer = new QTimer();
start=0;
connect(timer,SIGNAL(timeout()),this,SLOT(video_show()));//连接一个信号槽,定时器超时发出信号,执行槽函数采集一张图片。
if(0==v4l2_open())//打开摄像头,可以理解为摄像头初始化,如果初始化成功开始标志置1,定时器开始以10ms为周期计时。即10ms采集一张图片。
{
start=1;
timer->start(10);
}
}
MainWindow::~MainWindow()
{
v4l2_close();
delete ui;
}
int MainWindow:: v4l2_open()//和之前一篇文章讲到的框架大同小异,不再赘述。
{
//打开摄像头
fd = open("/dev/video0",O_RDWR);
if(fd<0)
{
perror("打开失败");
return -1;
}
//获取摄像头参数
struct v4l2_capability capability;
if(ioctl(fd,VIDIOC_QUERYCAP,&capability))
{
if((capability.capabilities&V4L2_CAP_VIDEO_CAPTURE)==0)
{
perror("不支持视频采集");
::close(fd);
return -2;
}
if((capability.capabilities&V4L2_MEMORY_MMAP)==0)
{
perror("不支持mmap内存映射");
::close(fd);
return -3;
}
}
//设置摄像头参数
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = video_width;
format.fmt.pix.height =video_height;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if(ioctl(fd,VIDIOC_S_FMT,&format)<0)
{
perror("设置失败");
::close(fd);
return -4;
}
//申请内存空间
struct v4l2_requestbuffers requestbuffers;
requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
requestbuffers.count = 4;
requestbuffers.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd,VIDIOC_REQBUFS,&requestbuffers)==0)
{
for(unsigned int i=0;i<requestbuffers.count;i++)
{
struct v4l2_buffer buffer;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.index = i;
buffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd,VIDIOC_QBUF,&buffer)==0)
{
userbuf[i] = (unsigned char*)mmap(NULL,buffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buffer.m.offset);
userbuf_length[i]=buffer.length;
}
}
}
else
{
perror("申请内存失败");
::close(fd);
return -5;
}
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(fd,VIDIOC_STREAMON,&type)>0)
{
perror("打开视频流失败!");
return -6;
}
return 0;
}
int MainWindow::v4l2_close()//关闭操作,可以理解为逆初始化
{
//停止采集,关闭视频流
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(fd,VIDIOC_STREAMOFF,&type)==0)
{
for(int i=0;i<4;i++)
{
munmap(userbuf[i],userbuf_length[i]);
}
::close(fd);
return 0;
}
perror("关闭视频流失败");
return -1;
}
void MainWindow::video_show()
{
QPixmap pix;
int jpg_size;
unsigned char *jpg_p = new unsigned char[240*320*3];//这里申请了内存后面要记得释放,不然会爆
struct v4l2_buffer buffer;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(fd,VIDIOC_DQBUF,&buffer)==0)
{
jpg_size = yuv_to_jpeg(320,240,240*320*3,userbuf[buffer.index],jpg_p,80);//yuv转jpg
pix.loadFromData(jpg_p,jpg_size);
pix.scaled(ui->label->width(),ui->label->height(),Qt::KeepAspectRatio);
ui->label->setPixmap(pix);
}
delete [] jpg_p;//释放内存
jpg_p = nullptr;
if(ioctl(fd,VIDIOC_QBUF,&buffer)<0)
{
perror("返回队列失败1");
}
}
以上便是QT的.c代码
QT添加外部链接库
由于在本qt工程中,调用到了jpeglib库相关的文件,而qt在编译的时候默认是找不到这个安装的外部库的,所以就需要我们指定一下这个库路径。
这里去找,我们Ubuntu下安装的jpeglib默认路径是在:/home/usr/local/lib下,选中libjpeg.a文件他自动就会索引上,我们的头文件。
然后下一步到完成即可。其实以上操作的本质就是在qt的.pro文件下添加了这一系列的代码,去索引我们的库文件。
QT添加C语言程序(C库)
由于C语言和C++很多代码都是共通的,C++也基本向下兼容C语言,所以这里操作比较简单。
把这个.c和.h文件包含进来,添加以下几行声明去兼容C即可。然后我们就可以像正常C语言一样去调用函数了。
总结
完成以上操作以后,我们的QT采集摄像头数据的工程也就完成了,接下来只需要往Ubuntu插入摄像头,然后运行即可。
一个最简单的Ubuntu下摄像头采集就完成了。
IMX6ULL平台下采集
首先得确保你的开发板能运行QT程序,其次要确保你的开发板已经安装了jepglib,如果符合以上要求再往下看。基于Ubuntu下采集的代码,我们只需要转换以下编译器,编译出一个可以在开发板运行的应用即可,具体流程如下。
创建QT交叉编译器
正常开发板出厂会提供一个qt的交叉编译器,先把这个安装了。这里我已经安装好了,不同的开发板有不同的安装方法,这里就不赘述了。
然后便是配置QT的脚本,打开qt安装目录下的.sh文件
sudo vi /opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh
在第一行插入以下:
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
保存
接下来是配置qt编译器,在编译器这里添加
路径就是你之前安装的qt交叉编译器下我的是:
/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-g++
/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc
g++对应的是C++代码的编译,gcc对应的是c语言代码的编译,这里配置好以后,到配置qmake。
qmake也在你安装的qt交叉编译器的路径下。配置好以后,到最后配置kits
指定为你之前新增的g++,gcc,qmake然后保存,现在我们就已经创建好了一个新的qt交叉编译工具,但由于这个交叉编译工具功能非常简陋,这边建议在linux下测试没问题再切换到这个交叉编译器编译上板的app。
编译工程
打开我们之前写好的摄像头代码,在箭头指的那个套件环境下测试过没问题后,切换到imx6ull的那个套件编译上板应用。
这里值得注意的是,由于我们引用了一个jpeglib,之前在Ubuntu下跑的摄像头代码选中的这个jpeglib不是用交叉编译器出来的。现在我们要在arm下跑,用前面那个库很明显就不行了。要重新指定一个用交叉编译器编译出来的jpeglib。下图这一段全删了。
重新右键添加库。这次选中的库,就是用交叉编译编译出来的jpeglib了。(如何用交叉编译器编译出一个jpeglinb,并移植到开发板可以看我之前文章)
选择好后,这个地方会生成新的一个引用代码。
此时再点左下角的锤子图标构建(注意,点绿色播放器按钮是会报错的,因为我们这个套件非常简陋并没有仿真运行功能)。
切换过来后,你原来的工程会有一堆红线错误,但不用管。锤子构建后,显示下图,即编译通过。
此时在产出目录下会有这个文件
把这个文件弄到开发板上(注意:开发板的qt环境要和你编译的qt环境一致)。此时在板子上运行这个app。
可以看到已经跑出来了。
总结
以上便是Ubuntu下和开发板下的两种QT采集摄像头显示的代码了,这里比较关键的是QT的配置,比较细节,初学可能会踩很多的坑。
补前文采集像素不能过高的问题
这里的问题原来是在于,imx6ull只支持usb2.0的接口,而我的摄像头是usb3.0的。这就导致在传输480*640这种分辨率比较大的数据时会出这种问题。目前的解决方法似乎只有更换摄像头或者开发板,最近也是在看内核相关的源码,看看能不能找出不更换硬件的解决方法。