我想使用OpenSL ES FileDescriptor对象从音频 Assets 中获取字节缓冲区,因此我可以将其重复排队到SimpleBufferQueue中,而不是使用SL接口(interface)播放/停止/搜索文件。

我想直接管理样本字节的三个主要原因:

  • OpenSL使用AudioTrack层播放/停止播放器对象等。这不仅会带来不必要的开销,而且还会带来一些错误,并且播放器的快速启动/停止会带来很多问题。
  • 我需要直接操作字节缓冲区以实现自定义DSP效果。
  • 我要播放的剪辑很小,可以全部加载到内存中以避免文件I / O开销。另外,将自己的缓冲区排入队列可以使我通过将0写入输出接收器来减少等待时间,并在播放时简单切换到采样字节,而不是停止,暂停和播放AudioTrack。

  • 好的,证明已经完成-这是我尝试过的工作-我有一个示例结构,该结构实质上包含一个输入和输出轨道,以及一个用于保存示例的字节数组。输入是我的FileDescriptor播放器,输出是SimpleBufferQueue对象。这是我的结构:
    typedef struct Sample_ {
        // buffer to hold all samples
        short *buffer;
        int totalSamples;
    
        SLObjectItf fdPlayerObject;
        // file descriptor player interfaces
        SLPlayItf fdPlayerPlay;
        SLSeekItf fdPlayerSeek;
        SLMuteSoloItf fdPlayerMuteSolo;
        SLVolumeItf fdPlayerVolume;
        SLAndroidSimpleBufferQueueItf fdBufferQueue;
    
        SLObjectItf outputPlayerObject;
        SLPlayItf outputPlayerPlay;
        // output buffer interfaces
        SLAndroidSimpleBufferQueueItf outputBufferQueue;
    } Sample;
    

    在初始化文件播放器 fdPlayerObject 之后,为我的字节缓冲区分配内存
    sample->buffer = malloc(sizeof(short)*sample->totalSamples);
    

    我正在使用它的BufferQueue接口(interface)
    // get the buffer queue interface
    result = (*(sample->fdPlayerObject))->GetInterface(sample->fdPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &(sample->fdBufferQueue));
    

    然后我实例化一个输出播放器:
    // create audio player for output buffer queue
    const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean req1[] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &(sample->outputPlayerObject), &outputAudioSrc, &audioSnk,
                                                   1, ids1, req1);
    
    // realize the output player
    result = (*(sample->outputPlayerObject))->Realize(sample->outputPlayerObject, SL_BOOLEAN_FALSE);
    assert(result == SL_RESULT_SUCCESS);
    
    // get the play interface
    result = (*(sample->outputPlayerObject))->GetInterface(sample->outputPlayerObject, SL_IID_PLAY, &(sample->outputPlayerPlay));
    assert(result == SL_RESULT_SUCCESS);
    
    // get the buffer queue interface for output
    result = (*(sample->outputPlayerObject))->GetInterface(sample->outputPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                                       &(sample->outputBufferQueue));
    assert(result == SL_RESULT_SUCCESS);
    
      // set the player's state to playing
    result = (*(sample->outputPlayerPlay))->SetPlayState(sample->outputPlayerPlay, SL_PLAYSTATE_PLAYING);
    assert(result == SL_RESULT_SUCCESS);
    

    当我想播放样本时,我正在使用:
    Sample *sample = &samples[sampleNum];
    // THIS WORKS FOR SIMPLY PLAYING THE SAMPLE, BUT I WANT THE BUFFER DIRECTLY
    //    if (sample->fdPlayerPlay != NULL) {
    //        // set the player's state to playing
    //        (*(sample->fdPlayerPlay))->SetPlayState(sample->fdPlayerPlay, SL_PLAYSTATE_PLAYING);
    //    }
    
    // fill buffer with the samples from the file descriptor
    (*(sample->fdBufferQueue))->Enqueue(sample->fdBufferQueue, sample->buffer,sample->totalSamples*sizeof(short));
    // write the buffer to the outputBufferQueue, which is already playing
    (*(sample->outputBufferQueue))->Enqueue(sample->outputBufferQueue, sample->buffer, sample->totalSamples*sizeof(short));
    

    但是,这导致我的应用程序冻结并关闭。这里不对劲。
    也是,我希望每次都不要从文件描述符的BufferQueue中获取样本。相反,我想将其永久存储在字节数组中,并在需要时将其排队到输出中。

    最佳答案

    在API级别14和更高级别上可以解码为PCM。

    创建解码器播放器时,需要将Android简单缓冲区队列设置为数据接收器:

    // For init use something like this:
    SLDataLocator_AndroidFD locatorIn = {SL_DATALOCATOR_ANDROIDFD, decriptor, start, length};
    SLDataFormat_MIME dataFormat = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
    SLDataSource audioSrc = {&locatorIn, &dataFormat};
    
    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataSink audioSnk = { &loc_bq, NULL };
    
    const SLInterfaceID ids[2] = {SL_IID_PLAY, SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    
    SLresult result = (*engineEngine)->CreateAudioPlayer(engineEngine, &(sample->fdPlayerObject), &outputAudioSrc, &audioSnk, 2, ids1, req1);
    

    对于解码器队列,您需要将一组空缓冲区排入Android简单缓冲区队列,该缓冲区将填充PCM数据。

    另外,您需要在解码器队列中注册一个回调处理程序,当PCM数据准备就绪时将调用该处理程序。回调处理程序应处理PCM数据,重新排入现在为空的缓冲区,然后返回。该应用程序负责跟踪解码缓冲区;回调参数列表没有包含足够的信息来指示哪个缓冲区已满或接下来要排队。

    解码为PCM支持暂停和初始搜索。不支持音量控制,效果,循环播放和播放速率。

    阅读OpenSL ES for Android将音频解码为PCM 以获得更多详细信息。

    08-05 21:02
    查看更多