How does one play multiple sounds simultaneously on XAudio2? - c++

I am currently trying to make a game app in Windows with XAudio2 and I cannot figure out how to make the application not block when playing a sound. I tried calling a new thread in the samples in this repository.
But it will just cause an error. I tried passing a reference to the mastering voice in the function but then it just raises a "XAudio2: Must create a mastering voice first" error. Am I missing something? I am just trying to make it play two sounds at once and build from there. I went over the documentation but it's very vague.

XAudio2 is a non-blocking API. To play two sounds simultaneously, you need two 'source voices' and one 'mastering voice' at a minimum.
DX::ThrowIfFailed(
CoInitializeEx( nullptr, COINIT_MULTITHREADED )
);
Microsoft::WRL::ComPtr<IXAudio2> pXAudio2;
// Note that only IXAudio2 (and APOs) are COM reference counted
DX::ThrowIfFailed(
XAudio2Create( pXAudio2.GetAddressOf(), 0 )
);
IXAudio2MasteringVoice* pMasteringVoice = nullptr;
DX::ThrowIfFailed(
pXAudio2->CreateMasteringVoice( &pMasteringVoice )
);
IXAudio2SourceVoice* pSourceVoice1 = nullptr;
DX::ThrowIfFailed(
pXaudio2->CreateSourceVoice( &pSourceVoice1, &wfx ) )
// The default 'pSendList' will be just to the pMasteringVoice
);
IXAudio2SourceVoice* pSourceVoice2 = nullptr;
DX::ThrowIfFailed(
pXaudio2->CreateSourceVoice( &pSourceVoice2, &wfx) )
// Doesn't have to be same format as other source voice
// And doesn't have to match the mastering voice either
);
DX::ThrowIfFailed(
pSourceVoice1->SubmitSourceBuffer( &buffer )
);
DX::ThrowIfFailed(
pSourceVoice2->SubmitSourceBuffer( &buffer /* could be different WAV data or not */)
);
DX::ThrowIfFailed(
pSourceVoice1->Start( 0 );
);
DX::ThrowIfFailed(
pSourceVoice2->Start( 0 );
);
You should take a look at the samples on GitHub as well as DirectX Tool Kit for Audio
If you wanted to ensure both source voices started at precisely the same time, you'd use:
DX::ThrowIfFailed(
pSourceVoice1->Start( 0, 1 );
);
DX::ThrowIfFailed(
pSourceVoice2->Start( 0, 1 );
);
DX::ThrowIfFailed(
pSourceVoice2->CommitChanges( 1 );
);

If you want to play multiple sounds simultaneously have a playSound function and launch various threads to play your various sounds each one of a certain source voice.
XAudio2 will take care of mapping each sound to available channels (or if you have a more advanced system you can specify the mapping yourself using IXAudio2Voice::SetOutputMatrix).
void playSound( IXAudio2SourceVoice* sourceVoice )
{
BOOL isPlayingSound = TRUE;
XAUDIO2_VOICE_STATE soundState = {0};
HRESULT hres = sourceVoice->Start( 0u );
while ( SUCCEEDED( hres ) && isPlayingSound )
{// loop till sound completion
sourceVoice->GetState( &soundState );
isPlayingSound = ( soundState.BuffersQueued > 0 ) != 0;
Sleep( 100 );
}
}
For example to play two sounds simultaneously:
IXAudio2SourceVoice* pSourceVoice1 = nullptr;
IXAudio2SourceVoice* pSourceVoice2 = nullptr;
// setup the source voices, load the sounds etc..
std::thread thr1{ playSound, pSourceVoice1 };
std::thread thr2{ playSound, pSourceVoice2 };
thr1.join();
thr2.join();

Related

DirectX: Get InfoQueue before Device

