原创

  • 文章写了好多年了,只在一个网站发布过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内核使用的内存大小

setenv 'mem=288M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:768K(boot),2304K(sdr021000),13M(rootfs)'

加载内核模块分配MMZ内存大小

insmod mmz.ko mmz=anonymous,0,0x8E000000,792M anony=1

例如512M的DDR

DDR:

DDR:                                                          
-----------|----------|  0x80000000   # Memory managed by OS.             
   64M     |   OS     |                                                
           |          |                                                
-----------|----------|  0x84000000   # Memory managed by MMZ block anonymous.         
   448M    |    MMZ   |                                                
           |          |                                                
-----------|----------|  0xA0000000   # Memory managed by MMZ block jpeg.                      

1.5 HiMPP 支持的绑定关系

数据源数据接收者
VIVO
VENC
VDA
VPSS
PCIV
VPSSVO
VENC
VDA
VPSS
PCIV
VDECVPSS
VO(只能是标清设备或 single 模式分割)
VDA
PCIV
vo(WBC)VO
VENC
VPSS
PCIV
AIAENC
AO
ADECAO

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
    Get/Put 用来往某个模块输入数据。

  • Acquire/Release
    Acquire/Release 用来从某个模块获取数据。

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内核使用内存不再一个地方,应该是通过物理地址往虚拟地址映射来使用的。

# cat /proc/cmdline
mem=1G console=ttyAMA0,115200 root=/dev/romblock3 rootfstype=squashfs mtdparts=hinand:5M(boot),1M(baseparam),7M(kernel),18M(rootfs),64M(appfs),16M(qte),4M(config),12M(log),1M(logo) mmz=ddr,0,0,500M

具体就是在bootargs中配置mem=1G mmz=ddr,0,0,435M

查看内存使用情况跟监控相同,通过cat /proc/media-mem

3.3.2 解码vid内存

关于码流解码过程中视频缓冲buffer u32VidBufSize配置:

创建avplay时,可以指定视频es buf大小,参考如下代码

Ret = HI_UNF_AVPLAY_GetDefaultConfig(&AvplayAttr, HI_UNF_AVPLAY_STREAM_TYPE_ES);
AvplayAttr.stStreamAttr.u32VidBufSize = 2*1024*1024;
Ret |= HI_UNF_AVPLAY_Create(&AvplayAttr, &hAvplay[i]);

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(覆盖率统计)等分析方式。

4.2 基础模块:hi_media hi_mmz hi_common

​ 可以从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

HiSTBLinuxFS/source/common/api
mpi_mem.c
MEM_POOL_Init 应用层的内存池,重复利用。
mpi_memmap.c
映射使用链表区分。管理mmap映射
 
 
mpi_module.c
HI_MODULE_Init中有打开内存操作,内存统一管理。
模块id,名称注册,相互查找。
 
mpi_stat.c
线程时间归零,等相关处理
 
hi_common.c
proc,sys一些路径目录增删提供了用户层接口,mmz内存管理用户层接口
 
mpi_userproc
用户层提供线程 通用proc接口,proc读写是在其它模块内部实现的,函数指针形式
 
 
注:himedia 相关模块是海思自己实现的内核子系统。有自己实现的总线,驱动,设备模型结构体。也是在标准linux模型基础上封装的。
 

4.2.2 基本通用模块驱动接口common/dev

HiSTBLinuxFS/source/common/dev
drv_dev_ext_k.c 重要
HI_DRV_DEV_Init 所有模块宏定义在这里,注册模块结构体在这里
himedia.c himedia_bash.c     模块注册,注销基础函数
#define DYNAMIC_MINORS 128 /* like dynamic majors */            himedia主设备号218
static unsigned char himedia_minors[DYNAMIC_MINORS / 8];
 
typedef struct tagPM_BASEDEV_S{
    HI_S32        id;
    const HI_S8    * name;
    struct device    dev;
}PM_BASEDEV_S;
 
#define TO_PM_BASEDEV(x) container_of((x), PM_BASEDEV_S, dev)
 
typedef struct tagPM_BASEOPS_S {
    HI_S32  (*probe)(PM_BASEDEV_S *);
    HI_S32  (*remove)(PM_BASEDEV_S *);
    HI_VOID (*shutdown)(PM_BASEDEV_S *);
    HI_S32  (*prepare)(PM_BASEDEV_S *);
    HI_VOID (*complete)(PM_BASEDEV_S *);
    HI_S32  (*suspend)(PM_BASEDEV_S *, pm_message_t state);
    HI_S32  (*suspend_late)(PM_BASEDEV_S *, pm_message_t state);
    HI_S32  (*resume_early)(PM_BASEDEV_S *);
    HI_S32  (*resume)(PM_BASEDEV_S *);
}PM_BASEOPS_S;
 
 
typedef struct tagPM_BASEDRV_S {
    HI_S32  (*probe) (PM_BASEDEV_S *);
    HI_S32  (*remove)(PM_BASEDEV_S *);
    HI_VOID (*shutdown)(PM_BASEDEV_S *);
    HI_S32  (*suspend)(PM_BASEDEV_S *, pm_message_t state);
    HI_S32  (*suspend_late)(PM_BASEDEV_S *, pm_message_t state);
    HI_S32  (*resume_early)(PM_BASEDEV_S *);
    HI_S32  (*resume)(PM_BASEDEV_S *);
    struct device_driver driver;
}PM_BASEDRV_S;
 
typedef struct tagPM_DEVICE_S  {
    HI_S32 minor;
    const HI_S8 *name;
    struct module *owner;
    const struct file_operations *app_ops;
    PM_BASEOPS_S *base_ops;
    struct list_head list;
    struct device *app_device;
    PM_BASEDEV_S *base_device;
    PM_BASEDRV_S *base_driver;
}PM_DEVICE_S;
 
static struct class *himedia_class;
static struct file_operations himedia_fops = {
    .owner        = THIS_MODULE,
    .open        = himedia_open,
};
 
设备模型结构体
struct himedia_devobj {
    PM_BASEDEV_S pdev;
    char name[1];
};
 
typedef struct tagPM_BASEDEV_S{
    HI_S32        id;
    const HI_S8    * name;
    struct device    dev;
}PM_BASEDEV_S;
 
海思模块顶层总线himedia_bus
struct device himedia_bus = {
         .init_name        = "himediaBusDev",
         .release    = himedia_bus_release
};  /*top level bus, parent and bus member are both NULL*//*CNcomment:这是顶层总线,parent 和 bus 成员为 NULL*/
struct bus_type himedia_bus_type = {
         .name                = "himediaBus",
         .dev_attrs        = himedia_dev_attrs,
         .match               = himedia_match,
         .uevent              = himedia_uevent,
         .pm                     = HIMEDIA_PM_OPS_PTR,
};
定义HIMEDIA_PM_OPS_PTR
                            static struct dev_pm_ops himedia_dev_pm_ops = {
                                     .prepare        = himedia_pm_prepare,
                                     .complete       = himedia_pm_complete,
                                     .suspend        = himedia_pm_suspend,
                                     .resume         = himedia_pm_resume,
                                     .freeze         = himedia_pm_freeze,
                                     .thaw           = himedia_pm_thaw,
                                     .poweroff       = himedia_pm_poweroff,
                                     .restore        = himedia_pm_restore,
                                     .suspend_noirq  = himedia_pm_suspend_noirq,
                                     .resume_noirq   = himedia_pm_resume_noirq,
                                     .freeze_noirq   = himedia_pm_freeze_noirq,
                                     .thaw_noirq     = himedia_pm_thaw_noirq,
                                     .poweroff_noirq = himedia_pm_poweroff_noirq,
                                     .restore_noirq  = himedia_pm_restore_noirq,
                            };
                            #define HIMEDIA_PM_OPS_PTR     (&himedia_dev_pm_ops)

内核到用户往用户空间写文件调试

drv_file_ext.c 提供内核空间创建,读写用户空间文件函数,/mnt文件存储路径。提供了内核数据导出到用户空间的方法。

应该是echo * > /proc/msp/** 这种调试方式的实现方式

内核log日志
drv_log_ioctl.h  打印日志相关。  这里日志位置可选0:串口 1:网络 2:U盘
/*structure of mode log level */
/*CNcomment: 模块打印级别控制信息结构 */
typedef struct hiLOG_CONFIG_INFO_S
{
    HI_U8 ModName[16+12];     /*mode name 16 + '_' 1 + pid 10 */
    HI_U8 u8LogLevel;         /*log level*//*CNcomment:  模块打印级别控制 */
    HI_U8 u8LogPrintPos;      /*log output location, 0:serial port; 1:network;2:u-disk*//*CNcomment:  模块打印位置控制 0:串口 1:网络 2:U盘 */
    HI_U8 u8UdiskFlag;        /* u-disk log flag */
}LOG_CONFIG_INFO_S;

hi_debug.h    log调试级别
/**Default level of the output debugging information*/
/**CNcomment: 默认的调试信息输出级别*/
#define HI_LOG_LEVEL_DEFAULT HI_LOG_LEVEL_ERROR
 
/**Level of the output debugging information*/
/**CNcomment: 调试信息输出级别*/
typedef enum hiLOG_LEVEL_E
{
    HI_LOG_LEVEL_FATAL   = 0,     /**<Fatal error. It indicates that a critical problem occurs in the system. Therefore, you must pay attention to it.*/
                                  /**<CNcomment: 致命错误, 此类错误需要特别关注,一般出现此类错误代表系统出现了重大问题 */
    HI_LOG_LEVEL_ERROR   = 1,     /**<Major error. It indicates that a major problem occurs in the system and the system cannot run.*/
                                  /**<CNcomment: 一般错误, 一般出现此类错误代表系统出现了比较大的问题,不能再正常运行 */
    HI_LOG_LEVEL_WARNING = 2,     /**<Warning. It indicates that a minor problem occurs in the system, but the system still can run properly.*/
                                  /**<CNcomment: 告警信息, 一般出现此类信息代表系统可能出现问题,但是还能继续运行 */
    HI_LOG_LEVEL_INFO    = 3,     /**<Message. It is used to prompt users. Users can open the message when locating problems. It is recommended to disable this message in general.*/
                                  /**<CNcomment: 提示信息, 一般是为提醒用户而输出,在定位问题的时候可以打开,一般情况下建议关闭 */
    HI_LOG_LEVEL_DBG     = 4,     /**<Debug. It is used to prompt developers. Developers can open the message when locating problems. It is recommended to disable this message in general.*/
                                  /**<CNcomment: 提示信息, 一般是为开发人员调试问题而设定的打印级别,一般情况下建议关闭 */
 
    HI_LOG_LEVEL_BUTT
} HI_LOG_LEVEL_E;
 
drv_media_mem_v2.c    实现了cat /proc/media-mem
 
drv_mem_ext.c 实现了 KMEM_POOL_S 的管理,
内核模块管理
drv_module_ext.c

COMMON_DRV_ModInit

         MMNGR_DRV_ModInit

        

    s32Ret = HI_DRV_MODULE_Register(HI_ID_SYS,    "HI_SYS",      HI_NULL);

    s32Ret |= HI_DRV_MODULE_Register(HI_ID_MODULE, "HI_MODULE",   HI_NULL);

    s32Ret |= HI_DRV_MODULE_Register(HI_ID_LOG,    "HI_LOG",      HI_NULL);

    s32Ret |= HI_DRV_MODULE_Register(HI_ID_PROC,   "HI_PROC",     HI_NULL);

    s32Ret |= HI_DRV_MODULE_Register(HI_ID_STAT,   "HI_STAT",     HI_NULL);

    s32Ret |= HI_DRV_MODULE_Register(HI_ID_MEM,    "HI_MEM",      HI_NULL);

 

drv_media_mem.c  使用dma_alloc_from_contiguous来分配

HI_DRV_MMZ_Init 这里根据bootargs传递的mmz参数分配内存大小。 mmz分配时的参数:<mmz_name>,<gfp>,<phys_start_addr>,<size>,<alloc_type>"

内核Proc的实现
drv_proc_ext.c
HI_DRV_PROC_Init 实现cat /proc/hisi/msp/
    proc_mkdir("graphics", g_pHisi_proc);
    proc_symlink("msp", NULL, "hisi/msp");
    proc_symlink("graphics", NULL, "hisi/graphics");

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分析
HI_MPI_AVPLAY_Create
         AVPLAY_FrcCreate  帧率控制?    
         HI_MPI_SYNC_Create     音视频同步
         AVPLAY_CreateThread

HI_MPI_AVPLAY_Create.__avplay_mpi_avplay_c

上图插图的是svg格式图片,可以使用IE,firefox打开,最好使用专门的看图软件打开。

HI_MPI_VDEC_AllocChan
         VPSS_Control(pstVdec->hVdec,VPSS_CMD_CREATEVPSS,&(pstVdec->hVpss))
        
UNF调用一般过程,最终采用ioctl调用驱动   
HI_UNF_AVPLAY_GetBuf
         HI_MPI_AVPLAY_GetBuf
                   HI_MPI_VDEC_ChanGetBuffer
                            VDEC_GetStreamBuf                /* If get but not put, return last address */    这里有对不对称调用特殊处理
                                     ioctl(s_stVdecAdpParam.s32DevFd, UMAPC_VDEC_GETBUF, &stBufParam);     
                                    
HI_MPI_AVPLAY_Start
         AVPLAY_StartVidChn
                   HI_MPI_SYNC_Start
                   HI_MPI_VDEC_ChanStart
                             ioctl(g_SyncDevFd, CMD_SYNC_START_SYNC, &SyncId);
         HI_MPI_SYNC_Play 结构体状态改变
         AVPLAY_Play             结构体状态控制
         HI_MPI_STAT_Event                                   
 
HI_MPI_AVPLAY_Stop
         AVPLAY_StopVidChn
                   HI_MPI_VDEC_ChanStop
                            VDEC_ChanStop      这里调用函数指针
                   HI_MPI_VDEC_ResetChan
                            VDEC_ResetStreamBuf
                            VDEC_VPSSCMD(hVdec, VPSS_CMD_RESETVPSS, HI_NULL);         调用函数指针,指向VPSS_Control
                   AVPLAY_ResetWindow
                   HI_MPI_SYNC_Stop
         HI_MPI_STAT_Event(STAT_EVENT_VSTOP, 0);
 
VDEC使用函数指针集来支持多种解码方式,内部vfmw。mjpeg,内部硬件265协处理器
mpi_vdec_vpu.c
HI_MPI_VDEC_Init
         HI_CODEC_Register(VDEC_MJPEG_Codec());
         HI_CODEC_Register(VDEC_VFMW_Codec());
 
static HI_CODEC_S hi_codecVPU_entry =
{
    .pszName                  = "VPU",
    .unVersion                 = {.stVersion = {1, 0, 0, 0}},
    .pszDescription = "Hisilicon H265 codec",
 
    .GetCap                      = HI_VPU_GetCap,
    .Create                       = HI_VPU_Create,
    .Destroy            = HI_VPU_Destroy,
    .Start                           = HI_VPU_Start,
    .Stop                            = HI_VPU_Stop,
    .Reset                         = HI_VPU_Reset,
    .SetAttr             = HI_VPU_SetAttr,
    .GetAttr            = HI_VPU_GetAttr,
    .DecodeFrame          = HI_VPU_DecodeFrame,
    .EncodeFrame = HI_NULL,
    .RegFrameBuffer = HI_VPU_RegFrameBuffer,
    .GetStreamInfo        = HI_VPU_GetStreamInfo,
    .Control             = HI_VPU_Control,
};
 
avplay,vi和 vo之间使用vpss关联。
HI_UNF_VO_DetachWindow
         HI_MPI_AVPLAY_DetachWindow
                   HI_MPI_VDEC_DestroyPort
         HI_MPI_VI_Detach(hSrc, hWindow);
                   VI_DetachFromVpss
        
HI_MPI_AVPLAY_ChnClose
         AVPLAY_FreeVidChn
                   HI_MPI_VDEC_ChanBufferDeInit
                            VDEC_VFMWSpecCMD(hVdec, VFMW_CMD_DETACHBUF, HI_NULL);
                            VDEC_DestroyStreamBuf(pstVdec->hStreamBuf);                   ummap内存块,ioctl通知内核vdec销毁内存
                   AVPLAY_FreeVdec
                            HI_MPI_VDEC_FreeChan
                                     VPSS_Control(pstVdec->hVdec,VPSS_CMD_DESTORYVPSS,&(pstVdec->hVpss));
                                     VDEC_DestroyCodec(pstVdec);
 
HI_UNF_AVPLAY_Destroy
         HI_MPI_AVPLAY_Destroy
                   HI_MPI_SYNC_Destroy(pAvplay->hSync);
                   ioctl(g_AvplayDevFd, CMD_AVPLAY_DESTROY, &AvplayUsrAddr.AvplayId);
                  
avplay内部的线程
    (HI_VOID)pthread_join(pAvplay->AvplayDataThdInst, HI_NULL);
#ifdef AVPLAY_VID_THREAD
    (HI_VOID)pthread_join(pAvplay->AvplayVidDataThdInst, HI_NULL);
#endif
    (HI_VOID)pthread_join(pAvplay->AvplayStatThdInst, HI_NULL);          

4.3.2 海思MSP模块驱动层接口source/msp/drv/

VENC

VENC 模块的具体工作原理如上图所示,注意图中褐色箭头,是 VENC 调用到VPSS 模块,来对帧信息做缩放处理后编码的数据流,该处理只用于外部用户送帧的模式。若在绑定情况下出现帧信息分辨率与编码分辨率不匹配, VENC 模块会通知绑定的前级模块进行缩放处理,而不会自行调用 VPSS 模块缩放。

VENC_DRV_ModInit
         VENC_DRV_Init
                   HI_DRV_MODULE_Register(HI_ID_VENC, "HI_VENC", (HI_VOID*)&s_VencExportFuncs);
                            MODULE_DRV_Register
                                     ModuleMgr_Link_AddNode(g_pstKModuleHeader, &stModule); 链表管理
                                     LOGAddModule((HI_PCHAR)pu8ModuleName, (HI_MOD_ID_E)u32ModuleID);  在log模块中注册
         HI_DRV_DEV_Register(&g_VencRegisterData)
                   HI_DRV_PM_Register     海思模块管理非常重要的模块,了解这个函数实现过程就大概明白海思内核模块实现思想了
         VENC_DRV_BoardDeinit();      一些寄存器配置,reset,clock操作。
         venc调用硬件协处理器,涉及263,264,mpeg4,
    snprintf(g_VencRegisterData.devfs_name, 64, "%s", UMAP_DEVNAME_VENC);
    g_VencRegisterData.fops   = &VENC_FOPS;
    g_VencRegisterData.minor  = UMAP_MIN_MINOR_VENC;
    g_VencRegisterData.owner  = THIS_MODULE;
    g_VencRegisterData.drvops = &venc_drvops;      
avplay

  • AVPLAY 主要由以下三部分组成:
    • 音视频解码器
    • 同步播放控制模块
    • 命令与状态管理模块

主要负责响应外部控制命令的执行、状态查询上报、使解码后的帧数据通过同步控制
模块输出

avplay初始化分析
AVPLAY_DRV_ModInit
​    HI_DRV_MODULE_Register
​         HI_DRV_MODULE_Register(HI_ID_AVPLAY, AVPLAY_NAME, HI_NULL);
​             ModuleMgr_Link_AddNode  内核模块管理
​             KMODULE_MEM_POOL_AddModule
​                 KMODULE_MEM_POOL_FindNode    找到空的没有使用的节点
​                 KMODULE_MEM_POOL_MALLOC 分配内存,然后放到链表
​             LOGAddModule                    日志
​    HI_DRV_DEV_Register(&g_AvplayRegisterData)
​         HI_DRV_PM_Register(&s_umap_devs[i]);   himedia封装处理
​             himedia_device_alloc(himedia->name, -1);
​             himedia_device_add(bdev);
​                 dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
​                 device_add(&pdev->dev);
​             device_create(himedia_class, &(bdev->dev), dev, NULL, "%s", himedia->name);
​             himedia_driver_alloc(himedia->name, himedia->owner, himedia->base_ops);
​             himedia_driver_register(bdrv);
​                 driver_register(&drv->driver);
​   
 
  g_AvplayRegisterData.fops = &AVPLAY_FOPS;
  g_AvplayRegisterData.minor = UMAP_MIN_MINOR_AVPLAY;
  g_AvplayRegisterData.owner = THIS_MODULE;
  g_AvplayRegisterData.drvops = &AVPLAY_DRVOPS;  
 
  s_umap_devs[i].minor = umapd->minor;
  s_umap_devs[i].name = umapd->devfs_name;
  s_umap_devs[i].owner = umapd->owner;
  s_umap_devs[i].app_ops = umapd->fops;
  s_umap_devs[i].base_ops = umapd->drvops;
IOCTL实现分析 基于avplay

在HI_DRV_DEV_Register中注册

static struct file_operations AVPLAY_FOPS =
{
    .owner          =  THIS_MODULE,
    .open           =  AVPLAY_DRV_Open,
    .unlocked_ioctl =  AVPLAY_DRV_Ioctl,
    .release        =  AVPLAY_DRV_Close,
};      
AVPLAY_DRV_Ioctl
         HI_DRV_UserCopy(ffile->f_dentry->d_inode, ffile, cmd, arg, AVPLAY_Ioctl);
                   func(inode, file, cmd, (parg));           这里对ioctl进行了一层封装,copy_from_user,copy_to_user的自动化处理
AVPLAY_Ioctl(struct inode *inode, struct file *file, unsigned int cmd, HI_VOID *arg)  
                   CMD_AVPLAY_CREATE            
                            AVPLAY_Create
                                     HI_DRV_MMZ_AllocAndMap(BufName, MMZ_OTHERS, 0x2000, 0, &MemBuf);
                                     HI_DRV_PROC_AddModule(ProcName, HI_NULL, HI_NULL);                 这里实现了proc目录的生成,下面是读写proc
                                pProcItem->read = AVPLAY_ProcRead;
                                pProcItem->write = AVPLAY_ProcWrite;
avplay 多个实例如何找到对应实例
AVPLAY_GET_INST_AND_LOCK();
         AVPLAY_CheckHandle
                   pAvplayUsrAddr->AvplayId = hAvplay & 0xff;
                   ioctl(g_AvplayDevFd, CMD_AVPLAY_CHECK_ID, pAvplayUsrAddr);

drv中的实现

AVPLAY_Ioctl            
         AVPLAY_CheckId              
                   pAvplayUsrAddr->AvplayUsrAddr = g_AvplayGlobalState.AvplayInfo[pAvplayUsrAddr->AvplayId].AvplayUsrAddr;
VPSS

    g_VpssRegisterData.fops   = &s_VpssFileOps;
    g_VpssRegisterData.minor  = UMAP_MIN_MINOR_VPSS;
    g_VpssRegisterData.owner  = THIS_MODULE;
g_VpssRegisterData.drvops = &s_VpssBasicOps;
VPSS_DRV_Init
VPSS_CTRL_Init
         VPSS_CTRL_RegistISR     注册中断
         VPSS_CTRL_InitInstList((VPSS_IP_E)i)     管理vpss实例链表初始化
         VPSS_HAL_Init                   内存,寄存器配置
         VPSS_CTRL_CreateThread      内核线程
                   VPSS_CTRL_ThreadProc
                            VPSS_CTRL_CreateTask           创建任务
                            VPSS_CTRL_StartTask               开始任务
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

HI_MPI_AVPLAY_Create.__avplay_mpi_avplay_c

MCE实现了启动过程画面的平滑切换

IR

驱动层

负责完成红外模块的中断处理,在底半部中遍历所有协议并调用 match 函数来判定当前收到的红外帧属于哪种协议。如果没有匹配,则丢弃当前帧。完成最顶层的错误处理:如果当前帧当前时刻还不能被解析,则会等待大约 200ms 之后再次尝试,如果失败,则丢弃当前帧。

协议适配层

完成各个协议的初始化工作,向上提供遍历协议的接口,向下提供容纳协议描述符的存储空间。

协议处理层

协议处理层完成判定:由从某个 symbol 开始的一连串 symbol 是不是符合本协议的一
帧。在判定某一帧符合本协议的前提条件下,完成一帧 symbol 的解析,将对应的键值解析出来。如果在解析过程中出错,或者判定是本协议的帧,但无法解析时,完成错误处理。

4.4 Sample_mosaic分析

应用层调用

上图是部分截图,完整大的原图见目录中svg文件

valgrind --tool=callgrind ./sample_mosaic 1080P.h264 h264

samplemosaic-callgrind-all

系统调用

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。

HI_DRV_FILE_Open
filp_open
HI_DRV_FILE_Close
​    filp_close
HI_DRV_FILE_Read
​    pFile->f_op->read
HI_DRV_FILE_Write
​    pFile->f_op->write
HI_DRV_FILE_Lseek
​    vfs_llseek

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

具体分析方法,使用工具见前面章节。

参考文档:

  1. 03-HMS 开发指南.pdf
  2. HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide
  3. HiMPP V3.0 媒体处理软件开发参考.pdf
  4. HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide
  5. 3798m SDK 代码
  6. HMS API Development Reference.chm
  7. HiSTBAndroidV600R001C00SPC060_cn\document\01-Development Guide\09-API Reference

相关文章

如何编写Linux驱动
本文介绍了编写驱动必备基础知识,编写驱动的难点之处。并从按键驱动到Sensor驱动简单介绍示范了驱动编写过程。并给出了驱动学习方法和评价驱动能力的技术指标!
3G,4G,Wifi选型需求分析及技术简介
详细介绍了3G,4G,wifi技术类型,选型,移植,性能介绍全过程。写了好多年了,禁止转载,第一次公开发表。
海思MPP&UNF构架源代码级分析
行业中分析海思MPP内核构架,源码分析,多年经验总结积累结果。写了好多年了,禁止转载,第一次公开发表。
如何实现自己的操作系统
作为一个程序员,你肯定设想过创造属于自己的操作系统,这其中涉及非常多的知识。本文大概介绍了涉及的知识点,并给出了相关书籍和参考源代码仓库!
手把手教你构建 C 语言编译器
“手把手教你构建 C 语言编译器” 这一系列教程将带你从头编写一个 C 语言的编译器。希望通过这个系列,我们能对编译器的构建有一定的了解,同时,我们也将构建出一个能用的 C 语言编译器,尽管有许多语法并不支持。

系列教程

全部文章RSS订阅

Embeded系列

Embeded 分类 RSS 订阅


作者: 夜法之书
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 夜法之书 !
评论
数据加载中 ...
 上一篇

阅读全文

破解Gitlab EE
破解Gitlab EE 破解Gitlab EE
crack Gitlab EE。破解 Gitlab 企业版,使用各种高级功能。个人研究使用,企业请付费购买正版授权。
2021-07-09
下一篇 

阅读全文

Github Pages + jekyll 全面介绍极简搭建个人网站和博客
Github Pages + jekyll 全面介绍极简搭建个人网站和博客 Github Pages + jekyll 全面介绍极简搭建个人网站和博客
如何利用github pages搭建个人博客了?本文采用github默认支持的Jekyll,使用github action自动部署,详尽步骤全介绍。
2021-03-03