USB摄像头使用记录

USB摄像头使用记录

1.概述

1.1 v4l2介绍

V4L2是Video for linux two的简称,是Linux内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范,那什么是视频类设备呢?一个非常典型的视频类设备就是视频采集设备,譬如各种摄像头;当然还包括其它类型视频类设备,这里就不再给介绍了。
使用V4L2设备驱动框架注册的设备会在Linux系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为videoX(X标准一个数字编号,0、1、2、3……),每一个videoX设备文件就代表一个视频类设备。应用程序通过对videoX设备文件进行I/O操作来配置、使用设备类设备

1.2v4l2应用层操作步骤

1.首先是打开摄像头设备;
2.查询设备的属性或功能;
3.设置设备的参数,譬如像素格式、帧大小、帧率;
4.申请帧缓冲、内存映射;
5.帧缓冲入队;
6.开启视频采集;
7.帧缓冲出队、对采集的数据进行处理;
8.处理完后,再次将帧缓冲入队,往复;
9.结束采集。

操作流程图:
在这里插入图片描述

文件系统操作的节点

在这里插入图片描述

缓冲区原理图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.使用

2.1linux应用

2.1.0获取并打印摄像头参数

/* 枚举出摄像头所支持的所有视频像素格式\采集分辨率\帧率 */
static void v4l2_print_formats(camera_ctrl_t* camera_ctrl)
{
    camera_ctrl->fmtdesc.index = 0;
    camera_ctrl->fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频采集
    /*查看支持的像素格式*/
    while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FMT, &camera_ctrl->fmtdesc)) {
        printf("fmt: \" %s \"  <0x%d>\n", camera_ctrl->fmtdesc.description, camera_ctrl->fmtdesc.pixelformat);
        camera_ctrl->fmtdesc.index++;

        camera_ctrl->frmsize.index = 0;
        camera_ctrl->frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        camera_ctrl->frmsize.pixel_format = camera_ctrl->fmtdesc.pixelformat;
        /*查看支持的分辨率*/
        while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FRAMESIZES, &camera_ctrl->frmsize)) {
            printf("frm_size  \t <%d*%d>\n", camera_ctrl->frmsize.discrete.width, camera_ctrl->frmsize.discrete.height);
            camera_ctrl->frmsize.index++;

            camera_ctrl->frmival.index = 0;
            camera_ctrl->frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            camera_ctrl->frmival.pixel_format = camera_ctrl->fmtdesc.pixelformat;
            camera_ctrl->frmival.width = camera_ctrl->frmsize.discrete.width;
            camera_ctrl->frmival.height = camera_ctrl->frmsize.discrete.height;
            /*查看支持的帧率*/
            while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FRAMEINTERVALS, &camera_ctrl->frmival)) {
                printf("\t <%dfps>\n", (camera_ctrl->frmival.discrete.denominator / camera_ctrl->frmival.discrete.numerator));
                camera_ctrl->frmival.index++;
            }
        }
        printf("\r\n");
    }
}
2.1.1设置图片或者数据的格式

/* 设置格式 */
static int v4l2_set_format(camera_ctrl_t* camera_ctrl, unsigned int format, int width, int height)
{
    // 检查输入参数是否有效
    if (camera_ctrl == NULL) {
        fprintf(stderr, "Error: camera_ctrl is NULL\n");
        return -1;
    }

    if (width <= 0 || height <= 0) {
        fprintf(stderr, "Error: invalid width or height\n");
        return -1;
    }

    // 清零并初始化格式结构体
    memset(&camera_ctrl->fmt, 0, sizeof(struct v4l2_format));
    camera_ctrl->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频采集
    camera_ctrl->fmt.fmt.pix.width = width; // 设置分辨率的宽
    camera_ctrl->fmt.fmt.pix.height = height; // 设置分辨率的高
    camera_ctrl->fmt.fmt.pix.pixelformat = format; // 设置视频输出格式
    camera_ctrl->fmt.fmt.pix.field = V4L2_FIELD_ANY;

    // 设置视频格式
    if (ioctl(camera_ctrl->fd, VIDIOC_S_FMT, &camera_ctrl->fmt) < 0) {
        perror("Error: v4l2_set_format"); // 打印错误信息
        fprintf(stderr, "Failed to set format: %s\n", strerror(errno));
        return -1; // 返回错误标志
    }

    // 查询当前摄像头的工作模式
    if (ioctl(camera_ctrl->fd, VIDIOC_G_FMT, &camera_ctrl->fmt) < 0) {
        perror("Error: v4l2_get_format"); // 打印错误信息
        fprintf(stderr, "Failed to get format: %s\n", strerror(errno));
        return -1; // 返回错误标志
    }
    
    // 检查设置是否成功
    if (camera_ctrl->fmt.fmt.pix.pixelformat == format &&
        camera_ctrl->fmt.fmt.pix.width == width &&
        camera_ctrl->fmt.fmt.pix.height == height) {
        printf("Successfully set format: width=%d, height=%d\n", camera_ctrl->fmt.fmt.pix.width, camera_ctrl->fmt.fmt.pix.height);
        return 0; // 返回成功标志
    } else {
        fprintf(stderr, "Failed to set format: requested (width=%d, height=%d), got (width=%d, height=%d)\n",
                width, height, camera_ctrl->fmt.fmt.pix.width, camera_ctrl->fmt.fmt.pix.height);
        return -1; // 返回错误标志
    }
}
注意:这里如果漏掉设置的话,后续存储的图片可能打不开
2.1.2申请帧缓冲、内存映射

内核中相关结构体定义

帧缓冲buffer请求结构体
struct v4l2_requestbuffers {
	__u32			count;
	__u32			type;		/* enum v4l2_buf_type */
	__u32			memory;		/* enum v4l2_memory */
	__u32			reserved[2];
};

count:请求的缓冲区数量
type:请求的缓冲区类型,通常是枚举类型v4l2_buf_type的值
memory:请求的内存类型,通常是枚举类型v4l2_memory的值
reserved:保留字段
    
示例用法:
struct v4l2_requestbuffers req;
req.count = 4;  // 请求4个缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 请求视频捕获类型的缓冲区
req.memory = V4L2_MEMORY_MMAP;  // 请求内存映射类型的存储
帧缓冲buffer管理结构体

