目录

图片解码内存优化(C/C++)

应用在进行图片解码操作时,需要申请对应内存。内存占用的大小与内存分配类型和像素格式密切相关。当前指导将介绍不同的内存类型、像素格式,以及如何组合使用以达到最优的解码性能。

应用侧通过解码API接口获取PixelMap,并将其传递给Image组件以进行显示。

当PixelMap较大且使用共享内存时,RS主线程将经历较长的纹理上传时间,导致卡顿现象。图形侧提供了DMA内存零拷贝功能,可在绘制图片时避免纹理上传时间消耗。此外,通过设置合适的像素格式(如YUV格式),可进一步降低内存占用。

内存类型介绍

当前PixelMap的内存类型包括以下两种。

系统提供了OH_ImageSourceNative_CreatePixelmapUsingAllocator接口,以便用户能够自定义内存分配类型进行解码。接口定义及使用示例详见图片解码接口说明image_source_native.h。

[h2]SHARE_MEMORY和DMA_ALLOC的区别

名称 SHARE_MEMORY DMA_ALLOC
定义 操作系统提供的共享内存(如ashmem/匿名共享),便于在同一物理页上读写。 使用可被外设/GPU/显示管线直接DMA访问的缓冲区(常见形态是dmabuf/SurfaceBuffer),用于零拷贝链路。
工作原理 进程共享同一段内存,通过CPU进行读写。若要给GPU/显示使用,通常需进行拷贝。 解码器通过DMA将数据写入dmabuf;GPU/显示直接使用该dmabuf,无需拷贝。
使用场景 用于进程或线程间的数据共享,如后处理、算法中间结果交换等场景。 视频/图片硬解、预览、显示等高带宽数据传输场景。
CPU占用 CPU需参与共享内存的管理和同步(如加锁、解锁),会造成额外开销。 占用极低,CPU仅参与DMA控制器的配置,实际数据传输无需CPU干预。
硬件依赖 依赖操作系统支持的共享内存机制。 强依赖硬件DMA控制器。
内存分配与访问权限 系统为共享内存分配物理或虚拟内存区域,访问需通过用户或内核映射操作。 DMA控制器直接操作物理内存,需预先分配DMA缓冲区(通常是连续内存)。
优势 灵活性强。支持多线程或多进程同时共享数据,便于图像后处理和协作。 高效、低延迟;适合大数据量、连续数据块的传输。
缺点 共享内存操作需要额外的同步机制,增加编程复杂度和CPU负担。 需要硬件支持,数据传输范围受DMA地址空间限制(通常需要连续物理内存)。

[h2]使用DMA_ALLOC的优势

开发者在使用DMA_ALLOC时,必须关注stride(步幅)与图片宽度的差异,并在数据读取、解析、送显前进行对齐处理。

[h2]使用限制

当前图片解码功能针对内存分配模式有如下限制。

使用接口OH_ImageSourceNative_CreatePixelmapUsingAllocator进行解码时,若设置的内存分配模式与图片格式或解码方式不匹配,则会抛出内存分配失败的异常。

如果用户选择的分配类型为AUTO,系统将根据解码和渲染的时间综合评估,以决定使用DMA_ALLOC还是SHARE_MEMORY分配机制。

不同的内存分配策略会导致图片的stride(步幅)有所差异。对于通过DMA_ALLOC申请的内存,在对PixelMap执行编辑等操作时,必须使用stride。接下来将介绍如何获取stride。

[h2]获取stride

stride(步幅)描述了图片在内存中每一行像素数据的存储宽度。它是图片绘制过程中的重要参数,用于正确定位图片数据在内存中的布局。

使用DMA分配机制分配内存时,stride必须满足硬件对齐要求。

  1. 调用OH_PixelmapNative_GetImageInfo方法,获取 OH_Pixelmap_ImageInfo 对象。
  2. 调用OH_PixelmapImageInfo_GetRowStride方法,获取stride的值。

