应用在进行图片解码操作时,需要申请对应内存。内存占用的大小与内存分配类型和像素格式密切相关。当前指导将介绍不同的内存类型、像素格式,以及如何组合使用以达到最优的解码性能。
应用侧通过解码API接口获取PixelMap,并将其传递给Image组件以进行显示。
当PixelMap较大且使用共享内存时,RS主线程将经历较长的纹理上传时间,导致卡顿现象。图形侧提供了DMA内存零拷贝功能,可在绘制图片时避免纹理上传时间消耗。此外,通过设置合适的像素格式(如YUV格式),可进一步降低内存占用。
当前PixelMap的内存类型包括以下两种。
系统提供了OH_ImageSourceNative_CreatePixelmapUsingAllocator接口,以便用户能够自定义内存分配类型进行解码。接口定义及使用示例详见图片解码接口说明image_source_native.h。
减少纹理上传时间
当使用SHARE_MEMORY时,图片数据需通过CPU复制到GPU显存,增加了纹理上传的时间。而采用DMA_ALLOC后,数据直接保存在GPU可访问的内存中,避免了耗时的复制过程。
减轻CPU负载
DMA_ALLOC允许GPU直接访问解码后数据,减少了内存复制带来的负载。
开发者在使用DMA_ALLOC时,必须关注stride(步幅)与图片宽度的差异,并在数据读取、解析、送显前进行对齐处理。
当前图片解码功能针对内存分配模式有如下限制。
使用接口OH_ImageSourceNative_CreatePixelmapUsingAllocator进行解码时,若设置的内存分配模式与图片格式或解码方式不匹配,则会抛出内存分配失败的异常。
如果用户选择的分配类型为AUTO,系统将根据解码和渲染的时间综合评估,以决定使用DMA_ALLOC还是SHARE_MEMORY分配机制。
不同的内存分配策略会导致图片的stride(步幅)有所差异。对于通过DMA_ALLOC申请的内存,在对PixelMap执行编辑等操作时,必须使用stride。接下来将介绍如何获取stride。
stride(步幅)描述了图片在内存中每一行像素数据的存储宽度。它是图片绘制过程中的重要参数,用于正确定位图片数据在内存中的布局。
使用DMA分配机制分配内存时,stride必须满足硬件对齐要求。
stride值需为硬件平台要求字节数的整数倍。
当stride值大于图片宽度时,系统会自动补齐填充数据(padding)。
stride的值可以通过OH_PixelmapNative_GetImageInfo 接口获取。
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版本。
创建GetJsResult函数处理napi返回值。
// 处理napi返回值。
napi_value GetJsResult(napi_env env, int result)
{
napi_value resultNapi = nullptr;
napi_create_int32(env, result, &resultNapi);
return resultNapi;
} 获取和操作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);
} | 名称 | 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硬件解码可直接输出,避免格式转换开销。 |
显著降低内存占用
以4K图片(3840×2160)为例:
减少格式转换开销
JPEG等格式的图片在硬件解码时,解码器可直接输出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 |