1

I am trying to use the XAudio2 audio system to play a looping sound asynchronously. It functions, but when the DLL is unloaded (the code here is inside of a DLL loaded by another program), I get an access violation exception in the parent application.

I have noticed that I receive no error until I add the mastering voice into the mix, but I don't know what I could be doing wrong with it.

The code is below:

In soundengine.h

#include <Windows.h>
#include <xaudio2.h>
#include <fstream>
#define SAFE_DELETE(p) if(p) {delete p; p = NULL;}
#define SAFE_DELETE_ARRAY(p) if(p) {delete[] p; p = NULL;}
#define DXTRACE_ERR(str,hr) DXTrace(__FILE__,(DWORD)__LINE__,hr,str, FALSE)
using namespace std;

#define XA2SE_PLAY_LOOP 1
#define XA2SE_PLAY_STD  0
#define XA2SE_ERROR_NOTPLAYING  -1
#define XA2SE_ERROR_COULD_NOT_OPEN_FILE -2
#define XA2SE_ERROR_NOT_WAVE_FILE   -3
#define XA2SE_ERROR_COULD_NOT_SEEK  -4
#define XA2SE_ERROR_COULD_NOT_DESCEND   -5
#define XA2SE_ERROR_COULD_NOT_RESET -6
#define XA2SE_ERROR_RIFF_ERROR  -7
#define XA2SE_ERROR_SIZE_ERROR  -8
#define XA2SE_ERROR_COULD_NOT_READ  -9
#define XA2SE_ERROR_UNEXPECTED_NULL_VALUE   -10
#define XA2SE_ERROR_COULD_NOT_ASCEND    -11
#define XA2SE_ERROR_COULD_NOT_GET_INFO  -12
#define XA2SE_ERROR_COULD_NOT_ADVANCE   -13
#define XA2SE_ERROR_COULD_NOT_READEND   -14
#define XA2SE_ERROR_COULD_NOT_SET_INFO  -15
#define XA2SE_ERROR_COULD_NOT_CREATE_VOICE  -16
#define XA2SE_COULD_NOT_SUBMIT_BUFFER   -17
#define WAVEFILE_READ   0x001

class XA2SoundEngine {
public:
    IXAudio2MasteringVoice* mv;
    IXAudio2SourceVoice* sv;
    IXAudio2* pXA2; 
    XA2SoundEngine();
    ~XA2SoundEngine();
    BOOL Play(LPWSTR file, int mode);   
};

class WaveFile {
public:
    WAVEFORMATEX* m_pwfx;        // Pointer to WAVEFORMATEX structure
    HMMIO m_hmmio;       // MM I/O handle for the WAVE
    MMCKINFO m_ck;          // Multimedia RIFF chunk
    MMCKINFO m_ckRiff;      // Use in opening a WAVE file
    DWORD m_dwSize;      // The size of the wave file
    MMIOINFO m_mmioinfoOut;    
    BYTE* m_pbData;
    BYTE* m_pbDataCur;
    ULONG m_ulDataSize;
    CHAR* m_pResourceBuffer;

protected:
    HRESULT ReadMMIO();
    HRESULT WriteMMIO( WAVEFORMATEX* pwfxDest );

public:
            WaveFile();
            ~WaveFile();

    HRESULT Open( LPWSTR strFileName);    
    HRESULT Close();

    HRESULT Read( BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead );
    HRESULT Write( UINT nSizeToWrite, BYTE* pbData, UINT* pnSizeWrote );

    DWORD   GetSize();
    HRESULT ResetFile();
    WAVEFORMATEX* GetFormat() {
        return m_pwfx;
    };
};

extern XA2SoundEngine* soundengine;

In soundengine.cpp

#include "soundengine.h"
struct async_play_params {
    LPWSTR file;        
    int mode;
};
DWORD _stdcall async_play_routine(void* param);

XA2SoundEngine::XA2SoundEngine() {      
    HRESULT hr;
    CoInitializeEx(NULL,COINIT_MULTITHREADED);
    pXA2 = NULL;    
    sv = NULL;  
    if(FAILED(hr = XAudio2Create(&pXA2))) {
        MessageBoxW(NULL,L"The TFDi 737 Extreme Sound system failed to initialize the XAudio2 engine.\r\n\r\nError: FAILED(hr = XAudio2Create(&pXA2, flags))",L"TFDi 737XS Sound Error",MB_OK + MB_ICONERROR);
        return;
    }   
    if(FAILED(hr = pXA2->CreateMasteringVoice(&mv))) {
        MessageBoxW(NULL,L"The TFDi 737 Extreme Sound system failed to initialize the XAudio2 mastering voice.\r\n\r\nError: FAILED(hr = pXA2->CreateMasteringVoice(&mv))",L"TFDi 737XS Sound Error",MB_OK + MB_ICONERROR);
        return;
    }   
}

