Android NDK sample 之 audio-echo

本文最后更新于:1 年前

Reference


启动页

Start Echo控制运行/结束运行。
delay控制耳返的延迟时间,范围是0~1秒。
decay控制耳返的音量衰减系数,范围是0~100%。


OpenSL ES 基础

OpenSL_ES_Specification_1.0.1.pdf 部分内容摘抄。

(3.1.1)对象的状态机

  • Unrealized(未实现(初始状态)):对象处于活动状态但尚未分配资源。尚不能使用;它的接口方法还不能被调用。
  • Realized(实现): 对象的资源已分配,对象可用。
  • Suspended(挂起):对象的资源少于可用所需的资源,但它保持挂起时的状态。这种状态是可选的,因为面对资源损失,实现可以选择将对象置于挂起状态或未实现状态。

(3.1.5)创建一个对象的步骤

  1. 如果尚未创建引擎对象,则创建并实现引擎对象。
  2. 从此对象获取 SLEngineItf 接口。
  3. 调用此接口的相应对象创建方法。
  4. 如果调用成功,该方法将返回新对象的 SLObjectItf 接口。
  5. 处理完对象后,调用 SLObjectItf 的 Destroy() 方法来释放对象及其资源。

(4.1)OpenSL ES 对象

  1. Engine 对象
  2. Media 对象
  3. Metadata Extractor 对象
  4. Audio Output Mix 对象
  5. LED Array 对象
  6. Vibration Control 对象

(4.1.1)Engine 对象

OpenSL ES API 的入口点。此对象使您能够创建 OpenSL ES 中使用的所有其他对象。

engine 对象是特殊的,因为它是使用全局函数 slCreateEngine() 创建的。创建的结果是 engine 对象的 SLObjectItf 接口。

创建 engine 对象后,大部分工作将通过该对象公开的 SLEngineItf 接口完成。

engine 对象通过唯一的设备 ID 代表系统的各种多媒体相关设备。它支持 enumeration of audio input(枚举音频输入)、audio output(音频输出)、LED 和 vibrator devices(振动器)以及查询其功能的机制。

(4.1.2)Media 对象

media 对象通过在给定一组规定的输入和输出的情况下执行一些媒体处理任务来实现多媒体用例。media 对象包括(但不限于)呈现和捕获媒体流的对象,通常分别称为播放器和记录器。它们对音频数据进行操作。

(4.1.2.1)Data Source(数据源) 和 Sink Structures(接收器)

数据源是 media 对象的输入参数,指定 media 对象将从何处接收特定类型的数据(例如采样音频或 MIDI 数据)。
数据接收器也是 media 对象的输入参数,指定 media 对象将向何处发送特定类型的数据。

数据源和接收器的数量和类型因媒体对象而异。以下特征定义了数据源或接收器:

  • 它的数据定位器标识数据所在的位置。可能的定位器包括:

    • URIs(例如文件名)
    • 内存地址
    • IO设备
    • Output Mixes(输出混音)
    • Buffer queues(缓冲队列)
  • 它的数据格式标识了数据流的特征。可能的格式包括:

    • 基于 MIME 类型的格式
    • PCM 格式

(4.1.3)Metadata Extractor 对象

播放器对象支持读取媒体内容的元数据。但是,有时仅能够读取元数据而不必能够播放媒体就很有用。Metadata Extractor 对象可用于读取元数据,而无需为媒体播放分配资源。当应用程序只对呈现元数据感兴趣而不播放内容以及想要呈现多个文件的元数据时,特别推荐使用此对象。后者对于为演示目的生成播放列表特别有趣,因为播放器对象会不必要地分配播放资源。此外,播放器不能动态改变他们的数据源;因此,对于从多个文件中提取元数据,应用程序需要多次创建和销毁播放器对象,这既低效又可能导致堆碎片化。Metadata Extractor 对象没有数据接收器,但它有一个可以动态更改的数据源。

(4.1.4)Audio Output Mix 对象

