DM6446的视频前端VPFE驱动之ioctl控制(视频缓存区,CCDC,decoder)解析之二

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。http://blog.youkuaiyun.com/gzzaigcn/article/details/7754066

欢迎和大家交流。qq:1037701636 email:200803090209@zjut.comgzzaigcn2012@gmail.com 

本文承接上文的主要内容,对视频缓存区队列的相关执行过程进行一个解析。先给出之前的应用层的调用流程

本文主要涉及到的命令为VIDIOC_QBUF,VIDIOC_DQBUF,VIDIOC_STREAMON。

 

1.VIDIOC_QBUF命令

这个命令实际的内容可理解为进行缓存区队列的入列操作。也许这样说比较抽象,但是看过源码后就很好理解。在源码中会调用videobuf_qbuf函数,其中主要由buf_prepare指针函数来完成缓存区队列的准备

[html]  view plain copy
  1. static int buffer_prepare(struct videobuf_queue *q,  
  2.               struct videobuf_buffer *vb, enum v4l2_field field)  
  3. {  
  4.     vpfe_obj *vpfe = &vpfe_device;  
  5.     unsigned int buf_size;  
  6.     dev_dbg(vpfe_dev, "\nstarting buffer_prepare");  
  7.     if (device_type == TVP5146) {  
  8.         buf_size = VPFE_TVP5146_MAX_FBUF_SIZE;  
  9.     } else {  
  10.         buf_size = VPFE_MT9T001_MAX_FBUF_SIZE;  
  11.     }  
  12.     if (vb->state == STATE_NEEDS_INIT) {  
  13.         vb->width = vpfe->vwin.width; //720  
  14.         vb->height = vpfe->vwin.height; //576  
  15.         vb->size = buf_size;  //216*4K  
  16.         vb->field = field;  //V4L2_FIELD_INTERLACED  
  17.     }  
  18.     vb->state = STATE_PREPARED;  
  19.     dev_dbg(vpfe_dev, "\nEnd of buffer_prepare");  
  20.     return 0;  
  21.   
  22. }  

我们可以发现,他将每一个缓存区的状态设置为了STATE_PREPARED。随后执行list_add_tail(&buf->stream,&q->stream);完成将缓存区的stream链表数据添加到整个缓存区队列的stream中。

2.VIDIOC_STREAMON命令

在完成之前的缓存区申请,已经mmap的相关操作之后,就会执行该命令行的系统调用,在这个命令行中,主要完成的是Dm6446的视频前端VPFE的相关硬件初始化,Tvp5416的初始化。

