android平臺下基于OpenSL ES實現(xiàn)音頻錄制功能

概述

我們?nèi)粘T谔幚硪纛l錄制的時候,大部分情況下都是使用AudioRecord錄制原始的PCM數(shù)據(jù),但是音頻相關的處理通常都是在native層進行的,今天筆者要記錄一下在native層通過OpenSL ES來完成音頻的錄制。

配置權限

動態(tài)權限的申請這里不贅述

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

導入OpenSL ES庫

CMake方式:CMakeList.txt中加入

target_link_libraries(native-lib OpenSLES)

NDK Build方式:在Android.mk文件添加選項

LOCAL_LDLIBS = -lOpenSLES

源代碼中引入頭文件

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

錄制流程分析

image

開始錄制

SL_API SLresult SLAPIENTRY slCreateEngine(
    SLObjectItf             *pEngine,           //對象地址,用于傳出對象
    SLuint32                numOptions,         //配置參數(shù)數(shù)量
    const SLEngineOption    *pEngineOptions,    //配置參數(shù),為枚舉數(shù)組
    SLuint32                numInterfaces,      //支持的接口數(shù)量
    const SLInterfaceID     *pInterfaceIds,     //具體的要支持的接口,是枚舉的數(shù)組
    const SLboolean         *pInterfaceRequired //具體的要支持的接口是開放的還是關閉的,也是一個數(shù)組,這三個參數(shù)長度是一致的
);
void OpenSLESRecorder::StartRecord(const char *pcmPath, int sampleRate, int channels, int bitRate) {

    //打開輸出文件
    pcmFile = fopen(pcmPath, "w");

    recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2);

    //1. 調(diào)用全局方法創(chuàng)建一個引擎對象(OpenSL ES唯一入口)
    SLresult result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //2. 實例化這個對象
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //3. 從這個對象里面獲取引擎接口
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //4. 設置IO設備(麥克風)
    SLDataLocator_IODevice ioDevice = {
            SL_DATALOCATOR_IODEVICE,         //類型
            SL_IODEVICE_AUDIOINPUT,          //device類型 選擇了音頻輸入類型
            SL_DEFAULTDEVICEID_AUDIOINPUT,   //deviceID
            NULL                             //device實例
    };
    SLDataSource dataSource = {
            &ioDevice,                      //SLDataLocator_IODevice配置輸入
            NULL                             //輸入格式,采集的并不需要
    };

    //5. 設置輸出buffer隊列
    SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,    //類型 這里只能是這個常量
            2                                           //buffer的數(shù)量
    };
    //6. 設置輸出數(shù)據(jù)的格式
    SLDataFormat_PCM pcmFormat = {
            SL_DATAFORMAT_PCM,                             //輸出PCM格式的數(shù)據(jù)
            (SLuint32) channels,                                  //輸出的聲道數(shù)量
            SL_SAMPLINGRATE_44_1,                          //輸出的采樣頻率,這里是44100Hz
            SL_PCMSAMPLEFORMAT_FIXED_16,                   //輸出的采樣格式,這里是16bit
            SL_PCMSAMPLEFORMAT_FIXED_16,                   //一般來說,跟隨上一個參數(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配置輸出
            &pcmFormat                      //輸出數(shù)據(jù)格式
    };

    SLAndroidSimpleBufferQueueItf recorderBufferQueue; //Buffer接口

    //7. 創(chuàng)建錄制的對象
    const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioRecorder(engineEngine,        //引擎接口
                                                  &recorderObject,   //錄制對象地址,用于傳出對象
                                                  &dataSource,          //輸入配置
                                                  &audioSink,         //輸出配置
                                                  1,                  //支持的接口數(shù)量
                                                  id,                 //具體的要支持的接口
                                                  req                 //具體的要支持的接口是開放的還是關閉的
    );
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //8. 實例化這個錄制對象
    result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //9. 獲取錄制接口
    (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecorder);
    //10. 獲取Buffer接口
    (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                    &recorderBufferQueue);

    finished = false;

    result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(),
                                             recorderSize);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, RecordCallback,
                                                      this);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //11. 開始錄音
    (*recorderRecorder)->SetRecordState(recorderRecorder, SL_RECORDSTATE_RECORDING);

}

停止錄制

void OpenSLESRecorder::StopRecord() {
    if (NULL != recorderRecorder) {
        finished = true;
    }
}

回調(diào)函數(shù)

static void RecordCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *context) {

    LOGI("錄制大小: %d", recorderSize);

    OpenSLESRecorder *recorder = (OpenSLESRecorder *) context;

    if (NULL != recorder->recordBuffer) {
        fwrite(recorder->recordBuffer->getNowBuffer(), 1, recorderSize, recorder->pcmFile);
    }

    if (recorder->finished) {
        (*recorder->recorderRecorder)->SetRecordState(recorder->recorderRecorder,
                                                      SL_RECORDSTATE_STOPPED);
        //刷新緩沖區(qū)后,關閉流
        fclose(recorder->pcmFile);
        //釋放內(nèi)存
        delete recorder->recordBuffer;
        recorder->recordBuffer = NULL;
        LOGI("停止錄音");
    } else {
        (*bufferQueue)->Enqueue(bufferQueue, recorder->recordBuffer->getRecordBuffer(),
                                recorderSize);
    }
}

小結

如果參考官方的例子,整個過程并不算復雜,筆者也是參考官方的例子進行操作實踐

image

項目地址:audio-opensles
https://github.com/byhook/ffmpeg4android

參考

https://github.com/googlesamples/android-ndk/tree/master/native-audio
https://developer.android.com/ndk/guides/audio/opensl/getting-started

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容