'Synchronize NAudio playback to multiple devices

I am trying to write a program which synchronizes playback of an audio file (MP3) using NAudio.

I've wrote a sync method which kicks in every 5 seconds to reposition the stream reader.

public class Advanced : IAutoSync, IDisposable
{
    private readonly ManualResetEventSlim _AutoSyncEvent = new ManualResetEventSlim(false);
    public void AutoSync(
        IDictionary<int, NAudioPlayItem> playItems, 
        IWavePosition getMasterPosition, 
        Func<long> getMasterStreamPosition, 
        Func<NAudioPlayItem, bool> isMasterPlayItem
    )
    {
        long masterStreamPosition = default;

        void Sync(object obj)
        {
            var pi = (NAudioPlayItem)obj;
            _AutoSyncEvent.Wait();
            pi.Reader.StreamPosition = masterStreamPosition;
            var playerPosition = pi.Player.GetPosition();
            var masterPosition = getMasterPosition.GetPosition();
            Debug.WriteLine("**ADVANCED AUTO-SYNC APPLIED. PLAYER POS: {0}, MASTER POS: {1}**", playerPosition, masterPosition);
        }

        void ResetMaster(object obj)
        {
            var pi = (NAudioPlayItem)obj;
            _AutoSyncEvent.Wait();
            pi.Reader.StreamPosition = masterStreamPosition;
            Debug.WriteLine("**ADVANCED AUTO-SYNC APPLIED. RESET MASTER POS**");
        }

        void StartThread(Action<object> start, NAudioPlayItem param)
        {
            var thread = new Thread(new ParameterizedThreadStart(start));
            thread.Priority = ThreadPriority.Highest;
            thread.Start(param);
        }
        
        _AutoSyncEvent.Reset();
        
        foreach (var pi in playItems)
        {
            var playItem = pi.Value;

            if (isMasterPlayItem(playItem))
            {
                StartThread(ResetMaster, playItem);
                continue;
            }

            StartThread(Sync, playItem);
        }

        masterStreamPosition = getMasterStreamPosition();

        _AutoSyncEvent.Set();
    }

    public void Dispose()
    {
        _AutoSyncEvent.Dispose();
    }
}

I have a collection of PlayItems that gets passed into this method. Each PlayItem has its own reader and player.

public class NAudioPlayItem
{
    public NAudioFileReader Reader { get; set; }
    public WaveOutEvent Player { get; set; }
    public Equalizer EQ { get; set; }
}

The problem is that it doesn't seem to sync accurately between all output devices.

Is there anyone that could please shed any light on how I could achieve accurate synchronization?



Solution 1:[1]

Accurate synchronization is a very hard problem to solve due to the latencies involved in each playback device you work with. The "position" in the source file is just an indication of how far in the audio file you have read. Internally, the sound card has a buffer (of maybe a couple of hundred milliseconds of sound) which it is playing through. The IWavePosition interface in NAudio allows you to get an accurate report of how many samples a player has played, and this would be better used for detecting synchronization.

I'm afraid I don't have any code to share but if I were to implement a sync algorithm myself with NAudio I'd probably create a custom ISampleProvider that could inject a set number of silence samples if the sync algorithm detected that that particular playback device was ahead of the others (alternatively you could make the ones that were behind skip forwards a set number of samples).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Heath