You need to enable JavaScript to run this app.
导航
集成 HEIF 编码库
最近更新时间:2024.11.18 20:48:41首次发布时间:2024.05.07 15:24:06

本文为您介绍 veImageX 自研 Android 端 HEIF 编码库的接入流程。

适用版本

本文档适用于 Android 4.0 及以上开发版本。

前提条件

您已完成独立 HEIF 编解码库的集成准备

注意事项

Grid 编码不支持编码带有 alpha 通道的图片。如需正常编码带有 alpha 通道的图片,请关闭 Grid 编码。

添加 Maven 仓库

确保 project 根目录下的build.gradle下配置服务,代码示例如下所示:

maven {
    url 'https://artifact.bytedance.com/repository/Volcengine/'
    // 最新版本号获取地址:https://artifact.bytedance.com/repository/Volcengine/
}

获取 SDK 最新版本号

请参考发布历史,获取 SDK 最新版本号。

添加 SDK 依赖

在 module 目录下的build.gradle文件中的dependencies中添加 SDK 依赖,并填入获取的最新版本号。代码示例如下所示:

implementation 'com.bytedance.fresco:authorization:xxx' //添加授权认证+云控配置
implementation 'com.bytedance.fresco:nativeheifencoder:xxx' //heif 编码
implementation 'com.bytedance.fresco:nativelibexif:xxx' //管理图片 EXIF 数据(可选)
implementation 'com.bytedance.fresco:statistics:xxx'// 编码日志埋点上报(可选)

混淆规则

proguard-rules.pro 文件中配置 SDK 混淆规则。

-keep class com.bytedance.fresco.cloudcontrol.CloudControl {*;}
-keep class com.bytedance.fresco.native_heif_encoder.HeifEncoder {*;}
-keep class com.bytedance.imagex.bdauthorization.AuthManagerNative {
    native <methods>;
}

日志打印

测试开发的过程中,建议打开 logcat 日志,便于排查和调试问题。

注意

对于 Release 线上版本建议您关闭日志打印能力,以减少实际业务使用时的性能开销。

ImageXLog.setDebug(true);
AuthManagerNative.nativeOpenLog(true);

配置授权

在自定义的ApplicationonCreate方法中执行下面代码:

List<String> encodedAuthCode = new ArrayList<>();
encodedAuthCode.add("xxxxxxxxxxxxxxxxxxxxxxxxx");

InitConfig initConfig = new InitConfig(
        this,             // Application Context
        "000000",         // App id, 如实填写
        "sample",         // App Name,暂未用到,传入app名称
        "debug",          // channel,暂未用到,传入例如OPPO 
        "0.0.1",          // App的versionName, 如实填写
        "1",              // App的versionCode, 如实填写
        "48144589260",    // device id,暂未用到
        InitConfig.CHINA, // App上线的区域,如实填写
        "M2ZmYzkzZjUtN2", // Token,如实填写,您可在 接入准备-购买授权 获取Token
        encodedAuthCode   // 授权码List<String>,如实填写,您可在 接入准备-购买授权 获取授权码
);
CloudControl.init(initConfig);

HeifEncoder.init();       //如果要使用编码日志埋点上报功能,请在上述代码后调用。

编码准备

为避免因加载库失败导致后续调用编码库接口时出现异常,请确保在调用编码接口之前先调用 HeifEncoder.init() 加载 so。如果返回值为 true 则表示 so 加载成功,此时您可正常调用编码接口进行图片编码。如果返回值为 false,您可重新调用 init() 进行重试。代码示例如下所示:

public class HeifEncoder {
    /**
     * 调用该接口会进行 so 加载。
     * sdk 会在类加载时会自动调用该接口,但特殊机型可能会偶现 so 加载失败的情况,这时可调用此接口重试 so 加载。
     * 建议在调用编码接口之前调用,并判断返回值,返回 true 表示 so 加载成功,可能正常使用编码接口。
     * 
     * @return true, so 加载成功,可使用以下编码接口。
     *         false,so 加载失败,可调整时机再次调用 init 重试 so 加载。
     */
    public synchronized static boolean init();
}

编码接口介绍

3.3.1-tob 及以后版本的编码库 SDK 提供了最新编码配置接口类,建议您使用最新配置类。

public static int encode(HeifEncodeConfig config) 
	

HeifEncodeConfig 为编码配置类,支持 bitmap/yuv/yuva/rgba/rgb/jpg/png/webp 进行 heic 编码。代码示例如下所示:

@Retention(CLASS)
@IntDef(
        value = {
                EncodeFormat.FORMAT_RGBA,  // 编码格式:RGBA 像素数据
                EncodeFormat.FORMAT_RGB,   // 编码格式:RGB 像素数据
                EncodeFormat.FORMAT_YUV,   // 编码格式:YUV 像素数据
                EncodeFormat.FORMAT_YUVA,  // 编码格式:YUVA 像素数据
                EncodeFormat.FORMAT_BITMAP // 编码格式:本地图片文件(webp,png,jpeg)以及 Bitmap 对象
        })
public @interface EncodeFormat;


@Retention(CLASS)
@IntDef(
        value = {
                EncodePreset.PRESET_FAST,    // 编码档位:快速,压缩率低
                EncodePreset.PRESET_SLOW     // 编码档位:慢速,压缩率高
        })
public @interface EncodePreset;

/**
 * 针对本地图片(webp,png,jpeg)编码配置的构造方法
 *
 * @param filePath 编码前本地图片的文件路径
 * @param stream   编码后 heic 文件的 outputStream
 */
public HeifEncodeConfig(String filePath, @NonNull OutputStream stream) throws IllegalArgumentException 


/**
 * 针对 Bitmap 数据编码配置的构造方法
 *
 * @param sourceBitmap 编码前 Bitmap 数据
 * @param stream       编码后 heic 文件的 outputStream
 */
public HeifEncodeConfig(Bitmap sourceBitmap, @NonNull OutputStream stream) throws IllegalArgumentException 


/**
 * 针对 RGBA、RGB、YUV、YUVA 文件的编码配置的构造方法
 *
 * @param pixelData 编码前的像素数据
 * @param stream    输出 heic 文件的 outputStream
 * @param format    编码格式
 */
public HeifEncodeConfig(byte[] pixelData, @EncodeFormat int format,
                        int width,
                        int height, @NonNull OutputStream stream) throws IllegalArgumentException

/**
 * 设置编码档位
 * @param preset 编码档位
 * @return       编码配置类
 */
public HeifEncodeConfig setEncodePreset(@EncodePreset int preset) 


/**
 * 设置编码质量
 * @param encodeQuality 编码质量
 * @return              编码配置类
 */
public HeifEncodeConfig setEncodeQuality(int encodeQuality)

/**
 * 设置图片宽度,在编码格式为 RGBA、RGB、YUV、YUVA 时必须设置
 * @param width 图片宽度
 * @return 编码配置类
 */
public HeifEncodeConfig setWidth(int width)

/**
 * 设置图片高度,在编码格式为 RGBA、RGB、YUV、YUVA 时必须设置
 * @param height 图片高度
 * @return
 */
public HeifEncodeConfig setHeight(int height)

/**
 * 设置编码后图片是否包含透明通道
 * @param hasAlpha 是否包含透明通道
 * @return 编码配置类
 */
public HeifEncodeConfig setHasAlpha(boolean hasAlpha)

/**
 * 设置图片的 exif 数据
 * @param exifData exif 数据,传入后可使编码后图片自动旋转并保留 EXIF 信息。您可参考 BDExifHelper 提取解析图片中的 EXIF 信息。
 * @return 编码配置类
 */
public HeifEncodeConfig setExifData(byte[] exifData)

/**
 * 设置编码后图片是否与原图大小基本保持一致
 * @param writeSourceSize 是否写入源文件大小,默认关闭。开启后,通过 setSourceSize 传入的原始图片体积大小将被指定为编码后 heic 的体积,编码前后体积基本保持一致。适用于所有类型的图片。
 * @return 编码配置类
 */
public HeifEncodeConfig setWriteSourceSize(boolean writeSourceSize)
	

当传入本地图片文件路径时,SDK 会自动解析并获取原始图片的体积大小。但对于非本地图片文件,您可以通过 setSourceSize 手动传入原始图片的文件大小。

适用于在 jpeg 转 heic 转 jpeg 这种特定场景,能使编码前后两个 jpeg 的图片体积大小基本保持一致。详见 当 jpeg 转 heic 转 jpeg 时,如何使最终输出的 jpeg 与最初 jpeg 原图的体积大小基本保持一致?此外,您还能在编码监控中查询上报的编码前文件大小。

heifEncodeConfig.getEncodeResult().setSourceSize(sourceSize);    // 原图的体积大小,单位为字节
	

示例

将一张本地图片编码为 heic 图片,代码示例如下所示:

val heifEncodeConfig = HeifEncodeConfig(imagePath, outputStream) // 构造本地图片编码的配置类
.apply {
    setHasAlpha(true)                                            // 设置编码后图片含有 alpha 通道
    setExifData(exifData)                                        // 设置 ExifData
    setEncodePreset(HeifEncodeConfig.EncodePreset.PRESET_FAST)   // 设置编码档位为 fast
    setEncodeQuality(80)                                         // 设置编码质量为 80
    encodeResult.sourceSize = fileSize ?: 0                      // 设置原图的体积大小
}
HeifEncoder.encode(heifEncodeConfig)                             // 执行编码操作
	

编码日志埋点上报

