Android Open SL ES — 官方Demo解析native-audio

Open SL ES简介

OpenSL ES – 嵌入式音频加速标准。OpenSL ES™ 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。在Android中主要用到了一部分Open SL ES的功能,两者之间有交集但不完全一样,Android有自己的一部分扩展。

《Android Open SL ES — 官方Demo解析native-audio》

主要功能介绍

  1. Assets目录音频播放
  2. c头文件形式播放
  3. 本地音频播放
  4. 录音和回放

本文中主要介绍从缓冲队列中播放和录音到缓冲队列中,主要用到以下几个native方法。其他方法操作步骤类似,可以参考学习。

代码解析

    public static native void createEngine();
    public static native void createBufferQueueAudioPlayer(int sampleRate, int samplesPerBuf);
    public static native boolean createAudioRecorder();
    public static native void startRecording();
    public static native void shutdown();

从native方法的排序可以看出程序的调用流程

《Android Open SL ES — 官方Demo解析native-audio》 播放流程
《Android Open SL ES — 官方Demo解析native-audio》 录音流程

从图中可以看出不管是播放还是录音都必须先创建引擎,在Open SL ES中所有的对象创建过程都是一样的步骤。

  1. create
  2. Realize
  3. GetInterface

一、Engine的创建基本就是这几个步骤,Interface 可以根据自己的业务需求来获取,不需要的可以删除。

// 创建引擎
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    // 获取引擎接口
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineItf);
    // create output mix, with environmental reverb specified as a non-required interface
    // const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    // SLboolean req[1] = {SL_BOOLEAN_FALSE};
    result = (*engineItf)->CreateOutputMix(engineItf, &outputMixObject, 0, NULL, NULL);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    // realize the output mix
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);

二、 createBufferQueueAudioPlayer

创建的步骤和引擎类似,从上面的图片中可以出,多了一些东西需要配置,DataSource 和 DataSink,DataSource 顾名思义就是要播放的数据源,缓冲队列的播放形式只支持PCM格式的数据源,DataSink 就是音频输出,也就是创建引擎时获取的 outputMixObject 对象。PCM 数据源需要配置采样率、通道、定点、扬声器、小端模式,参数配置需要参考官方文档,有些参数是不支持的,需要测试。另一个比较重要的就是缓冲队列和播放回调,因为和录音一样,所以放到下面一起介绍。

    // create BufferQueueAudioPlayer
    // configure audio source
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
                                   1,
                                   SL_SAMPLINGRATE_32,
                                   SL_PCMSAMPLEFORMAT_FIXED_16,
                                   SL_PCMSAMPLEFORMAT_FIXED_16,
                                   SL_SPEAKER_FRONT_CENTER,
                                   SL_BYTEORDER_LITTLEENDIAN}; //合适pcm  单通道   采样率   定点    扬声器  小端
//    if (sampleRate) {
//        format_pcm.samplesPerSec = (SLuint32) sampleRate;
//    }
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    //需要请求的接口   缓冲队列   音量
    const SLInterfaceID idsAudioPlayer[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
    const SLboolean reqAudioPlayer[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};

    result = (*engineItf)->CreateAudioPlayer(engineItf, &playerObject, &audioSrc, &audioSnk, 2,idsAudioPlayer, reqAudioPlayer);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    // realize the player
    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);
    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueueItf);
    result = (*playerBufferQueueItf)->RegisterCallback(playerBufferQueueItf, playCallback, audioProcess);
    //静音模式/solo不支持单声道
    // mute/solo is not supported for sources that are known to be mono, as this is
    // get the mute/solo interface
    //result = (*playerObject)->GetInterface(playerObject, SL_IID_MUTESOLO, &playerMuteSoloItf);
    // get the volume interface
    result = (*playerObject)->GetInterface(playerObject, SL_IID_VOLUME, &playerVolumeItf);

三 、createAudioRecorder

录音的创建和播放类似,只是它们的 DataSource 和 DataSink 不同,录音的数据源 DataSource 是来自麦克风,输出数据DataSink 和 播放的数据源是一样的,PCM格式的数据,配置方式也是一样。

  //录音
    // configure audio source
    SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
    SLDataSource recordAudioSrc = {&loc_dev, NULL};

    // configure audio sink
    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};//缓冲的数量
    SLDataFormat_PCM recordFormat_pcm = {SL_DATAFORMAT_PCM,
                                         1,
                                         SL_SAMPLINGRATE_32,
                                         SL_PCMSAMPLEFORMAT_FIXED_16,
                                         SL_PCMSAMPLEFORMAT_FIXED_16,
                                         SL_SPEAKER_FRONT_CENTER,
                                         SL_BYTEORDER_LITTLEENDIAN};
