Android 扩展

适用于 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 接口不同,因为您的应用程序可以在实例化相应对象之前使用它;因此,您可以在实例化对象之前对其进行配置。位于 /sysroot/usr/include/SLESOpenSLES_AndroidConfiguration.h 头文件记录了以下可用的配置键和值

  • 音频播放器的流类型(默认值为 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

本节介绍 OpenSL ES 1.0.1 的一项弃用 Android 特定扩展,用于将编码流解码为 PCM,而无需立即播放。下表提供了有关使用此扩展和替代方案的建议。

API 级别 替代方案
15 及以下 具有合适许可证的开源编解码器
16 到 20 MediaCodec 类或具有合适许可证的开源编解码器
21 及以上 NDK MediaCodec 在 <media/NdkMedia*.h> 头文件中,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 之后,不允许再进行排队。

我们建议您确保提供完整的 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;
在采样音频页面上阅读有关 浮点音频 的更多信息。