API 允许将音频路由到多个音频输出,并包含一个 Audio Output Mix 对象,以促进此功能。应用程序从引擎检索输出混合对象,并且可以将该输出混合指定为媒体对象的接收器。Audio Output Mix 对象被指定为媒体对象的接收器,使用 SL_DATALOCATOR_OUTPUTMIX 数据定位器。引擎使用一组默认音频输出设备填充输出混合。应用程序可以通过 SLOutputMixItf 接口查询此设备列表或请求对其进行更改。API 不提供直接音频输出 IO 设备作为媒体对象的接收器。

(4.1.5)LED Array 对象

设备 LED 的控制是通过 LED 阵列对象处理的。查询 LED 阵列对象的功能和创建 LED 阵列对象是引擎级操作,而对单个 LED 的控制由对象处理。

(4.1.6)Vibration Control 对象

设备振动支持的控制是通过 Vibra 对象处理的。查询 Vibra 对象的能力和创建 Vibra 对象是引擎级别的操作,而实际振动的控制由对象处理。

(4.6)示例用例

(4.6.1)采样音频播放

Audio Player 对象用于采样音频播放。我们使用 engine 对象的 SLEngineItf 接口创建 Audio Player 和 Output Mix。同时,将 Audio Player 和 Output Mix 关联用于音频输出。我们还在创建时设置了 Audio Player 的数据源,数据源可以是指向本地文件系统中的音频文件的 URI。默认情况下,Output Mix 与系统相关的 default output device 相关联。

(4.6.4)录制音频

录音用例由 Audio Recorder 对象处理。我们使用引擎对象的 SLEngineItf 接口创建录音机对象。创建时,我们将其与音频数据源相关联,例如可以是 microphone(音频输入设备)。Audio Recorder 的数据接收器可以是指向本地文件系统中的音频文件的 URI,音频将被记录到该文件系统中。

(4.6.5)读取元数据

Metadata Extractor 对象将读取音频文件的元数据,而无需为音频播放分配资源。与其他用例一样,我们使用引擎对象的 SLEngineItf 接口创建对象,并在创建时设置元数据提取器的数据源。数据源通常是指向本地文件系统中的音频文件的 URI。但是,元数据提取器支持 SLDynamicSourceItf 接口,我们可以使用它来更改数据源。因此,我们可以从多个文件(连续)中提取元数据,而无需为每个文件创建新的 Metadata Extractor 对象。SLMetadataExtractionItf 和 SLMetadataTraversalItf 接口用于从文件中实际读取和遍历元数据。

(4.7.1)电话

(4.7.2)音乐

(4.7.4)数据源


(6.1)slCreateEngine

初始化引擎对象并为用户提供一个句柄。

(7)对象类型

(7.2)Audio Player

音频播放器媒体对象播放由数据源指定的一段内容,执行任何隐式解码,应用任何指定的处理,并将其呈现到数据接收器指定的目的地。

(7.3)Audio Recorder

音频记录器媒体对象将内容片段记录到数据接收器指定的目的地,从数据源指定的输入中捕获它并执行任何指定的编码或处理。

(7.4)Engine Object

此对象类型是 API 的入口点。一个实现应该能够创建至少一个这样的对象,但尝试创建更多实例(通过单个应用程序或多个不同的应用程序)可能会失败。

引擎对象支持通过其 SLEngineItf 接口创建所有 API 的对象,并通过其 SLEngineCapabilitiesItf 接口查询实现的功能。

(7.7)Metadata Extractor Object

此对象可用于读取元数据,而无需为媒体播放分配资源。当应用程序只对呈现元数据感兴趣而不播放内容以及想要呈现多个文件的元数据时,特别推荐使用此对象。后者对于为演示目的生成播放列表特别有趣,因为音频播放器会不必要地分配播放资源。此外,玩家不能动态改变他们的数据源;因此,对于从多个文件中提取元数据,应用程序需要多次创建和销毁播放器对象,这既低效又可能导致堆碎片化。

(7.9)Output Mix

输出混合对象表示一组音频输出设备,一个音频输出流被发送到这些设备。应用程序从引擎检索输出混合对象,并且可以将该输出混合指定为媒体对象的接收器。引擎必须至少支持一种输出混合,但它可能支持更多。API 不提供直接音频输出 IO 设备作为媒体对象的接收器。

