一、应用开启Stream on
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
printf ("VIDIOC_STREAMON failed\n");
二、驱动处理流程
先看__video_do_ioctl, 根据cmd直接调用ioctl_ops的vidioc_streamon
static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
void *fh = file->private_data;
struct v4l2_fh *vfh = NULL;
struct v4l2_format f_copy;
int use_fh_prio = 0;
long ret = -EINVAL;
... ...
case VIDIOC_STREAMON:
{
enum v4l2_buf_type i = *(int *)arg;
if (!ops->vidioc_streamon)
break;
dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
//直接调用video_device.ioctrl_ops.vidioc_streamon
ret = ops->vidioc_streamon(file, fh, i);
break;
}
... ...
}
进入看下vidioc_streamon
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct vivi_dev *dev = video_drvdata(file);
return vb2_streamon(&dev->vb_vidq, i);
}
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{
struct vb2_buffer *vb;
int ret;
if (q->fileio) {
dprintk(1, "streamon: file io in progress\n");
return -EBUSY;
}
if (type != q->type) {
dprintk(1, "streamon: invalid stream type\n");
return -EINVAL;
}
if (q->streaming) {
dprintk(1, "streamon: already streaming\n");
return -EBUSY;
}
/*
* Cannot start streaming on an OUTPUT device if no buffers have
* been queued yet.
*/
if (V4L2_TYPE_IS_OUTPUT(q->type)) {
if (list_empty(&q->queued_list)) {
dprintk(1, "streamon: no output buffers queued\n");
return -EINVAL;
}
}
/*
* Let driver notice that streaming state has been enabled.
*/
//前面判断都跳过,直接切入start_streaming
ret = call_qop(q, start_streaming, q);
if (ret) {
dprintk(1, "streamon: driver refused to start streaming\n");
return ret;
}
//将vb2_queue->streaming状态赋值为1
q->streaming = 1;
/*
* If any buffers were queued before streamon,
* we can now pass them to driver for processing.
*/
//在之前我们已经QBUF了,所以queued_list链表不为空,获取vb2_buffer
list_for_each_entry(vb, &q->queued_list, queued_entry)
__enqueue_in_driver(vb);
dprintk(3, "Streamon successful\n");
return 0;
}
EXPORT_SYMBOL_GPL(vb2_streamon);
start_streaming
static int start_streaming(struct vb2_queue *vq)
{
struct vivi_dev *dev = vb2_get_drv_priv(vq);
dprintk(dev, 1, "%s\n", __func__);
//start_streaming也没有做什么,直接调用vivi_start_generating
return vivi_start_generating(dev);
}
static int vivi_start_generating(struct vivi_dev *dev)
{
struct vivi_dmaqueue *dma_q = &dev->vidq;
dprintk(dev, 1, "%s\n", __func__);
/* Resets frame counters */
//复位一些值
dev->ms = 0;
dev->mv_count = 0;
dev->jiffies = jiffies;
dma_q->frame = 0;
dma_q->ini_jiffies = jiffies;
//创建启动vivi_thread线程,这个vivi_thread线程里面做了数据buffer的填充
dma_q->kthread = kthread_run(vivi_thread, dev, dev->v4l2_dev.name);
if (IS_ERR(dma_q->kthread)) {
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return PTR_ERR(dma_q->kthread);
}
/* Wakes thread */
//唤醒dma_q->wq里面的vivi_thread线程
wake_up_interruptible(&dma_q->wq);
dprintk(dev, 1, "returning from %s\n", __func__);
return 0;
}
vivi_thead
static int vivi_thread(void *data)
{
struct vivi_dev *dev = data;
dprintk(dev, 1, "thread started\n");
//设置内核线程可以冻结标志,不知道为什么要这么设置?
set_freezable();
//死循环调用vivi_sleep函数,除非该线程应该停止,才会跳出循环
for (;;) {
vivi_sleep(dev);
if (kthread_should_stop())
break;
}
dprintk(dev, 1, "thread: exit\n");
return 0;
}
vivi_sleep, 这个名字觉得起的不是很好,看代码是先填充buffer再休眠。
static void vivi_sleep(struct vivi_dev *dev)
{
struct vivi_dmaqueue *dma_q = &dev->vidq;
int timeout;
//创建waitqueue
DECLARE_WAITQUEUE(wait, current);
dprintk(dev, 1, "%s dma_q=0x%08lx\n", __func__,
(unsigned long)dma_q);
//将waitqueue加入到等待队列dma_q->wq里面去
add_wait_queue(&dma_q->wq, &wait);
if (kthread_should_stop())
goto stop_task;
/* Calculate time to wake up */
//计算唤醒时间,这里是一帧视频时间
timeout = msecs_to_jiffies(frames_to_ms(1));
//vivi_thread_tick主要任务是填充buffer
vivi_thread_tick(dev);
//将当前线程调度出去,并在timeout时间到了后唤醒执行
schedule_timeout_interruptible(timeout);
stop_task:
//将wait从dma_q->wq中删除
remove_wait_queue(&dma_q->wq, &wait);
//不太懂这里的作用
try_to_freeze();
}
vivi_thread_tick
static void vivi_thread_tick(struct vivi_dev *dev)
{
struct vivi_dmaqueue *dma_q = &dev->vidq;
struct vivi_buffer *buf;
unsigned long flags = 0;
dprintk(dev, 1, "Thread tick\n");
spin_lock_irqsave(&dev->slock, flags);
//理论上第一次进入这个函数dma_q->active是空的,所以后面直接返回
//要等到vb2_streamon中执行完__enqueue_in_driver(vb);后dma_q->active不为空,才能往下执行。
if (list_empty(&dma_q->active)) {
dprintk(dev, 1, "No active queue to serve\n");
goto unlock;
}
//从dma_q->active中获取待填充的vivi_buffer
buf = list_entry(dma_q->active.next, struct vivi_buffer, list);
list_del(&buf->list);
//获取当前时间
do_gettimeofday(&buf->vb.v4l2_buf.timestamp);
/* Fill buffer */
//填充buffer
vivi_fillbuff(dev, buf);
dprintk(dev, 1, "filled buffer %p\n", buf);
//设置vb2_buffer相应标志为VB2_BUF_STATE_DONE
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index);
unlock:
spin_unlock_irqrestore(&dev->slock, flags);
}
vivi_fillbuff
static void vivi_fillbuff(struct vivi_dev *dev, struct vivi_buffer *buf)
{
int wmax = dev->width;
int hmax = dev->height;
struct timeval ts;
//获取到buffer的内存地址,用于存放视频数据
void *vbuf = vb2_plane_vaddr(&buf->vb, 0);
unsigned ms;
char str[100];
int h, line = 1;
if (!vbuf)
return;
//dev->line存放着需要输出的数据,数据是在之前precalculate_line(dev)函数中处理好的
//通过memcpy赋值到vbuf
for (h = 0; h < hmax; h++)
memcpy(vbuf + h * max * 2, dev->line + (dev->mv_count % wmax) * 2, wmax * 2);
/* Updates stream time */
dev->ms += jiffies_to_msecs(jiffies - dev->jiffies);
dev->jiffies = jiffies;
ms = dev->ms;
snprintf(str, sizeof(str), " %02d:%02d:%02d:%03d ",
(ms / (60 * 60 * 1000)) % 24,
(ms / (60 * 1000)) % 60,
(ms / 1000) % 60,
ms % 1000);
//下面gen_text主要是在每一帧图片上显示一些文字信息
gen_text(dev, vbuf, line++ * 16, 16, str);
snprintf(str, sizeof(str), " %dx%d, input %d ",
dev->width, dev->height, dev->input);
gen_text(dev, vbuf, line++ * 16, 16, str);
mutex_lock(&dev->ctrl_handler.lock);
snprintf(str, sizeof(str), " brightness %3d, contrast %3d, saturation %3d, hue %d ",
dev->brightness->cur.val,
dev->contrast->cur.val,
dev->saturation->cur.val,
dev->hue->cur.val);
gen_text(dev, vbuf, line++ * 16, 16, str);
snprintf(str, sizeof(str), " volume %3d ", dev->volume->cur.val);
gen_text(dev, vbuf, line++ * 16, 16, str);
snprintf(str, sizeof(str), " int32 %d, int64 %lld ",
dev->int32->cur.val,
dev->int64->cur.val64);
gen_text(dev, vbuf, line++ * 16, 16, str);
snprintf(str, sizeof(str), " boolean %d, menu %s, string \"%s\" ",
dev->boolean->cur.val,
dev->menu->qmenu[dev->menu->cur.val],
dev->string->cur.string);
mutex_unlock(&dev->ctrl_handler.lock);
gen_text(dev, vbuf, line++ * 16, 16, str);
if (dev->button_pressed) {
dev->button_pressed--;
snprintf(str, sizeof(str), " button pressed!");
gen_text(dev, vbuf, line++ * 16, 16, str);
}
//mv_count不知道干什么?
dev->mv_count += 2;
buf->vb.v4l2_buf.field = dev->field;
dev->field_count++;
buf->vb.v4l2_buf.sequence = dev->field_count >> 1;
do_gettimeofday(&ts);
//将当前时间戳赋值给buf->vb.v4l2_buf.timestamp
buf->vb.v4l2_buf.timestamp = ts;
}
vb2_plane_vaddr
void *vb2_plane_vaddr(struct vb2_buffer *vb, unsigned int plane_no)
{
struct vb2_queue *q = vb->vb2_queue;
if (plane_no > vb->num_planes)
return NULL;
//调用vb2_vmalloc_vaddr
return call_memop(q, plane_no, vaddr, vb->planes[plane_no].mem_priv);
}
EXPORT_SYMBOL_GPL(vb2_plane_vaddr)
vb2_vmalloc_vaddr
static void *vb2_vmalloc_vaddr(void *buf_priv)
{
struct vb2_vmalloc_buf *buf = buf_priv;
BUG_ON(!buf);
if (!buf->vaddr) {
printk(KERN_ERR "Address of an unallocated plane requested\n");
return NULL;
}
//直接返回vb->planes[plane_no].mem_priv->vaddr, 这个地址就是VIDIOC_REQBUFS时申请的
return buf->vaddr;
}
再返回vb2_buffer_done函数
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{
struct vb2_queue *q = vb->vb2_queue;
unsigned long flags;
if (vb->state != VB2_BUF_STATE_ACTIVE)
return;
if (state != VB2_BUF_STATE_DONE && state != VB2_BUF_STATE_ERROR)
return;
dprintk(4, "Done processing on buffer %d, state: %d\n",
vb->v4l2_buf.index, vb->state);
/* Add the buffer to the done buffers list */
spin_lock_irqsave(&q->done_lock, flags);
//state设置为VB2_BUF_STATE_DONE
vb->state = state;
//将vb2_buffer->done_entry加入到vb2_queue->done_list链表上
list_add_tail(&vb->done_entry, &q->done_list);
//减少计数
atomic_dec(&q->queued_count);
spin_unlock_irqrestore(&q->done_lock, flags);
/* Inform any processes that may be waiting for buffers */
//通知done_wq上的等待进程
wake_up(&q->done_wq);
}
EXPORT_SYMBOL_GPL(vb2_buffer_done);
最后返回vb2_streamon中的__enqueue_in_driver
/**
* __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing
*/
static void __enqueue_in_driver(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
//将vb2_buffer->state改成VB2_BUF_STATE_ACTIVE
vb->state = VB2_BUF_STATE_ACTIVE;
增加queued_count计算
atomic_inc(&q->queued_count);
//
q->ops->buf_queue(vb);
}
static void buffer_queue(struct vb2_buffer *vb)
{
struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb);
struct vivi_dmaqueue *vidq = &dev->vidq;
unsigned long flags = 0;
dprintk(dev, 1, "%s\n", __func__);
spin_lock_irqsave(&dev->slock, flags);
//将vivi_buffer->list挂到vidq->active上面去,回到vivi_thread_tick函数里面有对
//vidq->active是否为空的判断,所以只有__enqueue_in_driver之后,vivi_thread_tick才能
//往下执行。
list_add_tail(&buf->list, &vidq->active);
spin_unlock_irqrestore(&dev->slock, flags);
}
总结:stream_on主要做了,先从vb2_queue->queued_list链表中将vb2_buffer依次取下,挂载到vivi_dev->vivi_dmaqueue->active链表上去。启动vivi_thread线程, 线程里面有一个死循环,循环里从vivi_dev->vidq->active去获取需要填充的buffer,拿到对应buffer后在vivi_fillbuff里面填充,休眠直到下一帧图片需要填充时唤醒或主动调用wake_up_interruptible(&dma_q->wq)进行唤醒,然后继续vivi_thread里面的循环。填充好数据的vb2_buffer从vidq->active链表上删除并挂载到vb2_queue->done_list链表上去