前言
這篇文章簡單介紹下移動(dòng)端Android系統(tǒng)下利用OpenSL ES進(jìn)行音頻采集方法移剪。
按照慣例先上一份源碼 AudioRecordLib 苦银。
OpenSL ES采集的核心實(shí)現(xiàn)在于 openslescore.cpp 這個(gè)文件幔虏。
權(quán)限申請(qǐng)
想要使用OpenSL ES,需要在AndroidManifest.xml的配置文件里面增加權(quán)限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
OpenSL ES開發(fā)簡介
什么是OpenSL ES
OpenSL ES全稱為Open Sound Library for Embedded Systems贝椿,即嵌入式音頻加速標(biāo)準(zhǔn)瑟蜈。OpenSL ES是無授權(quán)費(fèi)渣窜、跨平臺(tái)乔宿、針對(duì)嵌入式系統(tǒng)精心優(yōu)化的硬件音頻加速 API。它為嵌入式移動(dòng)多媒體設(shè)備上的本地 應(yīng)用程序開發(fā)者提供了標(biāo)準(zhǔn)化坝橡、高性能计寇、低響應(yīng)時(shí)間的音頻功能實(shí)現(xiàn)方法,同時(shí)還實(shí)現(xiàn)了軟/硬件音頻性能的直接跨平臺(tái)部署狼讨,不僅降低了執(zhí)行難度朽基,而且促進(jìn)了高級(jí)音頻市場的發(fā)展稼虎。
OpenSL ES架構(gòu)原理
雖然OpenSL ES是基于C語言設(shè)計(jì)的API沉眶,但是其實(shí)基于對(duì)象和接口提供服務(wù)的,采用了面向?qū)ο蟮乃枷雭黹_發(fā)API捌肴。
這里簡單說一下OpenSL ES里面的對(duì)象和接口的概念:
- 對(duì)象:類似于C++中類用來提供一組資源極其狀態(tài)的抽象孽查,也就是我們可以根據(jù)特定類型type(例如音頻錄制type)來獲取一個(gè)音頻錄制的對(duì)象洲胖,但是對(duì)于這個(gè)對(duì)象我們并不能直接操作(換句話講也就是我們不能直接在這個(gè)對(duì)象調(diào)用開始/結(jié)束錄制的邏輯)腐晾。
- 接口:接口是對(duì)象提供一組特定功能方法的抽象巨柒,也就是可以從對(duì)象中獲取接口(例如從錄制對(duì)象中獲取錄制接口)珍坊,然后通過接口來改變對(duì)象的狀態(tài)(例如通過接口設(shè)置開始錄制)以便使用對(duì)象的功能(對(duì)于就是錄制功能)。
PS:對(duì)象可以有一個(gè)或者多個(gè)接口的實(shí)例,但是接口實(shí)例肯定只屬于一個(gè)對(duì)象,以上就是OpenSL ES的開發(fā)理念。
引用相關(guān)庫文件以及頭文件
怎么導(dǎo)入OpenSL ES庫
CMake方式:CMakeList.txt中加入
#找打Android lib庫里面的libOpenSLES.so的庫
find_library( OpenSLES-lib
OpenSLES )
#鏈接到你的native工程的庫
target_link_libraries( your-native.so
${OpenSLES-lib}
)
NDK Build方式:在Makefile文件Android.mk添加鏈接選項(xiàng)
LOCAL_LDLIBS = -lOpenSLES
引入頭文件
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
創(chuàng)建引擎對(duì)象
簡單介紹下入口slCreateEngine()這個(gè)全局方法:
SL_API SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine, //對(duì)象地址洪灯,用于傳出對(duì)象
SLuint32 numOptions, //配置參數(shù)數(shù)量
const SLEngineOption *pEngineOptions, //配置參數(shù),為枚舉數(shù)組
SLuint32 numInterfaces, //支持的接口數(shù)量
const SLInterfaceID *pInterfaceIds, //具體的要支持的接口憎夷,是枚舉的數(shù)組
const SLboolean *pInterfaceRequired //具體的要支持的接口是開放的還是關(guān)閉的,也是一個(gè)數(shù)組级及,這三個(gè)參數(shù)長度是一致的
);
一個(gè)較為完整的創(chuàng)建過程代碼示例:
SLObjectItf engine_object; //引擎對(duì)象
SLEngineItf engine_engine; //引擎接口
//調(diào)用全局方法創(chuàng)建一個(gè)引擎對(duì)象(OpenSL ES唯一入口)
slCreateEngine(&engine_object, 0, NULL, 0, NULL, NULL);
//實(shí)例化這個(gè)對(duì)象
(*engine_object)->Realize(engine_object, SL_BOOLEAN_FALSE);
//從這個(gè)對(duì)象里面獲取引擎接口
(*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine_engine);
當(dāng)然調(diào)用每一個(gè)API后要檢測其返回值是否等于 SL_RESULT_SUCCESS县踢,限于篇幅就在上面代碼沒有處理,后續(xù)的示例代碼也是同理。
設(shè)置IO設(shè)備(麥克風(fēng)) 輸入輸出
我們需要設(shè)置采集設(shè)備的一些輸入輸出配置:
//設(shè)置IO設(shè)備(麥克風(fēng))
SLDataLocator_IODevice io_device = {
SL_DATALOCATOR_IODEVICE, //類型 這里只能是SL_DATALOCATOR_IODEVICE
SL_IODEVICE_AUDIOINPUT, //device類型 選擇了音頻輸入類型
SL_DEFAULTDEVICEID_AUDIOINPUT, //deviceID 對(duì)應(yīng)的是SL_DEFAULTDEVICEID_AUDIOINPUT
NULL //device實(shí)例
};
SLDataSource data_src = {
&io_device, //SLDataLocator_IODevice配置輸入
NULL //輸入格式拯爽,采集的并不需要
};
//設(shè)置輸出buffer隊(duì)列
SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //類型 這里只能是SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
2 //buffer的數(shù)量
};
//設(shè)置輸出數(shù)據(jù)的格式
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM, //輸出PCM格式的數(shù)據(jù)
num_channels, //輸出的聲道數(shù)量
SL_SAMPLINGRATE_44_1, //輸出的采樣頻率,這里是44100Hz
SL_PCMSAMPLEFORMAT_FIXED_16, //輸出的采樣格式,這里是16bit
SL_PCMSAMPLEFORMAT_FIXED_16, //一般來說,跟隨上一個(gè)參數(shù)
SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT, //雙聲道配置焊傅,如果單聲道可以用 SL_SPEAKER_FRONT_CENTER
SL_BYTEORDER_LITTLEENDIAN //PCM數(shù)據(jù)的大小端排列
};
SLDataSink audioSink = {
&buffer_queue, //SLDataFormat_PCM配置輸出
&format_pcm //輸出數(shù)據(jù)格式
};
創(chuàng)建錄制器
主要是創(chuàng)建錄制對(duì)象和獲取錄制相關(guān)的接口:
SLObjectItf recorder_object; //錄制對(duì)象,這個(gè)對(duì)象我們從里面獲取了2個(gè)接口
SLRecordItf recorder_recoder; //錄制接口
SLAndroidSimpleBufferQueueItf recorder_buffer_queue; //Buffer接口
//創(chuàng)建錄制的對(duì)象骆姐,并且指定開放SL_IID_ANDROIDSIMPLEBUFFERQUEUE這個(gè)接口
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
(*engine_engine)->CreateAudioRecorder(engine_engine, //引擎接口
&recorder_object, //錄制對(duì)象地址,用于傳出對(duì)象
&data_src, //輸入配置
&audioSink, //輸出配置
1, //支持的接口數(shù)量
id, //具體的要支持的接口
req //具體的要支持的接口是開放的還是關(guān)閉的
);
//實(shí)例化這個(gè)錄制對(duì)象
(*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);
設(shè)置數(shù)據(jù)回調(diào)并且開始錄制
設(shè)置開始錄制狀態(tài),并通過回調(diào)函數(shù)獲取錄制的音頻PCM數(shù)據(jù):
int8_t *pcm_data; //數(shù)據(jù)緩存區(qū)
//申請(qǐng)一塊內(nèi)存窟社,注意RECORDER_FRAMES是自定義的一個(gè)宏,指的是采集的frame數(shù)量,具體還要根據(jù)你的采集格式(例如16bit)計(jì)算
pcm_data = static_cast<int8_t *>(malloc(sizeof(int8_t) * RECORDER_FRAMES));
//設(shè)置數(shù)據(jù)回調(diào)接口bqRecorderCallback寸潦,最后一個(gè)參數(shù)是可以傳輸自定義的上下文引用
(*recorder_buffer_queue)->RegisterCallback(recorder_buffer_queue, bqRecorderCallback, this);
//設(shè)置錄制器為錄制狀態(tài) SL_RECORDSTATE_RECORDING
(*recorder_recoder)->SetRecordState(recorder_recoder, SL_RECORDSTATE_RECORDING);
/在設(shè)置完錄制狀態(tài)后一定需要先Enqueue一次色鸳,這樣的話才會(huì)開始采集回調(diào)
(*recorder_buffer_queue)->Enqueue(recorder_buffer_queue, pcm_data, RECORDER_FRAMES);
//數(shù)據(jù)回調(diào)函數(shù)
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
//從pcm_data獲取RECORDER_FRAMES長度的PCM數(shù)據(jù)
//注意這個(gè)是另外一條采集線程回調(diào),可能需要加一個(gè)變量recodering控制退出采集
if (recodering)
{
pcm_write(pcm_data, RECORDER_FRAMES);
//取完數(shù)據(jù)见转,需要調(diào)用Enqueue觸發(fā)下一次數(shù)據(jù)回調(diào)
(*recorder_buffer_queue)->Enqueue(recorder_buffer_queue, pcm_data, RECORDER_FRAMES);
}
}
停止錄制和釋放OpenSL ES資源
如果我們不需要采集了赊抖,需要調(diào)用接口停止采集并在適當(dāng)?shù)臅r(shí)機(jī)釋放OpenSL ES相關(guān)資源浴鸿。
//設(shè)置錄制器為停止?fàn)顟B(tài) SL_RECORDSTATE_STOPPED
(*recorder_recoder)->SetRecordState(recorder_recoder, SL_RECORDSTATE_STOPPED);
//只需要銷毀OpenSL ES對(duì)象,接口不需要做Destroy處理。
(*recorder_object)->Destroy(recorder_object);
(*engine_object)->Destroy(engine_object);
//釋放緩存區(qū)內(nèi)存
free(pcm_data);
這樣就完整的結(jié)束了OpenSL ES的采集業(yè)務(wù)。
播放PCM文件
Audacity這個(gè)工具可以導(dǎo)入pcm原始文件薇缅,并且提供了波形圖查看和播放功能危彩。
操作流程是:
文件 => 導(dǎo)入 => 原始數(shù)據(jù) => 設(shè)置PCM數(shù)據(jù)格式 => 導(dǎo)入
具體效果圖如下:
結(jié)語
上一篇博客了介紹了Android利用AudioRecord進(jìn)行錄音導(dǎo)出PCM數(shù)據(jù)。
本文同步發(fā)布于簡書泳桦、CSDN汤徽。
End!