如何在 C# 中一次播放多个声音

Posted

技术标签:

【中文标题】如何在 C# 中一次播放多个声音【英文标题】:How to play multiple sounds at once in C# 【发布时间】:2018-07-06 17:39:14 【问题描述】:

我希望能够同时播放多个声音。我尝试使用多线程进行此操作,但发现它们仍然会一个接一个地播放。有没有办法让他们同时玩?

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();
        
    

【问题讨论】:

Play multiple sounds using SoundPlayer的可能重复 【参考方案1】:

如果我们安排并行执行怎么样。 像这样:

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();
);

【讨论】:

【参考方案2】:

尝试使用

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

用法:

       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);

【讨论】:

【参考方案3】:

您不能同时启动线程,但在这种情况下,您可以通过在启动线程之前加载声音来改进代码:

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();

否则你需要实现一个同步系统。

【讨论】:

【参考方案4】:

最好的方法是通过 SharpDX 使用 DirectX xAudio2,你可以在这里找到它: https://github.com/sharpdx/SharpDX-Samples/tree/master/Desktop/XAudio2/PlaySound

它工作得很好,但是通过使用事件和任务而不是 while 循环和同步执行来使代码更好,并且能够动态加载 xAudio2 图形以改变声音而不重建,这有点棘手整个图表。

【讨论】:

【参考方案5】:

添加这些 Nuget 包 SharpDx SharpDX.MediaFoundation SharpDx.Xaudio2 并使用下面的代码,它使用 DirectX xAudio2 并使用混音器构建图形,它也可以播放 WAV 8 和 16

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();
    

您可以根据需要使用多个 SoundServices 实例:

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

【讨论】:

以上是关于如何在 C# 中一次播放多个声音的主要内容,如果未能解决你的问题,请参考以下文章

如何在 c# wpf 中一次加载/保存多个文件? [关闭]

使用 c# wpf 从资源文件播放声音

如何防止 map() 在 react-native 中一次渲染整个数组

如何在Python中一次运行多个while循环[重复]

c#如何播放多个.wav文件,并设置每个文件的音量

如何在c#中播放特定的Windows错误声音[重复]