//    if (sampleRate) {
//        recordFormat_pcm.samplesPerSec = (SLuint32) sampleRate;
//    }
    SLDataSink recordSink = {&loc_bq, &recordFormat_pcm};

    const SLInterfaceID idsRecord[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean reqRecord[1] = {SL_BOOLEAN_TRUE};
    //创建录音对象
    result = (*engineItf)->CreateAudioRecorder(engineItf,
                                               &recorderObject,
                                               &recordAudioSrc,
                                               &recordSink,
                                               1,
                                               idsRecord,
                                               reqRecord);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    // realize the audio recorder
    result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    // 获取录音接口
    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderItf);
    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                             &recorderBufferQueueItf);
    result = (*recorderBufferQueueItf)->RegisterCallback(recorderBufferQueueItf, recordCallback,
                                                         audioProcess);

四、播放和录音的缓冲队列机制

在创建播放和录音的时候都设置了缓冲队列和回调,注意播放和录音代码中都配置了一个 SL_BUFFERSIZE 参数,这是我自定义的一个宏,根据名字可以推测这是设置缓冲队列的大小,也就是当前 BufferQueue 中最大可以容纳的buffer数量。 每次播完或录完一个 buffer 都会回调创建时 RegisterCallback 传入的callback方法,代表当前buffer数据已经处理完毕,buffer出队, 如果是录音你应该获取这个 buffer 保存下来,写文件或者其他操作都可以;如果是播放就代表 当前buffer已经播放完毕,你可以 Enqueue 下一个buffer了。Enqueue到队列中的buffer数量超过了你设置的 SL_BUFFERSIZE 值,会报错SL_RESULT_BUFFER_INSUFFICIENT。

五、需要注意的坑

当你需要停止录音和停止播放的时候,调用以下代码,官方案例也是这么做的,但是这么做会有bug,当你下次再开始录音或播放的时候,会报错SL_RESULT_BUFFER_INSUFFICIENT,也就是说队列满了。根据测试发现Clear函数并不起作用。因为设置了SL_PLAYSTATE_STOPPED状态,录音或播放已经停止了,所以不会再回调callback函数了,也就是代表剩余的buffer没用出队,所以会报错。

  //停止播放
   SLresult result = (*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_STOPPED);
   assert(SL_RESULT_SUCCESS == result);
   (void) result;
   result = (*playerBufferQueueItf)->Clear(playerBufferQueueItf);
            assert(SL_RESULT_SUCCESS == result);
   (void) result;
   // 停止录音
    result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    result = (*recorderBufferQueue)->Clear(recorderBufferQueue);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;

解决办法:

思路一:

在callback中判断缓冲队列的状态,等到buffer全部出队再停止播放或录音,这样就会有延迟,延迟时间是根据你设置的 SL_BUFFERSIZE 队列大小来决定的,所以 SL_BUFFERSIZE 不宜设置太大,官方DEMO推荐至少设置2个,buffer数组也不宜过大,假设采样率是32000,buffer是 short *buffer[512] 延迟是16毫秒,2个就是32毫秒,这样的延迟几乎可以忽略了。

    AudioProcess *audioProcess = context;
    SLAndroidSimpleBufferQueueState recQueueState;//缓冲区状态
    (*bq)->GetState(bq, &recQueueState);
        if (recQueueState.count == 0) {
            SLresult result = (*audioProcess->playerItf)->SetPlayState(audioProcess->playerItf, SL_PLAYSTATE_STOPPED);
            assert(SL_RESULT_SUCCESS == result);
            (void) result;
            result = (*audioProcess->playerBufferQueueItf)->Clear(audioProcess->playerBufferQueueItf);
            assert(SL_RESULT_SUCCESS == result);
            (void) result;

            LOGD("停止播放");
        }

思路二:

下次开始录制或播放的时候重置buffer数据,接着走回调函数,直接在callback中控制流程,这样就相当于 SL_PLAYSTATE_STOPPED 是一个暂停的状态,可以避免播放和录音的启动时间,也是一种不错的方案。

参考资料:

官方DEMO
音频相关博客推荐
Open SL ES 资料:ndk目录/docs/Additional_library_docs/opensles/OpenSL_ES_Specification_1.0.1.pdf

    原文作者:WuTa0
    原文地址: https://www.jianshu.com/p/5308ab38a3d0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