| #define _WIN32_DCOM |
| #define _CRT_SECURE_NO_DEPRECATE |
| #include <windows.h> |
| #include <xaudio2.h> |
| #include <strsafe.h> |
| #include <shellapi.h> |
| #include <mmsystem.h> |
| #include <conio.h> |
| #include <Xact3wb.h> |
| #include <assert.h> |
|
|
| #define USE_XWMA_FILE ( 1 ) // If we use the XWMA file, this example code doesn't function correctly |
| // It works just fine with the wave equivalent |
| |
| //-------------------------------------------------------------------------------------- |
| // Callback structure |
| //-------------------------------------------------------------------------------------- |
| struct StreamingVoiceContext : public IXAudio2VoiceCallback |
| { |
| STDMETHOD_( void, OnVoiceProcessingPassStart )( UINT32 ) |
| { |
| } |
| STDMETHOD_( void, OnVoiceProcessingPassEnd )() |
| { |
| } |
| STDMETHOD_( void, OnStreamEnd )() |
| { |
| } |
| STDMETHOD_( void, OnBufferStart )( void* ) |
| { |
| } |
| STDMETHOD_( void, OnBufferEnd )( void* ) |
| { |
| wprintf( L"Buffer End\n" ); |
| SetEvent( hBufferEndEvent ); |
| } |
| STDMETHOD_( void, OnLoopEnd )( void* ) |
| { |
| } |
| STDMETHOD_( void, OnVoiceError )( void*, HRESULT ) |
| { |
| } |
| |
| HANDLE hBufferEndEvent; |
| |
| StreamingVoiceContext() : hBufferEndEvent( CreateEvent( NULL, FALSE, FALSE, NULL ) ) |
| { |
| } |
| |
| virtual ~StreamingVoiceContext() |
| { |
| CloseHandle( hBufferEndEvent ); |
| } |
| }; |
| |
| |
| struct xWMAExtraInfo |
| { |
| UINT32 packetCount; |
| UINT32 *dpds; |
| }; |
| |
| struct DataBuffer |
| { |
| UINT32 size; |
| BYTE *data; |
| }; |
| |
| WAVEFORMATEX *ReadWave( DataBuffer *buffer, HMMIO hmmio ) |
| { |
| MMCKINFO riff; |
| // Search the input file for for the 'fmt ' chunk. |
| riff.ckid = mmioFOURCC( 'f', 'm', 't', ' ' ); |
| if( 0 != mmioDescend( hmmio, &riff, NULL, MMIO_FINDCHUNK ) ) |
| { |
| return NULL; |
| } |
| |
| // 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( riff.cksize < ( LONG )sizeof( PCMWAVEFORMAT ) ) |
| { |
| return NULL; |
| } |
| |
| // Read the 'fmt ' chunk into <pcmWaveFormat>. |
| PCMWAVEFORMAT pcmWaveFormat; // Temp PCM structure to load in. |
| |
| if( mmioRead( hmmio, ( HPSTR )&pcmWaveFormat, sizeof( pcmWaveFormat ) ) != sizeof( pcmWaveFormat ) ) |
| { |
| return NULL; |
| } |
| |
| // Allocate the waveformatex, but if its not pcm format, read the next |
| // word, and thats how many extra bytes to allocate. |
| assert( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM ); |
| WAVEFORMATEX *wfx = (WAVEFORMATEX *)malloc( sizeof( WAVEFORMATEX ) ); |
| |
| // Copy the bytes from the pcm structure to the waveformatex structure |
| memcpy( wfx, &pcmWaveFormat, sizeof( pcmWaveFormat ) ); |
| wfx->cbSize = 0; |
| |
| // Ascend the input file out of the 'fmt ' chunk. |
| if( 0 != mmioAscend( hmmio, &riff, 0 ) ) |
| { |
| free( wfx ); |
| return NULL; |
| } |
| |
| riff.ckid = mmioFOURCC( 'd', 'a', 't', 'a' ); |
| if( 0 != mmioDescend( hmmio, &riff, NULL, MMIO_FINDCHUNK ) ) |
| { |
| free( wfx ); |
| return NULL; |
| } |
| buffer->size = riff.cksize; |
| buffer->data = (BYTE*)malloc( riff.cksize ); |
| if( mmioRead( hmmio, ( HPSTR )buffer->data, riff.cksize ) != riff.cksize ) |
| { |
| free( wfx ); |
| free( buffer->data ); |
| return NULL; |
| } |
| return wfx; |
| } |
| |
| WAVEFORMATEX *ReadXWMA( DataBuffer *buffer, xWMAExtraInfo *extraInfo, HMMIO hmmio ) |
| { |
| MMCKINFO riff; |
| // Search the input file for for the 'fmt ' chunk. |
| riff.ckid = mmioFOURCC( 'f', 'm', 't', ' ' ); |
| if( 0 != mmioDescend( hmmio, &riff, NULL, MMIO_FINDCHUNK ) ) |
| { |
| return NULL; |
| } |
| |
| // 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( riff.cksize < ( LONG )sizeof( PCMWAVEFORMAT ) ) |
| { |
| return NULL; |
| } |
| |
| // Read the 'fmt ' chunk into <pcmWaveFormat>. |
| PCMWAVEFORMAT pcmWaveFormat; // Temp PCM structure to load in. |
| |
| if( mmioRead( hmmio, ( HPSTR )&pcmWaveFormat, sizeof( pcmWaveFormat ) ) != sizeof( pcmWaveFormat ) ) |
| { |
| return NULL; |
| } |
| // Read in length of extra bytes. |
| WORD cbExtraBytes = 0L; |
| if( mmioRead( hmmio, ( CHAR* )&cbExtraBytes, sizeof( WORD ) ) != sizeof( WORD ) ) |
| { |
| return NULL; |
| } |
| |
| const int formatSize = sizeof( WAVEFORMATEX ) + cbExtraBytes; |
| WAVEFORMATEX *wfx = ( WAVEFORMATEX* )malloc( formatSize ); |
| if( NULL == wfx ) |
| { |
| return NULL; |
| } |
| |
| // Copy the bytes from the pcm structure to the waveformatex structure |
| memcpy( wfx, &pcmWaveFormat, sizeof( pcmWaveFormat ) ); |
| wfx->cbSize = cbExtraBytes; |
| |
| // Now, read those extra bytes into the structure, if cbExtraAlloc != 0. |
| if( mmioRead( hmmio, ( CHAR* )( ( ( BYTE* )&( wfx->cbSize ) ) + sizeof( WORD ) ), |
| cbExtraBytes ) != cbExtraBytes ) |
| { |
| free( wfx ); |
| return NULL; |
| } |
| |
| // Ascend the input file out of the 'fmt ' chunk. |
| if( 0 != mmioAscend( hmmio, &riff, 0 ) ) |
| { |
| free( wfx ); |
| return NULL; |
| } |
| |
| // Search the input file for for the 'dpds' chunk. |
| riff.ckid = mmioFOURCC( 'd', 'p', 'd', 's' ); |
| if( 0 != mmioDescend( hmmio, &riff, NULL, MMIO_FINDCHUNK ) ) |
| { |
| free( wfx ); |
| return NULL; |
| } |
| |
| extraInfo->packetCount = riff.cksize / sizeof( UINT32 ); |
| extraInfo->dpds = (UINT32*)malloc( riff.cksize ); |
| if( mmioRead( hmmio, ( HPSTR )extraInfo->dpds, riff.cksize ) != riff.cksize ) |
| { |
| free( wfx ); |
| free( extraInfo->dpds ); |
| return NULL; |
| } |
| // Ascend the input file out of the 'fmt ' chunk. |
| if( 0 != mmioAscend( hmmio, &riff, 0 ) ) |
| { |
| free( wfx ); |
| free( extraInfo->dpds ); |
| return NULL; |
| } |
| |
| riff.ckid = mmioFOURCC( 'd', 'a', 't', 'a' ); |
| if( 0 != mmioDescend( hmmio, &riff, NULL, MMIO_FINDCHUNK ) ) |
| { |
| free( wfx ); |
| free( extraInfo->dpds ); |
| return NULL; |
| } |
| |
| buffer->size = riff.cksize; |
| buffer->data = (BYTE*)malloc( riff.cksize ); |
| if( mmioRead( hmmio, ( HPSTR )buffer->data, riff.cksize ) != riff.cksize ) |
| { |
| free( wfx ); |
| free( extraInfo->dpds ); |
| free( buffer->data ); |
| return NULL; |
| } |
| return wfx; |
| } |
| |
| WAVEFORMATEX *ReadAudioFile( LPTSTR name, DataBuffer *buffer, xWMAExtraInfo *extraInfo ) |
| { |
| HMMIO hmmio = mmioOpen( name, NULL, MMIO_ALLOCBUF | MMIO_READ ); |
| |
| MMCKINFO riff; |
| |
| if( ( 0 != mmioDescend( hmmio, &riff, NULL, 0 ) ) ) |
| { |
| mmioClose( hmmio, 0 ); |
| return 0; |
| } |
| |
| // Check to make sure this is a valid wave file |
| if( riff.ckid != FOURCC_RIFF ) |
| { |
| mmioClose( hmmio, 0 ); |
| return 0; |
| } |
| |
| WAVEFORMATEX *wfx = NULL; |
| |
| switch( riff.fccType ) |
| { |
| case mmioFOURCC( 'W', 'A', 'V', 'E' ): |
| { |
| wfx = ReadWave( buffer, hmmio ); |
| break; |
| } |
| |
| case mmioFOURCC( 'X', 'W', 'M', 'A' ): |
| { |
| wfx = ReadXWMA( buffer, extraInfo, hmmio ); |
| break; |
| } |
| } |
| mmioClose( hmmio, 0 ); |
| return wfx; |
| } |
| |
| |
| //-------------------------------------------------------------------------------------- |
| // Entry point to the program |
| //-------------------------------------------------------------------------------------- |
| int main() |
| { |
| HRESULT hr; |
| |
| // Initialize XAudio2 |
| CoInitializeEx( NULL, COINIT_MULTITHREADED ); |
| |
| IXAudio2* pXAudio2 = NULL; |
| |
| UINT32 flags = 0; |
| #ifdef _DEBUG |
| flags |= XAUDIO2_DEBUG_ENGINE; |
| #endif |
| |
| if( FAILED( hr = XAudio2Create( &pXAudio2, flags ) ) ) |
| { |
| wprintf( L"Failed to init XAudio2 engine: %#X\n", hr ); |
| CoUninitialize(); |
| return 0; |
| } |
| |
| IXAudio2MasteringVoice* pMasteringVoice = NULL; |
| |
| if( FAILED( hr = pXAudio2->CreateMasteringVoice( &pMasteringVoice ) ) ) |
| { |
| wprintf( L"Failed creating mastering voice: %#X\n", hr ); |
| pXAudio2->Release(); |
| CoUninitialize(); |
| return 0; |
| } |
|
| #if USE_XWMA_FILE |
| // counting.xwm is generated by the command: "xwmaencode counting.wav" |
| // I am using the March 2009 version of the DirectX 9.0c SDK |
| const LPTSTR name = L"counting.xwm"; |
| #else |
| const LPTSTR name = L"counting.wav"; |
| #endif |
| xWMAExtraInfo extraInfo; |
| DataBuffer dataBuffer; |
| // Read in our audio file |
| WAVEFORMATEX *wfx = ReadAudioFile( name, &dataBuffer, &extraInfo ); |
| |
| if( wfx == NULL ) |
| { |
| wprintf( L"Invalid format\n" ); |
| return 1; |
| } |
| |
| bool xwma = wfx->wFormatTag == WAVE_FORMAT_WMAUDIO2 || wfx->wFormatTag == WAVE_FORMAT_WMAUDIO3; |
| StreamingVoiceContext voiceContext; |
| IXAudio2SourceVoice* pSourceVoice; |
| if( FAILED( hr = pXAudio2->CreateSourceVoice( &pSourceVoice, wfx, 0, 1.0f, &voiceContext ) ) ) |
| { |
| wprintf( L"\nError %#X creating source voice\n", hr ); |
| return hr; |
| } |
| free( wfx ); |
| |
| // set up the buffer |
| XAUDIO2_BUFFER buf = {0}; |
| buf.AudioBytes = dataBuffer.size; |
| buf.pAudioData = dataBuffer.data; |
| buf.Flags = XAUDIO2_END_OF_STREAM; |
| XAUDIO2_BUFFER_WMA bufferWma; |
| XAUDIO2_BUFFER_WMA *wma = NULL; |
| |
| if( xwma ) |
| { |
| wma = &bufferWma; |
| bufferWma.PacketCount = extraInfo.packetCount; |
| bufferWma.pDecodedPacketCumulativeBytes = extraInfo.dpds; |
| } |
| pSourceVoice->SubmitSourceBuffer( &buf, wma ); |
| |
| // We've submitted one buffer and started the audio stream. Everything is working fine so far. |
| pSourceVoice->Start( 0, 0 ); |
| |
| // Wait a second. |
| Sleep( 1000 ); |
| |
| // stop the source voice |
| pSourceVoice->Stop( 0 ); |
| wprintf( L"About to flush buffers\n" ); |
| pSourceVoice->FlushSourceBuffers(); |
| |
| // wait for the buffer to get flushed |
| WaitForSingleObject( voiceContext.hBufferEndEvent, INFINITE ); |
| |
| // Start over from the beginning by submitting the exact same buffer with the |
| // exact same parameters. |
| pSourceVoice->SubmitSourceBuffer( &buf, wma ); |
| wprintf( L"About to start playing again\n" ); |
| pSourceVoice->Start( 0 ); |
| |
| // Wait for the voice to reach the end of the resubmitted buffer. With wave files, it starts over |
| // properly and the sound file is played out in its entirety. However, with xWMA files, |
| // it immediately reaches the end of the buffer. In my tests, it always dumps the first |
| // buffer when starting over on an xWMA file, but if there are multiple buffers queued, |
| // it will happily continue on the second buffer. However, it will always skip the first |
| // buffer completely. |
| WaitForSingleObject( voiceContext.hBufferEndEvent, INFINITE ); |
| wprintf( L"Finished playing the buffer in its entirety (assuming you were using a wave file)\n" ); |
| |
| // Clean up |
| pSourceVoice->DestroyVoice(); |
| pMasteringVoice->DestroyVoice(); |
| |
| pXAudio2->Release(); |
| CoUninitialize(); |
| |
| free( dataBuffer.data ); |
| if( xwma ) |
| { |
| free( extraInfo.dpds ); |
| } |
| |
| return 0; |
| } |
| |