将正弦波流式传输到 XAudio2

问题描述

我正在尝试编写一个通过 xaudio2 播放的非常简单的正弦波发生器。

目前正在播放声音,如果我调用 Win32XAudioInit() 然后调用 Win32PlayTone() 会播放一个提示音,并且在随后调用 Win32PlayTone() 时提示音会改变,但是几乎每次提示音都会有明显的咔嗒声变化。

我知道有几个原因可能会导致这种情况:

  1. 我没有跟踪相位偏移,这意味着新的波浪会错位。
  2. 我只是更新缓冲区指向的内存,而不考虑正在播放的内容

关于#2,我不确定 XAudio 是否希望我创建一个新的 xaudio2_BUFFER 并在每次更改音调时重新提交,或者我是否应该以某种方式跟踪“播放头”的位置(因为缺少一个更好的术语)并且只更新已经播放的字节。

我知道 #2 是否有问题,如果我修复了它,我将无法听到我仍然受到问题 #1 的困扰。 我已经通读了 XAudio2 - Play generated sine,when changing frequency clicking sound,如果我知道 xaudio2 设置正确,我想我可以找出正弦波问题。

任何想法都会有所帮助,谢谢!

struct win32_audio_buffer
{
    real32 Memory[44100 * 1];  // samples per buffer (44100) * channels 1
    int BytesPerBuffer;
    xaudio2_BUFFER XBuffer;
    Ixaudio2 *XEngine;
    Ixaudio2SourceVoice *SourceVoice;
    WAVEFORMATEX WaveFormat;
};

// NOTE xaudio2
internal HRESULT
Win32XAudioInit(win32_audio_buffer *AudioBuffer)
{
    // Initialize a COM:
    HRESULT HRes;
    HRes = CoInitializeEx(NULL,COINIT_MULTITHREADED);
    if(Failed(HRes)) { return(HRes); }
    
    // Init XAUdio Engine
    AudioBuffer->XEngine = {};
    if (Failed(HRes = xaudio2Create(&AudioBuffer->XEngine,xaudio2_DEFAULT_PROCESSOR)))
    { return HRes; }
    
    // MASTER VOICE
    Ixaudio2MasteringVoice* XAudioMasterVoice = nullptr;
    if (Failed(HRes = AudioBuffer->XEngine->CreateMasteringVoice(&XAudioMasterVoice)))
    { return HRes; }
    
    AudioBuffer->WaveFormat = {};
    
    //int32 SamplesPerBuffer = 4410;
    int SampleHz = 44100;
    WORD Channels = 1;
    WORD BitsPerChannel = 32; // 4 byte samples
    int32 BufferSize = Channels * BitsPerChannel * SampleHz;
    AudioBuffer->BytesPerBuffer = SampleHz * Channels;
    
    AudioBuffer->WaveFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; // or Could use WAVE_FORMAT_PCM WAVE_FORMAT_IEEE_FLOAT
    AudioBuffer->WaveFormat.nChannels = Channels;
    AudioBuffer->WaveFormat.nSamplesPerSec = SampleHz;
    AudioBuffer->WaveFormat.wBitsPerSample = BitsPerChannel; // 32
    AudioBuffer->WaveFormat.nBlockAlign = (Channels * BitsPerChannel) / 8;
    AudioBuffer->WaveFormat.nAvgBytesPerSec = SampleHz * Channels * BitsPerChannel / 8;
    AudioBuffer->WaveFormat.cbSize = 0;    // set to zero for PCM or IEEE float
    
    AudioBuffer->XBuffer.Flags = 0;
    AudioBuffer->XBuffer.AudioBytes = SampleHz * Channels * BitsPerChannel / 8;
    AudioBuffer->XBuffer.PlayBegin = 0;
    AudioBuffer->XBuffer.PlayLength = 0;
    AudioBuffer->XBuffer.LoopBegin = 0;
    AudioBuffer->XBuffer.LoopLength = 0;
    AudioBuffer->XBuffer.LoopCount = xaudio2_LOOP_INFINITE;
    AudioBuffer->XBuffer.pContext = NULL;
    AudioBuffer->XBuffer.pAudioData = (BYTE *)&AudioBuffer->Memory;
    
    if(Failed(HRes = AudioBuffer->XEngine->CreateSourceVoice(&AudioBuffer->SourceVoice,(WAVEFORMATEX*)&AudioBuffer->WaveFormat))) 
    { return HRes; }
    
    if(Failed(HRes = AudioBuffer->SourceVoice->Start(0)))
    { return HRes; }
    
    if(Failed(HRes = AudioBuffer->SourceVoice->SubmitSourceBuffer(&AudioBuffer->XBuffer)))
    { return HRes; }
    
    return(S_OK);
}

internal HRESULT
Win32PlayTone(win32_audio_buffer *Buffer,int32 Hz)
{
    real32 PI2 = (real32)6.28318; //530718;
    
    for(int i = 0;
        i < Buffer->BytesPerBuffer;
        i++)
    {
        real32 CurrentSample = sinf(i * PI2 / 44100 * Hz);
        Buffer->Memory[i] = CurrentSample;
    }
    
    return(S_OK);
}

解决方法

XAudio2 是完全异步的,因此在数据包完成之前,您不应更改播放数据包所指向的内存,否则您将获得如您描述的点击。

此外,当当前数据包完成时,如果您希望声音连续,您希望另一个已经排队。

请参阅这些资源以了解如何对 XAudio2 进行编程: