Android 扩展

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

有关 Android 扩展的 API 定义,请参见 OpenSLES_Android.h 及其包含的头文件。请查阅 OpenSLES_Android.h 了解这些扩展的详情。此文件位于安装根目录下的 sysroot/usr/include/SLES 目录中。除非另有说明,否则所有接口都是显式接口。

这些扩展是 Android 专有的扩展,会限制您的应用移植到其他 OpenSL ES 实现的可移植性。要减轻此问题,您可以避免使用这些扩展,或者使用 #ifdef 在编译时将其排除。

下表显示 Android OpenSL ES 针对每种对象类型支持的 Android 特定接口和数据定位器。如果单元格中的值为“是”,则说明相应的接口和数据定位器可用于各对象类型。

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

Android 配置接口

Android 配置接口提供一种为对象设置平台专用参数的方式。此接口与其他 OpenSL ES 1.0.1 接口的不同之处在于,您的应用可以先使用此接口再将相应对象实例化;因此,您可以先配置对象,再将其实例化。位于 /sysroot/usr/include/SLES 中的 OpenSLES_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 缓冲区队列。

缓冲区队列行为

参考规范要求在回放进入 SL_PLAYSTATE_STOPPED 状态时,播放光标需返回到当前正在播放的缓冲区的开头,而 Android 实现不包括这一要求。此实现可以符合该行为要求,也可以保持播放光标的位置不变。因此,您的应用不能假设发生任何一种行为。所以,您应在过渡到 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 及以上 <media/NdkMedia*.h> 头文件中的 NDK MediaCodec、MediaCodec 类或具有合适许可的开源编解码器

注意:目前没有 NDK 版本的 MediaCodec API 的文档记录。不过,您可以参考 native-codec 示例代码。

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

此功能主要用于让游戏在切换到新关卡时预加载其音频资源,这一功能与 SoundPool 类提供的功能类似。

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

数据源通过在流结束时交付 SL_PLAYEVENT_HEADATEND 事件隐式报告流结尾 (EOS)。将接收到的所有数据解码后,应用便不再对 Android 简单缓冲区队列回调进行更多调用。

在采样率、频道计数和位深方面,接收器的 PCM 数据格式一般与编码数据源匹配。不过,您可以解码为不同的采样率、频道计数或位深。如需了解有关用于检测实际 PCM 格式的预配的信息,请参阅通过元数据确定已解码 PCM 数据的格式

OpenSL ES for Android 的 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 简单缓冲区队列并不相同。流式解码器使用两种缓冲区队列:Android 缓冲区队列用于 ADTS AAC 数据源,Android 简单缓冲区队列用于 PCM 数据接收器。如需了解有关 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)及更高版本上运行的应用可以单精度浮点格式向音频播放器提供数据。

在下面的示例代码中,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;
请在“音频采样”页面上阅读浮点音频的更多相关内容。