【问题标题】:How to play decoded in-memory PCM with Oboe properly?如何使用双簧管正确播放解码的内存 PCM?
【发布时间】:2018-11-26 10:37:54
【问题描述】:

我使用oboe 在我的 ndk 库中播放声音,我使用OpenSL with Android extensions 将 wav 文件解码为 PCM。解码后的签名 16 位 PCM 存储在内存中 (std::forward_list<int16_t>),然后通过回调将它们发送到双簧管流中。我从手机听到的声音在音量级别上与原始 wav 文件相似,但是,这种声音的“质量”却不是——它爆裂和噼啪作响。

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


为了将文件解码为 PCM,我使用 AndroidSimpleBufferQueue 作为接收器,并使用 AndroidFDAAssetManager 作为源:

// 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;
    }
}

【问题讨论】:

    标签: android audio android-ndk oboe


    【解决方案1】:

    看起来您的 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;
        }
    }
    

    【讨论】:

    • 谢谢,但遗憾的是,事实并非如此。我更改了代码以尊重声音渲染和混音器渲染中的通道数(混音器只是一个代理,因为我现在只测试一个声音)但是,这没有帮助。
    • 可能不仅仅是样本/框架问题。如果迭代器正在从文件中读取,那么会有一些严重的实时问题会导致故障。听起来是否有非常短的带有故障的正确声音?还是声音完全乱码?
    • 我相信这不是 I/O 问题,因为我将整个 WAV 文件同步读入内存,然后才渲染它。我使用的声音只是双簧管演奏的一个音符,从我的手机中,我听到了类似原始录音的声音,但非常“机器人化”。所以我猜它以某种方式被打乱了。
    • 几个 cmets:1) 您确定您的流具有您使用构建器请求的属性吗?您应该检查采样率、通道数和格式等调用openStream 之后 以验证您确实得到了您所要求的。 2)正如Phil所说,您可以将WAV文件直接加载到内存中并播放。我使用 Audacity->File->Export->Other uncompressed files->Header=RAW, Encoding=Signed 16-bit PCM。此处输出/使用示例:codelabs.developers.google.com/codelabs/musicalgame-using-oboe/…
    • 使用原始文件时,文件与机器的字节序匹配很重要。如果文件存储的是BigEndian,而计算机是LittleEndian,那么听起来会很糟糕。
    【解决方案2】:

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


    我确信 std::advancestd::next 方法的线性复杂性在我的 sound::render 方法中没有任何区别。但是,当我尝试将原始指针和指针算术(因此,常量复杂性)与 cmets 中建议的调试方法一起使用时(Extracting PCM from WAV with Audacity,然后直接使用 AAssetManager 加载此资产到内存中),我意识到,输出声音的“损坏”量与渲染方法中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;
                }
            }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      • 1970-01-01
      • 2012-07-04
      相关资源
      最近更新 更多