如何使用Oboe正确播放解码的内存PCM?

时间:2018-11-26 10:37:55

标签: android audio android-ndk oboe

我使用oboe在ndk库中播放声音,并使用OpenSL with Android extensions将wav文件解码为PCM。解码的带符号的16位PCM存储在内存(std::forward_list<int16_t>)中,然后通过回调将其发送到双簧管流中。我可以从手机听到的声音在音量上类似于原始的wav文件,但是,这种声音的“质量”却不高-会突然爆裂。

我猜测我以错误的顺序或格式(采样率?)以音频流发送PCM。如何将OpenSL解码与双簧管音频流一起使用?


要将文件解码为PCM,我使用 AndroidSimpleBufferQueue 作为接收器,并使用 AndroidFD AAssetManager 作为源:

// Loading asset
AAsset* asset = AAssetManager_open(manager, path, AASSET_MODE_UNKNOWN);
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
AAsset_close(asset);

// Creating audio source
SLDataLocator_AndroidFD loc_fd = { SL_DATALOCATOR_ANDROIDFD, fd, start, length };
SLDataFormat_MIME format_mime = { SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED };
SLDataSource audio_source = { &loc_fd, &format_mime };

// Creating audio sink
SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
SLDataFormat_PCM pcm = {
    .formatType = SL_DATAFORMAT_PCM,
    .numChannels = 2,
    .samplesPerSec = SL_SAMPLINGRATE_44_1,
    .bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16,
    .containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,
    .channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
    .endianness = SL_BYTEORDER_LITTLEENDIAN
};
SLDataSink sink = { &loc_bq, &pcm };

然后我注册回调,排队缓冲区,并将PCM从缓冲区移到存储,直到完成。

注意:wav音频文件也是2通道带符号的16位44.1Hz PCM

我的双簧管流配置相同:

AudioStreamBuilder builder;
builder.setChannelCount(2);
builder.setSampleRate(44100);
builder.setCallback(this);
builder.setFormat(AudioFormat::I16);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);

音频渲染就像这样:

// Oboe stream callback
audio_engine::onAudioReady(AudioStream* self, void* audio_data, int32_t num_frames) {
    auto stream = static_cast<int16_t*>(audio_data);
    sound->render(stream, num_frames);
}

// Sound::render method
sound::render(int16_t* audio_data, int32_t num_frames) {
    auto iter = pcm_data.begin();
    std::advance(iter, cur_frame);

    const int32_t rem_size = std::min(num_frames, size - cur_frame);
    for(int32_t i = 0; i < rem_size; ++i, std::next(iter), ++cur_frame) {
        audio_data[i] += *iter;
    }
}

2 个答案:

答案 0 :(得分:3)

您的render()方法似乎使示例和框架混乱。 帧是一组同时采样。 在立体声流中,每个帧有两个样本。

我认为您的迭代器以示例为基础。换句话说,next(iter)将前进到下一个样本,而不是下一帧。试试这个(未经测试的)代码。

sound::render(int16_t* audio_data, int32_t num_frames) {
    auto iter = pcm_data.begin();
    const int samples_per_frame = 2; // stereo
    std::advance(iter, cur_sample);

    const int32_t num_samples = std::min(num_frames * samples_per_frame,
              total_samples - cur_sample);
    for(int32_t i = 0; i < num_samples; ++i, std::next(iter), ++cur_sample) {
        audio_data[i] += *iter;
    }
}

答案 1 :(得分:0)

简而言之:实际上,由于使用std::forward_list来存储PCM,我遇到了欠载的情况。在这种情况下(使用迭代器检索PCM),必须使用其迭代器实现 LegacyRandomAccessIterator (例如std::vector)的容器。


我确信方法std::advancestd::next的线性复杂度在我的sound::render方法中没有任何区别。但是,当我尝试在注释中建议的调试方法中使用原始指针和指针算法(因此,具有恒定的复杂性)(使用Audacity从WAV中提取PCM,然后直接使用 AAssetManager 加载此资产时)进入内存),我意识到输出声音的“损坏”量与render方法中std::advance(iter, position)中的 position 参数直接成比例。

因此,如果声音损坏的程度与std::advance(以及std::next)的复杂度成正比,那么我必须使复杂度保持不变-通过使用std::vector作为一个容器。然后使用@philburk的答案,将其作为工作结果:

class sound {
    private:
        const int samples_per_frame = 2; // stereo
        std::vector<int16_t> pcm_data;
        ...
    public:
        render(int16_t* audio_data, int32_t num_frames) {
            auto iter = std::next(pcm_data.begin(), cur_sample);
            const int32_t s = std::min(num_frames * samples_per_frame,
                                       total_samples - cur_sample);

            for(int32_t i = 0; i < s; ++i, std::advance(iter, 1), ++cur_sample) {
                audio_data[i] += *iter;
            }
        }
}