文章写了好多年了,只在一个网站发布过PDF版本,行业内应该很多人看过这个。由于早期笔记使用Word格式,转换格式时有不少格式错误,笔者尽量修正。
关于本blog,图床一般使用github,已经配置了CDN,如果图片还是未显示请自行代理解决
原创声明警告,本文禁止转载,禁止发布到其它任何网站,可以接受约稿。
前言:
本文通过分析海思文档和代码,把海思SDK的MPI和UNF构架大概实现思想和构架进行了简略的分析。着重分析了内存管理,底层功能如何实现。
前面章节简要分析了NVR芯片MPI构架及其内存管理机制,后面着重详细分析了3798M底层模块api和drv实现的细节过程及其方法流程。
本文前面简略分析了DVR,MPI构架的大体实现机制。后面就具体分析3798M UNF构架的实现。
本文不光分析了UNF构架,还使用了很多工具,辅助分析代码。这里从三个层面分析了UNF的实现。
1: 应用层,驱动层的实现框架,使用source insight查看代码并着重分析了avplay等几个模块。
2:静态分析函数调用。使用cflow,dot工具生成调用关系图
3:动态追踪运行过程。Ltrace, strace, valgrind分析函数调用,perf动态分析内核调用。
1:Hi35xx系列芯片MPP构架
1.1 概述
海思提供的媒体处理软件平台(Media Process Platform,简称 MPP),可支持应用软件快速开发。该平台对应用软件屏蔽了芯片相关的复杂的底层处理,并对应用软件直接提供MPI(MPP Programe Interface)接口完成相应功能。该平台支持应用软件快速开发以下功能:输入视频捕获、H.264/MJPEG/JPEG/MPEG4 编码、H264/H.265/VC1/MPEG4/MPEG2/AVS 解码、视频输出显示、视频图像前处理(包括去噪、增强、锐化、Deinterlace) 、编码码流叠加 OSD、视频侦测分析、智能分析、音频捕获及输出、音频编解码等功能。
1.2 整体软硬件构架
MPP 平台支持的典型的系统层次如上图所示,主要分为以下层次:
硬件层
硬件层由 Hi35xx 芯片加上必要的外围器件构成。外围器件包括 Flash、DDR
(Double Data-Rate) 、视频 Sensor 或 AD、音频 AD 等。
操作系统层
基于 Linux 3.10.y 的 OS 系统。
媒体处理平台
基于操作系统层,控制芯片完成相应的媒体处理功能。它对应用层屏蔽了硬件处理细节,并为应用层提供 API 接口完成相应功能。
其他驱动
除媒体处理平台外,海思为 Hi35xx 芯片的其他相关硬件处理单元提供了相应的驱动,
包括 GMAC、SDIO、I2C、USB、SSP 等驱动。
应用层
基于海思媒体处理平台及其他驱动,由用户开发的应用软件系统。
1.3 海思媒体处理平台架构
海思媒体处理平台的主要内部处理流程如上图所示,主要分为视频输入(VI) 、视频处理(VPSS) 、视频编码(VENC) 、视频解码(VDEC) 、视频输出(VO)、视频侦测分析(VDA)、音频输入(AI)、音频输出(AO)、音频编码(AENC) 、音频解码(ADEC) 、区域管理(REGION)等模块。主要的处理流程介绍如下
VI 模块捕获视频图像,可对其做剪切、缩放、镜像等处理,并输出多路不同分辨
率的图像数据。
解码模块对编码后的视频码流进行解码,并将解析后的图像数据送 VPSS 进行图
像处理或直接送 VO 显示。可对 H.264/H.265/VC1/MPEG4/MPEG2/AVS 格式的视
频码流进行解码。
VPSS 模块接收 VI 和解码模块发送过来的图像,可对图像进行去噪、图像增强、
锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓
拍。
编码模块接收 VI 捕获并经 VPSS 处理后输出的图像数据,可叠加用户通过 Region
模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。
VDA 模块接收 VI 的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析
结果。
VO 模块接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置
的输出协议输出给外围视频设备。
AI 模块捕获音频数据,然后 AENC 模块支持按多种音频协议对其进行编码,最后
输出音频码流。
用户从网络或外围存储设备获取的音频码流可直接送给 ADEC 模块,ADEC 支持
1.4 MMZ与模块绑定
MPP 提供系统绑定接口(HI_MPI_SYS_Bind) ,即通过数据接收者绑定数据源来建立
两者之间的关联关系(只允许数据接收者绑定数据源) 。绑定后,数据源生成的数据将
自动发送给接收者。
Uboot环境变量中定义linux内核使用的内存大小
加载内核模块分配MMZ内存大小
例如512M的DDR
DDR:
1.5 HiMPP 支持的绑定关系
数据源 | 数据接收者 |
---|
VI | VO |
| VENC |
| VDA |
| VPSS |
| PCIV |
VPSS | VO |
| VENC |
| VDA |
| VPSS |
| PCIV |
VDEC | VPSS |
| VO(只能是标清设备或 single 模式分割) |
| VDA |
| PCIV |
vo(WBC) | VO |
| VENC |
| VPSS |
| PCIV |
AI | AENC |
| AO |
ADEC | AO |
1.6 函数约定说明
Open/Close
Open/Close 操作用来打开、关闭那些可枚举的设备。
Create/ Destroy
Create/ Destroy 操作用来创建、销毁那些不可枚举的设备。
Handle
Handle 用来标识一个“不可枚举”类型的设备。Handle 只在本进程内有效,也就是说 Handle 不可以跨进程传递Handle 是一个 32bit 的数据,其低 8bit 表示设备的 ID,高 16bit 表示模块 ID。比如0x00110000 表示模块 ID 为 0x11 的第 0 个设备。
Attach /Detach
Attach/Detach 用来绑定、解绑定两个设备直接的关联。
Attach 后,目的设备将自动处于 Start/Enable 状态; Detach 后,目的设备将自动处于Stop/Disable 状态。
对于多级绑定,要求从最后一个设备逐级向前绑定,解绑定的顺序则相反,要求逐级向后。**
** 当绑定在一起的多个设备可以设置相同属性时,必须通过最后一个设备进行设置。
Get/Put和Acquire/Release推荐成对使用
1.7 MPP优缺点
1.7.1 MPP优点
内存VB管理和模块绑定子系统,proc运行时调试信息是MPP构架节约用户时间最大的设计。
大部分外围接口都封装好,用户调用接口直接使用,快速,方便。
1.7.2 MPP缺点
没有相应子系统的源代码。出了问题,有bug必须需要海思查看,解决。
MPI接口和标准linux接口差异非常大,构架也不一样。有问题需要找海思。中间时间成本。
不利于研发技术积累。
2: MPP和UNF对比
从函数命名,参数,使用方法来看。UNF和MPP分开比较早,UNF构架在上面函数约定说明基础上的MPI接口封装了一层,UNF->MPI调用,UNF开发支持更多的模块接口。MPP的模块接口比较固定,比UNF少得多,增强了MMZ内存,VB管理功能,模块绑定功能。
MPP由HI_MPI_SYS_Bind来绑定两个模块,而UNF还是使用Hi_XXX_Attach来绑定两个模块。
MPP内存管理MMZ,VB也比UNF更细致,强大。
UNF支持更多的外设模块。如TUNER(广播电视),CIPHER(芯片内部加密),Subtitle(字幕),PDM(低功耗),SCI(智能卡),GPU等。
UNF开源。在一些模块的实现中,也使用了一些开源项目,如FFmpeg,Alsa,wpa_supplicant等。
3: 3798芯片UNF处理构架
3.1 应用架构
海思媒体处理平台( MSP)实现了对海思高清机顶盒解决方案处理器中媒体、图形以
及外设的屏蔽和封装,对应用软件直接提供 API( Application Program Interface)接口
完成相应功能。典型的应用架构如下图所示
3798比35xx多了一个UNF层,它在MPI基础上进行了一层封装
软件架构主要包含以下 4 层:
UNF层
媒体处理平台( MSP)对外统一的应用开发接口。
MPI层
处理器各模块硬件能力实现层的用户态部分。
DRV层
处理器各模块硬件能力实现层的内核态部分。
HAL层
处理器各模块的硬件抽象层。
3.2 3798SDK 概览(功能介绍)
媒体处理平台( MSP)中所有模块按照功能可以分为媒体处理,图形处理,外设处理 3
类:
媒体处理
DEMUX、 AVPLAY、 SOUND、 DISPLAY、 VO、 HDMI、 PVR、 VDEC、 VENC、
ADEC、 AENC、 VI、 AI
图形处理
HIFB、 HIGO、 TDE、 JPEG、 JPGE、 GPU
外设处理
Cipher、 OTP、 PMOC、 Frontend、 I2C、 SCI、 KEYLED、 GPIO、 IR、 WDG、 C51
3.3 3798内存管理
3.3.1 Mmz内存
3798的mmz内存在uboot bootargs环境变量中分配,在内核中大块连续内存使用dma_alloc_from_contiguous(drv/mmz/drv_media_mem_v2.c)来分配内存, 小块内存使用kmalloc分配,并且使用内核链表标记管理。和应用层交互使用mamp,umamp,并使用内核链表管理内存。这点和35xx DVR芯片有很大区别,DVR芯片模块内存和linux内核使用内存不再一个地方,应该是通过物理地址往虚拟地址映射来使用的。
具体就是在bootargs中配置mem=1G mmz=ddr,0,0,435M
查看内存使用情况跟监控相同,通过cat /proc/media-mem
3.3.2 解码vid内存
关于码流解码过程中视频缓冲buffer u32VidBufSize配置:
创建avplay时,可以指定视频es buf大小,参考如下代码
Es buf的作用存储码流原始es流。
这个buffer的大小跟码流分辨率、码率都有关系,一般情况下,D1/720P/1080P/4K 码流的配置为 2M/3M/5M/16M 即可,特殊码流除外。
3.4 3798模块
DEMUX:
数据输入模块。IF,TSI,RAM。
NVR使用的是RAM接口输入ES码流。
VI:
虚拟VI
VDEC
解码器模块提供一组函数指针,供解码使用。目前有264,mpeg4两个。
视频解码的简单流程是:
步骤 1 VDEC 从 Demux 或 ES buffer 获取 ES 数据送 Firmware 进行解码( Firmware: StreamInput(VDEC->Firmware))。
步骤 2 Firmware 将解码完的 1D 或者 2D 帧存放在帧 buffer 中。
步骤 3 VDEC 从帧 buffer 中获取( Firmware: Frame Output(Firmware->VDEC))视频帧放入其帧队列中。
步骤 4 VPSS 在线程中从 VDEC 的帧队列中取帧做解码后处理( Frame Output(VDEC->VPSS))。
步骤 5 AVPLAY 向 VDEC 获取视频帧, VDEC 从 VPSS 获取视频帧返回给 AVPLAY。
SYNC:
音视频同步工具
DISPLAY:显示设备
WINDOW:显示通道
msp 目录下的“ windowXXYY”节点的中的 XX 为显示通道的编号, YY 为 window 序号。比如 window0100, bit[15:8]的 01 表示该 window 基于 DISPLAY1 创建, bit[7:0]的00 表示 window 序号为 0。有支持3D显示
DISP( Display)模块接收 WINDOW 提供的视频图像、 Frame Buffer 提供的图形画
面,进行图像叠加处理和图像色彩调节,并将叠加后图像通过多种视频输出端口输出
给显示设备;此外, DISP 支持获取视频输出端口上的包含视频和图形的完整图像。
窗口类型
- Display:独立显示窗口
- Virtual:虚拟窗口
- Main:同源显示主窗口
- Slave:同源显示从窗口
SO( Subtitle Output)
PDM
PDM(Product Data Manager)模块用于管理与产品相关的数据,包括基本参数、开机画
面参数及数据、瞬播参数及数据的管理
用户通过镜像制作工具制作基本参数镜像与瞬播镜像,通过烧写工具烧写至 FLASH,PDM 模块在 BOOT 下将基本参数及瞬播数据读入内存,并传递至 KERNEL。 KERNEL下瞬播从 PDM 模块获取参数及数据,调用 DEMUX、 VDEC、 VO 等模块驱动,完成瞬播播放
demux
DEMUX 模块接收 DEMOD 或内存中的 TS 流,按客户应用程序(下简称 APP)的设
置提取出其中的 SEC 数据、 PES 数据、 TS 数据、音频数据和视频数据。 APP 获取
SEC 数据、 PES 数据和 TS 数据进行处理。 AVPLAY 获取音频数据进行处理, VDEC 获
取视频数据进行处理。
AVPLAY 主要依赖 ADEC/VDEC/DEMUX 等模块,其向应用或中间件播放器提供基本
的播放业务相关接口。
3.5 3798和NVR模块使用方面区别:
3798模块中vpss,sync等模块是其它模块自动创建,自动绑定处理的。这点和NVR系列芯片差别很大,nvr中vi,vpss,vo都需要用户自己绑定,设置属性,而3798中vpss,vo创建,属性设置是在上层模块中自动处理的(少量设置属性上层传递下去)。
简单来说,MPP接口只实现了基本的功能接口,没有封装或很少封装。
UNF 应用层实现了很多业务接口,对底层驱动进行了多层封装,并且实现了一些常用的业务。
4: 3798代码祥看
4.1 分析方法
4.1.1:查看代码,静态分析应用层,驱动层实现方法
使用Source insight分析kernel,msp代码。
4.1.2:图示静态分析函数调用关系
使用cflow, tree2dotx,和dot生成函数静态调用图。
4.1.3:图示动态分析函数调用关系:库函数,系统调用,内核调用
- 使用ltrace分析系统库函数
- strace分析系统调用流程
- 使用Ftrace,valgrind,gprof2dot生成运行时调用图。
- Perf分析内核空间调用。发现一些依赖库在ubuntu12.04里面不可用。
这一步实现了valgrind动态分析,其它的几个工具需要交叉编译,TODO。使用Perf可能需要换更新的linux版本。
其它还有time(大概运行时间),gconv(覆盖率统计)等分析方式。
可以从lsmod中很清晰的看出来hi_media hi_mmz hi_common这几个模块的基础地位。hi_media基础模块管理,hi_mmz提供内存管理,hi_common提供proc,log等基础功能,符号导出供其他海思模块使用。
Common
- COMMON 模块是 SDK 的基础模块,提供的 API 主要供 SDK 的其他模块调用。
COMMON 模块现在主要有以下功能:- 系统初始化和去初始化;
- 版本信息获取;
- 获取时间戳;
- 寄存器读写、映射功能;
- MMZ 内存使用;
- 调试信息控制;
- Flash 操作。
4.2.1 基本通用模块应用层接口common/api
4.2.2 基本通用模块驱动接口common/dev
设备模型结构体
内核到用户往用户空间写文件调试
drv_file_ext.c 提供内核空间创建,读写用户空间文件函数,/mnt文件存储路径。提供了内核数据导出到用户空间的方法。
应该是echo * > /proc/msp/** 这种调试方式的实现方式
内核log日志
内核模块管理
内核Proc的实现
drv_userproc.c proc使用红黑树管理目录路径
创建销毁目录,路径在这里实现,具体每一个不同模块的proc文件读写在每个模块内部实现,使用函数指针。
4.2.3 业务模块应用层通用组件source/component
/home/andy/HiSTBLinuxFS/source/component 这个目录里面很多目录只有.so .a库,没有源代码。
/source/component/hiplayer 没有源码。
hi_debug.h 各种调试宏定义,不同级别调试定义
HI_MALLOC 内存分配使用了内核链表管理
内存在一个模块内分配,往用户空间传递物理地址,其它模块使用这个物理地址映射到虚拟地址,这样来共享内存
海思这里大多数模块都只提供了编译后的库
4.3 海思专用组件
4.3.1 海思MSP模块应用层接口source/msp/api
Avplay分析
上图插图的是svg格式图片,可以使用IE,firefox打开,最好使用专门的看图软件打开。
4.3.2 海思MSP模块驱动层接口source/msp/drv/
VENC
VENC 模块的具体工作原理如上图所示,注意图中褐色箭头,是 VENC 调用到VPSS 模块,来对帧信息做缩放处理后编码的数据流,该处理只用于外部用户送帧的模式。若在绑定情况下出现帧信息分辨率与编码分辨率不匹配, VENC 模块会通知绑定的前级模块进行缩放处理,而不会自行调用 VPSS 模块缩放。
avplay
主要负责响应外部控制命令的执行、状态查询上报、使解码后的帧数据通过同步控制
模块输出
avplay初始化分析
IOCTL实现分析 基于avplay
在HI_DRV_DEV_Register中注册
avplay 多个实例如何找到对应实例
drv中的实现
VPSS
VDEC
Avplay解码ES流过程
步骤 1 初始化并配置相关设备,如 DISPLAY、 VO、 SOUND、 DEMUX 设备等。
步骤 2 调用 HI_UNF_AVPLAY_Init 接口初始化 AVPLAY 设备。
步骤 3 调用 HI_UNF_AVPLAY_GetDefaultConfig 接口获取缺省的 AVPLAY 配置,注意选择HI_UNF_AVPLAY_STREAM_TYPE_ES 模式。
步骤 4 根据应用需要,修改码流属性参数。
步骤 5 调用 HI_UNF_AVPLAY_Create 接口创建 AVPLAY 播放器。
步骤 6 调用 HI_UNF_AVPLAY_ChnOpen 接口打开音频或视频通道。
步骤 7 调用 HI_UNF_AVPLAY_SetAttr 接口设置音频解码器或视频解码器属性,同步设置为自由播放。
步骤 8 调用 HI_UNF_SND_CreateTrack 创建 Track。
步骤 9 调用 HI_UNF_SND_Attach 接口将音频播放器与 Track 绑定。
步骤 10 调用 HI_UNF_VO_AttachWindow 接口将视频播放器与 VO 的窗口绑定。
步骤 11 调用 HI_UNF_VO_SetWindowEnable 接口使能窗口。
步骤 12 调用 HI_UNF_AVPLAY_Start 接口发送 Start 命令,开始播放音频或视频。
步骤 13 调用 HI_UNF_AVPLAY_GetBuf 接口申请存放音频或视频的缓冲区。
步骤 14 通过内存拷贝或文件读取等方式,将音频或视频数据直接写到缓冲区中。
步骤 15 调用 HI_UNF_AVPLAY_PutBuf 接口更新音视频缓冲区中的读写指针。
步骤 16 播放任务结束后,首先调用 HI_UNF_AVPLAY_Stop 接口停止 AVPLAY,再调用HI_UNF_AVPLAY_Destroy 接口销毁 AVPLAY,释放相关资源。
步骤 17 关闭相关设备,如 DISPLAY 设备、 VO 设备、 SOUND 设备、 DEMUX 设备和AVPLAY
MCE实现了启动过程画面的平滑切换
IR
驱动层
负责完成红外模块的中断处理,在底半部中遍历所有协议并调用 match 函数来判定当前收到的红外帧属于哪种协议。如果没有匹配,则丢弃当前帧。完成最顶层的错误处理:如果当前帧当前时刻还不能被解析,则会等待大约 200ms 之后再次尝试,如果失败,则丢弃当前帧。
协议适配层
完成各个协议的初始化工作,向上提供遍历协议的接口,向下提供容纳协议描述符的存储空间。
协议处理层
协议处理层完成判定:由从某个 symbol 开始的一连串 symbol 是不是符合本协议的一
帧。在判定某一帧符合本协议的前提条件下,完成一帧 symbol 的解析,将对应的键值解析出来。如果在解析过程中出错,或者判定是本协议的帧,但无法解析时,完成错误处理。
4.4 Sample_mosaic分析
应用层调用
上图是部分截图,完整大的原图见目录中svg文件
valgrind --tool=callgrind ./sample_mosaic 1080P.h264 h264
系统调用
Strace分析
5:小结:
海思MPI驱动其实就是在标准linux驱动基础之上封装了一层,增加了log,proc,内存管理(映射,链表)功能。MPI是在标准linux驱动基础上封装了DVR常用外围逻辑接口,并且统一并简化了接口操作方式,原来各个外围子系统操作方式差别很大,不同实现框架,实现方式使用方法也不一样(如VI,VO,AI/AO,VPSS,如果自己用标准linux子系统实现要复杂得多)。
应用层MPI接口是在MPI驱动基础上再次封装了一层,封装了驱动的调用,配置等操作,在驱动功能的基础上进一步封装完成了一些简单的业务功能接口,方便了用户的使用。UNF就是在MPI基础上增加了业务逻辑封装,把常用的业务逻辑封装成接口,大大方便用户的应用程序开发。
5.1 UNF相对标准内核模块区别
5.1.1 Proc读写调试。
实时查询运行状态,修改设置。通用的proc目录、文件创建销毁有通用接口。读写proc文件在每个内核模块中有单独实现。
5.1.2 内核空间往应用空间写文件
方便调试,Dump内核数据到用户控件方便定位Bug。
5.1.3Log管理
日志打印分级,实时修改。如何实现的见前面章节。
5.1.4内存映射
物理地址,虚拟地址映射关系管理,小块内存使用kmalloc申请,大块内存使用dma_alloc_from_contiguous申请。
使用内核链表管理。
打印到proc,log模块中,实时查看运行状态,内存。
方便查找,打印。
在多个内核共享数据也是这样实现的。
5.2代码分析方法
5.2.1 查看代码,静态分析应用层,驱动层实现方法
5.2.2 图示静态分析函数调用关系.
Cflow, dot
5.2.3 图示动态分析函数调用关系:库函数,系统调用,内核调用
Ltrace, strace, valgrind, perf
具体分析方法,使用工具见前面章节。
参考文档:
- 03-HMS 开发指南.pdf
- HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide
- HiMPP V3.0 媒体处理软件开发参考.pdf
- HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide
- 3798m SDK 代码
- HMS API Development Reference.chm
- HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide\09-API Reference
相关文章
如何编写Linux驱动
本文介绍了编写驱动必备基础知识,编写驱动的难点之处。并从按键驱动到Sensor驱动简单介绍示范了驱动编写过程。并给出了驱动学习方法和评价驱动能力的技术指标!
如何实现自己的操作系统
作为一个程序员,你肯定设想过创造属于自己的操作系统,这其中涉及非常多的知识。本文大概介绍了涉及的知识点,并给出了相关书籍和参考源代码仓库!
手把手教你构建 C 语言编译器
“手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的 C 语言编译器,尽管有许多语法并不支持。
系列教程
全部文章RSS订阅
Embeded系列
Embeded 分类 RSS 订阅