XA2SoundEngine::~XA2SoundEngine() {
    if(sv)
        sv->DestroyVoice();
    if(mv)
        mv->DestroyVoice();
    if(pXA2)
        pXA2->Release();
    CoUninitialize();
}

BOOL XA2SoundEngine::Play(LPWSTR file, int mode) {
        HRESULT hr;
        WaveFile wf;
        if(FAILED(hr = wf.Open(file)))
            return XA2SE_ERROR_COULD_NOT_OPEN_FILE; 
        WAVEFORMATEX* pwfx = wf.GetFormat();
        DWORD wavesize = wf.GetSize();
        BYTE* wavedata = new BYTE[wavesize];    
        if(FAILED(hr = wf.Read(wavedata, wavesize, &wavesize)))
            return XA2SE_ERROR_COULD_NOT_READ;
        if(soundengine->sv != NULL) {
            soundengine->sv->FlushSourceBuffers();
            soundengine->sv->DestroyVoice();            

        }

        if(FAILED(hr = soundengine->pXA2->CreateSourceVoice(&soundengine->sv,pwfx))) {
            SAFE_DELETE(wavedata);
            return XA2SE_ERROR_COULD_NOT_CREATE_VOICE;
        }


        XAUDIO2_BUFFER buffer = {0};
        buffer.pAudioData = wavedata;
        buffer.Flags = XAUDIO2_END_OF_STREAM;
        buffer.AudioBytes = wavesize;
        buffer.LoopBegin = 0;
        double nos = (double)wavesize / ((pwfx->wBitsPerSample * pwfx->nChannels) / pwfx->nSamplesPerSec);
        buffer.LoopLength = (unsigned int)nos;


        buffer.LoopLength = 0;

        buffer.LoopCount = (mode == XA2SE_PLAY_LOOP ? XAUDIO2_LOOP_INFINITE : 0);

        if(FAILED(hr = soundengine->sv->SubmitSourceBuffer(&buffer))) {                     
            SAFE_DELETE_ARRAY(wavedata);
            return XA2SE_COULD_NOT_SUBMIT_BUFFER;
        }

        hr = soundengine->sv->Start(0);     
        //SAFE_DELETE_ARRAY(wavedata);
    return TRUE;
}

WaveFile::WaveFile() {
    m_pwfx = NULL;
    m_hmmio = NULL;
    m_pResourceBuffer = NULL;
    m_dwSize = 0;    
}
WaveFile::~WaveFile() {
    Close();
    SAFE_DELETE_ARRAY(m_pwfx);
}

HRESULT WaveFile::Open( LPWSTR strFileName)
{
    HRESULT hr;

    if( strFileName == NULL )
        return E_INVALIDARG;
    SAFE_DELETE_ARRAY(m_pwfx);

    m_hmmio = mmioOpenW( strFileName, NULL, MMIO_ALLOCBUF | MMIO_READ );

    if( NULL == m_hmmio ) {
        return XA2SE_ERROR_COULD_NOT_OPEN_FILE;
    }

    if(FAILED(hr = ReadMMIO())) {
        // ReadMMIO will fail if its an not a wave file
        mmioClose( m_hmmio, 0 );
        return XA2SE_ERROR_NOT_WAVE_FILE;
    }

    if( FAILED( hr = ResetFile() ) )
        return XA2SE_ERROR_COULD_NOT_RESET;

    // After the reset, the size of the wav file is m_ck.cksize so store it now
    m_dwSize = m_ck.cksize;

    return hr;
}
HRESULT WaveFile::ReadMMIO()
{
    MMCKINFO ckIn;           // chunk info. for general use.
    PCMWAVEFORMAT pcmWaveFormat;  // Temp PCM structure to load in.

    memset( &ckIn, 0, sizeof(ckIn) );

    m_pwfx = NULL;

    if((0 != mmioDescend(m_hmmio,&m_ckRiff,NULL,0)))
        return XA2SE_ERROR_COULD_NOT_DESCEND;

    // Check to make sure this is a valid wave file
    if((m_ckRiff.ckid != FOURCC_RIFF) || (m_ckRiff.fccType != mmioFOURCC('W','A','V','E')))
        return XA2SE_ERROR_RIFF_ERROR;

    // Search the input file for for the 'fmt ' chunk.
    ckIn.ckid = mmioFOURCC( 'f', 'm', 't', ' ' );
    if( 0 != mmioDescend( m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK ) )
        return XA2SE_ERROR_COULD_NOT_DESCEND;

    // Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
    // if there are extra parameters at the end, we'll ignore them
    if( ckIn.cksize < ( LONG )sizeof( PCMWAVEFORMAT ) )
        return XA2SE_ERROR_SIZE_ERROR;

    // Read the 'fmt ' chunk into <pcmWaveFormat>.
    if( mmioRead( m_hmmio, ( HPSTR )&pcmWaveFormat,
        sizeof( pcmWaveFormat ) ) != sizeof( pcmWaveFormat ) )
        return XA2SE_ERROR_COULD_NOT_READ;

    // Allocate the waveformatex, but if its not pcm format, read the next
    // word, and thats how many extra bytes to allocate.
    if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM )
    {
        m_pwfx = ( WAVEFORMATEX* )new CHAR[ sizeof( WAVEFORMATEX ) ];
        if( NULL == m_pwfx )
            return XA2SE_ERROR_UNEXPECTED_NULL_VALUE;

        // Copy the bytes from the pcm structure to the waveformatex structure
        memcpy( m_pwfx, &pcmWaveFormat, sizeof( pcmWaveFormat ) );
        m_pwfx->cbSize = 0;
    }
    else
    {
        // Read in length of extra bytes.
        WORD cbExtraBytes = 0L;
        if( mmioRead( m_hmmio, ( CHAR* )&cbExtraBytes, sizeof( WORD ) ) != sizeof( WORD ) )
            return XA2SE_ERROR_COULD_NOT_READ;

        m_pwfx = ( WAVEFORMATEX* )new CHAR[ sizeof( WAVEFORMATEX ) + cbExtraBytes ];
        if( NULL == m_pwfx )
            return XA2SE_ERROR_UNEXPECTED_NULL_VALUE;

        // Copy the bytes from the pcm structure to the waveformatex structure
        memcpy( m_pwfx, &pcmWaveFormat, sizeof( pcmWaveFormat ) );
        m_pwfx->cbSize = cbExtraBytes;

        // Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
        if( mmioRead( m_hmmio, ( CHAR* )( ( ( BYTE* )&( m_pwfx->cbSize ) ) + sizeof( WORD ) ),
            cbExtraBytes ) != cbExtraBytes )
        {
            SAFE_DELETE( m_pwfx );
            return XA2SE_ERROR_COULD_NOT_READ;
        }
    }

    // Ascend the input file out of the 'fmt ' chunk.
    if( 0 != mmioAscend( m_hmmio, &ckIn, 0 ) )
    {
        SAFE_DELETE( m_pwfx );
        return XA2SE_ERROR_COULD_NOT_ASCEND;
    }

    return S_OK;
}

DWORD WaveFile::GetSize() {
    return m_dwSize;
}
HRESULT WaveFile::ResetFile() {
    if( m_hmmio == NULL )
        return CO_E_NOTINITIALIZED;
    // Seek to the data
    if( -1 == mmioSeek( m_hmmio, m_ckRiff.dwDataOffset + sizeof( FOURCC ),
        SEEK_SET ) )
        return XA2SE_ERROR_COULD_NOT_SEEK;

    // Search the input file for the 'data' chunk.
    m_ck.ckid = mmioFOURCC( 'd', 'a', 't', 'a' );
    if( 0 != mmioDescend( m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK ) )
        return XA2SE_ERROR_COULD_NOT_DESCEND;

        return S_OK;
}

HRESULT WaveFile::Read( BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead ) {

    MMIOINFO mmioinfoIn; // current status of m_hmmio

    if( m_hmmio == NULL )
        return CO_E_NOTINITIALIZED;
    if( pBuffer == NULL || pdwSizeRead == NULL )
        return E_INVALIDARG;

    *pdwSizeRead = 0;

    if( 0 != mmioGetInfo( m_hmmio, &mmioinfoIn, 0 ) )
        return XA2SE_ERROR_COULD_NOT_GET_INFO;

    UINT cbDataIn = dwSizeToRead;
    if( cbDataIn > m_ck.cksize )
        cbDataIn = m_ck.cksize;

    m_ck.cksize -= cbDataIn;

    for( DWORD cT = 0; cT < cbDataIn; cT++ )
    {
        // Copy the bytes from the io to the buffer.
        if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
        {
            if( 0 != mmioAdvance( m_hmmio, &mmioinfoIn, MMIO_READ ) )
                return XA2SE_ERROR_COULD_NOT_ADVANCE;

            if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
                return XA2SE_ERROR_COULD_NOT_READEND;
        }

        // Actual copy.
        *( ( BYTE* )pBuffer + cT ) = *( ( BYTE* )mmioinfoIn.pchNext );
        mmioinfoIn.pchNext++;
    }

    if( 0 != mmioSetInfo( m_hmmio, &mmioinfoIn, 0 ) )
        return XA2SE_ERROR_COULD_NOT_SET_INFO;

    *pdwSizeRead = cbDataIn;

    return S_OK;

}

HRESULT WaveFile::Close() {

    if ( m_hmmio != NULL ) {
        mmioClose( m_hmmio, 0 );
        m_hmmio = NULL;
    }
    SAFE_DELETE_ARRAY( m_pResourceBuffer );

    return S_OK;
}
4

3 回答 3

1

I happen to be using XAudio for certain things, and I checked my code. Strangely enough, I had a comment saying not to use CoUninitialize(); in the AudioDevice destructor itself, and I remembered why:

You're initializing COM with CoInitializeEx( NULL, COINIT_MULTITHREADED );. This means that there's other threads (specifically, other XAudio threads) running besides your application partially out of your control. When you co CoUninitialize() in the destructor of your XA2SoundEngine, that thread might not have exited or finished cleaning up its resources. Essentially, you're asking COM to do some killing that it's probably not ready to do (this is my best guess, because I kept getting Access Violations myself).

The way I fixed it was just not having CoUninitialize(). Microsoft, in newer versions of XAudio2, realized how dumb it was to not tell you when the threads it was using were being spawned or dying, and so XAudio2 (2.8, I believe, on Windows 8) did CoInitializeEx and CoUninitialize for you. If you're in that env, you shouldn't be calling these methods in the first place.

If this happens to be in a DLL, you can safely call your CoUninitialize and CoInitializeEx when your DLL attaches itself. It will run without error:

BOOL APIENTRY DllMain ( HANDLE hModule, DWORD dwReason, LPVOID lpReserved ) {

    switch (dwReason) {

    case DLL_PROCESS_ATTACH:
        CoInitializeEx(NULL, COINIT_MULTITHREADED);
        break;
    case DLL_THREAD_ATTACH:
        CoInitializeEx(NULL, COINIT_MULTITHREADED);
        break;
    case DLL_THREAD_DETACH:
        CoUninitialize();
        break;
    case DLL_PROCESS_DETACH:
        CoUninitialize();
        break;
    default:
        // Wat...
        break;
    }

    return TRUE;

}

Maybe a COM expert can tell me if I'm doing it wrong, but so far the above has been working for me.

Good luck!

于 2013-04-17T02:33:23.903 回答
1

The problem ended up being a bunch of things, including the fact that I was using the Windows 8 version of XAudio2 in DirectX9 based environment, my misuse of the CoInitialize() functions, and the fact that I never explicitly called 'delete soundengine'. I have modified the class a bit since this, and it works beautifully now.

Thanks guys!

于 2013-04-17T21:18:57.837 回答
0

Had the same problem (or at least, a strikingly similar one), my solution was to properly free my two instances of IXAudio2* as the very last in the shutdown process, like this:

SafeRelease(&m_musicEngine);
SafeRelease(&m_soundEffectEngine);

where SafeRelease is this utility:

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

with just a delete m_musicEngine; in the wrong order, my game kept throwing the access violation exception in the XAudio_N.dll - this is on Windows 10 platform SDK version 10.0.17134.0

Now it exits cleanly. It is notable that MS themselves include an exception handler for DirectX audio in their official SDK samples, which looks like this:

void  _stdcall AudioEngineCallbacks::OnCriticalError(HRESULT Error)

and which is given the following explanation:

// This [...] used to tell when the audio system
// is experiencing critial errors.
// XAudio2 gives a critical error when the user unplugs
// the headphones and a new speaker configuration is generated.

So this kind of error is apparently to be expected even when the audio engine lifetime is otherwise properly maintained.

于 2019-12-07T13:00:25.920 回答