输出混合是一个逻辑对象;它并不(必然)代表物理混合。因此,由混合对象及其与媒体对象的关联在逻辑上定义的混合的实际实现是一个实现细节。输出混音不代表系统的主混音。此外,混合对象表示应用程序对输出的贡献;实施可能会将此贡献与其他来源的输出混合。

(8)API

(8.12)SLBufferQueueItf

此接口用于流式传输音频数据。它提供了一种在播放器对象上排队缓冲区以供设备播放的方法。它还提供了每当队列中的缓冲区完成时调用的回调函数。缓冲区按它们排队的顺序播放。可以查询缓冲区队列的状态以提供有关缓冲区队列播放状态的信息。这个接口实现了一个简单的流媒体机制。

  • 尝试在数据源不是 SL_DATALOCATOR_BUFFERQUEUE 或 SL_DATALOCATOR_MIDIBUFFERQUEUE 类型的 media 对象上实例化 SLBufferQueueItf 是无效的,将失败。
  • 当播放器处于由 SLPlayItf 接口控制的 SL_PLAYSTATE_PLAYING 状态时,填充缓冲区将隐式开始播放。在队列中缓冲区不足导致饥饿的情况下,音频数据的播放会停止,但播放器仍保持在 SL_PLAYSTATE_PLAYING 状态,直到再次填充缓冲区,音频数据的播放才会恢复。请注意,排队缓冲区的饥饿会导致音频数据流中出现可听见的间隙。在播放器未处于播放状态的情况下,填充缓冲区不会启动音频播放。
  • 排队的缓冲区就地使用,设备不需要复制,尽管这可能取决于实现。应用程序开发人员应该知道,在缓冲区排队之后修改缓冲区的内容是未定义的,并且可能导致音频损坏。
  • 一旦入队的缓冲区完成播放,如回调通知所通知的那样,删除缓冲区或用新数据填充缓冲区并再次将缓冲区入队以进行播放是安全的。
  • 在转换到 SL_PLAYSTATE_STOPPED 状态时,播放光标返回到当前播放缓冲区的开头。
  • 在转换到 SL_PLAYSTATE_PAUSED 状态时,播放光标保持在缓冲区中的当前位置。
  • 对于数据源是缓冲区队列的播放器,无法在缓冲区内查找。尝试在具有缓冲队列作为数据源的媒体对象上实例化 SLSeekitf 接口将失败。

默认

  • 没有缓冲区排队
  • 没有注册回调方法
Callbacks描述
slBufferQueueCallback播放完缓冲区队列中的缓冲区后调用的回调函数。

Methods|描述|评论
–|–
Enqueue|向队列添加缓冲区。该方法将指向要排队的数据的指针和缓冲区的大小(以字节为单位)作为参数。缓冲区按使用此方法排队的顺序播放。|当播放器处于由 SLPlayItf 接口控制的 SL_PLAYSTATE_PLAYING 状态时,填充缓冲区将隐式开始播放。在队列中缓冲区不足导致饥饿的情况下,音频数据的播放会停止,但播放器仍保持在 SL_PLAYSTATE_PLAYING 状态,直到再次填充缓冲区,音频数据的播放才会恢复。请注意,排队缓冲区的饥饿会导致音频数据流中出现可听见的间隙。在播放器未处于播放状态的情况下,填充缓冲区不会启动音频播放。如果已达到使用 CreateAudioPlayer 或 CreateMidiPlayer 方法创建媒体对象时用作数据源的 SLDataLocator_BufferQueue 结构中指定的最大缓冲区数,则不会将该缓冲区添加到缓冲区队列,并返回 SL_RESULT_BUFFER_INSUFFICIENT。此时客户端应该等待,直到它收到缓冲区完成的回调通知,此时它可以将缓冲区排入队列。
Clear|释放当前在缓冲区队列中排队的所有缓冲区。不调用已释放缓冲区的回调函数。SLBufferQueueState 被重置为初始状态。|此方法会重置用于 GetPosition() 的 SLPlayItf 接口中使用的累积位置信息。
GetState|返回缓冲区队列的当前状态。|
RegisterCallback|设置在缓冲区完成时调用的回调函数。|RegisterCallback() 方法只能在媒体对象处于 SL_PLAYSTATE_STOPPED 状态时调用,以避免回调函数的删除和可能正在处理的回调之间的竞争条件。回调函数可以通过多次调用 RegisterCallback() 方法来更改,但仅限于媒体对象处于停止状态时。如果调用该方法时媒体对象不处于 SL_PLAYSTATE_STOPPED 状态,则返回 SL_RESULT_PRECONDITIONS_VIOLATED。

