HDMI VCU编码工程中GST-VCU-APP部分代码解析

HDMI VCU编码工程中GST-VCU-APP部分代码解析

APP概述

GST_VCU_APP 是基于 GStreamer 库开发的一个控制软件, 通过 APP 可实现对 PL 端 IP
核的管理。APP 完成了对 PL 端 IP 核的参数配置,以及控制 vcu 的编解码,将 vcu 编码后的
数据打包,用 UDP 协议将打包后的数据发送至以太网。
那么 app 是怎么实现对 IP 核的管理呢?我们按照这个问题展开说明。 从 zcu106 的 PS 与 PL 的结构图中可知,PS 与 PL 的交互是通过 AXI 总线完成的。
在这里插入图片描述
APP是运行在linux系统上的一个软件,而linux系统又是存放在PS端DDR中的,APU处理器通过CCI读取存储在DDR中的指令,DDR就是APU的主存。处理器通过AXI总线向PL端IP核写入控制信号。
在vivado工程中,为每个IP核设置了对应的地址。而在PS端的设备树中描述了相应IP核的信息(包括地址),在加载kernel后,系统根据设备树来注册驱动,生成虚拟节点。妙处就在这里,也就是说这里把一个linux系统中的虚拟节点与IP核对应了起来,那么用户空间就可以通过节点来操作IP核了(这不就是ARM处理器的基本操作吗,但这里的数据交互是通过AXI协议进行的,更高效节省资源)。
在这里插入图片描述
软件整体框架:(参考:ug1250 第31页。注:工程上的问题,xilinx官网基本上都会有相关文档或者博客介绍)
在这里插入图片描述
VCU_GST_APP是一个通用的、可实现多个功能的应用软件(既可用于视频编解码控制也可用于音频编解码,本工程只用到了视频编码)。下图为应用软件库之间的调用关系。
在这里插入图片描述
软件库的调用关系及驱动与硬件设备的管理关系框架
在这里插入图片描述
在这里插入图片描述
前面说到,系统为每个IP核注册了对应的虚拟节点,通过media节点可在运行时配置IP核,通过video可实现对IP核的操作。HDMI Rx将接收到的信号分离出视频与音频信号(这里没有用到音频),再通过VPSS对信号作转换;frame buffer write将数据写入PS端DDR(frmbuf Wr怎么知道写到DDR的哪个地址呢?PS端开始时配置了Frmbuf Wr IP核寄存器,使其按指定模式工作;然后PS端linux系统通过AXI总线下发写地址给Frmbuf Wr,告知其数据写入的地方),写完一帧信号后Frmbuf Wr这个IP核将发出一个中断信号给PS端处理器。然后VCU硬核从DDR中读取未经编码的数据(通过DMA方式读写),编码后再放回DDR,最终PS端将编码后的数据打包成UDP包发送出去。
在这个过程中,DDR需要开辟内存用于存储PL端传输过来的数据,这个开辟内存的操作是由V4L2驱动来完成的。V4L2驱动开辟了DMA_BUFFER然后将得到的句柄传递给上层Gstremer库的插件V4L2插件,由V4L2插件将句柄传递给gst-omx编码器插件,编码器插件再将文件文件句柄传给编码驱动,由此编码器驱动即可告知PL端VCU要从哪里读取数据进行编码了。(关于什么是插件章节请看3.3 Gstreamer的详细介绍 )下图为共享DMABuffer关系图,DRM/KMS与显示有关。(参考:ug1250-zcu106-trd 47页)
其中V4L2驱动开辟了多个DMABUF,用于乒乓处理,例如开辟了三个buffer,开始时数据存在buffer_1中,VCU从这块内存读取数据进行编码,在编码的过程中,frmbufWr又将数据存进buffer_2中,等VCU编完buffer_1中的数据并打包发送完成,就继续编码buffer_2中的数据,同时frmbuf Wr又开始把数据放于buffer_3中……以此循环往复。
在这里插入图片描述

APP框架流程图

以下程序流程图是自己从代码中提取的信息整理,如有理解错误或不当之处,待指出后更正.
Main函数:
在这里插入图片描述

读取configure文件
在这里插入图片描述

创建media设备
在这里插入图片描述
带宽监视
在这里插入图片描述
vgst_config_options
在这里插入图片描述

使用Gstreamer库流程
在这里插入图片描述
本工程用到的管道
在这里插入图片描述

Omxh265enc插件
在这里插入图片描述
capsfilter
在这里插入图片描述
rtpmp2tpay
在这里插入图片描述
mpegtsmux
在这里插入图片描述
DMA_BUFFER的开辟
在这里插入图片描述

APP中重要的函数

(1)必要的概念:媒体设备框架:https://www.jianshu.com/p/83dcdc679901
将硬件抽象为实体,然后按照一定的顺序将他们连接起来,形成管道。
注册media设备:media_device_register(struct media_device *mdev);
卸载media设备:media_device_unregister(struct media_device *mdev);
media 相关API介绍:
http://www.staroceans.org/myprojects/v4l2-utils/utils/media-ctl/mediactl.h
(2)Vgst_init函数,根据已有的media节点,例化media设备
2.1 struct media_device *media_device_new(const char *devnode);在已有的节点下例化媒体设备,在使用这些设备前必须进行枚举。

