zoukankan      html  css  js  c++  java
  • 浅析 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

  • 相关阅读:
    HLG 1522 子序列的和【队列的应用】
    POJ 3273 Monthly Expense【二分】
    HDU 4004 The Frog's Games 【二分】
    POJ 2001 Shortest Prefixes【第一棵字典树】
    POJ 2823 Sliding Window【单调对列经典题目】
    HDU 1969 Pie 【二分】
    POJ 3125 Printer Queue【暴力模拟】
    POJ 3250 Bad Hair Day【单调栈】
    字典树【模板】
    验证码 Code
  • 原文地址:https://www.cnblogs.com/llil/p/14521522.html
Copyright © 2011-2022 走看看