struct v4l2_buffer {
	__u32			index;
	__u32			type;
	__u32			bytesused;
	__u32			flags;
	__u32			field;
	struct timeval		timestamp;
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	__u32			memory;
	union {
		__u32           offset;
		unsigned long   userptr;
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	__u32			length;
	__u32			reserved2;
	__u32			reserved;
};



index:缓冲区的索引号
type:缓冲区的类型
bytesused:已使用的字节数
flags:标志位
field:视频帧的场类型
timestamp:时间戳,用于记录视频帧的时间信息
timecode:时间码
sequence:序列号
接下来是关于内存位置的信息:

memory:内存位置属性,表示缓冲区数据所在的内存类型
m:联合体,根据memory的值选择不同的成员。可能是offset(偏移量)、userptr(用户指针)、planes(平面)或fd(文件描述符)
其他成员包括:

length:缓冲区的长度
reserved2:保留字段
reserved:保留字段
    
    
该结构体用于管理视频缓冲区的信息

上述两个结构体作用和区别

这两个结构体 v4l2_requestbuffers 和 v4l2_buffer 在视频设备的操作中有紧密的联系,它们共同参与管理和操作视频缓冲区。具体来说,v4l2_requestbuffers 用于请求视频设备分配缓冲区,而 v4l2_buffer 用于描述和管理这些缓冲区的具体属性和状态。

自定义结构体

typedef struct my_camera_ctrl
{
    int fd; //file_handle
    int type;
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;//像素格式
    struct v4l2_frmsizeenum frmsize;//采集分辨率
    struct v4l2_frmivalenum frmival;//帧率
    struct v4l2_format fmt; //设置设备参数
    struct v4l2_requestbuffers reqbuf;//帧缓冲区
    struct v4l2_buffer buf;
    unsigned char *frm_base[4];
    
} camera_ctrl_t;

/*申请帧缓冲、内存映射*/
static int v4l2_init_buffer(camera_ctrl_t* camera_ctrl)
{
    if (camera_ctrl)
    {
        LOG_ERROR("NULL PTR err\n");
        return -1;
    }
    //reqbuf requst 
    memset(&camera_ctrl->reqbuf, 0, sizeof(camera_ctrl->reqbuf));
    camera_ctrl->reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    camera_ctrl->reqbuf.count = NUM_BUFF;  //帧缓冲的数量
    camera_ctrl->reqbuf.memory =  V4L2_MEMORY_MMAP;
    /*申请帧缓冲*/
    if(ioctl(camera_ctrl->fd, VIDIOC_REQBUFS, &camera_ctrl->reqbuf) < 0){
        printf("error: v4l2_init_buffer\r\n");
        return 0;
    }

    //buf 获取和操作
    memset(&camera_ctrl->buf, 0, sizeof(camera_ctrl->buf));
    camera_ctrl->buf.index = 0;
    camera_ctrl->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    camera_ctrl->buf.memory = V4L2_MEMORY_MMAP;
    /*内存映射*/ 
    for(camera_ctrl->buf.index = 0;camera_ctrl->buf.index < NUM_BUFF;camera_ctrl->buf.index++)
    {
        if(ioctl(camera_ctrl->fd, VIDIOC_QUERYBUF, &camera_ctrl->buf))
        {
            LOG_ERROR("ioctl err\n");
            return -1;
        }
        camera_ctrl->frm_base[camera_ctrl->buf.index] = mmap(NULL, camera_ctrl->buf.length, \
                                PROT_READ | PROT_WRITE, MAP_SHARED, \
                                camera_ctrl->fd, camera_ctrl->buf.m.offset);
        if (MAP_FAILED == camera_ctrl->frm_base[camera_ctrl->buf.index]) {
            perror("mmap error");
            return -1;
        }
        printf("查询内存成功  camera_ctrl->buf[%d]==%d长度\r\n",camera_ctrl->buf.index,camera_ctrl->buf.length);
        //将填充好的buff加入视频设备的输入或输出队列中,从而使视频设备可以处理该缓冲区的数据。
        if(ioctl(camera_ctrl->fd,VIDIOC_QBUF,&camera_ctrl->buf) < 0){
            printf("error: VIDIOC_QBUF\r\n");
            return 0;
        }
    }
    return 0;
}

2.1.3打开文件,下发数据据采集指令
static int v4l2_stream_on(camera_ctrl_t* camera_ctrl)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    /* 打开摄像头、摄像头开始采集数据 */
    if (ioctl(camera_ctrl->fd, VIDIOC_STREAMON, &type) < 0) {
        printf("error: v4l2_stream_on\r\n");
        return 0;
    }
    printf("Camera_open : success\r\n");
    return 0;
}
2.1.4读取数据
// 开始数据采集
static int v4l2_read_data(camera_ctrl_t* camera_ctrl)
{
    //read data的操作里面可以将图片另存为其他数据
    //也可以将将数据打印到显示屏幕上进行实时显示

    camera_ctrl->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    camera_ctrl->buf.memory = V4L2_MEMORY_MMAP;
    camera_ctrl->buf.index = 1;

    while(1)
    {
		for(camera_ctrl->buf.index = 1; camera_ctrl->buf.index < 4; camera_ctrl->buf.index++)
		{
		    if(ioctl(camera_ctrl->fd, VIDIOC_DQBUF, &camera_ctrl->buf)!=0)//出队
		    {
		        printf("提取数据失败\r\n");
                return -1;
		    }
            // Send_Video_Data(connfd,frm_base[camera_ctrl->buf.index],camera_ctrl->buf.length);

		    if(ioctl(camera_ctrl->fd, VIDIOC_QBUF, &camera_ctrl->buf)!=0)//入队
		    {
		        printf("放回队列失败\r\n");
		        exit(1);
		    }
		    usleep(33000);
		}
    }
    return 0;
}
2.1.5下发steam off指令停止采集
static int v4l2_stream_off(camera_ctrl_t* camera_ctrl)
{
    // 判空
    if (NULL == camera_ctrl) {
        LOG_ERROR("v4l2_stream_off camera_ctrl ptr is NULL\n");
        return -1;
    }
    camera_ctrl->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(camera_ctrl->fd, VIDIOC_STREAMOFF, &camera_ctrl->type)) {
        LOG_ERROR("ioctl VIDIOC_STREAMOFF err\n");
        return -1;
    }

    // 取消内存映射
    for (int i = 0; i < NUM_BUFF; i++) {
        printf("Attempting to unmap buffer %d at address: %p buff length:%d\n", i, camera_ctrl->frm_base[i], camera_ctrl->buff_length);
        if (munmap(camera_ctrl->frm_base[i], camera_ctrl->buff_length) == -1) {
            printf("munmap buffer %d failed: %s\n", i, strerror(errno));
            exit(1);
        }
    }
    //  关闭文件
    if (close(camera_ctrl->fd) == -1) {
        LOG_ERROR("close camera file descriptor failed\n");
    }
    camera_ctrl->fd = -1; // 将文件描述符设置为无效值
    fprintf(stdout, "stream off sucess\n");
    return 0;
}

2.2linux驱动

3.调试

make
    
cp bin/USB_CAMERA ~/nfs_rootfs/
    
ls /dev/video*
    

3.1韦东山参考示例

