G2D 图形加速器

发布于:2025-09-06 ⋅ 阅读:(11) ⋅ 点赞:(0)

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; // 默认源图像格式为 NV21
    pConfig->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; // 是否预乘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] = (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; // 使用全局Alpha
    bld.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;
    else
        eSrcFormat = 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;  // NV21
    pConfig->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].NV21M
    char 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;
    // 对齐,通常为0
    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 = 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

    // 启用VIPP
    ret = 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;  // 接口类型:LCD
    pContext->mVoDisplayCtx.mPubAttr.enIntfSync = VO_OUTPUT_NTSC;  // 同步类型
    // 设置VO公共属性
    AW_MPI_VO_SetPubAttr(pContext->mVoDisplayCtx.mVoDev, &pContext_->mVoDisplayCtx.mPubAttr);  // 注意:这里有个笔误,应该是pContext

    pContext->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虚拟通道和VIPP
    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_RUN
    // 停止ISP
    ret = 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_RUN
    ret = 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;
_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;      // 线程ID
    VI_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;     // 线程ID
    VO_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

网站公告

今日签到

点亮在社区的每一天
去签到