适用于Android的OpenSL ES指南-OpenSL ES

2018-11-09  本文已影响89人  月下溪明

翻译自Android Extensions

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

Android扩展的API定义在OpenSLES_Android.h和它包含的头文件中。查阅OpenSLES_Android.h了解这些扩展的详细信息。这个文件位于您的安装根目录下,在sysroot/usr/include/SLES目录下。除非另有说明,所有接口都是显式的。

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

下表显示了Android特定的接口和数据定位器及Android OpenSL ES支持的每种对象类型。单元格中的Yes值表示对象类型都可用的接口和数据定位器data locators。

Feature Audio player Audio recorder Engine Output mix
Android buffer queue Yes: Source (decode) No No No
Android configuration Yes Yes No No
Android effect Yes No No Yes
Android effect capabilities No No Yes No
Android effect send Yes No No No
Android simple buffer queue Yes: Source (playback) or sink (decode) Yes No No
Android buffer queue data locator Yes: Source (decode) No No No
Android file descriptor data locator Yes: Source No No No
Android simple buffer queue data locator Yes: Source (playback) or sink (decode) Yes: Sink No No

Android配置接口

Android配置界面提供了一种为对象设置特定于平台的参数的方法。该接口不同于其他OpenSL ES 1.0.1接口,因为您的应用程序可以在实例化相应对象之前使用它;而且,您可以在实例化对象之前配置它。OpenSLES_AndroidConfiguration.h头文件,驻留在/sysroot/usr/include/SLES中,记录了以下可用的配置键值:

下面的代码片段展示了如何在音频播放器上设置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 effects interfaces 效果接口

Android的效果、效果发送和效果功能接口为应用程序查询和使用特定于设备的音频效果提供了通用机制。设备制造商应该记录他们提供的任一设备特定的音频效果。

便携应用应该使用OpenSL ES 1.0.1 api来实现音频效果,而不是Android效果扩展。

Android file descriptor data locator 文件描述符数据定位器

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

这个扩展与native asset manager结合使用特别有用,因为应用程序通过文件描述符从APK读取assets。

Android simple buffer queue data locator and interface 简单的缓冲队列数据定位器和接口

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

为了记录,您的应用程序应该排队空缓冲区。当一个注册回调发送一个通知,系统已经写完数据到缓冲区,应用程序可以从缓冲区读取。

回放以同样的方式工作。但是,为了将来的源代码兼容性,我们建议应用程序使用Android简单缓冲区队列,而不是OpenSL ES 1.0.1缓冲区队列。

buffer queue缓冲队列行为

Android实现不包括引用规范的要求,即当回放进入SL_PLAYSTATE_STOPPED状态时,播放cursor返回到当前播放缓冲区的开始位置。该实现可以顺应该行为,也可以保持播放cursor的位置不变。因此,您的应用程序不能假定这两种行为都发生了。因此,在转换到SL_PLAYSTATE_STOPPED之后,应该显式调用BufferQueue::Clear()方法。这样做将缓冲区队列设置为已知状态。

类似地,缓冲区队列回调的触发器是否必须转换为SL_PLAYSTATE_STOPPED或执行BufferQueue::Clear(),也没有规范来控制。因此,我们建议您不要对其中一个创建依赖关系;相反,你的应用程序应该能够同时处理这两种情况。

对象创建时的动态接口

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

扩展报告
有三种方法可以查询平台是否支持Android扩展。如下:

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

解码音频为PCM格式

本节描述了OpenSL ES 1.0.1中一个不赞成使用的android专用扩展,用于将编码流解码到PCM,而无需立即回放。下表给出了使用此扩展和替代方案的建议。

API level Alternatives替代方案
15 及以下 一种具有适当许可证的开源编解码器
16 到 20 MediaCodec类或具有适当许可证的开源编解码器
21 以上 NDK MediaCodec在<media/NdkMedia*.>头文件,MediaCodec类,或具有适当许可证的开源编解码器

注意:目前没有关于MediaCodec API的NDK版本的文档。但是,您可以参考natvie-codec示例代码。

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

这个特性主要用于游戏在转换到新的游戏级别时预加载它们的音频assets,这与SoundPool类提供的功能类似。

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

数据源通过在流的末尾传递SL_PLAYEVENT_HEADATEND事件隐式地报告流的结束(EOS)。当应用程序解码了它接收到的所有数据后,它就不再调用Android简单缓冲队列回调。

数据槽sink的PCM数据格式通常与编码的数据源在采样率、声道数和位深度方面匹配。但是,您可以解码到不同的采样率、声道数或位深度。有关检测实际PCM格式的规定的信息,请看下面的通过元数据确定解码PCM数据的格式部分

OpenSL ES为Android的PCM解码功能,支持暂停和初始搜索;它不支持音量控制、效果、循环或播放速率。

根据平台实现的不同,解码可能需要不能闲置的资源。因此,我们建议您确保提供足够数量的空PCM缓冲区;否则,解码器将挨饿。这可能发生,例如,如果您的应用程序从Android简单的缓冲区队列回调返回,而不排队另一个空缓冲区。解码器饿死的结果是未知的,但可能包括:删除解码PCM数据,暂停解码过程,或彻底终止解码器。

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

解码流式ADTS AAC转为PCM

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

该特性主要用于流媒体应用程序,这些应用程序处理AAC音频,但需要在回放之前执行定制的音频处理。大多数需要将音频解码到PCM的应用程序应该使用解码音频到PCM所描述的方法,因为该方法更简单,可以处理更多的音频格式。这里描述的技术是一种更专业的方法,只有在这两种条件都适用时才会使用:

应用程序最初应该在Android缓冲区队列中加入一组已填充的缓冲区。每个缓冲区包含一个或多个完整的ADTS AAC帧。每个缓冲区清空后,Android缓冲区队列回调会触发。回调处理程序应该重新填充缓冲区并重新排队,然后返回。应用程序不需要跟踪已编码的缓冲区;回调参数列表包含足够的信息来指示下一个应该加入队列的缓冲区。流的末尾通过对EOS项进行排队显式地标记。EOS后,不允许再排队。

我们建议您确保提供完整的ADTS AAC缓冲区,以避免使解码器挨饿。这可能发生,例如,如果您的应用程序从Android buffer queue回调返回,而不排队另一个完整的缓冲区。解码器饿死的结果是未知的。

除了数据源之外,流解码方法与解码音频到PCM的方法相同。

虽然名称相似,但Android缓冲队列与Android简单缓冲队列不同。流解码器使用两个缓冲队列:ADTS AAC数据源的Android缓冲队列和PCM数据槽的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 level 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;

在音频采样页上阅读关于浮点音频的更多信息。


下一篇: 适用于android的OpenSL ES指南-编程注意事项

上一篇下一篇

猜你喜欢

热点阅读