Android Audio 输出 OpenSL
来源:互联网 发布:有关大数据的论文题目 编辑:程序博客网 时间:2024/05/22 14:56
http://www.jianshu.com/p/2b8d2de9a47b
OpenSL ES:
Advantages:
- Low Level Audio API in Android
- Device Independent on Android Phones
- Good for Gaming
Disadvantages:
- Supports only on 2.3+ os
AudioTrack:
Advantages:
- High Level API
Disadvantages:
- Works on Java layer and Native code has to call javalayer to play audio.
开发Android上的音频应用,最常见的是使用MediaRecorder
和MediaPlayer
来实现音频的录制和播放,更基础点的会使用AudioRecord
和AudioTrack
来实现。用这两种方式已经能应对绝大部分的音频开发需求了。更底层的API,如NDK层的OpenSL ES
则鲜有问津。
最近因为工作需要,接触了NDK层相关API,这里简要记录下OpenSL ES
相关的知识。
关于OpenSL ES
OpenSL ES
官网OpenSL ES
Wiki- Google 官方的
OpenSL ES
介绍 Android OpenSL ES
编程要点- 高性能音频基础
HelloWorld
不同于传统的HelloWorld程序,这个示例稍微复杂一点,而且这回我们的实现,将让我们听到这句经典的编程入门欢迎语。
实现思路
- 创建并初始化Audio Engine(音频引擎,是和底层交互的入口)
- 打开OutputMix(音频输出),配置相关参数、Buffer Queue(缓冲队列),以便进行音频播放
- 打开Audio Input(音频输入),配置相关参数,配置Buffer Queue,以便获取音频输入
- 设置输出、输入的Callback(回调函数),实现将输入传给输出的逻辑
- 启动音频录制
也就是,这个程序实现的功能是:将话筒录制的声音,再播放出来,也就是返听的效果。
代码实现
1. 创建并初始化Audio Engine
// 创建Audio Engineresult = slCreateEngine(&openSLEngine, 0, NULL, 0, NULL, NULL);// 初始化上一步得到的openSLEngineresult = (*openSLEngine)->Realize(openSLEngine, SL_BOOLEAN_FALSE);// 获取SLEngine接口对象,后续的操作将使用这个对象SLEngineItf openSLEngineInterface = NULL;result = (*openSLEngine)->GetInterface(openSLEngine, SL_IID_ENGINE, &openSLEngineInterface);
2. 音频输出
2.1 打开音频输出设备
// 相关参数const SLInterfaceID ids[] = {SL_IID_VOLUME};const SLboolean req[] = {SL_BOOLEAN_FALSE};// 使用第一步的openSLEngineInterface,创建音频输出Output Mixresult = (*openSLEngineInterface)->CreateOutputMix(openSLEngineInterface, &outputMix, 0, ids, req);// 初始化outputMixresult = (*outputMix)->Realize(outputMix, SL_BOOLEAN_FALSE);// 由于不需要操作到ouputMix,所以这一步就不去获取它的接口对象
2.2 配置相关参数
// Buffer Queue的参数SLDataLocator_AndroidSimpleBufferQueue outputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };// 设置音频格式SLDataFormat_PCM outputFormat = { SL_DATAFORMAT_PCM, 2, samplerate, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };// 输出源SLDataSource outputSource = { &outputLocator, &outputFormat };// 输出管道SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, outputMix };SLDataSink outputSink = { &outputMixLocator, NULL };
2.3 创建播放器
// 参数const SLInterfaceID outputInterfaces[1] = { SL_IID_BUFFERQUEUE };const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };// 创建音频播放对象AudioPlayerresult = (*openSLEngineInterface)->CreateAudioPlayer(openSLEngineInterface, &audioPlayerObject, &outputSource, &outputSink, 1, outputInterfaces, requireds);// 初始化AudioPlayerresult = (*audioPlayerObject)->Realize(audioPlayerObject, SL_BOOLEAN_FALSE);// 获取音频输出的BufferQueue接口SLAndroidSimpleBufferQueueItf outputBufferQueueInterface = NULL;result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &outputBufferQueueInterface); // 获取播放器接口SLPlayItf outputPlayInterface;result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_PLAY, &audioPlayerInterface);
3. 音频输入
3.1 配置参数
// 参数SLDataLocator_IODevice deviceInputLocator = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };SLDataSource inputSource = { &deviceInputLocator, NULL };SLDataLocator_AndroidSimpleBufferQueue inputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };SLDataFormat_PCM inputFormat = { SL_DATAFORMAT_PCM, 2, samplerate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };SLDataSink inputSink = { &inputLocator, &inputFormat };const SLInterfaceID inputInterfaces[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
3.2 创建录制器
// 创建AudioRecorderresult = (*openSLEngineInterface)->CreateAudioRecorder(openSLEngineInterface, &andioRecorderObject, &inputSource, &inputSink, 2, inputInterfaces, requireds);// 初始化AudioRecorderresult = (*andioRecorderObject)->Realize(andioRecorderObject, SL_BOOLEAN_FALSE);// 获取音频输入的BufferQueue接口result = (*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &inputBufferQueueInterface);// 获取录制器接口SLRecordItf audioRecorderInterface;(*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_RECORD, &audioRecorderInterface);
4. 配置回调并启动输入、输出
4.1 配置输入、输出回调
// 输出回调result = *outputBufferQueueInterface)->RegisterCallback(outputBufferQueueInterface, outputCallback, NULL);// 输入回调result = (*inputBufferQueueInterface)->RegisterCallback(inputBufferQueueInterface, inputCallback, NULL);
4.2 启动输入输出
// 设置为播放状态result = (*audioPlayerInterface)->SetPlayState(audioPlayerInterface, SL_PLAYSTATE_PLAYING);// 设为录制状态result = (*andioRecorderObject)->SetRecordState(andioRecorderObject, SL_RECORDSTATE_RECORDING);// 启动回调机制(*inputBufferQueueInterface)->Enqueue(inputBufferQueueInterface, inputBuffers[0], buffersize * 4);(*outputBufferQueueInterface)->Enqueue(outputBufferQueueInterface, outputBuffers[0], buffersize * 4);
4.3 回调函数的实现
// 音频输入回调static void inputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) { // 获取同步锁 pthread_mutex_lock(&mutex); // 取一个可用的缓存 short int *inputBuffer = inputBuffers[inputBufferWrite]; if (inputBuffersAvailable == 0) inputBufferRead = inputBufferWrite; // 可用缓存+1 inputBuffersAvailable++; if (inputBufferWrite < numBuffers - 1) inputBufferWrite++; else inputBufferWrite = 0; pthread_mutex_unlock(&mutex); // 调用BufferQueue的Enqueue方法,把输入数据取到inputBuffer (*bufferQueue)->Enqueue(bufferQueue, inputBuffer, buffersize * 4);}// 音频输出回调static void outputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) { short int *outputBuffer = outputBuffers[outputBufferIndex]; pthread_mutex_lock(&mutex); if (inputBuffersAvailable < 1) { pthread_mutex_unlock(&mutex); memset(outputBuffer, 0, buffersize * 4); } else { short int *inputBuffer = inputBuffers[inputBufferRead]; if (inputBufferRead < numBuffers - 1) inputBufferRead++; else inputBufferRead = 0; inputBuffersAvailable--; pthread_mutex_unlock(&mutex); memcpy(outputBuffer, inputBuffer, buffersize * 4); } (*bufferQueue)->Enqueue(bufferQueue, outputBuffer, buffersize * 4); if (outputBufferIndex < numBuffers - 1) outputBufferIndex++; else outputBufferIndex = 0;}
回调函数使用生产者-消费者机制实现,当输入可用的时候,就从输入的缓冲队列里取数据出来,放到inputBuffers里;然后当输出准备就绪的时候,再从inputBuffers里取出数据,复制一份,然后放入输出的缓冲队列里。就这样实现了把音频输出转到音频输出的效果。
关于OpenSL的使用
使用OpenSL相关API的通用步骤是:
- 创建对象(通过带有create的函数)
- 初始化(通过Realize函数)
- 获取接口来使用相关功能(通过GetInterface函数)
OpenSL使用回调机制来访问音频IO,但不像跟Jack、CoreAudio那些音频异步IO框架,OpenSL 的回调里并不会把音频数据作为参数传递,回调方法仅仅是告诉我们:BufferQueue已经就绪,可以接受/获取数据了。
使用SLBufferQueueItf. Enqueue
函数从(往)音频设备获取(放入)数据。完整的函数签名是:SLresult (*Enqueue) (SLBufferQueueItf self, const void *pBuffer, SLuint32 size);
当BufferQueue就绪,这个方法就应被调用。当开启录制或开始播放时,BufferQueue就可以接受数据。这之后,回调机制通过回调来告知应用程序它已经准备好,可以消费(提供)数据。
Enqueue
方法可以在回调里调用,可以不。
如果选择在回调里调用,那么在开始播放(录制)的时候,需要先调用Enqueue
来启动回调机制,否则回调将不会被调用到。
如果选择不在回调里调用,回调则用于通知程序,它准备就绪了。程序可以在得到足够的数据缓存之后,再把数据给它处理。这示例使用的是前一种方式,在回调里调用Enqueue
。
That's all
使用OpenSL ES
可以更高效的使用Android的音频系统,尤其是需要低延迟的场景,如返听。随着Android设备性能的提升,以及Android系统的不断优化,音频延迟的问题已经有了可观的性能提升。而在游戏领域,OpenSL ES
的高性能也能提供更棒的游戏体验,甚至让移动平台也有打造《吟诵者(In Verbis Virtus)》这种语音类游戏的可能。
But not ALL
对于OpenSL ES
也仅仅是草草接触,如有不对或疏漏的地方,还请大家指正。
- Android Audio 输出 OpenSL
- Android Audio上层架构OpenSL ES、AAudio
- Android Audio: Problems, Hidden Limitations and OpenSL ES
- Android的声音编程--使用OpenSL ES Audio
- Android的声音编程--使用OpenSL ES Audio
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换 .
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- Android中的Audio播放:控制Audio输出通道切换
- 二维数组求和,,,针对键名数量不等的情况
- 目标跟踪算法----KCF进阶(基于KCF改进的算法总结)
- Codeforces Round #417:E. FountainsSagheer and Apple Tree(树上博弈)
- 5.Android-Manifest.xml文件注册活动、声明主活动
- UltraEdit v24破解
- Android Audio 输出 OpenSL
- 浅谈为什么要义无反顾的做游戏
- Timer,单例redis,HttpClient,反射 手记
- 图邻接表 和 括扑排序
- 自己实现ArrayList
- 使用QtCreator编译Python程序
- 委托和协议的区别
- 最近开发问题记录
- Java设计模式透析之 —— 组合(Composite)