海思uvc_app源码学习笔记

news/2024/12/5 5:08:12/

文章目录

  • 前言
  • 流程
    • hicamera
    • histream
    • hiuvc
  • 模块分析
  • 总结

前言

海思mpp例子里面的uvc_app源码学习笔记。
看了半天,以为是3516读取usb摄像头数据的,结果是实现一个usb摄像头的。可以搞个公对公的usb线测试下。
大概流程是从摄像头获取图像,进行处理或者编码,按照v4l2的标准操作字符设备实现图像的输出。基本思想是一个生产者和消费者的模型,用两个队列做图像缓冲区。

流程

读取配置文件,使用了一个开源的库iniparser库,配置文件格式是ini文件。

    if (create_config_svc("./uvc_app.conf") != 0){goto ERR;}

UVC全称为USB Video Class,即:USB视频类,UAC全称为USB Audio Class,即:USB音频类。
创建分别用于存放图像或者声音的缓存,实质上是一个链表实现的队列。

    if (create_uvc_cache() != 0){goto ERR;}if (g_uac){if (create_uac_cache() != 0){goto ERR;}}

初始化以及运行业务程序。

    if (get_hicamera()->init() != 0 ||get_hicamera()->open() != 0 ||get_hicamera()->run() != 0){goto ERR;}

退出时释放资源等等。

hicamera

hicamera结构体可以看作相机或者最顶层方法的封装,包括初始化、打开、关闭、运行等。

static hicamera __hi_camera =
{.init = __init,.open = __open,.close = __close,.run = __run,
};

init初始化方法:初始化hiuvc、histream、hiuac、hiaudio。
open打开方法:打开hiuvc、hiaudio。
close关闭方法:关闭hiuvc、histream、hiuac、hiaudio。
run运行方法:启动两个线程运行hiuvc、hiuac。

histream

typedef struct histream
{struct stream_control_ops *mpi_sc_ops;struct processing_unit_ops *mpi_pu_ops;struct input_terminal_ops *mpi_it_ops;struct extension_unit_ops *mpi_eu_ops;int streaming;int exposure_auto_stall;int brightness_stall;
} histream;static struct histream __hi_stream = {.mpi_sc_ops = NULL,.mpi_pu_ops = NULL,.mpi_it_ops = NULL,.mpi_eu_ops = NULL,.streaming = 0,.exposure_auto_stall = 0,.brightness_stall = 0,
};

结构体有些复杂,这里是和mpp驱动相关的部分,在sample_venc_config函数中进行注册。

HI_VOID sample_venc_config(HI_VOID)
{printf("\n@@@@@ HiUVC App Sample @@@@@\n\n");histream_register_mpi_ops(&venc_sc_ops, &venc_pu_ops, &venc_it_ops, HI_NULL);
}
static struct stream_control_ops venc_sc_ops = {.init = sample_venc_init,.startup = sample_venc_startup,.shutdown = sample_venc_shutdown,.set_idr = sample_venc_set_idr,.set_property = sample_venc_set_property,
};

这里是mpp初始化等等操作,是常规的处理流程。
sample_venc_init mpp初始化:初始化SYS和VB。
sample_venc_startup mpp流媒体开始:VPSS、VENC初始化,使用系统绑定处理图像,可以将原始数据或者编码后的数据放到UVC的队列里面。
sample_venc_shutdown mpp结束:关闭或者停止。

UVC图像节点设置的函数为sample_yuv_dump和__SAMPLE_COMM_VENC_SaveData,前者将YUV数据放入节点中,后者将编码后的数据放入节点中,具体执行哪个函数由一个全局变量__encoder_property控制,可以上位机进行。两个函数都是从free_queue节点中获取空闲图像缓存节点,存放数据放入到ok_queue队列中,是生产者。

static HI_VOID sample_yuv_dump(VIDEO_FRAME_S *pVBuf, FILE *g_pfd)
{PIXEL_FORMAT_E enPixelFormat = pVBuf->enPixelFormat;uvc_cache_t *uvc_cache = HI_NULL;frame_node_t *fnode = HI_NULL;sample_yuv_get_buf_size(pVBuf, &g_u32Size);g_pUserPageAddr = (HI_CHAR*)HI_MPI_SYS_Mmap(pVBuf->u64PhyAddr[0], g_u32Size);if (HI_NULL == g_pUserPageAddr){goto ERR;}//get free cache nodeuvc_cache = uvc_cache_get();if (uvc_cache){get_node_from_queue(uvc_cache->free_queue, &fnode);}if (!fnode){goto ERR;}if (PIXEL_FORMAT_YVU_SEMIPLANAR_422 == enPixelFormat){if (sample_yuv_sp422_to_p422(pVBuf, fnode) != HI_SUCCESS){goto ERR;}}else if (PIXEL_FORMAT_YVU_SEMIPLANAR_420 == enPixelFormat){sample_yuv_sp420_to_p420(pVBuf, fnode);}else{}ERR:if (fnode){put_node_to_queue(uvc_cache->ok_queue, fnode);}if (g_pUserPageAddr){HI_MPI_SYS_Munmap(g_pUserPageAddr, g_u32Size);g_pUserPageAddr = HI_NULL;}return;
}

其他操作是一些设置相关的,暂不分析。

hiuvc

hiuvc是对UVC类操作的封装,这里涉及到V4L2的一些知识。

static hiuvc __hi_uvc =
{.init = __init,.open = __open,.close = __close,.run = __run,
};

打开方法:打开设备及一些初始化操作。

