简体   繁体   中英

How to play multiple sounds at once in C#

I want to be able to play multiple sounds at once. I tried this using multi threading but found they would still play one after the other. Is there a way to get them to play at the same time?

static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
            Thread th = new Thread(playSound);
            Thread th1 = new Thread(playSound1);

            th.Start();
            th1.Start();


        }

        public static void playSound()
        {
            System.Media.SoundPlayer s1 = new System.Media.SoundPlayer(@"c:\Users\Ben\Documents\c#\note_c.wav");
            s1.Load();
            s1.PlaySync();
        }

        public static void playSound1()
        {
            System.Media.SoundPlayer s1 = new System.Media.SoundPlayer(@"c:\Users\Ben\Documents\c#\note_e.wav");
            s1.Load();
            s1.PlaySync();
        }
    }

How about if we schedule a parallel execution. Like that:

var files = new List<string>() {"note_1.wav", "note_2.wav"};     
Parallel.ForEach(files, (currentFile) => 
{
    System.Media.SoundPlayer s1 = new System.Media.SoundPlayer(currentFile);
    s1.Load();
    s1.PlaySync();
});

Try using

[DllImport("winmm.dll")]
static extern Int32 mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback);

Usage:

       mciSendString(@"open path\to\your\file.wav type waveaudio alias anAliasForYourSound", null, 0, IntPtr.Zero);
        mciSendString(@"play anAliasForYourSound", null, 0, IntPtr.Zero);
  mciSendString(@"open path\to\your\anotherFile.wav type waveaudio alias anAliasForYourAnotherSound", null, 0, IntPtr.Zero);
        mciSendString(@"play anAliasForYourAnotherSound", null, 0, IntPtr.Zero);

You can't start threads at exactly same time but in this case you can improve your code by loading the sounds before starting the threads:

static void Main(string[] args)
{
    System.Media.SoundPlayer s1 = new System.Media.SoundPlayer(
      @"c:\Users\Ben\Documents\c#\note_c.wav");
    s1.Load();

    System.Media.SoundPlayer s2 = new System.Media.SoundPlayer(
      @"c:\Users\Ben\Documents\c#\note_e.wav");
    s2.Load();

    Console.WriteLine("Hello World");
    Thread th = new Thread(() => playSound(s1));
    Thread th1 = new Thread(() => playSound(s2));

    th.Start();
    th1.Start();
}

public static void playSound(System.Media.SoundPlayer s)
{
    s.PlaySync();
}

Otherwise you'll need to implement a synchronization system.

The best way is to use DirectX xAudio2 via SharpDX , you can find it here: https://github.com/sharpdx/SharpDX-Samples/tree/master/Desktop/XAudio2/PlaySound

It works perfectly , but it would be a bit tricky to make the code better, by utilizing Events & tasks instead of the while loop and sync execution, and to be able to load the xAudio2 graph dynamically to change the sound without reconstructing the whole graph.

Add these Nuget packages SharpDx SharpDX.MediaFoundation SharpDx.Xaudio2 and use code below, it uses DirectX xAudio2 and constructs a graph with a mixer, it can play WAV 8 and 16 as well

public class SoundServices : IDisposable
{
    /// <summary>
    /// Gets current sound file
    /// </summary>
    string SoundFile { get; }
    /// <summary>
    /// Gets current sound stream
    /// </summary>
    public Stream SoundStream { get; private set; }
    /// <summary>
    /// Gets/Sets looping option, sound will loop if set to true.
    /// </summary>
    public bool IsLooping { get; private set; }
    /// <summary>
    /// Holds the message of last error if any, check it if some feature fails
    /// </summary>
    public string LastErrorMsg { get; private set; }
    //Play control flags
    bool IsPlaying = false;
    bool IsUserStop = false;


    AutoResetEvent Playing;
    float CurrentVolume = 1.0f;
    bool IsInitialized = false;

    SoundStream stream;
    WaveFormat waveFormat;
    AudioBuffer buffer;
    SourceVoice sourceVoice;
    XAudio2 xaudio2;
    MasteringVoice masteringVoice;

