zoukankan      html  css  js  c++  java
  • OpenSL ES: 利用OpenSL ES实现录音功能

    SLAudioRecorder.h

    //
    // Created by yongdaimi on 2020/3/2.
    //
    
    #ifndef DONGLEAPPDEMO_SLAUDIORECORDER_H
    #define DONGLEAPPDEMO_SLAUDIORECORDER_H
    
    #include <SLES/OpenSLES.h>
    #include <SLES/OpenSLES_Android.h>
    
    #include <stdio.h>
    
    
    class SLAudioRecorder
    {
    
    private:
        int      mIndex;
        short    *mRecordBuffs[2];
        unsigned mRecordBufferSize;
    
        bool mIsRecording;
    
        FILE *mFile;
    
        SLObjectItf                   mEngineObj;
        SLEngineItf                   mEngineInterface;
        SLObjectItf                   mRecorderObj;
        SLRecordItf                   mRecorderInterface;
        SLAndroidSimpleBufferQueueItf mBufferQueue;
    
    public:
        SLAudioRecorder(const char *fileSavePath);
    
        /** Call this method to start audio recording */
        bool start();
    
        /** Call this method to stop audio recording */
        void stop();
    
        ~SLAudioRecorder();
    
    private:
    
        bool initEngine();
    
        /** Call this method to initialize an audio recorder */
        bool initRecorder();
    
        /** Call this method to release the resources related to recording */
        void release();
    
        /** This method is called every time an audio frame is recorded*/
        static void recorderCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext);
    
    };
    
    
    #endif //DONGLEAPPDEMO_SLAUDIORECORDER_H

    SLAudioRecorder.cpp

    //
    // Created by yongdaimi on 2020/3/2.
    //
    
    
    #include "SLAudioRecorder.h"
    #include "SLLog.h"
    
    
    #define RECORD_BUFFER_QUEUE_NUM 2
    #define RECORDER_FRAMES 2048
    
    
    SLAudioRecorder::SLAudioRecorder(const char *fileSavePath) :
            mEngineObj(nullptr),
            mEngineInterface(nullptr),
            mRecorderObj(nullptr),
            mRecorderInterface(nullptr),
            mRecordBufferSize(RECORDER_FRAMES),
            mBufferQueue(nullptr),
            mIndex(0), mIsRecording(false)
    {
    
        this->mFile = fopen(fileSavePath, "w");
    
        this->mRecordBuffs[0] = new short[mRecordBufferSize]();
        this->mRecordBuffs[1] = new short[mRecordBufferSize]();
    }
    
    
    bool SLAudioRecorder::initEngine()
    {
    
        SLresult ret;
    
        ret = slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("slCreateEngine obj failed");
            return false;
        }
    
        ret = (*mEngineObj)->Realize(mEngineObj, SL_BOOLEAN_FALSE);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("sl engineObj realize failed");
            return false;
        }
    
        ret = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, &mEngineInterface);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("sl get engine interface failed");
            return false;
        }
    
        XLOGI("init engine success");
        return true;
    }
    
    
    bool SLAudioRecorder::initRecorder()
    {
    
        if (!mEngineInterface) {
            if (!initEngine()) {
                XLOGE("init engine failed");
                return false;
            }
        }
    
        SLresult ret;
    
        // Configuration the recorder's audio data source
        SLDataLocator_IODevice device = {
                SL_DATALOCATOR_IODEVICE,
                SL_IODEVICE_AUDIOINPUT,
                SL_DEFAULTDEVICEID_AUDIOINPUT,
                nullptr // Must be Null if deviceID parameter is to be used.
        };
    
        SLDataSource dataSource = {&device,
                                   nullptr}; // This parameter is ignored if pLocator is SLDataLocator_IODevice.
    
        // Configuration the recorder's audio data save way.
        SLDataLocator_AndroidSimpleBufferQueue queue = {
                SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                RECORD_BUFFER_QUEUE_NUM
        };
    
        // Audio Format: PCM
        // Audio Channels: 2
        // SampleRate: 44100
        // SampleFormat: 16bit
        // Endian: Little Endian
        SLDataFormat_PCM pcmFormat = {
                SL_DATAFORMAT_PCM,
                1,
                SL_SAMPLINGRATE_16,
                SL_PCMSAMPLEFORMAT_FIXED_16,
                SL_PCMSAMPLEFORMAT_FIXED_16,
                SL_SPEAKER_FRONT_CENTER,
                SL_BYTEORDER_LITTLEENDIAN
        };
    
        SLDataSink dataSink = {&queue, &pcmFormat};
    
        // Configure the interface that the recorder needs to support.
        SLInterfaceID ids[]          = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
        SLboolean     ids_required[] = {SL_BOOLEAN_TRUE};
        SLuint32      numInterfaces  = 1;
    
        // Create the audio recorder.
        ret = (*mEngineInterface)->CreateAudioRecorder(mEngineInterface, &mRecorderObj,
                                                       &dataSource,
                                                       &dataSink,
                                                       numInterfaces,
                                                       ids,
                                                       ids_required);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("CreateAudioRecorder() failed");
            return false;
        }
    
        ret = (*mRecorderObj)->Realize(mRecorderObj, SL_BOOLEAN_FALSE);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mRecorderObj realize failed");
            return false;
        }
    
        ret = (*mRecorderObj)->GetInterface(mRecorderObj, SL_IID_RECORD, &mRecorderInterface);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mRecorderObj get SL_IID_RECORD interface failed");
            return false;
        }
    
        ret = (*mRecorderObj)->GetInterface(mRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                            &mBufferQueue);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mRecorderObj get simpleBufferQueue interface failed");
            return false;
        }
    
        ret = (*mBufferQueue)->RegisterCallback(mBufferQueue, recorderCallback, this);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("register read pcm data callback failed");
            return false;
        }
    
        XLOGI("init recorder success");
        return true;
    
    }
    
    bool SLAudioRecorder::start()
    {
        if (mIsRecording) return true;
    
        if (!mRecorderInterface) {
            if (!initRecorder()) {
                XLOGE("init recorder failed");
                return false;
            }
        }
    
        if (!mFile) {
            XLOGE("record file open failed");
            return false;
        }
    
        SLresult ret;
    
        ret = (*mRecorderInterface)->SetRecordState(mRecorderInterface, SL_RECORDSTATE_STOPPED);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mRecorderInterface stop record state failed");
            return false;
        }
    
        ret = (*mBufferQueue)->Clear(mBufferQueue);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mBufferQueue clear bufferQueue failed");
            return false;
        }
    
        // Enqueue an empty buffer to fill data when recording audio to the recorder.
        ret = (*mBufferQueue)->Enqueue(mBufferQueue, mRecordBuffs[mIndex],
                                       mRecordBufferSize * sizeof(short));
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mBufferQueue enqueue buffer failed");
            return false;
        }
    
        ret = (*mRecorderInterface)->SetRecordState(mRecorderInterface, SL_RECORDSTATE_RECORDING);
        if (ret != SL_RESULT_SUCCESS) {
            XLOGE("mRecorderInterface start record state failed");
            return false;
        }
    
        mIsRecording = true;
        XLOGI("audioRecorder start recording...");
        return true;
    }
    
    void SLAudioRecorder::stop()
    {
        mIsRecording = false;
    }
    
    /**
     * Called after each frame of audio is recorded
     * @param bufferQueue
     * @param pContext
     */
    void SLAudioRecorder::recorderCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext)
    {
        if (pContext == nullptr) {
            XLOGE("SLAudioRecorder recorderCallback argus is null");
            return;
        }
    
        SLAudioRecorder *recorder = (SLAudioRecorder *) pContext;
    
        /*int ret = write(recorder->mRecordedFilePathFd,
                        recorder->mRecordBuffs[recorder->mIndex],
                        recorder->mRecordBufferSize);*/
    
        int ret = fwrite(recorder->mRecordBuffs[recorder->mIndex], sizeof(short),
                         recorder->mRecordBufferSize, recorder->mFile);
    
        if (ret < 0) {
            XLOGE("SLAudioRecorder recorderCallback write failed");
            return;
        }
    
        // If it is currently recording, modify the index and switch another buffer for the next recording
        if (recorder->mIsRecording) {
            recorder->mIndex = 1 - recorder->mIndex;
            (*recorder->mBufferQueue)->Enqueue(recorder->mBufferQueue,
                                               recorder->mRecordBuffs[recorder->mIndex],
                                               recorder->mRecordBufferSize * sizeof(short));
        } else {
            (*recorder->mRecorderInterface)->SetRecordState(recorder->mRecorderInterface,
                                                            SL_RECORDSTATE_STOPPED);
            fclose(recorder->mFile);
        }
    }
    
    void SLAudioRecorder::release()
    {
        if (mRecorderObj) {
            (*mRecorderInterface)->SetRecordState(mRecorderInterface, SL_RECORDSTATE_STOPPED);
            (*mRecorderObj)->Destroy(mRecorderObj);
            mRecorderObj       = nullptr;
            mRecorderInterface = nullptr;
            mBufferQueue       = nullptr;
        }
    
        if (mEngineObj) {
            (*mEngineObj)->Destroy(mEngineObj);
            mEngineObj       = nullptr;
            mEngineInterface = nullptr;
        }
    
        if (mRecordBuffs[0]) {
            delete mRecordBuffs[0];
            mRecordBuffs[0] = nullptr;
        }
    
        if (mRecordBuffs[1]) {
            delete mRecordBuffs[1];
            mRecordBuffs[1] = nullptr;
        }
    
        if (mFile) {
            fclose(mFile);
        }
    
        mIsRecording = false;
        mIndex       = 0;
    
        XLOGI("audioRecorder stopped");
    }
    
    
    SLAudioRecorder::~SLAudioRecorder()
    {
        release();
    }

    CMakeLists.txt

    # For more information about using CMake with Android Studio, read the
    # documentation: https://d.android.com/studio/projects/add-native-code.html
    
    # Sets the minimum version of CMake required to build the native library.
    
    cmake_minimum_required(VERSION 3.4.1)
    
    
    # Creates and names a library, sets it as either STATIC
    # or SHARED, and provides the relative paths to its source code.
    # You can define multiple libraries, and CMake builds them for you.
    # Gradle automatically packages shared libraries with your APK.
    
    add_library( # Sets the name of the library.
            native-lib
    
            # Sets the library as a shared library.
            SHARED
    
            # Provides a relative path to your source file(s).
            src/main/cpp/native-lib.cpp
            src/main/cpp/SLLog.cpp
            src/main/cpp/SLAudioRecorder.cpp
            )
    
    # Searches for a specified prebuilt library and stores the path as a
    # variable. Because CMake includes system libraries in the search path by
    # default, you only need to specify the name of the public NDK library
    # you want to add. CMake verifies that the library exists before
    # completing its build.
    
    find_library( # Sets the name of the path variable.
            log-lib
    
            # Specifies the name of the NDK library that
            # you want CMake to locate.
            log)
    
    # Specifies libraries CMake should link to your target library. You
    # can link multiple libraries, such as libraries you define in this
    # build script, prebuilt third-party libraries, or system libraries.
    
    target_link_libraries( # Specifies the target library.
            native-lib
            OpenSLES
            android
    
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib})
    CMakeLists.txt

    JNI调用函数

    //
    // Created by nisha_chen on 2020/1/20.
    //
    
    
    #include <jni.h>
    #include "SLLog.h"
    #include "SLAudioRecorder.h"
    
    SLAudioRecorder *audioRecorder = nullptr;
    
    extern "C"
    JNIEXPORT jboolean JNICALL
    Java_com_yongdaimi_android_dongle_helper_UsbAudioHelper_native_1start_1record(JNIEnv *env,
                                                                                jclass clazz,
                                                                                jstring record_file_save_path)
    {
        const char *recordFileSavePath = env->GetStringUTFChars(record_file_save_path, nullptr);
        if (!audioRecorder) {
            audioRecorder = new SLAudioRecorder(recordFileSavePath);
        }
    
        env->ReleaseStringUTFChars(record_file_save_path, recordFileSavePath);
        return (jboolean) audioRecorder->start();
    }
    
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_yongdaimi_android_dongle_helper_UsbAudioHelper_native_1stop_1record(JNIEnv *env,
                                                                               jclass clazz)
    {
        if (audioRecorder) {
            audioRecorder->stop();
            delete audioRecorder;
            audioRecorder = nullptr;
        }
    }
    native-lib.cpp

    有几点需要注意:

    1. 需要添加: <uses-permission android:name="android.permission.RECORD_AUDIO" />  权限,如果权限未添加,仍然没有办法正常录音;

    2. Android 10.0设备的适配问题,Android 10.0 之后,Google要求在sdcard/data/<packageName>目录下创建文件,所以生成的pcm文件建议放在这个目录或公共的Media目录下。

    3. CMakeLists.txt文件中需要添加相应的类库:OpenSL ES的类库名字为:OpenSLES

    4. OpenSL 使用回调机制来访问异步IO,但它的回调里并不会把音频数据作为参数传递,回调方法仅仅是告诉我们,BufferQueue已准备就绪,可以接收/写入数据了。

    录音完毕后可以使用 Audacity 这个软件检测生成的PCM文件是否有问题。方法如下:
    打开Audacity, 选择:

    选择生成的PCM文件:

    选择合适的采样率和采样位数(比如我在code中定义的录音器配置为:16000hz采样率,单声道,采样位数为16bit):

    点击导入,点最上方的三角形按钮可以选择试听:

    参考链接:

    1. OpenSL ES 实现音频的录制与播放

    2. Google native-audio

    3. 浅聊OpenSL ES音频开发

  • 相关阅读:
    Java8 Lambda表达式详解手册及实例
    成功,侥幸,以小博大?永远离不开的墨菲定律
    Java8 Stream性能如何及评测工具推荐
    康威定律,作为架构师还不会灵活运用?
    Java8 Stream新特性详解及实战
    Java SPI机制实战详解及源码分析
    jQuery是什么
    庞氏骗局
    excel中VBA的使用
    wcf ServiceContract
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/12426966.html
Copyright © 2011-2022 走看看