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博客
视频
韦东山: