C++ - XAudio2 - 尝试播放连续的正弦声音时发出噼啪声

C++ - XAudio2 - Cracking sound when trying to play a continuous sine sound

编辑:今天我发现我只有在使用有线耳机时才会遇到这个问题。不是耳机的问题,因为同样的耳机也可以无线使用,然后问题就消失了,类似于我使用其他无线耳机时的情况。不过,我更喜欢使用电源线,因为它的延迟更小。所以我希望有人可以提供这些额外信息来帮助我解开这个谜团。

我有这段代码可以播放正弦波声音。当我尝试播放它时,我不断听到咔嗒声。我很确定这是因为缓冲区的播放不完美,因为当您将 l 的值更改为更大的值(例如 44100)时,点击距离更远。我想我已经尽可能准确地遵循了 Microsoft 网站上关于如何使用回调的解释。我创建了三个轮流播放的源声音:一个正在播放,下一个准备就绪,下一个正在制作。我使用总时间(tt)放入sin()函数,因此下一个缓冲区的第一个字节应该与当前缓冲区的最后一个字节完全对齐。

有谁知道出了什么问题吗?

P.S.: 很多类似的问题都没有回答我的问题。简而言之:我没有修改播放缓冲区(至少我不这么认为);一个缓冲区到另一个缓冲区的边界不应有不连续性;我在播放期间也没有调整频率。所以我不认为这是重复的。

#include <xaudio2.h>
#include <iostream>

#define PI 3.14159265358979323846f
#define l 4410 //0.1 seconds

IXAudio2MasteringVoice* pMasterVoice;
IXAudio2* pXAudio2;
IXAudio2SourceVoice* pSourceVoice[3];
XAUDIO2_BUFFER buffer;
WAVEFORMATEX wfx;
XAUDIO2_VOICE_STATE state;
BYTE pDataBuffer[2*l];
BYTE bytw[2];

int pow16[2];
float w[l];
int i, p;
float tt, ampl;

class VoiceCallback : public IXAudio2VoiceCallback {
public:
    HANDLE hBufferEndEvent;
    VoiceCallback() : hBufferEndEvent(CreateEvent(NULL, FALSE, FALSE, NULL)) {}
    ~VoiceCallback() { CloseHandle(hBufferEndEvent); }

    //Called when the voice has just finished playing a contiguous audio stream.
    void STDMETHODCALLTYPE OnStreamEnd() {SetEvent(hBufferEndEvent);}

    //Unused methods are stubs
    void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() {}
    void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 SamplesRequired) {}
    void STDMETHODCALLTYPE OnBufferEnd(void * pBufferContext) {}
    void STDMETHODCALLTYPE OnBufferStart(void * pBufferContext) {}
    void STDMETHODCALLTYPE OnLoopEnd(void * pBufferContext) {}
    void STDMETHODCALLTYPE OnVoiceError(void * pBufferContext, HRESULT Error) {}
};

VoiceCallback voiceCallback[3];

int main() {
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);

    pXAudio2 = nullptr;
    XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);

    pMasterVoice = nullptr;
    pXAudio2->CreateMasteringVoice(&pMasterVoice);

    tt = 0, p = 660, ampl = 2000;

    pow16[0] = 16;
    pow16[1] = 4096;

    wfx = {0};
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = (WORD)1; //mono
    wfx.nSamplesPerSec = (DWORD)44100; //samplerate
    wfx.wBitsPerSample = (WORD)16; //16 bit (signed)
    wfx.nBlockAlign = (WORD)2; //2 bytes per sample
    wfx.nAvgBytesPerSec = (DWORD)88200; //samplerate*blockalign
    wfx.cbSize = (WORD)0;

    i = 0;
    while (true) {
        for (int t = 0; t < l; t++) {
            tt = (float)(t + i*l); //total time

            w[t] = sin(2.f*PI*tt/p)*ampl;

            int intw = (int)w[t];
            if (intw < 0) {
                intw += 65535;
            }

            bytw[0] = 0; bytw[1] = 0;
            for (int k = 1; k >= 0; k--) {
                //turn integer into a little endian byte array
                bytw[k] += (BYTE)(16*(intw/pow16[k]));
                intw -= bytw[k]*(pow16[k]/16);
                bytw[k] += (BYTE)(intw/(pow16[k]/16));
                intw -= (intw/(pow16[k]/16))*pow16[k]/16;
            }

            pDataBuffer[2*t] = bytw[0];
            pDataBuffer[2*t + 1] = bytw[1];
        }

        buffer.AudioBytes = 2*l; //number of bytes per buffer
        buffer.pAudioData = pDataBuffer;
        buffer.Flags = XAUDIO2_END_OF_STREAM;

        if (i > 2) {
            pSourceVoice[i%3]->DestroyVoice();
        }
        pSourceVoice[i%3] = nullptr;

        pXAudio2->CreateSourceVoice(&pSourceVoice[i%3], &wfx, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &voiceCallback[i%3], NULL, NULL);
        pSourceVoice[i%3]->SubmitSourceBuffer(&buffer);

        if (i > 1) {
            //wait until the current one is done playing
            while (pSourceVoice[(i - 2)%3]->GetState(&state), state.BuffersQueued > 0) {
                WaitForSingleObjectEx(voiceCallback[(i - 2)%3].hBufferEndEvent, INFINITE, TRUE);
            }
        }
        if (i > 0) {
            //play the next one while you're writing the one after that in the next iteration
            pSourceVoice[(i - 1)%3]->Start(0);
        }
        i++;
    }
}

如果您希望声音是 'looping',则将多个 data-packets 提交到同一源语音 - 或 - 设置一个循环值,以便它自动重新启动现有的音频数据包。如果您允许源语音 运行 超出数据,那么您将听到中间的中断,具体取决于音频输出系统的延迟。

此外,创建和销毁源声音是一个相对昂贵的操作,因此像这样循环执行并不是特别有效。

See DirectX Tool Kit for Audio for a complete example of XAudio2 usage, as well as the latest version of the XAudio2 samples from the legacy DirectX SDK on GitHub.