v4l2 API详解

(一)  V4L2 API详解<一> 背景知识
对V4L2 Device编程,通常包含以下步骤:
1. 打开Device。
2. 改变devcie的特性,选择video或者audio 输入,video 标准,图像亮度等等。
3. 协商数据格式。
4. 协商输入/输入的方法。
5. 真实的输入/输出 Loop。
6. 关闭Device。
(二)  V4L2 API详解<二> Camera详细设置
1 查询V4L2 功能集: VIDIOC_QUERYCAP
struct v4l2_capability cap;
ioctl(Handle, VIDIOC_QUERYCAP, &cap);

struct v4l2_capability
{
__u8 driver[16];       // 驱动名。通常为:uvcvideo
__u8 card[32];         // Device名。设备名:厂商会填写。
__u8 bus_info[32];     // 在Bus系统中存放位置。通常为:usb-hiusb-ehci-2.4
__u32 version;         // driver版本。通常为:V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
__u32 capabilities;    // 能力集
__u32 reserved[4];
};

能力集中包含:
V4L2_CAP_VIDEO_CAPTURE 0x00000001     The device supports the Video Capture interface.
V4L2_CAP_VIDEO_OUTPUT   0x00000002    The device supports the Video Output interface.
V4L2_CAP_VIDEO_OVERLAY 0x00000004     The device supports the Video Overlay interface.
A video overlay device typically stores captured images directly in the video memory of a graphics card,with hardware clipping and scaling.
V4L2_CAP_VBI_CAPTURE     0x00000010 The device supports the Raw VBI Capture interface, providing Teletext and Closed Caption data.
V4L2_CAP_VBI_OUTPUT     0x00000020      The device supports the Raw VBI Output interface.
V4L2_CAP_SLICED_VBI_CAPTURE  0x00000040 The device supports the Sliced VBI Capture interface.
V4L2_CAP_SLICED_VBI_OUTPUT   0x00000080 The device supports the Sliced VBI Output interface.
V4L2_CAP_RDS_CAPTURE    0x00000100      [to be defined]
#define V4L2_CAP_TUNER 0x00010000  
#define V4L2_CAP_AUDIO 0x00020000  
#define V4L2_CAP_RADIO 0x00040000  
#define V4L2_CAP_READWRITE 0x01000000  
#define V4L2_CAP_ASYNCIO 0x02000000  
#define V4L2_CAP_STREAMING 0x04000000
因为V4L2要求所有driver 和Device都支持这个Ioctl。 所以,可以通过这个ioctl是否成功来判断当前设备和dirver 是否支持V4L2规范。当然,这样同时还能够得到设备足够的能力信息。
2 枚举设备所支持的image format:   VIDIOC_ENUM_FMT
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(Handle, VIDIOC_ENUM_FMT, &fmtdesc);

struct v4l2_fmtdesc
{
__u32 index;             // 需要填充,从0开始,依次上升。
enum v4l2_buf_type type; // 如果为Camera,则填写V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 flags;             // 如果压缩的,则Driver 填写:V4L2_FMT_FLAG_COMPRESSED,否则为0
__u8 description[32];    // image format的描述,如:YUV 4:2:2 (YUYV)
__u32 pixelformat;       // 所支持的格式。 如:V4L2_PIX_FMT_UYVY
__u32 reserved[4];
};
使用ioctl(VIDIOC_ENUM_FMT)依次询问,type为V4L2_BUF_TYPE_VIDEO_CAPTURE。 index从0开始,依次增加,直到返回 -1 (如果index有效,则返回0)。Driver会填充结构体struct v4l2_fmtdesc的其它内容,如果index超出范围,则返回-1。
这样,则知道当前硬件支持什么样的image format. 下一步,则可以设置image了。
图像格式,见附录1。
3.  获取和设置Image Format: VIDIOC_G_FMT, VIDIOC_S_FMT 
3.1:获取当前Image Format
struct v4l2_format Format;
memset(&Format, 0, sizeof(struct v4l2_format));
Format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(Handle, VIDIOC_G_FMT, &Format);