    /// <summary>
    /// Initializes an instance by creating xAudio2 graph and preparing audio Buffers
    /// </summary>
    /// <param name="soundStream">WAV format sound stream</param>
    /// <param name="loop">Bool indicating to loop sound playing  or not</param>
    public SoundServices(Stream soundStream, bool loop)
    {
        try
        {
            if (soundStream == null)
            {
                throw new ArgumentNullException("soundStream", "Null is not allowed, please specify a valid stream");
            }
            SoundStream = soundStream;
            SoundFile = null;
            IsLooping = loop;
            //Playing = new ManualResetEvent(false);
            Playing = new AutoResetEvent(false);
            BuildxAudio2Graph();
        }
        catch (Exception e)
        {

            throw e;
        }

    }

    #region Sound Play API
    /// <summary>
    /// Plays the sound stream loaded during initialization
    /// </summary>
    /// <returns>Task of sound playing</returns>
    public Task PlaySound()
    {
        return Task.Factory.StartNew(() =>
        {
            PlayRepeatAsync();
        });
    }
    /// <summary>
    /// Task for starting play of sound buffers using xAudio2 graph
    /// </summary>
    void PlayRepeatAsync()
    {
        try
        {
            IsPlaying = true;
            if (buffer == null)
            {
                //stream = new SoundStream(SoundStream);
                //waveFormat = stream.Format;
                buffer = new AudioBuffer
                {
                    Stream = stream.ToDataStream(),
                    AudioBytes = (int)stream.Length,
                    Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
                };

                if (IsLooping)
                    buffer.LoopCount = AudioBuffer.LoopInfinite;

                sourceVoice.SubmitSourceBuffer(buffer, stream.DecodedPacketsInfo);
                //Close the stream as it is now loaded in buffer already
                //stream.Close();
            }

            sourceVoice.Start();
            Playing.WaitOne();
            sourceVoice.Stop();
            IsPlaying = false;
            sourceVoice.FlushSourceBuffers();
            //xAudio2 graph creation step (5) send the AudioBuffer to sourceVoice
            sourceVoice.SubmitSourceBuffer(buffer, stream.DecodedPacketsInfo);
        }
        catch (Exception e)
        {

            LastErrorMsg = "PlayRepeatAsync(): "+ e.Message;
            IsPlaying = false;
        }

    }
    /// <summary>
    /// Initializes xAudio2 graph to be used for sound playing
    /// </summary>
    void BuildxAudio2Graph()
    {
        try
        {
            stream = new SoundStream(SoundStream);
            waveFormat = stream.Format;
            //xAudio2 graph creation step (1) Create XAudio2 device
            xaudio2 = new XAudio2();
            //xAudio2 graph creation step (2) Create MasteringVoice and connect it to device
            //*Note: You must use aMasteringVoice to connect to xAudioDevice
            masteringVoice = new MasteringVoice(xaudio2);
            //SetVolume(CurrentVolume);

            //xAudio2 graph creation step (3) Prepare sourceVoice
            sourceVoice = new SourceVoice(xaudio2, waveFormat, true);

            // Adds a  callback check buffer end and Looping option
            sourceVoice.BufferEnd += SourceVoice_BufferEnd;
            //xAudio2 graph creation step (5) send the AudioBuffer to sourceVoice

            IsInitialized = true;
        }
        catch (Exception e)
        {

            LastErrorMsg = "BuildxAudio2Graph(): " + e.Message;
            IsInitialized = false;
        }
    }
    /// <summary>
    /// Recreates source buffer allowing sound change or loop change
    /// </summary>
    void RecreateBuffer()
    {
        try
        {
            SoundStream.Seek(0, SeekOrigin.Begin);
            stream = new SoundStream(SoundStream);
            waveFormat = stream.Format;
            //if (buffer == null)
            {
                //stream = new SoundStream(SoundStream);
                //waveFormat = stream.Format;
                buffer = new AudioBuffer
                {
                    Stream = stream.ToDataStream(),
                    AudioBytes = (int)stream.Length,
                    Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
                };

                if (IsLooping)
                    buffer.LoopCount = AudioBuffer.LoopInfinite;
                sourceVoice.FlushSourceBuffers();
                sourceVoice.SubmitSourceBuffer(buffer, stream.DecodedPacketsInfo);
                //Close the stream as it is now loaded in buffer already
                //stream.Close();
            }
        }
        catch (Exception e)
        {

            LastErrorMsg = "RecreateBuffer(): " + e.Message;
        }

    }

