如何在 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# 中一次播放多个声音的主要内容,如果未能解决你的问题,请参考以下文章