前言
这篇文章简单介绍下移动端Android系统下利用OpenSL ES进行音频采集方法。
权限申请
想要使用 OpenSL ES,需要在AndroidManifest.xml的配置文件里面增加权限
代码语言:javascript复制<uses-permission android:name="android.permission.RECORD_AUDIO"/>
OpenSL ES开发简介
什么是OpenSL ES
OpenSL ES全称为Open Sound Library for Embedded Systems,即嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API。它为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现方法,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。
OpenSL ES架构原理
虽然OpenSL ES是基于C语言设计的API,但是其实基于对象和接口提供服务的,采用了面向对象的思想来开发API。
这里简单说一下OpenSL ES里面的对象和接口的概念:
- 「对象」:类似于C 中类用来提供一组资源极其状态的抽象,也就是我们可以根据特定类型type(例如音频录制type)来获取一个音频录制的对象,但是对于这个对象我们并不能直接操作(换句话讲也就是我们不能直接在这个对象调用开始/结束录制的逻辑)。
- 「接口」:接口是对象提供一组特定功能方法的抽象,也就是可以从对象中获取接口(例如从录制对象中获取录制接口),然后通过接口来改变对象的状态(例如通过接口设置开始录制)以便使用对象的功能(对于就是录制功能)。
「PS」:对象可以有一个或者多个接口的实例,但是接口实例肯定只属于一个对象,以上就是OpenSL ES的开发理念。
引用相关库文件以及头文件
怎么导入OpenSL ES库
「CMake方式」:CMakeList.txt中加入
代码语言:javascript复制#找打Android lib库里面的libOpenSLES.so的库
find_library( OpenSLES-lib
OpenSLES )
#链接到你的native工程的库
target_link_libraries( your-native.so
${OpenSLES-lib}
)
「NDK Build方式」:在Makefile文件Android.mk添加链接选项
代码语言:javascript复制LOCAL_LDLIBS = -lOpenSLES
引入头文件
代码语言:javascript复制#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
创建引擎对象
简单介绍下入口 slCreateEngine() 这个全局方法:
代码语言:javascript复制SL_API SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine, //对象地址,用于传出对象
SLuint32 numOptions, //配置参数数量
const SLEngineOption *pEngineOptions, //配置参数,为枚举数组
SLuint32 numInterfaces, //支持的接口数量
const SLInterfaceID *pInterfaceIds, //具体的要支持的接口,是枚举的数组
const SLboolean *pInterfaceRequired //具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
);
一个较为完整的创建过程代码示例:
代码语言:javascript复制SLObjectItf engine_object; //引擎对象
SLEngineItf engine_engine; //引擎接口
//调用全局方法创建一个引擎对象(OpenSL ES唯一入口)
slCreateEngine(&engine_object, 0, NULL, 0, NULL, NULL);
//实例化这个对象
(*engine_object)->Realize(engine_object, SL_BOOLEAN_FALSE);
//从这个对象里面获取引擎接口
(*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine_engine);
当然调用每一个API后要检测其返回值是否等于 「SL_RESULT_SUCCESS」,限于篇幅就在上面代码没有处理,后续的示例代码也是同理。
设置IO设备(麦克风) 输入输出
我们需要设置采集设备的一些输入输出配置:
代码语言:javascript复制//设置IO设备(麦克风)
SLDataLocator_IODevice io_device = {
SL_DATALOCATOR_IODEVICE, //类型 这里只能是SL_DATALOCATOR_IODEVICE
SL_IODEVICE_AUDIOINPUT, //device类型 选择了音频输入类型
SL_DEFAULTDEVICEID_AUDIOINPUT, //deviceID 对应的是SL_DEFAULTDEVICEID_AUDIOINPUT
NULL //device实例
};
SLDataSource data_src = {
&io_device, //SLDataLocator_IODevice配置输入
NULL //输入格式,采集的并不需要
};
//设置输出buffer队列
SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //类型 这里只能是SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
2 //buffer的数量
};
//设置输出数据的格式
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM, //输出PCM格式的数据
num_channels, //输出的声道数量
SL_SAMPLINGRATE_44_1, //输出的采样频率,这里是44100Hz
SL_PCMSAMPLEFORMAT_FIXED_16, //输出的采样格式,这里是16bit
SL_PCMSAMPLEFORMAT_FIXED_16, //一般来说,跟随上一个参数
SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT, //双声道配置,如果单声道可以用 SL_SPEAKER_FRONT_CENTER
SL_BYTEORDER_LITTLEENDIAN //PCM数据的大小端排列
};
SLDataSink audioSink = {
&buffer_queue, //SLDataFormat_PCM配置输出
&format_pcm //输出数据格式
};
创建录制器
主要是创建录制对象和获取录制相关的接口:
代码语言:javascript复制SLObjectItf recorder_object; //录制对象,这个对象我们从里面获取了2个接口
SLRecordItf recorder_recoder; //录制接口
SLAndroidSimpleBufferQueueItf recorder_buffer_queue; //Buffer接口
//创建录制的对象,并且指定开放SL_IID_ANDROIDSIMPLEBUFFERQUEUE这个接口
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
(*engine_engine)->CreateAudioRecorder(engine_engine, //引擎接口
&recorder_object, //录制对象地址,用于传出对象
&data_src, //输入配置
&audioSink, //输出配置
1, //支持的接口数量
id, //具体的要支持的接口
req //具体的要支持的接口是开放的还是关闭的
);
//实例化这个录制对象
(*recorder_object)->Realize(recorder_object, SL_BOOLEAN_FALSE);
//获取录制接口
(*recorder_object)->GetInterface(recorder_object, SL_IID_RECORD, &recorder_recoder);
//获取Buffer接口
(*recorder_object)->GetInterface(recorder_object, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorder_buffer_queue);
设置数据回调并且开始录制
设置开始录制状态,并通过回调函数获取录制的音频 PCM 数据:
代码语言:javascript复制int8_t *pcm_data; //数据缓存区
//申请一块内存,注意RECORDER_FRAMES是自定义的一个宏,指的是采集的frame数量,具体还要根据你的采集格式(例如16bit)计算
pcm_data = static_cast<int8_t *>(malloc(sizeof(int8_t) * RECORDER_FRAMES));
//设置数据回调接口bqRecorderCallback,最后一个参数是可以传输自定义的上下文引用
(*recorder_buffer_queue)->RegisterCallback(recorder_buffer_queue, bqRecorderCallback, this);
//设置录制器为录制状态 SL_RECORDSTATE_RECORDING
(*recorder_recoder)->SetRecordState(recorder_recoder, SL_RECORDSTATE_RECORDING);
/在设置完录制状态后一定需要先Enqueue一次,这样的话才会开始采集回调
(*recorder_buffer_queue)->Enqueue(recorder_buffer_queue, pcm_data, RECORDER_FRAMES);
//数据回调函数
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
//从pcm_data获取RECORDER_FRAMES长度的PCM数据
//注意这个是另外一条采集线程回调,可能需要加一个变量recodering控制退出采集
if (recodering)
{
pcm_write(pcm_data, RECORDER_FRAMES);
//取完数据,需要调用Enqueue触发下一次数据回调
(*recorder_buffer_queue)->Enqueue(recorder_buffer_queue, pcm_data, RECORDER_FRAMES);
}
}
停止录制和释放OpenSL ES资源
如果我们不需要采集了,需要调用接口停止采集并在适当的时机释放 OpenSL ES 相关资源。
代码语言:javascript复制//设置录制器为停止状态 SL_RECORDSTATE_STOPPED
(*recorder_recoder)->SetRecordState(recorder_recoder, SL_RECORDSTATE_STOPPED);
//只需要销毁OpenSL ES对象,接口不需要做Destroy处理。
(*recorder_object)->Destroy(recorder_object);
(*engine_object)->Destroy(engine_object);
//释放缓存区内存
free(pcm_data);
这样就完整的结束了OpenSL ES的采集业务。
播放PCM文件
Audacity这个工具可以导入pcm原始文件,并且提供了波形图查看和播放功能。 「操作流程是:」 文件 => 导入 => 原始数据 => 设置PCM数据格式 => 导入 「具体效果图如下:」
结语
上一篇博客了介绍了Android利用AudioRecord进行录音导出PCM数据。 本文同步发布于简书、CSDN。
作者:码农叔叔 来源:https://www.jianshu.com/p/0cb2ba3171b8
-- END --