• 浅析 Hi MPP 中的 uvc_app


    以往我们说UVC一般搜索到的内容是板端作为主机,外接USB视频设备并使用UVC去控制,那么板端也就是从机中的UVC是如何实现的。下面就记录一个海思SDK中的例子,源码路径HISDK/mpp/sample/uvc_app

    文件描述

    文件 说明
    application.c 主函数起始
    hiuac.c 提供hiuac对象,负责音频控制
    hiuvc.c 提供hiuvc对象,负责视频控制
    camera.c 提供hicamera摄像头对象,负责hiuvc,hiuac对象控制
    uvc_gadget.c 实现uvc设备操作功能
    frame_cache.c 实现uvc缓存操作功能
    histream.c 实现视频流操作功能
    sample*.c 实现对接mpp媒体开发框架操作功能

    对象操作

    以文件划分功能,文件的操作函数都为静态文件作用域(对外部不可见),这些函数最终被赋值到变量中,而这个变量也是静态的,只能被唯一一个的全局作用域函数get_xx()获取。

    static int __init(void){};
    static int __run(void){};
    
    static hicamera __hi_camera =
    {
        .init = __init,
        .run = __run,
    };
    
    hicamera *get_hicamera(void)
    {
        return &__hi_camera;
    }
    

    对象分析

    直接进入正题以hiuvc对象为切入点,它由初始化、打开、关闭和运行四个部分组成。对象主要负责流程控制,不包含具体实现,其中__init并没有做任何事,__open__close为直接调用,__run创建了线程uvc_send_data_thread去循环run_uvc_data,然后主线程就进入循环run_uvc_device状态。通过查找可以发现这些对象支持函数都指向文件uvc_gadget.c

    __open

    深入open_uvc_device函数,最后可以看出它的最终执行的是v4l2常规流程,首先open设备视频设备节点获得fd,其次ioctl VIDIOC_UERYCAP去查询v4l2能力,最后再ioctl VIDIOC_SUBSCRBE_EVENT去设定订阅事件,如: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_data

    这个功能块就负责一件事,在流启动后去监听视频设备描述符,就绪时就通过uvc_video_process_userptr把一帧数据推入UVC视频缓冲区,具体功能实现先跳过。

    run_uvc_device

    这个功能块负责执行UVC事件处理,函数通过select监听描述符,当描述符就绪时就从视频设备的事件队列中出队一个事件并做处理。当初始化事件完成后会触发UVC_EVENT_STREAMON事件,对应的执行enable_uvc_video()去启动流。当不需要据流时触发UVC_EVENT_STREAMOFF事件去执行disable_uvc_video()停止流。

    至此整个框架基本完成,首先open_uvc_device打开视频设备驱动,其次run_uvc_device去控制应用层的视频流开启关闭,最后通过run_uvc_data推入流到驱动向外输出。接下来就看看数据是如何被启动关闭的又是如何流转的,这里就需要提到uvc_cache视频设备缓存管理。

    数据缓存

    uvc_cache它由有6个帧节点和2个帧队列组成。其中帧队列free_queue表示空闲节点队列,初始化时得到了所有节点的。帧队列ok_queue表示完成节点队列,当节点填充完数据后才会被put到这个队列当中。

    create_uvc_cache
        create_cache_node_list
            node = malloc //创建6块
            put_node_to_queue(uvc_cache->free_queue, node)
    

    数据制造

    前面提到,当触发UVC_EVENT_STREAMON事件是会执行enable_uvc_video去启动流,可用看到启动通常是先经过清理关机再开机的方式。直接进入histream_startup()这个函数动作,以看到最终创建了一条线程不断去监听Venc描述符,当图像就绪时去获取保存。首先会从空闲队列free_queue中取出节点,然后填充帧数据,最后put到完成队列ok_queue中,这是节点在队列中的第一次位置交换。函数最后可用看到dev-streaming被置1这就标志着流被开启,上面说到的run_uvc_data根据这个状态就可以开始推数据了。

    数据首次消费

    到这里数据流是开启了,但是初始化并没有完成,对于视频设备/dev/video目前也就仅经历了open和订阅UVC_EVENT_操作。接下来enable_uvc_videoioctl VIDIOC_REQBUFS去命令驱动申请缓存空间。接着从完成队列ok_queue中取出节点,并将节点成员node->mem赋值到v4l2_bufioctrl VIDIOC_QBUF入队到内核缓存空间中,这个node也还被记录在等待队列__waited_node[]上表示这个节点真正被处理。到这里就完成了视频帧的第一次消费。

    数据后续消费

    初始化后,数据制造者_SAMPLE_COMM_VENC_SaveData()会不断的从free_queue取出节点填充并挂到ok_queue上。而后续的消费工作也交回到线程的run_uvc_data去处理。可以看到首先会ioctl VIDIOC_DQBUF从内核出队一个帧,并从完成队列取出一个节点,被出队的帧号也对应等待队列的标号,帧可以出队就表示它被处理完了,这时对应等待队列中的节点__waited_node[buf->index] 就可以把他放回空闲队列中去,并记录下本次取出节点。同样节点成员node->mem也将通过v4l2_buf被ioctrl到内核中。这各过程被重复执行就实现了内核与用户数据源源不断的轮换。

    结束工作

    UVC_EVENT_STREAMOFF事件到来时,表示结束当前工作,首先ioctl VIDIOC_STREAMOFF停止内核流传输,接着关闭应用成流生产, 最后清空完成队列ok_queue

  • 相关阅读:
    数据库的左右表连接
    数据库索引-索引的优点、缺点、分类、不走索引的情况
    selenium中日期控件的操作方法
    esdoc 自动生成接口文档介绍
    css设置背景图(背景图太大导致图片显示不全)
    k8s调度器及调度队列源码分析
    MIT6.824 2020 Lab1 MapReduce 实现
    git命令
    python3.6、3.9多版本安装共存
    使用蓝鲸平台登录态验证接入第三方系统
  • 原文地址:https://www.cnblogs.com/llil/p/14521522.html
Copyright © 2020-2023  润新知