[html]  view plain copy
  1. case VIDIOC_STREAMON:  //启动视频流采集  
  2.         dev_dbg(vpfe_dev, "\nStarting VIDIOC_STREAMON ioctl");  
  3.         if (!fh->io_allowed) {  
  4.             ret = -EACCES;  
  5.             break;  
  6.         }  
  7.         if (vpfe->started) {  
  8.             ret = -EBUSY;  
  9.             break;  
  10.         }  
  11.         ret = videobuf_streamon(&vpfe->bufqueue);  
  12.         if (ret)  
  13.             break;  
  14.   
  15.         down_interruptible(&vpfe->lock);  
  16.         /* get the current and next frame buffers */  
  17.         /* we expect at least one buffer is in driver at this point */  
  18.         /* if not, error is returned */  
  19.         if (list_empty(&vpfe->dma_queue)) {  
  20.             ret = -EIO;  
  21.             break;  
  22.         }  
  23.         dev_dbg(vpfe_dev, "cur frame %x.\n",  
  24.             (unsigned int)vpfe->dma_queue.next);  
  25.         vpfe->nextFrm = vpfe->curFrm =  
  26.             list_entry(vpfe->dma_queue.next,  
  27.                    struct videobuf_buffer, queue);  
  28.         /* remove the buffer from the queue */  
  29.         list_del(&vpfe->curFrm->queue);  
  30.         vpfe->curFrm->state = STATE_ACTIVE;//确保有一个缓存区在缓存区队列中  
  31.   
  32.         if (device_type == TVP5146) {  
  33.             /* sense the current video input standard */  
  34.             tvp5146_ctrl(TVP5146_CONFIG, &vpfe->tvp5146_params);  //5146完成相关的配置  
  35.             frm_format = vpfe_device.ccdc_params_ycbcr.frm_fmt;  
  36.             image_window = vpfe_device.ccdc_params_ycbcr.win;  
  37.             /* configure the ccdc and resizer as needed   */  
  38.             /* start capture by enabling CCDC and resizer */  
  39.             ccdc_config_ycbcr(&vpfe->ccdc_params_ycbcr); //ccdc配置为YCbCr格式  
  40.         } else {  
  41.             frm_format = vpfe_device.ccdc_params_raw.frm_fmt;  
  42.             image_window = vpfe_device.ccdc_params_raw.win;  
  43.             /* configure the ccdc and resizer as needed   */  
  44.             /* start capture by enabling CCDC and resizer */  
  45.             ccdc_config_raw(&vpfe->ccdc_params_raw);  
  46.             /* enable internal timing generator */  
  47.             ccdc_vdhd_enable(TRUE);  
  48.         }  
  49.   
  50.         /* setup the memory address for the frame buffer */  
  51.         ccdc_setfbaddr(((unsigned long)(vpfe->curFrm->boff)));//32-bit SDRAM starting address for CCD controller output  
  52.         /* enable CCDC */  
  53.         vpfe->field_id = 0;//设为0  
  54.         vpfe->started = TRUE;  
  55.         vpfe->mode_changed = FALSE;  
  56.         vpfe->field_offset = (vpfe->vwin.height - 2) * vpfe->vwin.width;  
  57.         ccdc_enable(TRUE);//启动CCDC进行视频的采集  
  58.         up(&vpfe->lock);  
  59.         dev_dbg(vpfe_dev, "started video streaming.\n");  
  60.         break;  


首先会调用videobuf_streamon函数,在该函数中执行q->streaming = 1;//表示当前的缓存区队列已经处于视频流启动。随后进行缓存区链表的遍历,看当前缓存区是否已经处于STATE_PREPARED状态,因为之前已经执行VIDIOC_QBUF命令所以当前就会执行buffer_queue指针函数。

[html]  view plain copy
  1. static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)  
  2. {  
  3.     vpfe_obj *vpfe = &vpfe_device;  
  4.     /* add the buffer to the DMA queue */  
  5.     dev_dbg(vpfe_dev, "\nstarting buffer_queue");  
  6.     list_add_tail(&vb->queue, &vpfe->dma_queue);//视频缓存区添加到DMA队列  
  7.     vb->state = STATE_QUEUED;  
  8.     dev_dbg(vpfe_dev, "\nEnding buffer_queue");  
  9. }  


在这部分代码中主要实现,将当前的缓存区的链表数据加入到vpfe实体的dma链表头中,完成所谓的入列操作。并将当前状态设置为STATE_QUEUED。以此形成dma带领多个缓存区实体(以链表的形式存在)如下图:

最终会在内核形成形成这样一个链表形式。

在VIDIOC_STREAMON命令中所做的内容还有获取当前视频帧缓存区和下一个帧的缓存区,使curfrm=nextfrm,随后就将Buffer[0]的queue从dma_queue的链表中去除,主要用于在中断中对当前数据帧所存放的缓存区进行调度。应该来说这是整个缓存区合理使用的核心所做。过会再中断函数中介绍。

当然CCDC和TVP5416的相关初始化也在该命令行完成,包括各种硬件寄存器的设置,这些是VPFE能否正常工作的前提所在。完成好相关的参数设置后,使用ccdc_enable(true )启动CCDC模块,开始视频流的采集。

3.依据我的分析,启动CCDC后会立马产生一个偶场数据采集的中断,CCDC的中断VINT0(在中断中优先级最高),中断的回调函数如下:

