使用 PortAudio 和 sndfile 播放立体声 .wav 文件,输出模糊且音调降低/减慢
Playing a stereo .wav file with PortAudio & sndfile, output is fuzzy and pitched down / slowed
我一直在使用 PortAudio 和 sndfile 在 C++ 中编写一些代码来播放立体声 .wav 文件,但输出声音模糊且音高下降(音高下降对我来说不是什么问题,但它可能是问题的一部分)。它几乎看起来像是用模糊播放部分垃圾数据,但我不相信我正在使用任何变量,这可能发生在我没有先清除数据的地方。我试图跟随一些带有立体声播放的 PortAudios 示例,但由于输入来自 .wav 文件而不是生成的文件,我无法完美地跟随它。我还编译了 运行 一些 PortAudio 示例(使用立体声)并且它们工作正常。我不确定问题出在哪里。
Audio.h
struct AudioFile {
SNDFILE* file = nullptr;
SF_INFO info;
int buffer_size = 512;
int readHead = 0;
sf_count_t count = 1;
};
/*
Class for handling basic audio functions
*/
class Audio {
protected:
public:
/// Constructor
Audio();
/// Destructor
~Audio();
/// Load an audio file
AudioFile loadFile(const char* path);
/// Play an audio file
void playFile(AudioFile* file);
};
Audio.cpp
/// Audio constructor
Audio::Audio() {
PaError err = Pa_Initialize();
if (err != paNoError) std::cerr << "PAError: " << err << std::endl;
#ifdef DEBUG
std::cout << "Initialising PortAudio" << std::endl;
std::cout << "----------------------" << std::endl;
std::cout << "Version: " << Pa_GetVersion << std::endl;
std::cout << "Devices:" << std::endl;
std::cout << "----------------------" << std::endl;
int numDevices = Pa_GetDeviceCount();
for (int i=0; i < numDevices; i++) {
auto deviceInfo = Pa_GetDeviceInfo(i);
std::cout << "Name: " << deviceInfo->name << std::endl;
std::cout << "HostApi: " << deviceInfo->hostApi << std::endl;
std::cout << "SampleRate: " << deviceInfo->defaultSampleRate << std::endl;
std::cout << "InputChannels: " << deviceInfo->maxInputChannels << std::endl;
std::cout << "OutputChannels: " << deviceInfo->maxOutputChannels << std::endl;
std::cout << "----------------------" << std::endl;
}
#endif
}
Audio::~Audio() {
PaError err = Pa_Terminate();
if (err != paNoError) std::cerr << "PAError: " << err << std::endl;
}
/* Loads an audiofile */
AudioFile Audio::loadFile(const char* path) {
AudioFile file;
::memset(&file.info, 0, sizeof(file.info));
file.file = sf_open(path, SFM_READ, &file.info);
return file;
}
static int patestCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void* userData) {
/// Prevent warnings
(void)inputBuffer;
(void)timeInfo;
(void)statusFlags;
/// an AudioFile gets passed as userData
AudioFile* file = (AudioFile*)userData;
float* out = (float*)outputBuffer;
sf_seek(file->file, file->readHead, SF_SEEK_SET);
auto data = std::make_unique<float[]>(framesPerBuffer);
file->count = sf_read_float(file->file, data.get(), framesPerBuffer);
for (int i = 0; i < framesPerBuffer; i++) {
*out++ = data[i];
}
file->readHead += file->buffer_size;
if (file->count > 0) return paContinue;
else return paComplete;
}
void Audio::playFile(AudioFile* file) {
PaStream* stream = nullptr;
PaStreamParameters params;
params.device = Pa_GetDefaultOutputDevice();
params.channelCount = file->info.channels;
params.sampleFormat = paFloat32;
params.suggestedLatency =
Pa_GetDeviceInfo(params.device)->defaultLowOutputLatency;
params.hostApiSpecificStreamInfo = nullptr;
/// Check if params work
PaError err = Pa_IsFormatSupported(nullptr, ¶ms, file->info.samplerate);
if (err != paFormatIsSupported) {
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
return;
}
err = Pa_OpenStream(&stream, nullptr, ¶ms, file->info.samplerate,
file->buffer_size * params.channelCount, paClipOff,
&patestCallback, file);
if (err != paNoError) std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
err = Pa_StartStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
/// wait until file finishes playing
while (file->count > 0) {}
err = Pa_StopStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
err = Pa_CloseStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
}
我也尝试过不使用 data
指针(使用它似乎产生更清晰但仍然模糊的声音)并将音频文件按值传递到 playFile
函数。感谢任何帮助。
终于搞清楚了,我有一个主要问题,在这里:
err = Pa_OpenStream(&stream, nullptr, ¶ms, file->info.samplerate,
file->buffer_size * params.channelCount, paClipOff,
&patestCallback, file);
我给了 Pa_OpenStream 缓冲区大小 * 通道数,但是我应该只是给它缓冲区大小,然后直接在回调函数中对 framesPerBuffer 进行通道调整:
static int patestCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void* userData) {
/// Prevent warnings
(void)inputBuffer;
(void)timeInfo;
(void)statusFlags;
/// an AudioFile gets passed as userData
velox::AudioFile* file = (velox::AudioFile*)userData;
float* out = (float*)outputBuffer;
sf_seek(file->file, file->readHead, SF_SEEK_SET);
auto data = std::make_unique<float[]>(framesPerBuffer * file->info.channels);
file->count = sf_read_float(file->file, data.get(),
framesPerBuffer * file->info.channels);
for (int i = 0; i < framesPerBuffer * file->info.channels; i++) {
*out++ = data[i];
}
file->readHead += file->buffer_size;
if (file->count > 0) return paContinue;
else return paComplete;
}
此更改同时修复了音高和模糊,事后看来这是有道理的。
我一直在使用 PortAudio 和 sndfile 在 C++ 中编写一些代码来播放立体声 .wav 文件,但输出声音模糊且音高下降(音高下降对我来说不是什么问题,但它可能是问题的一部分)。它几乎看起来像是用模糊播放部分垃圾数据,但我不相信我正在使用任何变量,这可能发生在我没有先清除数据的地方。我试图跟随一些带有立体声播放的 PortAudios 示例,但由于输入来自 .wav 文件而不是生成的文件,我无法完美地跟随它。我还编译了 运行 一些 PortAudio 示例(使用立体声)并且它们工作正常。我不确定问题出在哪里。
Audio.h
struct AudioFile {
SNDFILE* file = nullptr;
SF_INFO info;
int buffer_size = 512;
int readHead = 0;
sf_count_t count = 1;
};
/*
Class for handling basic audio functions
*/
class Audio {
protected:
public:
/// Constructor
Audio();
/// Destructor
~Audio();
/// Load an audio file
AudioFile loadFile(const char* path);
/// Play an audio file
void playFile(AudioFile* file);
};
Audio.cpp
/// Audio constructor
Audio::Audio() {
PaError err = Pa_Initialize();
if (err != paNoError) std::cerr << "PAError: " << err << std::endl;
#ifdef DEBUG
std::cout << "Initialising PortAudio" << std::endl;
std::cout << "----------------------" << std::endl;
std::cout << "Version: " << Pa_GetVersion << std::endl;
std::cout << "Devices:" << std::endl;
std::cout << "----------------------" << std::endl;
int numDevices = Pa_GetDeviceCount();
for (int i=0; i < numDevices; i++) {
auto deviceInfo = Pa_GetDeviceInfo(i);
std::cout << "Name: " << deviceInfo->name << std::endl;
std::cout << "HostApi: " << deviceInfo->hostApi << std::endl;
std::cout << "SampleRate: " << deviceInfo->defaultSampleRate << std::endl;
std::cout << "InputChannels: " << deviceInfo->maxInputChannels << std::endl;
std::cout << "OutputChannels: " << deviceInfo->maxOutputChannels << std::endl;
std::cout << "----------------------" << std::endl;
}
#endif
}
Audio::~Audio() {
PaError err = Pa_Terminate();
if (err != paNoError) std::cerr << "PAError: " << err << std::endl;
}
/* Loads an audiofile */
AudioFile Audio::loadFile(const char* path) {
AudioFile file;
::memset(&file.info, 0, sizeof(file.info));
file.file = sf_open(path, SFM_READ, &file.info);
return file;
}
static int patestCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void* userData) {
/// Prevent warnings
(void)inputBuffer;
(void)timeInfo;
(void)statusFlags;
/// an AudioFile gets passed as userData
AudioFile* file = (AudioFile*)userData;
float* out = (float*)outputBuffer;
sf_seek(file->file, file->readHead, SF_SEEK_SET);
auto data = std::make_unique<float[]>(framesPerBuffer);
file->count = sf_read_float(file->file, data.get(), framesPerBuffer);
for (int i = 0; i < framesPerBuffer; i++) {
*out++ = data[i];
}
file->readHead += file->buffer_size;
if (file->count > 0) return paContinue;
else return paComplete;
}
void Audio::playFile(AudioFile* file) {
PaStream* stream = nullptr;
PaStreamParameters params;
params.device = Pa_GetDefaultOutputDevice();
params.channelCount = file->info.channels;
params.sampleFormat = paFloat32;
params.suggestedLatency =
Pa_GetDeviceInfo(params.device)->defaultLowOutputLatency;
params.hostApiSpecificStreamInfo = nullptr;
/// Check if params work
PaError err = Pa_IsFormatSupported(nullptr, ¶ms, file->info.samplerate);
if (err != paFormatIsSupported) {
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
return;
}
err = Pa_OpenStream(&stream, nullptr, ¶ms, file->info.samplerate,
file->buffer_size * params.channelCount, paClipOff,
&patestCallback, file);
if (err != paNoError) std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
err = Pa_StartStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
/// wait until file finishes playing
while (file->count > 0) {}
err = Pa_StopStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
err = Pa_CloseStream(stream);
if (err != paNoError)
std::cerr << "PAError: " << Pa_GetErrorText(err) << std::endl;
}
我也尝试过不使用 data
指针(使用它似乎产生更清晰但仍然模糊的声音)并将音频文件按值传递到 playFile
函数。感谢任何帮助。
终于搞清楚了,我有一个主要问题,在这里:
err = Pa_OpenStream(&stream, nullptr, ¶ms, file->info.samplerate,
file->buffer_size * params.channelCount, paClipOff,
&patestCallback, file);
我给了 Pa_OpenStream 缓冲区大小 * 通道数,但是我应该只是给它缓冲区大小,然后直接在回调函数中对 framesPerBuffer 进行通道调整:
static int patestCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags, void* userData) {
/// Prevent warnings
(void)inputBuffer;
(void)timeInfo;
(void)statusFlags;
/// an AudioFile gets passed as userData
velox::AudioFile* file = (velox::AudioFile*)userData;
float* out = (float*)outputBuffer;
sf_seek(file->file, file->readHead, SF_SEEK_SET);
auto data = std::make_unique<float[]>(framesPerBuffer * file->info.channels);
file->count = sf_read_float(file->file, data.get(),
framesPerBuffer * file->info.channels);
for (int i = 0; i < framesPerBuffer * file->info.channels; i++) {
*out++ = data[i];
}
file->readHead += file->buffer_size;
if (file->count > 0) return paContinue;
else return paComplete;
}
此更改同时修复了音高和模糊,事后看来这是有道理的。