How to: Stream a Sound from Disk
Note
This content applies only to desktop apps (and would require revision in order to function in a UWP app). Please refer to the documentation for CreateFile2, CreateEventEx, WaitForSingleObjectEx, SetFilePointerEx, and GetOverlappedResultEx. See the XAudio2 audio stream effect Windows 8 sample app from now-archived Windows SDK Samples Gallery.
You can stream audio data in XAudio2 by creating a separate thread and perform buffer reads of the audio data in the streaming thread, and then use callbacks to control that thread.
Performing buffer reads in the streaming thread
To perform buffer reads in the streaming thread follow these steps:
Create an array of read buffers.
#define STREAMING_BUFFER_SIZE 65536 #define MAX_BUFFER_COUNT 3 BYTE buffers[MAX_BUFFER_COUNT][STREAMING_BUFFER_SIZE];
Initialize an OVERLAPPED structure.
The structure is used to check when an asynchronous disk read has finished.
OVERLAPPED Overlapped = {0}; Overlapped.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
Call the Start function on the source voice that will be playing the streaming audio.
hr = pSourceVoice->Start( 0, 0 );
Loop while the current read position is not passed the end of the audio file.
CurrentDiskReadBuffer = 0; CurrentPosition = 0; while ( CurrentPosition < cbWaveSize ) { ... }
In the loop, do the following:
Read a chunk of data from the disk into the current read buffer.
DWORD dwRead; if( SUCCEEDED(hr) && 0 == ReadFile( hFile, pData, dwDataSize, &dwRead, pOverlapped ) ) hr = HRESULT_FROM_WIN32( GetLastError() ); DWORD cbValid = min( STREAMING_BUFFER_SIZE, cbWaveSize - CurrentPosition ); DWORD dwRead; if( 0 == ReadFile( hFile, buffers[CurrentDiskReadBuffer], STREAMING_BUFFER_SIZE, &dwRead, &Overlapped ) ) hr = HRESULT_FROM_WIN32( GetLastError() ); Overlapped.Offset += cbValid; //update the file position to where it will be once the read finishes CurrentPosition += cbValid;
Use the GetOverlappedResult function to wait for the event that signals the read has finished.
DWORD NumberBytesTransferred; ::GetOverlappedResult(hFile,&Overlapped,&NumberBytesTransferred, TRUE);
Wait for the number of buffers queued on the source voice to be less than the number of read buffers.
The state of the source voice is checked with the GetState function.
XAUDIO2_VOICE_STATE state; while( pSourceVoice->GetState( &state ), state.BuffersQueued >= MAX_BUFFER_COUNT - 1) { WaitForSingleObject( Context.hBufferEndEvent, INFINITE ); }
Submit the current read buffer to the source voice using the SubmitSourceBuffer function.
XAUDIO2_BUFFER buf = {0}; buf.AudioBytes = cbValid; buf.pAudioData = buffers[CurrentDiskReadBuffer]; if( CurrentPosition >= cbWaveSize ) { buf.Flags = XAUDIO2_END_OF_STREAM; } pSourceVoice->SubmitSourceBuffer( &buf );
Set the current read buffer index to the next buffer.
CurrentDiskReadBuffer++; CurrentDiskReadBuffer %= MAX_BUFFER_COUNT;
After the loop has finished, wait for the remaining queued buffers to finish playing.
When the remaining buffers have finished playing, the sound stops, and the thread can exit or be reused to stream another sound.
XAUDIO2_VOICE_STATE state; while( pSourceVoice->GetState( &state ), state.BuffersQueued > 0 ) { WaitForSingleObjectEx( Context.hBufferEndEvent, INFINITE, TRUE ); }
Creating the callback class
To create the callback class, create a class that inherits from the IXAudio2VoiceCallback interface.
The class should set an event in its OnBufferEnd method. This allows the streaming thread to put itself to sleep until the event signals it that XAudio2 has finished reading from an audio buffer. For more information about using callbacks with XAudio2, see How to: Use Source Voice Callbacks.
struct StreamingVoiceContext : public IXAudio2VoiceCallback
{
HANDLE hBufferEndEvent;
StreamingVoiceContext(): hBufferEndEvent( CreateEvent( NULL, FALSE, FALSE, NULL ) ){}
~StreamingVoiceContext(){ CloseHandle( hBufferEndEvent ); }
void OnBufferEnd( void* ){ SetEvent( hBufferEndEvent ); }
...
};
Related topics