[html]  view plain copy
  1. static irqreturn_t vpfe_isr(int irq, void *dev_id, struct pt_regs *regs)  
  2. {  
  3.     vpfe_obj *vpfe = &vpfe_device;  
  4.     int fid;  
  5.     unsigned long jiffies_time = get_jiffies_64();  
  6.     struct timeval timevalue;  
  7.     int val = 0;  
  8.   
  9.     val = ccdc_sbl_reset();  
  10.   
  11.     /*Convert time representations between jiffies and struct timeval */  
  12.     jiffies_to_timeval(jiffies_time, &timevalue);  
  13.   
  14.     dev_dbg(vpfe_dev, "\nStarting Davinci_vpfe\vpfe_isr...");  
  15.     if (frm_format == CCDC_FRMFMT_INTERLACED) {  
  16.         /* check which field we are in hardware */  
  17.         fid = ccdc_getfid();  //获取当前采集的场  
  18.         /* switch the software maintained field id */  
  19.         vpfe->field_id ^= 1;//field_id为1则结果为0,即vpfe->field_id=0为偶场,反之为1  
  20.         dev_dbg(vpfe_dev, "field id = %x:%x.\n", fid, vpfe->field_id);  
  21.         if (fid == vpfe->field_id) { /* we are in-sync here,continue */  
  22.             if (fid == 0) { /* even field */  //偶场  
  23.                 /*  One frame is just being captured. If the   
  24.                  * next frame is available, release the current   
  25.                  * frame and move on   
  26.                  */  
  27.                 if (vpfe->curFrm != vpfe->nextFrm) {  
  28.                     /* Copy frame capture time value in   
  29.                      * curFrm->ts   
  30.                      */  
  31.                     vpfe->curFrm->ts = timevalue;  
  32.                     vpfe->curFrm->state = STATE_DONE;  
  33.                     wake_up_interruptible(&vpfe->  
  34.                                   curFrm->done);  
  35.                     vpfe->curFrm = vpfe->nextFrm;  
  36.                 }  
  37.                 /* based on whether the two fields are stored    
  38.                  * interleavely or separately in memory,   
  39.                  * reconfigure the CCDC memory address   
  40.                  */  
  41.                 if (vpfe->field == V4L2_FIELD_SEQ_TB) {  
  42.                     u32 addr =  
  43.                         vpfe->curFrm->boff +  
  44.                         vpfe->field_offset;  
  45.                     ccdc_setfbaddr((unsigned long)  
  46.                                addr);  
  47.                 }  
  48.             } else if (fid == 1) {  /* odd field */  
  49.                 /* if one field is just being captured */  
  50.                 /* configure the next frame */  
  51.                 /* get the next frame from the empty queue */  
  52.                 /* if no frame is available, */  
  53.                 /* hold on to the current buffer */  
  54.                 if (!list_empty(&vpfe->dma_queue)       //奇场处理  
  55.                     && vpfe->curFrm == vpfe->nextFrm) {  
  56.                     vpfe->nextFrm =  
  57.                         list_entry(vpfe->dma_queue.  
  58.                                next, struct  
  59.                                videobuf_buffer, queue);  
  60.                     list_del(&vpfe->nextFrm->queue);  
  61.                     vpfe->nextFrm->state = STATE_ACTIVE;  
  62.                     ccdc_setfbaddr((unsigned long)  
  63.                                vpfe->nextFrm->boff);//奇数场奇数即一帧图像采集结束,更换ccdc采集的数据存放地址  
  64.                 }  
  65.                 if (vpfe->mode_changed) {  
  66.                     ccdc_setwin(&image_window,  
  67.                             frm_format, 2);  
  68.                     /* update the field offset */  
  69.                     vpfe->field_offset =  
  70.                         (vpfe->vwin.height -  
  71.                          2) * vpfe->vwin.width;  
  72.                     vpfe->mode_changed = FALSE;  
  73.                 }  
  74.             }  
  75.         } else if (fid == 0) {  /* even field */  
  76.             /* recover from any hardware out-of-sync due to */  
  77.             /* possible switch of video source              */  
  78.             /* for fid == 0, sync up the two fids           */  
  79.             /* for fid == 1, no action, one bad frame will  */  
  80.             /* go out, but it is not a big deal             */  
  81.             vpfe->field_id = fid;  
  82.         }  
  83.     } else if (frm_format == CCDC_FRMFMT_PROGRESSIVE) {  
  84.   
  85.         dev_dbg(vpfe_dev, "\nframe format is progressive...");  
  86.         if (vpfe->curFrm != vpfe->nextFrm) {  
  87.             /* Copy frame capture time value in curFrm->ts */  
  88.             vpfe->curFrm->ts = timevalue;  
  89.             vpfe->curFrm->state = STATE_DONE;  
  90.             wake_up_interruptible(&vpfe->curFrm->done);  
  91.             vpfe->curFrm = vpfe->nextFrm;  
  92.         }  
  93.   
  94.     }  
  95.     dev_dbg(vpfe_dev, "interrupt returned.\n");  
  96.     return IRQ_RETVAL(1);  

依旧我对这段中断程序的理解来说,先是执行偶场产生的中断(一帧数据VPFE会产生一个中断,但是隔行扫描时会出现奇偶两场的中断)所以在中断程序中会出现if的判断。

比起逐行的模式复杂很多。在中断程序中,偶场先到,然后会判断当前视频的帧缓存区是不是和下一个是一样(在启动前已经将当前的curfrm=nextfrm),在奇数场中断到来时,首先查看dma_queue链表是否为空并判断当前和下一个缓存区帧是否一样,如果一样,就获取链表中的缓存区buffer[1]给nextfrm,去除当前缓存区在dma_queue的位置。同时对下一帧数据在DDR中的存储地址进行修改。当当前帧完成,也就是一下针对偶场开始时,会发现nextfrm不等于curfrm(即第一个缓存区已经存储了第一帧数据),则把存储了第一帧数据的缓存区buffer[0]设置为STATE_DONE,随后curfrm=nextfrm=buffer[1]。以此循环进行。虽然我们会发现出现dma_queue中的内容消失了,也就是说缓存区都放好了数据之后,都不在该链表中。后来才发现还会再做入列操作。

 

4.VIDIOC_DQBUF命令


[html]  view plain copy
  1. int  
  2. videobuf_dqbuf(struct videobuf_queue *q,  
  3.            struct v4l2_buffer *b, int nonblocking)//nonblocking=0,缓存区出队列  
  4. {  
  5.     struct videobuf_buffer *buf;  
  6.     int retval;  
  7.   
  8.     down(&q->lock);  
  9.     retval = -EBUSY;  
  10.     if (q->reading)  
  11.         goto done;  
  12.     retval = -EINVAL;  
  13.     if (b->type != q->type)  
  14.         goto done;  
  15.     if (list_empty(&q->stream))   
  16.         goto done;  
  17.     buf = list_entry(q->stream.next, struct videobuf_buffer, stream);//遍历查找下一个缓存区  
  18.     retval = videobuf_waiton(buf, nonblocking, 1);//阻塞处理  
  19.     if (retval < 0)  
  20.         goto done;  
  21.     switch (buf->state) {  
  22.     case STATE_ERROR:  
  23.         retval = -EIO;  
  24.         /* fall through */  
  25.     case STATE_DONE:  
  26.         if(q->pci)  
  27.             videobuf_dma_pci_sync(q->pci,&buf->dma);  
  28.         buf->state = STATE_IDLE;  
  29.         break;  
  30.     default:  
  31.         retval = -EINVAL;  
  32.         goto done;  
  33.     }  
  34.     list_del(&buf->stream);//从链表中去除当前buf的链表  
  35.     memset(b,0,sizeof(*b));  
  36.     videobuf_status(b,buf,q->type);  
  37.   
  38.  done:  
  39.     up(&q->lock);  
  40.     return retval;  
  41. }  

其实按照函数的执行流程,该函数应该先于中断函数执行,该函数先生遍历stream链表获取当前的缓存区这里理解为buffer[0]。然后进入写数据以阻塞的方式进行,启动等待队列,直到该进程被再次调度。而该等待队列的唤醒,恰恰在中断中进行,也就是一帧数据写入完成到buffer[0]时进行,同时在完成这一步之后,随后在应用程序中就会再次调用VIDIOC_QBUF这个命令,完成对当前以完成一帧数据存储的缓存区buffer[0]重新加入到dma_queue中,这就顺利解决了缓存区数据的循环写入。不会像之前理解的那样以为只有出列操作。总结就是,先启动后,缓存区出列操作,进入等待队列阻塞,直到完成一帧数据的写入哦,应用程序再对当前缓存区实现入列操作。故形成循环。

        以上3篇博文都为自己阅读linux下面V4L2视频驱动架构下的源码所得,全部为自己总结。为整个项目的顺利进行打好基础。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值