常用的VIDIOC命令

  1. VIDIOC_QUERYCAP (查询设备属性)
  2. VIDIOC_ENUM_FMT (显示所有支持的格式)
  3. VIDIOC_S_FMT (设置视频捕获格式)
  4. VIDIOC_G_FMT (获取硬件现在的视频捕获格式)
  5. VIDIOC_TRY_FMT (检查是否支持某种帧格式)
  6. VIDIOC_ENUM_FRAMESIZES (枚举设备支持的分辨率信息)
  7. VIDIOC_ENUM_FRAMEINTERVALS (获取设备支持的帧间隔)
  8. VIDIOC_S_PARM && VIDIOC_G_PARM (设置和获取流参数)
  9. VIDIOC_QUERYCAP (查询驱动的修剪能力)
  10. VIDIOC_S_CROP (设置视频信号的边框)
  11. VIDIOC_G_CROP (读取设备信号的边框)
  12. VIDIOC_REQBUFS (向设备申请缓存区)
  13. VIDIOC_QUERYBUF (获取缓存帧的地址、长度)
  14. VIDIOC_QBUF (把帧放入队列)
  15. VIDIOC_DQBUF (从队列中取出帧)
  16. VIDIOC_STREAMON && VIDIOC_STREAMOFF (启动/停止视频数据流)
  
V4L2_BUF_TYPE_VIDEO_OUTPUT:指定缓冲类型为视频输出,用于将视频数据从应用程序传输到设备进行显示。

V4L2_CID_BRIGHTNESS:控制视频亮度参数。

V4L2_CID_CONTRAST:控制视频对比度参数。

V4L2_CID_SATURATION:控制视频饱和度参数。

V4L2_CID_HUE:控制视频色调参数。

V4L2_CID_EXPOSURE:控制视频曝光参数。

V4L2_CID_GAIN:控制视频增益参数。

V4L2_CID_WHITE_BALANCE_TEMPERATURE:控制视频白平衡温度参数。

原文链接:https://blog.csdn.net/mark_minge/article/details/81427489

上述的命令都可以在内核中的头文件linux/videodev2.h中可以看到更详细的数据结构和宏定义,有兴趣的可以看看,我贴在下面:

/*
 *	I O C T L   C O D E S   F O R   V I D E O   D E V I C E S
 *
 */
#define VIDIOC_QUERYCAP			_IOR('V',  0, struct v4l2_capability)
#define VIDIOC_RESERVED			_IO('V',  1)
#define VIDIOC_ENUM_FMT         	_IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT				_IOWR('V',  4, struct v4l2_format)
#define VIDIOC_S_FMT				_IOWR('V',  5, struct v4l2_format)
#define VIDIOC_REQBUFS				_IOWR('V',  8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF			_IOWR('V',  9, struct v4l2_buffer)
#define VIDIOC_G_FBUF		 		_IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF		 		_IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY		 	_IOW('V', 14, int)
#define VIDIOC_QBUF					_IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_EXPBUF				_IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF				_IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON		 	_IOW('V', 18, int)
#define VIDIOC_STREAMOFF	 		_IOW('V', 19, int)
#define VIDIOC_G_PARM				_IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM				_IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD		 		_IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD		 		_IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD			_IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT			_IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL				_IOWR('V', 27, struct v4l2_control)
#define VIDIOC_S_CTRL				_IOWR('V', 28, struct v4l2_control)
#define VIDIOC_G_TUNER				_IOWR('V', 29, struct v4l2_tuner)
#define VIDIOC_S_TUNER		 		_IOW('V', 30, struct v4l2_tuner)
#define VIDIOC_G_AUDIO		 		_IOR('V', 33, struct v4l2_audio)
#define VIDIOC_S_AUDIO		 		_IOW('V', 34, struct v4l2_audio)
#define VIDIOC_QUERYCTRL			_IOWR('V', 36, struct v4l2_queryctrl)
#define VIDIOC_QUERYMENU			_IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT		 		_IOR('V', 38, int)
#define VIDIOC_S_INPUT				_IOWR('V', 39, int)
#define VIDIOC_G_EDID				_IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_S_EDID				_IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_G_OUTPUT		 	_IOR('V', 46, int)
#define VIDIOC_S_OUTPUT			_IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT		_IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_AUDOUT		 	_IOR('V', 49, struct v4l2_audioout)
#define VIDIOC_S_AUDOUT		 	_IOW('V', 50, struct v4l2_audioout)
#define VIDIOC_G_MODULATOR		_IOWR('V', 54, struct v4l2_modulator)
#define VIDIOC_S_MODULATOR	 	_IOW('V', 55, struct v4l2_modulator)
#define VIDIOC_G_FREQUENCY		_IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY	 	_IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP				_IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP				_IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP		 		_IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP	 		_IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP	 		_IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD      	 	_IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT      		_IOWR('V', 64, struct v4l2_format)
#define VIDIOC_ENUMAUDIO			_IOWR('V', 65, struct v4l2_audio)
#define VIDIOC_ENUMAUDOUT		_IOWR('V', 66, struct v4l2_audioout)
#define VIDIOC_G_PRIORITY	 		_IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY	 		_IOW('V', 68, __u32) /* enum v4l2_priority */
#define VIDIOC_G_SLICED_VBI_CAP 	_IOWR('V', 69, struct v4l2_sliced_vbi_cap)
#define VIDIOC_LOG_STATUS       	_IO('V', 70)
#define VIDIOC_G_EXT_CTRLS		_IOWR('V', 71, struct v4l2_ext_controls)
#define VIDIOC_S_EXT_CTRLS			_IOWR('V', 72, struct v4l2_ext_controls)
#define VIDIOC_TRY_EXT_CTRLS		_IOWR('V', 73, struct v4l2_ext_controls)
#define VIDIOC_ENUM_FRAMESIZES	_IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS		_IOWR('V', 75, struct v4l2_frmivalenum)
#define VIDIOC_G_ENC_INDEX     	_IOR('V', 76, struct v4l2_enc_idx)
#define VIDIOC_ENCODER_CMD    	_IOWR('V', 77, struct v4l2_encoder_cmd)
#define VIDIOC_TRY_ENCODER_CMD 	_IOWR('V', 78, struct v4l2_encoder_cmd)

3.1.1 获取并打印摄像头参数

头文件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

.c

