Android音視頻(五) OpenSL ES錄製、播放音頻

Android音視頻(一) Camera2 API採集數據java

Android音視頻(二)音頻AudioRecord和AudioTrackgit

Android音視頻(三)FFmpeg Camera2推流直播github

Android音視頻(四)MediaCodec編解碼AACbash

OpenSL ES (Open Sound Library for Embedded Systems)是無受權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。它爲嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平臺部署,下降執行難度,促進高級音頻市場的發展。簡單來講OpenSL ES是一個嵌入式跨平臺免費的音頻處理庫。函數

在Android中通常使用AudioRecord、MediaRecorder對音頻進行採集,使用MediaPlayer、AudioTrack、SoundPool進行音頻播放。但這些都是在Java層上的接口,若是使用FFmpeg在C/C++層作音視頻處理,那麼調用這幾個方法就比較麻煩了,因此Android NDK也提供了一個叫作OpenSL的C語言引擎用於聲音的處理,這篇博客就是簡單使用OpenSL去錄製、播放音頻,基於以前作的AudioDemo開發。post

開發流程

OpenSL ES 的開發流程主要有以下6個步驟:性能

一、建立接口對象優化

二、設置混音器ui

三、建立播放器(錄音器)spa

四、設置緩衝隊列和回調函數

五、設置播放狀態

六、啓動回調函數

其中第4步和第6步是OpenSL ES 播放PCM等數據格式的音頻是須要用到的。

代碼實現

定義Native方法

//播放音頻
    public native int play(String filePath);

    //中止播放音頻
    public native int playStop();
    
    //錄製音頻
    public native int record(String filePath);
    
    //中止錄製音頻
    public native int stopRecod();
    
複製代碼

錄音

參數配置

//設置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格式的數據
            1,                                             //輸出的聲道數量
            SL_SAMPLINGRATE_44_1,                          //輸出的採樣頻率,這裏是44100Hz
            SL_PCMSAMPLEFORMAT_FIXED_16,                   //輸出的採樣格式,這裏是16bit
            SL_PCMSAMPLEFORMAT_FIXED_16,                   //通常來講,跟隨上一個參數
            SL_SPEAKER_FRONT_LEFT,  //雙聲道配置,若是單聲道能夠用 SL_SPEAKER_FRONT_CENTER
            SL_BYTEORDER_LITTLEENDIAN                      //PCM數據的大小端排列
    };
    SLDataSink audioSink = {
            &buffer_queue,                   //SLDataFormat_PCM配置輸出
            &format_pcm                      //輸出數據格式
    };

複製代碼

錄音流程

//1 建立引擎
    SLEngineItf eng = CreateRecordSL();
    if (eng) {
        LOGE("CreateSL success! ");
    } else {
        LOGE("CreateSL failed! ");
    }


    //2 建立錄製的對象,而且指定開放SL_IID_ANDROIDSIMPLEBUFFERQUEUE這個接口
    const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    re = (*eng)->CreateAudioRecorder(eng,        //引擎接口
                                     &recorder_object,   //錄製對象地址,用於傳出對象
                                     &data_src,          //輸入配置
                                     &audioSink,         //輸出配置
                                     1,                  //支持的接口數量
                                     id,                 //具體的要支持的接口
                                     req                 //具體的要支持的接口是開放的仍是關閉的
    );

    if (re != SL_RESULT_SUCCESS) {
        LOGE("CreateAudioRecorder failed!");
        return -1;
    }

    //實例化這個錄製對象
    re = (*recorder_object)->Realize(recorder_object, SL_BOOLEAN_FALSE);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("Realize failed!");
    }

    //3 獲取錄製接口
    re = (*recorder_object)->GetInterface(recorder_object, SL_IID_RECORD, &recordItf);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("GetInterface1 failed!");
    }
    //獲取Buffer接口
    re = (*recorder_object)->GetInterface(recorder_object, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                          &recorder_buffer_queue);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("GetInterface2 failed!");
    }

    //申請一塊內存,注意RECORDER_FRAMES是自定義的一個宏,指的是採集的frame數量,具體還要根據你的採集格式(例如16bit)計算
    pcm_data = malloc(BUFFER_SIZE_IN_BYTES);

    //4 設置數據回調接口bqRecorderCallback,最後一個參數是能夠傳輸自定義的上下文引用
    re = (*recorder_buffer_queue)->RegisterCallback(recorder_buffer_queue, bqRecorderCallback, 0);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("RegisterCallback failed!");
    }
    //5 設置錄製器爲錄製狀態 SL_RECORDSTATE_RECORDING
    re = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("SetRecordState failed!");
    }
    //6 在設置完錄製狀態後必定須要先Enqueue一次,這樣的話纔會開始採集回調
    re = (*recorder_buffer_queue)->Enqueue(recorder_buffer_queue, pcm_data, 8192);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("Enqueue failed!");
    }

複製代碼

回調函數

//數據回調函數
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {

    fwrite(pcm_data, BUFFER_SIZE_IN_BYTES, 1, gFile);
    //取完數據,須要調用Enqueue觸發下一次數據回調
    (*bq)->Enqueue(bq, pcm_data, BUFFER_SIZE_IN_BYTES);

}
複製代碼

播放

//1 建立引擎
    SLEngineItf eng = CreateSL();
    if (eng) {
        LOGE("CreateSL success! ");
    } else {
        LOGE("CreateSL failed! ");
        return -1;
    }

    //2 建立混音器
    SLObjectItf mix = NULL;
    SLresult re = 0;

    re = (*eng)->CreateOutputMix(eng, &mix, 0, 0, 0);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("SL_RESULT_SUCCESS failed!");
        return -1;
    }

    re = (*mix)->Realize(mix, SL_BOOLEAN_FALSE);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("(*mix)->Realize failed!");
        return -1;
    }


    SLDataLocator_OutputMix outmix = {SL_DATALOCATOR_OUTPUTMIX, mix};
    SLDataSink audioSink = {&outmix, 0};

    //3 配置音頻信息
    //數據定位器 就是定位要播放聲音數據的存放位置,分爲4種:內存位置,輸入/輸出設備位置,緩衝區隊列位置,和midi緩衝區隊列位置。
    SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    //音頻格式
    SLDataFormat_PCM pcm = {
            SL_DATAFORMAT_PCM,
            1,//    聲道數
            SL_SAMPLINGRATE_44_1,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT,
            SL_BYTEORDER_LITTLEENDIAN //字節序,小端
    };
    SLDataSource ds = {&que, &pcm};


    //4 建立播放器
    SLObjectItf player = NULL;
    SLPlayItf iplayer = NULL;
    SLAndroidSimpleBufferQueueItf pcmQue = NULL;
    const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[] = {SL_BOOLEAN_TRUE};
    re = (*eng)->CreateAudioPlayer(eng, &player, &ds, &audioSink,
                                   sizeof(ids) / sizeof(SLInterfaceID), ids, req);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("CreateAudioPlayer failed!");
    } else {
        LOGE("CreateAudioPlayer success!");
    }
    (*player)->Realize(player, SL_BOOLEAN_FALSE);
    //獲取player接口
    re = (*player)->GetInterface(player, SL_IID_PLAY, &iplayer);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("GetInterface SL_IID_PLAY failed!");
    }
    re = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &pcmQue);
    if (re != SL_RESULT_SUCCESS) {
        LOGE("GetInterface SL_IID_BUFFERQUEUE failed!");
    }

    //設置回調函數,播放隊列空調用
    (*pcmQue)->RegisterCallback(pcmQue, pcmCallBack, 0);

    //5 設置爲播放狀態
    (*iplayer)->SetPlayState(iplayer, SL_PLAYSTATE_PLAYING);

    //6 啓動隊列回調
    (*pcmQue)->Enqueue(pcmQue, "", 1);
複製代碼

回調保存數據

//回調函數
void pcmCallBack(SLAndroidSimpleBufferQueueItf bf, void *contex) {
    static char buf[1024 * 1024] = "";
    if (feof(File) == 0) { //沒到結尾
        int len = (int) fread(&buf, 1, 1024, File);
        if (len > 0) {
            // 加入隊列
            (*bf)->Enqueue(bf, &buf, len);
        }
    }
}
複製代碼

若有問題歡迎留言,Github源碼-AudioDemo-openSLActivity

相關文章
相關標籤/搜索