适用于Android的OpenSL ES指南-编程注意事项

翻译自OpenSL ES Programming Noteshtml

本节中的注释补充了OpenSL ES 1.0.1规范android

对象和接口初始化

OpenSL ES编程模型的两个方面多是新开发人员不熟悉的,即对象和接口之间的区别以及初始化顺序。c++

简单地说,OpenSL ES对象相似于Java和c++等编程语言中的对象概念,只是OpenSL ES对象仅经过其关联接口可见。这包括全部对象的初始接口,称为SLObjectItf。没有对象自己的句柄,只有对象的SLObjectItf接口的句柄。git

首先建立一个OpenSL ES对象,它返回一个SLObjectItf,而后实例化它。这相似于常见的编程模式,首先构造一个对象(除非缺乏内存或无效参数,不然不会失败),而后完成初始化(可能因为缺少资源而失败)。实例化这步为实例提供了在须要时分配额外资源的逻辑内存。github

做为建立对象的API的一部分,应用程序指定了它计划稍后获取的所需接口数组。注意,这个数组不会自动得到接口;它仅仅代表了未来获取它们的意图。接口被区分为隐式或显式。若是之后要得到显式接口,则必须在数组中列出它。隐式接口不须要在对象建立数组中列出,可是在那里列出它并无害处。OpenSL ES还有一种称为dynamic的接口,它不须要在对象建立数组中指定,能够在对象建立后添加。Android实现提供了一个方便的特性来避免这种复杂性,这种复杂性在OpenSL ES的Android扩展这篇文章中的对象建立时的动态接口中进行了描述。算法

在建立和实现对象以后,应用程序应该在SLObjectItf初始化后使用GetInterface为它须要的每一个特性获取接口。编程

最后,该对象能够经过其接口使用,不过请注意,有些对象须要进一步设置。特别是,带有URI数据源的音频播放器须要作更多的准备,以检测链接错误。有关详细信息,请参阅下面的音频播放器预读取部分。api

应用程序处理完对象后,应该显式地销毁它;参见下面的销毁部分。数组

音频播放器预读取

对于具备URI数据源的音频播放器,Object::Realize分配资源,但不链接数据源(准备阶段)或开始预读取数据。一旦将播放器状态设置为sl_playstate_pauseSL_PLAYSTATE_PLAYING,就会出现这种状况。安全

在此序列中,有些信息可能直到相对较晚的时候才会为人所知。特别是,初始时,Player::GetDuration返回SL_TIME_UNKNOWN还有 MuteSolo::GetChannelCount返回0,或者返回错误结果SL_RESULT_PRECONDITIONS_VIOLATED。当为已知时,才返回正确值。

其余最初未知的属性包括采样率和基于检查内容头的实际的媒体内容类型(与应用程序指定的MIME类型和容器类型相反)。这些也是在准备/预读取期间稍后肯定的,可是没有api来检索它们。

预读取状态接口对于检测什么时候全部可用信息很是有用,或者您的应用程序能够按期轮询。注意,一些信息,例如MP3流的持续时间,可能永远不会知道。

预取状态接口对于检测错误也颇有用。注册一个回调,并至少启用SL_PREFETCHEVENT_FILLLEVELCHANGESL_PREFETCHEVENT_STATUSCHANGE事件。若是这两个事件同时交付,PrefetchStatus::GetFillLevel报告0级,PrefetchStatus::GetPrefetchStatus报告SL_PREFETCHSTATUS_UNDERFLOW,那么这代表数据源中有一个不可恢复的错误。这包括没法链接数据源,由于本地文件名不存在或网络URI无效。

OpenSL ES的下一个版本预计将添加对数据源中错误处理的更加显式的支持。然而 ,为了未来的二进制兼容性,咱们打算继续支持当前不可恢复错误报告的方法。

总之,推荐的代码序列是:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. Object::GetInterface for SL_IID_PREFETCHSTATUS
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. Object::GetInterface for SL_IID_PLAY
  8. Play::SetPlayState to SL_PLAYSTATE_PAUSED, or SL_PLAYSTATE_PLAYING

注意:这里有准备和预读取;在这段时间内,你的回调会被按期的状态更新调用。

销毁

在退出应用程序时,请确保销毁全部对象。对象应该按照建立对象的相反顺序被销毁,由于销毁具备任何依赖对象的对象是不安全的。例如,按如下顺序销毁:音频播放器和录音机,输出混合,最后是引擎。

OpenSL ES不支持自动垃圾收集或接口的引用计数。在您调用Object::Destroy以后,全部从关联对象派生的现有接口都将没法定义。

Android OpenSL ES实例不会检测到这些接口的不正确使用状况。在对象被销毁后继续使用这些接口可能致使应用程序崩溃或以不可预知的方式运行。

咱们建议您显式地将主对象接口和全部关联接口都设置为NULL,做为对象销毁序列的一部分,这样能够防止对陈旧接口句柄的意外滥用。

立体声平移

Volume::EnableStereoPosition用于启用单声道源的立体平移时,总声波功率级别下降了3分贝。容许总声波功率水平保持不变是必要的,由于声源是从一个通道到另外一个通道。所以,只有在你须要的时候,才能启用立体声定位。有关更多信息,请参阅Wikipedia关于音频平移的文章。

回调和线程

当实例检测到事件时,一般同步调用回调处理程序。对于应用程序,这一点是异步的,所以应该使用非阻塞同步机制来控制应用程序和回调处理程序之间共享变量的访问权限。在示例代码(例如缓冲区队列)中,为了简单起见,咱们要么省略了这个同步,要么使用了阻塞同步。然而,适当的非阻塞同步对于任何代码都是相当重要的。

回调处理程序是从内部非应用程序线程调用的,这些线程不attach到Android runtime ,所以它们不具有使用JNI的资格。由于这些内部线程对OpenSL ES实例的完整性相当重要,因此回调处理程序也不该该阻塞或执行过多的工做。

若是回调处理程序须要使用JNI或执行与回调不相称的工做,则处理程序应该向另外一个线程发布一个事件来处理。可接受的回调工做负载的示例包括渲染和排队下一个输出缓冲区(用于AudioPlayer)、处理刚刚填充的输入缓冲区和排队下一个空缓冲区(用于AudioRecorder)或简单api(如Get系列的大部分)。关于工做负载,请参阅下面的性能部分。

注意,反过来是安全的:已使用JNI的Android应用程序线程能够直接调用OpenSL ES api,包括那些阻塞的api。可是,主线程不建议使用阻塞调用,由于它们可能致使应用程序不响应(ANR)。

关于调用回调处理程序的线程的决定很大程度上取决于OpenSL ES实现。这种灵活性的缘由是为了容许未来进行优化,特别是在多核设备上。

回调处理程序运行的线程不能保证在不一样调用之间具备相同的标识。所以,不要依赖pthread_self()返回的pthread_tgettid()返回的pid_t在调用之间保持一致。出于一样的缘由,不要从回调中使用线程本地存储(TLS) api,例如pthread_setspecific()pthread_getspecific()

该实现保证不会对同一对象发生相同类型的并发回调。然而,在不一样的线程上,对于相同对象的不一样类型的并发回调是可能的。

性能

因为OpenSL ES是一个 native C API,调用OpenSL ES的非运行时应用程序线程没有与运行时相关的开销,好比垃圾收集暂停。除了下面描述的一个例外,使用OpenSL ES没有其余性能优点。特别是,使用OpenSL ES并不能保证比平台一般提供的更低的音频延迟和更高的调度优先级。另外一方面,随着Android平台和特定设备实现的不断发展,OpenSL ES应用程序有望从将来的系统性能改进中获益。

其中一个改进是支持减小音频输出延迟。减小输出延迟的基础首先包含在Android 4.1 (API级别16)中,而后在Android 4.2 (API级别17)中继续进行。这些改进能够经过OpenSL ES用于设备实现,这些设备实现声称具备android.hardware.audio.low_latency特性。若是设备没有声明这个特性,可是支持Android 2.3 (API级别9)或更高,那么您仍然可使用OpenSL ES API,可是输出延迟可能会更高。只有当应用程序请求与设备本机输出配置兼容的缓冲区大小和采样率时,才使用较低的输出延迟路径。这些参数是特定于设备的,应以下所述得到。

从Android 4.2 (API level 17)开始,应用程序能够查询平台原生或最佳输出采样率和设备主输出流的缓冲区大小。当与刚才提到的特性测试结合使用时,应用程序如今能够适当地配置本身,以在声称支持的设备上下降输出延迟。

对于Android 4.2 (API级别17)及更早版本,为了下降延迟,须要两个或更多的缓冲区计数。从Android 4.3 (API级别18)开始,一个缓冲区计数就足以下降延迟。

全部用于输出效果的OpenSL ES接口都排除了较低的延迟路径。

推荐顺序以下:

  1. 检查API级别9或更高,以确认OpenSL ES的使用。
  2. 检查android.hardware.audio.low_latency特性使用以下代码:
import android.content.pm.PackageManager;
...
PackageManager pm = getContext().getPackageManager();
boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
复制代码
  1. 检查API级别17或更高,以确认android.media.AudioManager.getProperty()的使用。
  2. 使用如下代码得到原生或最优输出采样率和此设备的主输出流的缓冲区大小:
import android.media.AudioManager;
...
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));//采样率
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));//单位缓冲区帧数

复制代码

注意,sampleRateframesPerBuffer都是字符串。首先检查null,而后使用Integer.parseInt()将其转换为int。 5. 如今使用OpenSL ES建立一个带有PCM缓冲队列数据定位器的AudioPlayer。

注意:您可使用音频缓冲区大小测试应用程序来肯定音频设备上OpenSL ES音频应用程序的本机缓冲区大小和采样率。您还能够访问GitHub,查看音频缓冲大小的示例。