/*
 * @Descripttion: 
 * @version: 
 * @Author: Andy
 * @Date: 2024-05-26 00:03:08
 * @LastEditors: Andy
 * @LastEditTime: 2024-05-26 00:06:05
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>

int main(int argc, char **argv)
{
    int fd;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    int fmt_index = 0;
    int frame_index = 0;
    while (1)
    {
        /* 枚举格式 */
        fmtdesc.index = fmt_index;  // 比如从0开始
        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
        if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
            break;

        frame_index = 0;
        while (1)
        {
            /* 枚举这种格式所支持的帧大小 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = frame_index;

            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
            }
            else
            {
                break;
            }

            frame_index++;
        }

        fmt_index++;
    }

    return 0;
}
3.1.2get_data.c

涉及到的前置知识:

/**
  * struct v4l2_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
  *
  * @driver:	   name of the driver module (e.g. "bttv")
  * @card:	   name of the card (e.g. "Hauppauge WinTV")
  * @bus_info:	   name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
  * @version:	   KERNEL_VERSION
  * @capabilities: capabilities of the physical device as a whole
  * @device_caps:  capabilities accessed via this particular device (node)
  * @reserved:	   reserved fields for future extensions
  */
struct v4l2_capability {
	__u8	driver[16];
	__u8	card[32];
	__u8	bus_info[32];
	__u32   version;
	__u32	capabilities;
	__u32	device_caps;
	__u32	reserved[3];
};





对于__u32	capabilities;该字段描述了设备拥有的能力,该字段的值如下(可以是以下任意一个值或多个值的位或关系)
    
    /* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE		0x00000001  /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT		0x00000002  /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY		0x00000004  /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE			0x00000010  /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT			0x00000020  /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE	0x00000040  /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT	0x00000080  /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE				0x00000100  /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY	0x00000200  /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK			0x00000400  /* Can do hardware frequency seek  */
#define V4L2_CAP_RDS_OUTPUT				0x00000800  /* Is an RDS encoder */

/* Is a video capture device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE	0x00001000
/* Is a video output device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE	0x00002000
/* Is a video mem-to-mem device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_M2M_MPLANE		0x00004000
/* Is a video mem-to-mem device */
#define V4L2_CAP_VIDEO_M2M				0x00008000

#define V4L2_CAP_TUNER						0x00010000  /* has a tuner */
#define V4L2_CAP_AUDIO						0x00020000  /* has audio support */
#define V4L2_CAP_RADIO						0x00040000  /* is a radio device */
#define V4L2_CAP_MODULATOR				0x00080000  /* has a modulator */

#define V4L2_CAP_SDR_CAPTURE				0x00100000  /* Is a SDR capture device */
#define V4L2_CAP_EXT_PIX_FORMAT			0x00200000  /* Supports the extended pixel format */
#define V4L2_CAP_SDR_OUTPUT				0x00400000  /* Is a SDR output device */
#define V4L2_CAP_META_CAPTURE			0x00800000  /* Is a metadata capture device */

#define V4L2_CAP_READWRITE              	0x01000000  /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO                	0x02000000  /* async I/O */
#define V4L2_CAP_STREAMING              	0x04000000  /* streaming I/O ioctls */

#define V4L2_CAP_TOUCH                  	0x10000000  /* Is a touch device */

#define V4L2_CAP_DEVICE_CAPS            	0x80000000  /* sets device capabilities field */

    
    
    
    
    应用层使用示例:
 /* 查询设备功能 */
ioctl(fd, VIDIOC_QUERYCAP, &vcap);

/* 判断是否是视频采集设备 /
if (!(V4L2_CAP_VIDEO_CAPTURE & vcap.capabilities)) {
	fprintf(stderr, “Error: No capture video device!\n”);
	return -1;
}

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include <poll.h>
#include <sys/mman.h>

/* ./video_test </dev/video0> */

int main(int argc, char **argv)
{
    int fd;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    int fmt_index = 0;
    int frame_index = 0;
    int i;
    void *bufs[32];
    int buf_cnt;
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    struct pollfd fds[1];
    char filename[32];
    int file_cnt = 0;

    if (argc != 2)
    {
        printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
        return -1;
    }

    /* open */
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open %s\n", argv[1]);
        return -1;
    }

    /* 查询能力 */
    struct v4l2_capability cap;
    memset(&cap, 0, sizeof(struct v4l2_capability));
    
    if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
    {        
        if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
            fprintf(stderr, "Error opening device %s: video capture not supported.\n",
                    argv[1]);
            return -1;
        }
        
        if(!(cap.capabilities & V4L2_CAP_STREAMING)) {
            fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
            return -1;
        }
    }
    else
    {
        printf("can not get capability\n");
        return -1;
    }

    while (1)
    {
        /* 枚举格式 */
        fmtdesc.index = fmt_index;  // 比如从0开始
        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
        if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
            break;

        frame_index = 0;
        while (1)
        {
            /* 枚举这种格式所支持的帧大小 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = frame_index;

            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
            }
            else
            {
                break;
            }

            frame_index++;
        }

        fmt_index++;
    }


    /* 设置格式 */
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 1024;
    fmt.fmt.pix.height = 768;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))
    {
        printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    }
    else
    {
        printf("can not set format\n");
        return -1;
    }

    /*
     * 申请buffer
     */
    struct v4l2_requestbuffers rb;
    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
    rb.count = 32;
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;

    if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))
    {
        /* 申请成功后, mmap这些buffer */
        buf_cnt = rb.count;
        for(i = 0; i < rb.count; i++) {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.index = i;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
            {
                /* mmap */
                bufs[i] = mmap(0 /* start anywhere */ ,
                                  buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
                                  buf.m.offset);
                if(bufs[i] == MAP_FAILED) {
                    perror("Unable to map buffer");
                    return -1;
                }
            }
            else
            {
                printf("can not query buffer\n");
                return -1;
            }            
        }

        printf("map %d buffers ok\n", buf_cnt);
        
    }
    else
    {
        printf("can not request buffers\n");
        return -1;
    }

    /* 把所有buffer放入"空闲链表" */
    for(i = 0; i < buf_cnt; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(struct v4l2_buffer));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
        {
            perror("Unable to queue buffer");
            return -1;
        }
    }
    printf("queue buffers ok\n");

    /* 启动摄像头 */
    if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
    {
        perror("Unable to start capture");
        return -1;
    }
    printf("start capture ok\n");

    while (1)
    {
        /* poll */
        memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        if (1 == poll(fds, 1, -1))
        {
            /* 把buffer取出队列 */
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            
            if (0 != ioctl(fd, VIDIOC_DQBUF, &buf))
            {
                perror("Unable to dequeue buffer");
                return -1;
            }
            
            /* 把buffer的数据存为文件 */
            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
            int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
            if (fd_file < 0)
            {
                printf("can not create file : %s\n", filename);
            }
            printf("capture to %s\n", filename);
            write(fd_file, bufs[buf.index], buf.bytesused);
            close(fd_file);

            /* 把buffer放入队列 */
            if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
            {
                perror("Unable to queue buffer");
                return -1;
            }
        }
    }

    if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))
    {
        perror("Unable to stop capture");
        return -1;
    }
    printf("stop capture ok\n");
    close(fd);

    return 0;
}


3.1.3 ctrl_light

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include <poll.h>
#include <sys/mman.h>
#include <pthread.h>

/* ./video_test </dev/video0> */

static void *thread_brightness_control (void *args)
{
    int fd = (int)args;

    unsigned char c;
    int brightness;
    int delta;
    
    struct v4l2_queryctrl   qctrl;
    memset(&qctrl, 0, sizeof(qctrl));
    qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
    if (0 != ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))
    {
        printf("can not query brightness\n");
        return NULL;
    }

    printf("brightness min = %d, max = %d\n", qctrl.minimum, qctrl.maximum);
    delta = (qctrl.maximum - qctrl.minimum) / 10;
        
    struct v4l2_control ctl;
    ctl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
    ioctl(fd, VIDIOC_G_CTRL, &ctl);

    while (1)
    {
        c = getchar();
        if (c == 'u' || c == 'U')
        {
            ctl.value += delta;
        }
        else if (c == 'd' || c == 'D')
        {
            ctl.value -= delta;
        }
        if (ctl.value > qctrl.maximum)
            ctl.value = qctrl.maximum;
        if (ctl.value < qctrl.minimum)
            ctl.value = qctrl.minimum;

        ioctl(fd, VIDIOC_S_CTRL, &ctl);
    }
    return NULL;
}

int main(int argc, char **argv)
{
    int fd;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    int fmt_index = 0;
    int frame_index = 0;
    int i;
    void *bufs[32];
    int buf_cnt;
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    struct pollfd fds[1];
    char filename[32];
    int file_cnt = 0;

    if (argc != 2)
    {
        printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
        return -1;
    }

    /* open */
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open %s\n", argv[1]);
        return -1;
    }

    /* 查询能力 */
    struct v4l2_capability cap;
    memset(&cap, 0, sizeof(struct v4l2_capability));
    
    if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
    {        
        if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
            fprintf(stderr, "Error opening device %s: video capture not supported.\n",
                    argv[1]);
            return -1;
        }
        
        if(!(cap.capabilities & V4L2_CAP_STREAMING)) {
            fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
            return -1;
        }
    }
    else
    {
        printf("can not get capability\n");
        return -1;
    }

    while (1)
    {
        /* 枚举格式 */
        fmtdesc.index = fmt_index;  // 比如从0开始
        fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
        if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
            break;

        frame_index = 0;
        while (1)
        {
            /* 枚举这种格式所支持的帧大小 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = frame_index;

            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
            }
            else
            {
                break;
            }

            frame_index++;
        }

        fmt_index++;
    }


    /* 设置格式 */
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 1024;
    fmt.fmt.pix.height = 768;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))
    {
        printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    }
    else
    {
        printf("can not set format\n");
        return -1;
    }

    /*
     * 申请buffer
     */
    struct v4l2_requestbuffers rb;
    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
    rb.count = 32;
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;

    if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))
    {
        /* 申请成功后, mmap这些buffer */
        buf_cnt = rb.count;
        for(i = 0; i < rb.count; i++) {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.index = i;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
            {
                /* mmap */
                bufs[i] = mmap(0 /* start anywhere */ ,
                                  buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
                                  buf.m.offset);
                if(bufs[i] == MAP_FAILED) {
                    perror("Unable to map buffer");
                    return -1;
                }
            }
            else
            {
                printf("can not query buffer\n");
                return -1;
            }            
        }

        printf("map %d buffers ok\n", buf_cnt);
        
    }
    else
    {
        printf("can not request buffers\n");
        return -1;
    }

    /* 把所有buffer放入"空闲链表" */
    for(i = 0; i < buf_cnt; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(struct v4l2_buffer));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
        {
            perror("Unable to queue buffer");
            return -1;
        }
    }
    printf("queue buffers ok\n");

    /* 启动摄像头 */
    if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
    {
        perror("Unable to start capture");
        return -1;
    }
    printf("start capture ok\n");


    /* 创建线程用来控制亮度 */
    pthread_t thread;
    pthread_create(&thread, NULL, thread_brightness_control, (void *)fd);
   

    while (1)
    {
        /* poll */
        memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        if (1 == poll(fds, 1, -1))
        {
            /* 把buffer取出队列 */
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            
            if (0 != ioctl(fd, VIDIOC_DQBUF, &buf))
            {
                perror("Unable to dequeue buffer");
                return -1;
            }
            
            /* 把buffer的数据存为文件 */
            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
            int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
            if (fd_file < 0)
            {
                printf("can not create file : %s\n", filename);
            }
            printf("capture to %s\n", filename);
            write(fd_file, bufs[buf.index], buf.bytesused);
            close(fd_file);

            /* 把buffer放入队列 */
            if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
            {
                perror("Unable to queue buffer");
                return -1;
            }
        }
    }

    if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))
    {
        perror("Unable to stop capture");
        return -1;
    }
    printf("stop capture ok\n");
    close(fd);

    return 0;
}

3.2自己调试成品集成

3.2.1目录结构
.
├── bin
│   └── USB_CAMERA
├── build
│   ├── camera_utils
│   │   └── usb_camera.o
│   └── common
│       └── main.o
├── makefile
└── src
    ├── camera_utils
    │   ├── camera_utils.h
    │   ├── usb_camera.c
    │   └── usb_camera.h
    └── common
        ├── main.c
        └── main.h

7 directories, 9 files

3.2.2代码内容

makefile
.PHONY: all clean
# 项目名称
PROJECT_NAME := USB_CAMERA

# 编译器
CC := arm-buildroot-linux-gnueabihf-gcc

# 编译选项
CFLAGS := -g -Wno-unused-variable -Wno-unused-function

# 源文件目录
SRC_DIR := src

# 头文件目录
INC_DIR := -Iinclude

# 目标文件目录
BUILD_DIR := build

# 可执行文件输出目录
BIN_DIR := bin

# 使用shell和find命令查找整个项目目录下的所有.c文件
SRCS := $(shell find $(SRC_DIR) -type f -name '*.c')

# 目标文件
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)

# 可执行文件
TARGET := $(BIN_DIR)/$(PROJECT_NAME)

# 编译目标
$(TARGET): $(OBJS) | $(BIN_DIR)
	$(CC) $(CFLAGS) $^ -o $@

# 编译规则
# 生成目标文件的规则
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
#创建目录(如果不存在)
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) -c $< -o $@

# 创建目录
$(BUILD_DIR):
	mkdir -p $@

$(BIN_DIR):
	mkdir -p $@

# 打印所有.c文件的列表
print_srcs:
	@echo "All .c files in $(SRC_DIR) and its subdirectories:"
	@$(foreach src,$(SRCS),echo $(src);)

# 默认目标
all: 
	@echo "building ..."
	@$(MAKE) -f $(sub_mk)
	$(TARGET)
# 清理规则
clean:
	@echo "Cleaning up..."
	rm -rf $(BUILD_DIR) $(BIN_DIR)

camera_utils.h
/*
 * @Descripttion: 
 * @version: 
 * @Author: Andy
 * @Date: 2024-06-06 00:18:52
 * @LastEditors: Andy
 * @LastEditTime: 2024-06-23 19:57:25
 */
#ifndef __CAMERA_UTILS_H__
#define __CAMERA_UTILS_H__
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include <time.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <errno.h> 

#define NUM_BUFF 32
#define pic_width 1280
#define pic_height 720



#define LOG_ERROR(msg) do { \
    time_t now = time(NULL); \
    struct tm *t = localtime(&now); \
    fprintf(stderr, "[%d-%02d-%02d %02d:%02d:%02d] Error in file [%s], function [%s], line [%d]: [%s] (errno: %d)\n", \
            t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, \
            t->tm_hour, t->tm_min, t->tm_sec, __FILE__, __func__, __LINE__, msg, errno); \
} while (0)


