是否可以直接从 OpenSL ES(适用于 Android)中的音频资产获取字节缓冲区?

Posted

技术标签:

【中文标题】是否可以直接从 OpenSL ES(适用于 Android)中的音频资产获取字节缓冲区?【英文标题】:Is it possible to get a byte buffer directly from an audio asset in OpenSL ES (for Android)? 【发布时间】:2012-06-02 00:20:01 【问题描述】:

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

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

    OpenSL 使用 AudioTrack 层来播放/停止/等播放器对象。这不仅引入了不必要的开销,而且还存在一些错误,并且播放器的快速启动/停止会导致很多问题。 我需要直接操作字节缓冲区以获得自定义 DSP 效果。 我要播放的剪辑很小,可以全部加载到内存中以避免文件 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 中获取样本。相反,我想将它永久存储在一个字节数组中,并在我喜欢的时候将它排入输出。

【问题讨论】:

你好khiner,它会帮助你从资产文件夹中读取Java字节或短数组中的.wav-文件,然后进一步处理吗? 不确定我能提供多少帮助,但仅作记录 - 你在使用 NDK 吗?这就是你的代码使用 C++ 的原因吗? 是的,我正在使用 NDK,而且这实际上是纯 C。这个问题已经很长了。我在这方面取得了很大进展,并将开始添加一个体面的答案。 @khiner 请做,听听什么对你有用会很有趣 【参考方案1】:

在 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 以了解更多详情。

【讨论】:

以上是关于是否可以直接从 OpenSL ES(适用于 Android)中的音频资产获取字节缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章

Android OpenSL ES 开发:Android OpenSL 介绍和开发流程说明

音视频系列--OpenSL ES基础用法总结

OpenSL ES

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集