Portaudio - 无法播放音频文件
Portaudio - Unable to play audio file
我正在尝试使用 portaudio
实现一个非常简单的 API 音频播放。我只需要最少的代码来播放音频文件,但我没有收到任何错误 and/or 音频输出。
这是代码,
AudioStream::AudioStream()
{
PaError err = Pa_Initialize();
if (err != paNoError)
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
}
AudioStream::~AudioStream()
{
PaError err = Pa_Terminate();
if (err != paNoError)
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
}
int AudioStream::Callback(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->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;
}
AudioFile AudioStream::Load(const char* path)
{
AudioFile file;
std::memset(&file.info, 0, sizeof(file.info));
file.file = sf_open(path, SFM_READ, &file.info);
return file;
}
bool AudioStream::Play(AudioFile* file)
{
m_OutputParameters.device = Pa_GetDefaultOutputDevice();
m_OutputParameters.channelCount = file->info.channels;
m_OutputParameters.sampleFormat = paFloat32;
m_OutputParameters.suggestedLatency = Pa_GetDeviceInfo(m_OutputParameters.device)->defaultLowOutputLatency;
m_OutputParameters.hostApiSpecificStreamInfo = nullptr;
// Check if m_OutputParameters work
PaError err = Pa_IsFormatSupported(nullptr, &m_OutputParameters, file->info.samplerate);
if (err != paFormatIsSupported)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
err = Pa_OpenStream(&m_pStream,
nullptr,
&m_OutputParameters,
file->info.samplerate,
file->buffer_size,
paClipOff,
&AudioStream::Callback,
file);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
if (m_pStream)
{
err = Pa_StartStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
SH_LOG_DEBUG("Waiting for playback to finish..");
while (IsStreamActive()) // <----- this works, but the application freezes until this ends
Pa_Sleep(500);
Stop();
if (IsStreamStopped())
{
SH_LOG_DEBUG("Stream stopped..");
err = Pa_CloseStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
}
SH_LOG_DEBUG("Done..");
}
return true;
}
bool AudioStream::Stop()
{
PaError err = Pa_StopStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
else
return true;
}
bool AudioStream::IsStreamStopped()
{
if (Pa_IsStreamStopped(m_pStream))
return true;
else
return false;
}
我注意到,如果我在 while
循环中添加打印语句,我会得到音频输出。
// Wait until file finishes playing
while (file->count > 0) { SH_LOG_DEBUG(""); }
为什么它不使用 print 语句或 while 循环中的任何内容?我必须在 while 循环中有一些东西吗?
你有两个我能看到的缺陷。
一个正在使用看起来像 class 的方法作为 PulseAudio 的回调。由于 PA 是一个 C API,它在这里需要一个 C 函数指针。它不会使用 this
指针集调用该函数,因此您不能在此处使用 class 方法。但也许 AudioStream::Callback
是 static
?那会起作用的。
二是你需要考虑回调是在另一个线程中调用的。编译器在优化代码时不会考虑到这一点。据它所知,你的空 while 循环中没有任何东西可以改变 file->count
.
的值
调用调试函数后,它会引入足够的代码,其中一些可能在已经编译的库中,编译器无法确定没有修改任何内容 file->count
。也许 SH_LOG_DEBUG()
调用 prinf()
然后 printf()
调用 AudioStream::Load()
?当然不是,但是如果编译器看不到 printf()
的代码,因为它已经在库中,而你的 AudioStream
对象是全局的,那么它是可能的。所以它实际上如你所愿。
但即使有效,也确实很糟糕。因为线程坐在那里忙于等待计数停止,占用 the/a CPU。它应该阻塞并休眠,然后在播放结束时收到通知。
如果您想以一种有效的方式在线程之间传递信息,并且还想阻塞而不是忙于等待,请查看 C++ concurrency support library。 C++20 std::latch
和 std::barrier
在这里工作得很好。或者使用 C++11 std::condition_variable
.
我正在尝试使用 portaudio
实现一个非常简单的 API 音频播放。我只需要最少的代码来播放音频文件,但我没有收到任何错误 and/or 音频输出。
这是代码,
AudioStream::AudioStream()
{
PaError err = Pa_Initialize();
if (err != paNoError)
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
}
AudioStream::~AudioStream()
{
PaError err = Pa_Terminate();
if (err != paNoError)
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
}
int AudioStream::Callback(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->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;
}
AudioFile AudioStream::Load(const char* path)
{
AudioFile file;
std::memset(&file.info, 0, sizeof(file.info));
file.file = sf_open(path, SFM_READ, &file.info);
return file;
}
bool AudioStream::Play(AudioFile* file)
{
m_OutputParameters.device = Pa_GetDefaultOutputDevice();
m_OutputParameters.channelCount = file->info.channels;
m_OutputParameters.sampleFormat = paFloat32;
m_OutputParameters.suggestedLatency = Pa_GetDeviceInfo(m_OutputParameters.device)->defaultLowOutputLatency;
m_OutputParameters.hostApiSpecificStreamInfo = nullptr;
// Check if m_OutputParameters work
PaError err = Pa_IsFormatSupported(nullptr, &m_OutputParameters, file->info.samplerate);
if (err != paFormatIsSupported)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
err = Pa_OpenStream(&m_pStream,
nullptr,
&m_OutputParameters,
file->info.samplerate,
file->buffer_size,
paClipOff,
&AudioStream::Callback,
file);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
if (m_pStream)
{
err = Pa_StartStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
SH_LOG_DEBUG("Waiting for playback to finish..");
while (IsStreamActive()) // <----- this works, but the application freezes until this ends
Pa_Sleep(500);
Stop();
if (IsStreamStopped())
{
SH_LOG_DEBUG("Stream stopped..");
err = Pa_CloseStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
}
SH_LOG_DEBUG("Done..");
}
return true;
}
bool AudioStream::Stop()
{
PaError err = Pa_StopStream(m_pStream);
if (err != paNoError)
{
SH_LOG_ERROR("PAError: {}", Pa_GetErrorText(err));
return false;
}
else
return true;
}
bool AudioStream::IsStreamStopped()
{
if (Pa_IsStreamStopped(m_pStream))
return true;
else
return false;
}
我注意到,如果我在 while
循环中添加打印语句,我会得到音频输出。
// Wait until file finishes playing
while (file->count > 0) { SH_LOG_DEBUG(""); }
为什么它不使用 print 语句或 while 循环中的任何内容?我必须在 while 循环中有一些东西吗?
你有两个我能看到的缺陷。
一个正在使用看起来像 class 的方法作为 PulseAudio 的回调。由于 PA 是一个 C API,它在这里需要一个 C 函数指针。它不会使用 this
指针集调用该函数,因此您不能在此处使用 class 方法。但也许 AudioStream::Callback
是 static
?那会起作用的。
二是你需要考虑回调是在另一个线程中调用的。编译器在优化代码时不会考虑到这一点。据它所知,你的空 while 循环中没有任何东西可以改变 file->count
.
调用调试函数后,它会引入足够的代码,其中一些可能在已经编译的库中,编译器无法确定没有修改任何内容 file->count
。也许 SH_LOG_DEBUG()
调用 prinf()
然后 printf()
调用 AudioStream::Load()
?当然不是,但是如果编译器看不到 printf()
的代码,因为它已经在库中,而你的 AudioStream
对象是全局的,那么它是可能的。所以它实际上如你所愿。
但即使有效,也确实很糟糕。因为线程坐在那里忙于等待计数停止,占用 the/a CPU。它应该阻塞并休眠,然后在播放结束时收到通知。
如果您想以一种有效的方式在线程之间传递信息,并且还想阻塞而不是忙于等待,请查看 C++ concurrency support library。 C++20 std::latch
和 std::barrier
在这里工作得很好。或者使用 C++11 std::condition_variable
.