Android 扩展

警告:OpenSL ES 已弃用。开发者应使用可在 GitHub 上获取的开源 Oboe 库。Oboe 是一个 C++ 封装器,提供了一个与 AAudio 极为相似的 API。如果 AAudio 可用,Oboe 会调用 AAudio;如果 AAudio 不可用,则会回退到 OpenSL ES。

适用于 Android 的 OpenSL ES 扩展了 OpenSL ES 参考规范,使其与 Android 兼容,并利用了 Android 平台的强大功能和灵活性。

Android 扩展的 API 定义位于 OpenSLES_Android.h 及其包含的头文件中。请参阅 OpenSLES_Android.h,了解有关这些扩展的详细信息。该文件位于安装根目录下的 sysroot/usr/include/SLES 目录中。除非另有说明,所有接口均为显式接口。

这些扩展限制了您的应用对其他 OpenSL ES 实现的可移植性,因为它们是 Android 特有的。您可以通过避免使用扩展或使用 #ifdef 在编译时排除它们来缓解此问题。

下表显示了 Android OpenSL ES 支持的每种对象类型的 Android 特定接口和数据定位符。单元格中的“是”表示每种对象类型可用的接口和数据定位符。

功能 音频播放器 录音机 引擎 输出混音器
Android 缓冲区队列 是:源(解码)
Android 配置
Android 效果
Android 效果功能
Android 效果发送
Android 简单缓冲区队列 是:源(播放)或接收器(解码)
Android 缓冲区队列数据定位符 是:源(解码)
Android 文件描述符数据定位符 是:源
Android 简单缓冲区队列数据定位符 是:源(播放)或接收器(解码) 是:接收器

Android 配置接口

Android 配置接口提供了一种方法来为对象设置平台特定参数。此接口与其他 OpenSL ES 1.0.1 接口不同之处在于,您的应用可以在实例化相应对象之前使用它;因此,您可以在实例化对象之前对其进行配置。OpenSLES_AndroidConfiguration.h 头文件(位于 /sysroot/usr/include/SLES)文档记录了以下可用的配置键和值

  • 音频播放器的流类型(默认值为 SL_ANDROID_STREAM_MEDIA)。
  • 录音机的录制预设(默认值为 SL_ANDROID_RECORDING_PRESET_GENERIC)。

以下代码段显示了如何在音频播放器上设置 Android 音频流类型的示例

// CreateAudioPlayer and specify SL_IID_ANDROIDCONFIGURATION
// in the required interface ID array. Do not realize player yet.
// ...
SLAndroidConfigurationItf playerConfig;
result = (*playerObject)->GetInterface(playerObject,
    SL_IID_ANDROIDCONFIGURATION, &playerConfig);
