【DIY】Linux相机
【DIY】Linux相机
本文最后更新于291 天前,其中的信息可能已经过时,如有错误请发送邮件到273925452@qq.com

视频演示


项目介绍

基于 IMX6ULL_PRO 开发板,使用Linux操作系统,在 V4L2 框架下驱动 USB 摄像头,编写QT程序,实现拍照,分辨率调节,亮度调节,相册添加删除功能。


环境

开发板:IMX6ULL_PRO
屏幕: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库的主要原因如下:

  1. 解码JPEG数据:libjpeg是一个用于处理JPEG图像的软件库,它能够读取JPEG文件,并将其解码为原始像素数据。
  2. 颜色空间转换:由于JPEG文件中的像素数据通常是YCbCr颜色空间,因此需要将其转换为RGB颜色空间才能正确地在LCD屏幕上显示。
  3. 性能优化: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程序

功能目标:

主界面,相机图标;

点击图标打开摄像头设备,实时显示图像;

摄像头界面有拍照按钮,相册按钮;

点击拍照按钮执行拍照功能,点击相册按钮后台关闭摄像头,切换到相册界面,显示相册文件;


五、开源链接

giteehttps://gitee.com/heiweilu/linux-project/tree/master/Linux%E7%9B%B8%E6%9C%BA
💡商业转载请联系作者获得授权,非商业转载请注明出处。
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)。
使用这些免费资源的时候应遵守版权意识,切勿非法利用,售卖,尊重原创内容知识产权。未经允许严禁转载。

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