兼容媒体转码

Android 12 中引入的兼容媒体转码是一项功能,允许设备对视频拍摄使用更先进、存储效率更高的媒体格式(例如 HEVC),同时保持与应用的兼容性。借助此功能,设备制造商可以默认使用 HEVC 而不是 AVC,以在提高视频质量的同时降低存储和带宽要求。对于启用了兼容媒体转码功能的设备,当不支持该格式的应用打开以 HEVC 或 HDR 等格式录制的视频(长度不超过一分钟)时,Android 可以自动转换这些视频。这使得应用即使在设备上以较新格式捕获视频时也能正常运行。

默认情况下,兼容媒体转码功能处于关闭状态。要请求媒体转码,应用必须声明其媒体功能。如需详细了解如何声明媒体功能,请参阅 Android 开发者网站上的兼容媒体转码

工作原理

兼容媒体转码功能由两个主要部分组成

  • 媒体框架中的转码服务:这些服务使用硬件将文件从一种格式转换为另一种格式,以实现低延迟和高质量的转换。其中包括转码 API、转码服务、用于自定义过滤器的 OEM 插件和硬件。如需了解更多详情,请参阅架构概览
  • 媒体提供程序中的兼容媒体转码功能:媒体提供程序中的此组件会拦截应用访问媒体文件,并根据应用声明的功能提供原始文件或转码文件。如果应用支持媒体文件的格式,则无需特殊处理。如果应用不支持该格式,则框架会在应用访问该文件时将文件转换为旧格式(例如 AVC)。

图 1 显示了媒体转码过程的概述。

Compatible media transcoding process

图 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

架构概述

本节介绍媒体转码功能的架构。

media-transcoding-architecture

图 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 所示。

Toggle to enable media transcoding

图 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 源代码。

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 实例化所有可用的过滤器。只有更改缓冲区的过滤器才会在启动时启动。

Filter plugin architecture

图 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 类的详细信息。

创建

包装的组件在创建时实例化底层解码器和所有定义的过滤器。

查询和配置

包装的组件根据过滤器描述将传入参数与查询或配置请求分开。例如,过滤器控制参数的配置路由到相应的过滤器,并且来自过滤器的受影响参数存在于查询中(而不是从具有未受影响参数的解码器读取)。

Query and configuration

图 5. 查询和配置。

开始

在启动时,包装的组件启动解码器和所有更改缓冲区的已启用过滤器。如果未启用过滤器,则包装的组件启动解码器并直通缓冲区,并将命令发送到解码器本身。

缓冲区处理

Buffer handling

图 6. 缓冲区处理。

排队到包装解码器的缓冲区将进入底层解码器。包装的组件通过 onWorkDone_nb() 回调从解码器获取输出缓冲区,然后将其排队到过滤器。来自最后一个过滤器的最终输出缓冲区将报告给客户端。

为了使此缓冲区处理工作,包装的组件必须将 C2PortBlockPoolsTuning 配置为最后一个过滤器,以便框架输出来自预期块池的缓冲区。

停止、重置和释放

在停止时,包装的组件会停止解码器和所有已启动的已启用过滤器。在重置和释放时,所有组件都会被重置或释放,无论它们是否已启用。

实现示例过滤器插件

要启用插件,请执行以下操作

  1. 在库中实现 FilterPlugin 接口,并将其放在 /vendor/lib[64]/libc2filterplugin.so.
  2. 如果需要,将其他权限添加到 mediacodec.te
  3. 将适配层更新到 Android 12 并重建 media.c2 服务。

测试插件

要测试示例插件,请执行以下操作

  1. 重建并刷写设备。
  2. 使用以下命令构建示例插件

    m sample-codec2-filter-plugin
    
  3. 重新挂载设备并重命名供应商插件,以便编解码器服务可以识别它。

    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