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

翻译自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中,记录了以下可用的配置键值:

  • 音频播放器的流类型(默认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 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简单缓冲队列。
  • 您只能对这些队列使用PCM数据格式。

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

回放以同样的方式工作。但是,为了将来的源代码兼容性,我们建议应用程序使用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扩展。如下:

  • 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 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数据格式如下:

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

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

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

应用程序最初应该在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指南-编程注意事项

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容