Android NDK开发详解高性能音频之OpenSL ES for Android


本页面详细介绍了 OpenSL ES™ 的 NDK 实现与 OpenSL ES 1.0.1 参考规范的不同之处。使用此规范的示例代码时,您可能需要修改代码,才能使其在 Android 系统中正常工作。

除非另有说明,否则所有功能均适用于 Android 2.3(API 级别 9)及更高版本。某些功能仅适用于 Android 4.0(API 级别 14);这些功能会有相关说明。

注意:Android 兼容性定义文档 (CDD) 列出了兼容 Android 设备的所有硬件和软件要求。请参阅 Android 兼容性详细了解整体兼容性计划,并参阅 CDD 以获取实际的 CDD 文档。

OpenSL ES 提供 C 语言接口,您也可以使用 C++ 访问该接口。此外,OpenSL ES 还提供类似于下列 Android Java API 音频部分的功能:

    android.media.MediaPlayer
    android.media.MediaRecorder

就像 Android 原生开发工具包 (NDK) 的所有组件一样,OpenSL ES for Android 的主要用途是协助实现可使用 Java 原生接口 (JNI) 进行调用的共享库。NDK 不适用于编写纯 C/C++ 应用。不过,OpenSL ES 是功能全面的 API,我们认为您仅使用此 API 就可以满足自己的大部分音频需求,而不用向上调用在 Android 运行时中运行的代码。

注意:尽管基于 OpenSL ES,但 Android 原生音频(高性能音频)API 不是任何 OpenSL ES 1.0.1 配置文件(游戏、音乐或电话)的合规实现。这是因为,Android 并未实现任何一个配置文件所需的全部功能。Android 扩展页面说明了所有已知 Android 行为与规范有所不同的情况。

从参考规范继承的功能

OpenSL ES 的 Android NDK 实现继承了参考规范功能集内的大部分功能,但有一定的限制。

全局入口点

OpenSL ES for Android 支持 Android 规范中的所有全局入口点。这些入口点包括:

    slCreateEngine
    slQueryNumSupportedEngineInterfaces
    slQuerySupportedEngineInterfaces

对象与接口

下表显示 OpenSL ES 的 Android NDK 实现所支持的对象与接口。如果单元格显示“是”,则说明此实现中提供相应功能。

Android NDK 支持的对象与接口。
在这里插入图片描述

下一部分介绍其中一些功能的限制。

限制

表 1 中的功能可能存在某些限制。这些限制代表与参考规范的差异。本部分的剩余内容将介绍这些差异。
动态接口管理

OpenSL ES for Android 不支持 RemoveInterface 或 ResumeInterface。
效果组合:环境混响和预设混响

无法在同一输出混合上同时拥有环境混响和预设混响。

如果平台估计 CPU 负载会过高,则可能会忽略效果请求。
效果送出

SetSendLevel() 支持每个音频播放器拥有一个发送电平。
环境混响

环境混响不支持 SLEnvironmentalReverbSettings 结构的 reflectionsDelay、reflectionsLevel 或 reverbDelay 字段。
MIME 数据格式

MIME 数据格式只能与 URI 数据定位器结合使用,并且只能用于音频播放器。此数据格式不可用于音频录制器。

OpenSL ES 的 Android 实现要求将 mimeType 初始化为 NULL 或有效的 UTF-8 字符串。您还必须将 containerType 初始化为有效值。如果不需要考虑其他注意事项(例如移植到其他实现的可移植性或者应用无法通过头文件确定的内容格式),我们建议您将 mimeType 设置为 NULL,并将 containerType 设置为 SL_CONTAINERTYPE_UNSPECIFIED。

OpenSL ES for Android 支持以下音频格式,前提是 Android 平台也支持这些格式:

WAV PCM。
WAV alaw。
WAV ulaw。
MP3 Ogg Vorbis。
AAC LC。
HE-AACv1 (AAC+)。
HE-AACv2(增强型 AAC+)。
AMR。
FLAC。

注意:如需查看 Android 所支持的音频格式列表,请参阅支持的媒体格式。

在 OpenSL ES 的这种实现中处理这些格式及其他格式时,存在下列限制:

AAC 格式必须位于 MP4 或 ADTS 容器内。
OpenSL ES for Android 不支持 MIDI。
WMA 不属于 AOSP,我们未验证其是否与 OpenSL ES for Android 兼容。
OpenSL ES 的 Android NDK 实现不支持直接播放 DRM 或加密内容。如需播放受保护的音频内容,您必须先在应用中将其解密,然后才能使用强制实施任何 DRM 限制的应用播放音频内容。

与对象相关的方法

OpenSL ES for Android 不支持以下对象操控方法。