assert(SL_RESULT_SUCCESS == result);
SLint32 streamType = SL_ANDROID_STREAM_ALARM;
result = (*playerConfig)->SetConfiguration(playerConfig,
    SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
assert(SL_RESULT_SUCCESS == result);
// ...
// Now realize the player here.

您可以使用类似的代码配置录音机的预设

// ... obtain the configuration interface as the first four lines above, then:
SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
result = (*playerConfig)->SetConfiguration(playerConfig,
    RECORDING_PRESET, &presetValue, sizeof(SLuint32));

Android 效果接口

Android 的效果、效果发送和效果功能接口提供了一种通用机制,供应用查询和使用设备特定的音频效果。设备制造商应记录其提供的任何可用的设备特定音频效果。

可移植应用应使用 OpenSL ES 1.0.1 API 来实现音频效果,而不是使用 Android 效果扩展。

Android 文件描述符数据定位符

Android 文件描述符数据定位符允许您将音频播放器的源指定为具有读取权限的开放文件描述符。数据格式必须为 MIME。

此扩展与原生资源管理器结合使用时特别有用,因为应用会通过文件描述符从 APK 读取资源。

Android 简单缓冲区队列数据定位符和接口

在 OpenSL ES 1.0.1 参考规范中,缓冲区队列只能用于音频播放器,并且它们与 PCM 及其他数据格式兼容。Android 简单缓冲区队列数据定位符和接口规范与参考规范相同,但有两点例外

  • 您可以将 Android 简单缓冲区队列与录音机和音频播放器结合使用。
  • 您只能将 PCM 数据格式与这些队列结合使用。

对于录音,您的应用应将空缓冲区入队。当注册的回调通知系统已完成向缓冲区写入数据时,应用可以从该缓冲区读取数据。

播放方式相同。但是,为了未来源代码的兼容性,我们建议应用使用 Android 简单缓冲区队列而不是 OpenSL ES 1.0.1 缓冲区队列。

缓冲区队列行为

Android 实现不包含参考规范的要求,即当播放进入 SL_PLAYSTATE_STOPPED 状态时,播放光标返回到当前播放缓冲区的开头。此实现可以符合该行为,也可以保持播放光标的位置不变。因此,您的应用不能假设会发生任一行为。因此,您应在转换为 SL_PLAYSTATE_STOPPED 后显式调用 BufferQueue::Clear() 方法。这样做会将缓冲区队列设置为已知状态。

同样,没有规范规定缓冲区队列回调的触发器必须是转换为 SL_PLAYSTATE_STOPPED 或执行 BufferQueue::Clear()。因此,我们建议您不要依赖于其中一个;相反,您的应用应能够处理两者。

对象创建时的动态接口

为方便起见,OpenSL ES 1.0.1 的 Android 实现允许您的应用在实例化对象时指定动态接口。这是在实例化后使用 DynamicInterfaceManagement::AddInterface() 添加这些接口的替代方法。

扩展报告

有三种方法可以查询平台是否支持 Android 扩展。这些方法是

  • Engine::QueryNumSupportedExtensions()
  • Engine::QuerySupportedExtension()
  • Engine::IsExtensionSupported()

其中任一方法都会返回 ANDROID_SDK_LEVEL_<API-level>,其中 API-level 是平台 API 级别;例如,ANDROID_SDK_LEVEL_23。平台 API 级别为 9 或更高表示平台支持这些扩展。

将音频解码为 PCM

本节介绍了一个已弃用的 Android 特定 OpenSL ES 1.0.1 扩展,用于将编码流解码为 PCM 而不立即播放。下表给出了使用此扩展和替代方法的建议。

API 级别 替代方法
15 及以下 具有适当许可的开源编解码器
16 到 20 MediaCodec 类或具有适当许可的开源编解码器
21 及以上 <media/NdkMedia*.h> 头文件中的 NDK MediaCodec、MediaCodec 类或具有适当许可的开源编解码器

注意:目前没有针对 MediaCodec API 的 NDK 版本的文档。但是,您可以参考 native-codec 示例代码获取示例。

标准的音频播放器播放到音频设备,将输出混音指定为数据接收器。Android 扩展的不同之处在于,如果应用将数据源指定为 URI 或使用 MIME 数据格式描述的 Android 文件描述符数据定位符,则音频播放器充当解码器。在这种情况下,数据接收器是使用 PCM 数据格式的 Android 简单缓冲区队列数据定位符。

此功能主要用于游戏在更改游戏级别时预加载音频资源,类似于 SoundPool 类提供的功能。

应用应首先在 Android 简单缓冲区队列中入队一组空缓冲区。之后,应用会用 PCM 数据填充缓冲区。在每个缓冲区填满后,Android 简单缓冲区队列回调就会触发。回调处理程序会处理 PCM 数据,重新将现在为空的缓冲区入队,然后返回。应用负责跟踪已解码的缓冲区;回调参数列表不包含足够的信息来指示哪个缓冲区包含数据或下一个应入队的缓冲区。

数据源通过在流结束时发送 SL_PLAYEVENT_HEADATEND 事件来隐式报告流结束 (EOS)。在应用解码完收到的所有数据后,它不会再调用 Android 简单缓冲区队列回调。

接收器的 PCM 数据格式通常与编码数据源的采样率、通道数和位深度匹配。但是,您可以解码到不同的采样率、通道数或位深度。有关检测实际 PCM 格式的规定,请参阅 通过元数据确定已解码 PCM 数据的格式

适用于 Android 的 OpenSL ES 的 PCM 解码功能支持暂停和初始查找;它不支持音量控制、效果、循环或播放速率。

根据平台实现的不同,解码可能需要无法闲置的资源。因此,我们建议您确保提供足够数量的空 PCM 缓冲区;否则,解码器会饥饿。例如,如果您的应用从 Android 简单缓冲区队列回调返回而未入队另一个空缓冲区,就可能发生这种情况。解码器饥饿的结果未指定,但可能包括:丢弃已解码的 PCM 数据、暂停解码过程或完全终止解码器。

注意:要将编码流解码为 PCM 但不立即播放,对于在 Android 4.x(API 级别 16-20)上运行的应用,我们建议使用 MediaCodec 类。对于在 Android 5.0(API 级别 21)或更高版本上运行的新应用,我们建议使用等效的 NDK,即 <NdkMedia*.h>。这些头文件位于安装根目录下的 media/ 目录中。

将流式 ADTS AAC 解码为 PCM

如果数据源是使用 MIME 数据格式的 Android 缓冲区队列数据定位符,并且数据接收器是使用 PCM 数据格式的 Android 简单缓冲区队列数据定位符,则音频播放器充当流式解码器。按如下方式配置 MIME 数据格式

  • 容器:SL_CONTAINERTYPE_RAW
  • MIME 类型字符串:SL_ANDROID_MIME_AACADTS

此功能主要用于处理 AAC 音频但需要在播放前执行自定义音频处理的流媒体应用。大多数需要将音频解码为 PCM 的应用应使用将音频解码为 PCM 描述的方法,因为该方法更简单,并且处理更多音频格式。此处描述的技术是一种更专业的方法,仅在满足以下两个条件时使用

  • 压缩音频源是包含在 ADTS 头中的 AAC 帧流。
  • 应用管理此流。数据位于标识符为 URI 的网络资源中或标识符为文件描述符的本地文件中。

应用应首先在 Android 缓冲区队列中入队一组已填充的缓冲区。每个缓冲区包含一个或多个完整的 ADTS AAC 帧。在每个缓冲区排空后,Android 缓冲区队列回调就会触发。回调处理程序应重新填充和重新入队缓冲区,然后返回。应用无需跟踪已编码的缓冲区;回调参数列表包含足够的信息来指示下一个应入队的缓冲区。流结束 (EOS) 通过入队一个 EOS 项来明确标记。在 EOS 之后,不再允许进行入队操作。

我们建议您确保提供完整的 ADTS AAC 缓冲区,以避免解码器饥饿。例如,如果您的应用从 Android 缓冲区队列回调返回而未入队另一个完整的缓冲区,就可能发生这种情况。解码器饥饿的结果未指定。

除了数据源之外,流式解码方法在所有方面都与将音频解码为 PCM 描述的方法相同。

尽管名称相似,但 Android 缓冲区队列与 Android 简单缓冲区队列不同。流式解码器使用这两种缓冲区队列:用于 ADTS AAC 数据源的 Android 缓冲区队列,以及用于 PCM 数据接收器的 Android 简单缓冲区队列。有关 Android 简单缓冲区队列 API 的更多信息,请参阅Android 简单缓冲区队列数据定位符和接口。有关 Android 缓冲区队列 API 的更多信息,请参阅安装根目录下 docs/Additional_library_docs/openmaxal/ 目录中的 index.html 文件。

通过元数据确定已解码 PCM 数据的格式

SLMetadataExtractionItf 接口是参考规范的一部分。但是,指示已解码 PCM 数据实际格式的元数据键是 Android 特有的。OpenSLES_AndroidMetadata.h 头文件定义了这些元数据键。该头文件位于安装根目录下的 /sysroot/usr/include/SLES 目录中。

元数据键索引在 Object::Realize() 方法执行完成后立即可用。但是,直到应用解码第一个编码数据后,关联的值才可用。一种好的做法是在调用 Object::Realize 方法后,在主线程中查询键索引,并在首次调用 Android 简单缓冲区队列回调处理程序时读取 PCM 格式元数据值。有关使用此接口的示例,请参阅 NDK 软件包中的示例代码

元数据键名称是稳定的,但键索引未记录,可能会发生变化。应用不应假定索引在不同的执行运行中是持久的,也不应假定同一运行中的多个对象实例共享索引。

浮点数据

在 Android 5.0(API 级别 21)及更高版本上运行的应用可以向 AudioPlayer 提供单精度浮点格式的数据。

在以下示例代码中,Engine::CreateAudioPlayer() 方法创建了一个使用浮点数据的音频播放器

#include <SLES/OpenSLES_Android.h>
...
SLAndroidDataFormat_PCM_EX pcm;
pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
pcm.numChannels = 2;
pcm.sampleRate = SL_SAMPLINGRATE_44_1;
pcm.bitsPerSample = 32;
pcm.containerSize = 32;
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
pcm.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
...
SLDataSource audiosrc;
audiosrc.pLocator = ...
audiosrc.pFormat = &pcm;
在音频采样页面上详细了解浮点音频