【问题标题】:Waveform Audio - waveOutWrite gives choppy sound波形音频 - waveOutWrite 发出断断续续的声音
【发布时间】:2018-08-28 10:53:44
【问题描述】:

我正在尝试使用 Waveform Audio library 创建一个 C++ 程序,该程序将播放另一个程序提供的 AudioFrames(原始音频数据,每帧包含大约 1920 个字节)(现在我只是通过读取文件来模拟它作为音频帧)。从this thread 修改代码我能够制作完成这项工作的 SoundPlayer 类,但我得到的输出非常不稳定。更大的帧大小会变得更好,但即使帧大到 96000 字节,音频仍然每秒左右出现故障(我需要帧也比这小得多)。

我该如何解决这个问题?

Here 是我正在使用的测试文件。这是代码本身:

#include <windows.h>
#include <iostream>
#pragma comment(lib, "Winmm.lib")

constexpr int FRAME_SIZE_IN_BYTES = 1920;

struct AudioFrame
{
    char *Data;
    int DataSize;
};

class SoundPlayer
{
public:

    SoundPlayer()
    {
        // Initialize the sound format we will request from sound card
        m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;     // Uncompressed sound format
        m_waveFormat.nChannels = 1;                    // 1 = Mono, 2 = Stereo
        m_waveFormat.wBitsPerSample = 16;               // Bits per sample per channel
        m_waveFormat.nSamplesPerSec = 48000;           // Sample Per Second
        m_waveFormat.nBlockAlign = m_waveFormat.nChannels * m_waveFormat.wBitsPerSample / 8;
        m_waveFormat.nAvgBytesPerSec = m_waveFormat.nSamplesPerSec * m_waveFormat.nBlockAlign;
        m_waveFormat.cbSize = 0;
    }

