我无法在 xaudio2 中播放两次声音

I cannot play a sound twice in xaudio2

我正在尝试设置 xaudio2 并一直在关注文档。我可以很好地播放声音,直到我再次尝试播放,此时什么也没有发生。 我几乎逐行地遵循文档,但无法弄清楚为什么会这样。 这是代码。

        #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <xaudio2.h>
    #include <Windows.h>

    #ifdef _XBOX //Big-Endian
    #define fourccRIFF 'RIFF'
    #define fourccDATA 'data'
    #define fourccFMT 'fmt '
    #define fourccWAVE 'WAVE'
    #define fourccXWMA 'XWMA'
    #define fourccDPDS 'dpds'
    #endif

    #ifndef _XBOX //Little-Endian
    #define fourccRIFF 'FFIR'
    #define fourccDATA 'atad'
    #define fourccFMT ' tmf'
    #define fourccWAVE 'EVAW'
    #define fourccXWMA 'AMWX'
    #define fourccDPDS 'sdpd'
    #endif
    HRESULT FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
    {
        HRESULT hr = S_OK;
        if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
            return HRESULT_FROM_WIN32(GetLastError());

        DWORD dwChunkType;
        DWORD dwChunkDataSize;
        DWORD dwRIFFDataSize = 0;
        DWORD dwFileType;
        DWORD bytesRead = 0;
        DWORD dwOffset = 0;

        while (hr == S_OK)
        {
            DWORD dwRead;
            if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
                hr = HRESULT_FROM_WIN32(GetLastError());

            if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
                hr = HRESULT_FROM_WIN32(GetLastError());

            switch (dwChunkType)
            {
            case fourccRIFF:
                dwRIFFDataSize = dwChunkDataSize;
                dwChunkDataSize = 4;
                if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
                    hr = HRESULT_FROM_WIN32(GetLastError());
                break;

            default:
                if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
                    return HRESULT_FROM_WIN32(GetLastError());
            }

            dwOffset += sizeof(DWORD) * 2;

            if (dwChunkType == fourcc)
            {
                dwChunkSize = dwChunkDataSize;
                dwChunkDataPosition = dwOffset;
                return S_OK;
            }

            dwOffset += dwChunkDataSize;

            if (bytesRead >= dwRIFFDataSize) return S_FALSE;

        }

        return S_OK;

    }
    HRESULT ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset)
    {
        HRESULT hr = S_OK;
        if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
            return HRESULT_FROM_WIN32(GetLastError());
        DWORD dwRead;
        if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
            hr = HRESULT_FROM_WIN32(GetLastError());
        return hr;
    }


    class VoiceCallback : public IXAudio2VoiceCallback
    {
    public:
        void STDMETHODCALLTYPE OnStreamEnd() override
        {}
        void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() override
        {}
        void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 SamplesRequired) override
        {}
        void STDMETHODCALLTYPE OnBufferEnd(void* pBufferContext) override
        {
        
            std::cout << "buffer end" << std::endl;
        }
        void STDMETHODCALLTYPE OnBufferStart(void* pBufferContext) override
        {
            std::cout << "buffer start" << std::endl;
        }
        void STDMETHODCALLTYPE OnLoopEnd(void* pBufferContext) override
        {}
        void STDMETHODCALLTYPE OnVoiceError(void* pBufferContext, HRESULT Error) override
        {}
    };

    int main()
    {


        WAVEFORMATEXTENSIBLE wfx = { 0 };
        XAUDIO2_BUFFER buffer = { 0 };
        HANDLE hFile = CreateFileA(
            "meow.wav",
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            0,
            NULL);

        if (INVALID_HANDLE_VALUE == hFile)
            return HRESULT_FROM_WIN32(GetLastError());

        if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
            return HRESULT_FROM_WIN32(GetLastError());

        DWORD dwChunkSize;
        DWORD dwChunkPosition;
        //check the file type, should be fourccWAVE or 'XWMA'
        FindChunk(hFile, fourccRIFF, dwChunkSize, dwChunkPosition);
        DWORD filetype;
        ReadChunkData(hFile, &filetype, sizeof(DWORD), dwChunkPosition);
        if (filetype != fourccWAVE)
            return S_FALSE;
        FindChunk(hFile, fourccFMT, dwChunkSize, dwChunkPosition);
        ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition);
        //fill out the audio data buffer with the contents of the fourccDATA chunk
        FindChunk(hFile, fourccDATA, dwChunkSize, dwChunkPosition);
        BYTE* pDataBuffer = new BYTE[dwChunkSize];
        ReadChunkData(hFile, pDataBuffer, dwChunkSize, dwChunkPosition);
        buffer.AudioBytes = dwChunkSize;  //size of the audio buffer in bytes
        buffer.pAudioData = pDataBuffer;  //buffer containing audio data
        buffer.Flags = XAUDIO2_END_OF_STREAM; // tell the source voice not to expect any data after this buffer



        HRESULT hr;
        VoiceCallback vcb;

        // initialize COM
        if (FAILED(hr = CoInitializeEx(nullptr, COINITBASE_MULTITHREADED)))
        {
            return hr;
        }

        // audio engine initialized to nullptr
        IXAudio2* audioEngine = nullptr;
        // initialize audio engine
        if ( FAILED(hr = XAudio2Create(&audioEngine, 0)))
        {
            return hr;
        }

        // create mastering voice
        IXAudio2MasteringVoice* masteringVoice = nullptr;
        // initialize masterVoice
        if (FAILED(hr = audioEngine->CreateMasteringVoice(&masteringVoice)))
        {
            return hr;
        }


        // create source voice
        IXAudio2SourceVoice* sourceVoice = nullptr;
        // init source voice
        if (FAILED(hr = audioEngine->CreateSourceVoice(&sourceVoice, (WAVEFORMATEX*)&wfx, 0u, 2.0f, &vcb)))
        {
            return hr;
        }
        // submit audio buffer to source voice
        if (FAILED(hr = sourceVoice->SubmitSourceBuffer(&buffer)))
        {
            return hr;
        }


        bool over = false;
        while (!over)
        {
            if (GetAsyncKeyState('S'))
            {
                over = true;
            }
            if (GetAsyncKeyState('M'))
            {


                if (FAILED(hr = sourceVoice->Start(0)))
                {
                    return hr;
                }
            

            }
            if (GetAsyncKeyState('A'))
            {
                sourceVoice->Stop();
            }
   
        }


        audioEngine->Release();
        audioEngine = nullptr;

        masteringVoice = nullptr;

        CoUninitialize();


    }

如有任何帮助,我们将不胜感激。

单个XAudio2源语音只能播放或不播放。不能播放'twice'。如果您想要重叠播放的相同声音,则需要激活两个 XAudio2 源声音。您可以根据需要将相同的音频提交给尽可能多的不同格式兼容的声音。请记住,您的音频数据保留在应用程序的内存中,每个源声音只是直接从那里读取它(即没有像 DirectSound 中那样复制所有源数据)。

XAudio2 'real-time mixer' 用于处理游戏中的复杂声音。如果你只想播放 WAV 文件,那是旧的 Win32 PlaySound 函数所做的。

如果要再次播放相同的声音,则需要重新提交音频包。如果你重复这样做,它会一直循环。

或者,您可以在播放时设置循环计数,以便一遍又一遍地重复使用相同的提交数据包。

You should take a look at DirectX Tool Kit for Audio for a simple XAudio2-based audio library. It supports 'one-shot' sounds like you are trying to do here, as well as looping sounds, voice-management, positional audio, environmental effects, file-based streaming, etc.

如果您想循环播放音频,请像这样设置缓冲区 您可以将 (void*)&over 作为上下文传递。 然后在语音回调中做类似

的事情
bool *myContext = (bool*)pBufferContext; 

*myContext = true; 

退出循环。请记住,如果 loop_count = XAUDIO2_LOOP_INFINITE,则不会调用语音回调函数 OnBufferEnd(...)

如果您将接口和相关数据分组在一个结构中,或者 class 您可以在函数之间传递它们。保留接口,直到您绝对不再需要它们,并且在应用程序的生命周期内无需多次 CoInitialize 或 CoUnintialize。

class my_xaudio_implement
{
    private: // data and interfaces
     
     bool ready_to_play = false;

     bool using_wfmt_ex = false;

     UINT32 xa_fill_pos = 0;

     BYTE *xa_data = NULL;

     // SAMPLE_STRUCT *xa_samples = NULL; // used to generate 2 channel samples

     UINT32 xa_data_size = 0; // data size in bytes

     UINT32 xa_data_samples; // data size in samples

     HANDLE xa_fhandle = NULL;

     WAVEFORMAT xa_wfmt = { 0 };

     WAVEFORMATEXTENSIBLE xa_wfmt_ex = { 0 };

     XAUDIO2_BUFFER xa_buffer = { 0 };

    IXAudio2 *ifc_audio_engine = NULL;
    IXAudio2MasteringVoice *ifc_master_voice = NULL;
    IXAudio2SourceVoice *ifc_source_voice = NULL;

    public: // functions, constructor/destructor

    bool xa_over = false; // this data object needs to be public

    VoiceCallback my_voice_cb();

    // constructor
    my_xaudio_implement( /* parameters */ )
    {
      // create xaudio engine

      // open the file or create the buffer and generate the wave data

      // create and fill the buffer { xa_data = (BYTE*)new BYTE[xa_data_size]; } 

      // xa_samples = (SAMPLE_STRUCT *)xa_data;
      
      // generate a wav file example snipet
      // for (UINT32 t = 0; t < Samples_To_Add; ++t)
      // { 
      //   if (xa_fill_pos == xa_data_samples) { break; } 
      //   xa_samples[xa_fill_pos].left  = amp_lt * sin(((freq_lt * t) + phase_lt) * radian);
      //   xa_samples[xa_fill_pos].right = amp_rt * sin(((freq_rt * t) + phase_rt) * radian);
       // ++xa_fill_pos;
      // }

      // create the master voice

      // create the source voice

      ready_to_play = true;

      // optional auto play here
    };

    // destructor
    ~my_xaudio_implement()
    {
       // destroy voices in reverse order
       if (ifc_sorce_voice) { ifc_source_voice->DestroyVoice(); ifc_source_voice = NULL; }

       if (ifc_master_voice) { ifc_master_voice->DestroyVoice(); ifc_master_voice = NULL; }

       // release xaudio engine 
       if (ifc_audio_engine) { ifc_audio_engine->Release(); ifc_audio_engine = NULL; }

       // release memory
       if (xa_data) { delete xa_data; xa_data = NULL; } 

       // disable certain functions
       ready_to_play = false;
    };

    bool setup_xa_wfmt( /* my parameters*/ );

    bool setup_xa_wfmt_ex( /* my parameters */ );

    // example parameters to use
    bool setup_xa_buffer(UINT32 sample_size = 0, UINT32 loop_count = XAUDIO2_LOOP_INFINITE, void *context = (void*)&xa_over);

    bool play(UINT32 sample_size = 0, UIN32 loop_count = XAUDIO2_LOOP_INFINITE, void *context = (void*)&xa_over);

    // optional loop, not needed if there is a main loop
    bool play_wait();
};

bool my_xaudio_implement::setup_xa_buffer (UINT32 sample_size, UINT32 loop_count, void *context) 
{

// sanity check
if (ifc_source_voice == NULL) { return false; }

if ((sample_size == 0) || (sample_size >= xa_sample_size)) { sample_size = xa_sample_size; }

xa_buffer.pAudioData = xa_data; // actual buffer
xa_buffer.AudioBytes = xa_data_size; // buffer size

xa_buffer.Flags = XAUDIO2_END_OF_STREAM;
xa_buffer.LoopBegin = 0;
xa_buffer.LoopCount = loop_count; // number of loops
xa_buffer.LoopLength = 0; //entire buffer
xa_buffer.pContext = context; // user defined pointer which is used in the callbacks
xa_buffer.PlayBegin = 0;  // buffer offset to start playing, 0 = begining
xa_buffer.PlayLength = sample_size; // 0 = play the entire buffer 

if (FAILED(ifc_source_voice->SubmitSourceBuffer(&xa_buffer))
{
  // error message

  return false;
}

return true;

}

bool my_xaudio_implement::play(UINT32 sample_size, UINT32 loop_count, void *context)
{
  if (!ready_to_play) { return false; }

  // sanity check since trying to using a NULL interface can crash the app
  if (ifc_source_voice == NULL) { return false; }

  if (!setup_xa_buffer(sample_size, loop_count, context)) 
  { 
    // error message

    return false; 
  }
  
  if (FAILED(ifc_source_voice->Start(0, XAUDIO2_COMMIT_NOW)))
  {
    //error message

    return false;
  }

  return true;
}

所以现在你创建 my_xaudio_implement myobj();

每次执行 myobj.play(0, 0, (void*)&xa_over) 都会重新提交缓冲区

执行myobj.play();将使用默认参数值执行(整个缓冲区、无限循环、context = &xa_over)

析构函数将在对象超出范围时清理接口。

如果你创建 my_xaudio_implement *myobj = NULL; myobj = new my_xaudio_implement(); 然后执行 { if (myobj) { delete myobj; myobj = NULL; } } 执行清理

似乎没有人能够正确回答这个问题。 OP问的是关于如何播放两次相同的声音,他的意思是播放后要求声音播放后再次播放,声音没有做任何事情。

发生这种情况是因为 Stop 在 XAudio2 上实际上意味着“暂停”,对于 Stop 实际上重置声音位置,您必须调用 IXAudioSourceVoice2::FlushSourceBuffers()

这会将声音位置重置为 0,因此,要实际多次播放声音,请遵循以下代码:


void playSource(IXAudioSourceVoice2 voice, XAUDIO2_BUFFER* buffer)
{
    //Playing again without stopping is invalid on XAudio2
    voice->Stop(0); //Or use XAUDIO2_PLAY_TAILS for playing the reverb's tail
    //Remove the buffers and reset the audio position
    voice->FlushSourceBuffers();
    //Submit the buffer after the reset
    voice->SubmitSourceBuffer(buffer, null);
    //Play
    voice->Start(0);
}