如何正确播放已解码的内存PCM与Oboe?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何正确播放已解码的内存PCM与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;
}
}
看起来你的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;
}
}
简而言之:基本上,我遇到了一个不足,因为使用std::forward_list
来存储PCM。在这种情况下(使用迭代器来检索PCM),必须使用一个容器,其迭代器实现LegacyRandomAccessIterator(例如std::vector
)。
我确信std::advance
和std::next
方法的线性复杂性在我的sound::render
方法中没有任何区别。但是,当我尝试使用原始指针和指针算法(因此,常量复杂性)时,注释中建议的调试方法(使用Audacity从WAV中提取PCM,然后将此资产与AAssetManager直接加载到内存中),我意识到,输出声音的“腐败”量与渲染方法中std::advance(iter, 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;
}
}
}
以上是关于如何正确播放已解码的内存PCM与Oboe?的主要内容,如果未能解决你的问题,请参考以下文章
使用 (Python) Gstreamer 解码音频(到 PCM 数据)