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::Callbackstatic?那会起作用的。

二是你需要考虑回调是在另一个线程中调用的。编译器在优化代码时不会考虑到这一点。据它所知,你的空 while 循环中没有任何东西可以改变 file->count.

的值

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

但即使有效,也确实很糟糕。因为线程坐在那里忙于等待计数停止,占用 the/a CPU。它应该阻塞并休眠,然后在播放结束时收到通知。

如果您想以一种有效的方式在线程之间传递信息,并且还想阻塞而不是忙于等待,请查看 C++ concurrency support library。 C++20 std::latchstd::barrier 在这里工作得很好。或者使用 C++11 std::condition_variable.