如何做一款服装网站,免费简历模板下载word可编辑,国家企业信用信息查询平台官网,百度网盟推广案例前面3篇我们讲解了camera的基础概念#xff0c;MIPI协议#xff0c;CSI2#xff0c;常用命令等#xff0c;本文带领大家入门#xff0c;如何用c语言编写应用程序来操作摄像头。
Linux下摄像头驱动都是基于v4l2架构#xff0c;要基于该架构编写摄像头的应用程序#xff…前面3篇我们讲解了camera的基础概念MIPI协议CSI2常用命令等本文带领大家入门如何用c语言编写应用程序来操作摄像头。
Linux下摄像头驱动都是基于v4l2架构要基于该架构编写摄像头的应用程序必须先要搞清楚什么是v4l2。
1. 什么是v4l2
v4l2是video for Linux 2的缩写是一套Linux内核视频设备的驱动框架该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)
https://linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/官网有一个简单的用于抓图的程序capture.c。
本文后面基于该实例编写一个最简单的抓图程序。
v4l2接口
V4L2 video for linux 2 是 linux ⾥⼀套标准的视频驱动让应⽤层可以像访问普通⽂件⼀样对**/dev/videoX** 节点进⾏ open 、 read 、 ioctl 等操作。
V4L2在设计时是要支持很多广泛的设备的它们之中只有一部分在本质上是真正的视频设备可以支持多种设备,它可以有以下几种接口
1. video capture interface(捕获) 视频采集接口这种接口应用于摄像头v4l2在最初设计的时候就是应用于这种功能
2. video output interface(输出) 视频输出接口将静止图像或图像序列编码为模拟视频信号通过此接口应用程序可以控制编码过程并将图像从用户空间移动到驱动程序
3. video overlay interface(预览) 视频直接传输接口可以将采集到的视频数据直接传输到显示设备不需要cpu参与这种方式的显示图像的效率比其他方式高得多
本文主要讲解如何使用capture功能。
2. 截取图象的3种方法
1用mmap内存映射方式截取视频
mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后进程可以向访问普通内存一样对文件进行访问不必再调用read()write等操作。
两个不同进程A、B共享内存的意思是同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新反之亦然
采用共享内存通信的一个显而易见的好处是效率高因为进程可以直接读写内存而不需要任何数据的拷贝
*1设置picture的属性 *2 初始化video_mbuf以得到所映射的buffer的信息
ioctl(vd-fd, VIDIOCGMBUF, (vd-mbuf))*3可以修改video_mmap和帧状态的当前设置 Eg. vd-mmap.format VIDEO_PALETTE_RGB24vd-framestat[0] vd-framestat[1] 0; vd-frame 0;*4将mmap与video_mbuf绑定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len //映射到调用进程地址空间的字节数它从被映射文件开头offset个字节开始算起
Prot //指定共享内存的访问权限 PROT_READ可读, PROT_WRITE 可写, PROT_EXEC 可执行
flags // MAP_SHARED MAP_PRIVATE中必选一个 // MAP_ FIXED不推荐使用addr //共内存享的起始地址一般设0表示由系统分配
Mmap( ) 返回值是系统实际分配的起始地址
if((vd-map (unsigned char*)mmap(0, vd-mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd-fd, 0)) 0)
{perror(v4l_mmap mmap:);return -1;
}*5Mmap方式下真正做视频截取的 VIDIOCMCAPTURE
ioctl(vd-fd, VIDIOCMCAPTURE, (vd-mmap)) ;
若调用成功开始一帧的截取是非阻塞的
是否截取完毕留给VIDIOCSYNC来判断*6调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd-fd, VIDIOCSYNC, frame) 0)
{perror(v4l_sync:VIDIOCSYNC);return -1;
}若成功表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
frame是当前截取的帧的序号。关于双缓冲
video_bmuf bmuf.frames 2;
一帧被处理时可以采集另一帧
int frame; //当前采集的是哪一帧
int framestat[2]; //帧的状态 没开始采集|等待采集结束
帧的地址由vd-map vd-mbuf.offsets[vd-frame]得到
采集工作结束后调用munmap取消绑定
munmap(vd-map, vd-mbuf.size)2视频截取的第二种方法直接读设备
关于缓冲大小图象等的属性须由使用者事先设置
int read (要访问的文件描述符指向要读写的信息的指针应该读写的字符数);返回值为实际读写的字符数实例
int len ;
unsigned char *vd-map (unsigned char *) malloc(vd-capability.maxwidth*vd-capability.maxheight );
len read(vd-fd,vd-map, vd-capability.maxwidth*vd-capability.maxheight*3 );3用户指针
3. v4l2 设备操作说明
对设备的大多数操作都是应用层通过调用ioctl实现的 不同的命令需要操作不同的文件设备节点 具体的需要根据拓扑结构来决定操作那个字符设备。
以下是瑞芯微rk3568平台的摄像头拓扑图移植了ov13850摄像头。 其中摄像头对应的此设备为 /dev/v4l-subdev3应用层要配置通用配置、或者获取图像需要操作设备 /dev/video0有一些摄像头专用的命令我们可以操作 /dev/v4l-subdev3
ov13850摄像头驱动中注册了一些命令对应的回调函数 这些回调函数都注册到了V4L2架构中我们可以通过字符设备 /dev/videox、/dev/v4l-subdevx 直接或者间接访问到这些回调函数。
V4L2定义了一些通用的命令操作字符设备 /dev/videox即可调用命令具体定义如下
kernel\drivers\media\v4l2-core\v4l2-ioctl.c可以通过数组名命令对应的数值方式访问对应的回调函数。 该数组定义如下
struct v4l2_ioctl_info {unsigned int ioctl;u32 flags;const char * const name;int (*func)(const struct v4l2_ioctl_ops *ops, struct file *file,void *fh, void *p);void (*debug)(const void *arg, bool write_only);
};字符设备**/dev/v4l-subdevx**支持的命令如下
kernel\drivers\media\v4l2-core\v4l2-subdev.cstatic long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{……switch (cmd) {case VIDIOC_QUERYCTRL:……case VIDIOC_QUERY_EXT_CTRL:……case VIDIOC_QUERYMENU:……case VIDIOC_G_CTRL:……case VIDIOC_S_CTRL:……case VIDIOC_G_EXT_CTRLS:……case VIDIOC_S_EXT_CTRLS:……case VIDIOC_TRY_EXT_CTRLS:……case VIDIOC_DQEVENT:……case VIDIOC_SUBSCRIBE_EVENT:return v4l2_subdev_call(sd, core, subscribe_event, vfh, arg);case VIDIOC_UNSUBSCRIBE_EVENT:return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg);
#ifdef CONFIG_VIDEO_ADV_DEBUGcase VIDIOC_DBG_G_REGISTER:……case VIDIOC_DBG_S_REGISTER:……case VIDIOC_DBG_G_CHIP_INFO:……
#endifcase VIDIOC_LOG_STATUS: {……
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)case VIDIOC_SUBDEV_G_FMT: {……case VIDIOC_SUBDEV_S_FMT: {……case VIDIOC_SUBDEV_G_CROP: {……}case VIDIOC_SUBDEV_S_CROP: {……}case VIDIOC_SUBDEV_ENUM_MBUS_CODE: {……}case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: {……}case VIDIOC_SUBDEV_G_FRAME_INTERVAL: {……}case VIDIOC_SUBDEV_S_FRAME_INTERVAL: {……}case VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: {……}case VIDIOC_SUBDEV_G_SELECTION: {……}case VIDIOC_SUBDEV_S_SELECTION: {……}case VIDIOC_G_EDID: {……}case VIDIOC_S_EDID: {……}case VIDIOC_SUBDEV_DV_TIMINGS_CAP: {……}case VIDIOC_SUBDEV_ENUM_DV_TIMINGS: {……}case VIDIOC_SUBDEV_QUERY_DV_TIMINGS:return v4l2_subdev_call(sd, video, query_dv_timings, arg);case VIDIOC_SUBDEV_G_DV_TIMINGS:return v4l2_subdev_call(sd, video, g_dv_timings, arg);case VIDIOC_SUBDEV_S_DV_TIMINGS:return v4l2_subdev_call(sd, video, s_dv_timings, arg);case VIDIOC_G_INPUT:return v4l2_subdev_call(sd, video, g_input_status, arg);case VIDIOC_SUBDEV_G_STD:return v4l2_subdev_call(sd, video, g_std, arg);case VIDIOC_SUBDEV_S_STD: {v4l2_std_id *std arg;return v4l2_subdev_call(sd, video, s_std, *std);}case VIDIOC_SUBDEV_ENUMSTD: {……}case VIDIOC_SUBDEV_QUERYSTD:……}return 0;
}这其中有一些命令是和字符设备 /dev/videox 的命令重复的
比如VIDIOC_S_CTRL
VIDIOC_SUBDEV_ 开头的则是subdev私有的。
关于这些命令和回调函数后续会再深入讲解对于应用程序开发
我们首先搞清楚设备的拓扑结构然后需要知道我们要执行的命令功能以及对应的是哪一个设备节点即可。
4. ioctl命令说明
参见结构体见 /usr/include/linux/videodev2.h1Querying Capabilities
查询设备的功能
由于V4L2涵盖了各种各样的设备因此并非API的所有方面都适用于所有类型的设备在使用v4l2设备时必须调用此API获得设备支持的功能capture、output、overlay…
ID描述VIDIOC_QUERYCAP查询设备功能
struct v4l2_capability
{u8 driver[16]; // 驱动名字u8 card[32]; // 设备名字u8 bus_info[32]; // 设备在系统中的位置u32 version; // 驱动版本号u32 capabilities; // 设备支持的操作u32 reserved[4]; // 保留字段
};capabilities 常用值:
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取2Application Priority
应用优先级
当多个应用程序共享设备时可能需要为它们分配不同的优先级。视频录制应用程序可以例如阻止其他应用程序改变视频控制或切换当前的电视频道。
另一个目标是允许在后台工作的低优先级应用程序这些应用程序可以被用户控制的应用程序抢占并在以后自动重新获得对设备的控制
ID描述VIDIOC_G_PRIORITY获取优先级VIDIOC_S_PRIORITY设置优先级
3Device Inputs and Outputs
输入和输出设备
ID描述VIDIOC_ENUMINPUT枚举视频输入设备VIDIOC_G_INPUT获取当前的视频输入设备VIDIOC_S_INPUT设置视频输入设备VIDIOC_ENUMOUTPUT枚举视频输出设备VIDIOC_G_OUTPUT获取当前视频输出设备VIDIOC_S_OUTPUT设置视频输出设备VIDIOC_ENUMAUDIO枚举音频输入设备VIDIOC_G_AUDIO获取当前音频输入设备VIDIOC_S_AUDIO设置音频输入设备VIDIOC_ENUMAUDOUT枚举音频输出设备VIDIOC_G_OUTPUT获取音频输出设备VIDIOC_S_AUDOUT设置音频输出设备
VIDIOC_G_INPUT 和 VIDIOC_S_INPUT 用来查询和选则当前的 input一个 video 设备 节点可能对应多个视频源比如 saf7113 可以最多支持四路 cvbs 输入如果上层想在四 个cvbs视频输入间切换那么就要调用 ioctl(fd, VIDIOC_S_INPUT, input) 来切换。
VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回当前的 video input和output的index.
struct v4l2_input {__u32 index; /* Which input */__u8 name[32]; /* Label */__u32 type; /* Type of input */__u32 audioset; /* Associated audios (bitfield) */__u32 tuner; /* Associated tuner */v4l2_std_id std;__u32 status;__u32 reserved[4];
};我们可以通过VIDIOC_ENUMINPUT and VIDIOC_ENUMOUTPUT 分别列举一个input或者 output的信息我们使用一个v4l2_input结构体来存放查询结果这个结构体中有一个 index域用来指定你索要查询的是第几个input/ouput,如果你所查询的这个input是当前正 在使用的那么在v4l2_input还会包含一些当前的状态信息如果所 查询的input/output 不存在那么回返回EINVAL错误所以我们通过循环查找直到返回错误来遍历所有的 input/output. VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回当前的video input和output 的index.
4 Video Standards
视频标准
ID描述VIDIOC_ENUMSTD枚举设备支持的所有标准VIDIOC_G_STD获取当前正在使用的标准VIDIOC_S_STD设置视频标准VIDIOC_QUERYSTD有的设备支持自动侦测输入源的视频标准此ioctl获取检测到的标准
typedef u64 v4l2_std_id;struct v4l2_standard {u32 index;v4l2_std_id id;u8 name[24];struct v4l2_fract frameperiod; /* Frames, not fields */u32 framelines;u32 reserved[4];
};当然世界上现在有多个视频标准如NTSC和PAL他们又细分为好多种那么我们的设 备输入/输出究竟支持什么样的标准呢我们的当前在使用的输入和输出正在使用的是哪 个标准呢我们怎么设置我们的某个输入输出使用的标准呢这都是有方法的。
查询我们的输入支持什么标准首先就得找到当前的这个输入的index然后查出它的 属性在其属性里面可以得到该输入所支持的标准将它所支持的各个标准与所有的标准 的信息进行比较就可以获知所支持的各个标准的属性。一个输入所支持的标准应该是一 个集合而这个集合是用bit与的方式用一个64位数字表示。因此我们所查到的是一个数字。
5 Camera Control Reference
控制属性
ID描述VIDIOC_QUERYCTRL查询指定的control详细信息VIDIOC_QUERYMENU查询menuVIDIOC_G_CTRL获取设备指定control的当前信息VIDIOC_S_CTRL设置设备指定的control
6 Image Format
图像格式
图像由多种格式YUV和RGB还有压缩格式等等其中每种格式又分有多种格式比如RGBRGB565、RGB888…
所以在使用设备时需要对格式进行设置
ID描述VIDIOC_ENUM_FMT枚举设备支持的图像格式VIDIOC_G_FMT获取当前设备的图像格式VIDIOC_S_FMT设置图像格式VIDIOC_TRY_FMT测试设备是否支持此格式
查询并显示所有支持的格式VIDIOC_ENUM_FMT
struct v4l2_fmtdesc
{u32 index; // 要查询的格式序号应用程序设置enum v4l2_buf_type type; // 帧类型应用程序设置u32 flags; // 是否为压缩格式u8 description[32]; // 格式名称u32 pixelformat; // 格式u32 reserved[4]; // 保留
};查看或设置当前格式 VIDIOC_G_FMT, VIDIOC_S_FMT
struct v4l2_format
{enum v4l2_buf_type type; // 帧类型应用程序设置union fmt{struct v4l2_pix_format pix; // 视频设备使用 struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; u8 raw_data[200]; };
};struct v4l2_pix_format
{u32 width; // 帧宽单位像素 u32 height; // 帧高单位像素 u32 pixelformat; // 帧格式 enum v4l2_field field; u32 bytesperline; u32 sizeimage; enum v4l2_colorspace colorspace; u32 priv;
};7 Cropping, composing and scaling
图像裁剪、插入与缩放
ID描述VIDIOC_CROPCAP获取图像裁剪缩放能力VIDIOC_G_CROP获取当前的裁剪矩阵VIDIOC_S_CROP设置裁剪矩阵
Cropping 和 scaling 主要指的是图像的取景范围及图片的比例缩放的支持。Crop 就 是把得到的数据作一定的裁剪和伸缩裁剪可以只取样我们可以得到的图像大小的一部分 剪裁的主要参数是位置、长度、宽度。而 scale 的设置是通过 VIDIOC_G_FMT 和 VIDIOC_S_FMT 来获得和设置当前的 image 的长度宽度来实现的。 看下图
我们可以假设 bounds 是 sensor 最大能捕捉到的图像范围而 defrect 是设备默认 的最大取样范围这个可以通过 VIDIOC_CROPCAP 的 ioctl 来获得设备的 crap 相关的属 性 v4l2_cropcap其中的 bounds 就是这个 bounds其实就是上限。每个设备都有个默 认的取样范围就是 defrect就是 default rect 的意思它比 bounds 要小一些。这 个范围也是通过 VIDIOC_CROPCAP 的 ioctl 来获得的 v4l2_cropcap 结构中的 defrect 来表示的我们可以通过 VIDIOC_G_CROP 和 VIDIOC_S_CROP 来获取和设置设备当前的 crop 设置。
设置设备捕捉能力的参数
struct v4l2_cropcap
{enum v4l2_buf_type type; // 数据流的类型应用程序设置struct v4l2_rect bounds; // 这是 camera 的镜头能捕捉到的窗口大小的局限struct v4l2_rect defrect; // 定义默认窗口大小包括起点位置及长,宽的大小大小以像素为单位struct v4l2_fract pixelaspect; // 定义了图片的宽高比
};设置窗口取景参数 VIDIOC_G_CROP 和 VIDIOC_S_CROP
struct v4l2_crop
{enum v4l2_buf_type type;// 应用程序设置struct v4l2_rect c;
}8 buf Input/Output
数据的输入和输出
内核中使用缓存队列对图像数据进行管理用户空间获取图像数据有两种方式一种是通过read、write方式读取内核空间的缓存一种是将内核空间的缓存映射到用户空间即streaming。在操作v4l2设备时通过VIDIOC_QUERYCAP获取设备支持哪种方式。 streaming就是在内核空间中维护一个缓存队列然后将内存映射到用户空间应用读取图像数据就是一个不断地出队列和入队列的过程如下图所示 ID描述VIDIOC_REQBUFS申请缓存VIDIOC_QUERYBUF获取缓存信息VIDIOC_QBUF将缓存放入队列中VIDIOC_DQBUF将缓存从队列中取出
1. 向设备申请缓冲区 VIDIOC_REQBUFS
struct v4l2_requestbuffers
{u32 count; // 缓冲区内缓冲帧的数目enum v4l2_buf_type type; // 缓冲帧数据格式enum v4l2_memory memory; // 区别是内存映射还是用户指针方式u32 reserved[2];
};
enum v4l2_memoy
{V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR
};获取缓冲帧的地址长度VIDIOC_QUERYBUF
struct v4l2_buffer
{u32 index; //buffer 序号enum v4l2_buf_type type; //buffer 类型u32 byteused; //buffer 中已使用的字节数u32 flags; // 区分是MMAP 还是USERPTR enum v4l2_field field; struct timeval timestamp; // 获取第一个字节时的系统时间 struct v4l2_timecode timecode; u32 sequence; // 队列中的序号 enum v4l2_memory memory; //IO 方式被应用程序设置 union m { u32 offset; // 缓冲帧地址只对MMAP 有效 unsigned long userptr; }; u32 length; // 缓冲帧长度 u32 input; u32 reserved;
};2. 内存映射MMAP 及定义一个结构体来映射每个缓冲帧。 相关结构体
struct buffer
{void* start;unsigned int length;
}*buffers;相关函数
#include sys/mman.hvoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
参数
addr 映射起始地址一般为NULL 让内核自动选择
length 被映射内存块的长度
prot 标志映射后能否被读写其值为PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
flags 确定此内存映射能否被其他进程共享MAP_SHARED,MAP_PRIVATE
fd,offset, 确定被映射的内存地址 返回成功映射后的地址不成功返回MAP_FAILED ((void*)-1)3.将所有的缓存放入队列
struct v4l2_buffer v4l2_buffer;for(i 0; i nr_bufs; i)
{memset(v4l2_buffer, 0, sizeof(struct v4l2_buffer));v4l2_buffer.index i; //想要放入队列的缓存v4l2_buffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buffer.memory V4L2_MEMORY_MMAP; ret ioctl(fd, VIDIOC_QBUF, v4l2_buffer);if(ret 0){printf(Unable to queue buffer.\n);return -1;}
}9启动 或 停止数据流
VIDIOC_STREAMON VIDIOC_STREAMOFF
type V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd, VIDIOC_STREAMON, type);5. v4l2设备抓帧程序编写
v4l2设备的命令比较多其实常用的并不是很多下面通过一个实例来详细讲解如何操作v4l2设备。
1设备配置
本例将常用的摄像头配置参数等装成结构体
struct v4l2_dev
{int fd; //videoO对应的设备描述符int sub_fd; const char *path; //字符设备 /dev/videoOconst char *name; //摄像头名称const char *subdev_path;//字符设备 /dev/v4l-subdev3const char *out_type; //输出图像格式enum v4l2_buf_type buf_type;//缓存类型int format; //像素格式int width; //图像宽度int height; //图像高度unsigned int req_count; //缓存数量enum v4l2_memory memory_type; //读取图像的方法DMA还是MMAPstruct buffer *buffers; //缓冲区unsigned long int timestamp;//时长度int data_len;//图像数据长度unsigned char *out_data;//图像数据
};本例填写的摄像头ov13850的配置信息如下
struct v4l2_dev ov13850 {.fd -1,.sub_fd -1,.path /dev/video0,.name ov13850,.subdev_path /dev/v4l-subdev3,.out_type nv12,.buf_type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,.format V4L2_PIX_FMT_NV12,.width 800,.height 600,.req_count 4,.memory_type V4L2_MEMORY_MMAP,.buffers NULL,.timestamp 0,.data_len 0,.out_data NULL,
};2v4l2设备一般操作流程抓帧
v4l2设备一般操作流程如下图所示 各功能对应的ioctrl命令如下 测试程序一口君已经上传到gitee
https://gitee.com/yikoulinux/v4l2-appgit clone gitgitee.com:yikoulinux/v4l2-app.git欢迎各位老铁star。
3程序执行log
以下是在瑞芯微rk3568实际测试的log。
rk3568_r:/ # /data/capture
/data/capture
Open /dev/video0 succeed - 3 Open /dev/v4l-subdev3 succeed ------- VIDIOC_QUERYCAP ---- driver: rkisp_v5 card: rkisp_mainpath bus_info: platform:rkisp-vir0 version: 1.8.0 capabilities: 84201000 Video Capture Mplane Streaming VIDIOC_S_FMT succeed!
width 800, height 600, size 720000, bytesperline 0, format NV12 VIDIOC_REQBUFS succeed! Memory map succeed! VIDIOC_QBUF succeed! VIDIOC_STREAMON succeed! image: sequence 0, timestamp 1115378780
image: sequence 1, timestamp 1115511890
image: sequence 2, timestamp 1115645004
image: sequence 3, timestamp 1115778130
image: sequence 4, timestamp 1115911257
image: sequence 5, timestamp 1116044365
image: sequence 6, timestamp 1116177498
image: sequence 7, timestamp 1116310610
image: sequence 8, timestamp 1116443739
image: sequence 9, timestamp 1116576844
Save one frame to /data/ov13850.nv12 succeed! VIDIOC_STREAMOFF succeed! 文中各种mipi技术文档后台回复关键字mipi
掌握了这些命令我们就可以调试摄像头了。
后面还会继续更新几篇Camera文章
建议大家订阅本专题
也可以后台留言加一口君好友yikoupeng
拉你进高质量技术交流群。