Davinci视频采集驱动文档
概述
Davinci的视频采集接口的驱动涉及到内容包括I2C,AD芯片,V4L2,视频采集等内容。下面主要分成视频采集接口描述,I2C和A/D芯片,V4L2采集驱动以及V4L2应用程序编程。
名词解释:
A-low:
YUV: 在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄像机进行摄像,然后把摄得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。
Auto focus:
Auto white balance: ut
Auto exposure:
ITU-R BT.656:
BT.656并行数据结构:
BT.656并行接口除了传输4:2:2的YCbCr视频数据流外,还有行、列同步所用的控制信号。如图所示,一帧图像数据由一个625行、每行1 728字节的数据块组成。其中,23~311行是偶数场视频数据,336~624行是奇数场视频数据,其余为垂直控制信号。
BT.656每行的数据结构如图所示。
图中,每行数据包含水平控制信号和YCbCr--视频数据信号。视频数据信号排列顺序为Cb-Y-Cr-Y。每行开始的288字节为行控制信号,开始的4字节为EAV信号(有效视频结束),紧接着280个固定填充数据,最后是4字节的SAV信号(有效视频起始)。
SAV和EAV信号有3字节的前导:FF、FF、00;最后1字节XY表示该行位于整个数据帧的位置及如何区分SAV、EAV。XY字节各比特位含义见图。
图中,最高位bit7为固定数据1;F=0表示偶数场,F=1表示奇数场;V=0表示该行为有效视频数据,V=1表示该行没有有效视频数据;H=0表示为SAV信号,H=1表示为EAV信号;P3~P0为保护信号,由F、V、H信号计算生成;P3=V异或H;P2=F异或H;P1=F异或V;P0=F异或V异或H。
CCIR 656:
REC656:
Optical black clamp:
Low-Pass Filter:
Culling:
CFA:
Dark Fram write:
SDTV/LDTV/HDTV:数字电视(Digital TV)包括数字HDTV、数字SDTV和数字LDTV三种。三者区别主要在于图像质量和信道传输所占带宽的不同。从视觉效果来看,数字 HDTV(1000线以上)为高清晰度电视(High Definition Television)的简称,图象质量可达到或接近35mm宽银幕电影的水平;SDTV(500-600线)即标准清晰度电视,主要是对应现有电视的分辨率量级,其图象质量为演播室水平;LDTV(200-300线)即普通清晰度电视,主要是对应现有VCD的分辨率量级。因为电视全数字化是今后的趋势,所以目前提HDTV以及SDTV、LDTV如无特别说明,均指全数字体制。
一 视频采集接口VPFE
Davinci芯片提供一个视频采集接口VPFE主要可以接CMOS/CCD/video decoder等,还有一个视频后端处理接口VPBE主要是接视频输出设备。这里主要讨论视频采集接口VPFE。VPFE接口的结构框图如下图所示:
涉及的模块主要有:
· CCDC控制器
· Preview 预览引擎
· Resizer模块
· H3A模块
· Histogram模块
1. CCDC控制器
CCDC控制器主要从CMOS/CCD中接收原始的视频数据,并且可以支持多种YUV视频格式。
2. preview预览引擎
预览引擎主要是传输从CMOS/CCD中原始的视频数据到YCbCr 422的显示设备或者编码器。通常预览引擎的数据输出到外部的显示/压缩设备如NTSC/PAL模拟编码器或者LCD上。
3. Resize模块
Resizer模块可以对图像进行裁剪和缩放功能。
二 I2C和A/D芯片
Davinci内置了I2C控制器和I2C总线,一般视频前端处理的A/D芯片都是挂载在I2C总线上,通过Davinci的I2C控制器对A/D芯片的I2C从设备进行读写操作。
Davinci的I2C控制器的内部框图如下图所示:
这里I2C只有2根信号线:SCL和SDA。SCL信号线产生clock时钟,SDA数据线通过内部的ICXSR/ICDXR和ICRSR/ICDRR发生和接收数据。
对于I2C在发送和接收数据的时候会产生START位和STOP位。当SCL为高时,SDA由高变低的时候产生START位;当SCL为低时,SDA由低变高的时候产生STOP位。另外I2C支持的数据格式有:7-bit地址模式,10-bit地址模式和Free data格式模式。
I2C外设可以产生下面几种中断事件:
I2C中断
发生事件
丢失仲裁中断AL
当I2C仲裁丢失或者非法的START/STOP位发生
无应答中断NACK
当I2C从接收器中没有接收到应答信号
寄存器可以访问中断ARDY
当先前的编程地址,数据和命令已经执行和状态位已经更新了,I2C产生ARDY中断。这个中断让CPU知道I2C寄存器已经可以访问了。
接收中断ICRINT,ICRRDY
当ICRSR寄存器接收的数据已经拷贝到ICDRR寄存器中的时候发送接收中断。可以让CPU来查询ICRRDY位来从ICDRR中读接收数据。
发送中断ICXINT和ICXRDY
可以让CPU来查询ICXRDY位来往ICDXR中发送数据。
停止中断SCD
当STOP位发生
AAS中断
当I2C发现它的slave地址或者地址全为0。
2.1 Davinci的I2C控制器驱动
Davinci的I2C控制器驱动包括I2C总线注册,I2C总线读写操作,I2C总线中断处理几个部分,主要代码集中在/driver/i2c/busses/i2c-davinci.c。
把I2C控制器注册到I2C总线上是把davinci的I2C适配器的数据结构注册到linux 内核的I2C子系统中。I2C适配器的数据结构struct i2c_davinci_adap。
static struct i2c_adapter i2c_davinci_adap = {
.owner = THIS_MODULE,
.name = "DAVINCI I2C adapter",
.id = I2C_ALGO_EXP,
.algo = &i2c_davinci_algo,
.algo_data = NULL,
.client_register = NULL,
.client_unregister = NULL,
};
static int __init i2c_davinci_init(void)
{
…………………….
init_waitqueue_head(&i2c_davinci_dev.cmd_wait);
status = (int)request_region(I2C_BASE, I2C_IOSIZE, MODULE_NAME);
i2c_set_adapdata(&i2c_davinci_adap, &i2c_davinci_dev);
status = i2c_add_adapter(&i2c_davinci_adap);
request_irq(IRQ_I2C, i2c_davinci_isr, 0, "i2c",&i2c_davinci_dev);
…………………..
driver_register(&davinci_i2c_driver)
platform_device_register(&davinci_i2c_device)
return 0;
}
1. 初始化等待队列i2c_davinci_dev.cmd_wait
2. 为I2C寄存器组分配地址空间。这里可能有问
????????
3. 把 davinci的I2C的适配器挂入linux内核的I2C子系统中。
4. 注册I2C中断。
5. 注册linux设备模型。
I2C的读写操作主要的数据结构struct i2c_davinci_alog:
static struct i2c_algorithm i2c_davinci_algo = {
.name = "DAVINCI I2C algorithm",
.id = I2C_ALGO_EXP,
.master_xfer = i2c_davinci_xfer,
.smbus_xfer = NULL,
.slave_send = NULL,
.slave_recv = NULL,
.algo_control = NULL,
.functionality = i2c_davinci_func,
};
I2C的主要操作在i2c_davinci_xfer函数中。这个函数主要实现代码片断如下:
static int i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
…………………..
if ((ret = i2c_davinci_wait_for_bb(1, adap)) < 0)
return ret;
…………………..
for (count = 0; count < num; count++) {
i2c_davinci_xfer_msg(adap, &msgs[count],(count == (num - 1)));
}
…………………..
}
1. 判断I2C是否在忙
2. 通过i2c_davinci_xfer_msg函数来实现。
i2c_davinci_xfer > i2c_davinci_xfer_msg
static int 2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
{
dev->regs->icsar = msg->addr; -----------------------------------1
if (msg->len == 0) { ------------------------------------------------2
dev->buf = &zero_byte;
dev->buf_len = 1;
} else {
dev->buf = msg->buf;
dev->buf_len = msg->len;
}
dev->regs->iccnt = dev->buf_len;
at = dev->regs->icivr; ----------------------------------------------------3
if (msg->flags & I2C_M_RD) -------------------------------------------------------4
dev->regs->icimr |= DAVINCI_I2C_ICIMR_ICRRDY_MASK;
else
dev->regs->icimr |= DAVINCI_I2C_ICIMR_ICXRDY_MASK;
dev->regs->icmdr = flag; --------------------------------------- 5
wait_event_timeout (dev->cmd_wait, dev->cmd_complete, DAVINCI_I2C_TIMEOUT);---6
……………..
}
1. 设置slave地址。
2. 设置数据包的长度。
3. 读ICIVR中断向量寄存器表示清0。
4. 使能接收和发送寄存器。
5. 设置ICMDR寄存器。设置IRS,MST和STT位,具体含义看datasheet。
6. 等待数据读写完成。
当读写的数据完成的时候,会产生中断,进入中断服务例程ISR。
static irqreturn_t 2c_davinci_isr(int this_irq, void *dev_id, struct pt_regs *reg)
{
while ((stat = dev->regs->icivr) != 0) { ------------------------------------------------1
switch(stat) {
case DAVINCI_I2C_ICIVR_INTCODE_RDR: -----------------------------------2
if (dev->buf_len) {
*dev->buf++ = dev->regs->icdrr;
dev->buf_len--;
if (dev->buf_len) {
continue;
} else {
dev->regs->icimr &=
~DAVINCI_I2C_ICIMR_ICRRDY_MASK;
}
}
break;
case DAVINCI_I2C_ICIVR_INTCODE_TDR: -----------------------------------3
if (dev->buf_len) {
dev->regs->icdxr = *dev->buf++;
dev->buf_len--;
if (dev->buf_len)
continue;
else {
dev->regs->icimr &=
~DAVINCI_I2C_ICIMR_ICXRDY_MASK;
}
}
break;
case DAVINCI_I2C_ICIVR_INTCODE_RAR: --------------------------------------------4
/*i2c_warn("i2c: RAR detected");*/
dev->regs->icstr |= DAVINCI_I2C_ICSTR_ARDY_MASK;
i2c_davinci_complete_cmd(dev);
break;
}
}
}
1. 当有中断发生时候,icivr中断向量寄存器会告诉我们发生了那个中断。
2. 当接收中断发生的时候,从ICDRR寄存器中取数据,取完后清中断请求。
3. 发送中断处理。
4. 这里的中断说明先前的寄存器读写已经完成。这时候会唤醒等待队列i2c_davinci_dev.cmd_wait.
注意:中断处理程序要在ARDY中断(或者异常中断发生)才会唤醒等待队列。
2.2 ADV7180芯片驱动
ADV7180芯片的驱动主要是通过I2C来操作ADV7180本身的寄存器来完成工作的。首先需要把ADV7180做为I2C的从设备注册到I2C总线上。
ADV7180芯片寄存器的读写都是通过I2C来操作的。I2C读写操作如下图所示:
对于写操作,开始位START后,先写入7bit slave地址和一个LSB位,在ACK答应位之后,写入SUB地址和数值,止到STOP位。也就是一个START位后,写入地址和n个数据止到STOP位。
对于读操作,开始位START后,先写入7bit slave地址和一个LSB位,在ACK应答位之后,写入SUB ADDR和等待一个应答位。在开始另个START位之后,才是读出的数据。
static int i2c_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
int err = 0;
struct i2c_msg msg[1];
unsigned char data[2];
if (!client->adapter) {
err = -ENODEV;
} else {
msg->addr = client->addr;
msg->flags = 0;
msg->len = 2; //这里长度是2,即1个START位,发送2个数据,一个是reg,另一个是val。
msg->buf = data;
data[0] = reg;
data[1] = val;
err = i2c_transfer(client->adapter, msg, 1);
}
return err;
}
static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 * val)
{
int err = 0;
struct i2c_msg msg[1];
unsigned char data[1];
if (!client->adapter) {
err = -ENODEV;
} else {
msg->addr = client->addr;
msg->flags = 0;
msg->len = 1;
msg->buf = data;
data[0] = reg;
err = i2c_transfer(client->adapter, msg, 1);
if (err >= 0) {
msg->flags = I2C_M_RD;
err = i2c_transfer(client->adapter, msg, 1);
if (err >= 0) {
*val = data[0];
}
}
}
return err;
}
三 V4L2采集驱动
对于视频采集,为了让上层应用程序能兼容很多不同的采集卡或者摄像头等设备,内核提供了一组同一的标准接口API,即V4L接口。应用程序只需要按照V4L接口标准来编写程序,那么就可以支持所以采用V4L驱动的设备。V4L是一套针对视频采集,音频radio等音视频设备的标准API。
在V4L的基础上,从Linux2.5内核开始发展了V4L2标准。V4L2的可扩展性和灵活性都得到大大的提高,并且能够支持更多的设备。V4L2对VL4进行了彻底的改造,很多关键的API发生了变化,所以V4L2和V4L不兼容。
Davinci的视频采集驱动采用V4L2 API来编写驱动。V4L2 API支持3种采集的方法:read/write, MMAP和USER POINTERS。我们的驱动只支持mmap的方式。
V4L2 API除了传统的字符设备的方法集外,还有很多的操作是特定的ioctl操作来实现的。对于mmap采集来说,比较常用的几个ioctl操作如下:
Ioctl操作
用法
VIDIOC_QUERYCAP
查询设备参数
VIDIOC_CROPCAR
设备图像裁剪和缩放的能力
VIDIOC_S_CROP
获取或者设置当前裁剪方框的大小
VIDIOC_REQBUFS
初始化mmap映射的内存
VIDIOC_QUERYBUFS
内存初始化后查询buffer的状态
VIDIOC_QBUFS
把buffer挂入驱动incoming队列
VIDIOC_DQBUFS
从驱动outcoming队列中取buffer
VIDIOC_STREAMON
开始capture
VIDIOC_STREAMOFF
结束capture
1. VIDIOC_REQBUFS初始化buffer
这个ioctl操作主要目的是初始化mmap需要用到的buffers。V4L2为了简化驱动编程,抽象了一个buffer的管理buffer的文件/driver/video_buffer.c 文件中。这里会调用到video_buffer.c文件中相关函数来完成buffers的初始化。
· 初始化1个video_queue->stream队列。
· 初始化1个vpfe->dma_queue队列。
· 通过__get_free_pages来配置buffers,这里要分配几个buffer,可以由用户程序来控制。
· 分配struct videobuf_buffer,n个地址空间。
2. Mmap操作
这里mmap操作调用video-buffer.c文件中video_mmap_mapper()函数来实现。对于mmap操作,驱动程序需要为映射的地址范围建立合适的页表,这里主要通过 remap_pfn_range和nopage操作来实现。
3. VIDIOC_QBUF
这个ioctl操作主要实现初始化的时候把空buffers挂入queue->strream队列中和vpfe->dma_queue队列中;当应用程序读了1个buffer数据之后,把这个buffer归还。
4. VIDIOC_STEAMON:
这个ioctl操作主要是开始capture操作。
· 首先调用videobuf_streamon()函数。遍历queue->stream队列,找到组成队列的大结构struct videobuf_buffer。
· 从vpfe->dma_queue队列中取出队列成员vpfe->nextFrm = vpfe->curFrm。
· 设置vpfe->curFrm->state = STATE_ACTIVE。
· 设置AD芯片和配置CCDC控制
· 设置CCDC的输入地址为vpfe->curFrm->boff
· 使能CCDC控制器。
5. VIDIOC_DQBUF:
应用程序通过这个ioctl调用来获取那个buffer数据已经准备好了,从驱动返回v4l2_buffer->index值。
· 从queue->stream队列中取一个video_buffer成员。
· 通过videobuf_waiton函数进行阻塞等待。
· 中断处理之后,把这个video_buffer成员从队列中删除,然后把video_buffer->index返回用户空间。
6. 中断处理
中断处理要注意“场“的概念。因为视频输入源的摄像头有分PROGRESSIVE和INTERLACED之分。PROGRESSIVE的摄像头一帧只有一场(field),而INTERLACED的摄像头一帧有2场,其中field=1即偶场,先执行,然后才是field=0的奇场,当奇场完成是需要唤醒等待队列,然后VIDIOC_DQBUF调用返回参数完成数据的读取。
下面是INTERLACED的摄像头的中断处理过程:
if (fid == 1) {
if (!list_empty(&vpfe->dma_queue)
&& vpfe->curFrm == vpfe->nextFrm) {
vpfe->nextFrm = list_entry(vpfe->dma_queue.next, --------------------1
struct videobuf_buffer, queue);
list_del(&vpfe->nextFrm->queue);
vpfe->nextFrm->state = STATE_ACTIVE;
ccdc_setfbaddr(
(unsigned long)vpfe->nextFrm->boff);
}
if (fid == 0) { -------------------------------------------------2
if (vpfe->curFrm != vpfe->nextFrm) {
vpfe->curFrm->state = STATE_DONE;
wake_up_interruptible(&vpfe->curFrm->done);
vpfe->curFrm = vpfe->nextFrm;
}
1. 一帧数据,一般先是field=1的偶场先完成。在偶场中,取出下一帧buffer,并设置下一帧的ccdc输出地址。
2. 接着是field=0的奇场完成,这时候把当前buffer的状态标记为STATE_DONE,并唤醒等待队列,这时候VIDIOC_DQBUF调用返回v4l2-buffer->index参数,用户程序就可以读取数据了。最后把下一帧设置成当前帧。
四.V4L2应用程序编程
V4L2应用程序编程主要还是需要按照V4L2 API来完成。
1. 设备初始化
if (-1 == ioctl (fd, VIDIOC_QUERYCAP, &cap)) {
if (EINVAL == errno) {
fprintf (stderr, "%s is no V4L2 device\n",
dev_name);
exit (EXIT_FAILURE);
} else {
errno_exit ("VIDIOC_QUERYCAP");
}
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf (stderr, "%s is no video capture device\n",
dev_name);
exit (EXIT_FAILURE);
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf (stderr, "%s does not support streaming i/o\n",
dev_name);
exit (EXIT_FAILURE);
}
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 765
fmt.fmt.pix.height = 576
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
errno_exit ("VIDIOC_S_FMT");
2. buffer请求和mmap系统调用
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno) {
fprintf (stderr, "%s does not support "
"memory mapping\n", dev_name);
exit (EXIT_FAILURE);
} else {
errno_exit ("VIDIOC_REQBUFS");
}
}
if (req.count < 2) {
fprintf (stderr, "Insufficient buffer memory on %s\n",
dev_name);
exit (EXIT_FAILURE);
}
buffers = calloc (req.count, sizeof (*buffers));
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
errno_exit ("VIDIOC_QUERYBUF");//buf.m.offset
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap (NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit ("mmap");
}
3. select系统调用来查询buffer是否可读
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds);
FD_SET (fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select (fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit ("select");
4. 读数据
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
exit();
}
assert (buf.index < n_buffers);
process_image (buffers[buf.index].start);
if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
注意:这里的VIDIOC_DQBUF调用主要是从驱动中返回v4l2_buffer->index这个参数,有了这个参数就知道那个buffer可以读了。通过process_image读出数据。数据读完之后,需要调用VIDIOC_QBUF来把buffer返回给驱动。