(8.17)SLEngineItf

该接口公开了所有 OpenSL ES 对象类型的创建方法。

Methods|描述|评论
–|–
CreateLEDDevice|创建一个 LED 设备。|
CreateVibraDevice|创建振动器设备
CreateAudioPlayer|创建一个音频播放器对象。
CreateAudioRecorder|创建录音机。
CreateMidiPlayer|创建一个 MIDI 播放器。|如果在 pBankSrc 参数中提供了一个库,并且在 MIDI 源中嵌入了一个库,则播放器创建可能会失败并显示 SL_RESULT_FEATURE_UNSUPPORTED 代码。在这种情况下,建议应用程序尝试创建 MIDI 播放器而不在 pBankSrc 中提供库。如果应用程序计划单独使用 MIDI 消息接口 SLMIDIMessageItf 来提供 MIDI,则该应用程序可能希望将 pMIDISrc 设置为 NULL 从单个播放器到 MIDI 合成器的数据。
CreateListener|创建一个监听器。
Create3DGroup|创建一个 3D 组。
CreateOutputMix|创建输出混音器。
CreateMetadataExtractor|创建元数据提取器对象。
CreateExtensionObject|创建一个对象。此方法用于从规范外部定义的扩展对象。规范定义的对象必须由引擎接口中的特定创建方法创建。|如果引擎由于缺乏内存或资源而无法创建对象,它将分别返回 SL_RESULT_MEMORY_FAILURE 或 SL_RESULT_RESOURCE_ERROR 错误。由 pParameters 指向的 ObjectID 和数据结构应由扩展定义。当 ObjectID 无效时,该方法将返回 SL_RESULT_FEATURE_UNSUPPORTED。
QueryNumSupportedInterfaces|查询支持的可用接口数量。|支持的接口数量将包括对象可用的强制接口和可选接口。此方法可用于通过检查返回值来确定实现是否支持对象。
QuerySupportedInterfaces|查询支持的接口。|支持的接口数量将包括可用于对象的强制接口和可选接口。
QueryNumSupportedExtensions|查询支持的扩展数量。|支持的扩展数量将包括 Khronos 注册表中列出的标准化扩展和特定于供应商的扩展。
QuerySupportedExtension|根据给定的索引获取实现支持的扩展名。|如果给定的长度小于所需的大小,则返回 SL_RESULT_BUFFER_INSUFFICIENT 并且只会写入给定大小的数据; 但是,不会写入无效字符串。也就是说,空终止符始终存在,多字节字符不会在中间被截断。
IsExtensionSupported|查询实现是否支持给定的扩展。|这是一种替代方法,用于代替 QueryNumSupportedExtensions() 和 QuerySupportedExtension() 来查询仅一个已知扩展的可用性。

(8.29)SLObjectItf

SLObjectItf 接口为所有对象提供基本的实用方法。此类功能包括对象的销毁、实现和恢复、接口指针的获取、运行时错误的回调以及异步操作终止。

在任何给定时间,一个对象最多可以执行一个异步操作。当对象已经在处理异步调用时尝试调用异步操作等同于中止第一个操作,然后调用第二个操作。

SLObjectItf 是所有对象类型的隐式接口,在创建每个对象时自动可用。

默认

  • 对象处于未实现状态。
  • 没有注册回调。
Callbacks描述
slObjectCallback回调函数,通知运行时错误、异步调用终止或对象资源状态的更改。

