输入
视频捕获的应用首先要通过VIDIOC_ENUMINPUT
命令来枚举所有可用的输入。在V4L2层,这个调用会转换成调用一个驱动中对应的回调函数:
----------------------------------------------------------------------------------------------------------
int (*vidioc_enum_input)(struct file *file, void *private_data, struct v4l2_input *input);
---------------------------------------------------------------------------------------------------------
在这个调用中,file 对就的是打开的视频设备。private_data是驱动的私有字段,input字段是真正的传递的信息,它有如下几个值得关注的字段:
struct v4l2_input
{
__u32 index:应用关注的输入的索引号; 这是惟一一个用户空间设定的字段. 驱动要分配索引号给输入,从0开始,依次往上增加.想要知道所以可用的输入的应用会调用VIDIOC_ENUMINPUT
调用过索引号从0开始,并开始递增。 一旦返回EINVAL,应用就知道,输入己经用光了.只要有输入,输入索引号0就一定要存在的.
__u8 name[32]: 输入的名字,由驱动设定.简单起见,可以设为”Camera”,诸如此类;如果卡上有多个输入,名称就要与接口的打印相符合.
__u32 type:输入的类型,现在只有两个值可选:V4L2_INPUT_TYPE_TUNER
和V4L2_INPUT_TYPE_CAMERA.
__u32 audioset:描述哪个音频输入可以与些视频输入相关联. 音频输入与视频输入一样通过索引号枚举 (我们会在另一篇文章中关注音频),但并非所以的音频与视频的组合都是可用的.这个字段是一个掩码,代表对于当前枚举出的视频而言,哪些音频输入是可以与之关联的.如果没有音频输入可以与之关联,或是只有一个可选,那么就可以简单地把这个字段置0.
__u32 tuner: 如果输入是一个调谐器 (type字段置为V4L2_INPUT_TYPE_TUNER), 这个字段就是会包含一个相应的调谐设备的索引号.枚举和调谐器的控制也将在未来的文章中讲述.
v4l2_std_id std: 描述设备支持哪个或哪些视频标准.
__u32 status: 给出输入的状态. 全整的标识符集合可以在V4L2的文档中找到(即这里
);简而言之,status中设置的每一位都代表一个问题. 这些问题包括没有电源,没有信号,没有同频锁,或 the presence of Macrovision(这个是什么意思?没查到)或是其他一些不幸的问题.
__u32 reserved[4]:保留字段,驱动应该将其置0.
通常驱动会设置上面所以的字段,并返回0。如果索引值超出支持的输入范围,应该返回-EINVAL.这个调用里可能出现的错误不多。
}
当应用想改变现行的输入时,驱动会收到一个对回调函数vidioc_s_input()的调用。
----------------------------------------------------------------------------------------------------------
int (*vidioc_s_input) (struct file *file, void *private_data, unsigned int index);
------------------------------------------------------------------------------------------------------
index的值与上面讲到的意义相同 – 它相来确定哪个输入是相要的.驱动要对硬件编辑,选择那个输入并返回0.也有可能要返回-EINVAL
(索引号不正确时) 或-EIO
(硬件有问题). 即使只有一路输入,驱动也要实现这个回调函数.
还有另一个回调函数,指示哪一个输入是激活状态的:
-----------------------------------------------------------------------------------------------------------
int (*vidioc_g_input) (struct file *file, void *private_data, unsigned int *index);
---------------------------------------------------------------------------------------------------------
这里驱动把index值设为对应的输入的索引号.
输出
枚举和选择输出的过程与输入的是十分相似的。所以这里的描述就从简。输入枚举的回调函数是这样的:
-----------------------------------------------------------------------------------------------------
int(*vidioc_enum_output)(structfile*file,void*private_data ,struct v4l2_output *output);
--------------------------------------------------------------------------------------------------
struct v4l2_output
{
结构的字段是:
__u32 index:相关输入的索引号.其工作方式与输入的索引号相同:它从0开始递增。
__u8 name[32]: 输出的名字.
__u32 type: 输入的类型.支持的输出类型为:V4L2_OUTPUT_TYPE_MODULATOR
用于模拟电视调制器,V4L2_OUTPUT_TYPE_ANALOG
用于基本模拟视频输出,和V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY
用于模拟VGA覆盖设备.
__u32 audioset: 能与这个视频协同工作的音频集.
__u32 modulator: 与此设备相关的调制器 (对于类型为V4L2_OUTPUT_TYPE_MODULATOR 的设备而言).
v4l2_std_id std:输出所支持的视频标准.
__u32 reserved[4]: 保留字段,要设置为0.
}
也有用于获得和设定现行输入设置的回调函数;他们与输入的具体对称性:
---------------------------------------------------------------------------------------------------------------int (*vidioc_g_output) (struct file *file, void *private_data, unsigned int *index);
int (*vidioc_s_output) (struct file *file, void *private_data, unsigned int index);
---------------------------------------------------------------------------------------------------------
即便只有一个输出,设备驱动也要定义所有上述的三个回调函数.
有了这些函数之后,V4L2应用就可以知道有哪些输入和输入,并在它们间进行选择.然而选译这些输入输出中所传输的是什么视频数据流则是一件更加复杂的事情.本系列的下一期文章,我们将关注视频数据流的格式,以及如何与用户空间协定数据格式.
v4l2文档第五A--颜色与格式
颜色与格式这是不定期发布的关于写视频驱动程序的LWN系统文章的第五篇.没有看过介绍篇的,也许想从这里开始.
应用在可以使视频设备工作之前,它必须与驱动达成了解,知道视频数据是何种格式的。这种协商将是一个非常复杂的过程,其原因有二:1、视频硬件所支持的视频格互不相同。2、在内核的格式转换是令人难以接受的。所以应用在找出一种硬件支持的格式,并做出一种大家都可以接受的配置。这篇文章将会讲述格式的基本描述方式;下期文章则会讲述V4L2驱动与应用协商格式时所实现的API。
色域
色域在广义上来讲,就是系统在描述色彩时所使用的坐标。V4L2规范中定义了好几个,但只有两个使用最为广泛。它们是:
V4L2_COLORSPACE_SRGB.多数开发者所熟悉的[red,green,blue]数组包含在这个色域之中。它为每一种颜色提供了一个简单的强度值,把它们混合在一起,从而产生了一种广泛的颜色的效果。表示RGB值的方法有很多,我们在下面将会有所介绍。
这个色域也包含YUV和YCbCr的表示方法,这个表示方法最早是为了早期的彩色电视信号可以在黑白电视中的播放,所以Y(或说亮度)值只是一个简单的亮度值,单独播放时可以产生灰度图像。U和V(或Cb和Cr)色度值描述的是色彩中蓝色和红色的分量。绿色可以通过从亮度中减去这些分量而得到。YUV和RGB之间的转换并不简单,但是我们有一些成形的公式可选。
注意:YUV和YCbCr并非完成一样,虽然有时他们的名字会替代使用。
V4L2_COLORSPACE_SMPTE170M 这个是NTSC或PAL等电视信号的模拟色彩表示方法,电视调谐器通常产生的色域都属于这个色域。
还存在很多其他的色域,他们多数都是电视相关标准的变种。点击查看V4L2规范中的详细列表。
密集存储和平面存储[p=21, null, left]如汝所见,像素值是以数组的方式表示的,通常由RGB或YUV值组成。要把这数组组织成图像,通常有两种常用的方法。
Packed格式把一个像素的所有值存领教在一起.
Planar 格式把每一个分量单独存储成一个阵列,这样在YUV格式中,所有Y值都连续地一起存储在一个阵列中,U值存储在另一个中,V值存在第三个中.这些平面常常都存储在一个缓冲区中,但并不一定非要这样.
紧密型存储方式可能使用更为广泛,特别是RGB格式,但这两种存储方式都可以由硬件产生并由应用程序请求。如果设备可以产生紧密型和平面型两种,那么驱动就要让两种都在用户空间可见。
四字符码(four Charactor Code:FourCC)[p=21, null, left]V4L2 API中表示色彩格式采用的是广受好评的四字符码(fourcc)机制。这些编码都是32位的值,由四个ASCII码产生。如此一来,它就有一个优点就是,易于传递,对人可读。当一个色彩格式读作,例如,”RGB4″就没有必要去查表了。
[p=21, null, left]注意:四字符码在很不同的设定中都会使用,有些还是早于linux的.Mplayer中内部使用它们,然而,fourcc只是说明一种编码机制,并不说明使用何种编码。Mplayer有一个转换函数,用于在它自己的fourcc码和v4l2用的fourcc码之间做出转换。
RGB格式[p=21, null, left]在下面的格式描述中,字节是按存储顺序列出的。在小端模式下,LSByte在前面。每字节的LSbit在右侧。每种色域中,轻阴影位是最高有效的。
当使用有空位(上图中灰色部分)的格式 , 应用可以使用空位作alpha(透明度)值.
上面的最后一个格式是”Bayer(人名好像是)”格式,此格式与多数摄像机感光器所得到的真实数据非常接近。每个像素都有绿色分量,但蓝和红只是隔一个像素才有分量。本质上讲,绿色带有更重的强度信息,而蓝红色则在丢失时以相隔像素的内插值替换。这种模式我们交在YUV格式中再次见到。
YUV格式[p=21, null, left]YUV的紧密型模式在下面首先展示,看表的关键处如下:
= Y (intensity)
= U (Cb)
= V (Cr)
也有几中平面型的YUV格式在用,但把它们全画出来并没有什么大的帮助,所以我们只是在下面举一下例子,常用”YUV 4:2:2″(V4L2_PIX_FMT_YUV422, fourcc422P)格式使用三组阵列,一幅4X4的图片将如下表示:
对于Bayer格式, YUV 4:2:2 每隔一个Y值有一个U和一个V值,展示图像需要以内插值替换的丢失的值。其他的平面YUV格式有:
V4L2_PIX_FMT_YUV420: YUV 4:2:0格式,每四个Y值才有一个U值一个V值.U和V都要在水平和垂直两个方向上都以内插值替换.平面是以Y-U-V的顺序存储的,与上面的例子一致.
V4L2_PIX_FMT_YVU420: 与YUV 4:2:0格式类似,只是U,V值调换了位置.
V4L2_PIX_FMT_YUV410: 每16个Y值才有一个U值和V值.阵列的顺序是 Y-U-V.
V4L2_PIX_FMT_YVU410: 每16个Y 值才有一个U值和V值.阵列的顺序是Y-V-U.
还存在一些其他的YUV格式,但他们的使用极用少。想要了解的话请看这里。
其他格式[p=21, null, left]还有一些可能对驱动有用的格式如下:
V4L2_PIX_FMT_JPEG: 一种定义模糊的JPEG流;更多信息请看这里.
V4L2_PIX_FMT_MPEG: MPEG流.还有一些MPEG流格式的变种;未来的文章中将讨论流的控制.
[p=21, null, left]还有一些其他的混杂的格式,其中一些还是有传利保护的;这里 有一张列表.
格式的描述[p=21, null, left]现在我们己经了解了颜色的格式,下面将要看看下V4L2 API中是如何描述图像格式的了。这里的主要的结构体是struct v4l2_pix_format (定义于),它包含如下字段:
struct v4l2_pix_format
{
__u32 width: 图片宽度,以像素为单位.
__u32 height:图片高度,以像素为单位.
__u32 pixelformat: 描述图片格式的四字符码.
enum v4l2_field field:很多图片的源会使数据交错 -先传输奇数行,然后是偶到行.真正的摄像头设备是不会做数据的交错的。 V4L2 API 允许应用使用很多种方式交错字段.常用的值为V4L2_FIELD_NONE (字段不交错),V4l2_FIELD_TOP (只交错顶部字面),或V4L2_FIELD_ANY (无所谓). 详情见这里.
__u32 bytesperline: 相临扫描行之间的字节数.这包括各种设备可能会加入的填充字节.对于平面格式,这个值描述的是最大的 (Y) 平面.
__u32 sizeimage: 存储图片所需的缓冲区的大小.
enum v4l2_colorspace colorspace: 使用的色域.
}
加到一起,这些参数以合理而完整的方式描述了视频数据缓冲区。应用可以填充v4l2_pix_format 请求用户空间开发者所能想到的几乎任何格式. 然而,在驱动层面上,驱动开发者则要限制在硬件所能支持的格式上. 所以每一个 V4L2 应用都必须经历一个与驱动协定的过程,以便于使用一个硬件支持并且能滿足应用需要的图像格式。下一期文章,我们将从驱动角度,描述这种协定是什么进行的。
v4l2驱动编写篇第五B--格式的协定
这是不定期发布的关于写视频驱动程序的LWN系统文章的一篇续篇.介绍篇包含了对整个系统的描述,并且包含对本篇的上一篇的链接,在上一集,我们关注了V4L2 API是如何描述视频格式的:图片的大小,和像素在其内部的表示方式。这篇文章将完成对这个问题的讨论,它将描述如就硬件所支持的实际视频格与应用达到协议。
如我们在上一篇中所见,在存储器中表示图像有很多种方法。市场几乎找不到可以处理所有V4L2所理解的视频格式的设备。驱动不应支持底层硬件不懂的视频格式。实际上在内核中进行格式的转换是令人难以接受的。所以驱动必须可以应用选择一个硬件可以支持的格式。
第一步就是简单的允许应用查询所支持的格式。VIDIOC_ENUM_FMT ioctl()就是为此目的而提供的。在驱动内部这个调用会转化为这样一个回调函数(如果查询的是视频捕获设备)。
-----------------------------------------------------------------------------------------
int (*vidioc_enum_fmt_cap)(struct file *file, void *private_data,
struct v4l2_fmtdesc *f);
-----------------------------------------------------------------------------------------
这个回返调函数要求视频捕获设备描述其支持的格式。应用会传递一个v4l2_fmtdesc 结构体:
struct v4l2_fmtdesc
{
__u32 index;
enum v4l2_buf_type type;
__u32 flags;
__u8 description[32];
__u32 pixelformat;
__u32 reserved[4];
};
应用会设置index 和type 字段.
index :是用来确定格式的一个简单的整型 数;与其他V4L2所使用的索引(indexes)一样,这个也是从0开始递增,至最大允许的值为止.应用可以通过一直递增索引值(index)直到返回EINVAL的方式枚举所有支持的格式.
type :字段描述的是数据流类型 ;对于视频捕获设备来说(摄像头或调谐器就是V4L2_BUF_TYPE_VIDEO_CAPTURE.如果index 对就某个支持的格式,驱动应该填写结构体的其他字段.
Pixelformat:字段应该是描述视频表现方式的fourcc编码,
description :是对这个格式的一种简短的字符串描述.
flags :字段只定义了一个值即V4L2_FMT_FLAG_COMPRESSED,它表示是一个压缩了的视频格式.
上述的函数是视频捕获函数;只有当type 字段的是值是V4L2_BUF_TYPE_VIDEO_CAPTURE时才会调用. VIDIOC_ENUM_FMT 调用将根据type字段的值的不同解释成不同的回调函数。
int (*vidioc_enum_fmt_video_output)(file, private_date, f);
int (*vidioc_enum_fmt_overlay)(file, private_date, f);
int (*vidioc_enum_fmt_vbi)(file, private_date, f);
*/
int (*vidioc_enum_fmt_vbi_capture)(file, private_date, f);
int (*vidioc_enum_fmt_vbi_output)(file, private_date, f);
int (*vidioc_enum_fmt_type_private)(file, private_date, f);
参数类似对于所以的调用都是一样. 对于以V4L2_BUF_TYPE_PRIVATE开头的解码器,驱动可以支持特殊的缓冲类型是没有意义的。, 但在应用端却需要一个清楚的认识. 对这篇文章的目的而言,我们更加关心的是视频捕获和输出设备; 其他的视频设备我们会在将来某期的文章中讲述.
应用可以通过调用VIDIOC_G_FMT知道硬件现在的配置如何。这种情况下传递的参数是一个v4l2_format 结构体:
struct v4l2_format
{
enum v4l2_buf_type type;
union
{
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[200];
} fmt;
};
同样,type 描述的是缓冲区类型;V4L2层会根据type的不同,将调用解释成不同的驱动的回调函数. 对于视频捕获设备而言,这个回调函数就是:
-------------------------------------------------------------------------
int (*vidioc_g_fmt_cap)(struct file *file, void *private_data,
struct v4l2_format *f);
-----------------------------------------------------------------------------
对于视频捕获(和输出)设备, 联合体中pix 字段是我们关注的重点. 这是我们在上一期中见过的v4l2_pix_format 结构体;驱动应该用现在的硬件设置填充那个结构体并且返回。这个调用通常不会失败,除非是硬件出现了非常严重的问题。
其他的回调函数还有:
int (*vidioc_s_fmt_overlay)(file, private_data, f);
int (*vidioc_s_fmt_video_output)(file, private_data, f);
int (*vidioc_s_fmt_vbi)(file, private_data, f);
int (*vidioc_s_fmt_vbi_output)(file, private_data, f);
int (*vidioc_s_fmt_vbi_capture)(file, private_data, f);
int (*vidioc_s_fmt_type_private)(file, private_data, f);
vidioc_s_fmt_video_output()与捕获接口一样使用相同的方式使用同一个pix字段。
多数应用都想最终对硬件进行配置以使其为应用提供一种符合其目的的格式。改变视频格有两个接口。第一个是VIDIOC_TRY_FMT 调用,它在V4L2驱动中转化为下面的回调函数:
----------------------------------------------------------------------------
int (*vidioc_try_fmt_cap)(struct file *file, void *private_data,
struct v4l2_format *f);
int (*vidioc_try_fmt_video_output)(struct file *file, void *private_data,
struct v4l2_format *f);
-------------------------------------------------------------------------------
要处理这个调用,驱动会查看请求的视频格式,然后断定硬件是否支持这个格式。如果应用请求的格式是不能支持的,就会返回-EINVAL.所以,例如,一个描述了一个不支持格的fourcc编码或者请求了一个隔行扫描的视频,而设备只支持逐行扫描的就会失败。在另一方面,驱动可以调整size字段,以与硬件支持的图像大小相适应。普便的做法是可能的话就将大小调小。所以一个只能处理VGA分辨率的设备驱动会根据情况相应地调整width和height参数而成功返回。v4l2_format 结构体会在调用后复制给用户空间;驱动应该更新这个结构体以反映改变的参数,这样应用才可以知道它真正得到就是什么。
VIDIOC_TRY_FMT 这个处理对于驱动来说是可选的,但是不推荐忽略这个功能.如果提供了的话,这个函数可以在任何时候调用,甚至时设备正在工作的时候。它不可以对实质上的硬件参数做任何改变,只是让应用知道都可以做什么的一种方式。
如果应用要真正的改变硬件的格式,它使用VIDIOC_S_FMT 调用,它以下面的方式到达驱动:
------------------------------------------------------------------------------------
int (*vidioc_s_fmt_cap)(struct file *file, void *private_data,
struct v4l2_format *f);
int (*vidioc_s_fmt_video_output)(struct file *file, void *private_data,
struct v4l2_format *f);
----------------------------------------------------------------------------------------
与VIDIOC_TRY_FMT不同,这个调用是不能随时调用的.如果硬件正在工作,或者有流缓冲器己经开辟了(未来另一篇文章的),改变格式会带来无尽的麻烦。想想会发生什么,比如说,一个新的格式比现在使的缓冲区大的时候。所以驱动要一直保证硬件是空闲的,如果不空闲就对请求返回失败(-EBUSY).
格式的改变应该是原子的 – 它或者改变所以的参数以实现请求否则就必须一个也不改变.同样,驱动在必要时是可以改变图像的大小的,通常的回调函数格式与下面的差不多:
int my_s_fmt_cap(struct file *file, void *private,
struct v4l2_format *f)
{
struct mydev *dev = (struct mydev *) private;
int ret;
if (hardware_busy(mydev))
return -EBUSY;
ret = my_try_fmt_cap(file, private, f);
if (ret != 0)
return ret;
return tweak_hardware(mydev, &f->fmt.pix);
}
使用VIDIOC_TRY_FMT 句柄可以避免代码重写而且可以避免任何没有先实现那个函数的借口. 如果”try”函数成功返回,结果格式就己知并且可以直接编程进硬件。
还有很多其他调用也用影响视频I/O的完成方式。将来的文章将会讨论他们中的一部分。支持设置格式就足以让应用开始传输图像了,而且那也这个结构体的最终目的.所以下一篇文章,(希望会在这次之后的时间不会太久)我们会来关注对视频数据的读和写的支持。