本文主要介绍Android上可以进行音频(PCM)播放的两个组件–AudioTrack/OpenSL ES的简单使用方法。
对一个音频文件(如MP3文件),如何使用FFmpeg进行解码获取到PCM,之前的文章已经有相应的说明: 。 那么解码后或者mic采集的PCM数据,是如何播放的呢,首先一般会对PCM数据进行重采样,也即是转换为指定的格式。重采样可以参考:
最后,进入本文主题,介绍AudioTrack/OpenSL ES的简单使用方法。
AudioTrack是Android系统中管理和播放单一音频资源的类,Android提供了java层及native层的api,使用也比较简单,一般我推荐使用这个组件播放。 但需要注意的是,它仅能播放已经解码出来的PCM数据。
我们以java端的api为例(native层基本一致),AudioTrack使用方法如下: 1、创建:
参数说明: 1)int streamType:指定即将播放的声音类型,对于不同类型,Android的audio系统会有不同处理(如音量等级不同,音量控制不同等),一些常见类型如下,对于音乐文件,我们使用STREAM_MUSIC
- STREAM_ALARM:警告声
- STREAM_MUSIC:音乐声,例如music等
- STREAM_RING:铃声
- STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等
- STREAM_VOCIE_CALL:通话声
AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对应的是数据加载模式和音频流类型, 对应着两种完全不同的使用场景。
2)int sampleRateInHz:采样率 3)int channelConfig:音频声道对应的layout,如立体声是AudioFormat.CHANNEL_OUT_STEREO 4)int audioFormat:音频格式 5)int bufferSizeInBytes:缓冲区大小 缓冲区大小可以通过函数getMinBufferSize获取,传入采样率、声道layout、音频格式即可,如下:
6)int mode:数据加载模式(MODE_STREAM和MODE_STATIC),两种模式对应着两种不同的使用场景
- MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。
- MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。
2、启动:
3、数据注入:
参数比较简单,数据、偏移、size
4、停止:
5、释放:
6、获取状态: STATE_INITIALIZED和STATE_UNINITIALIZED就不用说明了。STATE_NO_STATIC_DATA是个中间状态,当使用MODE_STATIC模式时,创建AudioTrack ,首先会进入改状态,需要write数据后,才会变成STATE_INITIALIZED状态。非STATE_INITIALIZED状态下去进行play的话,会抛出异常,所以MODE_STATIC模式如果没有write就去play是不行的。
java端示例
OpenSL ES(Open Sound Library for Embedded Systems,开源的嵌入式声音库)是一个免授权费、跨平台、C语言编写的适用于嵌入式系统的硬件加速音频库。简单来说,它提供了一些标准化的api,让开发者可以在不同硬件平台上,操作音频设备。包括播放,录制等等。
虽然是C语音,但其采用了面向对象的方式,在开发中,我们使用对象(object)和接口(interface)来开发。
- 对象:提供一组资源极其状态的抽象,例如录音器对象,播放器对象。
- 接口:对象提供一特定功能方法的抽象,例如播放器对象的播放接口。
也就是说每种对象都提供了一些最基础的操作:Realize,Resume,GetState,Destroy 等等,但是对象不能直接使用,必须通过其 GetInterface 函数用ID号拿到指定接口(如播放器的播放接口),然后通过该接口来访问功能函数。
所有对象在创建后都要调用Realize 进行初始化,释放时需要调用Destroy 进行销毁。
音频播放场景:
从该场景图来讲解播放方法:
1、创建OpenSL engine,
2、通过engine创建 AudioPlayer和outputMix
3、指定AudioPlayer的输入为DataSource,输出为outputMix,outputMix是会关联到设备的默认输出。
4、启动播放。
下面详细讲解各个部分
2.2.1 Engine
OpenSL ES 里面最核心的对象,它主要提供如下两个功能: (1) 管理 Audio Engine 的生命周期。 (2) 提供管理接口: SLEngineItf,该接口可以用来创建所有其他的对象。
使用步骤: 1、创建Engine
参数说明:
- pEngine:指向输出的engine对象的指针。 numOptions:可选配置数组的大小。
- pEngineOptions:可选配置数组。 numInterfaces:对象要求支持的接口数目,不包含隐含的接口。
- pInterfaceId:对象需要支持的接口id的数组。
- pInterfaceRequired:指定每个要求接口的接口是可选或者必须的标志位数组。如果要求的接口没有实现,创建对象会失败并返回错误码SL_RESULT_FEATURE_UNSUPPORTED。
2、创建完engine后就可以通过获取管理接口:
3、然后通过管理接口iengine,就可以继续创建其他需要的对象。
2.2.2 AudioPlayer
音频播放对象,它需要指定输入(DataSource)和输出(DataSink) Datasource 代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的; DataSink 代表着输出的信息,即数据输出到哪儿、以什么样的参数来输出。
- DataSource 的定义如下:
- DataSink 的定义如下:
其中,pLocator 主要有如下几种:
- SLDataLocator_Address
- SLDataLocator_BufferQueue
- SLDataLocator_IODevice
- SLDataLocator_MIDIBufferQueue
- SLDataLocator_URI
也就是说,输入源/输出源,既可以是 URL,也可以 Device,或者来自于缓冲区队列等等。
那么我们在创建AudioPlayer对象前,还需要先创建输入和输出,输入我们使用一个缓冲队列,输出则使用outputMix输出到默认声音设备。
使用步骤: 1、输入源
2、输出源 outputMix对象需要通过接口创建,并调用初始化
3、创建AudioPlayer并初始化 AudioPlayer对象需要通过接口创建,并调用初始化
4、设置输入队列的回调函数,回调函数在数据不足时会内部调用。然后启动播放即可。 需要通过获取队列接口,并设置回调函数。 需要通过获取播放接口,并设置为播放状态,同时调用给队列压入一帧空数据。 需要注意的是,入队列的数据并非立刻播放,所以不能把这个数据立刻释放,否则会造成丢帧。
当播放队列数据为空时,会调用队列的回调函数,所以需要在回调函数中给队列注入音频数据
5、播放结束后,需要调用将各个对象释放。
opensl es参考自:https://www.jianshu.com/p/cccb59466e99