C API 获取和操作stride示例代码如下。在使用下面的示例代码之前,开发者需要打开native工程的src/main/cpp/CMakeLists.txt,在target_link_libraries依赖中添加libimage_source.so以及日志依赖libhilog_ndk.z.so。

target_link_libraries(entry PUBLIC libhilog_ndk.z.so libimage_source.so libimage_packer.so libpixelmap.so)

部分接口在API version 20以后才支持,需要开发者在进行开发时选择合适的API版本。

  1. 创建GetJsResult函数处理napi返回值。

    // 处理napi返回值。
    napi_value GetJsResult(napi_env env, int result)
    {
        napi_value resultNapi = nullptr;
        napi_create_int32(env, result, &resultNapi);
        return resultNapi;
    }
  2. 获取和操作stride值。

    #include <cstring>
    #include <hilog/log.h>
    #include <multimedia/image_framework/image/image_common.h>
    #include <multimedia/image_framework/image/pixelmap_native.h>
    #include <multimedia/image_framework/image/image_source_native.h>
    // ...
    
    // 根据像素格式返回每像素的字节数。
    int32_t GetPixelFormatBytes(int32_t pixelFormat)
    {
        switch (pixelFormat) {
            case PIXEL_FORMAT_RGB_565:
                return 2; // 每像素2字节。
            case PIXEL_FORMAT_RGBA_8888:
            case PIXEL_FORMAT_BGRA_8888:
                return 4; // 每像素4字节。
            case PIXEL_FORMAT_RGB_888:
                return 3; // 每像素3字节。
            case PIXEL_FORMAT_ALPHA_8:
                return 1; // 每像素1字节。
            case PIXEL_FORMAT_RGBA_F16:
                return 8; // 每通道16位浮点数,共4通道,合计8字节。
            case PIXEL_FORMAT_NV21:
            case PIXEL_FORMAT_NV12:
                // NV21和NV12是YUV 4:2:0半平面格式,不能用整数每像素字节数计算行跨度。
                return 0;
            case PIXEL_FORMAT_RGBA_1010102:
                return 4; // 每像素4字节。
            case PIXEL_FORMAT_YCBCR_P010:
            case PIXEL_FORMAT_YCRCB_P010:
                return 2; // 每像素2字节。
            default:
                return 0; // 如果像素格式未知,返回0。
        }
    }
    
    struct PixelmapInfo {
        uint32_t width = 0;
        uint32_t height = 0;
        uint32_t rowStride = 0;
        int32_t pixelFormat = 0;
        uint32_t byteCount = 0;
        uint32_t allocationByteCount = 0;
    };
    
    static void GetPixelmapInfo(OH_PixelmapNative *pixelmap, PixelmapInfo *info)
    {
        OH_Pixelmap_ImageInfo *srcInfo = nullptr;
        OH_PixelmapImageInfo_Create(&srcInfo);
        OH_PixelmapNative_GetImageInfo(pixelmap, srcInfo);
        OH_PixelmapImageInfo_GetWidth(srcInfo, &info->width);
        OH_PixelmapImageInfo_GetHeight(srcInfo, &info->height);
        OH_PixelmapImageInfo_GetRowStride(srcInfo, &info->rowStride);
        OH_PixelmapImageInfo_GetPixelFormat(srcInfo, &info->pixelFormat);
        OH_PixelmapImageInfo_Release(srcInfo);
        srcInfo = nullptr;
        return;
    }
    
    static void GetPixelmapAddrInfo(OH_PixelmapNative *pixelmap, PixelmapInfo *info)
    {
        OH_PixelmapNative_GetByteCount(pixelmap, &info->byteCount);
        OH_PixelmapNative_GetAllocationByteCount(pixelmap, &info->allocationByteCount);
        return;
    }
    
    static void CopyPixelRows(void *pixels, void *newPixels, const PixelmapInfo &srcInfo, uint32_t dstRowStride,
        IMAGE_ALLOCATOR_TYPE allocatorType)
    {
        uint8_t *src = reinterpret_cast<uint8_t *>(pixels);
        uint8_t *dst = reinterpret_cast<uint8_t *>(newPixels);
        uint32_t dstSize = srcInfo.byteCount;
        uint32_t rowSize = allocatorType == IMAGE_ALLOCATOR_TYPE::IMAGE_ALLOCATOR_TYPE_DMA ? srcInfo.rowStride :
            dstRowStride;
        for (uint32_t i = 0; i < srcInfo.height; ++i) {
            if (dstSize >= dstRowStride) {
                std::copy(src, src + dstRowStride, dst);
            } else {
                break;
            }
            src += rowSize;
            dst += dstRowStride;
            dstSize -= dstRowStride;
        }
    }
    
    void DataCopy(OH_PixelmapNative *pixelmap, OH_ImageSourceNative* imageSource, OH_DecodingOptions *options,
                  IMAGE_ALLOCATOR_TYPE allocatorType)
    {
        PixelmapInfo srcInfo;
        GetPixelmapInfo(pixelmap, &srcInfo);
        GetPixelmapAddrInfo(pixelmap, &srcInfo);
        void *pixels = nullptr;
        OH_PixelmapNative_AccessPixels(pixelmap, &pixels);
        OH_PixelmapNative *newPixelmap = nullptr;
        Image_ErrorCode image_ErrorCode = OH_ImageSourceNative_CreatePixelmap(imageSource, options, &newPixelmap);
        if (image_ErrorCode != IMAGE_SUCCESS || newPixelmap == nullptr) {
            OH_PixelmapNative_UnaccessPixels(pixelmap);
            OH_DecodingOptions_Release(options);
            OH_ImageSourceNative_Release(imageSource);
            OH_PixelmapNative_Release(pixelmap);
            if (newPixelmap != nullptr) {
                OH_PixelmapNative_Release(newPixelmap);
            }
            return;
        }
        int32_t pixelBytes = GetPixelFormatBytes(srcInfo.pixelFormat);
        if (pixelBytes == 0) {
            OH_PixelmapNative_UnaccessPixels(pixelmap);
            OH_DecodingOptions_Release(options);
            OH_ImageSourceNative_Release(imageSource);
            OH_PixelmapNative_Release(pixelmap);
            OH_PixelmapNative_Release(newPixelmap);
            return;
        }
        uint32_t dstRowStride = srcInfo.width * pixelBytes;
        void *newPixels = nullptr;
        OH_PixelmapNative_AccessPixels(newPixelmap, &newPixels);
        CopyPixelRows(pixels, newPixels, srcInfo, dstRowStride, allocatorType);
        OH_PixelmapNative_UnaccessPixels(newPixelmap);
        OH_PixelmapNative_UnaccessPixels(pixelmap);
        OH_DecodingOptions_Release(options);
        options = nullptr;
        OH_ImageSourceNative_Release(imageSource);
        imageSource = nullptr;
        OH_PixelmapNative_Release(pixelmap);
        pixelmap = nullptr;
        OH_PixelmapNative_Release(newPixelmap);
        newPixelmap = nullptr;
    }
    
    napi_value TestStrideWithAllocatorType(napi_env env, napi_callback_info info)
    {
        napi_value argValue[1] = {nullptr};
        size_t argCount = 1;
        if (napi_get_cb_info(env, info, &argCount, argValue, nullptr, nullptr) != napi_ok || argCount < 1 ||
            argValue[0] == nullptr) {
            OH_LOG_ERROR(LOG_APP, "CreateImageSource napi_get_cb_info failed!");
            return GetJsResult(env, IMAGE_BAD_PARAMETER);
        }
        
        const size_t maxPathLength = 1024;
        char filePath[maxPathLength];
        size_t pathSize = maxPathLength;
        napi_get_value_string_utf8(env, argValue[0], filePath, maxPathLength, &pathSize);
    
        OH_ImageSourceNative* imageSource = nullptr;
        Image_ErrorCode image_ErrorCode = OH_ImageSourceNative_CreateFromUri(filePath, pathSize, &imageSource);
        if (image_ErrorCode != IMAGE_SUCCESS || imageSource == nullptr) {
            return GetJsResult(env, image_ErrorCode == IMAGE_SUCCESS ? IMAGE_BAD_PARAMETER : image_ErrorCode);
        }
        OH_DecodingOptions *options = nullptr;
        image_ErrorCode = OH_DecodingOptions_Create(&options);
        if (image_ErrorCode != IMAGE_SUCCESS || options == nullptr) {
            OH_ImageSourceNative_Release(imageSource);
            return GetJsResult(env, image_ErrorCode == IMAGE_SUCCESS ? IMAGE_BAD_PARAMETER : image_ErrorCode);
        }
        IMAGE_ALLOCATOR_TYPE allocatorType = IMAGE_ALLOCATOR_TYPE::IMAGE_ALLOCATOR_TYPE_DMA;  // 使用DMA创建pixelMap。
        OH_PixelmapNative *pixelmap = nullptr;
        image_ErrorCode = OH_ImageSourceNative_CreatePixelmapUsingAllocator(imageSource, options, allocatorType, &pixelmap);
        if (image_ErrorCode != IMAGE_SUCCESS || pixelmap == nullptr) {
            OH_DecodingOptions_Release(options);
            OH_ImageSourceNative_Release(imageSource);
            return GetJsResult(env, image_ErrorCode == IMAGE_SUCCESS ? IMAGE_BAD_PARAMETER : image_ErrorCode);
        }
        DataCopy(pixelmap, imageSource, options, allocatorType);
        return GetJsResult(env, image_ErrorCode);
    }

