1、效果图
2、补丁(参考https://github.com/jianfenggithub/V4L2_example)
commit a3aab64424a416879be7651165e5b0b19da5400a
Author: rockemd <rockemd2020@163.com>
Date: Wed Dec 9 23:10:26 2020 +0800
support USB dual camera
diff --git a/app/QUVCTest/QUVCTest.pro b/app/QUVCTest/QUVCTest.pro
new file mode 100755
index 0000000..1b98345
--- /dev/null
+++ b/app/QUVCTest/QUVCTest.pro
@@ -0,0 +1,34 @@
+
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++11 console
+
+TARGET = QUVCTest
+TEMPLATE = app
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+#DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ widget.cpp \
+ v4l2.cpp \
+ camerathread.cpp
+
+HEADERS += \
+ widget.h \
+ v4l2.h \
+ camerathread.h
+
+FORMS += \
+ widget.ui
diff --git a/app/QUVCTest/camerathread.cpp b/app/QUVCTest/camerathread.cpp
new file mode 100755
index 0000000..fc682bb
--- /dev/null
+++ b/app/QUVCTest/camerathread.cpp
@@ -0,0 +1,35 @@
+#include "camerathread.h"
+#include <QThread>
+
+extern QLabel *mylabel;
+extern QLabel *mylabel2;
+
+CameraThread::CameraThread()
+{
+
+}
+
+CameraThread::~CameraThread()
+{
+
+}
+
+/** 获取摄像头数据线程 */
+void CameraThread::dowork()
+{
+ av4l.V4l_Init("/dev/video44", 30); /** 初始化 */
+ bv4l.V4l_Init("/dev/video43", 30); /** 初始化 */
+
+ while(1){
+ if((QThread::currentThread()->isInterruptionRequested())){/** 是否有中断此线程信号 */
+ av4l.Close_Camera(); /** 关闭摄像头 */
+ bv4l.Close_Camera(); /** 关闭摄像头 */
+ break;
+ }
+ currentimagea = av4l.Get_image(); /** 获得一帧图片数据 */
+ currentimageb = bv4l.Get_image(); /** 获得一帧图片数据 */
+ mylabel->setPixmap(QPixmap::fromImage(currentimagea)); /** 显示到label上 */
+ mylabel2->setPixmap(QPixmap::fromImage(currentimageb)); /** 显示到label上 */
+ }
+
+}
diff --git a/app/QUVCTest/camerathread.h b/app/QUVCTest/camerathread.h
new file mode 100755
index 0000000..48414e2
--- /dev/null
+++ b/app/QUVCTest/camerathread.h
@@ -0,0 +1,27 @@
+#ifndef CAMERATHREAD_H
+#define CAMERATHREAD_H
+
+#include <QObject>
+#include "v4l2.h"
+#include <QImage>
+#include "widget.h"
+#include "ui_widget.h"
+
+class CameraThread : public QObject
+{
+ Q_OBJECT
+public:
+ CameraThread();
+ ~CameraThread();
+ V4L2 av4l;
+ V4L2 bv4l;
+ QImage currentimagea; /** 保存摄像头一帧图片数据,然后显示到label上 */
+ QImage currentimageb; /** 保存摄像头一帧图片数据,然后显示到label上 */
+
+signals:
+
+public slots:
+ void dowork(); /** 获取摄像头数据线程 */
+};
+
+#endif // CAMERATHREAD_H
diff --git a/app/QUVCTest/main.cpp b/app/QUVCTest/main.cpp
new file mode 100755
index 0000000..f92e588
--- /dev/null
+++ b/app/QUVCTest/main.cpp
@@ -0,0 +1,26 @@
+#include "widget.h"
+#include <QApplication>
+#include <QThread>
+#include "camerathread.h"
+
+QThread *ImageThread = new QThread; /** 获取摄像头数据线程 */
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ Widget w;
+
+ CameraThread *camera_worker = new CameraThread;
+
+ camera_worker->moveToThread(ImageThread); /** 创建线程 */
+ QObject::connect(ImageThread, SIGNAL(started()),
+ camera_worker, SLOT(dowork()));
+ QObject::connect(ImageThread, SIGNAL(finished()),
+ camera_worker, SLOT(deleteLater())); /** 当线程结束时,释放资源 */
+
+ ImageThread->start(); /** 启动线程 */
+
+ w.show();
+
+ return a.exec();
+}
diff --git a/app/QUVCTest/v4l2.cpp b/app/QUVCTest/v4l2.cpp
new file mode 100755
index 0000000..15e49f7
--- /dev/null
+++ b/app/QUVCTest/v4l2.cpp
@@ -0,0 +1,155 @@
+/**
+ * 功能:使用V4L2采集UVC摄像头数据
+ * 日期:2018.3.26
+ * 用法:在Ubuntu下的Qt工程中添加V4L2.h和V4L2.cpp文件,定义一个V4L2对象,
+ * 调用bool V4L2::V4l_Init(char* camera_path, unsigned int frame) 函数对摄像头进行初始化操作
+ * 调用QImage V4L2::Get_image() 函数就可以获得摄像头传来的一张图片
+ * 调用bool Close_Camera(void)函数关闭摄像头
+ * 注意:需要在V4L2.cpp的54行左右可修改摄像头图片的输出格式,如修改为MJPEG输出
+ * 在V4L2.h的宏定义中可以修改输出图片的像素
+ *
+ * v1.1-2018.9.26:修改注释风格,规范代码
+ * v1.2-2019.3.2:优化Get_image函数,减少数据转移次数
+ */
+
+#include "v4l2.h"
+
+V4L2::V4L2()
+{
+ n = 0;
+}
+
+V4L2::~V4L2()
+{
+
+}
+
+/** 摄像头初始化,需要传入摄像头的挂载路径和输出的帧率,初始化成功则返回真 */
+bool V4L2::V4l_Init(char *camera_path, unsigned int frame)
+{
+ if((fd=open(camera_path, O_RDWR)) == -1){ /** 读写方式打开摄像头 */
+ qDebug()<<"Error opening V4L interface";
+ return false;
+ }
+ if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1){ /** 检查摄像头的信息 */
+ qDebug()<<"Error opening device "<<camera_path<<": unable to query device.";
+ return false;
+ }else{ /** 打印摄像头的信息 */
+ qDebug()<<"driver:\t\t"<<QString::fromLatin1((char *)cap.driver); /** 驱动名 */
+ qDebug()<<"card:\t\t"<<QString::fromLatin1((char *)cap.card); /** Device名 */
+ qDebug()<<"bus_info:\t\t"<<QString::fromLatin1((char *)cap.bus_info); /** 在Bus系统中存放位置 */
+ qDebug()<<"version:\t\t"<<cap.version; /** driver 版本 */
+ qDebug()<<"capabilities:\t"<<cap.capabilities; /** 能力集,通常为:V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING */
+ }
+
+ fmtdesc.index=0;
+ fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /** 和struct v4l2_format中的type一致 */
+ qDebug()<<"Support format:";
+ while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1){ /** 获取摄像头输出图片所支持的格式 */
+ qDebug()<<"\t\t"<<fmtdesc.index+1<<QString::fromLatin1((char *)fmtdesc.description);
+ fmtdesc.index++;
+ }
+
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /** 像素格式 */
+ fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; /** JPEG格式输出 */
+ fmt.fmt.pix.height = Image_High; /** 图像尺寸,在这里设置想要输出的图片宽和高 */
+ fmt.fmt.pix.width = Image_Width;
+ fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; /** 视频帧传输方式,交错式 */
+ if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1){ /** 配置摄像头的采集格式 */
+ qDebug()<<"Unable to set format";
+ return false;
+ }
+ if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1){ /** 重新读取结构体,以确认完成设置 */
+ qDebug()<<"Unable to get format";
+ return false;
+ }else{
+ qDebug()<<"fmt.type:\t\t"<<fmt.type; /** 输出像素格式 */
+ qDebug()<<"pix.height:\t"<<fmt.fmt.pix.height;/** 输出图像的尺寸 */
+ qDebug()<<"pix.width:\t\t"<<fmt.fmt.pix.width;
+ qDebug()<<"pix.field:\t\t"<<fmt.fmt.pix.field; /** 视频帧传输方式 */
+ }
+
+ setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ setfps.parm.capture.timeperframe.denominator = frame;/** 预期的帧率 */
+ setfps.parm.capture.timeperframe.numerator = 1; /** fps=frame/1 */
+ if(ioctl(fd, VIDIOC_S_PARM, &setfps)==-1){ /** 设置帧数 */
+ qDebug()<<"Unable to set fps";
+ return false;
+ }
+ if(ioctl(fd, VIDIOC_G_PARM, &setfps)==-1){ /** 重新读取结构体,以确认完成设置 */
+ qDebug()<<"Unable to get fps";
+ return false;
+ }else{
+ qDebug()<<"fps:\t\t"<<setfps.parm.capture.timeperframe.denominator/setfps.parm.capture.timeperframe.numerator;/** 输出帧率 */
+ }
+
+ req.count=Video_count; /** 3个缓存区 */
+ req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /** 与struct v4l2_format中的type一致 */
+ req.memory=V4L2_MEMORY_MMAP; /** Memory Mapping模式,设置为V4L2_MEMORY_MMAP时count字段才有效 */
+ if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1){ /** 向内核申请视频缓存 */
+ qDebug()<<"request for buffers error";
+ return false;
+ }
+ for (i=0; i<Video_count; i++){ /** mmap四个缓冲区 */
+ bzero(&buffer[i], sizeof(buffer[i]));
+ buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buffer[i].memory = V4L2_MEMORY_MMAP;
+ buffer[i].index = i;
+ if (ioctl (fd, VIDIOC_QUERYBUF, &buffer[i]) == -1){ /** 获得缓冲区的参数,mmap时需要这些参数 */
+ qDebug()<<"query buffer error";
+ return false;
+ }
+ length[i] = buffer[i].length; /** 保存缓存的大小 */
+ start[i] = (unsigned char *)mmap(NULL,buffer[i].length,PROT_READ |PROT_WRITE, MAP_SHARED, fd, buffer[i].m.offset);
+ /** 将申请的内核缓存映射到用户空间 */
+ }
+ for (i=0; i<Video_count; i++){
+ buffer[i].index = i;
+ ioctl(fd, VIDIOC_QBUF, &buffer[i]); /** 将缓存入队 */
+ }
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ioctl (fd, VIDIOC_STREAMON, &type); /** 开启I/O流 */
+
+ bzero(&v4lbuf, sizeof(v4lbuf));
+ v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ v4lbuf.memory = V4L2_MEMORY_MMAP;
+ qDebug()<<"init ok";
+
+ return true;
+}
+
+QImage V4L2::Get_image() /** 得到摄像头捕获的图片,返回值为QImage类型的变量 */
+{
+ v4lbuf.index = n%Video_count;
+ ioctl(fd, VIDIOC_DQBUF, &v4lbuf); /** 将获得图像数据的缓存出队 */
+
+ QByteArray temp;
+ temp.append((const char *)start[n%Video_count],length[n%Video_count]);
+ image.loadFromData(temp); /** 将图片数据放入image变量中 */
+
+ v4lbuf.index = n%Video_count;
+ ioctl(fd, VIDIOC_QBUF, &v4lbuf); /** 将已经读取过数据的缓存块重新入队 */
+ n++;
+ if(n == 3){ /** 防止n累加溢出 */
+ n = 0;
+ }
+ return image; /** 返回图片数据 */
+}
+
+bool V4L2::Close_Camera() /** 关闭摄像头,关闭成功则返回真 */
+{
+ if(fd != -1){
+ ioctl(fd, VIDIOC_STREAMOFF, &type); /** 结束图像显示 */
+ int n = close(fd); /** 关闭视频设备 */
+ if(n == -1){
+ qDebug()<<"close camera failed";
+ return false;
+ }
+ }
+ for(i=0; i<Video_count; i++){
+ if(start[i] != NULL){ /** 释放申请的内存 */
+ start[i] = NULL;
+ }
+ }
+ return true;
+}
diff --git a/app/QUVCTest/v4l2.h b/app/QUVCTest/v4l2.h
new file mode 100755
index 0000000..b417ea3
--- /dev/null
+++ b/app/QUVCTest/v4l2.h
@@ -0,0 +1,57 @@
+/**
+ * 功能:使用V4L2采集UVC摄像头数据
+ * 日期:2018.3.26
+ * 用法:在Ubuntu下的Qt工程中添加V4L2.h和V4L2.cpp文件,定义一个V4L2对象,
+ * 调用bool V4L2::V4l_Init(char* camera_path, unsigned int frame) 函数对摄像头进行初始化操作
+ * 调用QImage V4L2::Get_image() 函数就可以获得摄像头传来的一张图片
+ * 调用bool Close_Camera(void)函数关闭摄像头
+ * 注意:需要在V4L2.cpp的54行左右可修改摄像头图片的输出格式,如修改为MJPEG输出
+ * 在V4L2.h的宏定义中可以修改输出图片的像素
+ *
+ * v1.1-2018.9.26:修改注释风格,规范代码
+ * v1.2-2019.3.2:优化Get_image函数,减少数据转移次数
+ */
+
+#ifndef V4L2_H
+#define V4L2_H
+
+#include <fcntl.h> /** 打开摄像头的open函数所在的头文件 */
+#include <sys/mman.h> /** 将申请的内核缓存映射到用户空间的mmap函数所在头文件 */
+#include <linux/videodev2.h> /** V4L2相关结构体所在的头文件 */
+#include <unistd.h> /** 关闭摄像头的close函数所在的头文件 */
+#include <sys/ioctl.h>
+#include <QDebug>
+#include <QImage>
+
+#define Video_count 3 /** 缓冲帧数 */
+#define Image_Width 1280 /** 输出图片的像素 */
+#define Image_High 720
+
+class V4L2
+{
+public:
+ V4L2();
+ ~V4L2();
+ bool V4l_Init(char *camera_path, unsigned int frame);/** 摄像头初始化函数,需要传入摄像头的挂载路径和输出的帧率,初始化成功则返回真 */
+ QImage Get_image(void); /** 获取图片,返回值为QImage类型的变量 */
+ bool Close_Camera(void); /** 关闭摄像头,关闭成功则返回真 */
+ int n; /** 用在Get_image函数,获取图片时,用来控制哪快缓存 */
+
+private:
+ int i;
+ int fd; /** 摄像头句柄 */
+ int length[Video_count];/** 用来保存申请的缓存的大小 */
+ QImage image;
+ unsigned char * start[Video_count];/** 用来保存图片数据 */
+
+ struct v4l2_buffer buffer[Video_count];/** 向内核申请缓存时用到此结构体,每一个struct v4l2_buffer对应内核摄像头驱动中的一个缓存 */
+ struct v4l2_format fmt; /** 用来设置像素格式 图片输出格式 图像尺寸 扫描方式 */
+ struct v4l2_fmtdesc fmtdesc; /** 获取摄像头输出图片所支持的格式时需要用到此结构体 */
+ struct v4l2_capability cap; /** 检查摄像头的信息时需要用到此结构体 */
+ struct v4l2_streamparm setfps; /** 设置帧率时用到此结构体 */
+ struct v4l2_requestbuffers req; /** 向内核申请缓存时用到此结构体 */
+ struct v4l2_buffer v4lbuf; /** 缓存出队 入队时用到此结构体 */
+ enum v4l2_buf_type type; /** 开启I/O流时用到此结构体 */
+};
+
+#endif // V4L2_H
diff --git a/app/QUVCTest/widget.cpp b/app/QUVCTest/widget.cpp
new file mode 100755
index 0000000..00bf7c3
--- /dev/null
+++ b/app/QUVCTest/widget.cpp
@@ -0,0 +1,30 @@
+#include "widget.h"
+#include "ui_widget.h"
+
+
+QLabel *mylabel = NULL;
+QLabel *mylabel2 = NULL;
+extern QThread *ImageThread; /** 获取摄像头数据线程 */
+
+Widget::Widget(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::Widget)
+{
+ ui->setupUi(this);
+
+ mylabel = ui->label; /** 获得界面上的label对象, 使得可以在“获取摄像头数据线程”获取的图片数据显示到这个label上 */
+ mylabel2 = ui->label2; /** 获得界面上的label对象, 使得可以在“获取摄像头数据线程”获取的图片数据显示到这个label上 */
+}
+
+Widget::~Widget()
+{
+ delete ui;
+}
+
+/** 关闭窗口时候的处理函数 */
+void Widget::closeEvent(QCloseEvent *)
+{
+ ImageThread->requestInterruption(); /** 请求中断 */
+ ImageThread->quit(); /** 关闭线程 */
+ ImageThread->wait(); /** 同步关闭 */
+}
diff --git a/app/QUVCTest/widget.h b/app/QUVCTest/widget.h
new file mode 100755
index 0000000..9852ecc
--- /dev/null
+++ b/app/QUVCTest/widget.h
@@ -0,0 +1,25 @@
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include <QWidget>
+#include <QThread>
+
+namespace Ui {
+class Widget;
+}
+
+class Widget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Widget(QWidget *parent = 0);
+ ~Widget();
+ Ui::Widget *ui;
+ void closeEvent(QCloseEvent *); /** 关闭窗口时候的处理函数 */
+
+private:
+
+};
+
+#endif // WIDGET_H
diff --git a/app/QUVCTest/widget.ui b/app/QUVCTest/widget.ui
new file mode 100755
index 0000000..81c6246
--- /dev/null
+++ b/app/QUVCTest/widget.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget</class>
+ <widget class="QWidget" name="Widget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>480</width>
+ <height>800</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Widget</string>
+ </property>
+ <widget class="QLabel" name="label">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>480</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>picture</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>400</y>
+ <width>480</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>picture</string>
+ </property>
+ </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/buildroot/configs/rockchip_rv1126_rv1109_facial_gate_defconfig b/buildroot/configs/rockchip_rv1126_rv1109_facial_gate_defconfig
old mode 100644
new mode 100755
index 71e9632..55c7442
--- a/buildroot/configs/rockchip_rv1126_rv1109_facial_gate_defconfig
+++ b/buildroot/configs/rockchip_rv1126_rv1109_facial_gate_defconfig
@@ -24,6 +24,7 @@ BR2_PACKAGE_RKMEDIA_ANR=y
BR2_PACKAGE_RKMEDIA_AEC=y
BR2_PACKAGE_RKMEDIA_EXAMPLES=y
BR2_PACKAGE_QFACIALGATE=y
+BR2_PACKAGE_QUVCTEST=y
# BR2_PACKAGE_MINIGUI_SOFTWARE_SCALE is not set
BR2_PACKAGE_RKNPU_USE_MINI_DRIVER=y
BR2_PACKAGE_RKFACIAL_USE_WEB_SERVER=y
diff --git a/buildroot/package/rockchip/Config.in b/buildroot/package/rockchip/Config.in
old mode 100644
new mode 100755
index 6735cdc..5f381b9
--- a/buildroot/package/rockchip/Config.in
+++ b/buildroot/package/rockchip/Config.in
@@ -180,6 +180,7 @@ source "package/rockchip/carmachine/Config.in"
source "package/rockchip/gallery/Config.in"
source "package/rockchip/QLauncher/Config.in"
source "package/rockchip/QFacialGate/Config.in"
+source "package/rockchip/QUVCTest/Config.in"
source "package/rockchip/settings/Config.in"
source "package/rockchip/qcamera/Config.in"
source "package/rockchip/qfm/Config.in"
diff --git a/buildroot/package/rockchip/QUVCTest/Config.in b/buildroot/package/rockchip/QUVCTest/Config.in
new file mode 100755
index 0000000..45cb896
--- /dev/null
+++ b/buildroot/package/rockchip/QUVCTest/Config.in
@@ -0,0 +1,4 @@
+config BR2_PACKAGE_QUVCTEST
+ bool "quvctest"
+ help
+ rockemd qt quvctest
\ No newline at end of file
diff --git a/buildroot/package/rockchip/QUVCTest/QUVCTest.mk b/buildroot/package/rockchip/QUVCTest/QUVCTest.mk
new file mode 100755
index 0000000..26d2a86
--- /dev/null
+++ b/buildroot/package/rockchip/QUVCTest/QUVCTest.mk
@@ -0,0 +1,23 @@
+################################################################################
+#
+# QUVCTest
+#
+################################################################################
+
+QUVCTEST_VERSION = 1.0
+QUVCTEST_SITE = $(TOPDIR)/../app/QUVCTest
+QUVCTEST_SITE_METHOD = local
+
+QUVCTEST_LICENSE = Apache V2.0
+QUVCTEST_LICENSE_FILES = NOTICE
+define QUVCTEST_CONFIGURE_CMDS
+cd $(@D); $(TARGET_MAKE_ENV) $(HOST_DIR)/bin/qmake
+endef
+define QUVCTEST_BUILD_CMDS
+$(TARGET_MAKE_ENV) $(MAKE) -C $(@D)
+endef
+
+define QUVCTEST_INSTALL_TARGET_CMDS
+$(INSTALL) -D -m 0755 $(@D)/QUVCTest $(TARGET_DIR)/usr/bin/QUVCTest
+endef
+$(eval $(generic-package))
\ No newline at end of file
技术交流群: 微信号