I am using the streaming principles from SharpDX Samples (https://github.com/sharpdx/SharpDX-Samples/blob/master/WindowsDesktop/XAudio2/AudioPlayerApp/AudioPlayer.cs) to play several sounds at the same time.
I have a list with multiple versions of the same song. I want to start them at exactly the same time. I start a new task for each song, and I use a combination of CountDownEvent and ManualResetEvent to make sure they start at the same time:
In the task I prepare buffers and signal the CountDownEvent that the song is ready to play. The song then waits for a signal from the ManualResetEvent. When the CountDownEvent has a signal from each song being ready, it signals the ManualResetEvent and SubmitSourceBuffer is called.
9 out of 10 times the songs start sample sync (no detectable phasing between the songs, simply louder) but sometimes one is slightly off.
Here is some code to elaborate:
Task playingtask = Task.Factory.StartNew(() =>
{
int nextbuffer = 0;
long zeroticks = MasterClock.Elapsed.Ticks;
try
{
while (true)
{
if (oursong.ADecoder == null || oursong.Status == PlaybackStatus.Stop) { break; }
var sampleiterator = oursong.ADecoder.GetSamples(TimeSpan.FromTicks(oursong.Position)).GetEnumerator();
while (true)
{
if (oursong.ADecoder == null || oursong.Status == PlaybackStatus.Stop) { break; }
foreach (SourceVoice SVoice in oursong.SVoices)
{
while (SVoice.State.BuffersQueued == oursong.BufferRing.Length && oursong.ADecoder != null)
{
BufferEndEvent.WaitOne(1);
}
}
if (!sampleiterator.MoveNext())
{
break;
}
var bufferpointer = sampleiterator.Current;
if (bufferpointer.Size > oursong.MBufferRing[nextbuffer].Size)
{
if (oursong.MBufferRing[nextbuffer].Pointer != IntPtr.Zero)
{
SharpDX.Utilities.FreeMemory(oursong.MBufferRing[nextbuffer].Pointer);
}
oursong.MBufferRing[nextbuffer].Pointer = SharpDX.Utilities.AllocateMemory(bufferpointer.Size);
oursong.MBufferRing[nextbuffer].Size = bufferpointer.Size;
}
SharpDX.Utilities.CopyMemory(oursong.MBufferRing[nextbuffer].Pointer, bufferpointer.Pointer, bufferpointer.Size);
oursong.BufferRing[nextbuffer].AudioDataPointer = oursong.MBufferRing[nextbuffer].Pointer;
oursong.BufferRing[nextbuffer].AudioBytes = bufferpointer.Size;
if (oursong.Status == PlaybackStatus.Ready)
{
CountDownEvent.Signal();
SongManualResetEvent.WaitOne();
oursong.Status = PlaybackStatus.Start;
}
foreach (SourceVoice SVoice in oursong.SVoices)
{
SVoice.SubmitSourceBuffer(oursong.BufferRing[nextbuffer], null);
}
oursong.Position += MasterClock.Elapsed.Ticks - zeroticks;
zeroticks = MasterClock.Elapsed.Ticks;
nextbuffer = ++nextbuffer % oursong.BufferRing.Length;
}
}
}
finally
{
DisposeAudio(oursong);
oursong.Position = oursong.Start;
}
}, TaskCreationOptions.LongRunning);
Sidenote: I send the buffer for each song to multiple audiodevices, in the same order for each song using the foreach statements. The Ready state is set beforehand using a load method which presets memory, audiodecoders etc.
I know that it is possible to use operation sets to simultaneously start sourcevoices but is there another way (using clocks or other sync method) to make sure the samples are send at the same time for each playing song?
You should be able to achieve this directly by using XAudio2 Operation Sets without having to workaround it with external custom sync events.