低延迟音频播放器的数量是有限的。若是您的应用程序须要多个音频源,请考虑在应用程序级别混合音频。当您的活动暂停时,请确保销毁您的音频播放器,由于它们是与其余应用程序共享的全局资源。

为了不出现可听见的故障,缓冲区队列回调处理程序必须在一个小而可预测的时间窗口内执行。这一般意味着对互斥对象、条件或I/O操做没有不可控制的阻塞。相反,应该考虑使用锁、锁和超时等待以及非阻塞算法

渲染下一个缓冲区(用于AudioPlayer)或使用前一个缓冲区(用于AudioRecord)所需的计算时间应该与每次回调的时间大体相同。避免在不肯定的时间内执行的算法,或者在计算中出现问题。若是在任何给定回调中所花费的CPU时间明显大于平均值,则回调计算就会很激烈。总之,理想的状况是处理程序的CPU执行时间接近于零,处理程序在不设限时间内不阻塞。

只对如下输出作到低延迟音频是可能的:

  • 设备内置扬声器。
  • 有线耳麦。
  • 有线耳机。
  • 线路输出(音响)。
  • USB数字音频

在某些设备上,因为须要对扬声器进行校订和保护的数字信号处理,扬声器等待时间比其余路径要长。

在某些设备上,因为须要对扬声器进行校订和维护及数字信号处理,扬声器等待时间比其余路径要长。

从Android 5.0 (API Level 21)开始,被选的设备支持较低的音频输入延迟。要利用这个特性,首先要确承认以使用上面描述的较低的延迟输出。低延迟输出的能力是低延迟输入特性的先决条件。而后,建立一个AudioRecorder,其采样率和缓冲区大小与用于输出的相同。用于输入效果的OpenSL ES接口排除了较低的延迟路径。为了下降延迟,record预设必须使用 SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;此预设将禁用特定于设备的数字信号处理,这可能会增长输入路径的延迟。有关record预置的更多信息,请参阅OpenSL ES的Android扩展这篇文章中的Android配置接口部分。

对于同时输入和输出,每一方都使用单独的缓冲区队列完成处理程序。没有保证这些回调的相对顺序,或音频时钟的同步,即便双方使用相同的采样率。应用程序应该使用适当的缓冲区同步来缓冲数据。

可能独立的音频时钟的一个后果是须要异步采样率转换。异步采样率转换的一种简单(虽然不太理想)技术是在零交叉点附近复制或删除采样。更复杂的转换也是可能的。

性能模式

从Android 7.1 (API级别25)开始,OpenSL ES引入了一种方法来指定音频路径的性能模式。 选项是:

  • SL_ANDROID_PERFORMANCE_NONE:没有特定的性能要求。容许硬件和软件效果。
  • SL_ANDROID_PERFORMANCE_LATENCY:优先考虑延迟。没有硬件或软件的效果。这是默认模式。
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:优先考虑延迟,同时仍然容许硬件和软件效果。
  • SL_ANDROID_PERFORMANCE_POWER_SAVING:优先考虑节约能源。容许硬件和软件效果。

注意:若是您不须要低延迟路径,而且但愿利用设备内置的音频效果(例如提升视频播放的音质),那么您必须显式地将性能模式设置为SL_ANDROID_PERFORMANCE_NONE

要设置性能模式,必须使用Android配置接口调用SetConfiguration,以下所示:

// Obtain the Android configuration interface using a previously configured SLObjectItf.
  SLAndroidConfigurationItf configItf = nullptr;
  (*objItf)->GetInterface(objItf, SL_IID_ANDROIDCONFIGURATION, &configItf);

  // Set the performance mode.
  SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE;
    result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                     &performanceMode, sizeof(performanceMode));
复制代码

安全与权限

至于谁能作什么,Android的安全是在进程级别完成的。Java编程语言代码没有比原生代码作更多,原生代码也没有能比Java编程语言代码作更多事。它们之间惟一的区别是可用的api。

使用OpenSL ES的应用程序必须请求对相似的非原生api所需的权限。例如,若是您的应用程序录制音频,那么它须要android.permission。RECORD_AUDIO权限。使用音频效果的应用程序须要android.permission.MODIFY_AUDIO_SETTINGS。运行网络URI资源的应用程序须要android.permission.NETWORK。有关更多信息,请参见使用系统权限

根据平台的版本和实现,媒体内容解析器和软件编解码器可能在调用OpenSL ES的Android应用程序上下文中运行(硬件编解码器是抽象的,但与设备相关)。为了利用解析器和编解码器漏洞而设计的畸形内容是一个都知道的攻击方向。咱们建议您只从可靠的来源播放媒体,或者将应用程序分区,以便处理来自不可靠来源的媒体的代码在一个相对沙箱环境中运行。例如,您能够在一个单独的进程中处理来自不可靠来源的媒体。虽然这两个进程仍然在同一个UID下运行,可是这种分离确实使攻击更加困难。


上一篇: 适用于Android的OpenSL ES指南-OpenSL ES的Android扩展

相关文章
相关标签/搜索