    /// <summary>
    /// Changes current audio to a new one
    /// </summary>
    /// <param name="soundStream">a stream from wav file</param>
    public void ChangeSoundTo(Stream soundStream, bool Loop)
    {
        try
        {
            IsLooping = Loop;
            SoundStream = soundStream;
            stream = new SoundStream(SoundStream);
            waveFormat = stream.Format;
            sourceVoice = new SourceVoice(xaudio2, waveFormat, true);
            sourceVoice.BufferEnd += SourceVoice_BufferEnd;
            RecreateBuffer();
        }
        catch (Exception e)
        {
            IsInitialized = false;
            LastErrorMsg = "ChangeSoundTo(): " + e.Message;
        }
    }

    /// <summary>
    /// Set loop ot no loop
    /// </summary>
    /// <param name="loop">True = Loop forever, false = play till end</param>
    public void SetLooping(bool loop)
    {
        if (IsPlaying)
            Stop();
        IsLooping = loop;
        RecreateBuffer();
    }
    #endregion

    /// <summary>
    /// Immediately Stops currently playing sound
    /// </summary>
    public void Stop()
    {
        try
        {
            if (IsPlaying)
            {
                IsUserStop = true;
                Playing.Set();
            }
        }
        catch (Exception e)
        {

            LastErrorMsg = "Stop(): " + e.Message;
        }
    }

    /// <summary>
    /// Gets Current Volume
    /// </summary>
    /// <returns>Current volume</returns>
    public float GetVolume()
    {
        float current = 0.0f;
        try
        {
            if (sourceVoice == null || sourceVoice.IsDisposed) return CurrentVolume;

            sourceVoice.GetVolume(out current);
        }
        catch (Exception e)
        {

            LastErrorMsg = "GetVolume(): " + e.Message;
        }
        return current;
    }

    /// <summary>
    /// Sets the current volume
    /// </summary>
    /// <param name="newVolume">returns back the current setting for confirmation</param>
    /// <returns>The current set volume</returns>
    public float SetVolume(float newVolume)
    {
        try
        {
            if (newVolume > 1 || newVolume < 0) return GetVolume();
            if (sourceVoice == null || sourceVoice.IsDisposed)
            {
                CurrentVolume = newVolume;
                return newVolume;
            }
            sourceVoice.SetVolume(newVolume, 0);

            return GetVolume();
        }
        catch (Exception e)
        {

            LastErrorMsg = "SetVolume(): " + e.Message;
            return 0.0f;
        }
    }


    /// <summary>
    /// End of buffer event handler
    /// </summary>
    /// <param name="obj"></param>
    private void SourceVoice_BufferEnd(IntPtr obj)
    {
        //Debug.WriteLine($"buffer end reached with looping {IsLooping}");
        if (!IsLooping)
        {
            if (IsPlaying && !IsUserStop)
                Playing.Set();
            else if(IsUserStop)
            {
                IsUserStop = false;
            }
        }

    }

    public void Dispose()
    {
        if (sourceVoice != null && !sourceVoice.IsDisposed)
        {
            sourceVoice.DestroyVoice();

            sourceVoice.Dispose();
        }
        if (buffer != null && buffer.Stream != null)
            buffer.Stream.Dispose();
        if (masteringVoice != null && !masteringVoice.IsDisposed)
            masteringVoice.Dispose();
        if (xaudio2 != null && !xaudio2.IsDisposed)
            xaudio2.Dispose();
    }

    ~SoundServices()
    {
        if (sourceVoice != null && !sourceVoice.IsDisposed)
        {
            sourceVoice.DestroyVoice();

            sourceVoice.Dispose();
        }
        if (buffer != null && buffer.Stream != null)
            buffer.Stream.Dispose();
        if (masteringVoice != null && !masteringVoice.IsDisposed)
            masteringVoice.Dispose();
        if (xaudio2 != null && !xaudio2.IsDisposed)
            xaudio2.Dispose();
    }
}

And you can use multiple SoundServices instances as you need:

var sp = new SoundServices(new MemoryStream(File.ReadAllBytes(WavFileName)), true);
            if (sp != null)
                sp.PlaySound();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM