1. 多媒体

1.1. 多媒体软件架构(MMF)

多媒体软件架构(Multimedia Framework,简称MMF),用以缩短应用程序开发所需的时间。 此架构屏蔽了处理器端的复杂底层设计和差异,对应用程序提供统一且便捷的MMF Programming Interface编程接口。

MMF包含了以下功能:ISP影像前处理(包含HDR、去噪、边缘锐化等)、输入影像撷取及输出、 图像几何校正、H.265/H.264/JPEG 编解码、音频撷取及输出、音频编解码等。

1.1.1. 多媒体模块

多媒体软件分为:视频输入(Video Input,VI),图像处理子系统(Video Process Sub-System,VPSS),视频编码(Video Decoder,VENC),视频解码(VDEC), 视频输出(Video Output,VO),音频编码(AENC),音频解码(ADEC),图像信号处理器(Image Signal Processor,ISP),区域管理(REGION)等等模块。

1.1.2. 内部主要处理流程

broken

(上面图片来自 MMF媒体软件开发指南

  • VI捕捉视频图像,可对其做剪切、影像优化等处理后,再将图像数据传递给VPSS处理,处理后的数据经过编码器编码成h264/h265/jpeg等格式的码流。

  • VDEC将解码对应的视频图像文件,再将图像数据传递给VPSS处理,VO接收VPSS处理后的图像,并根据设定的时序输出到显示设备。

  • 其中的REGION可以将用户所指定的位图(Bitmap)作为OSD加到图像数据上。

1.1.3. MMF库编译

MMF源文件在SDK中middleware目录下, 如何编译请参考下 《嵌入式Linux镜像构建与部署—LubanCat-sg200x系列板卡》 中SDK middleware编译选项。

MMF提供一些使用例程,请参考下源码middleware/v2/sample,其他更多相关信息请查看下参考链接相关文档。

1.2. 解码显示测试

接下来将在LubanCat-P1板卡上,简单测试下硬件解码一个h264码流,并显示到LCD屏上。

// Video Pipeline:
//            +------+      +-------+         +------+   mipi-tx
//  h264 ---> | VDEC |----->|  VPSS | ------> |  VO  | -----------> LCD
//            +------+      +-------+         +------+
//

教程测试的例程请参考 配套例程

1.2.1. 系统控制模块初始化

系统控制根据各个处理器的特性,完成硬件各个部件的复位、基本初始化工作, 同时负责完成MMF系统各个功能模块的初始化、控制、去初始化以及管理MMF系统各个功能模块的工作状态、提供大块物理内存管理等功能。

在使用MMF前,都要初始化该模块,具体接口说明可以简单参考下 MMF媒体软件开发指南 ) 。 本章的例程是使用middleware/v2/sample/common中提供的便利函数接口直接初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/************************************************
* Init SYS and common VB
************************************************/
memset(&stVbConf, 0, sizeof(VB_CONFIG_S));
stVbConf.u32MaxPoolCnt              = 1;

u32BlkSize = COMMON_GetPicBufferSize(stSize.u32Width, stSize.u32Height, PIXEL_FORMAT_RGB_888, DATA_BITWIDTH_8
            , enCompressMode, DEFAULT_ALIGN);
stVbConf.astCommPool[0].u32BlkSize  = u32BlkSize;
stVbConf.astCommPool[0].u32BlkCnt   = 4;
// SAMPLE_PRT("common pool[0] BlkSize %d\n", u32BlkSize);

s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf);
if (s32Ret != CVI_SUCCESS) {
  SAMPLE_PRT("system init failed with %#x\n", s32Ret);
  return -1;
}

在调用 SAMPLE_COMM_SYS_Init 函数之前,我们还配置一个common VBPool, 该视频内存区块池主要向各模块(VPSS/VO…)提供大块物理内存管理功能, 负责内存的取得、分配和回收,让物理内存资源在各个媒体处理模块中分享使用。

1.2.2. VO模块初始化

