Android音频开发之OpenSL ES
https://www.jianshu.com/p/2b8d2de9a47b
开发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 Engine
result = slCreateEngine(&openSLEngine, 0, NULL, 0, NULL, NULL);// 初始化上一步得到的openSLEngine
result = (*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 Mix
result = (*openSLEngineInterface)->CreateOutputMix(openSLEngineInterface, &outputMix, 0, ids, req);// 初始化outputMix
result = (*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 };// 创建音频播放对象AudioPlayer
result = (*openSLEngineInterface)->CreateAudioPlayer(openSLEngineInterface, &audioPlayerObject, &outputSource, &outputSink, 1, outputInterfaces, requireds);// 初始化AudioPlayer
result = (*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 创建录制器
// 创建AudioRecorder
result = (*openSLEngineInterface)->CreateAudioRecorder(openSLEngineInterface, &andioRecorderObject, &inputSource, &inputSink, 2, inputInterfaces, requireds);// 初始化AudioRecorder
result = (*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;// 可用缓存+1inputBuffersAvailable++;if (inputBufferWrite < numBuffers - 1) inputBufferWrite++; elseinputBufferWrite = 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
也仅仅是草草接触,如有不对或疏漏的地方,还请大家指正。
作者:罗力
链接:https://www.jianshu.com/p/2b8d2de9a47b
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Android音频开发之OpenSL ES相关推荐
- Android NDK开发之旅31 FFmpeg音频解码
###前言 #####基于Android NDK开发之旅30--FFmpeg视频播放这篇文章,我们已经学会视频解码基本过程.这篇文章就对音频解码进行分析. #####音频解码和视频解码的套路基本是一样 ...
- 【Android音视频】OpenSL ES音频播放示例一
本文将实现一个使用OpenSL ES来播放assets目录下mp3歌曲的demo(实际推荐大家使用oboe库). Android NDK之高性能音频https://developer.android. ...
- Android NDK开发之 NEON基础介绍
原文:http://blog.csdn.net/app_12062011/article/details/50434259 Android NDK开发之 NEON基础介绍 这是官方介绍: http:/ ...
- Android底层开发之Audio HAL Android Audio Overview
http://blog.csdn.net/kangear/article/details/44939429 Android底层开发之Audio HAL 在Android音频底层调试-基于tinyals ...
- android离线语音开发,Android应用开发之Android 云之声离线语音合成
本文将带你了解Android应用开发之Android 云之声离线语音合成,希望本文对大家学Android有所帮助. 离线语音解析 public class SpeechUtilOffline impl ...
- android vr播放器 开发,Android应用开发之Android VR Player(全景视频播放器)- ExoPlayer播放器MPEG-DASH视频播放...
本文将带你了解Android应用开发之Android VR Player(全景视频播放器)- ExoPlayer播放器MPEG-DASH视频播放,希望本文对大家学Android有所帮助. Androi ...
- Android 耳返实践 OpenSL ES AAudio Oboe
耳返概述: 耳返主要实现监听的功能,在低延时的情况下可以给主播一个比较真实音频的反馈,在演唱会等专业场景里比较常用. 技术实现上来说就是要时时的把录制进的音频数据立刻播放出去,当然这个过程要低延迟. ...
- Android底层开发之Audio HAL
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Andr ...
- 镜像处理坐标 android,Android应用开发之Android重写ImageView实现图片镜像效果的代码教程...
本文将带你了解Android应用开发之Android重写ImageView实现图片镜像效果的代码教程,希望本文对大家学Android有所帮助. 前两天朋友问我一个问题,如何实现从手机系统相册加载一张图 ...
最新文章
- 号称下一代监控系统,来看看它有多强!
- ucl计算机硕士选课,伦敦大学学院UCL这4个超酷的机器学习专业
- java 对象 php对象_java对象是什么?
- CreateEvent的用法
- 前大灯是近光灯还是远光灯_汽修案例:长安福特翼博前大灯间歇自动点亮
- 用 Python 实现一个大数据搜索引擎
- ActiveReports 报表应用教程 (8)---交互式报表之动态过滤
- 微信小程序 本地mysql_微信小程序系列之使用缓存在本地模拟服务器数据库
- 赞!清华大学发布首个自动图机器学习工具包AutoGL
- 1: 认识WPF和一个小Demon
- log4j容器初始化探究
- 允许其他用户通过本计算机连接+连接手机,如何用手机搜索到的WF网络通过数据线连接台式电脑,让台式电脑共享网络...
- ibatis学习(一)--ibatis介绍以及用例 [转]
- Illustrator导入word中的visio图片方法
- Servlet是什么
- 【房价预测】BP神经网络回归的现实应用-上海市二手房价格影响因素分析——思路剖析和结果分享
- 软件文档写作学习总结——绪论
- 微信小程序----返回上一页刷新或当前页刷新
- 哔咔官网打不开显示黑屏?
- PCA主成分分析法浅理解
热门文章
- 广告投放方案-精准广告投放方案应该怎么做?
- c语言双重循环教程,C语言教程之如何进行循环的嵌套详细程序实例说明
- mysql外键约束案例_详解MySQL中的外键约束问题
- 几个常用的js 函数汇总大全
- Android开发日志打卡APP(一)
- PDF电子书制作书签目录全过程
- 设计模式期末考试复习客观题
- Continued Fraction(https://acs.jxnu.edu.cn/problem/ICPCJX2021B)
- sd卡--1--驱动
- java nodelist.item_XPath NodeList顺序(Java)