Android 扩展

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

Android 扩展的 API 定义位于其包含的 OpenSLES_Android.h 和头文件中。 请查阅 OpenSLES_Android.h 了解这些扩展的详情。 此文件位于安装根目录下的 platforms/android-version/abi/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 接口的不同之处在于,您可以在将相应对象实例化之前使用此接口,因此,您可以先配置对象,然后再实例化对象。 位于 platforms/android-<version>/<abi>/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 数据的格式

面向 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 头文件定义这些元数据键。此头文件位于安装根目录下的 platforms/android-<version>/<abi>/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;
在“采样音频”页面上阅读浮点音频的更多相关内容。