本文章参考:
http://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html
还参考了imx6测试代码:Mxc_v4l2_tvin.c
对以上内容进行自己的总结
v4l2详情可以参考:
http://work-blog.readthedocs.io/en/latest/v4l2%20intro.html
Video For Linux Two 是内核提供给应用程序访问音、视频驱动的统一接口,我们只需要利用其提供的API进行应用程序编程
1)linux一切皆文件,首先打开设备文件
2)V4L2获取图像设置(Capture)
3)V4L2输出图像设置(Output)
4)Frambuffer的设置
5)开始循环获取图像数据并输出
6)关闭设备
(注意,以下代码不是完整代码,许多错误处理等内容都已省略)
#define TFAIL -1
char v4l_capture_dev[100] = "/dev/video0";
char v4l_output_dev[100] = "/dev/video17";
fd_capture_v4l = open(v4l_capture_dev, O_RDWR, 0)
fd_output_v4l = open(v4l_output_dev, O_RDWR, 0)) < 0)
fd_fb = open(fb_device, O_RDWR )
打开设备没什么可说的,/dev/video0为输入,/dev/video17为输出
if (v4l_capture_setup() < 0) {
printf("Setup v4l capture failed.\n");
return TFAIL;
}
int v4l_capture_setup(void)主要包括以下几个方面:
(注意:在Capture中,所有type属性皆为V4L2_BUF_TYPE_VIDEO_CAPTURE)
struct v4l2_capability cap
ioctl (fd_capture_v4l, VIDIOC_QUERYCAP, &cap)
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
获取视频捕捉能力参数cropcap:
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd_capture_v4l, VIDIOC_CROPCAP, &cropcap)
设置实际取景参数crop:
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; /* 设为默认大小 */
ioctl (fd_capture_v4l, VIDIOC_S_CROP, &crop)
struct v4l2_streamparm parm;
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = 30;
parm.parm.capture.capturemode = 0;
其中帧率计算方式为 numerator/denominator
ioctl(fd_capture_v4l, VIDIOC_S_PARM, &parm)
struct v4l2_format fmt
设置:
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 800; //视频分辨率宽度
fmt.fmt.pix.height = 480; //视频分辨率高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; //像素格式,根据实际设置
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; //视频采样为隔行采样
ioctl (fd_capture_v4l, VIDIOC_S_FMT, &fmt)
获取:
ioctl(fd_capture_v4l, VIDIOC_G_FMT, &fmt)
这里先设置,后获取是因为VIDIOC_S_FMT可能会改变实际的宽和高,有时候设置的并不是实际的宽高值,所以要再获取一次
设置真正的宽和高:
g_in_width = fmt.fmt.pix.width;
g_in_height = fmt.fmt.pix.height;
struct v4l2_requestbuffers req
memset(&req, 0, sizeof (req));
req.count = g_capture_num_buffers; //缓冲区个数,不能少于3个,我这里的g_capture_num_buffers设置为3
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; //缓冲区为内存映射方式
ioctl (fd_capture_v4l, VIDIOC_REQBUFS, &req) //申请拥有3个缓冲帧的缓冲区
if (v4l_output_setup() < 0) {
printf("Setup v4l output failed.\n");
close(fd_capture_v4l);
return TFAIL;
}
int v4l_output_setup(void)的主要内容如下(与Capture比较相似):
(注意:在Output中,所有type属性皆为V4L2_BUF_TYPE_VIDEO_OUTPUT)
ioctl(fd_output_v4l, VIDIOC_QUERYCAP, &cap)
struct v4l2_fmtdesc fmtdesc
fmtdesc.index = 0; //查询格式序号
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
获取:
memset(&cropcap, 0, sizeof(cropcap));
cropcap.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(fd_output_v4l, VIDIOC_CROPCAP, &cropcap)
设置:
crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
crop.c.top = g_display_top; // 0
crop.c.left = g_display_left; // 0
crop.c.width = g_display_width; // 显示宽度
crop.c.height = g_display_height; // 显示高度
ioctl(fd_output_v4l, VIDIOC_S_CROP, &crop)
fb.flags = V4L2_FBUF_FLAG_OVERLAY;
ioctl(fd_output_v4l, VIDIOC_S_FBUF, &fb);
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width= g_in_width;
fmt.fmt.pix.height= g_in_height; //g_in_width和g_in_height已经在Capture中配置好了
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.bytesperline = g_in_width;
fmt.fmt.pix.priv = 0;
fmt.fmt.pix.sizeimage = 0;
ioctl(fd_output_v4l, VIDIOC_S_FMT, &fmt)
ioctl(fd_output_v4l, VIDIOC_G_FMT, &fmt)
g_frame_size = fmt.fmt.pix.sizeimage; //设置图像帧大小
memset(&buf_req, 0, sizeof(buf_req));
buf_req.count = g_output_num_buffers; //这里输出缓冲帧设置为4
buf_req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf_req.memory = V4L2_MEMORY_MMAP; //同样是采用内存映射方式
ioctl(fd_output_v4l, VIDIOC_REQBUFS, &buf_req)
struct mxcfb_gbl_alpha alpha
alpha.alpha = 0;
alpha.enable = 1;
ioctl(fd_fb, MXCFB_SET_GBL_ALPHA, &alpha)
调用mxc_v4l_tvin_test()
int mxc_v4l_tvin_test(void)主要内容如下:
全局的输出缓冲区:struct testbuffer output_buffers[4]
其中, struct testbuffer
{
unsigned char *start;
size_t offset;
unsigned int length;
};
struct v4l2_buffer output_buf;
for (i = 0; i < g_output_num_buffers; i++)
{
memset(&output_buf, 0, sizeof(output_buf));
output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
output_buf.memory = V4L2_MEMORY_MMAP;
output_buf.index = i;
if (ioctl(fd_output_v4l, VIDIOC_QUERYBUF, &output_buf) < 0)
{
printf("VIDIOC_QUERYBUF error\n");
return TFAIL;
}
output_buffers[i].length = output_buf.length;
output_buffers[i].offset = (size_t) output_buf.m.offset;
output_buffers[i].start = mmap (NULL, output_buffers[i].length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd_output_v4l, output_buffers[i].offset);
if (output_buffers[i].start == NULL) {
printf("v4l2 tvin test: output mmap failed\n");
return TFAIL;
}
}
以上内容就是VIDIOC_QUERYBUF查询输出缓冲区的内容并将其放入全局的输出缓冲帧数组output_buffers,对缓冲区起始地址进行内存映射
unsigned int i;
struct v4l2_buffer buf;
enum v4l2_buf_type type;
for (i = 0; i < g_capture_num_buffers; i++)
{
memset(&buf, 0, sizeof (buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd_capture_v4l, VIDIOC_QUERYBUF, &buf) < 0)
{
printf("VIDIOC_QUERYBUF error\n");
return TFAIL;
}
capture_buffers[i].length = buf.length;
capture_buffers[i].offset = (size_t) buf.m.offset;
capture_buffers[i].start = mmap (NULL, capture_buffers[i].length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd_capture_v4l, capture_buffers[i].offset);
memset(capture_buffers[i].start, 0xFF, capture_buffers[i].length);
}
同理,VIDIOC_QUERYBUF查询capture缓冲区的内容并将其放入全局的capture缓冲帧数组capture_buffers,对缓冲区起始地址进行内存映射
for (i = 0; i < g_capture_num_buffers; i++)
{
memset(&buf, 0, sizeof (buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.offset = capture_buffers[i].offset;
if (ioctl (fd_capture_v4l, VIDIOC_QBUF, &buf) < 0) {
printf("VIDIOC_QBUF error\n");
return TFAIL;
}
}
VIDIOC_QBUF将capture缓冲帧依次放入capture队列中准备开始获取图像
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl (fd_capture_v4l, VIDIOC_STREAMON, &type) < 0) {
printf("VIDIOC_STREAMON error\n");
return TFAIL;
}
VIDIOC_STREAMON开始获取图像流
循环中分为begin和next两部分:
begin:
if (ioctl(fd_capture_v4l, VIDIOC_G_STD, &id)) {
printf("VIDIOC_G_STD failed.\n");
return TFAIL;
}
if (id == g_current_std)
goto next;
else if (id == V4L2_STD_PAL || id == V4L2_STD_NTSC) {
type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(fd_output_v4l, VIDIOC_STREAMOFF, &type);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd_capture_v4l, VIDIOC_STREAMOFF, &type);
for (j = 0; j < g_output_num_buffers; j++)
{
munmap(output_buffers[j].start, output_buffers[j].length);
}
for (j = 0; j < g_capture_num_buffers; j++)
{
munmap(capture_buffers[j].start, capture_buffers[j].length);
}
if (v4l_capture_setup() < 0) {
printf("Setup v4l capture failed.\n");
return TFAIL;
}
if (v4l_output_setup() < 0) {
printf("Setup v4l output failed.\n");
return TFAIL;
}
if (prepare_output() < 0)
{
printf("prepare_output failed\n");
return TFAIL;
}
if (start_capturing() < 0)
{
printf("start_capturing failed\n");
return TFAIL;
}
i = 0;
printf("TV standard changed\n");
} else {
sleep(1);
/* Try again */
if (ioctl(fd_capture_v4l, VIDIOC_G_STD, &id)) {
printf("VIDIOC_G_STD failed.\n");
return TFAIL;
}
if (id != V4L2_STD_ALL)
goto begin;
printf("Cannot detect TV standard\n");
return 0;
}
begin部分其实内容不多,通过VIDIOC_G_STD再次获取v4l2_std_id与之前的id做对比,若相同则正式进入循环,若不同则重新建立capture和output并prepare_output和start_capturing,相当于对配置重新来一遍
next:
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd_capture_v4l, VIDIOC_DQBUF, &capture_buf) < 0) {
printf("VIDIOC_DQBUF failed.\n");
return TFAIL;
}
memset(&output_buf, 0, sizeof(output_buf));
output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
output_buf.memory = V4L2_MEMORY_MMAP;
if (i < g_output_num_buffers) {
output_buf.index = i;
if (ioctl(fd_output_v4l, VIDIOC_QUERYBUF, &output_buf) < 0)
{
printf("VIDIOC_QUERYBUF failed\n");
return TFAIL;
}
} else {
output_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
output_buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd_output_v4l, VIDIOC_DQBUF, &output_buf) < 0)
{
printf("VIDIOC_DQBUF failed\n");
return TFAIL;
}
}
memcpy(output_buffers[output_buf.index].start, capture_buffers[capture_buf.index].start, g_frame_size);
if (ioctl(fd_capture_v4l, VIDIOC_QBUF, &capture_buf) < 0) {
printf("VIDIOC_QBUF failed\n");
return TFAIL;
}
output_buf.timestamp.tv_sec = tv_start.tv_sec;
output_buf.timestamp.tv_usec = tv_start.tv_usec + (g_frame_period * i);
if (g_vdi_enable)
output_buf.field = g_tb ? V4L2_FIELD_INTERLACED_TB :
V4L2_FIELD_INTERLACED_BT;
if (ioctl(fd_output_v4l, VIDIOC_QBUF, &output_buf) < 0)
{
printf("VIDIOC_QBUF failed\n");
return TFAIL;
}
if (i == 1) {
type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(fd_output_v4l, VIDIOC_STREAMON, &type) < 0) {
printf("Could not start stream\n");
return TFAIL;
}
}
}
next部分的逻辑也不复杂,通过VIDIOC_DQBUF将capture和output缓冲帧分别取出保存在capture_buf和output_buf,再通过memcpy将capture映射的地址赋值给output,相当于将视频采集的数据传递给输出端,然后将capture和output缓冲区通过VIDIOC_QBUF重新放回队列,最后通过VIDIOC_STREAMON开启视频输出
type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(fd_output_v4l, VIDIOC_STREAMOFF, &type);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd_capture_v4l, VIDIOC_STREAMOFF, &type);
for (i = 0; i < g_output_num_buffers; i++)
{
munmap(output_buffers[i].start, output_buffers[i].length);
}
for (i = 0; i < g_capture_num_buffers; i++)
{
munmap(capture_buffers[i].start, capture_buffers[i].length);
}
close(fd_capture_v4l);
close(fd_output_v4l);
close(fd_fb);
关闭视频流,解除内存映射,关闭文件