Portaudio - 无法播放音频文件

发布于 2025-01-19 17:30:01 字数 4672 浏览 0 评论 0原文

我正在尝试使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

泪眸﹌ 2025-01-26 17:30:01

我可以看出你有两个缺点。

一种是使用看起来像类方法的方法作为 PulseAudio 的回调。由于 PA 是一个 C API,因此它需要一个 C 函数指针。它不会使用 this 指针集调用该函数,因此这里不能有类方法。但也许 AudioStream::Callbackstatic ?那会起作用的。

二是需要考虑回调是在另一个线程中调用的。编译器在优化代码时不会考虑这一点。据了解,空 while 循环中没有任何内容可能会更改 file->count 的值。

一旦调用调试函数,它就会引入足够的代码,其中一些可能位于已编译的库中,编译器无法确定没有任何内容修改了 file->count。也许 SH_LOG_DEBUG() 调用 prinf(),然后 printf() 调用 AudioStream::Load()?当然不会,但是如果编译器看不到 printf() 的代码,因为它已经在库中,并且您的 AudioStream 对象是全局的,那么这是可能的。所以它实际上按照你想要的方式工作。

但即使有效,效果也很糟糕。因为线程正坐在那里忙着等待计数停止,从而占用了 CPU。它应该阻塞并休眠,然后在播放完成时收到通知。

如果您想以有效的方式在线程之间传递信息,并且也想阻塞而不是忙于等待,请查看 C++并发支持库。 C++20 std::latchstd::barrier 在这里可以很好地工作。或者使用 C++11 std::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 maybe AudioStream::Callback is static? 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. Maybe SH_LOG_DEBUG() calls prinf() and then printf() calls AudioStream::Load()? Of course it doesn't, but if the compiler doesn't see the code of printf() because it's already in a library, and your AudioStream 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 and std::barrier would work well here. Or use a C++11 std::condition_variable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文