Is there a way to get the InfoQueue or set the break parameters before the Device is created?
Right now i am creating the Device and then getting the InfoQueue, but any messages that are emitted before that point are going to be ignored and buried in the output window.
ID3D11Device* pDevice;
//...Create Device...
ID3D11InfoQueue* pInfoQueue;
pDevice->QueryInterface(__uuidof(ID3D11InfoQueue), &pInfoQueue);
pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
And I want something like:
ID3D11InfoQueue* pInfoQueue;
//...Get InfoQueue...
pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
ID3D11Device* pDevice;
//...Create Device...
Given the documentation for ID3D11InfoQueue saying that you get the interface pointer via a QueryInterface call on the device I would say the answer is 'no'.
What you want to do is enable DXGI debugging which will provide debug information for the device & swapchain creation.
#include <dxgidebug.h>
#if defined(_DEBUG)
Microsoft::WRL::ComPtr<IDXGIInfoQueue> dxgiInfoQueue;
typedef HRESULT (WINAPI * LPDXGIGETDEBUGINTERFACE)(REFIID, void ** );
HMODULE dxgidebug = LoadLibraryEx( L"dxgidebug.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32 );
if ( dxgidebug )
{
auto dxgiGetDebugInterface = reinterpret_cast<LPDXGIGETDEBUGINTERFACE>(
reinterpret_cast<void*>( GetProcAddress( dxgidebug, "DXGIGetDebugInterface" ) ) );
if ( SUCCEEDED( dxgiGetDebugInterface( IID_PPV_ARGS( dxgiInfoQueue.GetAddressOf() ) ) ) )
{
dxgiInfoQueue->SetBreakOnSeverity( DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true );
dxgiInfoQueue->SetBreakOnSeverity( DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true );
}
}
#endif
See this blog post and this one

Implement Microsoft Speech Platform languages in SAPI 5

I created a little program in C++ where I use the SAPI library. In my code, I list the number of voices installed on my system. When I compile, I get 11, but there are only 8 installed and the only voice speaking is Microsoft Anna. I checked it in the registry (HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices).
I have several languages installed , especially languages from the Microsoft Speech Platform but none can be used.
Furthermore, when I change the voice ID, I get an unhandled exception error and I think it is because the chosen ID does not exist.
Here is my code
#include "stdafx.h"
int main( int argc, char* argv[] )
{
CComPtr<ISpObjectToken> cpVoiceToken;
CComPtr<IEnumSpObjectTokens> cpEnum;
ISpVoice * pVoice = NULL;
ULONG count = 0;
string str;
if( FAILED( ::CoInitialize( NULL ) ) )
return FALSE;
HRESULT hr = CoCreateInstance( CLSID_SpVoice, NULL, CLSCTX_ALL,
IID_ISpVoice, ( void ** )&pVoice );
if( SUCCEEDED( hr ) )
{
//Enumerate Voices
hr = SpEnumTokens( SPCAT_VOICES, NULL /*L"Gender=Female"*/, NULL, &cpEnum);
printf( "Success\n" );
}
else
{
printf( "Failed to initialize SAPI" );
}
if( SUCCEEDED( hr ) )
{
//Get number of voices
hr = cpEnum->GetCount( &count );
printf( "TTS voices found: %i\n", count );
}
else
{
printf( "Failed to enumerate voices" );
hr = S_OK;
}
if( SUCCEEDED( hr ) )
{
cpVoiceToken.Release();
cpEnum->Item( 3, &cpVoiceToken ); //3 represents the ID of the voice
pVoice->SetVoice( cpVoiceToken );
hr = pVoice->Speak( L"You have selected Microsoft Server Speech Text to Speech Voice (en-GB, Hazel) as the computer's default voice.", 0, NULL ); //speak sentence
pVoice->Release();
pVoice = NULL;
}
::CoUninitialize();
system( "PAUSE" );
}
The only voice working is Microsoft Anna, and I don't understand why. If the other languages were not available, the program won't show me that there are so many(11). I wonder if the Microsoft Speech Platform languages are compatible with SAPI.
After many tries and fails, I managed to find an answer to my problem.
I compiled my program in Win32. So I decided to change it to x64 and I recompiled the solution. I changed the voice ID in my program, and the voices from the Microsoft Speech Platform worked. This means that the MS Speech Platform languages are 64 bit voices and Microsoft Anna is a 32 bit voice.
The following post inspired me.

Using xaudio2 and a parallel port together

I am using C++ to code a neuroscience experiment in my research lab. We are studying tactile perception, and we use a parallel port to trigger our brain stimulating device. The timing is very important.
We recently started using xaudio2 to play very simple WAV files, which are used to trigger our vibrotactile stimulators (for example, our "tactile" stimuli are 100 and 200 Hz sounds with a duration of 100 ms, that move a piezo-electric stimulator that is placed on the hand).
Our problem is that we need to send out 3 commands to the brain stimulator via the parallel port: once 40 ms before the tactile stimulus, once 10 ms after the start of the stimulus, and a third time 60 ms into the stimulus. Remember, the tactile stimulus lasts 100 ms.
However, the way xaudio2 triggers sound is that it plays the wave and blocks until it is finished. As a consequence, the program ignores the two parallel port commands which should be sent during the stimulus.
Does anybody know how I can make sure the tactile stimulus is still triggered for the entirety of its 100 ms duration, but also send out parallel port commands during it? I am using the MSDN XAudio2Samples as the basic structure for playing the wav files, and the PlayWave function is the one which "blocks" any other input while the Wav file is playing -- but I can't figure out how to modify it so that it will also take my parallel port commands (which are Out32(888,1)) while a sound is being played.
Thank you!
Here is the code for the PlayWave function:
//--------------------------------------------------------------------------------------
// Name: PlayWave
// Desc: Plays a wave and blocks until the wave finishes playing
//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT PlayWave( IXAudio2* pXaudio2, LPCWSTR szFilename )
{
//
// Locate the wave file
//
WCHAR strFilePath[MAX_PATH];
HRESULT hr = FindMediaFileCch( strFilePath, MAX_PATH, szFilename );
if( FAILED( hr ) )
{
wprintf( L"Failed to find media file: %s\n", szFilename );
return hr;
}
//
// Read in the wave file
//
std::unique_ptr<uint8_t[]> waveFile;
DirectX::WAVData waveData;
if ( FAILED( hr = DirectX::LoadWAVAudioFromFileEx( strFilePath, waveFile, waveData ) ) )
{
wprintf( L"Failed reading WAV file: %#X (%s)\n", hr, strFilePath );
return hr;
}
//
// Play the wave using a XAudio2SourceVoice
//
// Create the source voice
IXAudio2SourceVoice* pSourceVoice;
if( FAILED( hr = pXaudio2->CreateSourceVoice( &pSourceVoice, waveData.wfx ) ) )
{
wprintf( L"Error %#X creating source voice\n", hr );
return hr;
}
// Submit the wave sample data using an XAUDIO2_BUFFER structure
XAUDIO2_BUFFER buffer = {0};
buffer.pAudioData = waveData.startAudio;
buffer.Flags = XAUDIO2_END_OF_STREAM; // tell the source voice not to expect any data after this buffer
buffer.AudioBytes = waveData.audioBytes;
if ( waveData.loopLength > 0 )
{
buffer.LoopBegin = waveData.loopStart;
buffer.LoopLength = waveData.loopLength;
buffer.LoopCount = 1; // We'll just assume we play the loop twice
}
#if (_WIN32_WINNT < 0x0602 /*_WIN32_WINNT_WIN8*/)
if ( waveData.seek )
{
XAUDIO2_BUFFER_WMA xwmaBuffer = {0};
xwmaBuffer.pDecodedPacketCumulativeBytes = waveData.seek;
xwmaBuffer.PacketCount = waveData.seekCount;
if( FAILED( hr = pSourceVoice->SubmitSourceBuffer( &buffer, &xwmaBuffer ) ) )
{
wprintf( L"Error %#X submitting source buffer (xWMA)\n", hr );
pSourceVoice->DestroyVoice();
return hr;
}
}
#else
if ( waveData.seek )
{
wprintf( L"This platform does not support xWMA or XMA2\n" );
pSourceVoice->DestroyVoice();
return hr;
}
#endif
else if( FAILED( hr = pSourceVoice->SubmitSourceBuffer( &buffer ) ) )
{
wprintf( L"Error %#X submitting source buffer\n", hr );
pSourceVoice->DestroyVoice();
return hr;
}
hr = pSourceVoice->Start( 0 );
// Let the sound play
BOOL isRunning = TRUE;
while( SUCCEEDED( hr ) && isRunning )
{
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState( &state );
isRunning = ( state.BuffersQueued > 0 ) != 0;
// Wait till the escape key is pressed
if( GetAsyncKeyState( VK_ESCAPE ) )
break;
Sleep( 10 );
}
// Wait till the escape key is released
while( GetAsyncKeyState( VK_ESCAPE ) )
Sleep( 10 );
pSourceVoice->DestroyVoice();
return hr;
}

Playing music with Media Engine on Windows 8

I am porting a Windows Phone 8 app to Windows 8 and it seems that Media Engine library works differently.
Here is my initialization code that works on WP8:
DX::ThrowIfFailed(
MFStartup(MF_VERSION)
);
ComPtr<IMFMediaEngineClassFactory> mediaEngineFactory;
ComPtr<IMFAttributes> mediaEngineAttributes;
// Create the class factory for the Media Engine.
DX::ThrowIfFailed(
CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mediaEngineFactory))
);
// Define configuration attributes.
DX::ThrowIfFailed(
MFCreateAttributes(&mediaEngineAttributes, 1)
);
ComPtr<MediaEngineNotify> notify = Make<MediaEngineNotify>();
ComPtr<IUnknown> unknownNotify;
DX::ThrowIfFailed(
notify.As(&unknownNotify)
);
DX::ThrowIfFailed(
mediaEngineAttributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, unknownNotify.Get())
);
// Create the Media Engine.
DX::ThrowIfFailed(
mediaEngineFactory->CreateInstance(0, mediaEngineAttributes.Get(), &m_mediaEngine)
);
CreateInstance() throws 0xc00d36e6 exception ( MF_E_ATTRIBUTENOTFOUND ).
I've tried to search for samples for Media Engine playback of mp3s but can only find Video Playback samples.
Any ideas?
I figured out the missing attribute. I had to add MF_MEDIA_ENGINE_AUDIO_CATEGORY to specify what M the Media Engine is actually doing. Here is an example that works for both WP8 and WIN8:
#if PLATFORM_WINDOWS8
DX::ThrowIfFailed(
MFStartup(MF_VERSION)
);
#endif
ComPtr<IMFMediaEngineClassFactory> mediaEngineFactory;
ComPtr<IMFAttributes> mediaEngineAttributes;
// Create the class factory for the Media Engine.
DX::ThrowIfFailed(
CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mediaEngineFactory))
);
// Define configuration attributes.
DX::ThrowIfFailed(
MFCreateAttributes(&mediaEngineAttributes, 1)
);
ComPtr<MediaEngineNotify> notify = Make<MediaEngineNotify>();
ComPtr<IUnknown> unknownNotify;
DX::ThrowIfFailed(
notify.As(&unknownNotify)
);
DX::ThrowIfFailed(
mediaEngineAttributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, unknownNotify.Get())
);
DWORD flags=0;
#if PLATFORM_WINDOWS8
DX::ThrowIfFailed(
mediaEngineAttributes->SetUINT32(MF_MEDIA_ENGINE_AUDIO_CATEGORY, AudioCategory_GameMedia)
);
flags = MF_MEDIA_ENGINE_AUDIOONLY | MF_MEDIA_ENGINE_REAL_TIME_MODE;
#endif
// Create the Media Engine.
DX::ThrowIfFailed(
mediaEngineFactory->CreateInstance(flags, mediaEngineAttributes.Get(), &m_mediaEngine)
);