VO(Video Output 视频输出)模块从内存读取视频和图形数据,并通过相应的显示设备输出视频和图形。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/************************************************
* Init VO
************************************************/
SAMPLE_VO_CONFIG_S stVoConfig;
RECT_S stDefDispRect  = {0, 0, 1080, 1920};
SIZE_S stDefImageSize = {1080, 1920};
VO_CHN VoChn = 0;

s32Ret = SAMPLE_COMM_VO_GetDefConfig(&stVoConfig);
if (s32Ret != CVI_SUCCESS) {
  printf("SAMPLE_COMM_VO_GetDefConfig failed with %#x\n", s32Ret);
  return s32Ret;
}

stVoConfig.VoDev     = 0;
stVoConfig.stVoPubAttr.enIntfType  = VO_INTF_MIPI;    // 接口类型
stVoConfig.stVoPubAttr.enIntfSync  = VO_OUTPUT_1080P30; // 屏幕分辨率
stVoConfig.stDispRect        = stDefDispRect;
stVoConfig.stImageSize       = stDefImageSize;
stVoConfig.enPixFormat       = PIXEL_FORMAT_RGB_888;  // 输出格式
stVoConfig.enVoMode  = VO_MODE_1MUX;

s32Ret = SAMPLE_COMM_VO_StartVO(&stVoConfig);
if (s32Ret != CVI_SUCCESS) {
  printf("SAMPLE_COMM_VO_StartVO failed with %#x\n", s32Ret);
  return s32Ret;
}

VO的参数配置直接使用middleware/v2/sample/common中提供的便利函数接口, 使用 SAMPLE_COMM_VO_GetDefConfig 函数获取VO默认配置,然后根据连接的屏幕,修改相关参数, 最后掉用 SAMPLE_COMM_VO_StartVO 完成VO模块初始化。

修改的参数需要根据屏幕支持分辨率,支持的显示格式,例程设置的参数是mipi 5.5寸屏幕的。

1.2.3. VPSS模块初始化

VPSS(Video Process Sub System)是视频处理子系统,支持的具体图像处理功能包括Crop、Scale 、 像素格式转换、Mirror/Flip、固定角度旋转 、鱼眼校正、 Overlay/Overlayex等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/************************************************
* Init VPSS
************************************************/
VPSS_GRP       VpssGrp0       = 0;
VPSS_GRP_ATTR_S    stVpssGrpAttr;
VPSS_CHN           VpssChn        = VPSS_CHN0;
CVI_BOOL           abChnEnable[VPSS_MAX_PHY_CHN_NUM] = {0};
VPSS_CHN_ATTR_S    astVpssChnAttr[VPSS_MAX_PHY_CHN_NUM] = {0};

stVpssGrpAttr.stFrameRate.s32SrcFrameRate    = -1;
stVpssGrpAttr.stFrameRate.s32DstFrameRate    = -1;
stVpssGrpAttr.enPixelFormat                  = PIXEL_FORMAT_YUV_PLANAR_420;
stVpssGrpAttr.u32MaxW                        = VDEC_WIDTH;
stVpssGrpAttr.u32MaxH                        = VDEC_HEIGHT;
stVpssGrpAttr.u8VpssDev                      = 0;

astVpssChnAttr[VpssChn].u32Width                    = VDEC_WIDTH;
astVpssChnAttr[VpssChn].u32Height                   = VDEC_HEIGHT;
astVpssChnAttr[VpssChn].enVideoFormat               = VIDEO_FORMAT_LINEAR;
astVpssChnAttr[VpssChn].enPixelFormat               = PIXEL_FORMAT_RGB_888;
astVpssChnAttr[VpssChn].stFrameRate.s32SrcFrameRate = -1;
astVpssChnAttr[VpssChn].stFrameRate.s32DstFrameRate = -1;
astVpssChnAttr[VpssChn].u32Depth                    = 0;
astVpssChnAttr[VpssChn].bMirror                     = CVI_FALSE;
astVpssChnAttr[VpssChn].bFlip                       = CVI_FALSE;
astVpssChnAttr[VpssChn].stAspectRatio.enMode        = ASPECT_RATIO_NONE;
astVpssChnAttr[VpssChn].stNormalize.bEnable         = CVI_FALSE;

/*start vpss*/
abChnEnable[0] = CVI_TRUE;
s32Ret = SAMPLE_COMM_VPSS_Init(VpssGrp0, abChnEnable, &stVpssGrpAttr, astVpssChnAttr);
if (s32Ret != CVI_SUCCESS) {
  printf("init vpss group failed. s32Ret: 0x%x !\n", s32Ret);
  return s32Ret;
}

s32Ret = SAMPLE_COMM_VPSS_Start(VpssGrp0, abChnEnable, &stVpssGrpAttr, astVpssChnAttr);
if (s32Ret != CVI_SUCCESS) {
  printf("start vpss group failed. s32Ret: 0x%x !\n", s32Ret);
  return s32Ret;
}

测试例程中,VPSS主要进行像素格式转换,将VDEC解码的YUV420P格式转换为RGB格式,也可以进行一些尺寸变换适应屏幕显示等等。

1.2.4. 解码模块使用

LubanCat-P1(sg2000)支持硬件解码,硬件解码模块支持解码协议有:H.264/H.265、JPEG/MJPEG, 支持的最大最小分辨率分别为:

教程测试h264解码,使用该解码模块时,分为下面几个步骤:

  • 设置Vdec模块参数,创建Vdec解码通道;

  • 向解码通道发送编码帧;

  • 解码模块没有绑定到其他模块,可以获取解码帧,手动发送到目标模块(Vpss或Vo或其他);

  • 解码测试完成,最后销毁解码通道。

创建Vdec解码通道和设置Vdec模块参数,直接使用middleware/v2/sample/common中提供的函数接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/************************************************
*  Init VDEC and VDEC VBPool
************************************************/
vdecChnCtx chnCtx0;
memset(&chnCtx0, 0, sizeof(vdecChnCtx));

chnCtx0.VdecChn = 0;
chnCtx0.stSampleVdecAttr.enType = PT_H264;
chnCtx0.stSampleVdecAttr.u32Width = VDEC_WIDTH;
chnCtx0.stSampleVdecAttr.u32Height = VDEC_HEIGHT;
chnCtx0.stSampleVdecAttr.enMode = VIDEO_MODE_FRAME;
chnCtx0.stSampleVdecAttr.enPixelFormat = PIXEL_FORMAT_YUV_PLANAR_420;
chnCtx0.stSampleVdecAttr.u32DisplayFrameNum = 3;
chnCtx0.stSampleVdecAttr.u32FrameBufCnt = DEFAULT_MAX_FRM_BUF << 1;
chnCtx0.bCreateChn = CVI_FALSE;

// VDEC VBPool
SAMPLE_COMM_VDEC_SetVbMode(3);
SAMPLE_VDEC_ATTR astSampleVdec[VDEC_MAX_CHN_NUM];

astSampleVdec[0].enType = PT_H264;
astSampleVdec[0].u32Width = VDEC_WIDTH;
astSampleVdec[0].u32Height = VDEC_HEIGHT;
astSampleVdec[0].enMode = VIDEO_MODE_FRAME;
astSampleVdec[0].stSampleVdecVideo.enDecMode = VIDEO_DEC_MODE_IP;
astSampleVdec[0].stSampleVdecVideo.enBitWidth = DATA_BITWIDTH_8;
astSampleVdec[0].stSampleVdecVideo.u32RefFrameNum = 2;
astSampleVdec[0].u32DisplayFrameNum = 3;
astSampleVdec[0].u32FrameBufCnt = DEFAULT_MAX_FRM_BUF << 1;
astSampleVdec[0].enPixelFormat = PIXEL_FORMAT_YUV_PLANAR_420;

s32Ret = SAMPLE_COMM_VDEC_InitVBPool(1, &astSampleVdec[0]);
if (s32Ret != CVI_SUCCESS) {
  CVI_VDEC_ERR("SAMPLE_COMM_VDEC_InitVBPool fail\n");
}

// init vdec
s32Ret = SAMPLE_COMM_VDEC_Start(&chnCtx0);
if (s32Ret != CVI_SUCCESS) {
  printf("start VDEC fail for %#x!\n", s32Ret);
  goto out;
}

以教程测试例程为例,代码如上所示。例程只使用一个通道,直接填充vdecChnCtx结构体来设置解码参数, 主要的参数是宽高、码流发送方式、解码协议类型等等。

  • 解码协议类型,有PT_H264、PT_H265、PT_MJPEG/PT_JPEG。

  • 码流发送方式,目前只支持按帧发送,每次发送完整一帧码流到解码器,需保证每次调用发送接口发送的码流必须为一帧。参数设置为 VIDEO_MODE_FRAME

  • 解码帧存分配方式,支持Common Mode和User Mode。Common Mode不需要使用者管理,自动创建,User Mode需要用户手动创建。

手动创建VDEC VBPool,解码帧存分配方式的只支持COMMON 和 USER Mode,,我们这里是使用USER Mode,自行创建, 这里是调用SAMPLE_COMM_VDEC_InitVBPool函数接口,配置该函数的参数和前面设置解码参数一样,

1
2
3
4
5
6
/************************************************
*  send stream to VDEC
*************************************************/
initVdecThreadParam(&chnCtx0, &chnCtx0.stVdecThreadParamSend, video_path, -1);

SAMPLE_COMM_VDEC_StartSendStream(&chnCtx0.stVdecThreadParamSend, &chnCtx0.vdecThreadSend);

向解码通道发送编码帧,直接使用middleware/v2/sample/common中提供的函数接口 SAMPLE_COMM_VDEC_StartSendStream , 将创建一个线程,该线程会一直向解码通道发送编码帧。

测试例程没有绑定解码模块到其他模块,获取解码帧使用手动接收的方式,接收的帧发送至VPSS,最后发送到VO模块显示, 详细请参考 配套例程

1.2.5. 例程测试

获取交叉编译, 请根据部署的板卡系统选择交叉编译器,如果是arm64设置aarch64-linux-gnu,如果是riscv64设置riscv64-linux-musl-x86_64

# 获取交叉编译器(如果前面测试获取了交叉编译器,就不需要),教程测试rsiv64系统,并设置交叉编译器到环境变量
wget https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz
cd /workspace/tpu-mlir
tar xvf host-tools.tar.gz
cd host-tools
export PATH=$PATH:$(pwd)/gcc/riscv64-linux-musl-x86_64/bin
# 如果是aarch64系统,设置交叉编译器到环境变量
#export PATH=$PATH:$(pwd)/gcc/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin

# 检测交叉编译
riscv64-unknown-linux-musl-gcc -v
Using built-in specs.
COLLECT_GCC=riscv64-unknown-linux-musl-gcc
COLLECT_LTO_WRAPPER=/home/dev/sg2000/cvi_mmf_sdk_test/host-tools/gcc/riscv64-linux-musl-x86_64/bin/
../libexec/gcc/riscv64-unknown-linux-musl/10.2.0/lto-wrapper
Target: riscv64-unknown-linux-musl
Configured with: /mnt/ssd/jenkins_iotsw/slave/workspace/Toolchain/build-gnu-riscv_4/
./source/riscv/riscv-gcc/configure #省略................
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 10.2.0 (Xuantie-900 linux-5.10.4 musl gcc Toolchain V2.6.1 B-20220906

拉取例程,然后交叉编译器:

# 获取配套例程文件并切换到mobilenetv2目录下
git clone https://gitee.com/LubanCat/lubancat_sg2000_application_code.git

# 切换到例程目录下
cd lubancat_sg2000_application_code/example/ 待加

# 执行命令,交叉编译:
./build.sh -a musl_riscv

然后复制交叉编译例程到板卡:

# 复制rsiscv交叉编译的程序
scp install/build_riscv_musl/video_dec_display       cat@192.168.103.155:~/

需要注意,LubanCat-P1(sg2000)引出mipi dsi接口,用来连接显示屏,教程测试的是mipi 5.5寸屏幕, 在测试例程前需要配置好mipi屏的参数,使用下面命令设置:

# 5.5.寸mipi屏
sudo /mnt/system/usr/bin/sample_panel --panel=HX8399_1080P

然后再板卡上执行命令:

./video_dec_display test.h264

结果显示:

broken

按下CTRL+C,然后回车退出程序。