V4l2驱动编写篇第五B--格式的协定
这是不定期发布的关于写视频驱动程序的LWN系统文章的一篇续篇.介绍篇 包含了对整个系统的描述,并且包含对本篇的上一篇的链接,在上一集,我们关注了V4L2 API是如何描述视频格式的:图片的大小,和像素在其内部的表示方式。这篇文章将完成对这个问题的讨论,它将描述如就硬件所支持的实际视频格与应用达到协 议。
1、 枚举硬件所支持的所有格式
如我们在上一篇中所见,在存储器中表示图像有很多种方法。市场几乎找不到可以处理所有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字段的值的不同解释成不同的回调函数。
/* V4L2_BUF_TYPE_VIDEO_OUTPUT */
int (*vidioc_enum_fmt_video_output)(file, private_date, f);
/* V4L2_BUF_TYPE_VIDEO_OVERLAY */
int (*vidioc_enum_fmt_overlay)(file, private_date, f);
/* V4L2_BUF_TYPE_VBI_CAPTURE */
int (*vidioc_enum_fmt_vbi)(file, private_date, f);
/* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ */
int (*vidioc_enum_fmt_vbi_capture)(file, private_date, f);
/* V4L2_BUF_TYPE_VBI_OUTPUT */
/* V4L2_BUF_TYPE_SLICED_VBI_OUTPUT */
int (*vidioc_enum_fmt_vbi_output)(file, private_date, f);
/* V4L2_BUF_TYPE_VIDEO_PRIVATE */
int (*vidioc_enum_fmt_type_private)(file, private_date, f);
参数类型对于所有的调用都是一样, 对于以V4L2_BUF_TYPE_PRIVATE开头的解码器,驱动可以支持特殊的缓冲类型是没有意义的。 但在应用端却需要一个清楚的认识. 对这篇文章的目的而言,我们更加关心的是视频捕获和输出设备; 其他的视频设备我们会在将来某期的文章中讲述.
2、当前硬件的图像配置
应用可以通过调用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 结构体(API中有详细解释);驱动应该用当前硬件的设置填充这个结构体并且返回。这个调用通常不会失败,除非是硬件出现了非常严重的问题。
其他的回调函数还有:
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字段。
3,设置硬件的视频格式
多数应用都想最终对硬件进行配置以使其为应用提供一种符合其目的的格式。改变视频格式有两个接口。第一个是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);
/* And so on for the other buffer types */
要处理这个调用,驱动会查看请求的视频格式,然后断定硬件是否支持这个格式。如果应用请求的格式是硬件不能支持的,就会返回-EINVAL。所以,例如,一个描述了一个不支持格的fourcc编码或者请求了一个隔行扫描的视频,而设备只支持逐行扫描的就会失败。 在另一方面,驱动可以调整size字段,以与硬件支持的图像大小相适应。普便的做法是可能的话就将大小调小。所以一个只能处理VGA分辨率的设备驱动会根据情况相应地调整width和height参数而成功返回。v4l2_format 结构体会在调用后复制给用户空间;驱动应该更新这个结构体以反映改变的参数,这样应用才可以知道它真正得到就是什么。
VIDIOC_TRY_FMT 这个处理对于驱动来说是可选的,但是不推荐忽略这个功能。如果提供了的话,这个函数可以在任何时候调用,甚至是设备正在工作的时候。它不可以对实质上的硬件参数做任何改变,只是让应用知道都可以做什么的一种方式。
4、改变硬件的视频图像格式
如果应用要真正的改变硬件的格式,它使用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的完成方式。将来的文章将会讨论他们中的一部分。支持设置格式就足以让应用开始传输图像了,而且那也这个结构体的最终目的.所以下一篇文章,(希望会在这次之后的时间不会太久)我们会来关注对视频数据的读和写的支持。