【LINUX内核学习】(一) 一行一行分析,用V4L2架构实现摄像头采集数据

【LINUX内核学习】(一) 一行一行分析,用V4L2架构实现摄像头采集数据

linux 内核中,集成了V4L2这个框架。这个框架是为linux实现的一套视频图像音频框架。可以理解为视频源捕捉的驱动框架。

v4l2的主要子模块:

  • v4l2_device:这个结构体是输入设备的总结构体,是v4l2的核心入口。v4l2_device下面有v4l2_subdev。v4l2_device就相当于是管理驱动的核心结构体。
  • media_device:用于运行时候的数据流的管理,其功能主要是对于每一个子设备建立一条虚拟管道,并同时可以动态改变、管理设备的接入与断开。
  • v4l2_ctrl_handler:控制模块,提供许多色彩空间的操作接口,比如改变亮度、对比度、饱和度、锐度等等。
  • vb2_queue:提供内核与用户空间的buffer流转接口。当摄像头采集了很多数据,它将按照一些规则,将数据分成几段存放在内核中,等待用户调用。而实现这一系列的工作需要这个模块来完成。

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

(一)前期工作 获取摄像头属性

下面两行先获取 设备名,# /dev/video0 多个设备就是 video1.2.3…
以阻塞方式打开摄像头设备。节省CPU资源。
如果使用非阻塞方式打开,即便摄像头没有采集到数据,驱动架构V4L2仍然会把缓存 DQBUFF的所有数据返给程序。不建议这么做

char *device_name=argv[1];
int uvc_fd = open(device_name,O_RDWR);
//int uvc_fd = open(device_name,O_RDWR | O_NONBLOCK); 非阻塞

接着我们看看我们的摄像头是否正常打开了。

if(ucv_fd<0) {printf("[-] failed in open camera! plz check the path of your device\n");return -1;}
printf("[+] camera open successfully!\n");

打开后,我们看看当前我们的设备属性。
这里要用到v4l2中的结构体 v4l2_format 。 在调用这个结构体前,应该先自定义好这个结构体的各个域,比如type,fmt.pix.width宽高等等。
memset(void *__s,int __c,size_t __n)
该函数将一段内存空间全部设置为某个字符。可以当作一个初始化函数

struct v4l2_format format;
struct v4l2_fmtdesc fmt;
memset( &fmt, 0, sizeof(fmt) );

接着我们设定一下fmt中的参数:
注意:这里我们的操作是对 fmt的,因为fmtdesc 是 format-description的意思,就是格式的描述,只是我们在设定格式前先获取看看原本它的描述。这里不是我们设定某些属性的地方。 只是看看这个设备的属性。

fmt.index=0;
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("[+] format supported are : \n");
while( ioctl(uvc_fd,VIDIOC_ENUM_FMT,&fmt) == 0 ) {
	fmt.index++;
	printf("<'%c%c%c%c'--'%s'>\n",fmt.pixelformat & 0ff,.....,(fmt.pixelformat >> 24) & fmt.description );
}

这一段代码在设置了 fmt中参数后, 调用ioctl函数对设备进行操作。
ioctl 执行成功时,会返回 0 . 同时 struct v4l2_fmtdesc 结构体中的 .pixelformat 和 .description 成员会返回当前 index 下标的设备所支持的视频格式。

运行结果:
在这里插入图片描述

(二) 设置摄像头采集后的的输出格式
在这个步骤,我们才开始对 结构体 format操作,而不是 fmtdesc。
我们设置相关帧数,图像大小之类的参数。
第一步同上,先用memset 对内存初始化
然后设置是可以捕获视频的格式。例如:
V4L2_BUF_TYPE_VIDEO_CAPTURE 这个操作数是表示 支持视频捕获

