图片解码内存优化(ArkTS)

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

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

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

内存类型介绍

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

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

[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]使用限制

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

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

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

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

[h2]获取stride

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

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

stride的值可以通过getImageInfo() 接口获取。

  1. 调用getImageInfo()方法,获取ImageInfo对象。

  2. 从ImageInfo对象中访问stride值:info.stride。

    // 导入相关模块。
    import { image } from '@kit.ImageKit';
    import { common } from '@kit.AbilityKit';
    async CreatePixelMapUsingAllocator(context: Context, type: image.AllocatorType): Promise<image.PixelMap | undefined> {
      const resourceMgr = context.resourceManager;
      try {
        const rawFile = await resourceMgr.getRawFileContent('99_132.jpg'); // 测试图片为99*132的jpg图。
        let imageSource: image.ImageSource = image.createImageSource(rawFile.buffer as ArrayBuffer);
        let options: image.DecodingOptions = {};
        let pixelmap = await imageSource.createPixelMapUsingAllocator(options, type);
        if (pixelmap != undefined) {
          let info = await pixelmap.getImageInfo();
          // 用DMA_ALLOC内存申请出的pixelmap的stride与SHARE_MEMORY内存申请出的pixelmap的stride不同。
          console.info('stride = ' + info.stride);
        }
        return pixelmap;
      } catch (err) {
        console.error(`Create PixelMap by setting allocator type failed: ${err}.`);
        return undefined;
      }
        
    }

像素格式介绍

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

[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格式解码时,需在DecodingOptions中设置desiredPixelFormat参数,并推荐配合DMA内存分配使用。

async CreatePixelMapWithYUV(context: Context): Promise<image.PixelMap | undefined> {
  const resourceMgr = context.resourceManager;
  try {
    const rawFileDescriptor = await resourceMgr.getRawFd('test.jpeg');
    let imageSource: image.ImageSource = image.createImageSource(rawFileDescriptor);

    // 设置YUV像素格式和DMA内存分配,实现最优解码性能。
    let options: image.DecodingOptions = {
      desiredPixelFormat: image.PixelMapFormat.NV21  // 设置为YUV格式,也可选择NV12。
    };
    let pixelmap = await imageSource.createPixelMapUsingAllocator(options, image.AllocatorType.DMA);

    if (pixelmap != undefined) {
      let info = await pixelmap.getImageInfo();
      console.info('YUV PixelMap created, stride = ' + info.stride + ', pixelFormat = ' + info.pixelFormat);
    }
    return pixelmap;
  } catch (err) {
    console.error(`Create PixelMap with YUV format failed: ${err}.`);
    return undefined;
  }
}

系统默认的内存分配方式

使用createPixelMap或createPixelMapSync解码时,系统自动选择共享内存或DMA内存。

需要指定内存类型时,应调用createPixelMapUsingAllocator或createPixelMapUsingAllocatorSync,将allocatorType设置为image.AllocatorType.DMA或image.AllocatorType.SHARE_MEMORY。

解码单张图片的内存限制

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

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

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

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

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

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

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

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

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