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

标签 android audio-streaming opensl

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

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

  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 接口(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了解更多详情。

关于android - 是否可以直接从 OpenSL ES(适用于 Android)中的音频 Assets 获取字节缓冲区?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10770979/

相关文章:

java - 从 OnClickListener 抛出异常

audio - 增加传入音频流的增益

Android 快速路径音频环回

C - 为什么用 & 而不是 switch/if 来比较常量?

适用于 Windows、Mac 和 iOS 的音频库? OpenAL/OpenSL

java - 我的 Android 应用程序在从 Playstore 下载时崩溃,但当我从 Android Studio 运行到任何设备时它可以正常工作

java - 我的 DataReader 类实例出现问题

android - GCM(谷歌云消息)需要谷歌账户吗?

android - 如何通过基于 VLC 或 RTSP 的 WiFi 从 Android 发送流媒体?

ios - 我正在学习 iOS 中的 "Audio File Stream Services Reference"