Resume()
RegisterCallback()
AbortAsyncOperation()
SetPriority()
GetPriority()
SetLossOfControlInterfaces()

PCM 数据格式

PCM 是唯一一种可以用于缓冲区队列的数据格式。支持的 PCM 播放配置具有以下特性:

8 位无符号数或 16 位有符号数。
单声道或立体声。
小端字节序字节排序。
下列采样率:
    8,000 Hz。
    11,025 Hz。
    12,000 Hz。
    16,000 Hz。
    22,050 Hz。
    24,000 Hz。
    32,000 Hz。
    44,100 Hz。
    48,000 Hz。

OpenSL ES for Android 所支持的录制配置取决于设备;通常情况下,无论什么设备都支持 16,000 Hz 单声道/16 位有符号配置。

samplesPerSec 字段值的单位为 milliHz,单位名称容易引起误导。为了避免意外使用错误的值,我们建议您使用其中一个专门定义的符号常量(例如 SL_SAMPLINGRATE_44_1)初始化此字段。

Android 5.0(API 级别 21)及更高版本支持浮点数据。
播放速率

OpenSL ES 播放速率表示对象呈现数据的速度,一般以正常速度的千分之几(或千分率)表示。例如,千分之一千的播放速率为 1000/1000,即正常速度。速率范围是一个闭区间,表示一系列可能的播放速率。

对播放速率范围和其他功能的支持可能因平台版本和实现的不同而有所差别。您的应用可以在运行时使用 PlaybackRate::GetRateRange() 或 PlaybackRate::GetCapabilitiesOfRate() 来查询设备,以确定这些功能。

对于 PCM 格式的数据源,设备一般支持相同的速率范围,而对于其他格式,则支持千分之一千到千分之一千的统一速率范围;也就是说,统一速率范围实际上是单个值。
录制

OpenSL ES for Android 不支持 SL_RECORDEVENT_HEADATLIMIT 和 SL_RECORDEVENT_HEADMOVING 事件。
跳转

SetLoop() 方法可以实现全文件循环。如需启用循环,请将 startPos 参数设置为 0,并将 endPos 参数设置为 SL_TIME_UNKNOWN。
缓冲区队列数据定位器

具有缓冲区队列数据定位器的音频播放器或录制器仅支持 PCM 数据格式。
I/O 设备数据定位器

如果您已将定位器指定为 Engine::CreateAudioRecorder() 的数据源,则 OpenSL ES for Android 将仅支持使用 I/O 设备数据定位器。请使用以下代码段中包含的值来初始化设备数据定位器:


SLDataLocator_IODevice loc_dev =
  {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
  SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};

URI 数据定位器

OpenSL ES for Android 只能将 URI 数据定位器与 MIME 数据格式结合使用,并且仅可用于音频播放器。不能将 URI 数据定位器用于音频录制器。URI 只能使用 http: 和 file: 架构。不允许使用 https:、ftp: 或 content: 等其他架构。

我们未在 Android 平台上验证对 rtsp: 音频架构的支持。
数据结构

Android 支持以下 OpenSL ES 1.0.1 数据结构:

SLDataFormat_MIME
SLDataFormat_PCM
SLDataLocator_BufferQueue
SLDataLocator_IODevice
SLDataLocator_OutputMix
SLDataLocator_URI
SLDataSink
SLDataSource
SLEngineOption
SLEnvironmentalReverbSettings
SLInterfaceID

平台配置

OpenSL ES for Android 专为多线程应用而设计,并且能满足线程安全要求。它支持每个应用一个引擎,且每个引擎最多 32 个对象。可用的设备内存和 CPU 可能会进一步限制可用的对象数。

下列引擎选项会被识别,但是 slCreateEngine 会将其忽略:

SL_ENGINEOPTION_THREADSAFE
SL_ENGINEOPTION_LOSSOFCONTROL

OpenMAX AL 和 OpenSL ES 可以在同一个应用中一起使用。在这种情况下,内部存在一个共享的引擎对象,而对象数限值 32 也在 OpenMAX AL 与 OpenSL ES 之间共享。应用应创建两个引擎,使用这两个引擎,最后将其销毁。实现会维护共享引擎上的引用计数,以便在第二次销毁操作期间正确销毁该引擎。

编程说明

OpenSL ES 编程说明提供了可以确保正确实现 OpenSL ES 的补充信息。

注意:为方便起见,我们随 NDK 一起在 docs/opensles/OpenSL_ES_Specification_1.0.1.pdf 中提供 OpenSL ES 1.0.1 规范副本。

平台问题

本部分说明支持这些 API 的初始平台版本中存在的已知问题。

动态接口管理

DynamicInterfaceManagement::AddInterface 不起作用。请改为在传递到 Create() 的数组中指定接口,如环境混响的示例代码所示。