3.3.1-tob 及以后版本的编码库 SDK 支持在 HEIF 编码监控查询上报指标,埋点上报功能为默认开启。

  • 修改采样率:在控制台的 SDK 配置下发,对allow_log_type下的imagex_heif_encoder_monitor更改采样率参数,默认采样率为 100%。
  • 关闭埋点上报:通过EncodeTraceConfig.setEncodeTraceEnable(false)关闭编码埋点上报功能。

以下为开启埋点上报后,SDK 收集的埋点信息:

参数类型说明
image_widthInt编码图片宽度
image_heightInt编码图片高度
data_typeInt编码图片数据类型
presetString编码档位
qualityInt编码质量参数
has_alphaInt是否编码为带 alpha 通道的 heic 图片
has_exifInt编码时是否传入了 exif 数据
exif_sizeIntexif 数据大小
heic_sizeInt编码后 heic 数据大小
rr_codeInt错误码
err_descString错误码描述
system_dec_durationInt系统生成像素数据的耗时
encode_durationInt编码耗时
encoder_sdk_versionString编码库版本
encode_statusString编码状态
open_gridInt是否使用了 grid 编码
image_typestring编码前原图格式
source_sizeInt编码前原图体积

编码内存占用判断

3.1.0-tob 及之后版本已支持。

由于移动端应用可使用的内存有限,当编码大图的时候容易产生 OOM 异常。为了保证编码成功,建议您在编码前参考以下代码示例,判断当前内存编码时是否存在 OOM 风险(内存溢出)。具体操作如下所示。

  1. 初始化编码边界,支持调整内存比例参数和其他编码配置。

    说明

    如果不调用 HeifEncodeMemUtil.setEncodeMemoryConfig 进行初始化,那么底层会使用 EncodeMemoryConfig 类的无参构造方法去初始化。

    val encodeMemoryConfig = EncodeMemoryConfig(0.9f, true, 0.1f)
    // 如需自定义内存比例参数以及底层编码算法,请调用下述接口进行调整。
    HeifEncodeMemUtil.setEncodeMemoryConfig(encodeMemoryConfig)
    

    EncodeMemoryConfig 参数说明如下所示:

    public class EncodeMemoryConfig {
        /**
         *  内存比例。判断逻辑为:若 所需内存 < (空闲内存 x 内存比例),则认为没有 OOM 风险,反之则可能存在风险。
         *  @note 取值建议不小于 0.5f,且不大于 1.0f。
         * 若取值过小,则会浪费内存。导致原本可以成功编码的大图,会被判定为有编码风险。
         *  若取值大于 0.9f,特殊情况下则可能会增加 OOM 的风险。
         */
        private float memBasicRatio = 0.9f;
        
        /**
         * 是否开启 grid 编码
         * 开启后,底层编码图片时会使用更少的内存完成编码。因此在编码的场景设置为 true,可提高编码的上限。
         */
        private boolean openGrid = false;
        
        /**
         * 编码时创建的数组,占用剩余 jvm 内存空间的占比。
         *  @note 默认值为 0.1f,取值应不小于 0.05f,且不大于 0.2f。
         * 当 Android 系统小于 8.0 时,剩余的比例将用来计算容纳 Bitmap。
         * 一般情况下建议使用默认值 0.1f。
         */
        private float jvmArrayOccupyRation = 0.1f;
        
        private static final float MIN_MEM_BASIC_RATIO = 0.5f;
        
        private static final float MAX_MEM_BASIC_RATIO = 1.0f;
        
        private static final float MIN_JVM_ARRAY_RATIO = 0.05f;
        
        private static final float MAX_JVM_ARRAY_RATIO = 0.2f;
        
        public EncodeMemoryConfig() {
        }
        
        public EncodeMemoryConfig(boolean openGrid) {
            this.openGrid = openGrid;
        }
        
        public EncodeMemoryConfig(float memBasicRatio, boolean openGrid, float jvmArrayOccupyRation) {
            assert memBasicRatio > MIN_MEM_BASIC_RATIO  && memBasicRatio < MAX_MEM_BASIC_RATIO;
            assert jvmArrayOccupyRation < MAX_JVM_ARRAY_RATIO  && jvmArrayOccupyRation > MIN_JVM_ARRAY_RATIO;
            this.openGrid = openGrid;
            this.memBasicRatio = memBasicRatio;
            this.jvmArrayOccupyRation = jvmArrayOccupyRation;
        }
        
        public float getMemBasicRatio() {
            return memBasicRatio;
        }
        
        public boolean isOpenGrid() {
            return openGrid;
        }
        
        public float getJvmArrayOccupyRation() {
            return jvmArrayOccupyRation;
        }
    }
    
  2. 编码判断,您可通过 HeifEncodeMemUtil 去判断当前内存是否能够成功编码为期望宽高的图片以及当前内存能够成功编码的像素上限。

    适用场景如下所示:

    • 使用 compressLocalPic 对普通图片(JPEG、PNG、WebP 等)编码成 heic 图。

    • 使用 compressBitmap 对 Bitmap 编码为 heic 图。

      注意

      请在生成 Bitmap 前调用 HeifEncodeMemUtil 方法,判断能否编码成功。若返回 true,您可生成 Bitmap 数据,再使用 compressBitmap 执行编码操作。

    # HeifEncodeMemUtil
    
    /**
      *  判断当前内存是否能够成功编码为期望宽高的图片。
      *
      *  @param  context   Application
      *  @param  width          期望编码后图片的宽
      *  @param  height        期望编码后图片的高
      *  @param  useBitmap  是否使用的是 Bitmap 接口,使用 compressLocalPic  或  compressBitmap 接口时,请指定为  true。固定传 true 即可。
      *  @return  true 表示可成功编码为期望宽高的图片,false 表示编码有 OOM 风险(内存溢出)。
      */
    public static boolean canSupportEncode(@NonNull Application context, int width, int height, boolean useBitmap) 
    
    /**
      *  判断当前内存能够成功编码的像素上限。
      *
      *  @param  context   Application
      *  @param  useBitmap 是否使用的是 Bitmap 接口,使用 compressLocalPic  或  compressBitmap 接口时,请指定为 true。固定传 true 即可。
      *  @return  当前内存支持的编码像素上限。
      */
    public static long getThresholdPixel(@NonNull Application context, boolean useBitmap)
    