typedef struct my_camera_ctrl
{
    int fd; //file_handle
    int type;
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;//像素格式
    struct v4l2_frmsizeenum frmsize;//采集分辨率
    struct v4l2_frmivalenum frmival;//帧率
    struct v4l2_format fmt; //设置设备参数
    struct v4l2_requestbuffers reqbuf;//帧缓冲区
    struct v4l2_buffer buf;
    unsigned char *frm_base[NUM_BUFF];
    int count_jpg;
    int buff_length;
} camera_ctrl_t;


#endif
usb_camera.h
/*
 * @Descripttion: 
 * @version: 
 * @Author: Andy
 * @Date: 2024-06-07 00:36:03
 * @LastEditors: Andy
 * @LastEditTime: 2024-06-23 15:48:22
 */
#ifndef __USB_CAMERA_H__
#define __USB_CAMERA_H__

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include "camera_utils.h"

static int camera_capabilities_pri(camera_ctrl_t camera_ctrl);
static int camera_frmsizeenum_pri(camera_ctrl_t camera_ctrl);
static int camera_fmtdesc_pri(camera_ctrl_t camera_ctrl);

/* 枚举出摄像头所支持的所有视频像素格式\采集分辨率\帧率 */
static void v4l2_print_formats(camera_ctrl_t* camera_ctrl);

/* 设置格式 */
static int v4l2_set_format(camera_ctrl_t* camera_ctrl, unsigned int format, int width, int height);

/*申请帧缓冲、内存映射*/
static int v4l2_init_buffer(camera_ctrl_t *camera_ctrl);


static int v4l2_stream_on(camera_ctrl_t* camera_ctrl);
//将缓冲区中拿到的buff数据另存为照片

static int Buffer_Save_As_Image(camera_ctrl_t* camera_ctrl, const char* filename, struct v4l2_buffer buf);

static int v4l2_read_data(camera_ctrl_t* camera_ctrl);

//关闭数据采集
static int v4l2_stream_off(camera_ctrl_t *camera_ctrl);

#endif
usb_camera.c
/*
 * @Descripttion:
 * @version:
 * @Author: Andy
 * @Date: 2024-05-26 00:03:08
 * @LastEditors: Andy
 * @LastEditTime: 2024-06-23 23:26:18
 */
#include "usb_camera.h"
#include "camera_utils.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int camera_capabilities_pri(camera_ctrl_t camera_ctrl)
{
    // Check if file descriptor is valid
    if (camera_ctrl.fd < 0) {
        LOG_ERROR("Invalid file descriptor");
        return -1;
    }

    // judge device ability
    ioctl(camera_ctrl.fd, VIDIOC_QUERYCAP, &camera_ctrl.cap);
    if (!(V4L2_CAP_VIDEO_CAPTURE & camera_ctrl.cap.capabilities)) {
        LOG_ERROR("Error: No capture video device!");
        return -1;
    } else if (!(camera_ctrl.cap.capabilities & V4L2_CAP_STREAMING)) {
        fprintf(stderr, "does not support streaming i/o\n");
        return -1;
    } else {
        fprintf(stdout, "\tthis is a capture video device!\n");
    }
    return 0;
}

int camera_frmsizeenum_pri(camera_ctrl_t camera_ctrl)
{
    int frmsizeenum_index = 0;

    if (camera_ctrl.fd < 0) {
        LOG_ERROR("Invalid file descriptor");
        return -1;
    }

    while (1) {
        memset(&camera_ctrl.fsenum, 0, sizeof(struct v4l2_frmsizeenum));
        camera_ctrl.fsenum.index = frmsizeenum_index;
        camera_ctrl.fsenum.pixel_format = camera_ctrl.fmtdesc.pixelformat;
        camera_ctrl.fsenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(camera_ctrl.fd, VIDIOC_ENUM_FRAMESIZES, &(camera_ctrl.fsenum)) < 0) {
            if (errno == EINVAL) {
                // Indicates that the index is out of range, which means we have enumerated all formats
                printf("camera_frmsizeenum_pri EINVAL exit\n");
                break;
            } else {
                LOG_ERROR("Failed to enumerate frame sizes");
                return -1;
            }
        }

        printf("\tFrame size index: %d\n", frmsizeenum_index);
        if (camera_ctrl.fsenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
            printf("\t\tType: Discrete\n");
            printf("\t\t\tWidth: %u\n \t\t\tHeight: %u\n",
                camera_ctrl.fsenum.discrete.width, camera_ctrl.fsenum.discrete.height);
        } else if (camera_ctrl.fsenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
            printf("\t\tType: Stepwise\n");
            printf("\t\t\tMin Width: %u\n \t\t\tMax Width: %u\n \t\t\tStep Width: %u\n",
                camera_ctrl.fsenum.stepwise.min_width, camera_ctrl.fsenum.stepwise.max_width,
                camera_ctrl.fsenum.stepwise.step_width);
            printf("\t\t\tMin Height: %u\n \t\t\tMax Height: %u\n \t\t\tStep Height: %u\n",
                camera_ctrl.fsenum.stepwise.min_height, camera_ctrl.fsenum.stepwise.max_height,
                camera_ctrl.fsenum.stepwise.step_height);
        } else {
            printf("\t\tUnsupported frame size type\n");
        }

        frmsizeenum_index++;
    }

    return 0;
}

int camera_fmtdesc_pri(camera_ctrl_t camera_ctrl)
{
    int fmt_index = 0;
    if (camera_ctrl.fd < 0) {
        LOG_ERROR("Invalid file descriptor");
        return -1;
    }

    while (1) {
        camera_ctrl.fmtdesc.index = fmt_index;
        camera_ctrl.fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(camera_ctrl.fd, VIDIOC_ENUM_FMT, &camera_ctrl.fmtdesc) < 0) {
            if (errno == EINVAL) {
                // Indicates that the index is out of range, which means we have enumerated all formats
                break;
            } else {
                LOG_ERROR("Invalid file descriptor");
                return -1;
            }
        }
        printf("\tfmtdesc.index:[%d] fmtdesc.description: [%s] fmtdesc.pixelformat: [%u]\n",
            camera_ctrl.fmtdesc.index, camera_ctrl.fmtdesc.description, camera_ctrl.fmtdesc.pixelformat);
        if (0 != camera_frmsizeenum_pri(camera_ctrl)) {
            LOG_ERROR("camera_frmsizeenum_pri error\n");
            return -1;
        }
        fmt_index++;
    }

    return 0;
}

static int camera_device_ability_pri(camera_ctrl_t camera_ctrl)
{
    // judge device ability
    ioctl(camera_ctrl.fd, VIDIOC_QUERYCAP, &camera_ctrl.cap);
    if (!(V4L2_CAP_VIDEO_CAPTURE & camera_ctrl.cap.capabilities)) {
        fprintf(stderr, "Error: No capture video device!\n");
        return -1;
    } else {
        fprintf(stdout, "this is a capture video device!\n");
    }
    return 0;
}

