视频演示
项目介绍
基于 IMX6ULL_PRO 开发板,使用Linux操作系统,在 V4L2 框架下驱动 USB 摄像头,编写QT程序,实现拍照,分辨率调节,亮度调节,相册添加删除功能。
环境
屏幕:7寸LCD触摸屏;
内核版本:Linux-4.9.88
QT版本: 5.12.9
Qt Creator: 4.12.2
编译器:arm-buildroot-linux-gnueabihf-gcc
摄像头模块:USB免驱200万像素内置麦克风
一、V4L2简介
V4L2(Video for Linux 2):Linux内核中视频设备中的驱动框架,对于应用层它提供了一系列的API接口,同时对于硬件层,它适配大部分的视频设备,因此通过调用V4L2的接口函数可以适配大部分的视频设备。
二、准备工作
1.插上USB免驱摄像头设备
2.编写测试程序获取摄像头设备支持格式和功能
test00.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
/* ./test00.c /dev/video1
*/
int main(int argc, char **argv)
{
if(argc != 2)
{
printf("Usage: %s </dev/video0,1...>\n", argv[0]);
return 1;
}
/* 打开摄像头设备 */
int fd = open(argv[1], O_RDWR);
if(fd < 0)
{
perror("open err");
return -1;
}
/* 获取摄像头支持格式 */
struct v4l2_fmtdesc v4fmt;
struct v4l2_capability cap; // 摄像头能力
memset(&cap, 0, sizeof(struct v4l2_capability));
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 选择视频抓取
int i = 0;
while(1)
{
v4fmt.index = i;
i++;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt); // 获取格式
if (ret < 0)
{
perror("获取格式失败");
break;
}
printf("index = %d\n", v4fmt.index); // 索引
printf("flags = %d\n", v4fmt.flags); // 0
printf("descrrption = %s\n", v4fmt.description); // 格式描述
unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]); // 像素格式
printf("reserved = %d\n", v4fmt.reserved[0]); // 保留
}
int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); //查询驱动功能
if (ret < 0)
perror("获取功能失败");
printf("drivers:%s\n", cap.driver); // 读取驱动名字
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf("%s 支持视频捕获\n", argv[1]);
if (cap.capabilities & V4L2_CAP_STREAMING)
printf("%s 支持流读写\n", argv[1]);
close(fd);
return 0;
}
/* 4.枚举摄像头支持的格式 (VIDIOC_ENUM_FMT:MJPG、YUYV等)
列举出每种格式下支持的分辨率 (VIDIOC_ENUM_FRAMESIZES) */
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置视频采集设备类型
int i = 0;
while(1){
fmtdesc.index = i++;
// 获取支持格式
if(0 == ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc)){
printf("支持格式:%s, %c%c%c%c\n", fmtdesc.description,
fmtdesc.pixelformat & 0xff,
fmtdesc.pixelformat >> 8 & 0xff,
fmtdesc.pixelformat >> 16 & 0xff,
fmtdesc.pixelformat >> 24 & 0xff);
// 列出该格式下支持的分辨率 VIDIOC_ENUM_FRAMESIZES & 默认帧率 VIDIOC_G_PARM
// 1.默认帧率
struct v4l2_streamparm streamparm;
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(0 == ioctl(video_fd, VIDIOC_G_PARM, &streamparm))
printf("该格式默认帧率 %d fps\n", streamparm.parm.capture.timeperframe.denominator);
// 2.循环列出支持的分辨率
struct v4l2_frmsizeenum frmsizeenum;
frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmsizeenum.pixel_format = fmtdesc.pixelformat; //设置成对应的格式
int j = 0;
printf("支持的分辨率有:\n");
while(1){
frmsizeenum.index = j++;
if(0 == ioctl(video_fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum))
printf("%d x %d\n", frmsizeenum.discrete.width, frmsizeenum.discrete.height);
else break;
}
printf("\n");
}else break;
}
编译程序后拷贝可执行程序进开发板执行
结果显示我的摄像头支持视频捕获,同时支持流读写支持两种像素格式YUYV和MJPG
3.配置摄像头
在视频采集之前需要设置视频采集格式,定义v4l2_format结构体变量,然后通过结构体v4l2_pix_format来设置采集的高、宽以及像素格式(YUYV),设置之后,可以采用打印的方式来查看是否设置成功。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
/* ./test00.c /dev/video1
*/
int main(int argc, char **argv)
{
if(argc != 2)
{
printf("Usage: %s </dev/video0,1...>\n", argv[0]);
return 1;
}
/* 打开摄像头设备 */
int fd = open(argv[1], O_RDWR);
if(fd < 0)
{
perror("open err");
return -1;
}
/* 设置摄像头采集格式 */
struct v4l2_format vfmt; // 帧格式
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 选择视频抓取
vfmt.fmt.pix.width = 640; // 设置宽,不能随意设置
vfmt.fmt.pix.height = 480; // 设置高
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 设置视频采集格式
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); // VIDIOC_S_FMT:设置捕获格式
if (ret < 0)
{
perror("设置采集格式错误");
}
memset(&vfmt, 0, sizeof(vfmt));
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); // VIDIOC_G_FMT:获取捕获格式
if (ret < 0)
{
perror("读取采集格式失败");
}
printf("width = %d\n", vfmt.fmt.pix.width);
printf("height = %d\n", vfmt.fmt.pix.height);
unsigned char *p = (unsigned char *)&vfmt.fmt.pix.pixelformat;
printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);
close(fd);
return 0;
}
结果和设置一样
4.向内核申请帧缓冲队列并映射
V4L2读取数据时有两种方式,第一种是用read读取(调用read函数),第二种是用流(streaming)读取,在第二步上已经获取到我的设备支持流读写,为了提高效率采用流读写,流读写就是在内核中维护一个缓存队列,然后再映射到用户空间,应用层直接读取队列中的数据。
步骤为:申请缓冲区->逐个查询申请到的缓冲区->逐个映射->逐个放入队列中
5.采集一帧图片
做完前面的设置就可以进行采集数据,打开设备->读取数据->关闭设备->释放映射。
读取数据的本质就是从上一个步骤中映射的队列中取出数据,取出数据后需要将该缓冲区放入队列中,以便于再去采集下一帧数据。
为了便于查看,在设置采集格式时,选择MJPEG格式,用fopen函数建立一个1.jpg文件,用fwrite函数保存读到的一帧数据。然后Ubuntu里面设置USB为3.1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
/* ./test00.c /dev/video1
*/
int main(int argc, char **argv)
{
if(argc != 2)
{
printf("Usage: %s </dev/video0,1...>\n", argv[0]);
return 1;
}
/* 1.打开摄像头设备 */
int fd = open(argv[1], O_RDWR);
if(fd < 0)
{
perror("open err");
return -1;
}
/* 2.设置摄像头采集格式 */
struct v4l2_format vfmt; // 帧格式
memset(&vfmt, 0, sizeof(struct v4l2_format));
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 选择视频抓取
vfmt.fmt.pix.width = 1024; // 设置宽
vfmt.fmt.pix.height = 768; // 设置高
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置视频采集格式 备份:V4L2_PIX_FMT_YUYV
vfmt.fmt.pix.field = V4L2_FIELD_ANY; // 配置视频格式
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); // VIDIOC_S_FMT:设置捕获格式
if (ret < 0)
{
perror("设置采集格式错误");
return -1;
}
// memset(&fmt, 0, sizeof(fmt));
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); // VIDIOC_G_FMT:获取捕获格式
if (ret < 0)
{
perror("读取采集格式失败");
}
printf("width = %d\n", vfmt.fmt.pix.width);
printf("height = %d\n", vfmt.fmt.pix.height);
unsigned char *p = (unsigned char *)&vfmt.fmt.pix.pixelformat;
printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);
/* 3.申请缓冲队列 */
struct v4l2_requestbuffers reqbuffer;
memset(&reqbuffer, 0, sizeof(struct v4l2_requestbuffers));
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 32; // 申请32个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; // 采用内存映射的方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if (ret < 0)
{
perror("申请缓冲队列失败");
return -1;
}
/* 4.映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列 */
struct v4l2_buffer mapbuffer;
unsigned char *mmpaddr[4]; // 用于存储映射后的首地址
unsigned int addr_length[4]; // 存储映射后空间的大小
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 初始化type
for (int i = 0; i < 4; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer); // 查询缓存信息
if (ret < 0)
perror("查询缓存队列失败");
/* mapbuffer.m.offset映射文件的偏移量 */
mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);
/* 放入队列 */
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if (ret < 0)
perror("放入队列失败");
}
/* 5.打开设备 */
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
perror("打开设备失败");
return -1;
}
printf("start capture ok\n");
/* 6.从队列中提取一帧数据 */
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer); // 从缓冲队列获取一帧数据(出队列)
// 出队列后得到缓存的索引index,得到对应缓存映射的地址mmpaddr[readbuffer.index]
if (ret < 0)
perror("获取数据失败");
FILE *file = fopen("1.jpg", "w+"); // 建立文件用于保存一帧数据
fwrite(mmpaddr[readbuffer.index], readbuffer.length, 1, file); // 二进制形式写入file文件中
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer); // 读取数据后将缓冲区放入队列
if (ret < 0)
perror("放入队列失败");
/* 关闭设备 */
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
perror("关闭设备失败");
/* 取消映射 */
for (int i = 0; i < 4; i++)
munmap(mmpaddr[i], addr_length[i]);
close(fd);
return 0;
}
采集一帧图片成功
三、在LCD上实时显示摄像头采集的JPEG数据
1.libjpeg介绍
利用libjpeg库将采集到的JPEG数据转化为RGB格式,并实时显示。
JPEG是一种广泛使用的图像压缩标准和文件格式。JPEG图像在存储时采用了有损压缩技术来减少所需存储空间,这意味着原始像素数据被转换为一种更高效但不完全可逆的形式。JPEG格式内部使用的是YCbCr颜色模型(或有时是另一种颜色空间变换),而不是直接的RGB颜色模型。
当JPEG图像需要显示在LCD(液晶显示器)等设备上时,通常需要将压缩的数据解码为显示器可以理解的颜色模型,即RGB格式。这是因为LCD屏幕上的每个像素点都由红、绿、蓝三种颜色的子像素组成,它们通过不同的亮度组合来显示各种颜色。
使用libjpeg库的主要原因如下:
- 解码JPEG数据:libjpeg是一个用于处理JPEG图像的软件库,它能够读取JPEG文件,并将其解码为原始像素数据。
- 颜色空间转换:由于JPEG文件中的像素数据通常是YCbCr颜色空间,因此需要将其转换为RGB颜色空间才能正确地在LCD屏幕上显示。
- 性能优化:libjpeg库已经过优化,可以在多种平台上高效运行,这有助于确保JPEG图像可以快速且准确地转换并显示出来。
开源库地址:http:// http://www.ijg.org/files/
2.jpeg的解码流程
①创建jpeg解码对象;②指定解码数据源;③读取图像信息;④设置解码参数;⑤开始解码;⑥读取解码后的数据;⑦解码完毕;⑧释放解码对象。
为了便于方便,我直接将解码和LCD显示写在了一个函数里面,在获取到一帧数据后,直接调用该函数,就可以直接在LCD上显示。因为libjpeg在解析数据时,是按照一行一行进行解析的,所以我也是解析一行后进行转换,转换后的数据直接写入framebuffer中。
里面涉及了把BGR888转为RGB888
int LCD_JPEG_Show(const char *JpegData, int size)
{
int min_hight = LCD_height, min_width = LCD_width, valid_bytes;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr); // 错误处理对象与解压对象绑定
// 创建解码对象
jpeg_create_decompress(&cinfo);
// 指定解码数据源
jpeg_mem_src(&cinfo, JpegData, size);
// 读取图像信息
jpeg_read_header(&cinfo, TRUE);
// printf("jpeg图像的大小为:%d*%d\n", cinfo.image_width, cinfo.image_height);
// 设置解码参数
cinfo.out_color_space = JCS_RGB; // 可以不设置默认为RGB
// cinfo.scale_num = 1;
// cinfo.scale_denom = 1;设置图像缩放,scale_num/scale_denom缩放比例,默认为1
// 开始解码
jpeg_start_decompress(&cinfo);
// 为缓冲区分配空间
unsigned char *jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
unsigned int *fb_line_buf = malloc(line_length); // 每个成员4个字节和RGB888对应
// 判断图像和LCD屏那个分辨率更低
if (cinfo.output_width < min_width)
min_width = cinfo.output_width;
if (cinfo.output_height < min_hight)
min_hight = cinfo.output_height;
// 读取数据,数据按行读取
valid_bytes = min_width * bpp / 8; // 一行的有效字节数,实际写进LCD显存的一行数据大小
unsigned char *ptr = fbbase;
while (cinfo.output_scanline < min_hight)
{
jpeg_read_scanlines(&cinfo, &jpeg_line_buf, 1); // 每次读取一行
// 将读取到的BGR888数据转化为RGB888
unsigned int red, green, blue;
unsigned int color;
for (int i = 0; i < min_width; i++)
{
red = jpeg_line_buf[i * 3];
green = jpeg_line_buf[i * 3 + 1];
blue = jpeg_line_buf[i * 3 + 2];
color = red << 16 | green << 8 | blue;
fb_line_buf[i] = color;
}
memcpy(ptr, fb_line_buf, valid_bytes);
ptr += LCD_width * bpp / 8;
}
// 完成解码
jpeg_finish_decompress(&cinfo);
// 销毁解码对象
jpeg_destroy_decompress(&cinfo);
// 释放内存
free(jpeg_line_buf);
free(fb_line_buf);
return 1;
}
3.在LCD上实时显示
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
#include <jpeglib.h>
#include <linux/fb.h>
int fd_fb;
int screen_size; // 屏幕像素大小
int LCD_width; // LCD宽度
int LCD_height; // LCD高度
unsigned char *fbbase = NULL; // LCD显存地址
unsigned long line_length; // LCD一行的长度(字节为单位)
unsigned int bpp; // 像素深度bpp
/* 初始化LCD */
int LCD_Init(void)
{
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
/* 打开图形帧缓冲设备 */
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
perror("打开LCD失败");
return -1;
}
/* 获取LCD信息 */
ioctl(fd_fb, FBIOGET_VSCREENINFO, &var); // 获取屏幕可变信息
ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix); // 获取屏幕固定信息
// LCD_width = var.xres * var.bits_per_pixel / 8;
// pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
LCD_width = var.xres;
LCD_height = var.yres;
bpp = var.bits_per_pixel;
line_length = fix.line_length;
printf("LCD分辨率:%d %d\n", LCD_width, LCD_height);
printf("bpp: %d\n", bpp);
fbbase = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); // 映射
if (fbbase == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
memset(fbbase, 0xFF, screen_size); // LCD设置为白色背景
return 0;
}
int LCD_JPEG_Show(const char *JpegData, int size)
{
int min_hight = LCD_height, min_width = LCD_width, valid_bytes;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr); // 错误处理对象与解压对象绑定
// 创建解码对象
jpeg_create_decompress(&cinfo);
// 指定解码数据源
jpeg_mem_src(&cinfo, JpegData, size);
// 读取图像信息
jpeg_read_header(&cinfo, TRUE);
// printf("jpeg图像的大小为:%d*%d\n", cinfo.image_width, cinfo.image_height);
// 设置解码参数
cinfo.out_color_space = JCS_RGB; // 可以不设置默认为RGB
// cinfo.scale_num = 1;
// cinfo.scale_denom = 1;设置图像缩放,scale_num/scale_denom缩放比例,默认为1
// 开始解码
jpeg_start_decompress(&cinfo);
// 为缓冲区分配空间
unsigned char *jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
unsigned int *fb_line_buf = malloc(line_length); // 每个成员4个字节和RGB888对应
// 判断图像和LCD屏那个分辨率更低
if (cinfo.output_width < min_width)
min_width = cinfo.output_width;
if (cinfo.output_height < min_hight)
min_hight = cinfo.output_height;
// 读取数据,数据按行读取
valid_bytes = min_width * bpp / 8; // 一行的有效字节数,实际写进LCD显存的一行数据大小
unsigned char *ptr = fbbase;
while (cinfo.output_scanline < min_hight)
{
jpeg_read_scanlines(&cinfo, &jpeg_line_buf, 1); // 每次读取一行
// 将读取到的BGR888数据转化为RGB888
unsigned int red, green, blue;
unsigned int color;
for (int i = 0; i < min_width; i++)
{
red = jpeg_line_buf[i * 3];
green = jpeg_line_buf[i * 3 + 1];
blue = jpeg_line_buf[i * 3 + 2];
color = red << 16 | green << 8 | blue;
fb_line_buf[i] = color;
}
memcpy(ptr, fb_line_buf, valid_bytes);
ptr += LCD_width * bpp / 8;
}
// 完成解码
jpeg_finish_decompress(&cinfo);
// 销毁解码对象
jpeg_destroy_decompress(&cinfo);
// 释放内存
free(jpeg_line_buf);
free(fb_line_buf);
return 1;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("%s </dev/video0,1...>\n", argv[0]);
return -1;
}
/* LCD初始化 */
if (LCD_Init() != 0)
{
return -1;
}
/* 1.打开摄像头设备 */
int fd = open(argv[1], O_RDWR);
if (fd < 0)
{
perror("打开设备失败");
close(fd);
return -1;
}
/* 2.设置摄像头采集格式 */
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 选择视频抓取
vfmt.fmt.pix.width = LCD_width; // 设置宽,设置为LCD的宽高
vfmt.fmt.pix.height = LCD_height; // 设置高
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置视频采集像素格式
vfmt.fmt.pix.field = V4L2_FIELD_ANY;
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); // VIDIOC_S_FMT:设置捕获格式
if (ret < 0)
{
perror("设置采集格式错误");
close(fd);
return -1;
}
memset(&vfmt, 0, sizeof(vfmt)); // 清空vfmt结构体
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);
if (ret < 0)
{
perror("读取采集格式失败");
}
printf("设置分辨率width = %d\n", vfmt.fmt.pix.width);
printf("设置分辨率height = %d\n", vfmt.fmt.pix.height);
unsigned char *p = (unsigned char *)&vfmt.fmt.pix.pixelformat;
printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);
/* 3.申请缓冲队列 */
struct v4l2_requestbuffers reqbuffer;
memset(&reqbuffer, 0, sizeof(reqbuffer));
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; // 申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; // 采用内存映射的方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if (ret < 0)
{
perror("申请缓冲队列失败");
}
/* 4.映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列 */
struct v4l2_buffer mapbuffer;
unsigned char *mmpaddr[4]; // 用于存储映射后的首地址
unsigned int addr_length[4]; // 存储映射后空间的大小
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 初始化type
for (int i = 0; i < reqbuffer.count; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer); // 查询缓存信息
if (ret < 0)
perror("查询缓存队列失败");
mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset); // mapbuffer.m.offset映射文件的偏移量
addr_length[i] = mapbuffer.length;
// 放入队列
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if (ret < 0)
perror("放入队列失败");
}
/* 5.开始采集数据 */
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 捕获
ret = ioctl(fd, VIDIOC_STREAMON, &type); // 视频流
if (ret < 0)
perror("打开设备失败");
while (1)
{
// 从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer); // 从缓冲队列获取一帧数据(出队列)
// 出队列后得到缓存的索引index,得到对应缓存映射的地址mmpaddr[readbuffer.index]
if (ret < 0)
perror("获取数据失败");
// 显示在LCD上
LCD_JPEG_Show(mmpaddr[readbuffer.index], readbuffer.length);
// 读取数据后将缓冲区放入队列
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if (ret < 0)
perror("放入队列失败");
}
/* 6.关闭设备 */
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
perror("关闭设备失败");
/* 7.取消映射 */
for (int i = 0; i < 4; i++)
munmap(mmpaddr[i], addr_length[i]);
/* 8.关闭设备 */
close(fd);
return 0;
}
四、编写QT程序
功能目标:
主界面,相机图标;
点击图标打开摄像头设备,实时显示图像;
摄像头界面有拍照按钮,相册按钮;
点击拍照按钮执行拍照功能,点击相册按钮后台关闭摄像头,切换到相册界面,显示相册文件;
评论