struct v4l2_format
{
enum v4l2_buf_type type;      // Camera,则用户必须填写:V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;   // used by video capture and output devices
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[200];
} fmt;
};

因为是Camera, 所以采用pix. 现在分析如下:
struct v4l2_pix_format
{
__u32 width;        // Image width in pixels.
__u32 height;       // Image Height in pixels.
__u32 pixelformat;  // Image格式,最常见的有:V4L2_PIX_FMT_YYUV
enum v4l2_field field; // 是否逐行扫描,是否隔行扫描. Sam通常采用V4L2_FIELD_NONE,逐行放置数据 (注1)
__u32 bytesperline;    // 每行的byte数
__u32 sizeimage;       // 总共的byte数,bytesperline * height
enum v4l2_colorspace colorspace;  //This information supplements the pixelformat and must be set by the driver
__u32 priv;
};
利用ioctl(VIDIOC_G_FMT)得到当前图像格式设置。因为Camera为CAPTURE设备,所以需要设置type为V4L2_BUF_TYPE_VIDEO_CAPTURE。然后Driver会填充其它内容。
3.2:设置Image Format
struct v4l2_format Format;
Format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
Format.fmt.pix.width = Width;
Format.fmt.pix.height = Height;
Format.fmt.pix.pixelformat = pixelformat; //V4L2_PIX_FMT_YUYV;
Format.fmt.pix.field = field;
io_rel = ioctl(Handle, VIDIOC_S_FMT, &Format);
设置了Image Format, 是指每一帧的数据格式。
Stream需要设置,这就是下面所说的Stream设置了。它就包含帧数设置和修改。
4. 获取和设置Stream信息: VIDIOC_G_PARM, VIDIOC_S_PARM
Stream信息,主要是设置帧数。
4.1:获取Stream信息
struct v4l2_streamparm Stream_Parm;
memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));
Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
io_rel = ioctl(Handle, VIDIOC_G_PARM, &Stream_Parm);

struct v4l2_streamparm
{
enum v4l2_buf_type type;
union
{
struct v4l2_captureparm capture;
struct v4l2_outputparm output;
__u8 raw_data[200];
} parm;
};

因为是Camera, 所以使用capture。
struct v4l2_captureparm
{
__u32 capability;   // 是否可以被timeperframe控制帧数。可以则:V4L2_CAP_TIMEPERFRAME
__u32 capturemode;  // 是否为高清模式。如果是则设置为:V4L2_MODE_HIGHQUALITY。 高清模式会牺牲其它信息。通常设置为0。
struct v4l2_fract timeperframe;  //帧数。
__u32 extendedmode; //定制的。如果不支持,设置为0
__u32 readbuffers;
__u32 reserved[4];
};

struct v4l2_fract {
__u32 numerator;   // 分子。 例:1
__u32 denominator; // 分母。 例:30
};
用户只需要填充type为V4L2_BUF_TYPE_VIDEO_CAPTURE。 Driver就会把结构体中其它部分填充好。
4.2:设置Stream
struct v4l2_streamparm Stream_Parm;
memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));
Stream_Parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
Stream_Parm.parm.capture.timeperframe.denominator = Denominator;;
Stream_Parm.parm.capture.timeperframe.numerator = Numerator;
io_rel = ioctl(Handle, VIDIOC_S_PARM, &Stream_Parm);
请注意,哪怕ioctl返回0。也有可能没设置成功。所以需要再次GetParam。
当然,哪怕Get发现设置成功。真正抓帧也可能没那么高。
5. 获取一些设置: VIDIOC_G_CTRL
如曝光模式(Exposure Type),曝光值(Exposure),增益(Gain),白平衡(WHITE_BALANCE),亮度(BRIGHTNESS),饱和度(SATURATION),对比度(CONTRAST)等信息。
以曝光模式,曝光,和增益为例。
曝光模式:
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_EXPOSURE_AUTO;
ret = ioctl(Handle, VIDIOC_G_CTRL, &ctrl);

