Open SL ES简介
OpenSL ES – 嵌入式音频加速标准。OpenSL ES™ 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。在Android中主要用到了一部分Open SL ES的功能,两者之间有交集但不完全一样,Android有自己的一部分扩展。
主要功能介绍
1、Assets目录音频播放
2、c头文件形式播放
3、本地音频播放
4、录音和回放
本文中主要介绍从缓冲队列中播放和录音到缓冲队列中,主要用到以下几个native方法。其他方法操作步骤类似,可以参考学习。
代码解析:
public static native void createEngine();
public static native void createBufferQueueAudioPlayer(int sampleRate, int samplesPerBuf);
public static native boolean createAudioRecorder();
public static native void startRecording();
public static native void shutdown();
从native方法的排序可以看出程序的调用流程
录音流程:
从图中可以看出不管是播放还是录音都必须先创建引擎,在Open SL ES中所有的对象创建过程都是一样的步骤。
1、create
2、Realize
3、GetInterface
一、Engine的创建基本就是这几个步骤,Interface 可以根据自己的业务需求来获取,不需要的可以删除。
// 创建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
// 获取引擎接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineItf);
// create output mix, with environmental reverb specified as a non-required interface
// const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
// SLboolean req[1] = {SL_BOOLEAN_FALSE};
result = (*engineItf)->CreateOutputMix(engineItf, &outputMixObject, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
二、 createBufferQueueAudioPlayer
创建的步骤和引擎类似,从上面的图片中可以出,多了一些东西需要配置,DataSource 和 DataSink,DataSource 顾名思义就是要播放的数据源,缓冲队列的播放形式只支持PCM格式的数据源,DataSink 就是音频输出,也就是创建引擎时获取的 outputMixObject 对象。PCM 数据源需要配置采样率、通道、定点、扬声器、小端模式,参数配置需要参考官方文档,有些参数是不支持的,需要测试。另一个比较重要的就是缓冲队列和播放回调,因为和录音一样,所以放到下面一起介绍。
// create BufferQueueAudioPlayer
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
1,
SL_SAMPLINGRATE_32,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN}; //合适pcm 单通道 采样率 定点 扬声器 小端
// if (sampleRate) {
// format_pcm.samplesPerSec = (SLuint32) sampleRate;
// }
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
//需要请求的接口 缓冲队列 音量
const SLInterfaceID idsAudioPlayer[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean reqAudioPlayer[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineItf)->CreateAudioPlayer(engineItf, &playerObject, &audioSrc, &audioSnk, 2,idsAudioPlayer, reqAudioPlayer);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the player
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);
result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueueItf);
result = (*playerBufferQueueItf)->RegisterCallback(playerBufferQueueItf, playCallback, audioProcess);
//静音模式/solo不支持单声道
// mute/solo is not supported for sources that are known to be mono, as this is
// get the mute/solo interface
//result = (*playerObject)->GetInterface(playerObject, SL_IID_MUTESOLO, &playerMuteSoloItf);
// get the volume interface
result = (*playerObject)->GetInterface(playerObject, SL_IID_VOLUME, &playerVolumeItf);
三 、createAudioRecorder
录音的创建和播放类似,只是它们的 DataSource 和 DataSink 不同,录音的数据源 DataSource 是来自麦克风,输出数据DataSink 和 播放的数据源是一样的,PCM格式的数据,配置方式也是一样。
//录音
// configure audio source
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource recordAudioSrc = {&loc_dev, NULL};
// configure audio sink
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};//缓冲的数量
SLDataFormat_PCM recordFormat_pcm = {SL_DATAFORMAT_PCM,
1,
SL_SAMPLINGRATE_32,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN};
// if (sampleRate) {
// recordFormat_pcm.samplesPerSec = (SLuint32) sampleRate;
// }
SLDataSink recordSink = {&loc_bq, &recordFormat_pcm};
const SLInterfaceID idsRecord[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean reqRecord[1] = {SL_BOOLEAN_TRUE};
//创建录音对象
result = (*engineItf)->CreateAudioRecorder(engineItf,
&recorderObject,
&recordAudioSrc,
&recordSink,
1,
idsRecord,
reqRecord);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the audio recorder
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// 获取录音接口
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderItf);
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&recorderBufferQueueItf);
result = (*recorderBufferQueueItf)->RegisterCallback(recorderBufferQueueItf, recordCallback,
audioProcess);
四、播放和录音的缓冲队列机制
在创建播放和录音的时候都设置了缓冲队列和回调,注意播放和录音代码中都配置了一个 SL_BUFFERSIZE 参数,这是我自定义的一个宏,根据名字可以推测这是设置缓冲队列的大小,也就是当前 BufferQueue 中最大可以容纳的buffer数量。 每次播完或录完一个 buffer 都会回调创建时 RegisterCallback 传入的callback方法,代表当前buffer数据已经处理完毕,buffer出队, 如果是录音你应该获取这个 buffer 保存下来,写文件或者其他操作都可以;如果是播放就代表 当前buffer已经播放完毕,你可以 Enqueue 下一个buffer了。Enqueue到队列中的buffer数量超过了你设置的 SL_BUFFERSIZE 值,会报错SL_RESULT_BUFFER_INSUFFICIENT。
五、需要注意的坑
当你需要停止录音和停止播放的时候,调用以下代码,官方案例也是这么做的,但是这么做会有bug,当你下次再开始录音或播放的时候,会报错SL_RESULT_BUFFER_INSUFFICIENT,也就是说队列满了。根据测试发现Clear函数并不起作用。因为设置了SL_PLAYSTATE_STOPPED状态,录音或播放已经停止了,所以不会再回调callback函数了,也就是代表剩余的buffer没用出队,所以会报错。
//停止播放
SLresult result = (*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void) result;
result = (*playerBufferQueueItf)->Clear(playerBufferQueueItf);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// 停止录音
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void)result;
result = (*recorderBufferQueue)->Clear(recorderBufferQueue);
assert(SL_RESULT_SUCCESS == result);
(void)result;
解决办法:
思路一:
在callback中判断缓冲队列的状态,等到buffer全部出队再停止播放或录音,这样就会有延迟,延迟时间是根据你设置的 SL_BUFFERSIZE 队列大小来决定的,所以 SL_BUFFERSIZE 不宜设置太大,官方DEMO推荐至少设置2个,buffer数组也不宜过大,假设采样率是32000,buffer是 short *buffer[512] 延迟是16毫秒,2个就是32毫秒,这样的延迟几乎可以忽略了。
AudioProcess *audioProcess = context;
SLAndroidSimpleBufferQueueState recQueueState;//缓冲区状态
(*bq)->GetState(bq, &recQueueState);
if (recQueueState.count == 0) {
SLresult result = (*audioProcess->playerItf)->SetPlayState(audioProcess->playerItf, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void) result;
result = (*audioProcess->playerBufferQueueItf)->Clear(audioProcess->playerBufferQueueItf);
assert(SL_RESULT_SUCCESS == result);
(void) result;
LOGD("停止播放");
}
思路二:
下次开始录制或播放的时候重置buffer数据,接着走回调函数,直接在callback中控制流程,这样就相当于 SL_PLAYSTATE_STOPPED 是一个暂停的状态,可以避免播放和录音的启动时间,也是一种不错的方案。
参考资料:
官方demo
Open SL ES相关的博客
Open SL ES 资料:ndk目录/docs/Additional_library_docs/opensles/OpenSL_ES_Specification_1.0.1.pdf