本文为您介绍 veImageX 自研 Android 端 HEIF 编码库的接入流程。
本文档适用于 Android 4.0 及以上开发版本。
您已完成独立 HEIF 编解码库的集成准备。
Grid 编码不支持编码带有 alpha 通道的图片。如需正常编码带有 alpha 通道的图片,请关闭 Grid 编码。
确保 project 根目录下的build.gradle
下配置服务,代码示例如下所示:
maven { url 'https://artifact.bytedance.com/repository/Volcengine/' // 最新版本号获取地址:https://artifact.bytedance.com/repository/Volcengine/ }
请参考发布历史,获取 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);
在自定义的Application
的onCreate
方法中执行下面代码:
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
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "bitmap_encode.heic")); HeifEncoder.compressBitmap(bitmap, quality, fileOutputStream);
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "yuv_encode.heic")); HeifEncoder.compressYuv(yuv, width, height, quality, fileOutputStream);
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "yuva_encode.heic")); HeifEncoder.compressYuva(yuva, width, height, quality, fileOutputStream, exifData, HeifEncoder.ENCODE_SPEED_FAST);
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "rgba_encode.heic")); HeifEncoder.compressRgba(rgba, width, height, quality, fileOutputStream);
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "rgb_encode.heic")); HeifEncoder.compressRgb(rgb, width, height, quality, fileOutputStream, exifData, HeifEncoder.ENCODE_SPEED_FAST);
FileOutputStream fileOutputStream = new FileOutputStream(new File(getFilesDir(), "jpg_encode.heic")); HeifEncoder.compressLocalPic(filePath, quality, fileOutputStream);
3.1.0-tob 及之后版本已支持。
由于移动端应用可使用的内存有限,当编码大图的时候容易产生 OOM 异常。为了保证编码成功,建议您在编码前参考以下代码示例,判断当前内存编码时是否存在 OOM 风险(内存溢出)。具体操作如下所示。
初始化编码边界,支持调整内存比例参数和其他编码配置。
说明
如果不调用 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; } }
编码判断,您可通过 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)
说明
使用前请确保已添加 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)