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. 内部主要处理流程¶
(上面图片来自 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, 支持的最大最小分辨率分别为:
H.264/H.265:2880x1620;
JPEG/MJPEG: 2880x1620@30fps。
教程测试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
结果显示:
按下CTRL+C,然后回车退出程序。