使用Linux内核自带的V4L2设备驱动 采集图像

一、定义

V4L2代表Video for Linux Two,它是Linux内核的一部分,提供了一种统一的方式来访问各种视频输入/输出设备,如摄像头、电视卡等。

二、工作流程(重点)

打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备。

1. 首先是打开摄像头设备;

2. 查询设备的属性或功能;

3. 设置设备的参数,譬如像素格式、 帧大小、 帧率;

4. 申请帧缓冲、 内存映射;

5. 帧缓冲入队;

6. 开启视频采集;

7. 帧缓冲出队、对采集的数据进行处理;

8. 处理完后,再次将帧缓冲入队,往复;

9. 结束采集

三、相关接口

1.设备的打开和关闭

#include <fcntl.h>
int open(const char *device_name, int flags);
#include <unistd.h>
int close(int fd);

int fd=open(“/dev/video0”,O_RDWR); // 打开设备

close(fd); // 关闭设备

注意:V4L2 的相关定义包含在头文件<linux/videodev2.h> 中.

2.查询设备属性

VIDIOC_QUERYCAP  这个控制命令用来查询设备的属性

ioctl()函数是一个通用的I/O控制函数,在Linux中用于与设备进行通信。

ioctl()对于设备文件来说是一个非常重要的系统调用, 凡是涉及到配置设备、获取设备配置等操作都会使用 ioctl 来完成;但对于普通文件来说, ioctl()几乎没什么用。

int ioctl(int fd, unsigned long request, ... /* variable argument list */ );
V4L2 指令描述
VIDIOC_QUERYCAP查询设备的属性/能力/功能
VIDIOC_ENUM_FMT枚举设备支持的像素格式
VIDIOC_G_FMT获取设备当前的帧格式信息
VIDIOC_S_FMT设置帧格式信息
VIDIOC_REQBUFS申请帧缓冲
VIDIOC_QUERYBUF查询帧缓冲
VIDIOC_QBUF帧缓冲入队操作
VIDIOC_DQBUF帧缓冲出队操作
VIDIOC_STREAMON开启视频采集
VIDIOC_STREAMOFF关闭视频采集
VIDIOC_G_PARM获取设备的一些参数
VIDIOC_S_PARM设置参数
VIDIOC_TRY_FMT尝试设置帧格式、用于判断设备是否支持该格式
VIDIOC_ENUM_FRAMESIZES枚举设备支持的视频采集分辨率
VIDIOC_ENUM_FRAMEINTERVALS枚举设备支持的视频采集帧


 例如

int ioctl(int fd, int request, struct v4l2_capability *argp);

fd是设备文件描述符,request是请求号,argp是一个指向结构体的指针,用于传递额外的参数。在这个例子中,request是VIDIOC_QUERYCAP,表示查询设备属性。

argp是一个指向struct v4l2_capability结构体的指针,用于存储设备能力的相关信息。

struct v4l2_capability

{

u8 driver[16]; // 驱动名字

u8 card[32]; // 设备名字

u8 bus_info[32]; // 设备在系统中的位置

u32 version; // 驱动版本号

u32 capabilities; // 设备支持的操作

u32 reserved[4]; // 保留字段

};

显示设备信息

struct v4l2_capability cap;

ioctl(fd,VIDIOC_QUERYCAP,&cap);

printf(“Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n”,
                cap.driver,
                cap.card,
                cap.bus_info,
                (cap.version>>16)&0XFF,
                (cap.version>>8)&0XFF,
                cap.version&0XFF);

3.设置视频帧格式 

int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);

struct v4l2_fmtdesc结构体的指针,用于存储格式描述信息

int ioctl(int fd, int request, struct v4l2_format *argp);

4l2_format结构体的指针,用于设置或获取格式信息

v4l2_format 的各个域,如 type(传输流类型),

fmt.pix.width(宽),

fmt.pix.heigth(高),

fmt.pix.field(采样区域,如隔行采样),

fmt.pix.pixelformat(采样类型,如 YUV4:2:2),

然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式

四、用V4L2 查询设备信息以及 用它驱动摄像头拍摄一张JPEG格式的照片。

#if 0
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define DEVICE "/dev/video0"


