OpenSL ES的錄音要比播放簡單一些,在建立好引擎後,再建立好錄音接口基本就能夠錄音了。在這裏咱們作的是流式錄音,因此須要用至少2個buffer來緩存錄制好的PCM數據,這裏咱們能夠動態建立一個二維數組,裏面有2個buffer,而後每次錄音取出一個,錄製好後再寫入文件就能夠了,2個buffer依次來存儲PCM數據,這樣就能夠連續錄製流式音頻數據了,二維數組裏面本身維護了一個索引,來標識當前處於哪一個buffer錄製狀態,暴露給外部的只是調用方法而已,細節對外也是隱藏的。html
#ifndef OPENSLRECORD_RECORDBUFFER_H #define OPENSLRECORD_RECORDBUFFER_H class RecordBuffer { public: short **buffer; int index = -1; public: RecordBuffer(int buffersize); ~RecordBuffer(); /** * 獲得一個新的錄製buffer * @return */ short* getRecordBuffer(); /** * 獲得當前錄製buffer * @return */ short* getNowBuffer(); }; #endif //OPENSLRECORD_RECORDBUFFER_H
#include "RecordBuffer.h" RecordBuffer::RecordBuffer(int buffersize) { buffer = new short *[2]; for(int i = 0; i < 2; i++) { buffer[i] = new short[buffersize]; } } RecordBuffer::~RecordBuffer() { } short *RecordBuffer::getRecordBuffer() { index++; if(index > 1) { index = 0; } return buffer[index]; } short *RecordBuffer::getNowBuffer() { return buffer[index]; }
這個隊列其實就是PCM存儲的buffer,getRecordBuffer()爲即將要錄入PCM數據的buffer,getNowBuffer()是當前錄製好的PCM數據的buffer,能夠寫入文件,即咱們獲得的PCM數據。git
過程分爲:建立引擎->初始化IO設備(自動檢測麥克風等音頻輸入設備)->設置緩存隊列->設置錄製PCM數據規格->設置錄音器接口->設置隊列接口並設置錄音狀態爲錄製->開始錄音。github
const char *path = env->GetStringUTFChars(path_, 0); /** * PCM文件 */ pcmFile = fopen(path, "w"); /** * PCMbuffer隊列 */ recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2); SLresult result; /** * 建立引擎對象 */ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); /** * 設置IO設備(麥克風) */ SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; SLDataSource audioSrc = {&loc_dev, NULL}; /** * 設置buffer隊列 */ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; /** * 設置錄製規格:PCM、2聲道、44100HZ、16bit */ SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; SLDataSink audioSnk = {&loc_bq, &format_pcm}; const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; const SLboolean req[1] = {SL_BOOLEAN_TRUE}; /** * 建立錄製器 */ result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, id, req); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue); finished = false; result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); LOGD("開始錄音"); /** * 開始錄音 */ (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); env->ReleaseStringUTFChars(path_, path);
錄音回調以下:數組
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill // but instead, this is a one-time buffer so we stop recording LOGD("record size is %d", recorderSize); fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile); if(finished) { (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); fclose(pcmFile); LOGD("中止錄音"); } else{ (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); } }
這樣就完成了OPenSL ES的PCM音頻數據錄製,咱們這裏拿到了錄製的PCM數據能夠用mediacodec或ffmpeg來編碼成aac格式的音頻,也能夠直接用推流到服務器來實現音頻直播。緩存
完整代碼以下:服務器
#include <jni.h> #include <string> #include "AndroidLog.h" #include "RecordBuffer.h" #include "unistd.h" extern "C" { #include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> } //引擎接口 static SLObjectItf engineObject = NULL; //引擎對象 static SLEngineItf engineEngine; //錄音器接口 static SLObjectItf recorderObject = NULL; //錄音器對象 static SLRecordItf recorderRecord; //緩衝隊列 static SLAndroidSimpleBufferQueueItf recorderBufferQueue; //錄製大小設爲4096 #define RECORDER_FRAMES (2048) static unsigned recorderSize = RECORDER_FRAMES * 2; //PCM文件 FILE *pcmFile; //錄音buffer RecordBuffer *recordBuffer; bool finished = false; void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill // but instead, this is a one-time buffer so we stop recording LOGD("record size is %d", recorderSize); fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile); if(finished) { (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); fclose(pcmFile); LOGD("中止錄音"); } else{ (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); } } extern "C" JNIEXPORT void JNICALL Java_com_renhui_openslrecord_MainActivity_rdSound(JNIEnv *env, jobject instance, jstring path_) { const char *path = env->GetStringUTFChars(path_, 0); /** * PCM文件 */ pcmFile = fopen(path, "w"); /** * PCMbuffer隊列 */ recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2); SLresult result; /** * 建立引擎對象 */ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); /** * 設置IO設備(麥克風) */ SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; SLDataSource audioSrc = {&loc_dev, NULL}; /** * 設置buffer隊列 */ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; /** * 設置錄製規格:PCM、2聲道、44100HZ、16bit */ SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; SLDataSink audioSnk = {&loc_bq, &format_pcm}; const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; const SLboolean req[1] = {SL_BOOLEAN_TRUE}; /** * 建立錄製器 */ result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, id, req); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue); finished = false; result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); LOGD("開始錄音"); /** * 開始錄音 */ (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); env->ReleaseStringUTFChars(path_, path); }extern "C" JNIEXPORT void JNICALL Java_com_renhui_openslrecord_MainActivity_rdStop(JNIEnv *env, jobject instance) { // TODO if(recorderRecord != NULL) { finished = true; } }
有兩種方法:post
1. 使用Android OpenSL ES 開發:使用 OpenSL 播放 PCM 數據的demo進行播放。ui
2. 使用 ffplay 命令播放,命令爲:ffplay -f s16le -ar 44100 -ac 2 temp.pcm (命令由來:在錄製代碼裏的參數爲錄製規格:PCM、2聲道、44100HZ、16bit)this