文章目录
- G2D 图形加速器
- 1. 功能简介
- 1.1 矩形填充
- 1.2 旋转和镜像 (rotate and mirror)
- 1.3 透明度混合
- 1.4 colorkey
- 1.5 缩放 (Stretchblt)
- 2. G2D 框架
- 3. 全志 G2D 使用示例
- 3.1 使用G2D实现图像旋转缩放
- 3.2 实时预览中加入旋转缩放功能
G2D 图形加速器
G2D模块主要实现图像旋转、数据格式、颜⾊空间转换、图像压缩, 以及图层合成功能 (包括 alpha、colorkey、 rotate、mirror、rop 和 maskblt) 等加速功能。
术语解释:
- alpha :
- 含义:在图像处理中,alpha 通道用于表示图像的透明度信息。每个像素除了有红(R)、绿(G)、蓝(B)三种颜色分量外,还可以有一个 alpha 分量,其取值范围通常是 0 - 255,0 表示完全透明,255 表示完全不透明。通过 alpha 通道可以实现图像的半透明效果、渐变透明效果等,在图层合成时,它可以控制不同图层之间的融合程度。
- colorkey :
- 含义:色键,也称为抠像颜色。它是一种用于抠图的技术,指定一种特定的颜色作为“键色”,在图像或视频中,所有与该键色相同或相近的颜色区域会被视为透明区域。例如,在绿幕抠像中,绿色就是色键颜色,通过将绿色区域抠除,可以将前景主体从绿色背景中分离出来,然后与其他背景进行合成。
- rotate :
- 含义:旋转。在图像处理中,rotate 操作是指将图像绕某个中心点按照一定的角度进行旋转,常见的旋转角度有 90 度、180 度、270 度等,也可以进行任意角度的旋转。旋转操作会改变图像中像素的位置和排列方式。
- mirror :
- 含义:镜像。镜像操作是将图像沿着某条轴线进行翻转,分为水平镜像(左右翻转)和垂直镜像(上下翻转)。水平镜像时,图像左右两侧的像素位置互换;垂直镜像时,图像上下两侧的像素位置互换。
- rop :
- 含义:光栅操作(Raster Operation)。它是一种用于对光栅图像(由像素组成的图像)进行位操作的技术。ROP 定义了源图像、目标图像和掩码图像之间的逻辑运算规则,通过这些规则可以实现图像的复制、合并、擦除等操作。例如,常见的 ROP 操作有 SRCCOPY(将源图像直接复制到目标图像)、SRCPAINT(将源图像和目标图像进行或运算)等。
- maskblt :
- 含义:掩码位块传输。这是一种图像合成操作,它结合了掩码图像来控制源图像和目标图像之间的像素传输。掩码图像中的每个像素值决定了源图像中对应位置的像素是否会被复制到目标图像中。例如,掩码图像中为白色(通常表示允许复制)的区域,源图像的像素会被复制到目标图像;掩码图像中为黑色(通常表示禁止复制)的区域,目标图像的原有像素会保留。
1. 功能简介
1.1 矩形填充
填充矩形区域功能可以实现对某块区域进行预订的颜色值填充,如下图就填充了 0xFF0080FF ARGB 值,该功能还可以通过设定数据区域大小实现画点和直线,同时也可以通过设定 flag 实现一种填充颜色和目标做 alpha 运算。
1.2 旋转和镜像 (rotate and mirror)
旋转镜像主要是实现如下 Horizontal、Vertical、Rotate180°、Mirror45°、Rotate90°、Mirror135°、Rotate270° 共 7 种操作。
1.3 透明度混合
不同的图层之间可以做 alpha blending。Alpha 分为 pixel alpha、plane alpha、multi alpha 三种:
- pixel alpha 意为每个像素自带有一个专属 alpha 值;
- plane alpha 则是一个图层中所有像素共用一个 globe alpha 值;
- multi alpha 则每个像素在代入 alpha 运算时的值为 globe alpha*pixel alpha,可以通过 G2D 驱动接口的 flag 去控制。
1.4 colorkey
Colorkey 技术是作用在两个图像叠加混合的时候,对特殊色做特殊过滤。符合条件的区域叫 match 区,在 match 区就全部使用另外一个图层的颜色值;不符合条件的区域就是非 match 区,非 match 区就是走普通的 alpha 混合。Alpha 值越大就是越不透明。
不同 image 之间可以做 colorkey 效果:
- 左图中 destination 的优先级高于 source,destination 中 match 部分(橙色五角星部分),则被选择透过,显示为 source 与 destination 做 alpha blending 后的效果图。
- 右图中 source 的优先级高于 destination,则 source 中 match 部分(深红色五角星部分),则被选择透过,直接显示 destination 与 source 做 alpha blending 后的效果图。
1.5 缩放 (Stretchblt)
Stretchblt 主要是把 source 按照 destination 的 size 进行缩放,并最终与 destination 做 alpha blending、colorkey 等运算或直接旋转镜像后拷贝到目标,此接口在 1.0 版本上使用可以旋转和缩放一起用,但是 2.0 版本以后,缩放和旋转不可以同时操作。
2. G2D 框架
3. 全志 G2D 使用示例
3.1 使用G2D实现图像旋转缩放
示例:使用 G2D 实现图像旋转缩放
步骤:
- 初始化日志系统,MPP 平台,ION 内存管理器
- 启动 MPP 平台
- 打开源图像文件
- 打开G2D设备,根据配置执行G2D转换
- 保存结果图像,关闭g2d设备节点
- 清理资源
示例代码解析:
sample_g2d.c
/*** @file sample_g2d.c* @brief G2D (图形加速器) 示例程序** 该程序演示了如何使用 G2D 硬件加速器执行图像处理任务,如旋转、缩放、格式转换、透明叠加和批处理。* 程序通过命令行参数和配置文件读取运行参数,并利用 Linux ioctl 接口与 G2D 驱动进行通信。*/#define LOG_TAG "sample_g2d" // 定义日志标签,用于日志输出#include <utils/plat_log.h> // 平台日志库
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/g2d_driver.h> // G2D 驱动头文件,包含 ioctl 命令和结构体
#include <vo/hwdisplay.h>
#include <mpi_vo.h>
#include <media/mpi_sys.h>
#include <media/mm_comm_vi.h>
#include <mpi_videoformat_conversion.h>
#include <SystemBase.h>
#include <VideoFrameInfoNode.h>
#include "media/mpi_vi.h"
#include "media/mpi_isp.h"
#include <utils/PIXEL_FORMAT_E_g2d_format_convert.h> // 像素格式转换工具
#include <utils/VIDEO_FRAME_INFO_S.h>
#include <confparser.h> // 配置文件解析库
#include "sample_g2d.h"
#include "sample_g2d_mem.h" // G2D 内存管理
#include "sample_g2d_config.h" // G2D 配置常量
#include <cdx_list.h>/*** @brief 解析命令行参数** 该函数解析程序启动时传入的命令行参数,目前只支持 -path(指定配置文件路径)和 -h(帮助)。** @param argc 参数个数* @param argv 参数数组* @param pCmdLinePara 指向用于存储解析结果的结构体* @return int 成功返回0,遇到 -h 返回1,失败返回-1*/
static int ParseCmdLine(int argc, char **argv, SampleG2dCmdLineParam *pCmdLinePara)
{alogd("sample virvi path:[%s], arg number is [%d]", argv[0], argc); // 输出程序路径和参数数量int ret = 0;int i = 1; // 从第一个实际参数开始(跳过程序名)memset(pCmdLinePara, 0, sizeof(SampleG2dCmdLineParam)); // 初始化参数结构体while (i < argc){if (!strcmp(argv[i], "-path")) // 如果参数是 -path{if (++i >= argc) // 检查 -path 后面是否有值{aloge("fatal error! use -h to learn how to set parameter!!!"); // 没有值则报错ret = -1;break;}if (strlen(argv[i]) >= MAX_FILE_PATH_SIZE) // 检查文件路径长度是否过长{aloge("fatal error! file path[%s] too long: [%d]>=[%d]!", argv[i], strlen(argv[i]), MAX_FILE_PATH_SIZE);}strncpy(pCmdLinePara->mConfigFilePath, argv[i], MAX_FILE_PATH_SIZE - 1); // 复制配置文件路径pCmdLinePara->mConfigFilePath[MAX_FILE_PATH_SIZE - 1] = '\0'; // 确保字符串以 '\0' 结尾}else if (!strcmp(argv[i], "-h")) // 如果参数是 -h{alogd("CmdLine param:\n""\t-path /mnt/extsd/sample_vi_g2d.conf"); // 打印帮助信息ret = 1; // 返回1表示需要显示帮助break;}else // 忽略无效参数{alogd("ignore invalid CmdLine param:[%s], type -h to get how to set parameter!", argv[i]);}i++; // 处理下一个参数}return ret;
}/*** @brief 从配置文件加载 G2D 配置** 该函数读取指定的配置文件,解析其中的各项参数,并填充到 SampleG2dConfig 结构体中。* 配置项包括源/目标图像格式、尺寸、旋转角度、翻转模式、文件路径等。** @param pConfig 指向用于存储配置的结构体* @param pConfPath 配置文件路径* @return ERRORTYPE 成功返回 SUCCESS,失败返回 FAILURE*/
static ERRORTYPE loadSampleG2dConfig(SampleG2dConfig *pConfig, const char *pConfPath)
{int ret = SUCCESS;// 为配置项设置默认值pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // 默认源图像格式为 NV21pConfig->mSrcWidth = 1920; // 默认源图像宽度pConfig->mSrcHeight = 1080; // 默认源图像高度pConfig->mSrcRectX = 0; // 默认源裁剪区域左上角X坐标pConfig->mSrcRectY = 0; // 默认源裁剪区域左上角Y坐标pConfig->mSrcRectW = 1920; // 默认源裁剪区域宽度pConfig->mSrcRectH = 1080; // 默认源裁剪区域高度pConfig->mDstRotate = 90; // 默认目标旋转角度为90度pConfig->mDstWidth = 1080; // 默认目标图像宽度pConfig->mDstHeight = 1920; // 默认目标图像高度pConfig->mDstRectX = 0; // 默认目标区域左上角X坐标pConfig->mDstRectY = 0; // 默认目标区域左上角Y坐标pConfig->mDstRectW = 1080; // 默认目标区域宽度pConfig->mDstRectH = 1920; // 默认目标区域高度if (pConfPath != NULL) // 如果提供了配置文件路径{CONFPARSER_S stConfParser; // 声明配置解析器结构体ret = createConfParser(pConfPath, &stConfParser); // 创建配置解析器并加载文件if (ret < 0){aloge("load conf fail"); // 加载失败return FAILURE;}// 解析源图像格式char *pStrPixelFormat = (char *)GetConfParaString(&stConfParser, SAMPLE_G2D_KEY_PIC_FORMAT, NULL);if (!strcmp(pStrPixelFormat, "nv21")){pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // NV21 -> YVU Semi-Planar 4:2:0}else if (!strcmp(pStrPixelFormat, "nv12")){pConfig->mPicFormat = MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420; // NV12 -> YUV Semi-Planar 4:2:0}else if (!strcmp(pStrPixelFormat, "nv16")) // yuv422sp{pConfig->mPicFormat = MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422; // NV16 -> YUV Semi-Planar 4:2:2}else if (!strcmp(pStrPixelFormat, "nv61")) // yvu422sp{pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422; // NV61 -> YVU Semi-Planar 4:2:2}else if (!strcmp(pStrPixelFormat, "rgb888")){pConfig->mPicFormat = MM_PIXEL_FORMAT_RGB_888; // RGB888 -> RGB 8:8:8}else if (!strcmp(pStrPixelFormat, "rgb8888")){pConfig->mPicFormat = MM_PIXEL_FORMAT_RGB_8888; // RGB8888 -> RGB 8:8:8:8}else if (!strcmp(pStrPixelFormat, "yu12")){pConfig->mPicFormat = MM_PIXEL_FORMAT_YUV_PLANAR_420; // YU12 -> YUV Planar 4:2:0}else{aloge("fatal error! conf file pic_format[%s] is unsupported", pStrPixelFormat); // 不支持的格式pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // 使用默认值}// 解析目标图像格式pStrPixelFormat = (char *)GetConfParaString(&stConfParser, SAMPLE_G2D_DST_PIC_FORMAT, NULL);if (!strcmp(pStrPixelFormat, "nv21")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420;}else if (!strcmp(pStrPixelFormat, "nv12")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420;}else if (!strcmp(pStrPixelFormat, "rgb888")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_RGB_888;}else if (!strcmp(pStrPixelFormat, "rgb8888")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_RGB_8888;}else if (!strcmp(pStrPixelFormat, "nv16")) // yuv422sp{pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422;}else if (!strcmp(pStrPixelFormat, "nv61")) // yvu422sp{pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422;}else if (!strcmp(pStrPixelFormat, "yvu422pakage")) // yvu422sp{pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YVYU_AW_PACKAGE_422; // YVYU Packed 4:2:2}else if (!strcmp(pStrPixelFormat, "yuv422pakage")) // yuv422sp{pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YUYV_PACKAGE_422; // YUYV Packed 4:2:2}else if (!strcmp(pStrPixelFormat, "uyvy422pakage")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_UYVY_PACKAGE_422; // UYVY Packed 4:2:2}else if (!strcmp(pStrPixelFormat, "vyuy422pakage")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_VYUY_PACKAGE_422; // VYUY Packed 4:2:2}else if (!strcmp(pStrPixelFormat, "yu12")){pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YUV_PLANAR_420;}else{aloge("fatal error! conf dst pic_format[%s] is unsupported", pStrPixelFormat); // 不支持的格式pConfig->mDstPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // 使用默认值}aloge("config_pixel_fmt:src:%d,dst:%d", pConfig->mPicFormat, pConfig->mDstPicFormat); // 输出配置的像素格式// 解析源图像尺寸和裁剪区域pConfig->mSrcWidth = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_WIDTH, 0);pConfig->mSrcHeight = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_HEIGHT, 0);pConfig->mSrcRectX = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_RECT_X, 0);pConfig->mSrcRectY = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_RECT_Y, 0);pConfig->mSrcRectW = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_RECT_W, 0);pConfig->mSrcRectH = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_SRC_RECT_H, 0);// 解析目标旋转、尺寸和区域pConfig->mDstRotate = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_ROTATE, 0);pConfig->mDstWidth = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_WIDTH, 0);pConfig->mDstHeight = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_HEIGHT, 0);pConfig->mDstRectX = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_RECT_X, 0);pConfig->mDstRectY = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_RECT_Y, 0);pConfig->mDstRectW = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_RECT_W, 0);pConfig->mDstRectH = GetConfParaInt(&stConfParser, SAMPLE_G2D_KEY_DST_RECT_H, 0);aloge("width:%d,height:%d", pConfig->mDstWidth, pConfig->mDstHeight); // 输出目标尺寸// 解析G2D操作模式pConfig->g2d_mod = GetConfParaInt(&stConfParser, SAMPLE_G2D_MODE, 0); // 0:旋转, 1:缩放, 2:格式转换, 3:透明叠加, 4:批处理// 解析源文件路径char *tmp_ptr = (char *)GetConfParaString(&stConfParser, SAMPLE_G2D_SRC_FILE_STR, NULL);strcpy(pConfig->SrcFile, tmp_ptr);// 解析翻转模式tmp_ptr = (char *)GetConfParaString(&stConfParser, SAMPLE_G2D_KEY_DST_FLIP, NULL);if (!strcmp(tmp_ptr, "H"))pConfig->flip_flag = 'H'; // 水平翻转else if (!strcmp(tmp_ptr, "V"))pConfig->flip_flag = 'V'; // 垂直翻转else if (!strcmp(tmp_ptr, "N"))pConfig->flip_flag = 'N'; // 不翻转// 解析目标文件路径tmp_ptr = (char *)GetConfParaString(&stConfParser, SAMPLE_G2D_DST_FILE_STR, NULL);strcpy(pConfig->DstFile, tmp_ptr);aloge("src_file:%s, dst_file:%s", pConfig->SrcFile, pConfig->DstFile); // 输出文件路径// 解析透明叠加模式pConfig->g2d_bld_mod = GetConfParaInt(&stConfParser, SAMPLE_G2D_BLD_MODE, 0);destroyConfParser(&stConfParser); // 销毁配置解析器,释放资源}return ret;
}/*** @brief 释放用于存储源图像数据的内存** 该函数释放之前为 YUV 和 RGB 源图像帧分配的虚拟内存。** @param pYUVFrmInfo 指向 YUV 帧信息的指针* @param pRGBFrmInfo 指向 RGB 帧信息的指针* @return int 总是返回0*/
static int releaseSrcFile(SampleG2dMixerTaskFrmInfo *pYUVFrmInfo, SampleG2dMixerTaskFrmInfo *pRGBFrmInfo)
{for (int i = 0; i < 3; i++) // 释放每个平面的内存{if (NULL != pYUVFrmInfo->mpSrcVirFrmAddr[i]){g2d_freeMem(pYUVFrmInfo->mpSrcVirFrmAddr[i]); // 释放内存pYUVFrmInfo->mpSrcVirFrmAddr[i] = NULL; // 防止野指针}if (NULL != pRGBFrmInfo->mpSrcVirFrmAddr[i]){g2d_freeMem(pRGBFrmInfo->mpSrcVirFrmAddr[i]);pRGBFrmInfo->mpSrcVirFrmAddr[i] = NULL;}}return 0;
}/*** @brief 释放批处理任务中使用的所有缓冲区内存** 该函数释放批处理任务中为源图像和目标图像分配的所有内存。** @param pContext 指向批处理任务上下文的指针* @return int 总是返回0*/
static int releaseBuf(SampleG2dMixerTaskContext *pContext)
{int ret = 0;SampleG2dMixerTaskFrmInfo *pFrmInfo = NULL;releaseSrcFile(&pContext->mYUVFrmInfo, &pContext->mRGBFrmInfo); // 先释放源文件内存for (int i = 0; i < FRAME_TO_BE_PROCESS; i++) // 遍历所有待处理的帧{pFrmInfo = &pContext->mFrmInfo[i];for (int j = 0; j < 3; j++) // 释放每个平面的内存{if (NULL != pFrmInfo->mpSrcVirFrmAddr[j]){g2d_freeMem(pFrmInfo->mpSrcVirFrmAddr[j]);pFrmInfo->mpSrcVirFrmAddr[j] = NULL;}if (NULL != pFrmInfo->mpDstVirFrmAddr[j]){g2d_freeMem(pFrmInfo->mpDstVirFrmAddr[j]);pFrmInfo->mpDstVirFrmAddr[j] = NULL;}}}return ret;
}/*** @brief 释放所有为G2D操作分配的帧缓冲区内存** 该函数根据配置的G2D模式,决定是释放单帧缓冲区还是批处理缓冲区。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 总是返回0*/
static int FreeFrmBuff(SAMPLE_G2D_CTX *p_g2d_ctx)
{if (p_g2d_ctx->mConfigPara.g2d_mod != 4) // 如果不是批处理模式{// 释放源图像缓冲区if (NULL != p_g2d_ctx->src_frm_info.p_vir_addr[0]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);}if (NULL != p_g2d_ctx->src_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);}if (NULL != p_g2d_ctx->src_frm_info.p_vir_addr[2]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[2]);}// 释放目标图像缓冲区if (NULL != p_g2d_ctx->dst_frm_info.p_vir_addr[0]){g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);}if (NULL != p_g2d_ctx->dst_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[1]);}if (NULL != p_g2d_ctx->dst_frm_info.p_vir_addr[2]){g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[2]);}}else // 如果是批处理模式{releaseBuf(&p_g2d_ctx->mixertask); // 释放批处理缓冲区}return 0;
}/*** @brief 为G2D操作准备输入和输出帧缓冲区** 该函数根据配置的源图像和目标图像的尺寸、格式,计算所需内存大小,并调用 g2d_allocMem 分配内存。* 同时设置虚拟地址和对应的物理地址。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int PrepareFrmBuff(SAMPLE_G2D_CTX *p_g2d_ctx)
{SampleG2dConfig *pConfig = NULL;unsigned int size = 0;pConfig = &p_g2d_ctx->mConfigPara; // 获取配置信息// 设置帧信息中的宽度和高度p_g2d_ctx->src_frm_info.frm_width = pConfig->mSrcWidth;p_g2d_ctx->src_frm_info.frm_height = pConfig->mSrcHeight;p_g2d_ctx->dst_frm_info.frm_width = pConfig->mDstWidth;p_g2d_ctx->dst_frm_info.frm_height = pConfig->mDstHeight;// 计算对齐后的内存大小 (Y分量)size = AWALIGN(p_g2d_ctx->src_frm_info.frm_width, 16) * AWALIGN(p_g2d_ctx->src_frm_info.frm_height, 16);// 根据源图像格式分配内存if (pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 || pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420){// NV21/NV12: Y平面 + UV平面 (大小为Y的1/2)p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0]){aloge("malloc_src_frm_y_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size / 2);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]); // 分配失败,释放已分配的Y平面aloge("malloc_src_frm_c_mem_failed");return -1;}// 获取物理地址p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);p_g2d_ctx->src_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[1]);}else if (pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422 || pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422){// NV16/NV61: Y平面 + UV平面 (大小与Y相同)p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0]){aloge("malloc_src_frm_y_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);aloge("malloc_src_frm_c_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);p_g2d_ctx->src_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[1]);}else if (MM_PIXEL_FORMAT_RGB_888 == pConfig->mPicFormat){// RGB888: 一个平面,每个像素3字节p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size * 3);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0]){aloge("malloc_src_frm_y_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);}else if (MM_PIXEL_FORMAT_RGB_8888 == pConfig->mPicFormat){// RGB8888: 一个平面,每个像素4字节p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size * 4);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0]){aloge("malloc_src_frm_y_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);}else if (MM_PIXEL_FORMAT_YUV_PLANAR_420 == pConfig->mPicFormat){// YU12: Y平面 + U平面 + V平面 (U/V大小为Y的1/4)p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0]){aloge("malloc_src_frm_y_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size / 4);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);aloge("malloc_src_frm_c_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_vir_addr[2] = (void *)g2d_allocMem(size / 4);if (NULL == p_g2d_ctx->src_frm_info.p_vir_addr[2]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);aloge("malloc_src_frm_c_mem_failed");return -1;}p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);p_g2d_ctx->src_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[1]);p_g2d_ctx->src_frm_info.p_phy_addr[2] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[2]);}// 根据目标图像格式分配内存if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 ||pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420){size = AWALIGN(p_g2d_ctx->dst_frm_info.frm_width, 16) * AWALIGN(p_g2d_ctx->dst_frm_info.frm_height, 16);p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){// 分配失败,释放已分配的源图像内存g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size / 2);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);aloge("malloc_dst_frm_c_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);p_g2d_ctx->dst_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[1]);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422){size = AWALIGN(p_g2d_ctx->dst_frm_info.frm_width, 16) * AWALIGN(p_g2d_ctx->dst_frm_info.frm_height, 16);p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);aloge("malloc_dst_frm_c_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);p_g2d_ctx->dst_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[1]);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_UYVY_PACKAGE_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_VYUY_PACKAGE_422 ||pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUYV_PACKAGE_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVYU_AW_PACKAGE_422){// 打包格式 (Packed): 一个平面,每个像素2字节size = AWALIGN(p_g2d_ctx->dst_frm_info.frm_width, 16) * AWALIGN(p_g2d_ctx->dst_frm_info.frm_height, 16);p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size * 2);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_888){// RGB888: 一个平面,每个像素3字节size = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 3;p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){if (p_g2d_ctx->src_frm_info.p_vir_addr[0] != NULL){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);}if (p_g2d_ctx->src_frm_info.p_vir_addr[1] != NULL){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);}aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_8888){// RGB8888: 一个平面,每个像素4字节size = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 4;p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){if (p_g2d_ctx->src_frm_info.p_vir_addr[0] != NULL){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);}if (p_g2d_ctx->src_frm_info.p_vir_addr[1] != NULL){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);}aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_PLANAR_420){// YU12: Y平面 + U平面 + V平面size = AWALIGN(p_g2d_ctx->dst_frm_info.frm_width, 16) * AWALIGN(p_g2d_ctx->dst_frm_info.frm_height, 16);p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[2]);aloge("malloc_dst_frm_y_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size / 4);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[1]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[2]);g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);aloge("malloc_dst_frm_c_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_vir_addr[2] = (void *)g2d_allocMem(size / 4);if (NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[2]){g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[2]);g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);g2d_freeMem(p_g2d_ctx->dst_frm_info.p_vir_addr[1]);aloge("malloc_dst_frm_c_mem_failed");return -1;}p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);p_g2d_ctx->dst_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[1]);p_g2d_ctx->dst_frm_info.p_phy_addr[2] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[2]);}return 0;
}/*** @brief 打开 G2D 设备节点** 该函数打开 /dev/g2d 设备文件,获取文件描述符,用于后续的 ioctl 操作。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dOpen(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;p_g2d_ctx->mG2dFd = open("/dev/g2d", O_RDWR, 0); // 以读写模式打开设备if (p_g2d_ctx->mG2dFd < 0){aloge("fatal error! open /dev/g2d failed"); // 打开失败ret = -1;}return ret;
}/*** @brief 关闭 G2D 设备节点** 该函数关闭之前打开的 G2D 设备文件描述符。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 总是返回0*/
static int SampleG2d_G2dClose(SAMPLE_G2D_CTX *p_g2d_ctx)
{if (p_g2d_ctx->mG2dFd >= 0) // 检查文件描述符是否有效{close(p_g2d_ctx->mG2dFd);p_g2d_ctx->mG2dFd = -1; // 重置为无效值}return 0;
}/*** @brief 执行G2D旋转操作** 该函数配置 `g2d_blt_h` 结构体,设置旋转标志,并调用 ioctl 执行位块传输 (BitBLT) 操作。* 也支持水平/垂直翻转。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dConvert_rotate(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;g2d_blt_h blit; // G2D 位块传输操作结构体g2d_fmt_enh eSrcFormat, eDstFormat; // G2D 驱动使用的源和目标图像格式SampleG2dConfig *pConfig = NULL;pConfig = &p_g2d_ctx->mConfigPara;// 将MPP的像素格式转换为G2D驱动能识别的格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mPicFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mDstPicFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}// 初始化blit结构体memset(&blit, 0, sizeof(g2d_blt_h));// 根据配置设置旋转或翻转标志if (pConfig->mDstRotate >= 0){switch (pConfig->mDstRotate){case 0:blit.flag_h = G2D_BLT_NONE_H; // 0度旋转break;case 90:blit.flag_h = G2D_ROT_90; // 90度旋转break;case 180:blit.flag_h = G2D_ROT_180; // 180度旋转break;case 270:blit.flag_h = G2D_ROT_270; // 270度旋转break;default:aloge("fatal error! rotation[%d] is invalid!", pConfig->mDstRotate);blit.flag_h = G2D_BLT_NONE_H;break;}}else if (pConfig->flip_flag == 'H' || pConfig->flip_flag == 'V'){switch (pConfig->flip_flag){case 'H':blit.flag_h = G2D_ROT_H; // 水平翻转break;case 'V':blit.flag_h = G2D_ROT_V; // 垂直翻转break;case 'N':break; // 不翻转default:aloge("fatal error! dst_flip[%c] is invalid!", pConfig->flip_flag);break;}}else{aloge("fatal error! invalid rotate value %d or flip %d", pConfig->mDstRotate, pConfig->flip_flag);return -1;}// 配置源图像信息blit.src_image_h.format = eSrcFormat; // 图像格式blit.src_image_h.laddr[0] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[0]; // Y平面物理地址blit.src_image_h.laddr[1] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[1]; // C平面物理地址blit.src_image_h.laddr[2] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[2]; // 第三个平面物理地址 (如U/V)blit.src_image_h.width = p_g2d_ctx->src_frm_info.frm_width; // 图像宽度blit.src_image_h.height = p_g2d_ctx->src_frm_info.frm_height; // 图像高度blit.src_image_h.align[0] = 0; // 内存对齐要求blit.src_image_h.align[1] = 0;blit.src_image_h.align[2] = 0;// 裁剪区域 (源图像中要处理的部分)blit.src_image_h.clip_rect.x = pConfig->mSrcRectX;blit.src_image_h.clip_rect.y = pConfig->mSrcRectY;blit.src_image_h.clip_rect.w = pConfig->mSrcRectW;blit.src_image_h.clip_rect.h = pConfig->mSrcRectH;blit.src_image_h.gamut = G2D_BT601; // 色域blit.src_image_h.bpremul = 0; // 是否预乘alphablit.src_image_h.mode = G2D_PIXEL_ALPHA; // Alpha混合模式blit.src_image_h.fd = -1; // 文件描述符blit.src_image_h.use_phy_addr = 1; // 使用物理地址// 配置目标图像信息blit.dst_image_h.format = eDstFormat;blit.dst_image_h.laddr[0] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[0];blit.dst_image_h.laddr[1] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[1];blit.dst_image_h.laddr[2] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[2];blit.dst_image_h.width = p_g2d_ctx->dst_frm_info.frm_width;blit.dst_image_h.height = p_g2d_ctx->dst_frm_info.frm_height;blit.dst_image_h.align[0] = 0;blit.dst_image_h.align[1] = 0;blit.dst_image_h.align[2] = 0;// 目标区域 (输出图像中要绘制的位置)blit.dst_image_h.clip_rect.x = pConfig->mDstRectX;blit.dst_image_h.clip_rect.y = pConfig->mDstRectY;blit.dst_image_h.clip_rect.w = pConfig->mDstRectW;blit.dst_image_h.clip_rect.h = pConfig->mDstRectH;blit.dst_image_h.gamut = G2D_BT601;blit.dst_image_h.bpremul = 0;blit.dst_image_h.mode = G2D_PIXEL_ALPHA;blit.dst_image_h.fd = -1;blit.dst_image_h.use_phy_addr = 1;// 调用ioctl执行G2D操作ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_BITBLT_H, (unsigned long)&blit);if (ret < 0){aloge("fatal error! bit-block(image) transfer failed[%d]", ret);system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump"); // 打印G2D寄存器用于调试}return ret;
}/*** @brief 执行G2D缩放操作** 该函数配置 `g2d_blt_h` 结构体,并调用 ioctl 执行位块传输 (BitBLT) 操作。* **注意**:此函数的注释中指出,当进行缩放时,不应设置旋转。但代码中并未对此进行严格检查。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dConvert_scale(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;g2d_blt_h blit;g2d_fmt_enh eSrcFormat, eDstFormat;SampleG2dConfig *pConfig = NULL;pConfig = &p_g2d_ctx->mConfigPara;// 转换像素格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mPicFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mDstPicFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}// 初始化blit结构体memset(&blit, 0, sizeof(g2d_blt_h));// 检查是否尝试在缩放时进行旋转(通常不支持)if (0 != pConfig->mDstRotate){aloge("fatal_err: rotation can't be performed when do scaling");}blit.flag_h = G2D_BLT_NONE_H; // 设置为无旋转// 配置源图像信息 (与旋转操作类似)blit.src_image_h.format = eSrcFormat;blit.src_image_h.laddr[0] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[0];blit.src_image_h.laddr[1] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[1];blit.src_image_h.laddr[2] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[2];blit.src_image_h.width = p_g2d_ctx->src_frm_info.frm_width;blit.src_image_h.height = p_g2d_ctx->src_frm_info.frm_height;blit.src_image_h.align[0] = 0;blit.src_image_h.align[1] = 0;blit.src_image_h.align[2] = 0;blit.src_image_h.clip_rect.x = pConfig->mSrcRectX;blit.src_image_h.clip_rect.y = pConfig->mSrcRectY;blit.src_image_h.clip_rect.w = pConfig->mSrcRectW;blit.src_image_h.clip_rect.h = pConfig->mSrcRectH;blit.src_image_h.gamut = G2D_BT601;blit.src_image_h.bpremul = 0;blit.src_image_h.mode = G2D_PIXEL_ALPHA;blit.src_image_h.fd = -1;blit.src_image_h.use_phy_addr = 1;// 配置目标图像信息 (与旋转操作类似)blit.dst_image_h.format = eDstFormat;blit.dst_image_h.laddr[0] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[0];blit.dst_image_h.laddr[1] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[1];blit.dst_image_h.laddr[2] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[2];blit.dst_image_h.width = p_g2d_ctx->dst_frm_info.frm_width;blit.dst_image_h.height = p_g2d_ctx->dst_frm_info.frm_height;blit.dst_image_h.align[0] = 0;blit.dst_image_h.align[1] = 0;blit.dst_image_h.align[2] = 0;blit.dst_image_h.clip_rect.x = pConfig->mDstRectX;blit.dst_image_h.clip_rect.y = pConfig->mDstRectY;blit.dst_image_h.clip_rect.w = pConfig->mDstRectW;blit.dst_image_h.clip_rect.h = pConfig->mDstRectH;blit.dst_image_h.gamut = G2D_BT601;blit.dst_image_h.bpremul = 0;blit.dst_image_h.mode = G2D_PIXEL_ALPHA;blit.dst_image_h.fd = -1;blit.dst_image_h.use_phy_addr = 1;// 执行G2D操作ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_BITBLT_H, (unsigned long)&blit);if (ret < 0){aloge("fatal error! bit-block(image) transfer failed[%d]", ret);system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump");}return ret;
}/*** @brief 执行G2D格式转换操作** 该函数配置 `g2d_blt_h` 结构体,并调用 ioctl 执行位块传输 (BitBLT) 操作,实现不同像素格式之间的转换。* **注意**:此函数的注释中指出,当进行格式转换时,不应设置旋转。但代码中并未对此进行严格检查。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dConvert_formatconversion(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;g2d_blt_h blit;g2d_fmt_enh eSrcFormat, eDstFormat;SampleG2dConfig *pConfig = NULL;pConfig = &p_g2d_ctx->mConfigPara;// 转换像素格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mPicFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mDstPicFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}// 初始化blit结构体memset(&blit, 0, sizeof(g2d_blt_h));// 检查是否尝试在格式转换时进行旋转(通常不支持)if (0 != pConfig->mDstRotate){aloge("fatal_err: rotation can't be performed when do scaling");}blit.flag_h = G2D_BLT_NONE_H; // 设置为无旋转aloge("src format[0x%x] dst format[0x%x]", eSrcFormat, eDstFormat); // 输出源和目标格式// 配置源图像信息 (与缩放操作类似)blit.src_image_h.format = eSrcFormat;blit.src_image_h.laddr[0] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[0];blit.src_image_h.laddr[1] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[1];blit.src_image_h.laddr[2] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[2];blit.src_image_h.width = p_g2d_ctx->src_frm_info.frm_width;blit.src_image_h.height = p_g2d_ctx->src_frm_info.frm_height;blit.src_image_h.align[0] = 0;blit.src_image_h.align[1] = 0;blit.src_image_h.align[2] = 0;blit.src_image_h.clip_rect.x = pConfig->mSrcRectX;blit.src_image_h.clip_rect.y = pConfig->mSrcRectY;blit.src_image_h.clip_rect.w = pConfig->mSrcRectW;blit.src_image_h.clip_rect.h = pConfig->mSrcRectH;blit.src_image_h.gamut = G2D_BT601;blit.src_image_h.bpremul = 0;blit.src_image_h.mode = G2D_PIXEL_ALPHA;blit.src_image_h.fd = -1;blit.src_image_h.use_phy_addr = 1;// 配置目标图像信息 (与缩放操作类似)blit.dst_image_h.format = eDstFormat;blit.dst_image_h.laddr[0] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[0];blit.dst_image_h.laddr[1] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[1];blit.dst_image_h.laddr[2] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[2];blit.dst_image_h.width = p_g2d_ctx->dst_frm_info.frm_width;blit.dst_image_h.height = p_g2d_ctx->dst_frm_info.frm_height;blit.dst_image_h.align[0] = 0;blit.dst_image_h.align[1] = 0;blit.dst_image_h.align[2] = 0;blit.dst_image_h.clip_rect.x = pConfig->mDstRectX;blit.dst_image_h.clip_rect.y = pConfig->mDstRectY;blit.dst_image_h.clip_rect.w = pConfig->mDstRectW;blit.dst_image_h.clip_rect.h = pConfig->mDstRectH;blit.dst_image_h.gamut = G2D_BT601;blit.dst_image_h.bpremul = 0;blit.dst_image_h.mode = G2D_PIXEL_ALPHA;blit.dst_image_h.fd = -1;blit.dst_image_h.use_phy_addr = 1;// 执行G2D操作ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_BITBLT_H, (unsigned long)&blit);if (ret < 0){aloge("fatal error! bit-block(image) transfer failed[%d]", ret);system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump");}return ret;
}/*** @brief 执行G2D透明叠加 (Blend) 操作** 该函数配置 `g2d_bld` 结构体,并调用 ioctl 执行透明叠加操作,将源图像和目标图像(背景)进行混合。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dBld(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;g2d_bld bld; // G2D 透明叠加操作结构体g2d_fmt_enh eSrcFormat, eDstFormat;SampleG2dConfig *pConfig = NULL;pConfig = &p_g2d_ctx->mConfigPara;// 转换像素格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mPicFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mDstPicFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pConfig->mPicFormat);return -1;}memset(&bld, 0, sizeof(g2d_bld));alogd("size[%dx%d], size[%dx%d]", p_g2d_ctx->src_frm_info.frm_width,p_g2d_ctx->src_frm_info.frm_height, p_g2d_ctx->dst_frm_info.frm_width,p_g2d_ctx->dst_frm_info.frm_height); // 输出图像尺寸bld.bld_cmd = pConfig->g2d_bld_mod; // 设置叠加模式// 配置源图像(前景)信息bld.src_image_h.format = eSrcFormat;bld.src_image_h.laddr[0] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[0];bld.src_image_h.laddr[1] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[1];bld.src_image_h.laddr[2] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[2];bld.src_image_h.width = p_g2d_ctx->src_frm_info.frm_width;bld.src_image_h.height = p_g2d_ctx->src_frm_info.frm_height;bld.src_image_h.align[0] = 0;bld.src_image_h.align[1] = 0;bld.src_image_h.align[2] = 0;bld.src_image_h.clip_rect.x = pConfig->mSrcRectX;bld.src_image_h.clip_rect.y = pConfig->mSrcRectY;bld.src_image_h.clip_rect.w = pConfig->mSrcRectW;bld.src_image_h.clip_rect.h = pConfig->mSrcRectH;// bld.src_image_h.gamut = G2D_BT601; // 代码中注释掉了// bld.src_image_h.bpremul = 0; // 代码中注释掉了bld.src_image_h.mode = G2D_GLOBAL_ALPHA; // 使用全局Alphabld.src_image_h.alpha = 128; // 设置Alpha值为128 (半透明)bld.src_image_h.fd = -1;bld.src_image_h.use_phy_addr = 1;// 配置目标图像(背景)信息bld.dst_image_h.format = eDstFormat;bld.dst_image_h.laddr[0] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[0];bld.dst_image_h.laddr[1] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[1];bld.dst_image_h.laddr[2] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[2];bld.dst_image_h.width = p_g2d_ctx->dst_frm_info.frm_width;bld.dst_image_h.height = p_g2d_ctx->dst_frm_info.frm_height;bld.dst_image_h.align[0] = 0;bld.dst_image_h.align[1] = 0;bld.dst_image_h.align[2] = 0;bld.dst_image_h.clip_rect.x = pConfig->mDstRectX;bld.dst_image_h.clip_rect.y = pConfig->mDstRectY;bld.dst_image_h.clip_rect.w = pConfig->mDstRectW;bld.dst_image_h.clip_rect.h = pConfig->mDstRectH;// bld.dst_image_h.gamut = G2D_BT601; // 代码中注释掉了// bld.dst_image_h.bpremul = 0; // 代码中注释掉了bld.dst_image_h.mode = G2D_GLOBAL_ALPHA;bld.dst_image_h.alpha = 128;bld.dst_image_h.fd = -1;bld.dst_image_h.use_phy_addr = 1;// 执行G2D透明叠加操作ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_BLD_H, (unsigned long)&bld);if (ret < 0){aloge("fatal error! bit-block(image) transfer failed[%d]", ret);system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump");}return ret;
}/*** @brief 从文件中读取源图像数据** 该函数打开指定的文件,并根据帧信息中的格式,将数据读取到已分配的内存中。* 读取后会调用 `g2d_flushCache` 确保CPU缓存中的数据被写入内存,以便G2D硬件可以正确访问。** @param pFrmInfo 指向帧信息的指针,包含格式、尺寸和内存地址* @param pFilePath 源文件路径* @return int 成功返回0,失败返回-1*/
static int readSrcFile(SampleG2dMixerTaskFrmInfo *pFrmInfo, char *pFilePath)
{int read_len = 0;FILE *fp = fopen(pFilePath, "rb"); // 以二进制只读模式打开文件if (NULL == fp){aloge("open src file failed");return -1;}if (fp){if (G2D_FORMAT_RGB888 == pFrmInfo->mSrcPicFormat) // RGB888格式{read_len = pFrmInfo->mSrcWidth * pFrmInfo->mSrcHeight * 3; // 计算数据大小memset(pFrmInfo->mpSrcVirFrmAddr[0], 0, read_len); // 清空内存fread(pFrmInfo->mpSrcVirFrmAddr[0], read_len, 1, fp); // 读取数据g2d_flushCache(pFrmInfo->mpSrcVirFrmAddr[0], read_len); // 刷新缓存}else if (G2D_FORMAT_YUV420UVC_V1U1V0U0 == pFrmInfo->mSrcPicFormat) // NV21格式{read_len = pFrmInfo->mSrcWidth * pFrmInfo->mSrcHeight; // Y分量大小memset(pFrmInfo->mpSrcVirFrmAddr[0], 0, read_len);memset(pFrmInfo->mpSrcVirFrmAddr[1], 0, read_len / 2); // UV分量大小fread(pFrmInfo->mpSrcVirFrmAddr[0], read_len, 1, fp); // 读取Y分量fread(pFrmInfo->mpSrcVirFrmAddr[1], read_len / 2, 1, fp); // 读取UV分量g2d_flushCache(pFrmInfo->mpSrcVirFrmAddr[0], read_len); // 刷新缓存g2d_flushCache(pFrmInfo->mpSrcVirFrmAddr[1], read_len / 2);}}else{return -1;}fclose(fp); // 关闭文件fp = NULL;return 0;
}/*** @brief 为批处理任务准备源文件数据** 该函数根据配置的源图像格式,分配内存并从文件中读取数据。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int prepareSrcFile(SAMPLE_G2D_CTX *p_g2d_ctx)
{int len = 0;int ret = 0;SampleG2dMixerTaskFrmInfo *pYUVFrmInfo = &p_g2d_ctx->mixertask.mYUVFrmInfo;SampleG2dMixerTaskFrmInfo *pRGBFrmInfo = &p_g2d_ctx->mixertask.mRGBFrmInfo;if (p_g2d_ctx->mConfigPara.mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420) // NV21{memset(pYUVFrmInfo, 0, sizeof(SampleG2dMixerTaskFrmInfo));pYUVFrmInfo->mSrcPicFormat = G2D_FORMAT_YUV420UVC_V1U1V0U0;pYUVFrmInfo->mSrcWidth = p_g2d_ctx->mConfigPara.mSrcWidth;pYUVFrmInfo->mSrcHeight = p_g2d_ctx->mConfigPara.mSrcHeight;len = pYUVFrmInfo->mSrcWidth * pYUVFrmInfo->mSrcHeight;// 分配Y和UV平面内存pYUVFrmInfo->mpSrcVirFrmAddr[0] = g2d_allocMem(len);if (NULL == pYUVFrmInfo->mpSrcVirFrmAddr[0]){aloge("fatal error! prepare src file y data mem fail!");return -1;}pYUVFrmInfo->mpSrcVirFrmAddr[1] = g2d_allocMem(len / 2);if (NULL == pYUVFrmInfo->mpSrcVirFrmAddr[1]) // 这里应该是 [1],原代码有误{aloge("fatal error! prepare src file c data mem fail!");return -1;}ret = readSrcFile(pYUVFrmInfo, p_g2d_ctx->mConfigPara.SrcFile); // 读取文件if (ret){aloge("fatal error! prepare src yuv file fail!");return -1;}}else if (p_g2d_ctx->mConfigPara.mPicFormat == MM_PIXEL_FORMAT_RGB_888) // RGB888{memset(pRGBFrmInfo, 0, sizeof(SampleG2dMixerTaskFrmInfo));pRGBFrmInfo->mSrcPicFormat = G2D_FORMAT_RGB888;// **注意**:这里配置的是目标尺寸,可能是为了将RGB图片缩放到目标区域pRGBFrmInfo->mSrcWidth = p_g2d_ctx->mConfigPara.mDstWidth;pRGBFrmInfo->mSrcHeight = p_g2d_ctx->mConfigPara.mDstHeight;len = pRGBFrmInfo->mSrcWidth * pRGBFrmInfo->mSrcHeight * 3;pRGBFrmInfo->mpSrcVirFrmAddr[0] = g2d_allocMem(len);if (NULL == pRGBFrmInfo->mpSrcVirFrmAddr[0]){aloge("fatal error! prepare dst file data mem fail!");return -1;}ret = readSrcFile(pRGBFrmInfo, p_g2d_ctx->mConfigPara.SrcFile);if (ret){aloge("fatal error! prepare dst rgb file fail!");return -1;}}return 0;
}/*** @brief 将处理后的目标图像数据保存到文件** 该函数打开指定的文件,并根据帧信息中的格式,将内存中的数据写入文件。* 写入前会调用 `g2d_flushCache` 确保G2D写入的数据已从缓存同步到内存。** @param pFrmInfo 指向帧信息的指针* @param pFilePath 目标文件路径* @return int 成功返回0,失败返回-1*/
static int saveDstFile(SampleG2dMixerTaskFrmInfo *pFrmInfo, char *pFilePath)
{FILE *fp = NULL;int write_len = 0;fp = fopen(pFilePath, "wb"); // 以二进制只写模式打开文件if (fp){if (G2D_FORMAT_RGB888 == pFrmInfo->mDstPicFormat) // RGB888格式{write_len = pFrmInfo->mDstWidth * pFrmInfo->mDstHeight * 3;g2d_flushCache(pFrmInfo->mpDstVirFrmAddr[0], write_len); // 刷新缓存fwrite(pFrmInfo->mpDstVirFrmAddr[0], write_len, 1, fp); // 写入数据}else if (G2D_FORMAT_YUV420UVC_V1U1V0U0 == pFrmInfo->mDstPicFormat) // NV21格式{write_len = pFrmInfo->mDstWidth * pFrmInfo->mDstHeight;g2d_flushCache(pFrmInfo->mpDstVirFrmAddr[0], write_len);g2d_flushCache(pFrmInfo->mpDstVirFrmAddr[1], write_len / 2);fwrite(pFrmInfo->mpDstVirFrmAddr[0], write_len, 1, fp); // 写入Y分量fwrite(pFrmInfo->mpDstVirFrmAddr[1], write_len / 2, 1, fp); // 写入UV分量}}else{aloge("fatal error! open file[%s] fail!", pFilePath);return -1;}fclose(fp); // 关闭文件fp = NULL;return 0;
}/*** @brief 保存批处理任务的所有结果** 该函数遍历批处理任务中所有待处理的帧,根据其配置生成文件名并调用 saveDstFile 保存。** @param pContext 指向批处理任务上下文的指针* @return int 成功返回0,失败返回-1*/
static int saveDstFrm(SampleG2dMixerTaskContext *pContext)
{int ret = 0;char filePath[128] = {0};SampleG2dMixerTaskFrmInfo *pFrmInfo;for (int i = 0; i < FRAME_TO_BE_PROCESS; i++){pFrmInfo = &pContext->mFrmInfo[i];if (G2D_FORMAT_RGB888 == pFrmInfo->mDstPicFormat){memset(filePath, 0, sizeof(filePath));sprintf(filePath, "/mnt/extsd/mixer%d_%dx%d_rgb888.rgb", // 生成RGB文件名i, pFrmInfo->mDstWidth, pFrmInfo->mDstHeight);ret = saveDstFile(pFrmInfo, filePath);if (ret){aloge("fatal error! mixer[%d] save dst file fail!", i);return -1;}}else if (G2D_FORMAT_YUV420UVC_V1U1V0U0 == pFrmInfo->mDstPicFormat){memset(filePath, 0, sizeof(filePath));sprintf(filePath, "/mnt/extsd/mixer%d_%dx%d_nv21.yuv", // 生成YUV文件名i, pFrmInfo->mDstWidth, pFrmInfo->mDstHeight);ret = saveDstFile(pFrmInfo, filePath);if (ret){aloge("fatal error! mixer[%d] save dst file fail!", i);return -1;}}}return ret;
}/*** @brief 为批处理任务准备缓冲区和参数** 该函数为批处理任务分配目标缓冲区内存,并配置每个任务的参数(`mixer_para`)。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int prepareBuf(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;int len = 0;struct mixer_para *pMixerPara = NULL;struct SampleG2dMixerTaskFrmInfo *pFrmInfo = NULL;g2d_fmt_enh eSrcFormat; // G2D格式// 根据源图像格式确定G2D格式if (p_g2d_ctx->mConfigPara.mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420)eSrcFormat = G2D_FORMAT_YUV420UVC_V1U1V0U0;else if (p_g2d_ctx->mConfigPara.mPicFormat == MM_PIXEL_FORMAT_RGB_888)eSrcFormat = G2D_FORMAT_RGB888;elseeSrcFormat = G2D_FORMAT_YUV420UVC_V1U1V0U0;// 为每个待处理的帧配置参数for (int i = 0; i < FRAME_TO_BE_PROCESS; i++){pMixerPara = &p_g2d_ctx->mixertask.stMixPara[i];pFrmInfo = &p_g2d_ctx->mixertask.mFrmInfo[i];memset(pMixerPara, 0, sizeof(p_g2d_ctx->mixertask.stMixPara[i])); // 初始化参数if (i < FRAME_TO_BE_PROCESS) // 这个判断总是成立{pMixerPara->flag_h = G2D_BLT_NONE_H; // 无旋转pMixerPara->op_flag = OP_BITBLT; // 操作类型为位块传输pMixerPara->src_image_h.format = eSrcFormat; // 源图像格式pMixerPara->src_image_h.width = p_g2d_ctx->mConfigPara.mSrcWidth; // 源图像宽度pMixerPara->src_image_h.height = p_g2d_ctx->mConfigPara.mSrcHeight; // 源图像高度// 裁剪区域pMixerPara->src_image_h.clip_rect.x = p_g2d_ctx->mConfigPara.mSrcRectX;pMixerPara->src_image_h.clip_rect.y = p_g2d_ctx->mConfigPara.mSrcRectY;pMixerPara->src_image_h.clip_rect.w = p_g2d_ctx->mConfigPara.mSrcRectW;pMixerPara->src_image_h.clip_rect.h = p_g2d_ctx->mConfigPara.mSrcRectH;pMixerPara->src_image_h.mode = G2D_PIXEL_ALPHA; // Alpha模式pMixerPara->src_image_h.alpha = 255; // 完全不透明pMixerPara->src_image_h.use_phy_addr = 1; // 使用物理地址pMixerPara->src_image_h.fd = -1; // 无文件描述符// 目标图像信息pMixerPara->dst_image_h.format = eSrcFormat; // 目标格式与源格式相同pMixerPara->dst_image_h.width = p_g2d_ctx->mConfigPara.mDstWidth; // 目标宽度pMixerPara->dst_image_h.height = p_g2d_ctx->mConfigPara.mDstHeight; // 目标高度// 目标区域pMixerPara->dst_image_h.clip_rect.x = p_g2d_ctx->mConfigPara.mDstRectX;pMixerPara->dst_image_h.clip_rect.y = p_g2d_ctx->mConfigPara.mDstRectY;pMixerPara->dst_image_h.clip_rect.w = p_g2d_ctx->mConfigPara.mDstRectW;pMixerPara->dst_image_h.clip_rect.h = p_g2d_ctx->mConfigPara.mDstRectH;pMixerPara->dst_image_h.mode = G2D_PIXEL_ALPHA;pMixerPara->dst_image_h.alpha = 255;pMixerPara->dst_image_h.use_phy_addr = 1;pMixerPara->dst_image_h.fd = -1;}// 将参数中的信息复制到帧信息结构体中pFrmInfo->mSrcPicFormat = pMixerPara->src_image_h.format;pFrmInfo->mSrcWidth = pMixerPara->src_image_h.width;pFrmInfo->mSrcHeight = pMixerPara->src_image_h.height;pFrmInfo->mDstPicFormat = pMixerPara->dst_image_h.format;pFrmInfo->mDstWidth = pMixerPara->dst_image_h.width;pFrmInfo->mDstHeight = pMixerPara->dst_image_h.height;}// 初始化YUV和RGB帧信息memset(&p_g2d_ctx->mixertask.mYUVFrmInfo, 0, sizeof(SampleG2dMixerTaskFrmInfo));memset(&p_g2d_ctx->mixertask.mRGBFrmInfo, 0, sizeof(SampleG2dMixerTaskFrmInfo));// 准备源文件数据ret = prepareSrcFile(p_g2d_ctx);if (ret){aloge("fatal error! prepare src file fail!");goto _release_src_file; // 如果失败,跳转到清理源文件内存的标签}// 为每个任务分配目标缓冲区内存for (int i = 0; i < FRAME_TO_BE_PROCESS; i++){pMixerPara = &p_g2d_ctx->mixertask.stMixPara[i];pFrmInfo = &p_g2d_ctx->mixertask.mFrmInfo[i];// 设置源图像的物理地址switch (pMixerPara->src_image_h.format){case G2D_FORMAT_RGB888:{pMixerPara->src_image_h.laddr[0] = g2d_getPhyAddrByVirAddr(p_g2d_ctx->mixertask.mRGBFrmInfo.mpSrcVirFrmAddr[0]);break;}case G2D_FORMAT_YUV420UVC_V1U1V0U0:{pMixerPara->src_image_h.laddr[0] = g2d_getPhyAddrByVirAddr(p_g2d_ctx->mixertask.mYUVFrmInfo.mpSrcVirFrmAddr[0]);pMixerPara->src_image_h.laddr[1] = g2d_getPhyAddrByVirAddr(p_g2d_ctx->mixertask.mYUVFrmInfo.mpSrcVirFrmAddr[1]);break;}default:{aloge("mixer para[%d] src format[%d] is invalid!", i,pMixerPara->src_image_h.format);return -1;}}// 分配目标缓冲区内存并设置物理地址switch (pMixerPara->dst_image_h.format){case G2D_FORMAT_RGB888:{len = pFrmInfo->mDstWidth * pFrmInfo->mDstHeight * 3;pFrmInfo->mpDstVirFrmAddr[0] = g2d_allocMem(len);if (NULL == pFrmInfo->mpDstVirFrmAddr[0]){aloge("fatal error! malloc mixer[%d] mem fail!", i);return -1;}pMixerPara->dst_image_h.laddr[0] = g2d_getPhyAddrByVirAddr(pFrmInfo->mpDstVirFrmAddr[0]);break;}case G2D_FORMAT_YUV420UVC_V1U1V0U0:{len = pFrmInfo->mDstWidth * pFrmInfo->mDstHeight;pFrmInfo->mpDstVirFrmAddr[0] = g2d_allocMem(len);if (NULL == pFrmInfo->mpDstVirFrmAddr[0]){aloge("fatal error! malloc mixer[%d] mem fail!", i);return -1;}pFrmInfo->mpDstVirFrmAddr[1] = g2d_allocMem(len / 2);if (NULL == pFrmInfo->mpDstVirFrmAddr[1]){aloge("fatal error! malloc mixer[%d] mem fail!", i);return -1;}pMixerPara->dst_image_h.laddr[0] = g2d_getPhyAddrByVirAddr(pFrmInfo->mpDstVirFrmAddr[0]);pMixerPara->dst_image_h.laddr[1] = g2d_getPhyAddrByVirAddr(pFrmInfo->mpDstVirFrmAddr[1]);break;}default:{aloge("mixer para[%d] dst format[%d] is invalid!", i,pMixerPara->src_image_h.format);return -1;}}}return ret;_release_src_file: // 清理标签releaseSrcFile(&p_g2d_ctx->mixertask.mYUVFrmInfo, &p_g2d_ctx->mixertask.mRGBFrmInfo);return ret;
}/*** @brief 执行G2D批处理任务** 该函数首先准备缓冲区和参数,然后调用 ioctl 执行批处理任务,最后保存结果。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 成功返回0,失败返回-1*/
static int SampleG2d_G2dMixter_Task(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;ret = prepareBuf(p_g2d_ctx); // 准备缓冲区if (ret < 0){aloge("prepare buf failed!!\n", ret);return -1;}// 准备ioctl参数unsigned long arg[2];arg[0] = (unsigned long)p_g2d_ctx->mixertask.stMixPara; // 指向任务参数数组arg[1] = FRAME_TO_BE_PROCESS; // 任务数量// 执行G2D批处理任务ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_MIXER_TASK, arg);if (ret < 0){aloge("fatal error! G2D_CMD_MIXER_TASK fail!");goto _release_buf; // 如果失败,跳转到清理缓冲区的标签}#if 1 // 条件编译,用于控制是否保存结果ret = saveDstFrm(&p_g2d_ctx->mixertask); // 保存处理结果if (ret){aloge("fatal error! save dst file fail!");}
#endif_release_buf: // 清理标签releaseBuf(&p_g2d_ctx->mixertask); // 释放所有缓冲区内存return 0;
}/*** @brief 根据配置的G2D模式执行相应的操作** 该函数是G2D操作的分发器,根据 `g2d_mod` 的值调用不同的处理函数。** @param p_g2d_ctx 指向G2D上下文的指针* @return int 操作结果*/
static int SampleG2d_G2dConvert(SAMPLE_G2D_CTX *p_g2d_ctx)
{int ret = 0;SampleG2dConfig *pConfig = &p_g2d_ctx->mConfigPara;if (0 == pConfig->g2d_mod){ret = SampleG2d_G2dConvert_rotate(p_g2d_ctx); // 旋转}else if (1 == pConfig->g2d_mod){ret = SampleG2d_G2dConvert_scale(p_g2d_ctx); // 缩放}else if (2 == pConfig->g2d_mod){ret = SampleG2d_G2dConvert_formatconversion(p_g2d_ctx); // 格式转换}else if (3 == pConfig->g2d_mod){ret = SampleG2d_G2dBld(p_g2d_ctx); // 透明叠加}else if (4 == pConfig->g2d_mod){ret = SampleG2d_G2dMixter_Task(p_g2d_ctx); // 批处理}return ret;
}/*** @brief 程序主函数** 程序的入口点,负责初始化、解析参数、配置G2D、执行操作和资源清理。** @param argc 参数个数* @param argv 参数数组* @return int 程序退出状态*/
int main(int argc, char *argv[])
{int ret = 0;unsigned int size1 = 0;unsigned int size2 = 0;unsigned int read_len = 0;unsigned int out_len = 0;SAMPLE_G2D_CTX g2d_ctx; // 声明G2D上下文结构体SAMPLE_G2D_CTX *p_g2d_ctx = &g2d_ctx; // 指向上下文的指针memset(p_g2d_ctx, 0, sizeof(SAMPLE_G2D_CTX)); // 初始化上下文SampleG2dConfig *pConfig = &p_g2d_ctx->mConfigPara; // 获取配置指针GLogConfig stGLogConfig = // 日志配置{.FLAGS_logtostderr = 0,.FLAGS_colorlogtostderr = 1,.FLAGS_stderrthreshold = _GLOG_INFO,.FLAGS_minloglevel = _GLOG_INFO,.FLAGS_logbuflevel = -1,.FLAGS_logbufsecs = 0,.FLAGS_max_log_size = 1,.FLAGS_stop_logging_if_full_disk = 1,};strcpy(stGLogConfig.LogDir, "/tmp/log");strcpy(stGLogConfig.InfoLogFileNameBase, "LOG-");strcpy(stGLogConfig.LogFileNameExtension, "IPC-");log_init(argv[0], &stGLogConfig); // 初始化日志系统/* 1. 解析命令行参数 */if (ParseCmdLine(argc, argv, &p_g2d_ctx->mCmdLinePara) != 0){ret = -1;return ret;}char *pConfigFilePath = NULL;if (strlen(p_g2d_ctx->mCmdLinePara.mConfigFilePath) > 0){pConfigFilePath = p_g2d_ctx->mCmdLinePara.mConfigFilePath;}else{pConfigFilePath = NULL;}/* 2. 读取配置文件 */if (loadSampleG2dConfig(&p_g2d_ctx->mConfigPara, pConfigFilePath) != SUCCESS){aloge("fatal error! no config file or parse conf file fail");ret = -1;goto _exit;}/* 3. 初始化 MPP (Media Process Platform) */memset(&p_g2d_ctx->mSysConf, 0, sizeof(MPP_SYS_CONF_S));p_g2d_ctx->mSysConf.nAlignWidth = 32; // 设置内存对齐宽度AW_MPI_SYS_SetConf(&p_g2d_ctx->mSysConf);ret = AW_MPI_SYS_Init(); // 初始化MPP系统if (ret < 0){aloge("sys Init failed!");ret = -1;goto _exit;}g2d_MemOpen(); // 打开和初始化ION内存管理器// 如果不是批处理模式if (pConfig->g2d_mod != 4){ret = PrepareFrmBuff(p_g2d_ctx); // 为源和目标图像准备缓冲区if (0 != ret){aloge("malloc frm buffer failed");ret = -1;goto _err1;}// 打开源图像文件p_g2d_ctx->fd_in = fopen(p_g2d_ctx->mConfigPara.SrcFile, "r");if (NULL == p_g2d_ctx->fd_in){aloge("open src file failed");ret = -1;goto _err2;}fseek(p_g2d_ctx->fd_in, 0, SEEK_SET); // 移动文件指针到开头// 根据g2d_mod打开目标文件if (3 == pConfig->g2d_mod) // 透明叠加:目标文件作为背景,需要读取{p_g2d_ctx->fd_out = fopen(p_g2d_ctx->mConfigPara.DstFile, "rb");if (NULL == p_g2d_ctx->fd_out){aloge("open out file failed");ret = -1;goto _err2;}fseek(p_g2d_ctx->fd_out, 0, SEEK_SET);}else // 其他模式:目标文件作为输出,需要写入{p_g2d_ctx->fd_out = fopen(p_g2d_ctx->mConfigPara.DstFile, "wb");if (NULL == p_g2d_ctx->fd_out){aloge("open out file failed");ret = -1;goto _err2;}fseek(p_g2d_ctx->fd_out, 0, SEEK_SET);}// 计算一帧Y分量的大小read_len = p_g2d_ctx->src_frm_info.frm_width * p_g2d_ctx->src_frm_info.frm_height;// 根据源图像格式从文件中读取数据if (pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 || pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420){size1 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[0], 1, read_len, p_g2d_ctx->fd_in);if (size1 != read_len){aloge("read_y_data_frm_src_file_invalid");}size2 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[1], 1, read_len / 2, p_g2d_ctx->fd_in);if (size2 != read_len / 2){aloge("read_c_data_frm_src_file_invalid");}fclose(p_g2d_ctx->fd_in); // 读取完成后关闭源文件// 刷新缓存,确保数据在内存中g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[1], read_len / 2);}else if (pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422 || pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422){size1 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[0], 1, read_len, p_g2d_ctx->fd_in);if (size1 != read_len){aloge("read_y_data_frm_src_file_invalid");}size2 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[1], 1, read_len, p_g2d_ctx->fd_in);if (size2 != read_len){aloge("read_c_data_frm_src_file_invalid");}fclose(p_g2d_ctx->fd_in);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[1], read_len);}else if (MM_PIXEL_FORMAT_RGB_888 == pConfig->mPicFormat){size1 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[0], 1, read_len * 3, p_g2d_ctx->fd_in);fclose(p_g2d_ctx->fd_in);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len * 3);}else if (MM_PIXEL_FORMAT_RGB_8888 == pConfig->mPicFormat){size1 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[0], 1, read_len * 4, p_g2d_ctx->fd_in);fclose(p_g2d_ctx->fd_in);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len * 4);}else if (MM_PIXEL_FORMAT_YUV_PLANAR_420 == pConfig->mPicFormat){fread(p_g2d_ctx->src_frm_info.p_vir_addr[0], 1, read_len, p_g2d_ctx->fd_in);fread(p_g2d_ctx->src_frm_info.p_vir_addr[1], 1, read_len / 4, p_g2d_ctx->fd_in);fread(p_g2d_ctx->src_frm_info.p_vir_addr[2], 1, read_len / 4, p_g2d_ctx->fd_in);fclose(p_g2d_ctx->fd_in);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[1], read_len / 4);g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[2], read_len / 4);}// 对于透明叠加模式,还需要读取背景图像数据if (3 == p_g2d_ctx->mConfigPara.g2d_mod){if (MM_PIXEL_FORMAT_RGB_888 == pConfig->mDstPicFormat){int read_len = pConfig->mDstWidth * pConfig->mDstHeight * 3;fread(p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len, 1, p_g2d_ctx->fd_out);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len);}else if (MM_PIXEL_FORMAT_RGB_8888 == pConfig->mDstPicFormat){int read_len = pConfig->mDstWidth * pConfig->mDstHeight * 4;fread(p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len, 1, p_g2d_ctx->fd_out);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len);}else if (MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 == pConfig->mDstPicFormat ||MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420 == pConfig->mDstPicFormat){int read_len = pConfig->mDstWidth * pConfig->mDstHeight;fread(p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len, 1, p_g2d_ctx->fd_out);fread(p_g2d_ctx->dst_frm_info.p_vir_addr[1], read_len / 2, 1, p_g2d_ctx->fd_out);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], read_len);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[1], read_len / 2);}}} // end of if (pConfig->g2d_mod != 4)// G2D相关操作开始 ->ret = SampleG2d_G2dOpen(p_g2d_ctx); // 打开G2D设备if (ret < 0){aloge("fatal error! open /dev/g2d fail!");goto _err2;}ret = SampleG2d_G2dConvert(p_g2d_ctx); // 根据配置执行G2D转换if (ret < 0){aloge("fatal error! g2d convert fail!");goto _close_g2d;}// G2D相关操作结束 <-// 特殊处理:透明叠加的结果保存到固定文件if (3 == pConfig->g2d_mod){FILE *fp = fopen("/mnt/extsd/g2d_bld_test.rgb", "wb+");if (fp){alogd("save file size[%dx%d]", p_g2d_ctx->dst_frm_info.frm_width,p_g2d_ctx->dst_frm_info.frm_height);if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 ||pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[1], out_len / 2);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, fp);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[1], 1, out_len / 2, fp);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_888){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 3;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, fp);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_8888){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 4;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, fp);}fclose(fp);fp = NULL;}goto _close_g2d; // 处理完特殊保存后直接跳转到关闭G2D}// 对于非批处理和非透明叠加模式,将结果写入目标文件if (pConfig->g2d_mod != 4){if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 ||pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[1], out_len / 2);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[1], 1, out_len / 2, p_g2d_ctx->fd_out);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_422){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[1], out_len);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[1], 1, out_len, p_g2d_ctx->fd_out);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_UYVY_PACKAGE_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_VYUY_PACKAGE_422 ||pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUYV_PACKAGE_422 || pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YVYU_AW_PACKAGE_422){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len * 2);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len * 2, p_g2d_ctx->fd_out);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_888){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 3;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_8888){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 4;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);}else if (pConfig->mDstPicFormat == MM_PIXEL_FORMAT_YUV_PLANAR_420){out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height;g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[1], out_len / 4);g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[2], out_len / 4);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[1], 1, out_len / 4, p_g2d_ctx->fd_out);fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[2], 1, out_len / 4, p_g2d_ctx->fd_out);}}_close_g2d: // 关闭G2D设备SampleG2d_G2dClose(p_g2d_ctx);_close_out_fd: // 关闭输出文件if (p_g2d_ctx->fd_out > 0)fclose(p_g2d_ctx->fd_out);_err2: // 错误处理标签2:释放帧缓冲区FreeFrmBuff(p_g2d_ctx);_err1: // 错误处理标签1:关闭G2D内存管理g2d_MemClose();_mpp_exit: // MPP退出标签AW_MPI_SYS_Exit();_exit: // 程序退出标签alogd("%s test result: %s", argv[0], ((0 == ret) ? "success" : "fail"));return 0; // 注意:这里总是返回0,即使操作失败
}
sample_g2d.h
#ifndef __SAMPLE_G2D_H__
#define __SAMPLE_G2D_H__#include <plat_type.h>#define FRAME_TO_BE_PROCESS (5) /* 一次批量处理的帧数 */
#define MAX_FILE_PATH_LEN (128) /* 文件路径最大长度 */
#define MAX_FILE_PATH_SIZE (256) /* 配置文件路径最大长度 *//* 命令行参数结构体:仅保存配置文件路径 */
typedef struct SampleG2dCmdLineParam_s
{char mConfigFilePath[MAX_FILE_PATH_SIZE];
}SampleG2dCmdLineParam;/* G2D 处理完整配置结构体 */
typedef struct SampleG2dConfig_s
{PIXEL_FORMAT_E mPicFormat; /* 源图像像素格式 */PIXEL_FORMAT_E mDstPicFormat; /* 目标图像像素格式 */int mSrcWidth; /* 源图像宽度 */int mSrcHeight; /* 源图像高度 */int mSrcRectX; /* 源裁剪区域起始 X */int mSrcRectY; /* 源裁剪区域起始 Y */int mSrcRectW; /* 源裁剪区域宽度 */int mSrcRectH; /* 源裁剪区域高度 */int mDstRotate; /* 目标旋转角度:0/90/180/270,顺时针 */int mDstWidth; /* 目标图像宽度 */int mDstHeight; /* 目标图像高度 */int mDstRectX; /* 目标区域起始 X(可用于偏移叠加) */int mDstRectY; /* 目标区域起始 Y */int mDstRectW; /* 目标区域有效宽度 */int mDstRectH; /* 目标区域有效高度 */char flip_flag; /* 翻转标志:0 不翻转,1 水平,2 垂直,3 水平+垂直 */char SrcFile[MAX_FILE_PATH_LEN]; /* 输入文件路径 */char DstFile[MAX_FILE_PATH_LEN]; /* 输出文件路径 */int g2d_mod; /* G2D 功能模式:0 缩放/旋转/翻转,1 颜色空间转换,2 混合等 */int g2d_bld_mod; /* G2D 混合模式:0 无混合,1 alpha 混合等 */
}SampleG2dConfig;/* 帧缓冲区信息结构:保存单帧虚拟/物理地址 */
typedef struct frm_info_s
{unsigned int frm_width;unsigned int frm_height;void *p_vir_addr[3]; /* 3 个分量的虚拟地址 */void *p_phy_addr[3]; /* 3 个分量的物理地址 */
}FRM_INFO;/* 混合任务专用输入格式配置 */
typedef struct SampleG2dMixerTaskConfig
{PIXEL_FORMAT_E mInputYUVFormat; /* 输入 YUV 格式 */PIXEL_FORMAT_E mInputRGBFormat; /* 输入 RGB 格式 */
}SampleG2dMixerTaskConfig;/* 混合任务帧信息:描述单帧宽高、格式及地址 */
typedef struct SampleG2dMixerTaskFrmInfo
{g2d_fmt_enh mSrcPicFormat; /* 源格式枚举 */int mSrcWidth;int mSrcHeight;g2d_fmt_enh mDstPicFormat; /* 目标格式枚举 */int mDstWidth;int mDstHeight;void *mpSrcVirFrmAddr[3]; /* 源帧虚拟地址 */void *mpDstVirFrmAddr[3]; /* 目标帧虚拟地址 */
}SampleG2dMixerTaskFrmInfo;/* 混合任务上下文:保存 G2D 句柄、帧数组及混合参数 */
typedef struct SampleG2dMixerTaskContext
{int mG2dFd; /* G2D 设备文件描述符 */SampleG2dMixerTaskFrmInfo mYUVFrmInfo; /* YUV 帧信息 */SampleG2dMixerTaskFrmInfo mRGBFrmInfo; /* RGB 帧信息 */SampleG2dMixerTaskFrmInfo mFrmInfo[FRAME_TO_BE_PROCESS]; /* 批量帧数组 */struct mixer_para stMixPara[FRAME_TO_BE_PROCESS]; /* 每帧对应的混合参数 */
}SampleG2dMixerTaskContext;/* 全局 G2D 示例上下文:整合配置、命令行、系统、帧缓冲、文件句柄等 */
typedef struct sample_g2d_ctx_s
{SampleG2dConfig mConfigPara; /* 来自配置文件的参数 */SampleG2dCmdLineParam mCmdLinePara; /* 来自命令行的参数 */SampleG2dMixerTaskContext mixertask;/* 混合任务上下文 */MPP_SYS_CONF_S mSysConf; /* MPP 系统配置 */FRM_INFO src_frm_info; /* 源帧信息 */FRM_INFO dst_frm_info; /* 目标帧信息 */FILE * fd_in; /* 输入文件句柄 */FILE * fd_out; /* 输出文件句柄 */int mG2dFd; /* G2D 设备句柄 */
}SAMPLE_G2D_CTX;#endif
在全志 SDK 目录激活环境,并选择方案:
source build/envsetup.sh
lunch
进入配置界面:
make menuconfig
选择 MPP 示例程序,保存并退出:
Allwinner --->eyesee-mpp --->[*] select mpp sample
清理和编译MPP程序:
cleanmpp
mkmpp
进入目录,将编译出的文件上传到开发板:
cd ~/tina-v853-100ask/external/eyesee-mpp/middleware/sun8iw21/sample/bin
adb push sample_g2d sample_g2d.conf /mnt/UDISK
在开发板上运行程序:
./sample_g2d -path ./sample_g2d.conf
3.2 实时预览中加入旋转缩放功能
示例:从 mpi_vi 组件获取视频帧, 调用 g2d 驱动做旋转、剪切、缩放等处理,处理后的图像送 mpi_vo 显示,也可保存为原始图片供分析
步骤:
- 创建视频帧管理器,用于管理G2D转换后的帧
- 初始化VI模块
- 初始化G2D转换
- 创建获取图像处理线程
- 初始化VO模块
- 创建显示图像处理线程
- 等待退出并清理资源
示例代码解析:
sample_vi_g2d.c
#define LOG_TAG "sample_vi_g2d"
#include <utils/plat_log.h> // 平台日志系统头文件
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/g2d_driver.h> // G2D硬件加速驱动头文件
#include <vo/hwdisplay.h>
#include <mpi_vo.h> // 视频输出模块接口
#include <media/mpi_sys.h> // 系统模块接口
#include <media/mm_comm_vi.h> // 视频输入通用头文件
#include <mpi_videoformat_conversion.h>
#include <SystemBase.h>
#include <VideoFrameInfoNode.h>
#include "media/mpi_vi.h" // 视频输入模块接口
#include "media/mpi_isp.h" // 图像信号处理模块接口
#include <utils/PIXEL_FORMAT_E_g2d_format_convert.h> // 像素格式转换工具
#include <utils/VIDEO_FRAME_INFO_S.h> // 视频帧信息结构体
#include "sample_vi_g2d.h" // 本模块头文件
#include "sample_vi_g2d_config.h" // 配置文件键值定义
#include <cdx_list.h> // 链表操作库#define ISP_RUN 1 // 宏定义,1表示启用ISP模块,0表示不启用/*** 解析命令行参数* @param argc 参数个数* @param argv 参数数组* @param pCmdLinePara 存储解析结果的结构体指针* @return 0: 成功解析; 1: 打印帮助信息; -1: 解析失败*/
static int ParseCmdLine(int argc, char **argv, SampleViG2dCmdLineParam *pCmdLinePara)
{alogd("sample virvi path:[%s], arg number is [%d]", argv[0], argc);int ret = 0;int i = 1; // 从argv[1]开始遍历,跳过程序名memset(pCmdLinePara, 0, sizeof(SampleViG2dCmdLineParam)); // 初始化参数结构体while (i < argc){// 处理 -path 参数,用于指定配置文件路径if (!strcmp(argv[i], "-path")){if (++i >= argc) // 检查是否有值{aloge("fatal error! use -h to learn how to set parameter!!!");ret = -1;break;}// 检查路径长度是否超出限制if (strlen(argv[i]) >= MAX_FILE_PATH_SIZE){aloge("fatal error! file path[%s] too long: [%d]>=[%d]!", argv[i], strlen(argv[i]), MAX_FILE_PATH_SIZE);}// 复制路径到结构体strncpy(pCmdLinePara->mConfigFilePath, argv[i], MAX_FILE_PATH_SIZE - 1);pCmdLinePara->mConfigFilePath[MAX_FILE_PATH_SIZE - 1] = '\0'; // 确保字符串结束}// 处理 -h 参数,打印帮助信息else if (!strcmp(argv[i], "-h")){alogd("CmdLine param:\n""\t-path /mnt/extsd/sample_vi_g2d.conf"); // 打印使用示例ret = 1; // 返回1表示需要打印帮助break;}else{// 忽略无效参数alogd("ignore invalid CmdLine param:[%s], type -h to get how to set parameter!", argv[i]);}i++;}return ret;
}/*** 从配置文件加载参数* @param pConfig 存储配置的结构体指针* @param pConfPath 配置文件路径,为NULL则使用默认值* @return SUCCESS: 成功; FAILURE: 失败*/
static ERRORTYPE loadSampleViG2dConfig(SampleViG2dConfig *pConfig, const char *pConfPath)
{int ret = SUCCESS;// 设置默认配置值pConfig->mDevNum = 0;pConfig->mFrameRate = 60;pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // NV21pConfig->mDropFrameNum = 60;pConfig->mSrcWidth = 1920;pConfig->mSrcHeight = 1080;pConfig->mSrcRectX = 0;pConfig->mSrcRectY = 0;pConfig->mSrcRectW = 1920;pConfig->mSrcRectH = 1080;pConfig->mDstRotate = 90; // 默认旋转90度pConfig->mDstWidth = 1080;pConfig->mDstHeight = 1920;pConfig->mDstRectX = 0;pConfig->mDstRectY = 0;pConfig->mDstRectW = 1080;pConfig->mDstRectH = 1920;pConfig->mDstStoreCount = 2;pConfig->mDstStoreInterval = 60;strcpy(pConfig->mStoreDir, "/mnt/extsd");pConfig->mDisplayFlag = true;pConfig->mDisplayX = 60;pConfig->mDisplayY = 0;pConfig->mDisplayW = 360;pConfig->mDisplayH = 640;pConfig->mTestDuration = 30;// 如果提供了配置文件路径,则尝试加载if (pConfPath != NULL){CONFPARSER_S stConfParser; // 配置解析器结构体ret = createConfParser(pConfPath, &stConfParser); // 创建并初始化解析器if (ret < 0){aloge("load conf fail");return FAILURE;}// 从配置文件中读取各项参数pConfig->mDevNum = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DEV_NUM, 0);pConfig->mFrameRate = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_FRAME_RATE, 0);// 处理像素格式,字符串转枚举char *pStrPixelFormat = (char *) GetConfParaString(&stConfParser, SAMPLE_VIG2D_KEY_PIC_FORMAT, NULL);if (!strcmp(pStrPixelFormat, "nv21")){pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420;}else if (!strcmp(pStrPixelFormat, "nv12")){pConfig->mPicFormat = MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420;}else{aloge("fatal error! conf file pic_format[%s] is unsupported", pStrPixelFormat);pConfig->mPicFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420; // 回退到默认}// 读取源图像参数pConfig->mDropFrameNum = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DROP_FRAME_NUM, 0);pConfig->mSrcWidth = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_WIDTH, 0);pConfig->mSrcHeight = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_HEIGHT, 0);pConfig->mSrcRectX = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_RECT_X, 0);pConfig->mSrcRectY = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_RECT_Y, 0);pConfig->mSrcRectW = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_RECT_W, 0);pConfig->mSrcRectH = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_SRC_RECT_H, 0);// 读取目标图像参数(旋转、尺寸、矩形)pConfig->mDstRotate = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_ROTATE, 0);pConfig->mDstWidth = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_WIDTH, 0);pConfig->mDstHeight = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_HEIGHT, 0);pConfig->mDstRectX = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_RECT_X, 0);pConfig->mDstRectY = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_RECT_Y, 0);pConfig->mDstRectW = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_RECT_W, 0);pConfig->mDstRectH = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_RECT_H, 0);// 读取存储参数pConfig->mDstStoreCount = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_STORE_COUNT, 0);pConfig->mDstStoreInterval = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DST_STORE_INTERVAL, 0);char *pStrDir = (char *) GetConfParaString(&stConfParser, SAMPLE_VIG2D_KEY_STORE_DIR, NULL);strcpy(pConfig->mStoreDir, pStrDir); // 复制存储目录// 读取显示参数pConfig->mDisplayFlag = (bool) GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DISPLAY_FLAG, 0);pConfig->mDisplayX = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DISPLAY_X, 0);pConfig->mDisplayY = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DISPLAY_Y, 0);pConfig->mDisplayW = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DISPLAY_W, 0);pConfig->mDisplayH = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_DISPLAY_H, 0);// 读取测试时长pConfig->mTestDuration = GetConfParaInt(&stConfParser, SAMPLE_VIG2D_KEY_TEST_DURATION, 0);destroyConfParser(&stConfParser); // 销毁配置解析器,释放资源}// 打印最终加载的配置,用于调试alogd("dev_num=%d, frameRate=%d, pixFmt=0x%x, dropFrameNum=%d,srcSize=[%dx%d], srcRect=[%d,%d,%dx%d],""dstRot=%d, dstSize=[%dx%d], dstRect=[%d,%d,%dx%d], dstStoreCnt=%d, dstStoreInterval=%d, storeDir=[%s],""displayFlag=%d, displayRect=[%d,%d,%dx%d], testDuration=%d",pConfig->mDevNum, pConfig->mFrameRate, pConfig->mPicFormat, pConfig->mDropFrameNum,pConfig->mSrcWidth, pConfig->mSrcHeight, pConfig->mSrcRectX, pConfig->mSrcRectY, pConfig->mSrcRectW, pConfig->mSrcRectH,pConfig->mDstRotate, pConfig->mDstWidth, pConfig->mDstHeight, pConfig->mDstRectX, pConfig->mDstRectY, pConfig->mDstRectW, pConfig->mDstRectH,pConfig->mDstStoreCount, pConfig->mDstStoreInterval, pConfig->mStoreDir,pConfig->mDisplayFlag, pConfig->mDisplayX, pConfig->mDisplayY, pConfig->mDisplayW, pConfig->mDisplayH, pConfig->mTestDuration);return ret;
}/*** 保存原始图像到文件* @param pViCapCtx VI捕获上下文* @param pFrameInfo 要保存的视频帧信息* @return 0: 成功; -1: 失败*/
static int saveRawPicture(SampleViCapS *pViCapCtx, VIDEO_FRAME_INFO_S *pFrameInfo)
{int ret = 0;SampleViG2dContext *pContext = (SampleViG2dContext *) pViCapCtx->mpContext;// 根据像素格式确定文件后缀名int i;char strPixFmt[16] = {0};switch (pContext->mConfigPara.mPicFormat){case MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420:strcpy(strPixFmt, "nv12");break;case MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420:strcpy(strPixFmt, "nv21");break;default:strcpy(strPixFmt, "unknown");break;}// 构造文件名,如 /mnt/extsd/pic[0].NV21Mchar strFilePath[MAX_FILE_PATH_SIZE];snprintf(strFilePath, MAX_FILE_PATH_SIZE, "%s/pic[%d].%s", pContext->mConfigPara.mStoreDir, pViCapCtx->mRawStoreNum, strPixFmt);FILE *fpPic = fopen(strFilePath, "wb"); // 以二进制写模式打开文件if (fpPic != NULL){VideoFrameBufferSizeInfo FrameSizeInfo;getVideoFrameBufferSizeInfo(pFrameInfo, &FrameSizeInfo); // 获取Y、U、V平面的大小int yuvSize[3] = {FrameSizeInfo.mYSize, FrameSizeInfo.mUSize, FrameSizeInfo.mVSize};// 循环写入Y、U、V三个平面的数据for (i = 0; i < 3; i++){if (pFrameInfo->VFrame.mpVirAddr[i] != NULL){// 在写入前,确保MMZ缓存中的数据是最新的(刷新缓存)AW_MPI_SYS_MmzFlushCache(pFrameInfo->VFrame.mPhyAddr[i], pFrameInfo->VFrame.mpVirAddr[i], yuvSize[i]);fwrite(pFrameInfo->VFrame.mpVirAddr[i], 1, yuvSize[i], fpPic); // 写入数据alogd("virAddr[%d]=[%p], length=[%d]", i, pFrameInfo->VFrame.mpVirAddr[i], yuvSize[i]);// 写入后,可能需要再次刷新,确保数据已写入物理内存AW_MPI_SYS_MmzFlushCache(pFrameInfo->VFrame.mPhyAddr[i], pFrameInfo->VFrame.mpVirAddr[i], yuvSize[i]);}}fclose(fpPic); // 关闭文件alogd("store raw frame in file[%s]", strFilePath);ret = 0;}else{aloge("fatal error! open file[%s] fail!", strFilePath);ret = -1;}return ret;
}/*******************************************************************************
Function name: GetCSIFrameThread
Description: 这是一个独立的线程函数,负责:1. 从VI模块(VIPP)获取原始视频帧。2. 使用G2D硬件加速器对帧进行处理(旋转、缩放、裁剪)。3. 将处理后的帧放入视频帧管理器的“就绪”列表。4. 按配置要求,将处理后的帧保存到文件系统。
Parameters: pArg: 传入的参数,是 SampleViCapS 结构体的指针。
Return: void* 线程退出状态
Time: 2020/5/4
*******************************************************************************/
static void *GetCSIFrameThread(void *pArg)
{int ret = 0;int i = 0; // 帧计数器SampleViCapS *pViCapCtx = (SampleViCapS *) pArg;SampleViG2dContext *pContext = (SampleViG2dContext *) pViCapCtx->mpContext;VIDEO_FRAME_INFO_S stSrcFrameInfo; // 存储从VI获取的原始帧VIDEO_FRAME_INFO_S *pDstFrameInfo = NULL; // 指向从帧管理器获取的目标帧alogd("Cap threadid=0x%lx, ViDev = %d, ViCh = %d", pViCapCtx->mThreadId, pViCapCtx->mDev, pViCapCtx->mChn);while (1){// 检查退出标志,如果被设置则退出线程if (pContext->mbExitFlag){alogd("csi frame thread detects exit flag:%d, exit now", pContext->mbExitFlag);break;}// 从指定的VI设备和通道获取一帧图像if ((ret = AW_MPI_VI_GetFrame(pViCapCtx->mDev, pViCapCtx->mChn, &stSrcFrameInfo, pViCapCtx->mTimeout)) != 0){alogw("Get Frame from viChn[%d,%d] failed!", pViCapCtx->mDev, pViCapCtx->mChn);continue; // 获取失败,继续循环尝试}i++; // 成功获取一帧,计数器加1// 从视频帧管理器获取一个空闲的目标帧缓冲区_retryGetIdelFrame:pDstFrameInfo = pContext->mpFrameManager->GetIdleFrame(pContext->mpFrameManager, 1000);if (NULL == pDstFrameInfo){aloge("fatal error! why video frame manager has none idle frame?");goto _retryGetIdelFrame; // 等待直到有空闲帧}// 使用G2D硬件加速器进行图像格式转换、旋转、缩放、裁剪ret = pViCapCtx->mpG2dConvert->G2dConvertVideoFrame(pViCapCtx->mpG2dConvert, &stSrcFrameInfo, pDstFrameInfo);if (0 == ret){// G2D转换成功,通知帧管理器该帧已填充完毕,可以进入“就绪”状态ret = pContext->mpFrameManager->FillingFrameDone(pContext->mpFrameManager, pDstFrameInfo);if (ret != 0){aloge("fatal error! Filling frame done fail[%d], pDstFrame[%p]", ret, pDstFrameInfo);}}else{// G2D转换失败aloge("fatal error! g2d convert fail[%d]", ret);// 通知帧管理器填充失败,将该帧归还为空闲帧ret = pContext->mpFrameManager->FillingFrameFail(pContext->mpFrameManager, pDstFrameInfo);if (ret != 0){aloge("fatal error! Filling frame fail fail[%d], pDstFrame[%p]", ret, pDstFrameInfo);}}// 释放从VI获取的原始帧,归还给VI模块if ((ret = AW_MPI_VI_ReleaseFrame(pViCapCtx->mDev, pViCapCtx->mChn, &stSrcFrameInfo)) != 0){aloge("fatal error! release Frame from viChn[%d,%d] failed!", pViCapCtx->mDev, pViCapCtx->mChn);}// 检查是否需要存储原始处理后的图像if (pViCapCtx->mRawStoreNum < pContext->mConfigPara.mDstStoreCount) // 检查存储数量限制{if (0 == i % pContext->mConfigPara.mDstStoreInterval) // 检查是否达到存储间隔{ret = saveRawPicture(pViCapCtx, pDstFrameInfo);if (ret != 0){aloge("fatal error! save raw picture fail[%d]", ret);}pViCapCtx->mRawStoreNum++; // 已存储数量加1}}}return (void *) ret;
}/*** 显示线程函数* 功能:从视频帧管理器获取已处理好的“就绪”帧,并发送到VO(视频输出)模块进行显示。* @param pArg: 传入的参数,是 SampleVoDisplayS 结构体的指针。* @return void* 线程退出状态*/
static void *DisplayThread(void *pArg)
{int ret = 0;SampleVoDisplayS *pVoDisplayCtx = (SampleVoDisplayS *) pArg;SampleViG2dContext *pContext = pVoDisplayCtx->mpContext;VIDEO_FRAME_INFO_S *pFrameInfo = NULL;while (1){// 检查退出标志if (pContext->mbExitFlag){alogd("display thread detects exit flag:%d, exit now", pContext->mbExitFlag);break;}// 从视频帧管理器获取一个“就绪”的视频帧pFrameInfo = pContext->mpFrameManager->GetReadyFrame(pContext->mpFrameManager, 200);if (NULL == pFrameInfo){alogw("Be careful! get ready frame fail"); // 获取失败,可能是帧率低或处理慢continue;}// 将获取到的帧发送给VO通道进行显示ret = AW_MPI_VO_SendFrame(pVoDisplayCtx->mVoLayer, pVoDisplayCtx->mVOChn, pFrameInfo, 0);if (ret != SUCCESS){aloge("fatal error! send frame to vo fail[0x%x]!", ret);}// 注意:帧的释放由VO模块的回调函数SampleViG2d_VOCallback在显示完成后处理}return (void *) ret;
}/*** 初始化 SampleViCapS 结构体* @param pThiz: 要初始化的结构体指针* @return 0*/
int initSampleViCapS(SampleViCapS *pThiz)
{if (pThiz){memset(pThiz, 0, sizeof(*pThiz)); // 清零}return 0;
}/*** 销毁 SampleViCapS 结构体* @param pThiz: 要销毁的结构体指针* @return 0*/
int destroySampleViCapS(SampleViCapS *pThiz)
{// 检查是否有线程仍在运行或资源未释放,用于调试if (pThiz->mbThreadExistFlag){aloge("fatal error! SampleViCapS thread is still exist!");}if (pThiz->mpG2dConvert){aloge("fatal error! SampleViCapS G2dConvert[%p] is still exist!", pThiz->mpG2dConvert);}return 0;
}/*** 初始化 SampleVoDisplayS 结构体* @param pThiz: 要初始化的结构体指针* @return 0*/
int initSampleVoDisplayS(SampleVoDisplayS *pThiz)
{if (pThiz){memset(pThiz, 0, sizeof(*pThiz));}return 0;
}/*** 销毁 SampleVoDisplayS 结构体* @param pThiz: 要销毁的结构体指针* @return 0*/
int destroySampleVoDisplayS(SampleVoDisplayS *pThiz)
{if (pThiz->mbThreadExistFlag){aloge("fatal error! SampleVoDisplayS thread is still exist!");}return 0;
}/*** 打开G2D设备节点* @param pThiz: G2D转换器实例* @return 0: 成功; -1: 失败*/
static int SampleViG2d_G2dConvert_G2dOpen(SampleViG2d_G2dConvert *pThiz)
{int ret = 0;// 以读写方式打开G2D设备文件pThiz->mG2dFd = open("/dev/g2d", O_RDWR, 0);if (pThiz->mG2dFd < 0){aloge("fatal error! open /dev/g2d failed");ret = -1;}return ret;
}/*** 关闭G2D设备节点* @param pThiz: G2D转换器实例* @return 0*/
static int SampleViG2d_G2dConvert_G2dClose(SampleViG2d_G2dConvert *pThiz)
{if (pThiz->mG2dFd >= 0){close(pThiz->mG2dFd);pThiz->mG2dFd = -1; // 文件描述符置为无效}return 0;
}/*** 设置G2D转换参数* @param pThiz: G2D转换器实例* @param pConvertParam: 包含转换参数的结构体指针* @return 0*/
static int SampleViG2d_G2dConvert_G2dSetConvertParam(SampleViG2d_G2dConvert *pThiz, G2dConvertParam *pConvertParam)
{if (pConvertParam){pThiz->mConvertParam = *pConvertParam; // 复制参数}return 0;
}/*******************************************************************************
Function name: SampleViG2d_G2dConvert_G2dConvertVideoFrame
Description: 使用G2D硬件加速器将源视频帧转换(旋转、缩放、裁剪)为目标视频帧。这是主要的转换函数,支持旋转。
Parameters: pThiz: G2D转换器实例pSrcFrameInfo: 源视频帧信息pDstFrameInfo: 目标视频帧信息
Return: 0: 成功; <0: 失败
Time: 2020/5/4
*******************************************************************************/
static int SampleViG2d_G2dConvert_G2dConvertVideoFrame(SampleViG2d_G2dConvert *pThiz, VIDEO_FRAME_INFO_S *pSrcFrameInfo, VIDEO_FRAME_INFO_S *pDstFrameInfo)
{// 检查目标帧的尺寸是否与设置的转换参数一致if (pThiz->mConvertParam.mDstWidth != pDstFrameInfo->VFrame.mWidth || pThiz->mConvertParam.mDstHeight != pDstFrameInfo->VFrame.mHeight){aloge("fatal error! dst size is not match: [%dx%d]!=[%dx%d]", pThiz->mConvertParam.mDstWidth, pThiz->mConvertParam.mDstHeight, pDstFrameInfo->VFrame.mWidth, pDstFrameInfo->VFrame.mHeight);return -1;}int ret = 0;g2d_blt_h blit; // G2D位块传输(BitBLT)操作的结构体g2d_fmt_enh eSrcFormat, eDstFormat; // G2D内部使用的源和目标像素格式// 将MPP的像素格式转换为G2D驱动能识别的格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pSrcFrameInfo->VFrame.mPixelFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pSrcFrameInfo->VFrame.mPixelFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pDstFrameInfo->VFrame.mPixelFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pDstFrameInfo->VFrame.mPixelFormat);return -1;}// 配置blit结构体memset(&blit, 0, sizeof(g2d_blt_h));// 根据设置的旋转角度配置旋转标志switch (pThiz->mConvertParam.mDstRotate){case 0:blit.flag_h = G2D_BLT_NONE_H; // 无旋转break;case 90:blit.flag_h = G2D_ROT_90; // 顺时针旋转90度break;case 180:blit.flag_h = G2D_ROT_180; // 旋转180度break;case 270:blit.flag_h = G2D_ROT_270; // 顺时针旋转270度(或逆时针90度)break;default:aloge("fatal error! rotation[%d] is invalid!", pThiz->mConvertParam.mDstRotate);blit.flag_h = G2D_BLT_NONE_H;break;}// 配置源图像信息blit.src_image_h.format = eSrcFormat; // 像素格式// 物理地址,G2D直接访问物理内存blit.src_image_h.laddr[0] = pSrcFrameInfo->VFrame.mPhyAddr[0]; // Y平面blit.src_image_h.laddr[1] = pSrcFrameInfo->VFrame.mPhyAddr[1]; // UV平面(NV12/NV21)或U平面blit.src_image_h.laddr[2] = pSrcFrameInfo->VFrame.mPhyAddr[2]; // V平面(仅用于Planar格式)blit.src_image_h.width = pSrcFrameInfo->VFrame.mWidth;blit.src_image_h.height = pSrcFrameInfo->VFrame.mHeight;// 对齐,通常为0blit.src_image_h.align[0] = 0;blit.src_image_h.align[1] = 0;blit.src_image_h.align[2] = 0;// 裁剪矩形,定义从源帧的哪个区域进行处理blit.src_image_h.clip_rect.x = pThiz->mConvertParam.mSrcRectX;blit.src_image_h.clip_rect.y = pThiz->mConvertParam.mSrcRectY;blit.src_image_h.clip_rect.w = pThiz->mConvertParam.mSrcRectW;blit.src_image_h.clip_rect.h = pThiz->mConvertParam.mSrcRectH;blit.src_image_h.gamut = G2D_BT601; // 色域blit.src_image_h.bpremul = 0; // 预乘Alpha,此处未使用blit.src_image_h.mode = G2D_PIXEL_ALPHA; // Alpha混合模式blit.src_image_h.fd = -1; // 文件描述符,未使用blit.src_image_h.use_phy_addr = 1; // 使用物理地址// 配置目标图像信息blit.dst_image_h.format = eDstFormat;// 目标帧的物理地址blit.dst_image_h.laddr[0] = pDstFrameInfo->VFrame.mPhyAddr[0];blit.dst_image_h.laddr[1] = pDstFrameInfo->VFrame.mPhyAddr[1];blit.dst_image_h.laddr[2] = pDstFrameInfo->VFrame.mPhyAddr[2];blit.dst_image_h.width = pDstFrameInfo->VFrame.mWidth;blit.dst_image_h.height = pDstFrameInfo->VFrame.mHeight;blit.dst_image_h.align[0] = 0;blit.dst_image_h.align[1] = 0;blit.dst_image_h.align[2] = 0;// 目标裁剪矩形,定义处理后的图像放置在目标帧的哪个位置blit.dst_image_h.clip_rect.x = pThiz->mConvertParam.mDstRectX;blit.dst_image_h.clip_rect.y = pThiz->mConvertParam.mDstRectY;blit.dst_image_h.clip_rect.w = pThiz->mConvertParam.mDstRectW;blit.dst_image_h.clip_rect.h = pThiz->mConvertParam.mDstRectH;blit.dst_image_h.gamut = G2D_BT601;blit.dst_image_h.bpremul = 0;blit.dst_image_h.mode = G2D_PIXEL_ALPHA;blit.dst_image_h.fd = -1;blit.dst_image_h.use_phy_addr = 1;// 调用ioctl系统调用,向G2D驱动发送G2D_CMD_BITBLT_H命令,执行位块传输(即转换操作)ret = ioctl(pThiz->mG2dFd, G2D_CMD_BITBLT_H, (unsigned long) &blit);if (ret < 0){aloge("fatal error! bit-block(image) transfer failed[%d]", ret);// 调试信息:尝试dump G2D寄存器system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump");}// 设置目标帧的偏移信息,可能用于后续处理pDstFrameInfo->VFrame.mOffsetTop = pThiz->mConvertParam.mDstRectY;pDstFrameInfo->VFrame.mOffsetBottom = pThiz->mConvertParam.mDstRectY + pThiz->mConvertParam.mDstRectH;pDstFrameInfo->VFrame.mOffsetLeft = pThiz->mConvertParam.mDstRectX;pDstFrameInfo->VFrame.mOffsetRight = pThiz->mConvertParam.mDstRectX + pThiz->mConvertParam.mDstRectW;return ret;
}/*** 另一种G2D转换实现,使用MIXER_TASK命令。* 注意:此函数目前不支持旋转。* @param pThiz: G2D转换器实例* @param pSrcFrameInfo: 源视频帧信息* @param pDstFrameInfo: 目标视频帧信息* @return 0: 成功; -1: 失败*/
static int SampleViG2d_G2dConvert_G2dConvertVideoFrame_1(SampleViG2d_G2dConvert *pThiz, VIDEO_FRAME_INFO_S *pSrcFrameInfo, VIDEO_FRAME_INFO_S *pDstFrameInfo)
{// 检查是否支持旋转,当前实现不支持if (pThiz->mConvertParam.mDstRotate != 0){aloge("fatal error! g2d cmd mixer task don't support rotate[%d]", pThiz->mConvertParam.mDstRotate);return -1;}int ret = 0;g2d_fmt_enh eSrcFormat, eDstFormat;// 转换像素格式ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pSrcFrameInfo->VFrame.mPixelFormat, &eSrcFormat);if (ret != SUCCESS){aloge("fatal error! src pixel format[0x%x] is invalid!", pSrcFrameInfo->VFrame.mPixelFormat);return -1;}ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pDstFrameInfo->VFrame.mPixelFormat, &eDstFormat);if (ret != SUCCESS){aloge("fatal error! dst pixel format[0x%x] is invalid!", pDstFrameInfo->VFrame.mPixelFormat);return -1;}int num = 1; // 任务数量,这里只处理一个任务// 为mixer任务参数分配内存struct mixer_para *g2d_info = (struct mixer_para *) malloc(sizeof(*g2d_info) * num);if (NULL == g2d_info){aloge("fatal error! malloc fail!");return -1;}memset(g2d_info, 0, sizeof(*g2d_info) * num);int idx = 0;g2d_info[idx].op_flag = OP_BITBLT; // 操作标志:位块传输g2d_info[idx].flag_h = G2D_BLT_NONE_H; // 无特殊标志// 配置源图像信息(与上面的函数类似)g2d_info[idx].src_image_h.format = eSrcFormat;g2d_info[idx].src_image_h.laddr[0] = pSrcFrameInfo->VFrame.mPhyAddr[0];g2d_info[idx].src_image_h.laddr[1] = pSrcFrameInfo->VFrame.mPhyAddr[1];g2d_info[idx].src_image_h.laddr[2] = pSrcFrameInfo->VFrame.mPhyAddr[2];g2d_info[idx].src_image_h.width = pSrcFrameInfo->VFrame.mWidth;g2d_info[idx].src_image_h.height = pSrcFrameInfo->VFrame.mHeight;g2d_info[idx].src_image_h.align[0] = 0;g2d_info[idx].src_image_h.align[1] = 0;g2d_info[idx].src_image_h.align[2] = 0;g2d_info[idx].src_image_h.clip_rect.x = pThiz->mConvertParam.mSrcRectX;g2d_info[idx].src_image_h.clip_rect.y = pThiz->mConvertParam.mSrcRectY;g2d_info[idx].src_image_h.clip_rect.w = pThiz->mConvertParam.mSrcRectW;g2d_info[idx].src_image_h.clip_rect.h = pThiz->mConvertParam.mSrcRectH;g2d_info[idx].src_image_h.gamut = G2D_BT601;g2d_info[idx].src_image_h.bpremul = 0;g2d_info[idx].src_image_h.mode = G2D_PIXEL_ALPHA;g2d_info[idx].src_image_h.fd = -1;g2d_info[idx].src_image_h.use_phy_addr = 1;// 配置目标图像信息(与上面的函数类似)g2d_info[idx].dst_image_h.format = eDstFormat;g2d_info[idx].dst_image_h.laddr[0] = pDstFrameInfo->VFrame.mPhyAddr[0];g2d_info[idx].dst_image_h.laddr[1] = pDstFrameInfo->VFrame.mPhyAddr[1];g2d_info[idx].dst_image_h.laddr[2] = pDstFrameInfo->VFrame.mPhyAddr[2];g2d_info[idx].dst_image_h.width = pDstFrameInfo->VFrame.mWidth;g2d_info[idx].dst_image_h.height = pDstFrameInfo->VFrame.mHeight;g2d_info[idx].dst_image_h.align[0] = 0;g2d_info[idx].dst_image_h.align[1] = 0;g2d_info[idx].dst_image_h.align[2] = 0;g2d_info[idx].dst_image_h.clip_rect.x = pThiz->mConvertParam.mDstRectX;g2d_info[idx].dst_image_h.clip_rect.y = pThiz->mConvertParam.mDstRectY;g2d_info[idx].dst_image_h.clip_rect.w = pThiz->mConvertParam.mDstRectW;g2d_info[idx].dst_image_h.clip_rect.h = pThiz->mConvertParam.mDstRectH;g2d_info[idx].dst_image_h.gamut = G2D_BT601;g2d_info[idx].dst_image_h.bpremul = 0;g2d_info[idx].dst_image_h.mode = G2D_PIXEL_ALPHA;g2d_info[idx].dst_image_h.fd = -1;g2d_info[idx].dst_image_h.use_phy_addr = 1;// 准备ioctl调用的参数unsigned long arg[2] = {0};arg[0] = (unsigned long) g2d_info; // 指向mixer参数数组arg[1] = num; // 任务数量// 调用ioctl,发送G2D_CMD_MIXER_TASK命令ret = ioctl(pThiz->mG2dFd, G2D_CMD_MIXER_TASK, arg);if (ret < 0){aloge("fatal error! G2D_CMD_MIXER_TASK failure![%s]", strerror(errno));}// 释放动态分配的内存if (g2d_info != NULL){free(g2d_info);g2d_info = NULL;}// 设置目标帧的偏移信息pDstFrameInfo->VFrame.mOffsetTop = pThiz->mConvertParam.mDstRectY;pDstFrameInfo->VFrame.mOffsetBottom = pThiz->mConvertParam.mDstRectY + pThiz->mConvertParam.mDstRectH;pDstFrameInfo->VFrame.mOffsetLeft = pThiz->mConvertParam.mDstRectX;pDstFrameInfo->VFrame.mOffsetRight = pThiz->mConvertParam.mDstRectX + pThiz->mConvertParam.mDstRectW;return ret;
}/*** 构造并初始化一个G2D转换器对象* @return 成功返回对象指针,失败返回NULL*/
SampleViG2d_G2dConvert *constructSampleViG2d_G2dConvert()
{// 分配内存SampleViG2d_G2dConvert *pConvert = (SampleViG2d_G2dConvert *) malloc(sizeof(SampleViG2d_G2dConvert));if (NULL == pConvert){aloge("fatal error! malloc fail!");return NULL;}memset(pConvert, 0, sizeof(*pConvert));pConvert->mG2dFd = -1; // 初始化文件描述符// 初始化函数指针,将成员函数指向具体的实现pConvert->G2dOpen = SampleViG2d_G2dConvert_G2dOpen;pConvert->G2dClose = SampleViG2d_G2dConvert_G2dClose;pConvert->G2dSetConvertParam = SampleViG2d_G2dConvert_G2dSetConvertParam;pConvert->G2dConvertVideoFrame = SampleViG2d_G2dConvert_G2dConvertVideoFrame; // 默认使用支持旋转的函数return pConvert;
}/*** 销毁G2D转换器对象* @param pThiz: 要销毁的对象指针*/
void destructSampleViG2d_G2dConvert(SampleViG2d_G2dConvert *pThiz)
{if (pThiz != NULL){if (pThiz->mG2dFd >= 0){close(pThiz->mG2dFd);pThiz->mG2dFd = -1;}free(pThiz); // 释放对象内存}
}/*******************************************************************************
Function name: SampleViG2d_VideoFrameManager_GetIdleFrame
Description: 从视频帧管理器中获取一个空闲的视频帧缓冲区。这是生产者-消费者模式中的“获取空闲资源”操作。
Parameters: pThiz: 帧管理器实例nTimeout: 超时时间(毫秒)-1: 无限等待0: 立即返回>0: 等待指定时间
Return: 成功返回指向VIDEO_FRAME_INFO_S的指针,失败返回NULL
Time: 2020/4/30
*******************************************************************************/
static VIDEO_FRAME_INFO_S *SampleViG2d_VideoFrameManager_GetIdleFrame(SampleViG2d_VideoFrameManager *pThiz, int nTimeout)
{int ret;VideoFrameInfoNode *pNode = NULL;VIDEO_FRAME_INFO_S *pFrameInfo = NULL;pthread_mutex_lock(&pThiz->mLock); // 加锁,保证线程安全_retry:// 检查空闲帧列表是否为空if (!list_empty(&pThiz->mIdleFrameList)){// 从空闲列表头部取出一个节点pNode = list_first_entry(&pThiz->mIdleFrameList, VideoFrameInfoNode, mList);pFrameInfo = &pNode->VFrame; // 获取对应的帧信息// 将该节点移动到“正在填充”列表中list_move_tail(&pNode->mList, &pThiz->mFillingFrameList);}else{// 空闲列表为空,根据超时策略处理if (0 == nTimeout) // 不等待,立即返回{pFrameInfo = NULL;}else if (nTimeout < 0) // 无限等待{pThiz->mWaitIdleFrameFlag = true;// 循环等待,直到有空闲帧可用while (list_empty(&pThiz->mIdleFrameList)){pthread_cond_wait(&pThiz->mIdleFrameCond, &pThiz->mLock); // 阻塞等待}pThiz->mWaitIdleFrameFlag = false;goto _retry; // 重新尝试获取}else // 有限时间等待{pThiz->mWaitIdleFrameFlag = true;// 在条件变量上等待指定时间ret = pthread_cond_wait_timeout(&pThiz->mIdleFrameCond, &pThiz->mLock, nTimeout);if (ETIMEDOUT == ret) // 等待超时{alogv("wait output frame timeout[%d]ms, ret[%d]", nTimeout, ret);pFrameInfo = NULL;pThiz->mWaitIdleFrameFlag = false;}else if (0 == ret) // 等待成功(被信号唤醒){pThiz->mWaitIdleFrameFlag = false;goto _retry; // 重新尝试获取}else // 其他错误{aloge("fatal error! pthread cond wait timeout ret[%d]", ret);pFrameInfo = NULL;pThiz->mWaitIdleFrameFlag = false;}}}pthread_mutex_unlock(&pThiz->mLock); // 解锁return pFrameInfo;
}/*** 通知视频帧管理器,一个帧的填充工作已完成。* 将帧从“正在填充”列表移动到“就绪”列表。* @param pThiz: 帧管理器实例* @param pFrame: 已填充完成的帧* @return 0: 成功; -1: 失败*/
static int SampleViG2d_VideoFrameManager_FillingFrameDone(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame)
{int ret = 0;int nFindFlag = 0;VideoFrameInfoNode *pNode = NULL, *pTemp;pthread_mutex_lock(&pThiz->mLock);// 遍历“正在填充”列表list_for_each_entry_safe(pNode, pTemp, &pThiz->mFillingFrameList, mList){// 查找ID匹配的帧if (pNode->VFrame.mId == pFrame->mId){// 找到,移动到“就绪”列表尾部list_move_tail(&pNode->mList, &pThiz->mReadyFrameList);nFindFlag++;break;}}if (1 == nFindFlag){// 如果有线程在等待“就绪”帧,则唤醒一个if (pThiz->mWaitReadyFrameFlag){pthread_cond_signal(&pThiz->mReadyFrameCond);}}else{aloge("fatal error! find [%d] nodes", nFindFlag);ret = -1;}pthread_mutex_unlock(&pThiz->mLock);return ret;
}/*** 通知视频帧管理器,一个帧的填充工作失败。* 将帧从“正在填充”列表移动回“空闲”列表。* @param pThiz: 帧管理器实例* @param pFrame: 填充失败的帧* @return 0: 成功; -1: 失败*/
static int SampleViG2d_VideoFrameManager_FillingFrameFail(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame)
{int ret = 0;int nFindFlag = 0;VideoFrameInfoNode *pNode = NULL, *pTemp;pthread_mutex_lock(&pThiz->mLock);list_for_each_entry_safe(pNode, pTemp, &pThiz->mFillingFrameList, mList){if (pNode->VFrame.mId == pFrame->mId){// 找到,移动回“空闲”列表list_move(&pNode->mList, &pThiz->mIdleFrameList);nFindFlag++;break;}}if (1 == nFindFlag){// 如果有线程在等待“空闲”帧,则唤醒一个if (pThiz->mWaitIdleFrameFlag){pthread_cond_signal(&pThiz->mIdleFrameCond);}}else{aloge("fatal error! find [%d] nodes", nFindFlag);ret = -1;}pthread_mutex_unlock(&pThiz->mLock);return ret;
}/*** 从视频帧管理器中获取一个“就绪”的视频帧。* 这是生产者-消费者模式中的“消费资源”操作。* @param pThiz: 帧管理器实例* @param nTimeout: 超时时间(毫秒)* @return 成功返回指向VIDEO_FRAME_INFO_S的指针,失败返回NULL*/
static VIDEO_FRAME_INFO_S *SampleViG2d_VideoFrameManager_GetReadyFrame(SampleViG2d_VideoFrameManager *pThiz, int nTimeout)
{int ret;VideoFrameInfoNode *pNode = NULL;VIDEO_FRAME_INFO_S *pFrameInfo = NULL;pthread_mutex_lock(&pThiz->mLock);_retry:if (!list_empty(&pThiz->mReadyFrameList)){// 从“就绪”列表头部取出一个节点pNode = list_first_entry(&pThiz->mReadyFrameList, VideoFrameInfoNode, mList);pFrameInfo = &pNode->VFrame;// 移动到“正在使用”列表,表示该帧已被取出list_move_tail(&pNode->mList, &pThiz->mUsingFrameList);}else{// “就绪”列表为空,根据超时策略处理if (0 == nTimeout){pFrameInfo = NULL;}else if (nTimeout < 0){pThiz->mWaitReadyFrameFlag = true;while (list_empty(&pThiz->mReadyFrameList)){pthread_cond_wait(&pThiz->mReadyFrameCond, &pThiz->mLock);}pThiz->mWaitReadyFrameFlag = false;goto _retry;}else{pThiz->mWaitReadyFrameFlag = true;ret = pthread_cond_wait_timeout(&pThiz->mReadyFrameCond, &pThiz->mLock, nTimeout);if (ETIMEDOUT == ret){alogv("wait output frame timeout[%d]ms, ret[%d]", nTimeout, ret);pFrameInfo = NULL;pThiz->mWaitReadyFrameFlag = false;}else if (0 == ret){pThiz->mWaitReadyFrameFlag = false;goto _retry;}else{aloge("fatal error! pthread cond wait timeout ret[%d]", ret);pFrameInfo = NULL;pThiz->mWaitReadyFrameFlag = false;}}}pthread_mutex_unlock(&pThiz->mLock);return pFrameInfo;
}/*** 通知视频帧管理器,一个“正在使用”的帧已使用完毕(例如,显示完成)。* 将帧从“正在使用”列表移动回“空闲”列表。* @param pThiz: 帧管理器实例* @param pFrame: 使用完毕的帧* @return 0: 成功; -1: 失败*/
static int SampleViG2d_VideoFrameManager_UsingFrameDone(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame)
{int ret = 0;int nFindFlag = 0;VideoFrameInfoNode *pNode = NULL, *pTemp;pthread_mutex_lock(&pThiz->mLock);list_for_each_entry_safe(pNode, pTemp, &pThiz->mUsingFrameList, mList){if (pNode->VFrame.mId == pFrame->mId){// 找到,移动回“空闲”列表list_move_tail(&pNode->mList, &pThiz->mIdleFrameList);nFindFlag++;break;}}if (1 == nFindFlag){// 如果有线程在等待“空闲”帧,则唤醒一个if (pThiz->mWaitIdleFrameFlag){pthread_cond_signal(&pThiz->mIdleFrameCond);}}else{aloge("fatal error! find [%d] nodes", nFindFlag);ret = -1;}pthread_mutex_unlock(&pThiz->mLock);return ret;
}// 以下四个函数用于查询各个状态列表中的帧数量
static int SampleViG2d_VideoFrameManager_QueryIdleFrameNum(SampleViG2d_VideoFrameManager *pThiz)
{int cnt = 0;struct list_head *pList;pthread_mutex_lock(&pThiz->mLock);// 遍历空闲列表,计数list_for_each(pList, &pThiz->mIdleFrameList){cnt++;}pthread_mutex_unlock(&pThiz->mLock);return cnt;
}static int SampleViG2d_VideoFrameManager_QueryFillingFrameNum(SampleViG2d_VideoFrameManager *pThiz)
{int cnt = 0;struct list_head *pList;pthread_mutex_lock(&pThiz->mLock);list_for_each(pList, &pThiz->mFillingFrameList){cnt++;}pthread_mutex_unlock(&pThiz->mLock);return cnt;
}static int SampleViG2d_VideoFrameManager_QueryReadyFrameNum(SampleViG2d_VideoFrameManager *pThiz)
{int cnt = 0;struct list_head *pList;pthread_mutex_lock(&pThiz->mLock);list_for_each(pList, &pThiz->mReadyFrameList){cnt++;}pthread_mutex_unlock(&pThiz->mLock);return cnt;
}static int SampleViG2d_VideoFrameManager_QueryUsingFrameNum(SampleViG2d_VideoFrameManager *pThiz)
{int cnt = 0;struct list_head *pList;pthread_mutex_lock(&pThiz->mLock);list_for_each(pList, &pThiz->mUsingFrameList){cnt++;}pthread_mutex_unlock(&pThiz->mLock);return cnt;
}/*** 构造并初始化一个视频帧管理器对象。* 负责分配和管理多个视频帧缓冲区,并通过链表和条件变量管理其状态。* @param nFrameNum: 管理的帧缓冲区总数* @param nPixelFormat: 帧的像素格式* @param nBufWidth: 帧的宽度* @param nBufHeight: 帧的高度* @return 成功返回对象指针,失败返回NULL*/
SampleViG2d_VideoFrameManager *constructSampleViG2d_VideoFrameManager(int nFrameNum, PIXEL_FORMAT_E nPixelFormat, int nBufWidth, int nBufHeight)
{int ret;VideoFrameInfoNode *pNode, *pTemp;// 分配管理器对象SampleViG2d_VideoFrameManager *pManager = (SampleViG2d_VideoFrameManager *) malloc(sizeof(SampleViG2d_VideoFrameManager));if (NULL == pManager){aloge("fatal error! malloc fail!");return NULL;}memset(pManager, 0, sizeof(*pManager));// 初始化四个链表,分别管理空闲、填充中、就绪和使用中的帧INIT_LIST_HEAD(&pManager->mIdleFrameList);INIT_LIST_HEAD(&pManager->mFillingFrameList);INIT_LIST_HEAD(&pManager->mReadyFrameList);INIT_LIST_HEAD(&pManager->mUsingFrameList);// 初始化互斥锁ret = pthread_mutex_init(&pManager->mLock, NULL);if (ret != 0){aloge("fatal error! pthread mutex init fail[%d]", ret);goto _err0;}// 初始化条件变量属性,并设置时钟为CLOCK_MONOTONIC(单调时钟,不受系统时间调整影响)pthread_condattr_t condAttr;pthread_condattr_init(&condAttr);pthread_condattr_setclock(&condAttr, CLOCK_MONOTONIC);// 初始化两个条件变量ret = pthread_cond_init(&pManager->mIdleFrameCond, &condAttr);if (ret != 0){aloge("pthread cond init fail!");}ret = pthread_cond_init(&pManager->mReadyFrameCond, &condAttr);if (ret != 0){aloge("pthread cond init fail!");}// 记录配置pManager->mFrameNodeNum = nFrameNum;pManager->mPixelFormat = nPixelFormat;pManager->mBufWidth = nBufWidth;pManager->mBufHeight = nBufHeight;int i;// 为每一帧分配节点和实际的缓冲区内存for (i = 0; i < pManager->mFrameNodeNum; i++){pNode = (VideoFrameInfoNode *) malloc(sizeof(VideoFrameInfoNode));if (NULL == pNode){aloge("fatal error! malloc fail!");goto _err1;}memset(pNode, 0, sizeof(*pNode));// 将新节点加入“空闲”列表list_add_tail(&pNode->mList, &pManager->mIdleFrameList);// 初始化帧信息pNode->VFrame.mId = i;pNode->VFrame.VFrame.mWidth = pManager->mBufWidth;pNode->VFrame.VFrame.mHeight = pManager->mBufHeight;pNode->VFrame.VFrame.mPixelFormat = pManager->mPixelFormat;// 为YUV数据分配MMZ(连续物理内存)空间if (pManager->mBufWidth * pManager->mBufHeight > 0){switch (pManager->mPixelFormat){case MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420:case MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420:{// Semi-Planar格式 (NV12/NV21): Y平面 + UV共用一个平面ret = AW_MPI_SYS_MmzAlloc_Cached(&pNode->VFrame.VFrame.mPhyAddr[0], &pNode->VFrame.VFrame.mpVirAddr[0], pManager->mBufWidth * pManager->mBufHeight);if (ret != SUCCESS){pNode->VFrame.VFrame.mPhyAddr[0] = 0;pNode->VFrame.VFrame.mpVirAddr[0] = NULL;aloge("fatal error! phy alloc fail:%d", ret);goto _err1;}ret = AW_MPI_SYS_MmzAlloc_Cached(&pNode->VFrame.VFrame.mPhyAddr[1], &pNode->VFrame.VFrame.mpVirAddr[1], pManager->mBufWidth * pManager->mBufHeight / 2);if (ret != SUCCESS){pNode->VFrame.VFrame.mPhyAddr[1] = 0;pNode->VFrame.VFrame.mpVirAddr[1] = NULL;aloge("fatal error! phy alloc fail:%d", ret);goto _err1;}break;}case MM_PIXEL_FORMAT_YUV_PLANAR_420:case MM_PIXEL_FORMAT_YVU_PLANAR_420:{// Planar格式 (I420/YV12): Y、U、V三个独立平面ret = AW_MPI_SYS_MmzAlloc_Cached(&pNode->VFrame.VFrame.mPhyAddr[0], &pNode->VFrame.VFrame.mpVirAddr[0], pManager->mBufWidth * pManager->mBufHeight);if (ret != SUCCESS){pNode->VFrame.VFrame.mPhyAddr[0] = 0;pNode->VFrame.VFrame.mpVirAddr[0] = NULL;aloge("fatal error! phy alloc fail:%d", ret);goto _err1;}ret = AW_MPI_SYS_MmzAlloc_Cached(&pNode->VFrame.VFrame.mPhyAddr[1], &pNode->VFrame.VFrame.mpVirAddr[1], pManager->mBufWidth * pManager->mBufHeight / 4);if (ret != SUCCESS){pNode->VFrame.VFrame.mPhyAddr[1] = 0;pNode->VFrame.VFrame.mpVirAddr[1] = NULL;aloge("fatal error! phy alloc fail:%d", ret);goto _err1;}ret = AW_MPI_SYS_MmzAlloc_Cached(&pNode->VFrame.VFrame.mPhyAddr[2], &pNode->VFrame.VFrame.mpVirAddr[2], pManager->mBufWidth * pManager->mBufHeight / 4);if (ret != SUCCESS){pNode->VFrame.VFrame.mPhyAddr[2] = 0;pNode->VFrame.VFrame.mpVirAddr[2] = NULL;aloge("fatal error! phy alloc fail:%d", ret);goto _err1;}break;}default:{aloge("fatal error! not support pixel format:0x%x", pManager->mPixelFormat);goto _err1;}}}}// 将成员函数指针指向具体的实现pManager->GetIdleFrame = SampleViG2d_VideoFrameManager_GetIdleFrame;pManager->FillingFrameDone = SampleViG2d_VideoFrameManager_FillingFrameDone;pManager->FillingFrameFail = SampleViG2d_VideoFrameManager_FillingFrameFail;pManager->GetReadyFrame = SampleViG2d_VideoFrameManager_GetReadyFrame;pManager->UsingFrameDone = SampleViG2d_VideoFrameManager_UsingFrameDone;pManager->QueryIdleFrameNum = SampleViG2d_VideoFrameManager_QueryIdleFrameNum;pManager->QueryFillingFrameNum = SampleViG2d_VideoFrameManager_QueryFillingFrameNum;pManager->QueryReadyFrameNum = SampleViG2d_VideoFrameManager_QueryReadyFrameNum;pManager->QueryUsingFrameNum = SampleViG2d_VideoFrameManager_QueryUsingFrameNum;return pManager;// 错误处理标签
_err1:// 释放所有已分配的内存list_for_each_entry_safe(pNode, pTemp, &pManager->mIdleFrameList, mList){for (i = 0; i < 3; i++){if (pNode->VFrame.VFrame.mpVirAddr[i] != NULL){AW_MPI_SYS_MmzFree(pNode->VFrame.VFrame.mPhyAddr[i], pNode->VFrame.VFrame.mpVirAddr[i]);}}list_del(&pNode->mList);free(pNode);}pthread_mutex_destroy(&pManager->mLock);
_err0:free(pManager);pManager = NULL;return NULL;
}/*** 销毁视频帧管理器对象* @param pThiz: 要销毁的对象指针*/
void destructSampleViG2d_VideoFrameManager(SampleViG2d_VideoFrameManager *pThiz)
{VideoFrameInfoNode *pEntry, *pTemp;pthread_mutex_lock(&pThiz->mLock);int i;int cnt = 0;// 在销毁前,尝试将所有非空闲状态的帧都移回空闲列表list_for_each_entry_safe(pEntry, pTemp, &pThiz->mUsingFrameList, mList){list_move_tail(&pEntry->mList, &pThiz->mIdleFrameList);cnt++;}if (cnt > 0){aloge("fatal error! usingFrameList has [%d] entries.", cnt);}cnt = 0;list_for_each_entry_safe(pEntry, pTemp, &pThiz->mReadyFrameList, mList){list_move_tail(&pEntry->mList, &pThiz->mIdleFrameList);cnt++;}if (cnt > 0){alogw("Be careful! readyFrameList has [%d] entries.", cnt);}cnt = 0;list_for_each_entry_safe(pEntry, pTemp, &pThiz->mFillingFrameList, mList){list_move_tail(&pEntry->mList, &pThiz->mIdleFrameList);cnt++;}if (cnt > 0){aloge("fatal error! fillingFrameList has [%d] entries.", cnt);}// 释放所有帧的MMZ内存并销毁节点cnt = 0;list_for_each_entry_safe(pEntry, pTemp, &pThiz->mIdleFrameList, mList){for (i = 0; i < 3; i++){if (pEntry->VFrame.VFrame.mpVirAddr[i] != NULL){AW_MPI_SYS_MmzFree(pEntry->VFrame.VFrame.mPhyAddr[i], pEntry->VFrame.VFrame.mpVirAddr[i]);}}list_del(&pEntry->mList);free(pEntry);cnt++;}if (cnt != pThiz->mFrameNodeNum){aloge("fatal error! idle frame number is not match! [%d!=%d].", cnt, pThiz->mFrameNodeNum);}pthread_mutex_unlock(&pThiz->mLock);pthread_mutex_destroy(&pThiz->mLock);pthread_cond_destroy(&pThiz->mIdleFrameCond);pthread_cond_destroy(&pThiz->mReadyFrameCond);free(pThiz);
}/*** 构造主上下文对象* @return 成功返回对象指针,失败返回NULL*/
SampleViG2dContext *constructSampleViG2dContext()
{int ret;// 分配内存SampleViG2dContext *pThiz = (SampleViG2dContext *) malloc(sizeof(SampleViG2dContext));if (NULL == pThiz){aloge("fatal error! malloc fail!");return NULL;}memset(pThiz, 0, sizeof(SampleViG2dContext));// 初始化子结构体initSampleViCapS(&pThiz->mViCapCtx);pThiz->mViCapCtx.mpContext = (void *) pThiz; // 设置上下文指针,便于回调函数访问initSampleVoDisplayS(&pThiz->mVoDisplayCtx);pThiz->mVoDisplayCtx.mpContext = (void *) pThiz;// 初始化用于线程同步的信号量ret = cdx_sem_init(&pThiz->mSemExit, 0);if (ret != 0){aloge("fatal error! cdx sem init fail[%d]", ret);}return pThiz;
}/*** 销毁主上下文对象* @param pThiz: 要销毁的对象指针*/
void destructSampleViG2dContext(SampleViG2dContext *pThiz)
{if (pThiz != NULL){destroySampleVoDisplayS(&pThiz->mVoDisplayCtx);destroySampleViCapS(&pThiz->mViCapCtx);// 检查并销毁帧管理器if (pThiz->mpFrameManager){alogw("fatal error! video frame manager still exist? destruct it!");destructSampleViG2d_VideoFrameManager(pThiz->mpFrameManager);pThiz->mpFrameManager = NULL;}cdx_sem_deinit(&pThiz->mSemExit);free(pThiz);}
}/*** VI模块的回调函数* 用于处理来自VI模块的事件,如超时等。* @param cookie: 传入的上下文指针(即SampleViCapS)* @param pChn: 事件来源的通道信息* @param event: 事件类型* @param pEventData: 事件相关数据* @return SUCCESS*/
static ERRORTYPE SampleViG2d_VICallback(void *cookie, MPP_CHN_S *pChn, MPP_EVENT_TYPE event, void *pEventData)
{SampleViCapS *pViCapCtx = (SampleViCapS *) cookie;// 检查事件来源是否为VI模块if (MOD_ID_VIU == pChn->mModId){// 检查设备和通道ID是否匹配if (pViCapCtx->mDev != pChn->mDevId || pViCapCtx->mChn != pChn->mChnId){aloge("fatal error! viG2d viCallback don't match [%d,%d]!=[%d,%d]", pViCapCtx->mDev, pViCapCtx->mChn, pChn->mDevId, pChn->mChnId);}switch (event){case MPP_EVENT_VI_TIMEOUT:alogd("receive vi timeout. vipp:%d, chn:%d", pChn->mDevId, pChn->mChnId);break;default:aloge("fatal error! unknown event[0x%x] from channel[0x%x,0x%x,0x%x]!", event, pChn->mModId, pChn->mDevId, pChn->mChnId);break;}}else{aloge("fatal error! unknown event[0x%x] from channel[0x%x,0x%x,0x%x]!", event, pChn->mModId, pChn->mDevId, pChn->mChnId);}return SUCCESS;
}/*** VO模块的回调函数* 用于处理来自VO模块的事件,最主要的是MPP_EVENT_RELEASE_VIDEO_BUFFER,* 该事件表示一个视频帧在屏幕上显示完毕,可以被回收。* @param cookie: 传入的上下文指针(即SampleVoDisplayS)* @param pChn: 事件来源的通道信息* @param event: 事件类型* @param pEventData: 事件相关数据,对于释放帧事件,是VIDEO_FRAME_INFO_S指针* @return SUCCESS*/
static ERRORTYPE SampleViG2d_VOCallback(void *cookie, MPP_CHN_S *pChn, MPP_EVENT_TYPE event, void *pEventData)
{ERRORTYPE ret = SUCCESS;int nRet;SampleVoDisplayS *pVoDisplayCtx = (SampleVoDisplayS *) cookie;SampleViG2dContext *pContext = pVoDisplayCtx->mpContext; // 获取主上下文if (MOD_ID_VOU == pChn->mModId) // 检查是否为VO模块{switch (event){case MPP_EVENT_RELEASE_VIDEO_BUFFER:{VIDEO_FRAME_INFO_S *pFrameInfo = (VIDEO_FRAME_INFO_S *) pEventData;// 帧显示完毕,通知帧管理器,该帧可以被回收(移动回空闲列表)nRet = pContext->mpFrameManager->UsingFrameDone(pContext->mpFrameManager, pFrameInfo);if (nRet != 0){aloge("fatal error! frame id[%d] using frame done fail!", pFrameInfo->mId);}break;}case MPP_EVENT_SET_VIDEO_SIZE:{SIZE_S *pDisplaySize = (SIZE_S *) pEventData;alogd("vo layer[%d] report video display size[%dx%d]", pChn->mDevId, pDisplaySize->Width, pDisplaySize->Height);break;}case MPP_EVENT_RENDERING_START:{alogd("vo layer[%d] report rendering start", pChn->mDevId);break;}default:{aloge("fatal error! unknown event[0x%x] from channel[0x%x,0x%x,0x%x]!", event, pChn->mModId, pChn->mDevId, pChn->mChnId);ret = ERR_VO_ILLEGAL_PARAM;break;}}}else{aloge("fatal error! why modId[0x%x]?", pChn->mModId);ret = FAILURE;}return ret;
}// 全局变量,用于在信号处理函数中访问主上下文
static SampleViG2dContext *gpSampleViG2dContext = NULL;/*** 信号处理函数* 当程序收到SIGINT信号(通常是Ctrl+C)时被调用。* 它会设置主上下文的退出标志,并通过信号量唤醒等待中的线程。* @param signo: 信号编号*/
static void handle_exit(int signo)
{alogd("user want to exit!");if (NULL != gpSampleViG2dContext){cdx_sem_up(&gpSampleViG2dContext->mSemExit); // 发送信号,唤醒主循环}
}/*** 主函数* 程序的入口点,负责初始化、配置、创建线程、运行主循环和清理资源。*/
int main(int argc, char *argv[])
{int ret = 0;void *pValue = NULL;// 配置日志系统GLogConfig stGLogConfig ={.FLAGS_logtostderr = 0, // 不输出到stderr.FLAGS_colorlogtostderr = 1, // 带颜色输出.FLAGS_stderrthreshold = _GLOG_INFO, // stderr输出级别.FLAGS_minloglevel = _GLOG_INFO, // 最低日志级别.FLAGS_logbuflevel = -1,.FLAGS_logbufsecs = 0,.FLAGS_max_log_size = 1, // 日志文件最大1MB.FLAGS_stop_logging_if_full_disk = 1,};strcpy(stGLogConfig.LogDir, "/tmp/log");strcpy(stGLogConfig.InfoLogFileNameBase, "LOG-");strcpy(stGLogConfig.LogFileNameExtension, "IPC-");log_init(argv[0], &stGLogConfig); // 初始化日志alogd("hello, sample_vi_g2d.");MPPCallbackInfo cbInfo; // 用于注册回调的结构体// 创建主上下文对象SampleViG2dContext *pContext = constructSampleViG2dContext();if (NULL == pContext){ret = -1;goto _err0;}gpSampleViG2dContext = pContext; // 设置全局指针char *pConfigFilePath;// 解析命令行参数if (ParseCmdLine(argc, argv, &pContext->mCmdLinePara) != 0){ret = -1;goto _err1;}// 确定配置文件路径if (strlen(pContext->mCmdLinePara.mConfigFilePath) > 0){pConfigFilePath = pContext->mCmdLinePara.mConfigFilePath;}else{pConfigFilePath = NULL;}// 从配置文件加载参数if (loadSampleViG2dConfig(&pContext->mConfigPara, pConfigFilePath) != SUCCESS){aloge("fatal error! no config file or parse conf file fail");ret = -1;goto _err1;}// 注册SIGINT信号处理函数,用于优雅退出if (signal(SIGINT, handle_exit) == SIG_ERR){aloge("fatal error! can't catch SIGSEGV");}// 配置系统模块memset(&pContext->mSysConf, 0, sizeof(MPP_SYS_CONF_S));pContext->mSysConf.nAlignWidth = 32; // 内存对齐宽度AW_MPI_SYS_SetConf(&pContext->mSysConf);ret = AW_MPI_SYS_Init();if (ret < 0){aloge("sys Init failed!");ret = -1;goto _err1;}// 创建视频帧管理器,用于管理G2D转换后的帧pContext->mpFrameManager = constructSampleViG2d_VideoFrameManager(5, pContext->mConfigPara.mPicFormat, pContext->mConfigPara.mDstWidth, pContext->mConfigPara.mDstHeight);if (NULL == pContext->mpFrameManager){aloge("fatal error! malloc fail!");ret = -1;goto _err2;}// --- 初始化VI模块(视频输入) ---/* 设置VI通道属性 */memset(&pContext->mViCapCtx.mViAttr, 0, sizeof(VI_ATTR_S));pContext->mViCapCtx.mDev = pContext->mConfigPara.mDevNum; // VI设备号pContext->mViCapCtx.mChn = 0; // VI通道号pContext->mViCapCtx.mViAttr.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; // 多平面捕获pContext->mViCapCtx.mViAttr.memtype = V4L2_MEMORY_MMAP; // 内存映射方式// 将MPP像素格式转换为V4L2像素格式pContext->mViCapCtx.mViAttr.format.pixelformat = map_PIXEL_FORMAT_E_to_V4L2_PIX_FMT(pContext->mConfigPara.mPicFormat);pContext->mViCapCtx.mViAttr.format.field = V4L2_FIELD_NONE;pContext->mViCapCtx.mViAttr.format.colorspace = V4L2_COLORSPACE_JPEG;pContext->mViCapCtx.mViAttr.format.width = pContext->mConfigPara.mSrcWidth; // 源图像宽度pContext->mViCapCtx.mViAttr.format.height = pContext->mConfigPara.mSrcHeight; // 源图像高度pContext->mViCapCtx.mViAttr.nbufs = 5; // 缓冲区数量pContext->mViCapCtx.mViAttr.nplanes = 2; // 平面数量(对于NV12/NV21是2)pContext->mViCapCtx.mViAttr.fps = pContext->mConfigPara.mFrameRate; // 帧率pContext->mViCapCtx.mViAttr.use_current_win = 0; // 不使用当前窗口配置pContext->mViCapCtx.mViAttr.wdr_mode = 0; // WDR模式pContext->mViCapCtx.mViAttr.capturemode = V4L2_MODE_VIDEO; // 捕获模式:视频pContext->mViCapCtx.mViAttr.drop_frame_num = pContext->mConfigPara.mDropFrameNum; // 丢弃帧数pContext->mViCapCtx.mIspDev = 0; // ISP设备号pContext->mViCapCtx.mTimeout = 200; // 获取帧超时时间// 创建VIPP(视频输入处理管道)ret = AW_MPI_VI_CreateVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! create vipp fail[%d]", ret);}// 注册VI回调函数cbInfo.cookie = (void *) &pContext->mViCapCtx;cbInfo.callback = (MPPCallbackFuncType) &SampleViG2d_VICallback;ret = AW_MPI_VI_RegisterCallback(pContext->mViCapCtx.mDev, &cbInfo);if (ret != SUCCESS){aloge("fatal error! vipp[%d] RegisterCallback failed", pContext->mViCapCtx.mDev);}// 设置VIPP属性ret = AW_MPI_VI_SetVippAttr(pContext->mViCapCtx.mDev, &pContext->mViCapCtx.mViAttr);if (ret != SUCCESS){aloge("fatal error! set vipp attr fail[%d]", ret);}#if ISP_RUN// 启动ISP模块进行图像处理ret = AW_MPI_ISP_Run(pContext->mViCapCtx.mIspDev);if (ret != SUCCESS){aloge("fatal error! isp run fail[%d]", ret);}
#endif// 启用VIPPret = AW_MPI_VI_EnableVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! enable vipp fail[%d]", ret);}// 创建虚拟通道ret = AW_MPI_VI_CreateVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn, NULL);if (ret != SUCCESS){aloge("fatal error! Create VI Chn failed, VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}// 启用虚拟通道ret = AW_MPI_VI_EnableVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);if (ret != SUCCESS){aloge("fatal error! VI Enable VirChn failed,VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}// --- 初始化G2D转换 ---pContext->mViCapCtx.mpG2dConvert = constructSampleViG2d_G2dConvert();if (NULL == pContext->mViCapCtx.mpG2dConvert){aloge("fatal error! create g2dConvert fail[%d]", ret);goto _err3;}ret = pContext->mViCapCtx.mpG2dConvert->G2dOpen();if (ret != 0){aloge("fatal error! g2d open fail[%d]", ret);goto _err4;}// 设置G2D转换参数G2dConvertParam stConvertParam;memset(&stConvertParam, 0, sizeof(stConvertParam));stConvertParam.mSrcRectX = pContext->mConfigPara.mSrcRectX;stConvertParam.mSrcRectY = pContext->mConfigPara.mSrcRectY;stConvertParam.mSrcRectW = pContext->mConfigPara.mSrcRectW;stConvertParam.mSrcRectH = pContext->mConfigPara.mSrcRectH;stConvertParam.mDstRotate = pContext->mConfigPara.mDstRotate;stConvertParam.mDstWidth = pContext->mConfigPara.mDstWidth;stConvertParam.mDstHeight = pContext->mConfigPara.mDstHeight;stConvertParam.mDstRectX = pContext->mConfigPara.mDstRectX;stConvertParam.mDstRectY = pContext->mConfigPara.mDstRectY;stConvertParam.mDstRectW = pContext->mConfigPara.mDstRectW;stConvertParam.mDstRectH = pContext->mConfigPara.mDstRectH;pContext->mViCapCtx.mpG2dConvert->G2dSetConvertParam(pContext->mViCapCtx.mpG2dConvert, &stConvertParam);// 创建获取CSI帧的线程ret = pthread_create(&pContext->mViCapCtx.mThreadId, NULL, GetCSIFrameThread, (void *) &pContext->mViCapCtx);if (0 == ret){pContext->mViCapCtx.mbThreadExistFlag = true;alogd("vipp[%d]virChn[%d]: get csi frame thread id=[0x%x]", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn, pContext->mViCapCtx.mThreadId);}else{aloge("fatal error! pthread_create failed, vipp[%d]virChn[%d]", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);goto _err4;}// --- 初始化VO模块(视频输出/显示) ---pContext->mVoDisplayCtx.mVoDev = 0; // VO设备号pContext->mVoDisplayCtx.mUILayer = HLAY(2, 0); // UI层号AW_MPIVO_Enable(pContext->mVoDisplayCtx.mVoDev); // 使能VO设备AW_MPI_VO_AddOutsideVideoLayer(pContext->mVoDisplayCtx.mUILayer); // 添加外部视频层AW_MPI_VO_CloseVideoLayer(pContext->mVoDisplayCtx.mUILayer); /* 关闭UI层。 */// 获取VO公共属性AW_MPI_VO_GetPubAttr(pContext->mVoDisplayCtx.mVoDev, &pContext->mVoDisplayCtx.mPubAttr);pContext->mVoDisplayCtx.mPubAttr.enIntfType = VO_INTF_LCD; // 接口类型:LCDpContext->mVoDisplayCtx.mPubAttr.enIntfSync = VO_OUTPUT_NTSC; // 同步类型// 设置VO公共属性AW_MPI_VO_SetPubAttr(pContext->mVoDisplayCtx.mVoDev, &pContext_->mVoDisplayCtx.mPubAttr); // 注意:这里有个笔误,应该是pContextpContext->mVoDisplayCtx.mVoLayer = 0; // 视频层号// 使能视频层ret = AW_MPI_VO_EnableVideoLayer(pContext->mVoDisplayCtx.mVoLayer);if (ret != SUCCESS){aloge("fatal error! enable video layer[%d] fail!", pContext->mVoDisplayCtx.mVoLayer);pContext->mVoDisplayCtx.mVoLayer = MM_INVALID_LAYER;goto _err5;}// 获取视频层属性AW_MPI_VO_GetVideoLayerAttr(pContext->mVoDisplayCtx.mVoLayer, &pContext->mVoDisplayCtx.mLayerAttr);// 设置显示区域pContext->mVoDisplayCtx.mLayerAttr.stDispRect.X = pContext->mConfigPara.mDisplayX;pContext->mVoDisplayCtx.mLayerAttr.stDispRect.Y = pContext->mConfigPara.mDisplayY;pContext->mVoDisplayCtx.mLayerAttr.stDispRect.Width = pContext->mConfigPara.mDisplayW;pContext->mVoDisplayCtx.mLayerAttr.stDispRect.Height = pContext->mConfigPara.mDisplayH;// 设置视频层属性AW_MPI_VO_SetVideoLayerAttr(pContext->mVoDisplayCtx.mVoLayer, &pContext->mVoDisplayCtx.mLayerAttr);pContext->mVoDisplayCtx.mVOChn = 0; // VO通道号// 创建VO通道ret = AW_MPI_VO_CreateChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){pContext->mVoDisplayCtx.mVOChn = MM_INVALID_CHN;aloge("fatal error! create vo channel fail!");goto _err6;}// 注册VO回调函数cbInfo.cookie = (void *) &pContext->mVoDisplayCtx;cbInfo.callback = (MPPCallbackFuncType) &SampleViG2d_VOCallback;AW_MPI_VO_RegisterCallback(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn, &cbInfo);// 设置通道显示缓冲区数量AW_MPI_VO_SetChnDispBufNum(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn, 2);// 启动VO通道ret = AW_MPI_VO_StartChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){aloge("fatal error! vo start chn fail[%d]!", ret);}// 创建显示线程ret = pthread_create(&pContext->mVoDisplayCtx.mThreadId, NULL, DisplayThread, (void *) &pContext->mVoDisplayCtx);if (0 == ret){pContext->mVoDisplayCtx.mbThreadExistFlag = true;alogd("pthread create display threadId[0x%x]", pContext->mVoDisplayCtx.mThreadId);}else{aloge("fatal error! pthread_create failed");goto _err7;}// --- 主循环:等待退出信号 ---if (pContext->mConfigPara.mTestDuration > 0){// 如果设置了测试时长,则等待指定时间后自动退出cdx_sem_down_timedwait(&pContext->mSemExit, pContext->mConfigPara.mTestDuration * 1000);}else{// 否则,无限等待,直到收到SIGINT信号cdx_sem_down(&pContext->mSemExit);}// --- 开始退出流程 ---pContext->mbExitFlag = true; // 设置退出标志// 等待显示线程结束ret = pthread_join(pContext->mVoDisplayCtx.mThreadId, (void *) &pValue);alogd("VoDisplay threadId[0x%x] exit[%d], pValue[%p]", pContext->mVoDisplayCtx.mThreadId, ret, pValue);pContext->mVoDisplayCtx.mbThreadExistFlag = false;// 等待VI捕获线程结束ret = pthread_join(pContext->mViCapCtx.mThreadId, (void *) &pValue);aloge("ViCap threadId[0x%x] exit[%d], pValue[%p]", pContext->mViCapCtx.mThreadId, ret, pValue);pContext->mViCapCtx.mbThreadExistFlag = false;// 停止并销毁VO通道ret = AW_MPI_VO_StopChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){aloge("fatal error! vo stop chn fail[%d]!", ret);}ret = AW_MPI_VO_DestroyChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){aloge("fatal error! disable vo channel fail[%d]!", ret);}// 禁用视频层和VO设备ret = AW_MPI_VO_DisableVideoLayer(pContext->mVoDisplayCtx.mVoLayer);if (ret != SUCCESS){aloge("fatal error! disable video layer[%d] fail!", pContext->mVoDisplayCtx.mVoLayer);}ret = AW_MPI_VO_RemoveOutsideVideoLayer(pContext->mVoDisplayCtx.mUILayer);if (ret != SUCCESS){aloge("fatal error! remove outside video layer[%d] fail[%d]!", pContext->mVoDisplayCtx.mVoLayer, ret);}ret = AW_MPI_VO_Disable(pContext->mVoDisplayCtx.mVoDev);if (ret != SUCCESS){aloge("fatal error! vo disable fail[%d]!", ret);}pContext->mVoDisplayCtx.mVoDev = -1;// 销毁G2D转换器destructSampleViG2d_G2dConvert(pContext->mViCapCtx.mpG2dConvert);pContext->mViCapCtx.mpG2dConvert = NULL;// 禁用并销毁VI虚拟通道和VIPPret = AW_MPI_VI_DisableVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);if (ret != SUCCESS){aloge("fatal error! VI disable VirChn failed,VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}ret = AW_MPI_VI_DestroyVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);if (ret != SUCCESS){aloge("fatal error! Destroy VI Chn failed, VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}ret = AW_MPI_VI_DisableVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! Disable vipp fail[%d]", ret);}ret = AW_MPI_VI_DestroyVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! destroy vipp fail[%d]", ret);}#if ISP_RUN// 停止ISPret = AW_MPI_ISP_Stop(pContext->mViCapCtx.mIspDev);if (ret != SUCCESS){aloge("fatal error! isp stop fail[%d]", ret);}
#endif// 销毁帧管理器destructSampleViG2d_VideoFrameManager(pContext->mpFrameManager);pContext->mpFrameManager = NULL;// 退出系统模块ret = AW_MPI_SYS_Exit();if (ret < 0){aloge("fatal error! sys exit failed!");}// 销毁主上下文destructSampleViG2dContext(pContext);pContext = NULL;gpSampleViG2dContext = NULL;log_quit(); // 关闭日志return ret;// 错误处理标签(用于在出错时跳转并释放已分配的资源)
_err7:ret = AW_MPI_VO_StopChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){aloge("fatal error! vo stop chn fail[%d]!", ret);}ret = AW_MPI_VO_DestroyChn(pContext->mVoDisplayCtx.mVoLayer, pContext->mVoDisplayCtx.mVOChn);if (ret != SUCCESS){aloge("fatal error! disable vo channel fail[%d]!", ret);}
_err6:ret = AW_MPI_VO_DisableVideoLayer(pContext->mVoDisplayCtx.mVoLayer);if (ret != SUCCESS){aloge("fatal error! disable video layer[%d] fail!", pContext->mVoDisplayCtx.mVoLayer);}ret = AW_MPI_VO_RemoveOutsideVideoLayer(pContext->mVoDisplayCtx.mUILayer);if (ret != SUCCESS){aloge("fatal error! remove outside video layer[%d] fail[%d]!", pContext->mVoDisplayCtx.mVoLayer, ret);}ret = AW_MPI_VO_Disable(pContext->mVoDisplayCtx.mVoDev);if (ret != SUCCESS){aloge("fatal error! vo disable fail[%d]!", ret);}pContext->mVoDisplayCtx.mVoDev = -1;
_err5:pContext->mbExitFlag = true;ret = pthread_join(pContext->mViCapCtx.mThreadId, (void *) &pValue);if (ret != 0){aloge("fatal error! ViCap threadId[0x%x] join fail[%d]", pContext->mViCapCtx.mThreadId, ret);}
_err4:destructSampleViG2d_G2dConvert(pContext->mViCapCtx.mpG2dConvert);pContext->mViCapCtx.mpG2dConvert = NULL;
_err3:ret = AW_MPI_VI_DisableVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);if (ret != SUCCESS){aloge("fatal error! VI disable VirChn failed,VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}ret = AW_MPI_VI_DestroyVirChn(pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);if (ret != SUCCESS){aloge("fatal error! Destroy VI Chn failed, VIDev = %d,VIChn = %d", pContext->mViCapCtx.mDev, pContext->mViCapCtx.mChn);}ret = AW_MPI_VI_DisableVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! Disable vipp fail[%d]", ret);}ret = AW_MPI_VI_DestroyVipp(pContext->mViCapCtx.mDev);if (ret != SUCCESS){aloge("fatal error! destroy vipp fail[%d]", ret);}
#if ISP_RUNret = AW_MPI_ISP_Stop(pContext->mViCapCtx.mIspDev);if (ret != SUCCESS){aloge("fatal error! isp stop fail[%d]", ret);}
#endifdestructSampleViG2d_VideoFrameManager(pContext->mpFrameManager);pContext->mpFrameManager = NULL;
_err2:ret = AW_MPI_SYS_Exit();if (ret < 0){aloge("fatal error! sys exit failed!");}
_err1:destructSampleViG2dContext(pContext);pContext = NULL;gpSampleViG2dContext = NULL;
_err0:alogd("%s test result: %s", argv[0], ((0 == ret) ? "success" : "fail"));log_quit();return ret;
}
sample_vi_g2d.h
#ifndef _SAMPLE_VI_G2D_H_
#define _SAMPLE_VI_G2D_H_#include <plat_type.h> // 平台类型定义
#include <tsemaphore.h> // 信号量相关
#include <mm_comm_vo.h> // 视频输出公共头文件#define MAX_FILE_PATH_SIZE (256) // 最大文件路径长度// G2D转换参数结构体
typedef struct G2dConvertParam
{int mSrcRectX; // 源图像矩形区域X坐标int mSrcRectY; // 源图像矩形区域Y坐标int mSrcRectW; // 源图像矩形区域宽度int mSrcRectH; // 源图像矩形区域高度int mDstRotate; // 目标旋转角度(0,90,180,270,顺时针方向)int mDstWidth; // 目标图像宽度int mDstHeight; // 目标图像高度int mDstRectX; // 目标图像矩形区域X坐标int mDstRectY; // 目标图像矩形区域Y坐标int mDstRectW; // 目标图像矩形区域宽度int mDstRectH; // 目标图像矩形区域高度
}G2dConvertParam;// G2D转换器结构体
typedef struct SampleViG2d_G2dConvert SampleViG2d_G2dConvert;
typedef struct SampleViG2d_G2dConvert
{int mG2dFd; // G2D设备文件描述符G2dConvertParam mConvertParam; // G2D转换参数// 方法指针int (*G2dOpen)(); // 打开G2D设备int (*G2dClose)(); // 关闭G2D设备int (*G2dSetConvertParam)(SampleViG2d_G2dConvert *pThiz, G2dConvertParam *pConvertParam); // 设置转换参数int (*G2dConvertVideoFrame)(SampleViG2d_G2dConvert *pThiz, VIDEO_FRAME_INFO_S* pSrcFrameInfo, VIDEO_FRAME_INFO_S* pDstFrameInfo); // 转换视频帧
} SampleViG2d_G2dConvert;// 构造函数和析构函数声明
SampleViG2d_G2dConvert *constructSampleViG2d_G2dConvert();
void destructSampleViG2d_G2dConvert(SampleViG2d_G2dConvert *pThiz);// 视频帧管理器结构体
typedef struct SampleViG2d_VideoFrameManager SampleViG2d_VideoFrameManager;
typedef struct SampleViG2d_VideoFrameManager
{// 四种状态的帧列表struct list_head mIdleFrameList; // 空闲帧列表(VideoFrameInfoNode)struct list_head mFillingFrameList; // 正在填充的帧列表struct list_head mReadyFrameList; // 就绪帧列表struct list_head mUsingFrameList; // 正在使用的帧列表bool mWaitIdleFrameFlag; // 等待空闲帧标志bool mWaitReadyFrameFlag; // 等待就绪帧标志pthread_mutex_t mLock; // 互斥锁pthread_cond_t mIdleFrameCond; // 空闲帧条件变量pthread_cond_t mReadyFrameCond; // 就绪帧条件变量int mFrameNodeNum; // 帧节点数量PIXEL_FORMAT_E mPixelFormat; // 像素格式int mBufWidth; // 缓冲区宽度int mBufHeight; // 缓冲区高度// 方法指针VIDEO_FRAME_INFO_S* (*GetIdleFrame)(SampleViG2d_VideoFrameManager *pThiz, int nTimeout); // 获取空闲帧(超时时间ms)int (*FillingFrameDone)(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame); // 帧填充完成int (*FillingFrameFail)(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame); // 帧填充失败VIDEO_FRAME_INFO_S* (*GetReadyFrame)(SampleViG2d_VideoFrameManager *pThiz, int nTimeout); // 获取就绪帧(超时时间ms)int (*UsingFrameDone)(SampleViG2d_VideoFrameManager *pThiz, VIDEO_FRAME_INFO_S *pFrame); // 帧使用完成// 查询方法int (*QueryIdleFrameNum)(SampleViG2d_VideoFrameManager *pThiz); // 查询空闲帧数量int (*QueryFillingFrameNum)(SampleViG2d_VideoFrameManager *pThiz); // 查询正在填充的帧数量int (*QueryReadyFrameNum)(SampleViG2d_VideoFrameManager *pThiz); // 查询就绪帧数量int (*QueryUsingFrameNum)(SampleViG2d_VideoFrameManager *pThiz); // 查询正在使用的帧数量
} SampleViG2d_VideoFrameManager;// 构造函数和析构函数声明
SampleViG2d_VideoFrameManager *constructSampleViG2d_VideoFrameManager(int nFrameNum, PIXEL_FORMAT_E nPixelFormat, int nBufWidth, int nBufHeight);
void destructSampleViG2d_VideoFrameManager(SampleViG2d_VideoFrameManager *pThiz);// 视频捕获结构体
typedef struct SampleViCapS {bool mbThreadExistFlag; // 线程存在标志pthread_t mThreadId; // 线程IDVI_DEV mDev; // VI设备VI_CHN mChn; // VI通道VI_ATTR_S mViAttr; // VI属性ISP_DEV mIspDev; // ISP设备int mTimeout; // 超时时间(ms)void *mpContext; // 上下文指针(SampleViG2dContext*)int mRawStoreNum; // 原始帧存储数量} SampleViCapS;// 初始化函数声明
int initSampleViCapS(SampleViCapS *pThiz);
int destroySampleViCapS(SampleViCapS *pThiz);// 视频输出显示结构体
typedef struct SampleVoDisplayS {bool mbThreadExistFlag; // 线程存在标志pthread_t mThreadId; // 线程IDVO_DEV mVoDev; // VO设备VO_PUB_ATTR_S mPubAttr; // VO公共属性VO_LAYER mUILayer; // UI层VO_LAYER mVoLayer; // 视频层VO_VIDEO_LAYER_ATTR_S mLayerAttr; // 视频层属性VO_CHN mVOChn; // VO通道void *mpContext; // 上下文指针(SampleViG2dContext*)
} SampleVoDisplayS;// 初始化函数声明
int initSampleVoDisplayS(SampleVoDisplayS *pThiz);
int destroySampleVoDisplayS(SampleVoDisplayS *pThiz);// 命令行参数结构体
typedef struct SampleViG2dCmdLineParam
{char mConfigFilePath[MAX_FILE_PATH_SIZE]; // 配置文件路径
}SampleViG2dCmdLineParam;// 配置参数结构体
typedef struct SampleViG2dConfig
{int mDevNum; // 设备号int mFrameRate; // 帧率PIXEL_FORMAT_E mPicFormat; // 像素格式int mDropFrameNum; // 丢弃帧数int mSrcWidth; // 源宽度int mSrcHeight; // 源高度int mSrcRectX; // 源矩形X坐标int mSrcRectY; // 源矩形Y坐标int mSrcRectW; // 源矩形宽度int mSrcRectH; // 源矩形高度int mDstRotate; // 目标旋转角度(0,90,180,270)int mDstWidth; // 目标宽度int mDstHeight; // 目标高度int mDstRectX; // 目标矩形X坐标int mDstRectY; // 目标矩形Y坐标int mDstRectW; // 目标矩形宽度int mDstRectH; // 目标矩形高度int mDstStoreCount; // 目标存储计数int mDstStoreInterval; // 目标存储间隔char mStoreDir[MAX_FILE_PATH_SIZE]; // 存储目录bool mDisplayFlag; // 显示标志int mDisplayX; // 显示X坐标int mDisplayY; // 显示Y坐标int mDisplayW; // 显示宽度int mDisplayH; // 显示高度int mTestDuration; // 测试持续时间(秒)
}SampleViG2dConfig;// 上下文结构体
typedef struct SampleViG2dContext
{SampleViG2dCmdLineParam mCmdLinePara; // 命令行参数SampleViG2dConfig mConfigPara; // 配置参数MPP_SYS_CONF_S mSysConf; // MPP系统配置SampleViCapS mViCapCtx; // 视频捕获上下文SampleVoDisplayS mVoDisplayCtx; // 视频显示上下文SampleViG2d_VideoFrameManager *mpFrameManager; // 帧管理器指针bool mbExitFlag; // 退出标志cdx_sem_t mSemExit; // 退出信号量
}SampleViG2dContext;// 构造函数和析构函数声明
SampleViG2dContext *constructSampleViG2dContext();
void destructSampleViG2dContext(SampleViG2dContext *pThiz);#endif /* _SAMPLE_VI_G2D_H_ */
在全志 SDK 目录激活环境,并选择方案:
source build/envsetup.sh
lunch
进入配置界面:
make menuconfig
选择 MPP 示例程序,保存并退出:
Allwinner --->eyesee-mpp --->[*] select mpp sample
清理和编译MPP程序:
cleanmpp
mkmpp
进入目录,将编译出的文件上传到开发板:
cd ~/tina-v853-100ask/external/eyesee-mpp/middleware/sun8iw21/sample/bin
adb push sample_vi_g2d sample_vi_g2d.conf /mnt/UDISK
在开发板上运行程序:
./sample_vi_g2d -path ./sample_vi_g2d.conf