//  Driver Name: PC Camera
//  Driver Version: 132864
//  Current Format:
//  Width: 320
//  Height: 240
//  Pixel Format: JPEG
//  Supported Formats:
//  JPEG

void print_device_info(int fd) {   //fd 是表示视频设备的文件描述符
    struct v4l2_capability cap; // 用于存储设备的能力信息
    struct v4l2_format fmt;//用于存储设备的输出格式
    struct v4l2_fmtdesc fmt_desc;//用于枚举视频格式
    int i, ret;

    memset(&cap, 0, sizeof(cap));//  初始化清零
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {//使用ioctl函数查询设备能力,如果失败则输出错误信息并返回
        perror("VIDIOC_QUERYCAP");
        return;
    }
    printf("Driver Name: %s\n", cap.card);//打印设备名称
    printf("Driver Version: %u\n", cap.version);//打印设备版本信息

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//  表示视频捕获
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
        perror("VIDIOC_G_FMT");
        return;
    }
    printf("Current Format:\n");
    printf("  Width: %d\n", fmt.fmt.pix.width);//当前视频格式 宽和高
    printf("  Height: %d\n", fmt.fmt.pix.height);
    printf("  Pixel Format: %c%c%c%c\n",//打印当前视频格式的像素格式  
           fmt.fmt.pix.pixelformat & 0xFF,//fmt.fmt.pix.pixelformat 的从低到高的24位
           (fmt.fmt.pix.pixelformat >> 8) & 0xFF,//
           (fmt.fmt.pix.pixelformat >> 16) & 0xFF,//
           (fmt.fmt.pix.pixelformat >> 24) & 0xFF);//

    memset(&fmt_desc, 0, sizeof(fmt_desc));//初始化
    fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储的格式类型
    printf("Supported Formats:\n");//支持的类型
    for (i = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmt_desc) == 0; i++) {
        printf("  %c%c%c%c\n",
               fmt_desc.pixelformat & 0xFF,
               (fmt_desc.pixelformat >> 8) & 0xFF,
               (fmt_desc.pixelformat >> 16) & 0xFF,
               (fmt_desc.pixelformat >> 24) & 0xFF);
        fmt_desc.index++;
    }
}