Methods|描述|评论
–|–
Realize|同步或异步地将对象从 Unrealized 状态转换到 Realized 状态。|实现对象获取其功能所需的资源。如果可用资源不足,操作可能会失败。在这种情况下,应用程序可能会等到资源变得可用并收到 SL_OBJECT_EVENT_RESOURCES_AVAILABLE 事件,然后重试实现。另一种选择是尝试提高对象的优先级,从而增加对象窃取另一个对象资源的可能性。
Resume|同步或异步地将对象从挂起状态转换到已实现状态。|恢复对象获取其功能所需的资源。如果可用资源不足,操作可能会失败。在这种情况下,应用程序可能会等待资源变得可用并收到 SL_OBJECT_EVENT_RESOURCES_AVAILABLE 事件,然后重试恢复。另一种选择是尝试提高对象的优先级,从而增加对象窃取另一个对象资源的可能性。
GetState|检索当前对象状态。
GetInterface|获取对象暴露的接口|如果对象未公开请求的接口类型,则返回代码将为 SL_RESULT_FEATURE_UNSUPPORTED。
RegisterCallback|在发生运行时错误或异步操作终止时执行的对象上注册回调。|回调将仅报告运行时错误和调用异步函数的结果。
AbortAsyncOperation|中止对象当前处理的异步调用。此方法仅影响从 SLObjectItf 或 XADynamicInterfaceManagementItf 发起的异步调用。如果没有处理此类调用,则忽略该调用。如果注册了回调,它将被调用,SL_OBJECT_EVENT_ASYNC_TERMINATION 作为事件,SL_RESULT_OPERATION_ABORTED 作为返回码。|该方法用于正常超时或用户启动的异步调用中止。
Destroy|销毁对象|Destroy 通过 Unrealized 状态隐式传输对象,从而在释放对象之前释放分配给对象的所有资源。对属于此对象的接口的所有引用都将变为无效,并且如果使用可能会导致未定义的行为。所有挂起的异步操作都被中止,就好像 AbortAsyncOperations() 已被调用。
SetPriority|设置对象的优先级。|尽管可以在指定范围内设置任何优先级,但 SL_PRIORITY 定义了一组固定的优先级用于此方法。
GetPriority|获取对象的优先级。
SetLossOfControllnterfaces|设置/取消设置接口 ID 列表的失控功能。启用标志的默认值由全局设置确定。|对此方法的调用将覆盖指定接口列表的失控功能的全局设置。

(8.32)SLPlayItf

SLPlayItf 是一个用于控制对象播放状态的接口。

默认
最初,播放状态为 SL_PLAYSTATE_STOPPED,位置在内容的开头,更新周期为一秒,没有设置标记,也没有注册回调,回调事件标志被清除。

Callbacks描述
slPlayCallback通知播放器应用程序播放事件。