static int __open()
{const char *devpath = "/dev/video0";return open_uvc_device(devpath);
}

run运行方法:两个线程以死循环的形式分别跑了run_uvc_data,run_uvc_device函数。
这里需要注意的点是run_uvc_data和run_uvc_device都是使用select在等待前面open的同一个文件描述符,不过一个监听的是可以向其中写入数据,一个监听的是异常情况。
uvc_video_process_userptr是设备文件描述符可写时的处理函数,表示可以将图像视频数据输出,这里使用V4L2_MEMORY_USERPTR用户指针模式,内存由用户进行分配,V4L2_BUF_TYPE_VIDEO_OUTPUT表示视频图像输出。

static int uvc_video_process_userptr(struct uvc_device* dev)
{struct v4l2_buffer buf;int ret;// INFO("#############uvc_video_process_userptr\n");memset(&buf, 0, sizeof buf);buf.type   = V4L2_BUF_TYPE_VIDEO_OUTPUT;buf.memory = V4L2_MEMORY_USERPTR;if ((ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf)) < 0){return ret;}uvc_video_fill_buffer_userptr(dev, &buf);if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0){LOG("Unable to requeue buffer: %s (%d).\n", strerror(errno), errno);return ret;}return 0;
}

uvc_video_fill_buffer_userptr先获取用于输出图像的缓存,然后从ok_queue队列中取出已经存放好的图像,__waited_node数组指针用于存放上次的图像节点,然后放到free_queue队列中,表示该节点空闲可以再次使用,是消费者。
run_uvc_device运行uvc_events_process函数,主要是对UVC事件的处理函数。

模块分析

把代码大概划分一下模块

模块名作用实现
配置解析模块实现解析配置文件的功能iniparser解析库
缓存模块cache存放图像数据使用单向循环链表实现的队列
V4L2图像数据输出输出图像到上位机V4L2框架

总结

  1. iniparser库的使用
  2. UVC&UAC 总结
  3. 浅析 Hi MPP 中的 uvc_app
  4. V4l2视频输出实现流程
  5. uevent机制:uevent原理分析
  6. 多进程/线程select同一文件问题
  7. V4L2视频采集的基本流程

http://www.ppmy.cn/news/1015627.html

相关文章

ChatGPT发展到了什么程度?代码生成,程序员将被取代?

前言 ChatGPT 是一个基于人工智能的聊天机器人&#xff0c;由 OpenAI 开发。ChatGPT 的历史可以追溯到早期的语言模型&#xff0c;例如循环神经网络 (RNN) 和长短时记忆网络 (LSTM)。如今的 ChatGPT-3 则是最新的版本。 ChatGPT发展到了什么程度&#xff1f;代码生成&#xff0…

vs code remote ssh: Resolver error: Error: Got bad result from install script

今天像往常一样&#xff0c;打开 windows 11&#xff0c;使用 vs code 远程连接服务器 ubuntu 20&#xff0c;但是遇到了一个错误&#xff1a;Resolver error: Error: Got bad result from install script。 ok&#xff01;&#xff01;&#xff01;开始 Bing &#xff01;&…

每天一道leetcode:剑指 Offer 50. 第一个只出现一次的字符(适合初学者)

今日份题目&#xff1a; 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 示例1 输入&#xff1a;s "abaccdeff" 输出&#xff1a;b 示例2 输入&#xff1a;s "" 输出&#xff1a; 提示 0 …

【JVM满分总结】

文章目录 1、什么是JVM&#xff1f;2、能说一下JVM的内存区域吗&#xff1f;3、.说一下JDK1.6、1.7、1.8内存区域的变化&#xff1f;4、为什么使用元空间替代永久代作为方法区的实现&#xff1f;5、对象创建的过程了解吗&#xff1f;6、什么是指针碰撞&#xff1f;什么是空闲列…

2023华为od机试 Python【最多颜色的车辆】

前言 本题使用python解答,如果需要Java代码,参考点我 题目 在一个狭小的路口,每秒只能通过一辆车,假设车辆的颜色只有 3 种,找出 N 秒内经过的最多颜色的车辆数量。三种颜色编号为0 ,1 ,2 输入描述 第一行输入的是通过的车辆颜色信息。[0,1,1,2] 代表4 秒钟通过的车辆…

ZZULIOJ 1191: 数星星(结构体专题),Java

ZZULIOJ 1191: 数星星&#xff08;结构体专题&#xff09;&#xff0c;Java 题目描述 一天&#xff0c;小明坐在院子里数星星&#xff0c;Gardon就出了个难题给她&#xff1a;Gardon在天空画出了一个矩形区域&#xff0c;让他输入矩形区域里有多少颗星星&#xff0c;仁慈的上…

SpringBoot复习:(20)如何把bean手动注册到容器?

可以通过实现BeanDefinitionRegistryPostProcessor接口&#xff0c;它的父接口是BeanFactoryPostProcessor. 步骤&#xff1a; 一、自定义一个组件类&#xff1a; package com.example.demo.service;public class MusicService {public MusicService() {System.out.println(&q…

利用docker run --rm 命令实现使用宿主机中没有的命令

利用docker run --rm 命令实现使用宿主机中没有的命令 使用容器中的jar命令解压jar包&#xff0c;并将解压内容输出到挂载在宿主机中的目录里使用宿主机中没有的nmap命令来通过端口找IP 使用容器中的jar命令解压jar包&#xff0c;并将解压内容输出到挂载在宿主机中的目录里 do…