Portaudio - 无法播放音频文件
我正在尝试使用portaudio
实现一个非常简单的API来进行音频播放。播放音频文件所需的代码最少,但是我没有得到任何错误和/或音频输出。
这是代码,
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;
}
我注意到,如果我在中添加一个打印语句,而
循环我确实会获取音频输出。
// Wait until file finishes playing
while (file->count > 0) { SH_LOG_DEBUG(""); }
为什么不使用打印语句或循环中的任何内容播放?我必须在循环中有一些东西吗?
I'm trying to implement a very simple API for audio playback using portaudio
. I have minimal amount of code needed to play the audio file but I am not getting any errors and/or audio output.
Here is the code,
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;
}
I noticed that if I add a print statement in the while
loop I do get audio output.
// Wait until file finishes playing
while (file->count > 0) { SH_LOG_DEBUG(""); }
Why doesn't it play with a print statement or anything in while loop? Do I have to have something in the while loop?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我可以看出你有两个缺点。
一种是使用看起来像类方法的方法作为 PulseAudio 的回调。由于 PA 是一个 C API,因此它需要一个 C 函数指针。它不会使用
this
指针集调用该函数,因此这里不能有类方法。但也许AudioStream::Callback
是static
?那会起作用的。二是需要考虑回调是在另一个线程中调用的。编译器在优化代码时不会考虑这一点。据了解,空 while 循环中没有任何内容可能会更改
file->count
的值。一旦调用调试函数,它就会引入足够的代码,其中一些可能位于已编译的库中,编译器无法确定没有任何内容修改了
file->count
。也许SH_LOG_DEBUG()
调用prinf()
,然后printf()
调用AudioStream::Load()
?当然不会,但是如果编译器看不到printf()
的代码,因为它已经在库中,并且您的AudioStream
对象是全局的,那么这是可能的。所以它实际上按照你想要的方式工作。但即使有效,效果也很糟糕。因为线程正坐在那里忙着等待计数停止,从而占用了 CPU。它应该阻塞并休眠,然后在播放完成时收到通知。
如果您想以有效的方式在线程之间传递信息,并且也想阻塞而不是忙于等待,请查看 C++并发支持库。 C++20
std::latch
和std::barrier
在这里可以很好地工作。或者使用 C++11std::condition_variable
。You've got two flaws that I can see.
One is using what looks like a class method as the callback to PulseAudio. Since PA is a C API, it expects a C function pointer here. It won't call that function with the
this
pointer set, so you can't have a class method here. But maybeAudioStream::Callback
isstatic
? That will work.Two is that you need to consider that the callback is called in another thread. The compiler does not take that into account when it optimizes the code. As far as it knows, there is nothing in your empty while loop that could possibly change the value of
file->count
.Once you call the debug function, it brings in enough code, some probably in libraries that are already compiled, that the compiler can't be sure nothing has modified
file->count
. MaybeSH_LOG_DEBUG()
callsprinf()
and thenprintf()
callsAudioStream::Load()
? Of course it doesn't, but if the compiler doesn't see the code ofprintf()
because it's already in a library, and yourAudioStream
object is global, then it's possible. So it actually works like you want.But even when it works, it's really bad. Because the thread is sitting there busy waiting for the count to stop, hogging the/a CPU. It should block and sleep, then get notified when the playback finishes.
If you want to pass information between threads in a way that works, and also block instead of busy waiting too, look into the C++ concurrency support library. The C++20
std::latch
andstd::barrier
would work well here. Or use a C++11std::condition_variable
.