int main() {
    int fd = open(DEVICE, O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    print_device_info(fd);

    close(fd);
    return 0;
}
#endif
#if 1
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <jpeglib.h>  // 需要安装 libjpeg-dev

#define DEVICE "/dev/video0"
#define NB_BUFFER 4

typedef struct videoDeviceParam {      
    int iFd;   //文件描述符  用于标示打开的文件或设备
    int iWidth;
    int iHeight;
    int iBpp;//  表示位深度   每个像素占用的位数
    int pixelformat;// 表示视频帧的像素格式
    int iVideoBufCnt;//  标示视频缓冲区的数量
    int iVideoBufCurIndex;// 表示当前视频缓冲区的索引
    int iVideoBufMaxLen;//标示最大视频缓冲区长度
    unsigned char *pucVideBuf[NB_BUFFER];// 指针数组 用于存储视频缓冲区的指针
} T_videoDeviceParam, *PT_videoDeviceParam;//将结构体重命名  同时定义了指向该结构体的指针

//
int V4l2InitDevice(char *videoDevName, PT_videoDeviceParam pt_videoDeviceParam) {
    int i;
    int iFd;
    int iError;

    struct v4l2_capability tV4l2Cap;
    struct v4l2_format tV4l2Fmt;//声明一个 v4l2_format 结构体 用于设置视频格式
    struct v4l2_requestbuffers tV4l2ReqBuffs;//  用于请求缓冲区
    struct v4l2_buffer tV4l2Buf;//用于操作缓冲区

    iFd = open(videoDevName, O_RDWR);//打开视频设备
    if (iFd < 0) {
        perror("Video OPEN Failed");
        return -1;
    }
    pt_videoDeviceParam->iFd = iFd;

    memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));//初始化
    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
    if (iError) {
        perror("Unable to query device");
        close(iFd);
        return -1;
    }

    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type成员指定视频设备的操作类型 
	                         //V4L2_BUF_TYPE_VIDEO_CAPTURE 表示对视频进行视频捕捉操作
    tV4l2Fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; // Example format  设置像素格式为JPEG
    tV4l2Fmt.fmt.pix.width = 320;
    tV4l2Fmt.fmt.pix.height = 240;
    tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;//设置场域为任意

    iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);//  VIDIOC_S_FMT 控制指令 用于设置视频设备的格式
    if (iError) {
        perror("Unable to set format");
        close(iFd);
        return -1;
    }

    pt_videoDeviceParam->iWidth = tV4l2Fmt.fmt.pix.width;
    pt_videoDeviceParam->iHeight = tV4l2Fmt.fmt.pix.height;

    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;//设置缓冲区数量
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置缓冲区类型
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;//设置内存映射类型
    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);//求情缓冲区
    if (iError) {
        perror("Unable to request buffers");
        close(iFd);
        return -1;
    }

    pt_videoDeviceParam->iVideoBufCnt = tV4l2ReqBuffs.count;//将缓冲区数量赋值

    if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {// 如果设备支持流式传输
        for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
            memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));//清零
            tV4l2Buf.index = i;//设置缓冲区索引
            tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            tV4l2Buf.memory = V4L2_MEMORY_MMAP;
            iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);//查询缓冲区
            if (iError) {
                perror("Unable to query buffer");
                close(iFd);
                return -1;
            }

            pt_videoDeviceParam->iVideoBufMaxLen = tV4l2Buf.length;//将缓冲区最大长度赋值
            pt_videoDeviceParam->pucVideBuf[i] = mmap(0,
                tV4l2Buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, iFd,
                tV4l2Buf.m.offset);//将缓冲区地址赋值给结构体中的相应元素
            if (pt_videoDeviceParam->pucVideBuf[i] == MAP_FAILED) {//如果映射失败 报错
                perror("Unable to map buffer");
                close(iFd);
                return -1;
            }
        }

        for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
            memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
            tV4l2Buf.index = i;//
            tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//
            tV4l2Buf.memory = V4L2_MEMORY_MMAP;//
            iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);//将缓冲区加入队列
            if (iError) {
                perror("Unable to queue buffer");
                close(iFd);
                return -1;
            }
        }

        // Start streaming
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(iFd, VIDIOC_STREAMON, &type) < 0) {//开始流式传输
            perror("Unable to start streaming");
            close(iFd);
            return -1;
        }

        // Capture a single frame
        memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        if (ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf) < 0) {//从队列中取出一个缓冲区
            perror("Unable to dequeue buffer");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        unsigned char *frame_data = pt_videoDeviceParam->pucVideBuf[tV4l2Buf.index];//获取缓冲区数据指针
        printf("Captured a frame of length %d\n", tV4l2Buf.bytesused);//打印捕获到的帧长度

        // Save the frame as JPEG
        FILE *file = fopen("capture.jpg", "wb");//打开*.jpg文件进行写入  wb表示以二进制写入的方式打开文件
        if (!file) {
            perror("Unable to open file");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        fwrite(frame_data, 1, tV4l2Buf.bytesused, file);//写入帧数据到文件
        fclose(file);

        if (ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf) < 0) {//将缓冲区重新加入队列
            perror("Unable to queue buffer");
            ioctl(iFd, VIDIOC_STREAMOFF, &type);
            close(iFd);
            return -1;
        }

        // Stop streaming
        if (ioctl(iFd, VIDIOC_STREAMOFF, &type) < 0) {//停止流式传输
            perror("Unable to stop streaming");
        }
    }

    // Cleanup
    for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
        munmap(pt_videoDeviceParam->pucVideBuf[i], pt_videoDeviceParam->iVideoBufMaxLen);//释放缓冲区内存
    }
    close(iFd);

    return 0;
}

int main() {
    T_videoDeviceParam videoDeviceParam;
    if (V4l2InitDevice(DEVICE, &videoDeviceParam) == 0) {// 调用 V4l2InitDevice 函数  
//函数接受 一个设备名称字符串 和一个 指向 T_videoDeviceParam 结构体的指针作为参数 成功返回0 失败返回-1
        printf("Device initialized successfully.\n");
    } else {
        printf("Failed to initialize device.\n");
    }
    return 0;
}
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值