Android 12 中引入的兼容媒体转码是一项功能,允许设备对视频拍摄使用更先进、存储效率更高的媒体格式(例如 HEVC),同时保持与应用的兼容性。借助此功能,设备制造商可以默认使用 HEVC 而不是 AVC,以在提高视频质量的同时降低存储和带宽要求。对于启用了兼容媒体转码功能的设备,当不支持该格式的应用打开以 HEVC 或 HDR 等格式录制的视频(长度不超过一分钟)时,Android 可以自动转换这些视频。这使得应用即使在设备上以较新格式捕获视频时也能正常运行。
默认情况下,兼容媒体转码功能处于关闭状态。要请求媒体转码,应用必须声明其媒体功能。如需详细了解如何声明媒体功能,请参阅 Android 开发者网站上的兼容媒体转码。
工作原理
兼容媒体转码功能由两个主要部分组成
- 媒体框架中的转码服务:这些服务使用硬件将文件从一种格式转换为另一种格式,以实现低延迟和高质量的转换。其中包括转码 API、转码服务、用于自定义过滤器的 OEM 插件和硬件。如需了解更多详情,请参阅架构概览。
- 媒体提供程序中的兼容媒体转码功能:媒体提供程序中的此组件会拦截应用访问媒体文件,并根据应用声明的功能提供原始文件或转码文件。如果应用支持媒体文件的格式,则无需特殊处理。如果应用不支持该格式,则框架会在应用访问该文件时将文件转换为旧格式(例如 AVC)。
图 1 显示了媒体转码过程的概述。
图 1. 兼容媒体转码概述。
支持的格式
兼容媒体转码功能支持以下格式转换
- HEVC (8 位) 到 AVC: 编解码器转换通过连接一个 mediacodec 解码器和一个 mediacode 编码器来执行。
- HDR10+ (10 位) 到 AVC (SDR): HDR 到 SDR 转换使用 mediacodec 实例和供应商插件挂钩到解码器实例中来执行。有关详细信息,请参阅 HDR 到 SDR 编码。
支持的内容来源
兼容媒体转码功能支持由原生 OEM 相机应用生成的设备端媒体,这些媒体存储在主外部卷的 DCIM/Camera/
文件夹中。此功能不支持辅助存储设备上的媒体。通过电子邮件或 SD 卡传递到设备的内容不受支持。
应用会根据各种文件路径访问文件。以下描述了启用或绕过转码的文件路径
已启用转码
- 通过 MediaStore API 进行应用访问
- 通过直接文件路径 API(包括 Java 和原生代码)进行应用访问
- 通过存储访问框架 (SAF) 进行应用访问
- 通过操作系统共享表单 Intent 进行应用访问。(仅限 MediaStore URI)
- 从手机到 PC 的 MTP/PTP 文件传输
已绕过转码
- 通过弹出 SD 卡将文件从设备中移出
- 使用 Nearby Share 或蓝牙传输等选项在设备之间传输文件。
添加自定义文件路径以进行转码
设备制造商可以选择在 DCIM/
目录下添加媒体转码的文件路径。任何 DCIM/
目录之外的路径都将被拒绝。可能需要添加此类文件路径以满足运营商要求或当地法规。
要添加文件路径,请使用转码路径 运行时资源叠加 (RRO),config_supported_transcoding_relative_paths
。以下是如何添加文件路径的示例
<string-array name="config_supported_transcoding_relative_paths" translatable="false">
<item>DCIM/JCF/</item>
</string-array>
要验证配置的文件路径,请使用
adb shell dumpsys activity provider com.google.android.providers.media.module/com.android.providers.media.MediaProvider | head -n 20
架构概述
本节介绍媒体转码功能的架构。
图 2. 媒体转码架构。
媒体转码架构由以下组件组成
- MediaTranscodingManager 系统 API: 允许客户端与 MediaTranscoding 服务通信的接口。MediaProvider 模块使用此 API。
- MediaTranscodingService: 原生服务,用于管理客户端连接、调度转码请求以及管理
TranscodingSessions
的簿记。 - MediaTranscoder: 执行转码的原生库。此库构建于媒体框架 NDK 之上,以便与 模块 兼容。
兼容媒体转码功能会在服务和媒体转码器中记录转码指标。客户端和服务端代码位于 MediaProvider 模块中,以便及时修复错误和更新。
文件访问
兼容媒体转码构建于 用户空间文件系统 (FUSE) 文件系统之上,该文件系统用于作用域存储。FUSE 使 MediaProvider 模块能够检查用户空间中的文件操作,并根据策略控制对文件的访问,以允许、拒绝或编辑访问。
当应用尝试访问文件时,FUSE 守护程序会拦截来自应用的文件读取访问。如果应用支持较新的格式(例如 HEVC),则返回原始文件。如果应用不支持该格式,则将文件转码为较旧的格式(例如 AVC),或者如果转码版本可用,则从缓存返回。
请求转码文件
默认情况下,兼容媒体转码功能处于禁用状态,这意味着如果设备支持 HEVC,除非应用在清单文件或 强制转码列表 中指定,否则 Android 不会转码文件。
应用可以使用以下选项请求转码后的资源
- 在清单文件中声明不支持的格式。有关详细信息,请参阅 在资源中声明功能 和 在代码中声明功能。
- 将应用添加到 强制转码列表,该列表包含在 MediaProvider 模块中。这为尚未更新其清单文件的应用启用转码。一旦应用使用不支持的格式更新其清单文件,就必须从强制转码列表中删除该应用。设备制造商可以提名其应用添加到强制转码列表或从中删除,方法是提交补丁或报告错误。Android 团队会定期审核该列表,并可能从列表中删除应用。
- 在运行时使用应用兼容性框架禁用支持的格式(用户也可以在“设置”中为每个应用禁用此功能)。
- 使用
MediaStore
打开文件,同时使用openTypedAssetFileDescriptor
API 显式指定不支持的格式。
对于 USB 传输(设备到 PC),默认情况下禁用转码,但用户可以选择在USB 偏好设置设置屏幕中启用将视频转换为 AVC 开关来启用转码,如图 3 所示。
图 3. 在 USB 偏好设置屏幕中切换以启用媒体转码。
请求转码文件的限制
为防止转码请求长时间占用系统资源,请求转码会话的应用受到以下限制
- 10 个连续会话
- 总运行时间为三分钟
如果应用超出所有这些限制,框架将返回原始文件描述符。
设备要求
要支持兼容媒体转码功能,设备必须满足以下要求
- 设备的原生相机应用默认启用 HEVC 编码
- (支持 HDR 到 SDR 转码的设备)设备支持 HDR 视频拍摄
为确保媒体转码的设备性能,必须优化视频硬件和存储读/写访问性能。当媒体编解码器配置的优先级等于 1
时,编解码器必须以尽可能高的吞吐量运行。我们建议转码性能至少达到 200 fps。要测试您的硬件性能,请在 frameworks/av/media/libmediatranscoding/transcoder/benchmark
运行媒体转码器基准测试。
验证
要验证兼容媒体转码功能,请运行以下 CTS 测试
android.media.mediatranscoding.cts
android.mediaprovidertranscode.cts
全局启用媒体转码
要测试媒体转码框架或应用的转码行为,您可以全局启用或禁用兼容媒体转码功能。在设置 > 系统 > 开发者选项 > 媒体转码开发者选项页面中,将覆盖转码默认值开关设置为开启,然后将启用转码开关设置为开启或关闭。如果启用此设置,媒体转码可能会在后台为除您正在开发的应用之外的其他应用发生。
检查转码状态
在测试期间,您可以使用以下 ADB shell 命令来检查转码状态,包括当前和过去的转码会话
adb shell dumpsys media.transcoding
延长视频时长限制
出于测试目的,您可以使用以下命令延长转码的 1 分钟视频时长限制。运行此命令后可能需要重启。
adb shell device_config put storage_native_boot transcode_max_duration_ms <LARGE_NUMBER_IN_MS>
AOSP 源代码和参考资料
以下是与兼容媒体转码相关的 AOSP 源代码。
转码系统 API(仅供 MediaProvider 使用)
ApplicationMediaCapabilities API
frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
MediaTranscoding 服务
frameworks/av/services/mediatranscoding/
frameworks/av/media/libmediatranscoding/
原生 MediaTranscoder
frameworks/av/media/libmediatranscoding/transcoder
MediaTranscoder 的 HDR 示例插件
MediaProvider 文件拦截和转码代码
MediaTranscoder 基准测试
frameworks/av/media/libmediatranscoding/transcoder/benchmark
CTS 测试
cts/tests/tests/mediatranscoding/
HDR 到 SDR 编码
为了支持 HDR 到 SDR 编码,设备制造商可以使用 AOSP 示例 Codec 2.0 过滤器插件,该插件位于 /platform/frameworks/av/media/codec2/hidl/plugin/
。本节介绍过滤器插件的工作原理、如何实现插件以及如何测试插件。
如果设备不包含支持 HDR 到 SDR 编码的插件,则访问 HDR 视频的应用将获得原始文件描述符,而不管应用在清单中声明的媒体功能如何。
工作原理
本节介绍 Codec 2.0 过滤器插件的一般行为。
背景
Android 提供了 Codec 2.0 接口和 android.hardware.media.c2
HAL 接口之间在 android::hardware::media::c2
的适配层实现。对于过滤器插件,AOSP 包括一个包装机制,该机制将解码器与过滤器插件包装在一起。MediaCodec
将这些包装组件识别为具有过滤功能的解码器。
概览
FilterWrapper
类接受供应商编解码器,并将包装的编解码器返回到 media.c2
适配层。FilterWrapper
类通过 FilterWrapper::Plugin
API 加载 libc2filterplugin.so
,并从插件记录可用的过滤器。在创建时,FilterWrapper
实例化所有可用的过滤器。只有更改缓冲区的过滤器才会在启动时启动。
图 4. 过滤器插件架构。
过滤器插件接口
FilterPlugin.h
接口定义了以下 API 以公开过滤器
std::shared_ptr<C2ComponentStore>getComponentStore()
返回一个
C2ComponentStore
对象,其中包含过滤器。这与供应商的 Codec 2.0 实现公开的内容不同。通常,此存储仅包含FilterWrapper
类使用的过滤器。bool describe(C2String name, Descriptor *desc)
除了
C2ComponentStore
中可用的内容外,还描述了过滤器。定义了以下描述controlParam
:控制过滤器行为的参数。例如,对于 HDR 到 SDR 色调映射器,控制参数是目标传递函数。affectedParams
:受过滤操作影响的参数。例如,对于 HDR 到 SDR 色调映射器,受影响的参数是色彩方面。
bool isFilteringEnabled(const std::shared_ptr<C2ComponentInterface> &intf)
如果过滤器组件更改缓冲区,则返回
true
。例如,如果目标传递函数为 SDR 且输入传递函数为 HDR(HLG 或 PQ),则色调映射过滤器返回true
。
FilterWrapper 详细信息
本节介绍 FilterWrapper
类的详细信息。
创建
包装的组件在创建时实例化底层解码器和所有定义的过滤器。
查询和配置
包装的组件根据过滤器描述将传入参数与查询或配置请求分开。例如,过滤器控制参数的配置路由到相应的过滤器,并且来自过滤器的受影响参数存在于查询中(而不是从具有未受影响参数的解码器读取)。
图 5. 查询和配置。
开始
在启动时,包装的组件启动解码器和所有更改缓冲区的已启用过滤器。如果未启用过滤器,则包装的组件启动解码器并直通缓冲区,并将命令发送到解码器本身。
缓冲区处理
图 6. 缓冲区处理。
排队到包装解码器的缓冲区将进入底层解码器。包装的组件通过 onWorkDone_nb()
回调从解码器获取输出缓冲区,然后将其排队到过滤器。来自最后一个过滤器的最终输出缓冲区将报告给客户端。
为了使此缓冲区处理工作,包装的组件必须将 C2PortBlockPoolsTuning
配置为最后一个过滤器,以便框架输出来自预期块池的缓冲区。
停止、重置和释放
在停止时,包装的组件会停止解码器和所有已启动的已启用过滤器。在重置和释放时,所有组件都会被重置或释放,无论它们是否已启用。
实现示例过滤器插件
要启用插件,请执行以下操作
- 在库中实现
FilterPlugin
接口,并将其放在/vendor/lib[64]/libc2filterplugin.so.
。 - 如果需要,将其他权限添加到
mediacodec.te
。 - 将适配层更新到 Android 12 并重建
media.c2
服务。
测试插件
要测试示例插件,请执行以下操作
- 重建并刷写设备。
使用以下命令构建示例插件
m sample-codec2-filter-plugin
重新挂载设备并重命名供应商插件,以便编解码器服务可以识别它。
adb root adb remount adb reboot adb wait-for-device adb root adb remount adb push /out/target/<...>/lib64/sample-codec2-filter-plugin.so \ /vendor/lib64/libc2filterplugin.so adb push /out/target/<...>/lib/sample-codec2-filter-plugin.so \ /vendor/lib/libc2filterplugin.so adb reboot