我正在使用命令行编码器 opusenc 1.4 将 32 位浮点 Wave (
.wav
) 文件编码为 Opus。
所有 Wave 文件都没有剪辑(= 包含 -1.0 .. +1.0 范围之外的音频样本),但是:
如果我在 Audacity 中打开由命令行编码器生成的
.opus
文件,会发现少数个别音频样本的实例低于 -1.0 或高于 +1.0。
如果我通过 opusdec 1.4 解码
.opus
文件或通过 C++ 中的 libopusfile
读取它,则音频数据中不存在剪辑。
我将我的代码与Audacity的代码(https://github.com/audacity/audacity/blob/5b9d78bc9ecda8dcb84669541c268cce90b06848/modules/mod-opus/ImportOpus.cpp#L193)进行了比较,两者都使用
::op_read_float()
。
Audacity 有何不同之处?通过拖放到 Audacity 窗口中打开的
.opus
文件如何包含少量剪辑样本,而 opusdec 和我自己的代码却无法复制相同的剪辑样本?
我发现,如果我使用系统提供的
libopus
/ libopusfile
,即使在 Audacity 之外也会出现剪切问题。
因此,官方
opusdec
可执行文件、我的 libopus
/ libopusfile
调试版本和我系统上所述库的 riced 版本之间的解码似乎存在细微差别(编译标志包括 -march=native -Ofast -pipe -fomit-frame-pointer -g0 -fgraphite-identity -fno-common -flto=13 -fmerge-all-constants -falign-functions=32 -fno-stack-protector -floop-strip-mine -floop-block -ftree-vectorize -floop-interchange -floop-nest-optimize -floop-parallelize-all -fstack-check=no -fno-stack-check -fno-stack-clash-protection
) .
以下 C++ 片段与我的系统的
libopus
链接时,确实重现了我在 Audacity 中看到的剪辑:
// Uses a class from the 'AudioFile' library to store audio data
// https://github.com/adamstark/AudioFile
std::unique_ptr<AudioFile<float>> loadOpusFile(const std::string &opusFilePath) {
std::unique_ptr<AudioFile<float>> audioFile = std::make_unique<AudioFile<float>>();
::OggOpusFile *opusFile = ::op_open_file(opusFilePath.c_str(), nullptr);
std::size_t channelCount = ::op_channel_count(opusFile, -1);
//std::size_t sampleRate = ::op_head(opusFile, -1)->input_sample_rate;
// Docs: "The <tt>libopusfile</tt> API always decodes files to 48 kHz.
// The original sample rate is not preserved by the lossy compression, ..."
audioFile->setSampleRate(48000);
//audioFile->setNumChannels(channelCount);
std::vector<std::vector<float>> channels;
channels.resize(channelCount);
// Docs: "It is recommended that this be large enough for at least 120 ms
// of data at 48 kHz per channel (5760 samples per channel)"
std::vector<float> sampleBuffer;
sampleBuffer.resize(channelCount * 6144);
//::op_pcm_t pcm;
for(;;) {
// Docs: "he channel count cannot be known a priori (reading more samples might
// advance us into the next link, with a different channel count)"
int sampleCountPerChannel = ::op_read_float(
opusFile, sampleBuffer.data(), sampleBuffer.size(), nullptr
);
if(sampleCountPerChannel == 0) {
break;
}
if(sampleCountPerChannel < 0) {
::op_free(opusFile);
throw std::runtime_error(u8"Error reading/decoding OPUS file");
}
const ::OpusHead *header = ::op_head(opusFile, -1);
std::size_t currentChannelCount = header->channel_count;
if(currentChannelCount != channelCount) {
::op_free(opusFile);
throw std::runtime_error(u8"Channel count changes in the middle of OPUS file");
}
// Docs: "Multiple channels are interleaved using the
// <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
// channel ordering</a>."
float *currentSample = sampleBuffer.data();
for(int sampleIndex = 0; sampleIndex < sampleCountPerChannel; ++sampleIndex) {
for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex) {
channels[channelIndex].push_back(*currentSample);
++currentSample;
}
}
}
// Clean up
::op_free(opusFile);
// No move assignment? But new buffer also needs to be non-const?
// It would probably be much more efficient to call setAudioBufferSize()
// and fill the decoded samples directly into the audio buffer.
audioFile->setAudioBuffer(channels);
return audioFile;
}