文件描述
application.c 主函数起始
camera.c 提供hicamera摄像头对象,负责hiuvc,hiuac控制
hiuvc.c 提供hiuvc对象,负责视频控制
uvc_gadget.c 实现uvc设备操作功能
histream.c 实现视频流操作功能
frame_cache.c 实现uvc缓存操作功能
sample*.c 实现对接mpp媒体开发框架操作功能
hiuac.c 提供hiuac对象,负责音频控制
对象操作
以文件划分功能,文件的操作函数都为静态文件作用域(对外部不可见),这些函数最终被赋值到变量中,而这个变量也是静态的,只能被唯一一个的全局作用域函数get_xxx()
或取。
static int __init(void){};
static int __run(void){};
static hicamera __hi_camera =
{
.init = __init,
.run = __run,
};
hicamera *get_hicamera(void)
{
return &__hi_camera;
}
对象分析
以hiuvc
对象为切入点,它由初始化、打开、关闭和运行四个部分组成。对象主要负责流程控制,不包含具体实现,可以发现提供该对象最终支持的是uvc_gadget.c
。其中__init
并没有做任何事,__open
和__close
为直接调用,__run
中创建了线程去执行uvc_send_data_thread
,然后进入循环run_uvc_device
状态。
__open
深入open_uvc_device
函数,可以发现它的最终执行的是v4l2常规流程,首先open
设备视频设备节点获得fd,其次ioctl
查询V4l2能力,最后再去设定订阅事件,如VC处理(UVC_EVENT_SETUP),VS处理(UVC_EVENT_DATA),开启流(UVC_EVENT_STREAMON), 停止流(UVC_EVENT_STREAMOFF)。
__close
关闭是打开的相反操作,主要是去close
掉打开的描述符,在这之前需要关闭视频能力。
__run
这个函数会创建线程循环执行run_uvc_data
,自身进入run_uvc_device
循环。
run_uvc_device
这个功能块负责执行UVC事件处理,函数通过select监听描述符,当描述符就绪时就从视频设备的事件队列中出队一个事件并做处理。当初始化事件完成后会收到UVC_EVENT_STREAMON
事件,对应的执行enable_uvc_video()
启动流。当不需要据流时收到UVC_EVENT_STREAMOFF
事件,执行disable_uvc_video()
停止流。
run_uvc_data
这个功能块负责在启动流后当描述符就绪时就把一帧数据推入UVC视频缓冲区。
数据缓存
数据由uvc_cache
管理,它总共有2个帧队列和6个帧节点组成。队列free_queue
表示空闲节点队列,节点创建时被put
到free_queue
中等待使用。
create_uvc_cache
create_cache_node_list
node = malloc //创建6块
put_node_to_queue(uvc_cache->free_queue, node)
数据制造
数据来源由UVC_EVENT_STREAMON
事件的histream_startup()
这个函数动作产生,进入函数可以看到,它又创建了一条线程不断去监听Venc
,当就绪时获取图像数据。这里涉及到第二个队列ok_queue
,它用于存放填好数据的节点,节点正是由free_queue
队列中取出,填充帧数据后put
到ok_queue
中,这是节点在队列中的第一次位置交换。
相反过程就是UVC_EVENT_STREAMOFF
事件,需要关闭流和清空缓冲区。
数据首次消费
数据的首次消费也是在启动流 UVC_EVENT_STREAMON
事件到来时,节点第一次被从ok_queue
队列取出,其中node->mem
会被赋值到v4l2_buff
中由ioctrl
入队到内核(这需要事先申请好空间),这个node
还会被记录在__waited_node[]
等待队列中。uvc_video_stream_userptr
也是视频帧的第一个消费者。
数据后续消费
帧入队完后_SAMPLE_COMM_VENC_SaveData()
会继续从free_queue
取出节点填充并挂到ok_queue
中,此时初始化作为消费者的使命已经结束,交由线程的uvc_video_process_userptr
进行消费,线程首先从内核出队一个帧,并从ok队列中取一个节点,被出队的帧号对应的等待队列的节点也不再需要了,把他丢弃到free队列去并赋值为新的节点值,最后同样将node->mem
通过v4l2_buff
ioctrl到内核。这各过程被重复执行就实现了内核与用户数据源源不断的轮换。