33

我想使用 OpenSL ES FileDescriptor 对象从音频资产中获取字节缓冲区,因此我可以将其反复排入 SimpleBufferQueue,而不是使用 SL 接口来播放/停止/查找文件。

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

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

好的,所以理由完成了 - 这是我尝试过的 - 我有一个Sample结构,它本质上包含一个输入和输出轨道,以及一个保存样本的字节数组。输入是我的 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 接口

// 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 中获取样本。相反,我想将它永久存储在一个字节数组中,并在我喜欢的时候将它排入输出。

4

1 回答 1

6

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 支持暂停和初始搜索。不支持音量控制、效果、循环和播放速率。

阅读从适用于 Android 的 OpenSL ES 将音频解码为 PCM以了解更多详细信息。

于 2013-09-18T04:22:31.440 回答