/* 枚举出摄像头所支持的所有视频像素格式\采集分辨率\帧率 */
static void v4l2_print_formats(camera_ctrl_t* camera_ctrl)
{
    camera_ctrl->fmtdesc.index = 0;
    camera_ctrl->fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频采集
    /*查看支持的像素格式*/
    while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FMT, &camera_ctrl->fmtdesc)) {
        printf("fmt: \" %s \"  <0x%d>\n", camera_ctrl->fmtdesc.description, camera_ctrl->fmtdesc.pixelformat);
        camera_ctrl->fmtdesc.index++;

        camera_ctrl->frmsize.index = 0;
        camera_ctrl->frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        camera_ctrl->frmsize.pixel_format = camera_ctrl->fmtdesc.pixelformat;
        /*查看支持的分辨率*/
        while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FRAMESIZES, &camera_ctrl->frmsize)) {
            printf("frm_size  \t <%d*%d>\n", camera_ctrl->frmsize.discrete.width, camera_ctrl->frmsize.discrete.height);
            camera_ctrl->frmsize.index++;

            camera_ctrl->frmival.index = 0;
            camera_ctrl->frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            camera_ctrl->frmival.pixel_format = camera_ctrl->fmtdesc.pixelformat;
            camera_ctrl->frmival.width = camera_ctrl->frmsize.discrete.width;
            camera_ctrl->frmival.height = camera_ctrl->frmsize.discrete.height;
            /*查看支持的帧率*/
            while (!ioctl(camera_ctrl->fd, VIDIOC_ENUM_FRAMEINTERVALS, &camera_ctrl->frmival)) {
                printf("\t <%dfps>\n", (camera_ctrl->frmival.discrete.denominator / camera_ctrl->frmival.discrete.numerator));
                camera_ctrl->frmival.index++;
            }
        }
        printf("\r\n");
    }
}

/* 设置格式 */
static int v4l2_set_format(camera_ctrl_t* camera_ctrl, unsigned int format, int width, int height)
{
    // 检查输入参数是否有效
    if (camera_ctrl == NULL) {
        fprintf(stderr, "Error: camera_ctrl is NULL\n");
        return -1;
    }

    if (width <= 0 || height <= 0) {
        fprintf(stderr, "Error: invalid width or height\n");
        return -1;
    }

    // 清零并初始化格式结构体
    memset(&camera_ctrl->fmt, 0, sizeof(struct v4l2_format));
    camera_ctrl->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频采集
    camera_ctrl->fmt.fmt.pix.width = width; // 设置分辨率的宽
    camera_ctrl->fmt.fmt.pix.height = height; // 设置分辨率的高
    camera_ctrl->fmt.fmt.pix.pixelformat = format; // 设置视频输出格式
    camera_ctrl->fmt.fmt.pix.field = V4L2_FIELD_ANY;

    // 设置视频格式
    if (ioctl(camera_ctrl->fd, VIDIOC_S_FMT, &camera_ctrl->fmt) < 0) {
        perror("Error: v4l2_set_format"); // 打印错误信息
        fprintf(stderr, "Failed to set format: %s\n", strerror(errno));
        return -1; // 返回错误标志
    }

    // 查询当前摄像头的工作模式
    if (ioctl(camera_ctrl->fd, VIDIOC_G_FMT, &camera_ctrl->fmt) < 0) {
        perror("Error: v4l2_get_format"); // 打印错误信息
        fprintf(stderr, "Failed to get format: %s\n", strerror(errno));
        return -1; // 返回错误标志
    }

    // 检查设置是否成功
    if (camera_ctrl->fmt.fmt.pix.pixelformat == format && camera_ctrl->fmt.fmt.pix.width == width && camera_ctrl->fmt.fmt.pix.height == height) {
        printf("Successfully set format: width=%d, height=%d\n", camera_ctrl->fmt.fmt.pix.width, camera_ctrl->fmt.fmt.pix.height);
        return 0; // 返回成功标志
    } else {
        fprintf(stderr, "Failed to set format: requested (width=%d, height=%d), got (width=%d, height=%d)\n",
            width, height, camera_ctrl->fmt.fmt.pix.width, camera_ctrl->fmt.fmt.pix.height);
        return -1; // 返回错误标志
    }
}

/*申请帧缓冲、内存映射*/
static int v4l2_init_buffer(camera_ctrl_t* camera_ctrl)
{
    if (NULL == camera_ctrl) {
        LOG_ERROR("NULL PTR err\n");
        return -1;
    }
    // reqbuf requst
    memset(&camera_ctrl->reqbuf, 0, sizeof(camera_ctrl->reqbuf));
    camera_ctrl->reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    camera_ctrl->reqbuf.count = NUM_BUFF; // 帧缓冲的数量
    camera_ctrl->reqbuf.memory = V4L2_MEMORY_MMAP;
    /*申请帧缓冲*/
    if (ioctl(camera_ctrl->fd, VIDIOC_REQBUFS, &camera_ctrl->reqbuf) < 0) {
        printf("error: v4l2_init_buffer\r\n");
        return 0;
    }
    /*内存映射*/
    for (int i = 0; i < NUM_BUFF; i++) {
        // init buffers
        memset(&camera_ctrl->buf, 0, sizeof(camera_ctrl->buf));
        camera_ctrl->buf.index = i;
        camera_ctrl->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        camera_ctrl->buf.memory = V4L2_MEMORY_MMAP;
        // printf("%d\n",camera_ctrl->fd);
        if (ioctl(camera_ctrl->fd, VIDIOC_QUERYBUF, &camera_ctrl->buf)) {
            LOG_ERROR("ioctl err\n");
            return -1;
        }
        camera_ctrl->frm_base[camera_ctrl->buf.index] = mmap(NULL, camera_ctrl->buf.length,
            PROT_READ | PROT_WRITE, MAP_SHARED,
            camera_ctrl->fd, camera_ctrl->buf.m.offset);

        if (MAP_FAILED == camera_ctrl->frm_base[camera_ctrl->buf.index]) {
            perror("mmap error");
            return -1;
        }
        camera_ctrl->buff_length = camera_ctrl->buf.length;
        // printf("Attempting to unmap buffer %d at address: %p\n", i, camera_ctrl->frm_base[i]);
        printf("查询内存成功  camera_ctrl->buf[%d]==%d bit; address: %p\r\n", camera_ctrl->buf.index, camera_ctrl->buf.length, camera_ctrl->frm_base[i]);
        // 将填充好的buff加入视频设备的输入或输出队列中,从而使视频设备可以处理该缓冲区的数据。
        if (ioctl(camera_ctrl->fd, VIDIOC_QBUF, &camera_ctrl->buf) < 0) {
            printf("error: VIDIOC_QBUF\r\n");
            return 0;
        }
    }
    return 0;
}

static int v4l2_stream_on(camera_ctrl_t* camera_ctrl)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    /* 打开摄像头、摄像头开始采集数据 */
    if (ioctl(camera_ctrl->fd, VIDIOC_STREAMON, &type) < 0) {
        printf("error: v4l2_stream_on\r\n");
        return 0;
    }
    printf("Camera_open : success\r\n");
    return 0;
}