2.2.void media_device_unref(struct media_device *media);让一个例化的设备计数减1,该设备计数达到0时,释放它
注:每个IP核对应一个设备节点,app通过库去与对应的IP核交互。

(3)vgst_config_options函数中初始化了Gstreamer库,根据app_data的ip_param找到设备并对其配置
配置:先读取摄像头的视频格式,获取到格式后,把这个格式信息再写入DRM驱动设备中。(DRM驱动用来处理DMA,内存管理,资源锁以及安全硬件访问)。 我们这里使用了一路编码,所以在获取视频格式vlib_src_config函数中的case匹配到HDMI1。vcap_hdmi_set_media_ctrl函数中,通过V4L2驱动获取到视频格式。

(4)内存映射函数void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
注:内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了。

(5)perf_monitor_init()函数里初始化了性能监视器(APM),创建了一个线程一直读取AXI总线中VCU的带宽数据(线程中uPerfMon_getCounterValue函数读取了对应bus总线地址的数据,即为VCU带宽数据),存放在vcu_apm_counter数组中;在后面的回调函数time_cb()中使用。

(6)Time_cb()函数中,计算编解码器、管道的带宽,每秒回调一次。计算结果是否打印取决于配置文件。

(7)parse_config_file(configure文件路径)以只读的方式打开配置文件,判断文件里的配置信息,赋值给app_datasa结构体。(什么时候调用app_data结构体)
(8)get_encoder_config ():将读取的configure文件内容用于配置对应的编码参数

(9)管道的回调函数在vgst_utils.c文件中(bus_callback),管道中的元件都在play_ptr结构体中。
bus_callback回调函数:每个管道都有自己的总线,APP在只需要在总线上创建自己的消息处理器(这里用gst_bus_add_watch函数创建),当Glib库的主循环运行起来之后,将轮询轮询这个处理器是否有新的消息,当消息被采集到之后,总线呼叫对应的回调函数来完成任务(这里的回调函数为bus_callback)。
注:
1)bus_callback中先对得到信号进行判断,判断是管道中发生错误,还是数据流的结束,或是在数据流中找到了元数据。发生流结束信号时,将在总线中查找发出这个信号的元件。
2)g_main_loop_run ()函数启动了Glib库的主循环,当有事件产生时,它将处理相关事件;若没有事件产生,它就进入了睡眠等待状态,即阻塞在这个函数中。
3)app_data.playback的值一直为false,所以app的main中do…while循环并没有真正循环起来,故并没有在循环中一直创建新的管道,所以管道中的信息交互关键函数还是bus_callback;vcu编码的参数也不是每传完一帧数据又重新配置一次参数,而是只配置了一次。当vcu编码好一帧图像之后产生一个中断,PS对这个中断的响应。

(10)vgst_create_pipeline ():创建了各元件并将其连接成管道。其中结构体app中包含了各个元件、网络IP、PORT、设备类型等,对比app_data与app结构体,app_data中除了一些主函数中的必要变量几乎与app类似,app中特别的地方在于playback数组,存储了组成管道的元件。
在这里插入图片描述
在这里插入图片描述

Gstreamer库概述

Gstreamer 是一个非常强大而且通用的流媒体应用程序框架。GStreamer 是一个创建流媒体应用程序的框架。其基本设计思想来自于俄勒冈 (Oregon) 研究生学院有关视频管道的创意, 同时也借鉴了 DirectShow 的设计思想来源于其框架的模块化: Gstreamer 能够无缝的合并新的插件。GStreamer 的程序开发框架使得编写任意类型的流媒体应用程序成为了可能。在编写处理音频、视频或者两者皆有的应用程序时, GStreamer 可以让你的工作变得简单。GStreamer并不受限于音频和视频处理, 它能够处理任意类型的数据流。管道设计的方法对于实际应用的滤波器几乎没有负荷 , 它甚至可以用来设计出对延时有很高要求的高端音频应用程序。GStreamer 最 显 著 的 用 途 是 在 构 建 一 个 播 放 器上 。GStreamer 已 经 支 持 很 多 格 式 的 文 件 了 , 包 括 : MP3、Ogg/Vorbis、MPEG-1/2、AVI、Quicktime、 mod 等等。从这个角度看,GStreamer 更象是一个播放器。但是它主要的优点却是在于: 它的可插入组件能够很方便的接入到任意的管道当中。这个优点使得利用 GStreamer 编写一个万能的可编辑音视频应用程序成为可能。GStreamer 框架是基于插件的, 有些插件中提供了各种各样的多媒体数字信号编解码器,也有些提供了其他的功能。所有的插件都能够被链接到任意的已经定义了的数据流管道中。GStreamer 的管道能够被 GUI 编辑器编辑, 能够以 XML 文件来保存。这样的设计使得管道程序库的消耗变得非常少。GStreamer 核心库函数是一个处理插件、数据流和媒体操作的框架。 GStreamer 核心库 还提供了一个 API, 这个 API 是开放给程序员使用的—当程序员需要使用其他的插件来编写他所需要的应用程序的时候可以使用它。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页