zoukankan      html  css  js  c++  java
  • Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据

    一、实现说明

    OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了。在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以动态创建一个二维数组,里面有2个buffer,然后每次录音取出一个,录制好后再写入文件就可以了,2个buffer依次来存储PCM数据,这样就可以连续录制流式音频数据了,二维数组里面自己维护了一个索引,来标识当前处于哪个buffer录制状态,暴露给外部的只是调用方法而已,细节对外也是隐藏的。

    二、编码实现

    1、编写缓存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数据。

    2、使用OpenSL ES录制PCM数据

    过程分为:创建引擎->初始化IO设备(自动检测麦克风等音频输入设备)->设置缓存队列->设置录制PCM数据规格->设置录音器接口->设置队列接口并设置录音状态为录制->开始录音。

    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;
        }
    }

    三、验证录制成果

    有两种方法:

    1. 使用Android OpenSL ES 开发:使用 OpenSL 播放 PCM 数据的demo进行播放。

    2. 使用 ffplay 命令播放,命令为:ffplay -f s16le -ar 44100 -ac 2 temp.pcm (命令由来:在录制代码里的参数为录制规格:PCM、2声道、44100HZ、16bit)

    四、参考源码

    https://github.com/renhui/OpenSLRecord 

  • 相关阅读:
    奥数视频
    提车应该检查哪?4S店都怕你检查这4个“雷区”,别等后悔才知道
    乒乓球拍子和套胶选择
    2018天津英华国际学校初中报名指南
    水瓶座出生日期是几月几号到几月几号
    乒乓球 世锦赛
    鸡蛋羹要怎么蒸才会更嫩?秘诀在这里
    家庭理财方法:知道这7个定律可以帮你赚更多钱!
    要知道股市有“三底”,估值底、政策底、市场底!
    DbMigration使用方法
  • 原文地址:https://www.cnblogs.com/renhui/p/9604550.html
Copyright © 2011-2022 走看看