static int Buffer_Save_As_Image(camera_ctrl_t* camera_ctrl, const char* filename, struct v4l2_buffer buf)
{
    int fd_file = open(filename, O_WRONLY | O_CREAT, 0666);
    if (fd_file < 0) {
        fprintf(stderr, "can not create file: %s, error: %s\n", filename, strerror(errno));
        return -1;
    }

    printf("capture to %s\n", filename);

    ssize_t bytes_written = write(fd_file, camera_ctrl->frm_base[buf.index], buf.bytesused);
    if (bytes_written < 0) {
        fprintf(stderr, "failed to write to file: %s, error: %s\n", filename, strerror(errno));
        close(fd_file);
        return -1;
    } else if ((size_t)bytes_written != buf.bytesused) {
        fprintf(stderr, "incomplete write to file: %s, expected: %u, written: %zd\n", filename, buf.bytesused, bytes_written);
        close(fd_file);
        return -1;
    }

    if (close(fd_file) < 0) {
        fprintf(stderr, "failed to close file: %s, error: %s\n", filename, strerror(errno));
        return -1;
    }

    return 0;
}

// 开始数据采集
static int v4l2_read_data(camera_ctrl_t* camera_ctrl)
{
    int file_cnt = 0;
    camera_ctrl->count_jpg = 0;
    char filename[32] = { 0 };
    // read data的操作里面可以将图片另存为其他数据
    // 也可以将将数据打印到显示屏幕上进行实时显示
    /* 把buffer取出队列 */
    struct v4l2_buffer temp_buf;
    memset(&temp_buf, 0, sizeof(struct v4l2_buffer));
    temp_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    temp_buf.memory = V4L2_MEMORY_MMAP;
    while (1) {
        for (int i = 0; i < NUM_BUFF; i++) {
            if (ioctl(camera_ctrl->fd, VIDIOC_DQBUF, &temp_buf) != 0) // 出队
            {
                printf("提取数据失败\r\n");
                return -1;
            }
            // 数据如何处理
            //  网络方式发出去
            //  Send_Video_Data(connfd,frm_base[camera_ctrl->buf.index],temp_buf.length);

            // 图片方式另存为文件
            /* 把buffer的数据存为文件 */

            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
            if (Buffer_Save_As_Image(camera_ctrl, filename, temp_buf)) {
                LOG_ERROR("Buffer_Save_As_Image ERR \n");
                return -1;
            }
            // 实时打印到屏幕上

            if (ioctl(camera_ctrl->fd, VIDIOC_QBUF, &temp_buf) != 0) // 入队
            {
                printf("放回队列失败\r\n");
                exit(1);
            }

            camera_ctrl->count_jpg++;
            if (camera_ctrl->count_jpg == 10)
                break;
            // usleep(33000);
        }
        if (camera_ctrl->count_jpg == 10)
            break;
    }
    fprintf(stdout, "read data sucess\n");
    return 0;
}

static int v4l2_stream_off(camera_ctrl_t* camera_ctrl)
{
    // 判空
    if (NULL == camera_ctrl) {
        LOG_ERROR("v4l2_stream_off camera_ctrl ptr is NULL\n");
        return -1;
    }
    camera_ctrl->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(camera_ctrl->fd, VIDIOC_STREAMOFF, &camera_ctrl->type)) {
        LOG_ERROR("ioctl VIDIOC_STREAMOFF err\n");
        return -1;
    }

    // 取消内存映射
    for (int i = 0; i < NUM_BUFF; i++) {
        printf("Attempting to unmap buffer %d at address: %p buff length:%d\n", i, camera_ctrl->frm_base[i], camera_ctrl->buff_length);
        if (munmap(camera_ctrl->frm_base[i], camera_ctrl->buff_length) == -1) {
            printf("munmap buffer %d failed: %s\n", i, strerror(errno));
            exit(1);
        }
    }
    //  关闭文件
    if (close(camera_ctrl->fd) == -1) {
        LOG_ERROR("close camera file descriptor failed\n");
    }
    camera_ctrl->fd = -1; // 将文件描述符设置为无效值
    fprintf(stdout, "stream off sucess\n");
    return 0;
}
main.h
/*
 * @Descripttion: 
 * @version: 
 * @Author: Andy
 * @Date: 2024-06-03 23:22:27
 * @LastEditors: Andy
 * @LastEditTime: 2024-06-09 00:37:01
 */
#ifndef __MAIN_H__
#define __MAIN_H__

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include "../camera_utils/camera_utils.h"
#include "../camera_utils/usb_camera.c"
#define DEV_PATH "/dev/video1"


#endif
main.c
#include "main.h"
/*
 * @Descripttion:
 * @version:
 * @Author: psd
 * @Date: 2024-06-03 23:21:25
 * @LastEditors: Andy
 * @LastEditTime: 2024-06-23 18:47:15
 */

static camera_ctrl_t camera_ctrl;

int main()
{

    // 打开文件
    int fd = 0;
    fd = open(DEV_PATH, O_RDWR);
    if (0 > fd) {
        LOG_ERROR("open file err");
        return -1;
        // exit(1);
    }
    camera_ctrl.fd = fd;

    // 参数打印
    v4l2_print_formats(&camera_ctrl);

    /* 设置格式 */
    if (v4l2_set_format(&camera_ctrl, V4L2_PIX_FMT_MJPEG, pic_width, pic_height))
        exit(1);
    // buff init
    if (v4l2_init_buffer(&camera_ctrl))
        exit(1);
    // ioctl 下指令
    if (v4l2_stream_on(&camera_ctrl))
        perror("v4l2_stream_on error\n");

    // read data
    if (v4l2_read_data(&camera_ctrl))
        perror("v4l2_read_data error\n");

    // v4l2_stream_off
    if (v4l2_stream_off(&camera_ctrl))
        perror("v4l2_stream_off error\n");
    return 0;
}

3.3调试现象

操作命令:

PC端:
	make clean
	make
	cp bin/USB_CAMERA ~/nfs_rootfs
开发板端:
	韦东山家的板子挂载时常出问题,需要手动挂载:mount -t nfs -o nolock,vers=3 192.168.5.11:/home/psd/nfs_rootfs /mnt
	./USB_CAMERA

现象:
在这里插入图片描述

在这里插入图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里插入图片描述

注:晚上加上摄像头摆放位置问题,所以是黑的,正常现象

4.平台信息

开发板:韦东山imx6ull

摄像头:韦东山usb免驱摄像头

涉及软件:

乌班图
vscode
MobaXterm

linux版本:Linux-4.9.88

5.参考记录

参考资料感受:

正点原子家的资料还是更全更细更系统

博客

正点原子:

【正点原子Linux连载】第二十章 V4L2摄像头应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1_v4l2驱动框架 正点原子-CSDN博客

视频

韦东山:

03_V4L2应用程序开发_列出帧细节_哔哩哔哩_bilibili

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值