像素格式介绍

图片解码后的像素格式直接影响内存占用大小。当前支持的主要像素格式如下。

[h2]RGBA_8888和YUV格式的区别

名称 RGBA_8888 NV21/NV12(YUV 4:2:0)
定义 颜色信息由R(Red)、G(Green)、B(Blue)与透明度(Alpha)四部分组成,每个部分占8位,总共占32位。 颜色信息由亮度分量Y和交错排列的色度分量UV组成。Y分量占8位,UV分量因4:2:0采样平均占4位,总共平均占12位。
每像素字节数 4字节 约1.5字节
内存占用计算 width × height × 4 width × height × 1.5
适用场景 需要处理Alpha通道的场景,如透明度合成、阴影效果等。 图片预览、显示等场景,内存占用小,适合大尺寸图片解码。
优势 支持完整的Alpha通道操作,兼容性好。 内存占用小,JPEG硬件解码可直接输出,避免格式转换开销。

[h2]使用YUV格式的优势

  • SVG格式和TIFF格式的图片不支持解码为YUV像素格式。
  • YUV格式不含Alpha通道,有透明度需求的图片应使用RGBA_8888格式。

[h2]设置YUV像素格式

使用YUV格式解码时,需通过OH_DecodingOptions_SetPixelFormat设置像素格式,并推荐配合DMA内存分配使用。

napi_value CreatePixelmapWithYUV(napi_env env, napi_callback_info info)
{
    napi_value argValue[1] = {nullptr};
    size_t argCount = 1;
    if (napi_get_cb_info(env, info, &argCount, argValue, nullptr, nullptr) != napi_ok || argCount < 1 ||
        argValue[0] == nullptr) {
        OH_LOG_ERROR(LOG_APP, "CreatePixelmapWithYUV napi_get_cb_info failed!");
        return GetJsResult(env, IMAGE_BAD_PARAMETER);
    }
    const size_t maxPathLength = 1024;
    char filePath[maxPathLength];
    size_t pathSize = maxPathLength;
    napi_get_value_string_utf8(env, argValue[0], filePath, maxPathLength, &pathSize);

    OH_ImageSourceNative* imageSource = nullptr;
    Image_ErrorCode errorCode = OH_ImageSourceNative_CreateFromUri(filePath, pathSize, &imageSource);
    
    OH_DecodingOptions *options = nullptr;
    OH_DecodingOptions_Create(&options);
    // 设置YUV像素格式(NV21或NV12),实现内存优化。
    OH_DecodingOptions_SetPixelFormat(options, PIXEL_FORMAT_NV21);
    
    // 使用DMA内存分配,配合YUV格式实现最优解码性能。
    IMAGE_ALLOCATOR_TYPE allocatorType = IMAGE_ALLOCATOR_TYPE::IMAGE_ALLOCATOR_TYPE_DMA;
    OH_PixelmapNative *pixelmap = nullptr;
    errorCode = OH_ImageSourceNative_CreatePixelmapUsingAllocator(imageSource, options, allocatorType, &pixelmap);
    if (errorCode == IMAGE_SUCCESS && pixelmap != nullptr) {
        // 获取PixelMap信息,验证像素格式。
        OH_Pixelmap_ImageInfo *imageInfo = nullptr;
        OH_PixelmapImageInfo_Create(&imageInfo);
        OH_PixelmapNative_GetImageInfo(pixelmap, imageInfo);
        
        uint32_t width;
        uint32_t height;
        uint32_t rowStride;
        int32_t pixelFormat;
        OH_PixelmapImageInfo_GetWidth(imageInfo, &width);
        OH_PixelmapImageInfo_GetHeight(imageInfo, &height);
        OH_PixelmapImageInfo_GetRowStride(imageInfo, &rowStride);
        OH_PixelmapImageInfo_GetPixelFormat(imageInfo, &pixelFormat);
        OH_LOG_INFO(LOG_APP, "YUV PixelMap created: width=%{public}u, height=%{public}u, "
                    "rowStride=%{public}u, pixelFormat=%{public}d",
                    width, height, rowStride, pixelFormat);
        OH_PixelmapImageInfo_Release(imageInfo);
    } else {
        OH_LOG_ERROR(LOG_APP, "CreatePixelmapWithYUV failed, errorCode=%{public}d", errorCode);
    }
    
    OH_DecodingOptions_Release(options);
    options = nullptr;
    OH_ImageSourceNative_Release(imageSource);
    imageSource = nullptr;
    return GetJsResult(env, errorCode);
}

