Android OpenSL ES 開發:Android OpenSL 錄製 PCM 音頻數據

1、實現說明

OpenSL ES的錄音要比播放簡單一些,在建立好引擎後,再建立好錄音接口基本就能夠錄音了。在這裏咱們作的是流式錄音,因此須要用至少2個buffer來緩存錄制好的PCM數據,這裏咱們能夠動態建立一個二維數組,裏面有2個buffer,而後每次錄音取出一個,錄製好後再寫入文件就能夠了,2個buffer依次來存儲PCM數據,這樣就能夠連續錄製流式音頻數據了,二維數組裏面本身維護了一個索引,來標識當前處於哪一個buffer錄製狀態,暴露給外部的只是調用方法而已,細節對外也是隱藏的。html

2、編碼實現

一、編寫緩存buffer隊列:RecordBuffer.h、RecordBuffer.cpp

#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

二、使用OpenSL ES錄製PCM數據

過程分爲:建立引擎->初始化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;
    }
}

3、驗證錄製成果

有兩種方法: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

4、參考源碼

https://github.com/renhui/OpenSLRecord 編碼

相關文章
相關標籤/搜索