struct v4l2_control
{
__u32 id;
__s32 value;
};
ctrl.value则由Driver填写。告知当前曝光模式。有以下几个选择:
enum  v4l2_exposure_auto_type {
V4L2_EXPOSURE_AUTO = 0,
V4L2_EXPOSURE_MANUAL = 1,
V4L2_EXPOSURE_SHUTTER_PRIORITY = 2,
V4L2_EXPOSURE_APERTURE_PRIORITY = 3
};
V4L2_EXPOSURE_AUTO
Automatic exposure time, automatic iris aperture.
V4L2_EXPOSURE_MANUAL
Manual exposure time, manual iris.
V4L2_EXPOSURE_SHUTTER_PRIORITY
Manual exposure time, auto iris.
V4L2_EXPOSURE_APERTURE_PRIORITY
Auto exposure time, manual iris.
APP 填写结构体中的id。通过调用VIDIOC_G_CTRL,driver 会填写结构体中value项。
Sam测试发现,在Linux下,V4L2_EXPOSURE_ATUO并不被Firmware认可,要设置自动曝光,需要设置为:V4L2_EXPOSURE_APERTURE_PRIORITY。
曝光:
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
ret = ioctl(Handle, VIDIOC_G_CTRL, &ctrl);
增益:
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_GAIN;
ret = ioctl(Handle, VIDIOC_G_CTRL, &ctrl);
6. 获取设置具体信息: VIDIOC_QUERYCTRL
在很多情况下,我们并不知道如何设置一些信息,例如,曝光应该设置为多少?Driver能够接受的范围是多少?最大,最小值是多少?步长是多少?缺省值为多少?
可以通过VIDIOC_QUERYCTRL得到。
以增益为例:
struct v4l2_queryctrl  Setting;
Setting.id = V4L2_CID_GAIN;
ret = ioctl(Handle, VIDIOC_QUERYCTRL, &Setting);

struct v4l2_queryctrl
{
__u32 id;           // 用户设置。指定查找的是哪个ID。
enum v4l2_ctrl_type type;
__u8 name[32];      // ID对应的名字。
__s32 minimum;
__s32 maximum;
__s32 step;         // 步长
__s32 default_value;
__u32 flags;
__u32 reserved[2];
};
这样,就知道设置什么值是合法的了。那么,下一步就是设置了。
7. 设置特性: VIDIOC_S_CTRL
设置id和value.调用ioctl就好。
以增益为例:
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_GAIN;
ctrl.value = Gain;
ret = ioctl(Handle, VIDIOC_S_CTRL, &ctrl);
附录1: V4L2中图像格式
结构体v4l2_pix_format,其中包含pixelformat.表示像素格式。
V4L2_PIX_FMT_RGB565
每个像素使用2字节(16bit)存储,其中:
5bit R,6bit G,5bit B
存储格式如下所示:
低字节 高字节
GGGBBBBB RRRRRGGG
RGB565色深等于16,可以表示65536种颜色
V4L2_PIX_FMT_RGB24
每个像素使用3字节(24bit)存储,其中:
8bit R,8bit G,8bit B
存储格式如下所示:
低字节 高字节
RRRRRRRR GGGGGGGG BBBBBBBB
RGB24色深等于24,可以表示16777216种颜色。24位色深被称为真彩色,它达到了人眼分辨的极限
YUV的存储格式分两大类:Package(打包)格式和Planar(平面)格式。
Package格式将Y,U,V数据统一存放在一个数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel)。
Planar格式则是分别使用三个数组存放三个数据。
V4L2_PIX_FMT_YUV420
V4L2_PIX_FMT_YUV420属于planar格式,Y、U、V三个分量分别存储在三个平面,使用YUV4:2:0模式进行采样,该采样模式下,水平方向的两个连续像素和垂直方向的两个连续像素,总共四个像素组成一个宏像素,每个宏像素使用6字节(48bit)存储,每个像素使用1.5字节(12bit)存储。
请看下图,是水平方向的两个连续像素和垂直方向的两个连续像素,总共四个像素组成一个宏像素,即Y1、Y2、Y7、Y8和U1、V1组成一个宏像素,Y1、Y2、Y7、Y8共用U1、V1。
V4L2_PIX_FMT_YUYV
V4L2_PIX_FMT_YUYV属于package格式,Y、U、V三个分量交叉存储在同一个平面,使用YUV4:2:2模式进行采样,在该采样模式下,
水平方向的两个连续像素组成一个宏像素,每个宏像素使用4字节(32bit)存储,每个像素使用2字节(16bit)存储。
存储格式如下所示:
低字节 高字节
Y0 U0 Y1 V0 Y2 U1 Y3 V1 Y4 U2 Y5 V2 Y6 U3 Y7 V3
从上述信息可知:Y0 U0 Y1 V0组成一个宏像素Y0、Y1共用U0和V0
大多数Camera都提供这种格式。
V4L2_PIX_FMT_UYVY
V4L2_PIX_FMT_UYVY属于package格式,Y、U、V三个分量交叉存储在同一个平面,使用YUV4:2:2模式进行采样,在该采样模式下,
水平方向的两个连续像素组成一个宏像素,每个宏像素使用4字节(32bit)存储,每个像素使用2字节(16bit)存储。
存储格式如下所示:
低字节 高字节
U0 Y0 V0 Y1 U1 Y2 V1 Y3 U2 Y4 V2 Y5 U3 Y6 V3 Y7
从上述信息可知:U0 Y0 V0 Y1组成一个宏像素Y0、Y1共用U0和V0
(三)  V4L2 API详解<三> Buffer的准备和数据读取
介绍V4L2获得数据的几个关键ioctl,Buffer的申请和数据的抓取。
1. 初始化 Memory Mapping 或 User Pointer I/O
申请数据Buffer。
/*
* 参数一:open()所产生的句柄。
* 参数二:VIDIOC_REQBUFS
* 参数三:in/out结构体。
*/
int ioctl(int fd, int requestbuf, struct v4l2_requestbuffers * argp);
struct v4l2_requestbuffers
{
__u32 count;
enum v4l2_buf_type type;
enum v4l2_memory memory; //Applications set this field to V4L2_MEMORY_MMAP or V4L2_MEMORY_USERPTR
__u32 reserved[2];
};
注意,有两种方式的I/O。 Memory Mapping 和User Pointer。
Memory Mapping的Buffer由Driver申请为物理连续的内存空间(Kernel空间)。在此ioctl调用时分配内存(Kernel空间),之后需要mmap()将他们映射到用户空间。
1.1 Memory Mapping模式详解
在使用Memory Mapping模式时,参数三中结构体内每个field都需要设置。
__u32 count;              // 当memory=V4L2_MEMORY_MMAP时,此处才有效。表明要申请的buffer个数。
enum v4l2_buf_type type;  // Stream 或者Buffer的类型。此处肯定为V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory;  // 既然是Memory Mapping模式,则此处设置为:V4L2_MEMORY_MMAP
注意:count是个输入输出参数。因为你所申请到的Buffer个数不一定就是你所输入的Number。所以在ioctl执行后,driver会将真实申请到的buffer个数填充到此field。这个数目有可能大于你想要申请的,也可能小与,甚至可能是0个。
应用程序可以再次调用ioctl(VIDIOC_REQBUFS)来修改buffer个数。但前提是必须先释放已经 mapped 的 buffer ,可以先 munmap ,然后设置参数 count 为 0 来释放所有的 buffer。
支持Memory  Mapping  I/O方式的前提是:v4l2_capability中支持V4L2_CAP_STREAMING。在这个模式下,数据本身不会被Copy,只是在Kernel和用户态之间交换。在应用程序想要访问到这些数据之前,它必须调用mmap()映射到用户态。
同时也要注意,通过ioctl申请的内存,是物理内存,无法被交换入Disk,所以一定要释放:munmap()。
1.2 User Pointer模式
User Pointer模式时,应用程序实现申请内存。
只需要填充type=V4L2_BUF_TYPE_VIDEO_CAPTURE, memory=V4L2_MEMORY_USERPTR
2. 获取Buffer状态
/*
* 参数一:open()所产生的句柄。
* 参数二:VIDIOC_QUERYBUF
* 参数三:v4l2_buffer 结构体。(IN/OUT参数)
*/
int ioctl(int fd, int request, struct v4l2_buffer* argp);
注意,此ioctl是Memory Mapping的I/O方法之一,User Pointer模式不需要。在Buffer在ioctl-VIDIOC_REQBUFS执行时创建后,随时都可以调用此Ioctl得到buffer信息。在应用程序设计中通过调VIDIOC_QUERYBUF来获取内核空间的视频缓冲区信息,然后调用函数mmap把内核空间地址映射到用户空间,这样应用程序才能够访问位于内核空间的视频缓冲区。
我们首先通过v4l2_buffer结构体看看这个输入输出参数需要输入些什么,以及能够得到什么信息。
struct v4l2_buffer
{
__u32 index;
enum v4l2_buf_type type;
__u32 bytesused;
__u32 flags;
enum v4l2_field field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;

enum v4l2_memory memory;
union {
    __u32 offset;
    unsigned long userptr;
} m;
__u32 length;
__u32 input;
__u32 reserved;
};
在调用ioctl(VIDIOC_QUERYBUF)时,需要写入的参数有:
enum v4l2_buf_type type;  // V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 index;              // 这里需要解释一下,因为在调用ioctl(VIDIOC_REQBUFS)时,建立了count个Buffer。所以,这里index的有效范围是:0到count-1.
在调用ioctl-VIDIOC_QUERYBUF后,Driver会填充v4l2_buffer 结构体内所有信息供用户使用:
1. flags中, V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED and V4L2_BUF_FLAG_DONE被设置。
2. memory中,V4L2_MEMORY_MMAP被设置。
3. m.offset中,从将要mapping 的device memory头到数据头的offset.
4. length中,填充当前Buffer长度。
5. 其它的Field有可能设置,也有可能不被设置。
这样,mmap()需要有的信息就全了。而mmap()之后,Device Driver 申请的内存就能映射到用户空间。数据就可以被应用程序使用了。这才是ioctl(VIDIOC_QUERYBUF)的关键作用。
3.和Driver交换buffer
ioctl(VIDIOC_QBUF): 将指定的Buffer放到输入队列中,即向Device表明这个Buffer可以存放东西。
ioctl(VIDIOC_DQBUF): 将输出队列中的数据buffer取出。
在 driver 内部管理着两个 buffer queues ,一个输入队列,一个输出队列。对于 capture device 来说,当输入队列中的 buffer 被塞满数据以后会自动变为输出队列,等待调用 VIDIOC_DQBUF 将数据进行处理以后重新调用 VIDIOC_QBUF 将 buffer 重新放进输入队列。
ioctl( VIDIOC_QBUF):
int ioctl(int fd, int request, struct v4l2_buffer* argp);
参数一:open()所产生的句柄。
参数二:VIDIOC_QBUF
参数三:v4l2_buffer 结构体。参数三是IN/OUT 参数。需要填充:
enum v4l2_buf_type type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 index;  // 这里需要解释一下,因为在调用ioctl-VIDIOC_REQBUFS时,建立了count个Buffer。所以,这里index的有效范围是:0到count-1.
memory: V4L2_MEMORY_MMAP.
则这个结构体指明的buffer被送到输入队列,表明此Buffer可以被device 填充数据。
ioctl( VIDIOC_DQBUF):
int ioctl(int fd, int request, struct v4l2_buffer* argp);
参数一:open()所产生的句柄。
参数二:VIDIOC_DQBUF
参数三:v4l2_buffer 结构体。(IN/OUT参数)
从输出队列中取出一个有数据的Buffer。这个Buffer中的数据被处理后,此Buffer可以通过ioctl(VIDIOC_QBUF)再次放入输入队列中去。
换句话说: 调用ioctl(VIDIOC_DQBUF)时,Driver将输出队列中的buffer传递出来。index则指的是这次给出哪个buffer。 这个index或者说这个v4l2_buffer结构体还有用。 因为在copy完成数据后。还需要通过调用ioctl(VIDIOC_QBUF)告知Driver,这个buffer已经空了,可以被再次使用了。
4. 开始和结束捕获
ioctl( VIDIOC_STREAMON)
ioctl( VIDIOC_STREAMOFF)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值