1. VIDIOC_REQBUFS: 请求Kernel分配Video Buffer
其申请流程如下图所示:
1.1 Kernel态相关数据结构
struct uvc_fh {
struct uvc_video_chain *chain;
struct uvc_streaming *stream; //Stream--->
enum uvc_handle_state state;
};
struct uvc_streaming {
struct list_head list;
struct uvc_device *dev;
struct video_device *vdev;
struct uvc_video_chain *chain;
atomic_t active;
struct usb_interface *intf;
int intfnum;
__u16 maxpsize;
struct uvc_streaming_header header;
enum v4l2_buf_type type;
unsigned int nformats;
struct uvc_format *format;
struct uvc_streaming_control ctrl;
struct uvc_format *cur_format;
struct uvc_frame *cur_frame;
/* Protect access to ctrl, cur_format, cur_frame and hardware video
* probe control.
*/
struct mutex mutex;
unsigned int frozen : 1;
struct uvc_video_queue queue; // UVC Video Queue--->
void (*decode) (struct urb *urb, struct uvc_streaming *video,
struct uvc_buffer *buf);
/* Context data used by the bulk completion handler. */
struct {
__u8 header[256];
unsigned int header_size;
int skip_payload;
__u32 payload_size;
__u32 max_payload_size;
} bulk;
struct urb *urb[UVC_URBS];
char *urb_buffer[UVC_URBS];
dma_addr_t urb_dma[UVC_URBS];
unsigned int urb_size;
__u32 sequence;
__u8 last_fid;
struct tasklet_struct *tasklet[UVC_URBS]; /* ddl@rock-chips.com */
};
struct uvc_video_queue {
enum v4l2_buf_type type;
void *mem; // 已经分配的连续虚拟内存的首地址
unsigned int flags;
unsigned int count; // 已分配的buffer个数
unsigned int buf_size; // 每个buffer的大小
unsigned int buf_used;
struct uvc_buffer buffer[UVC_MAX_VIDEO_BUFFERS]; // UVC buffer--->
struct mutex mutex; /* protects buffers and mainqueue */
spinlock_t irqlock; /* protects irqqueue */
wait_queue_head_t wait; /* wait if mainqueue is empty */
struct list_head mainqueue;
struct list_head irqqueue;
};
struct uvc_buffer {
unsigned long vma_use_count;
struct list_head stream;
/* Touched by interrupt handler. */
struct v4l2_buffer buf; // v4l2_buffer --->
struct list_head queue;
wait_queue_head_t wait; // 初始化等待队列
enum uvc_buffer_state state;
unsigned int error;
};
struct v4l2_buffer {
__u32 index; //buffer索引
enum v4l2_buf_type type; //如V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 bytesused;
__u32 flags;
enum v4l2_field field; // V4L2_FIELD_NONE
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
enum v4l2_memory memory; // V4L2_MEMORY_MMAP
union {
__u32 offset; //在已经分配的大块内存中的偏移量,
//其首地址保存在uvc_video_queue->mem中
unsigned long userptr;
struct v4l2_plane *planes;
} m;
__u32 length; //申请的内存大小
__u32 input;
__u32 reserved;
};
1.2 uvc_alloc_buffers实现代码
/*
* Allocate the video buffers.
*
* Pages are reserved to make sure they will not be swapped, as they will be
* filled in the URB completion handler.
*
* Buffers will be individually mapped, so they must all be page aligned.
*/
int uvc_alloc_buffers(struct uvc_video_queue *queue, unsigned int nbuffers,
unsigned int buflength)
{
unsigned int bufsize = PAGE_ALIGN(buflength);
unsigned int i;
void *mem = NULL;
int ret;
if (nbuffers > UVC_MAX_VIDEO_BUFFERS)
nbuffers = UVC_MAX_VIDEO_BUFFERS;
mutex_lock(&queue->mutex);
if ((ret = __uvc_free_buffers(queue)) < 0)
goto done;
/* Bail out if no buffers should be allocated. */
if (nbuffers == 0)
goto done;
/* Decrement the number of buffers until allocation succeeds. */
for (; nbuffers > 0; --nbuffers) {
mem = vmalloc_32(nbuffers * bufsize);
if (mem != NULL)
break;
}
if (mem == NULL) {
ret = -ENOMEM;
goto done;
}
for (i = 0; i < nbuffers; ++i) {
memset(&queue->buffer[i], 0, sizeof queue->buffer[i]);
queue->buffer[i].buf.index = i;
queue->buffer[i].buf.m.offset = i * bufsize;
queue->buffer[i].buf.length = buflength;
queue->buffer[i].buf.type = queue->type;
queue->buffer[i].buf.field = V4L2_FIELD_NONE;
queue->buffer[i].buf.memory = V4L2_MEMORY_MMAP;
queue->buffer[i].buf.flags = 0;
init_waitqueue_head(&queue->buffer[i].wait);
}
queue->mem = mem;
queue->count = nbuffers;
queue->buf_size = bufsize;
ret = nbuffers;
done:
mutex_unlock(&queue->mutex);
return ret;
}
2. VIDIOC_QUERYBUF: 把Kernel分配的内存映射到用户空间
3. VIDIOC_QBUF: 把uvc_buffer放入队列中
/*
* Queue a video buffer. Attempting to queue a buffer that has already been
* queued will return -EINVAL.
*/
int uvc_queue_buffer(struct uvc_video_queue *queue,
struct v4l2_buffer *v4l2_buf)
{
struct uvc_buffer *buf;
unsigned long flags;
int ret = 0;
uvc_trace(UVC_TRACE_CAPTURE, "Queuing buffer %u.\n", v4l2_buf->index);
if (v4l2_buf->type != queue->type ||
v4l2_buf->memory != V4L2_MEMORY_MMAP) {
uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer type (%u) "
"and/or memory (%u).\n", v4l2_buf->type,
v4l2_buf->memory);
return -EINVAL;
}
mutex_lock(&queue->mutex);
if (v4l2_buf->index >= queue->count) {
uvc_trace(UVC_TRACE_CAPTURE, "[E] Out of range index.\n");
ret = -EINVAL;
goto done;
}
buf = &queue->buffer[v4l2_buf->index];
if (buf->state != UVC_BUF_STATE_IDLE) {
uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state "
"(%u).\n", buf->state);
ret = -EINVAL;
goto done;
}
if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
v4l2_buf->bytesused > buf->buf.length) {
uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
ret = -EINVAL;
goto done;
}
spin_lock_irqsave(&queue->irqlock, flags);
if (queue->flags & UVC_QUEUE_DISCONNECTED) {
spin_unlock_irqrestore(&queue->irqlock, flags);
ret = -ENODEV;
goto done;
}
buf->state = UVC_BUF_STATE_QUEUED;
if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
buf->buf.bytesused = 0;
else
buf->buf.bytesused = v4l2_buf->bytesused;
list_add_tail(&buf->stream, &queue->mainqueue);
list_add_tail(&buf->queue, &queue->irqqueue);
spin_unlock_irqrestore(&queue->irqlock, flags);
done:
mutex_unlock(&queue->mutex);
return ret;
}
4. VIDIOC_STREAMON: 做好准备工作并提交URB请求
/*
* Initialize bulk URBs and allocate transfer buffers. The packet size is
* given by the endpoint.
*/
static int uvc_init_video_bulk(struct uvc_streaming *stream,
struct usb_host_endpoint *ep, gfp_t gfp_flags)
{
struct urb *urb;
unsigned int npackets, pipe, i;
u16 psize;
u32 size;
// 获取每个包的大小
psize = le16_to_cpu(ep->desc.wMaxPacketSize) & 0x07ff;
// 一次可传输的最大负荷传输大小
size = stream->ctrl.dwMaxPayloadTransferSize;
stream->bulk.max_payload_size = size;
/*
分配置urb传输buffer,返回包个数(size/psize),它指每个urb
包含多少个包; 为每个stream->urb_buffer分配DMA buffer,如下:
for (i = 0; i < UVC_URBS; ++i) {
stream->urb_size = psize * npackets; // urb_buffer的大小
//分配DMA内存,并把地址保存在urb_buffer[i]和urb_dma[i]中,
//同一块内存,不同的表示方法
//1) stream->urb_buffer[i] = offset + page->vaddr;
//2) stream->urb_dma[i] = offset + page->dma;
stream->urb_buffer[i] = usb_alloc_coherent(
stream->dev->udev, stream->urb_size,
gfp_flags | __GFP_NOWARN, &stream->urb_dma[i]);
if (!stream->urb_buffer[i]) {
uvc_free_urb_buffers(stream);
break;
}
}
*/
npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
if (npackets == 0)
return -ENOMEM;
size = npackets * psize;
if (usb_endpoint_dir_in(&ep->desc))
pipe = usb_rcvbulkpipe(stream->dev->udev,
ep->desc.bEndpointAddress);
else
pipe = usb_sndbulkpipe(stream->dev->udev,
ep->desc.bEndpointAddress);
if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
size = 0;
for (i = 0; i < UVC_URBS; ++i) {
// 创建一个 urb
urb = usb_alloc_urb(0, gfp_flags);
if (urb == NULL) {
uvc_uninit_video(stream, 1);
return -ENOMEM;
}
/* 填充urb参数
struct urb {
void *transfer_buffer; // (in) associated data buffer
dma_addr_t transfer_dma;// (in) dma addr for transfer_buffer
usb_complete_t complete;// (in) completion routine
struct usb_iso_packet_descriptor iso_frame_desc[0];
// (in) ISO ONLY
}
*/
usb_fill_bulk_urb(urb, stream->dev->udev, pipe,
stream->urb_buffer[i], //传输buffer
size, //传输buffer的大小
uvc_video_complete, //URB请求完成之后的callback
stream);
urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
urb->transfer_dma = stream->urb_dma[i]; //给urb->transfer_dma赋值
// 把此urb保存到stream->urb[i]中
stream->urb[i] = urb;
}
return 0;
}
5. urb数据解析 (uvc_video_complete)
当URB请求(usb_submit_urb)完成之后,它将调用其回调函数(uvc_video_complete),下面分析此回调函数到底做了些什么。即如何把transfer_buffer或transfer_dma中数据转换为应用程序需要的v4l2_buffer中的数据。
5.1 uvc_video_complete
static void uvc_video_complete(struct urb *urb)
{
struct uvc_streaming *stream = urb->context;
struct uvc_video_queue *queue = &stream->queue;
struct uvc_buffer *buf = NULL;
unsigned long flags;
int ret;
switch (urb->status) {
case 0:
break;
default:
uvc_printk(KERN_WARNING, "Non-zero status (%d) in video "
"completion handler.\n", urb->status);
case -ENOENT: /* usb_kill_urb() called. */
if (stream->frozen)
return;
case -ECONNRESET: /* usb_unlink_urb() called. */
case -ESHUTDOWN: /* The endpoint is being disabled. */
uvc_queue_cancel(queue, urb->status == -ESHUTDOWN);
return;
}
spin_lock_irqsave(&queue->irqlock, flags);
if (!list_empty(&queue->irqqueue))
buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
queue);//从queue->irqqueue中取出一个空的uvc_buffer
spin_unlock_irqrestore(&queue->irqlock, flags);
//把urb中的数据转换为uvc_buffer中的数据,并设置对应的状态
stream->decode(urb, stream, buf);
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
ret);
}
}
5.2 stream->decode是什么?
现在关键是stream->decode到底做了些什么?它也是一个回调函数,首先要搞明白它是一个什么函数,其注册过程如下图所示:
5.3 uvc_video_decode_bulk
5.4 uvc_video_decode_isoc
static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream,
struct uvc_buffer *buf)
{
u8 *mem;
int ret, i;
for (i = 0; i < urb->number_of_packets; ++i) {
if (urb->iso_frame_desc[i].status < 0) {
uvc_trace(UVC_TRACE_FRAME, "USB isochronous frame "
"lost (%d).\n", urb->iso_frame_desc[i].status);
/* Mark the buffer as faulty. */
if (buf != NULL)
buf->error = 1;
continue;
}
/* Decode the payload header. */
mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
do {
ret = uvc_video_decode_start(stream, buf, mem,
urb->iso_frame_desc[i].actual_length);
if (ret == -EAGAIN)
buf = uvc_queue_next_buffer(&stream->queue,
buf);
} while (ret == -EAGAIN);
if (ret < 0)
continue;
/* Decode the payload data. */
uvc_video_decode_data(stream, buf, mem + ret,
urb->iso_frame_desc[i].actual_length - ret);
/* Process the header again. */
uvc_video_decode_end(stream, buf, mem,
urb->iso_frame_desc[i].actual_length);
if (buf->state == UVC_BUF_STATE_READY) {
if (buf->buf.length != buf->buf.bytesused &&
!(stream->cur_format->flags &
UVC_FMT_FLAG_COMPRESSED))
buf->error = 1;
buf = uvc_queue_next_buffer(&stream->queue, buf);
}
}
}
6. VIDIOC_DQBUF: 获取视频数据
7. CameraHAL工作流程