GmfBridge doesn't connect sink filter with source filter

I am trying to use GmfBridge library to dynamically change source filters from cam to file and vice versa. All functions return S_OK (well, almost all - pMediaControlOutput->Run() returns S_FALSE actually, but in msdn said that this can be ok too), so I've assumed that all is ok but data isn't transfered to the other side of the bridge. I use GraphStudio to connect to remote graph and all seems to be ok - all filters in both graph are connected as it should be. But while playing I receive following messages in bridge log file:
0: Started 2011-09-10 15:58:38
0: Buffer minimum 100
0: Added stream 1: 畡楤o, Decompressed Only, Discard mode
1: Sink 0x7ae0ca8 has 1 pins
1: Sink filter 0x7ae0cf8 in graph 0x193bf0
2: Source 0x7ae1030 has 1 pins
2: Source filter 0x7ae1080 in graph 0x194c18
14: ReceiveConnection Aware: false
14: Bridging 0x194c18 to 0x193bf0
14: Pin 0x7ae3438 disconnect
25: Source 0x7ae1030 pause from 0
25: Source pin 0x7ae3618 active
2234: Pin 0x7ae3438 receive 0x721ec68
2234: Sink pin 0x7ae3438 disconnected: discarding 0x721ec68
3389: Pin 0x7ae3438 receive 0x721ec68
3389: Sink pin 0x7ae3438 disconnected: discarding 0x721ec68
3940: Pin 0x7ae3438 receive 0x721ec68
3940: Sink pin 0x7ae3438 disconnected: discarding 0x721ec68
4440: Pin 0x7ae3438 receive 0x721ec68
So as you can see, left graph isn't connected to right one despite BridgeGraphs() returned S_OK and media sample is discarded. Below there is my code. Where am I going wrong?
// Create graphs
HRESULT hr = m_graphInput.CreateInstance(CLSID_FilterGraph);
ATLASSERT( SUCCEEDED( hr ) );
hr = m_graphOutput.CreateInstance(CLSID_FilterGraph);
ATLASSERT( SUCCEEDED( hr ) );
// Get IMediaControl interfaces
hr = m_graphInput.QueryInterface( IID_IMediaControl, (void**)&pMediaControlInput );
ATLASSERT( SUCCEEDED( hr ) );
hr = m_graphOutput.QueryInterface( IID_IMediaControl, (void**)&pMediaControlOutput );
ATLASSERT( SUCCEEDED( hr ) );
// Get builder interfaces
hr = m_graphInput.QueryInterface( IID_IGraphBuilder, (void**)&pBuilderInput );
ATLASSERT( SUCCEEDED( hr ) );
hr = m_graphOutput.QueryInterface( IID_IGraphBuilder, (void**)&pBuilderOutput );
ATLASSERT( SUCCEEDED( hr ) );
// Load source filter (on sink side)
LocateFilter( SOURCE_FILTER_NAME, CLSID_LegacyAmFilterCategory, &inputDevice );
hr = m_graphInput->AddFilter( inputDevice, SOURCE_FILTER_NAME );
ATLASSERT( SUCCEEDED( hr ) );
// Load render filter (on bridge's source side)
LocateFilter( _T( "Default DirectSound Device" ), CLSID_AudioRendererCategory, &audioOutputPreview );
hr = m_graphOutput->AddFilter( audioOutputPreview, _T( "Default DirectSound Device" ) );
ATLASSERT( SUCCEEDED( hr ) );
// Init bridge
bridge.CreateInstance( __uuidof(GMFBridgeController) );
hr = bridge->SetBufferMinimum( 100 );
ATLASSERT( SUCCEEDED( hr ) );
hr = bridge->AddStream( false, eUncompressed, false );
ATLASSERT( SUCCEEDED( hr ) );
// Add sink filter and connect to input graph
IUnknownPtr pSinkFilter;
{
hr = bridge->InsertSinkFilter( m_graphInput, (IUnknown**)&pSinkFilter );
ATLASSERT( SUCCEEDED( hr ) );
// Using own functions get pins
IPin* pInAudio = CPinController::getOutputPin( inputDevice, _T("Audio"));
IPin* pOutAudio = CPinController::getInputPin( pSinkFilter );
hr = pBuilderInput->Connect( pOutAudio, pInAudio );
ATLASSERT( SUCCEEDED( hr ) );
}
// Add output filter and connect to output graph
IUnknownPtr pFeederFilter;
{
hr = bridge->InsertSourceFilter( pSinkFilter, m_graphOutput, &pFeederFilter );
ATLASSERT( SUCCEEDED( hr ) );
// Get pins
IPin* pInAudio = CPinController::getOutputPin( pFeederFilter/*, _T("Audio")*/);
IPin* pOutAudio = CPinController::getInputPin( audioOutputPreview );
hr = pBuilderOutput->Connect( pInAudio, pOutAudio );
ATLASSERT( SUCCEEDED( hr ) );
}
// Run left
hr = pMediaControlInput->Run();
ATLASSERT( SUCCEEDED( hr ) );
// Run right
hr = pMediaControlOutput->Run();
ATLASSERT( SUCCEEDED( hr ) );
hr = bridge->BridgeGraphs( m_graphOutput, m_graphInput );
ATLASSERT( SUCCEEDED( hr ) );
It's really ridiculous but about a few minutes ago after a day of searching we have found the answer. All deal was about really huge hole in GmfBridge. I was giving wrong interfaces here (there are graphs instead of bridge's sink and source filters) 'coz function needed pointers to IUnknown:
hr = bridge->BridgeGraphs( m_graphOutput, m_graphInput );
And in GmfBridge library this situation wasn't handled properly - there is no "else" brunch to handle error and function returns hr which was set in begin to S_OK:
HRESULT STDMETHODCALLTYPE BridgeGraphs(
/* [in] */ IUnknown *pSourceGraphSinkFilter,
/* [in] */ IUnknown *pRenderGraphSourceFilter)
{
HRESULT hr = S_OK;
...
// if we are not given both filters, then
// we need do nothing
IBridgeSinkPtr pSink = pSourceGraphSinkFilter;
IBridgeSourcePtr pSource = pRenderGraphSourceFilter;
if ((pSink != NULL) && (pSource != NULL))
{
...
}
return hr;
}
So as you can see it just says there is nothing wrong and then it just do nothing! I think it's good idea to notify authors of the lib about this bug.
Hope this info will help someone.