Exif 接口介绍

说明

使用前请确保已添加 nativelibexif 依赖

仅支持对特定的 Tag 内容执行新增、删除和修改操作,详见 BDExifTags,各 Tag 的详细说明请参考 Standard Exif Tags

class BDExifHelper(filePath: String) {
    /**
     * 删除 Tag 内容
     * 具体可删除的 Tag,详见 BDExifTags
     */
    fun deleteExifTag(deleteTags: Array<String>?)

    /**
     * 新增/更新 Tag 内容
     * 若原 Tag 中信息为空,表示新增内容;若原 Tag 中信息不为空,表示更新内容
     * 具体可新增/更新的 Tag,详见 BDExifTags
     *  @param tag       指定需要新增/修改的 Tag 值
     *  @param ifd       指定 Tag 对应的 IFD
     *  @param format    指定 Tag 内容的格式,详见 BDExifFormats
     *  @param count     指定 Tag 内容的个数
     *  @param valueData 指定新增/修改后 Tag 的内容
          */
    fun setExifTag(tag: Int, ifd: Int, format: Int, count: Int, valueData: ByteArray)
    
    /**
     *  获取 Exif 中图片旋转信息
     */
    fun obtainOriInfo(): BDExifOriInfo
}

/**
  *  解析 Exif 中旋转信息,包括旋转角度、是否翻转等
  */
class BDExifOriInfo(
    var value: Int,
    var rotation: Int,
    var flipInHorizontal: Boolean,
    var flipInVertical: Boolean
)

示例

  • 使用 BDExifHelper 进行 Exif 源数据的提取并解析,代码示例如下所示:

    // 提取并解析:exifEditor.exifData 为 exif 的 ByteArray 源数据;exifEditor.exifTagMap 为解析之后 Map 形式的 exif 数据。
    val exifEditor = BDExifHelper(inputFilePath)
    // 若 exif 数据中包含旋转信息,可通过该接口进一步解析,获取旋转翻转信息,详见 BDExifOriInfo 类接口介绍。
    val oriInfo = exifEditor.obtainOriInfo()
    
  • 使用 BDExifHelper 删除 Exif 源数据中指定条目,代码示例如下所示:

    // deleteTags 中填入上述 exifEditor.exifTagMap 中的键值
    val deleteTags: Array<String> = arrayOf("UserComment", "Orientation", "DateTimeOriginal")
    exifEditor.deleteExifTag(deleteTags)
    
  • 使用 BDExifHelper 编辑 Exif 源数据中指定条目,代码示例如下所示:

    // 具体条目和格式相见 BDExifTags.kt
    // 设置 BDEXIF_TAG_USER_COMMENT 条目为 exif test
    val str = "exif test"
    val strBytes = str.toByteArray(Charsets.US_ASCII)
    exifEditor.setExifTag(BDExifTags.BDEXIF_TAG_USER_COMMENT,  2, BDExifFormats.BDEXIF_FORMAT_ASCII, strBytes.size, strBytes)
    // 设置 BDEXIF_TAG_ORIENTATION 条目为 1
    val ori: Short = 1
    val numBytes = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(ori).array()
    exifEditor.setExifTag(BDExifTags.BDEXIF_TAG_ORIENTATION, 0, BDExifFormats.BDEXIF_FORMAT_SHORT, 1, numBytes)