    void Play(AudioFrame* af)
    {
        // Create our "Sound is Done" event
        m_done = CreateEvent(0, FALSE, FALSE, 0);

        // Open the audio device
        if (waveOutOpen(&m_waveOut, 0, &m_waveFormat, (DWORD)m_done, 0, CALLBACK_EVENT) != MMSYSERR_NOERROR)
        {
            std::cout << "Sound card cannot be opened." << std::endl;
            return;
        }

        // Create the wave header for our sound buffer
        m_waveHeader.lpData = af->Data;
        m_waveHeader.dwBufferLength = af->DataSize;
        m_waveHeader.dwFlags = 0;
        m_waveHeader.dwLoops = 0;

        // Prepare the header for playback on sound card
        if (waveOutPrepareHeader(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error preparing Header!" << std::endl;
            return;
        }

        ResetEvent(m_done); // Reset our Event so it is non-signaled, it will be signaled again with buffer finished

        // Play the sound!
        if (waveOutWrite(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error writing to sound card!" << std::endl;
            return;
        }

        // Wait until sound finishes playing
        if (WaitForSingleObject(m_done, INFINITE) != WAIT_OBJECT_0)
        {
            std::cout << "Error waiting for sound to finish" << std::endl;
            return;
        }

        // Unprepare our wav header
        if (waveOutUnprepareHeader(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error unpreparing header!" << std::endl;
            return;
        }

        // Close the wav device
        if (waveOutClose(m_waveOut) != MMSYSERR_NOERROR)
        {
            std::cout << "Sound card cannot be closed!" << std::endl;
            return;
        }

        // Release our event handle
        CloseHandle(m_done);
    }



private:
    HWAVEOUT m_waveOut; // Handle to sound card output
    WAVEFORMATEX m_waveFormat; // The sound format
    WAVEHDR m_waveHeader; // WAVE header for our sound data
    HANDLE m_done; // Event Handle that tells us the sound has finished being played.
                   // This is a very efficient way to put the program to sleep
                   // while the sound card is processing the sound buffer

};

int main()
{
    FILE * fileDes;
    fopen_s(&fileDes, "Ducksauce.raw", "rb");
    if (fileDes == nullptr)
        std::cout << "File opening failed.\n";

    int bufferSize = FRAME_SIZE_IN_BYTES;
    char *buffer = new char[bufferSize];

    SoundPlayer sp;

    while (fread(buffer, sizeof(char), bufferSize, fileDes) > 0)
    {
        AudioFrame af;
        af.Data = buffer;
        af.DataSize = bufferSize;
        sp.Play(&af);
    }

    fclose(fileDes);
    delete[] buffer;
    return 0;
}

编辑:版本号 2。仍然无法按预期工作。

#include <windows.h>
#include <iostream>
#pragma comment(lib, "Winmm.lib")

constexpr int FRAME_SIZE_IN_BYTES = 1920;

struct AudioFrame
{
    char *Data;
    int DataSize;
};

class SoundPlayer
{
public:

    SoundPlayer()
    {
        // Initialize the sound format we will request from sound card
        m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;     // Uncompressed sound format
        m_waveFormat.nChannels = 1;                    // 1 = Mono, 2 = Stereo
        m_waveFormat.wBitsPerSample = 16;               // Bits per sample per channel
        m_waveFormat.nSamplesPerSec = 48000;           // Sample Per Second
        m_waveFormat.nBlockAlign = m_waveFormat.nChannels * m_waveFormat.wBitsPerSample / 8;
        m_waveFormat.nAvgBytesPerSec = m_waveFormat.nSamplesPerSec * m_waveFormat.nBlockAlign;
        m_waveFormat.cbSize = 0;

        // Create our "Sound is Done" event
        m_done = CreateEvent(0, FALSE, FALSE, 0);

        // Open the audio device
        if (waveOutOpen(&m_waveOut, 0, &m_waveFormat, (DWORD)m_done, 0, CALLBACK_EVENT) != MMSYSERR_NOERROR)
        {
            std::cout << "Sound card cannot be opened." << std::endl;
            return;
        }
    }

    ~SoundPlayer()
    {
        // Close the wav device
        if (waveOutClose(m_waveOut) != MMSYSERR_NOERROR)
        {
            std::cout << "Sound card cannot be closed!" << std::endl;
            return;
        }

        // Release our event handle
        CloseHandle(m_done);
    }

    void StartPlaying(AudioFrame* af)
    {
        // Create the wave header for our sound buffer
        m_waveHeader.lpData = af->Data;
        m_waveHeader.dwBufferLength = af->DataSize;
        m_waveHeader.dwFlags = 0;
        m_waveHeader.dwLoops = 0;

        // Prepare the header for playback on sound card
        if (waveOutPrepareHeader(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error preparing Header!" << std::endl;
            return;
        }

        ResetEvent(m_done); // Reset our Event so it is non-signaled, it will be signaled again with buffer finished

        // Play the sound!
        if (waveOutWrite(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error writing to sound card!" << std::endl;
            return;
        }
    }

    void WaitUntilFrameFinishes()
    {
        // Wait until sound finishes playing
        if (WaitForSingleObject(m_done, INFINITE) != WAIT_OBJECT_0)
        {
            std::cout << "Error waiting for sound to finish" << std::endl;
            return;
        }
        // Unprepare our wav header
        if (waveOutUnprepareHeader(m_waveOut, &m_waveHeader, sizeof(m_waveHeader)) != MMSYSERR_NOERROR)
        {
            std::cout << "Error unpreparing header!" << std::endl;
            return;
        }
    }

private:
    HWAVEOUT m_waveOut; // Handle to sound card output
    WAVEFORMATEX m_waveFormat; // The sound format
    WAVEHDR m_waveHeader; // WAVE header for our sound data
    HANDLE m_done; // Event Handle that tells us the sound has finished being played.
                   // This is a very efficient way to put the program to sleep
                   // while the sound card is processing the sound buffer

};

int main()
{
    FILE * fileDes;
    fopen_s(&fileDes, "Ducksauce.raw", "rb");
    if (fileDes == nullptr)
        std::cout << "File opening failed.\n";

    int bufferSize = FRAME_SIZE_IN_BYTES;
    char *buffer = new char[bufferSize];

    SoundPlayer sp;

    // Read first time
    fread(buffer, sizeof(char), bufferSize, fileDes);

    while (true)
    {
        AudioFrame af;
        af.Data = buffer;
        af.DataSize = bufferSize;
        // Start playing, but don't block
        sp.StartPlaying(&af);
        // Prepare the next chunk
        if (fread(buffer, sizeof(char), bufferSize, fileDes) <= 0)
            break;
        // Now block the code, waiting with next chunk already loaded
        // and ready to be played in the next iteration.
        sp.WaitUntilFrameFinishes();
    }

    fclose(fileDes);
    delete[] buffer;
    return 0;
}

编辑2:如果我在while之前添加它,它会起作用:

for (int i = 0; i < 3; i++ )
{
    fread(buffer, sizeof(char), bufferSize, fileDes);
    af.Data = buffer;
    af.DataSize = bufferSize;
    sp.StartPlaying(&af);
}

我也稍微修改了一下:

while (true)
{
    // Prepare the next chunk
    if (fread(buffer, sizeof(char), bufferSize, fileDes) <= 0)
        break;
    // Now block the code, waiting with next chunk already loaded
    // and ready to be played in the next iteration.
    sp.WaitUntilFrameFinishes();

    af.Data = buffer;
    af.DataSize = bufferSize;
    sp.StartPlaying(&af);
}

【问题讨论】:

  • 关于编辑:这很有趣。如果您无论如何都在等待音频完成,我认为推入 3 帧并不重要。显然还有一些我不知道的事情正在发生。很高兴您解决了问题,但我会在合适的时间深入挖掘以了解问题的根源。
  • @BartekBanachewicz 现在 95% 的情况下它都能正常工作,但有时几秒钟后就会变得不稳定。可能连接到其他应用同时使用播放设备。

标签: c++ audio playback waveform


【解决方案1】:

您应该在播放声音时从磁盘读取数据,而不是在缓冲区之间!

如果您不能一次读取整个文件,您应该更改您的Play 函数,使其不只是调用WaitForSingleObject。使用它会使您的代码阻塞并等待声音停止播放。

你需要的是开始播放,然后回到你的阅读循环,准备下一个缓冲区,然后然后等待音乐结束,就像这样(SoundPlayer):

void WaitUntilFrameFinishes() {
    // Wait until sound finishes playing
    if (WaitForSingleObject(m_done, INFINITE) != WAIT_OBJECT_0)

    // ... move all the code from Play till the end here
}

然后回到main循环:

// Read first frame
fread(buffer, sizeof(char), bufferSize, fileDes);

while (true)
{
    AudioFrame af;
    af.Data = buffer;
    af.DataSize = bufferSize;

    // Start playing, but don't block
    sp.Play(&af);

    // Prepare the next chunk
    if (fread(buffer, sizeof(char), bufferSize, fileDes) <= 0) {
        break;

    // Now block the code, waiting with next chunk already loaded
    // and ready to be played in the next iteration.
    sp.WaitUntilFrameFinishes();
}

理想情况下,您还可以将 fread 调用包装成可以以更好的方式提供块的东西。

【讨论】:

  • 我试图实现这个解决方案,但我不知道我是否正确,因为问题仍然存在。
  • @Soonmoon 你应该只在你的问题被它解决之后或者至少在它帮助你之后才接受答案。我需要构建整个代码并尝试复制您的问题,因为这应该修复它。
  • 对不起。经过一番试验,我发现在等待之前写 3 帧似乎可以解决问题。
【解决方案2】:

在尝试仅根据文档弄清楚如何进行音频播放之后,我找到了this excellent tutorial。如果有人在尝试使用 Waveform 音频创建音频播放时发现此线程,这是一个非常好的参考点(肯定比我上面的错误代码好得多)。

关于我的代码,我怀疑它不能正常工作,因为应该使用waveOutWrite() 始终保持 AudioFrames 队列至少有几个帧,以防止声卡必须等待另一个 AudioFrame 的情况。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多