memset(&format,0,sizeof(struct v4l2_format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
//以下都是预设定
format.fmt.pix.height=1080;
format.fmt.pix.width=1920;
format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
format.fmt.pix.field=V4L2_FIELD_ANY;
//=======
//开始设定
if( ioctl(uvc_fd,VIDIOC_S_FMT,&format) ) {printf("[-] failed in setting the format of camera!");return -2;}
int img_w=format.fmt.pix.width;
int img_h=format.fmt.pix.height;
printf("[+] final successful setting is : width=%d,height=%d\n",format.fmt.pix.width,format.fmt.pix.height);
if(format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) printf("[+] now we use format [YUV] to output img\n");
else {printf("[-] we can't use format [YUV] to output img\n");return -3;}

图像的大小和输出格式我们就设置好了。现在我们来设置以下帧数,这个最佳帧数应该参考你的摄像头说明书上的产品参数。

struct v4l2_streamparm streamparm;
streamparm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
streamparm.parm.capture.timeperframe.numerator=1; //每1秒
streamparm.parm.capture.timeperframe.denominator=30; //30帧
if( ioctl(uvc_fd,VIDIOC_S_PARM,&streamparm) ){ printf("[-] failed in setting the fps in camera!");return -3;}
printf("[+] camera is getting img-data in %d fps\n",streamparm.parm.capture.timeperframe.denominator);

这样子 对于前期我们参数的获取以及设定就已经完成啦!
现在我们就要申请缓冲区了,不然,数据按照刚刚我们设定好的参数采集进来 不知道该放在哪里。

(三) 向内核申请缓冲区

第一步同上,先初始化。
在设定预置的参数,执行 ioctl 函数操作设备即可.
注意:这一步申请的缓冲区最大不要超过5个噢。

struct v4l2_requestbuffers req_buff;
memset(&req_buff,0,sizeof(struct v4l2_requestbuffers));
req_buf.count=4;
req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buff.memory=V4L2_MEMORY_MMAP:
if( ioctl(uvc_fd,VIDIOC_REQUFS,&req_buff) ){printf("[-] request for 4 buffer bar to gather img_data failed!\n");return -4;}
printf("[+] request for %d buffer bar to gather img_data successfully \n",req_buff.count);

申请成功以后,我们就可以用我们申请好的缓存区了,现在我们把内核的地址用mmap映射到进程空间。

(四)配置申请好的缓冲区

unsigned char *img_buf[4];
struct v4l2_buffer buff_info;
memset(&buff_info,0,sizeof(struct v4l2_buffer));
int i;
//对每一块缓存区都遍历一次
for(i=0;i<req_buff.count;i++){
	buff_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buff_info=V4L2_MEMORY_MMAP;
	if( ioctl(uvc_fd,VIDIOC_QUERYBUF,&buff_info)){printf("[-] failed in finding info of buffers-%d\n",i); return -5;}
	img_buf[i]=mmap(NULL,buff_info.length,PROT_READ | PROT_WRITE,MAP_SHARED,uvc_fd,buff_info.m.offset);
	if(img_buf[i]==NULL){printf("[-] mmap with buffers failed!\n");return -6;}
}

好了,现在我们的缓冲区配置好了。现在我们把采集的数据放到缓冲区。

(五)将缓冲区放入采集队列

memset(&buf_info,0,sizeof(struct v4l2_buffer));
for(i=0;i<req_buff.count;i++){
	buff_info.type=buff_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buff_info.index=i;
	buff_info.memory=V4L2_MEMORY_MMAP;
	if( ioctl(uvc_fd,VIDIOC_QBUF,&buff_info) ){printf("[-] failed in putting buffers data in queue\n ");return -7;}
	}

好了现在所有的配置都完成了,现在我们开始采集数据把!

(六) 采集数据

int vidok=V4L2_BUF_TYPE_VIDEO_CAPTURE;
if( ioctl(uvc_fd,VIDIOC_STREAMON,&vidok)){printf("[-] start camera to gather data Error!\n");return -8;}

printf("================\n  Camera init ok!\n");

好了,这样子我们就开始按照我们指定的格式开始利用摄像头采集数据了。
但是我们还没处理结果呢。保存在我们设定好的缓冲区的数据我们还得把他取出来保存为图片或者输出给其他进程。

我们现在开始处理我们采集到缓冲区的数据吧。

(七)保存数据结果
我们以保存jpg为例子处理我们的数据结果,申请存放JPG的数据空间。


char jpg_name[40];
int jpg_size;
FILE *jpg_file;
struct pollfd fds;
fds.fd=uvc_fdl
fds.events=POLLIN;
//申请存放JPG的数据空间
unsigned char *jpg=(unsigned char *)malloc(img_h*img_w*3);

struct v4l2_buffer out_buffer;
int pic_num=0;
while(pic_num!=3){
	poll(&fds,1,-1);
	//取出采集完毕的缓冲区
	out_buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
	out_buffer.memory=V4L2_MEMORY_MMAP;
	ioctl(uvc_fd,VIDIOC_DQBUF,&out_buffer);
	printf("out_buffer[%d]=%X\n",out_buffer.index,img_buffer[out_buffer.index]);
	//因为我们保存的是YUV格式,要转一下成jpeg
	jpg_size=yuv2jpg(img_w,img_h,img_w*img_h*3,img_buffer[out_buffer.index],jpg_p,80);
	sprintf(jpg_name,"%d.jpg",i);
	printf("pic_name:%s,size=%d k \n",jpg_name,jpg_size/1024);
	jpg_file=fopen(jpg_name,"wb");
	fwrite(jpg,1,jpg_size,jpg_file);
	fclose(jpg_file);
	ioctl( uvc_fd,VIDIOC_QBUF,&out_buffer);
}
printf("========done============");
return 0;

这样一来,我们采的数据就以jpg格式保存下来了。
结果:
在这里插入图片描述

在这里插入图片描述

注意:第七部分的yuv2jpg()函数我没有贴出,因为蛮复杂的,这个转换。大家可以用现成的。我怕我写不清楚,之后分析好了再写一篇YUV2JPEG的文吧。 若找不到合适的转换函数的朋友可以私信我。

以上就是一步一步用C底层代码及LINUX内核V4L2架构实现摄像头采集数据保存为jpg的全部内容。有问题私信我。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值