You need to enable JavaScript to run this app.
导航
集成 HEIF 编码库
最近更新时间:2024.10.16 15:03:08首次发布时间: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 数据(可选)

混淆规则

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() 加载 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();
}

编码接口介绍

com.bytedance.fresco.native_heif_encoder.HeifEncoder 类

com.bytedance.fresco.native_heif_encoder.HeifEncoder为编码 heic 静图的接口类,支持 bitmap/yuv/yuva/rgba/rgb/jpg 进行 heic 编码。代码示例如下所示:

/**
 * @param filePath   需要编码的本地图片,必须是系统可以解码的图片格式,例如:jpg png
 * @param quality    编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param hasAlpha   是否有 alpha 通道
 * @param stream     写入编码 heic 数据的流
 * @param exifData   exifData 数据
 * @param speed      编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @param sourceSize 图片解码前原始体积,单位为字节。例如在 jpeg 转 heic 转 jpeg 这种特定场景,使编码前后两个 jpeg 的图片体积大小基本保持一致。详见 https://www.volcengine.com/docs/508/116621#%E5%BD%93-jpeg-%E8%BD%AC-heic-%E8%BD%AC-jpeg-%E6%97%B6%EF%BC%8C%E5%A6%82%E4%BD%95%E4%BD%BF%E6%9C%80%E7%BB%88%E8%BE%93%E5%87%BA%E7%9A%84-jpeg-%E4%B8%8E%E6%9C%80%E5%88%9D-jpeg-%E5%8E%9F%E5%9B%BE%E7%9A%84%E4%BD%93%E7%A7%AF%E5%A4%A7%E5%B0%8F%E5%9F%BA%E6%9C%AC%E4%BF%9D%E6%8C%81%E4%B8%80%E8%87%B4%EF%BC%9F
 * @return 0         成功
 *         12001     云控配置不支持编码,请提交工单联系技术支持协助您修改云控配置
 *         12002     授权错误,请检查是否正确配置授权
 *         13101     底层创建数组失败,请检查是否内存不足(2.9.5-tob 及以上版本支持)
 *         其他       未定义错误,您可提交工单联系技术支持协助排查和处理
 */
public static int compressLocalPic(String filePath, int quality, boolean hasAlpha, OutputStream stream,
                                   byte[] exifData, @EncodeSpeed String speed, int sourceSize)
        throws IllegalArgumentException, IOException       
/**
 * @param bitmap     需要编码的 bitmap 数据
 * @param quality    编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param hasAlpha   是否有 alpha 通道
 * @param stream     写入编码 heic 数据的流
 * @param exifData   exifData 数据
 * @param speed      编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @param sourceSize 图片解码前原始体积,单位为字节。例如在 jpeg 转 heic 转 jpeg 这种特定场景,使编码前后两个 jpeg 的图片体积大小基本保持一致。详见 https://www.volcengine.com/docs/508/116621#%E5%BD%93-jpeg-%E8%BD%AC-heic-%E8%BD%AC-jpeg-%E6%97%B6%EF%BC%8C%E5%A6%82%E4%BD%95%E4%BD%BF%E6%9C%80%E7%BB%88%E8%BE%93%E5%87%BA%E7%9A%84-jpeg-%E4%B8%8E%E6%9C%80%E5%88%9D-jpeg-%E5%8E%9F%E5%9B%BE%E7%9A%84%E4%BD%93%E7%A7%AF%E5%A4%A7%E5%B0%8F%E5%9F%BA%E6%9C%AC%E4%BF%9D%E6%8C%81%E4%B8%80%E8%87%B4%EF%BC%9F
 * @return 0         成功
 *         12001     云控配置不支持编码,请提交工单联系技术支持协助您修改云控配置
 *         12002     授权错误,请检查是否正确配置授权
 *         13101     底层创建数组失败,请检查是否内存不足(2.9.5-tob 及以上版本支持)
 *         其他      未定义错误,您可提交工单联系技术支持协助排查和处理
 */        
public static int compressBitmap(Bitmap bitmap, int quality, boolean hasAlpha, OutputStream stream, byte[] exifData, @EncodeSpeed String speed, int sourceSize)
        throws IllegalArgumentException, IOException
        
/**
 * @param yuvData  需要编码的 yuv 数据
 * @param width    宽
 * @param height   高
 * @param quality  编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param stream   写入编码 heic 数据的流
 * @param exifData exifData 数据
 * @param speed    编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @return 0       成功
 *         其他     失败
 */
public static int compressYuv(byte[] yuvData, int width,int height,int quality,OutputStream stream, 
byte[] exifData, @EncodeSpeed String speed) throws IllegalArgumentException, IOException 

/**
 * @param yuvaData 需要编码的 yuva 数据
 * @param width    宽
 * @param height   高
 * @param quality  编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param stream   写入编码 heic 数据的流
 * @param exifData exifData 数据
 * @param speed    编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @return 0       成功
 *         其他     失败
 */
public static int compressYuva(byte[] yuvaData, int width, int height, int quality, OutputStream stream,
                               byte[] exifData, @EncodeSpeed String speed) throws IllegalArgumentException, IOException       
        
/**
 * @param rgbaData 需要编码的 rgba 数据
 * @param width    宽
 * @param height   高
 * @param quality  编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param hasAlpha 是否有 alpha 通道
 * @param stream   写入编码 heic 数据的流
 * @param exifData exifData 数据
 * @param speed    编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @return 0       成功
 *         其他     失败
 */
public static int compressRgba(byte[] rgbaData,int width,int height,int quality,boolean hasAlpha,OutputStream stream, 
byte[] exifData, @EncodeSpeed String speed) throws IllegalArgumentException, IOException     

/**
 * @param rgbData  需要编码的 rgb 数据
 * @param width    宽
 * @param height   高
 * @param quality  编码质量,取值范围为[1,100]。1 表示为最小编码质量,100 表示为最大编码质量。值越大,编码后的图片体积越大,画质越好
 * @param stream   写入编码 heic 数据的流
 * @param exifData exifData 数据
 * @param speed    编码档位,“HeifEncoder.ENCODE_SPEED_SLOW”表示编码压缩率高速度慢,“HeifEncoder.ENCODE_SPEED_FAST”表示编码压缩率低速度快
 * @return 0       成功
 *        其他      失败
 */
public static int compressRgb(byte[] rgbData, int width, int height, int quality,
                              OutputStream stream, byte[] exifData, @EncodeSpeed String speed
) throws IllegalArgumentException, IOException
             
                        

示例

  • 使用 bitmap 编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "bitmap_encode.heic"));
HeifEncoder.compressBitmap(bitmap, quality, fileOutputStream);
  • 使用 yuv 数据编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "yuv_encode.heic"));
HeifEncoder.compressYuv(yuv, width, height, quality, fileOutputStream);
  • 使用 yuva 数据编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(),  "yuva_encode.heic"));

HeifEncoder.compressYuva(yuva, width, height, quality, fileOutputStream, exifData, HeifEncoder.ENCODE_SPEED_FAST);
  • 使用 rgba 数据编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "rgba_encode.heic"));
HeifEncoder.compressRgba(rgba, width, height, quality, fileOutputStream);
  • 使用 rgb 数据编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(),  "rgb_encode.heic"));

HeifEncoder.compressRgb(rgb, width, height, quality, fileOutputStream, exifData, HeifEncoder.ENCODE_SPEED_FAST);
  • 使用本地其他格式(jpg、png)文件编码成 heic,代码示例如下所示:
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "jpg_encode.heic"));
HeifEncoder.compressLocalPic(filePath, quality, fileOutputStream);

编码内存占用判断

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)