规划未来版本的 OpenSL ES

Android 高性能音频 API 基于 Khronos Group OpenSL ES 1.0.1。Khronos 已发布该标准版本的 1.1 修订版。修订版包含新功能和澄清说明,并校正了一些排版错误和某些不兼容性问题。大部分预期的不兼容性问题都是相对较小的问题,或者属于 Android 不支持的 OpenSL ES 范畴。

只要您遵循下面的规划以兼容二进制文件部分中列出的准则,使用此版本开发的应用应该就可以在未来版本的 Android 平台上运行。

注意:我们并未以兼容未来的源代码作为目标。也就是说,当您升级至新版 NDK 时,可能需要修改应用源代码,使其符合新 API 的要求。我们预计大部分的此类更改都是小幅改动;详见下文。

规划以兼容二进制文件

我们建议使您的应用遵循下面这些准则,以提升未来的二进制文件兼容性:

仅使用 Android 支持的 OpenSL ES 1.0.1 功能中已有记录的部分功能。
不要依赖于特定结果代码来了解未成功的操作;要做好处理其他结果代码的准备。
应用回调处理程序通常在受限制的环境中运行。编写的代码应当可以快速执行工作,然后尽快返回。请勿在回调处理程序中运行复杂的操作。例如,在缓冲区队列完成回调中,可以将另一个缓冲区加入队列,但不要创建音频播放器。
回调处理程序应该为不同频率的调用以及接收其他事件类型做好准备,并且应忽略它们无法识别的事件类型。配置有事件掩码(由已启用的事件类型组成)的回调应做好准备,支持在同时设置多个事件类型位的情况下进行调用。请使用“&”而不要使用 switch case 语句来测试每个事件位。
使用预提取状态和回调作为一般的进度指示,但不要依赖于特定的硬编码填充水平或回调序列。预提取状态填充水平的含义以及预提取期间检测到的错误的行为可能会有变化。

注意:请参阅下面的缓冲区队列行为部分,了解更多详情。

规划以与源代码兼容

如上文所述,Khronos Group 开发的下一版 OpenSL ES 预计会出现源代码不兼容性问题。可能会出现以下方面的变化:

缓冲区队列接口预计会有显著变化,在 BufferQueue::Enqueue、slBufferQueueCallback 的参数列表以及 SLBufferQueueState.playIndex 字段名称等方面尤其如此。我们建议您的应用代码改用 Android 简单缓冲区队列。由于这个原因,在 NDK 随附的示例代码中,我们已将 Android 简单缓冲区队列用于播放。(我们还使用 Android 简单缓冲区队列进行录制和 PCM 解码,不过这是因为标准版 OpenSL ES 1.0.1 不支持录制或解码到缓冲区队列数据接收器。)
将向由引用传递的输入参数和用作输入值的 SLchar * 结构字段中添加一个 const。不过,您不需要对代码作出任何更改。
某些当前有符号的参数将替换为无符号类型。您可能需要将参数类型从 SLint32 改为 SLuint32 或类似类型,或者添加类型转换。
Equalizer::GetPresetName 会将字符串复制到应用内存,而不是返回一个指向实现内存的指针。这将是一项重大更改,因此我们建议您避免调用此方法,或者在隔离状态下使用此方法。
结构类型中也会有更多字段。对于输出参数,可以忽略这些新字段;不过对于输入参数,则需要初始化新字段。幸运的是,所有这些字段预计都属于 Android 不支持的领域。
接口 GUID 将发生变化。请通过符号名称而不是 GUID 来引用接口,以避免依赖。
SLchar 将从 unsigned char 变更为 char。这一变更主要会影响 URI 数据定位器和 MIME 数据格式。
SLDataFormat_MIME.mimeType 将重命名为 pMimeType,SLDataLocator_URI.URI 将重命名为 pURI。我们建议您通过用英文大括号括起来的英文逗号分隔值列表来初始化 SLDataFormat_MIME 和 SLDataLocator_URI 数据结构,而不要按字段名称初始化,这样可以确保您的代码不受此变更影响。示例代码使用的就是这种方法。
SL_DATAFORMAT_PCM 不允许应用以带符号的整型、不带符号的整型或浮点形式指定数据表示。Android 实现假设 8 位数据为不带符号的整型,并假设 16 位数据为带符号的整型。此外,samplesPerSec 字段名称也不恰当,因为实际单位为 milliHz。这些问题预计将在下一个版本的 OpenSL ES 中得到解决,新版本将引入新的扩展 PCM 数据格式,该数据格式允许应用明确指定表示,并且会更正该字段名称。由于这是一种新的数据格式,当前的 PCM 数据格式仍继续可用(尽管已弃用),因此您不需要立即更改代码。

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-11-13。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值