Methods|描述|评论
–|–
SetPlayState|请求播放器转换到给定的播放状态。|所有状态转换都是合法的。状态默认为 SL_PLAYSTATE_STOPPED。请注意,尽管状态更改是立即发生的,但此方法的执行与其对行为的影响之间可能存在一些延迟。从这个意义上说,播放器的状态在技术上代表了应用程序对播放器的意图。请注意,播放器的状态会影响播放器的预取状态(有关详细信息,请参阅 SLPrefetchStatusItf)。播放器可能会分别返回 SL_RESULT_PERMISSION_DENIED、SL_RESULT_CONTENT_CORRUPTED 或 SL_RESULT_CONTENT_UNSUPPORTED 如果在请求状态更改时检测到权限不足、内容损坏或内容不受支持。当播放器到达内容结尾时,播放状态将转换为暂停和暂停,播放光标将停留在内容的末尾。
GetPlayState|获取播放器当前的播放状态|
GetDuration|获取当前内容的持续时间,以毫秒为单位。|在数据源为缓冲队列的情况下,当前时长是自上次缓冲队列Clear()方法以来所有缓冲的累计时长。
GetPosition|返回播放头相对于内容开头的当前位置。|返回值介于 0 和内容的持续时间之间。在数据源为缓冲队列的情况下,当前位置是自上次缓冲队列Clear()方法以来所有缓冲的累计持续时间。请注意,位置是相对于以 1 倍前进速率播放的内容定义的; 位置不会随着播放速率的变化而缩放。
RegisterCallback|设置播放回调函数。|回调函数默认为 NULL。
应用程序可以使用上下文指针将状态传递给回调函数。
SetCallbackEventsMask|启用/禁用播放事件的通知。|回调事件标志默认为清除所有标志。
GetCallbackEventsMask|查询播放事件的通知状态(启用/禁用)。|
SetMarkerPosition|设置播放标记的位置。|当播放头通过标记时,播放器将通过带有 SL_PLAYEVENT_HEADATMARKER 事件的回调通知应用程序。默认情况下,没有定义标记位置。当标记位置与周期性位置更新(由 SetPositionUpdatePeriod() 指定)重合时,标记位置回调和周期性位置更新回调必须彼此相邻发布。两个回调的顺序无关紧要。
ClearMarkerPosition|清除标记。|即使标记已经清除,此功能也会成功。
GetMarkerPosition|查询播放标记的位置。|
SetPositionUpdatePeriod|设置定期位置通知之间的间隔。|当播放头通过指定时间段所暗示的位置时,播放器将通知应用程序。这些位置被定义为相对于内容开头的周期的整数倍。默认情况下,更新周期为 1000 毫秒。当周期性位置更新与标记位置重合时(由 SetMarkerPosition() 指定),则位置更新周期回调和标记位置回调必须彼此相邻发布。两个回调的顺序无关紧要。
GetPositionUpdatePeriod|查询周期性位置通知的间隔时间。|

(8.37)SLRecordItf

SLRecordItf 是一个用于控制对象录制状态的接口。录制的状态机如下:

如果在记录到文件时存储介质变满,SL_OBJECT_EVENT_RUNTIME_ERROR 将通过 slObjectCallback 发布,SL_RESULT_IO_ERROR 作为此回调的结果参数。在这种情况下,记录器将自动转换到 SL_RECORDSTATE_STOPPED 状态。

默认
记录器默认为 SL_RECORDSTATE_STOPPED 状态,没有标记,没有持续时间限制,更新周期为一秒,没有设置标记,也没有注册回调,回调事件标志被清除。

Callbacks描述
slRecordCallback通知记录器应用程序记录事件。

Methods|描述|评论
–|–
SetRecordState|将记录器转换为给定的记录状态。|所有状态转换都是合法的。
GetRecordState|获取记录器的当前记录状态。|
SetDurationLimit|以毫秒为单位设置当前内容的持续时间。|当记录器达到限制时,它会自动转换到 SL_RECORDSTATE_STOPPED 状态并通过 SL_RECORDEVENT_HEADATLIMIT 事件通知应用程序。
GetPosition|返回记录头相对于内容开头的当前位置。|该位置与记录的内容量同义。
RegisterCallback|注册记录回调函数。|
SetCallbackEventsMask|设置记录事件的通知状态。|回调事件标志默认为清除所有标志。
GetCallbackEventsMask|查询记录事件的通知状态。|
SetMarkerPosition|设置记录标记的位置。|当录音头通过标记时,播放器将通过带有 SL_RECORDEVENT_HEADATMARKER 事件的回调通知应用程序。
ClearMarkerPosition|清除标记。|即使标记已经清除,此功能也会成功
GetMarkerPosition|查询录音标记的位置。|
SetPositionUpdatePeriod|设置定期位置通知之间的间隔。|当记录头通过指定时间段所暗示的位置时,记录器将通知应用程序。这些位置被定义为相对于内容开头的周期的整数倍。
GetPositionUpdatePeriod|查询周期性位置通知的间隔时间。


代码

(基本只比官方demo多了注释)
audio-echo


分析

1、时序图(app启动后~点击开始按钮前)

2、时序图(点击开始按钮后)

3、通信图



Android NDK sample 之 audio-echo
https://weichao.io/3c8edfb928da/
作者
魏超
发布于
2021年8月9日
更新于
2022年12月4日
许可协议