系统默认的内存分配方式

使用OH_ImageSourceNative_CreatePixelmap()解码时,系统自动选择共享内存或DMA内存。

需要指定内存类型时,应调用OH_ImageSourceNative_CreatePixelmapUsingAllocator(),将allocator设置为IMAGE_ALLOCATOR_TYPE_DMA或IMAGE_ALLOCATOR_TYPE_SHARE_MEMORY。

解码单张图片的内存限制

为了防止内存溢出导致系统崩溃,系统对进程内存做了限制,详细说明请参考应用被查杀问题检测方法。

图片框架对单张图片的解码设置了2GB的内存限制。进程需要主动管理自身内存,建议在不使用OH_PixelmapNative时及时释放,以避免进程被系统终止。

应用可使用onMemoryLevel监听系统内存变化情况。

PixelMap申请像素内存的计算规则如下所示。

pixels_size(像素内存大小) = stride(图片像素存储宽度) * height(图片像素高度)

对于原始像素内存超过2GB且支持下采样的图片,建议开发者使用OH_ImageSourceNative_CreatePixelmap或OH_ImageSourceNative_CreatePixelmapUsingAllocator接口,并在OH_DecodingOptions(解码参数)中设置desiredSize(期望输出大小)进行下采样解码。

从API version 21开始,对于支持下采样解码的图片,设置desiredSize(期望输出大小)后,解码器将以基准梯度为1/8的最优下采样率计算PixelMap的像素内存,即按照7/8、6/8、...、1/8的采样率,逐次递减取一个清晰度最高的采样数。

图片框架内,不同图片格式的下采样解码支持情况如下所示。

是否支持下采样 图片格式
支持 .jpg .png .heic(具体支持情况请参考设备规格文档。)
不支持 .gif .bmp .webp .dng .svg .ico