同步播放 3 个 MP3 声音文件时,我收到一个奇怪的异常
Posted
技术标签:
【中文标题】同步播放 3 个 MP3 声音文件时,我收到一个奇怪的异常【英文标题】:When playing 3 MP3 sound files in synchronously I receive a strange exception 【发布时间】:2011-06-03 18:57:18 【问题描述】:我想这样做:
Sistema.Util.MP3Player(@"sound1.mp3");
Sistema.Util.MP3Player(@"sound2.mp3");
namespace Sistema.Util.TextToSpeech
public class Player
static System.Windows.Media.MediaPlayer mp = new System.Windows.Media.MediaPlayer();
public static void MP3Player(string FileName, bool Async = false)
if (Async)
//mp.MediaOpened += new EventHandler(mp_MediaOpened);
//mp.MediaEnded += new EventHandler(mp_MediaEnded);
mp.Open(FileName.ToUri());
//mp.SpeedRatio = .2;
mp.Play();
else
// 03-06-2011
//using (var ms = System.IO.File.OpenRead(FileName)) // "test.mp3"
using (var rdr = new Mp3FileReader(FileName))
using (var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr))
using (var baStream = new BlockAlignReductionStream(wavStream))
using (var waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
//GC.KeepAlive(waveOut);
waveOut.Init(baStream);
waveOut.Play();
//waveOut.PlaybackStopped += new EventHandler(waveOut_PlaybackStopped);
while (waveOut.PlaybackState == PlaybackState.Playing)
System.Threading.Thread.Sleep(100);
问题是我有时会尝试,它会抛出错误:
检测到 CallbackOnCollectedDelegate 消息:对“NAudio!NAudio.Wave.WaveInterop+WaveCallback::Invoke”类型的垃圾收集委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。将委托传递给非托管代码时,托管应用程序必须使它们保持活动状态,直到保证它们永远不会被调用。
更新:我试过这个,但错误仍然发生 3 次。您能否尝试阅读此代码:
void play(string FileName)
var mre = new System.Threading.ManualResetEvent(false); // created unsignaled
var callbackInfo = WaveCallbackInfo.FunctionCallback(); //lifetime outside using
using (var rdr = new Mp3FileReader(FileName))
using (var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr))
using (var baStream = new BlockAlignReductionStream(wavStream))
using (var waveOut = new WaveOut(callbackInfo))
waveOut.Init(baStream);
waveOut.Play();
waveOut.PlaybackStopped += (sender, e) => mre.Set(); ;
mre.WaitOne();
play(@"C:\Users\Tony\AppData\Local\Temp\Sistema\Boa_Tarde(exclamacao).mp3");
play(@"C:\Users\Tony\AppData\Local\Temp\Sistema\Bem_vindo(exclamacao).mp3");
play(@"C:\Users\Tony\AppData\Local\Temp\Sistema\Boa_Tarde(exclamacao).mp3");
play(@"C:\Users\Tony\AppData\Local\Temp\Sistema\Bem_vindo(exclamacao).mp3");
【问题讨论】:
【参考方案1】:“拥有”WaveCallbackInfo.FunctionCallback()
委托的WaveOut
对象在using
块的末尾被处理和垃圾收集。看来您的 while
循环在事后无法防止使用委托(听起来像本机代码最终调用它,奇怪的架构)。
您可以使用ManualResetEvent 来实现等待:
// lifetime as long as your application
static WaveCallbackInfo callbackInfo = WaveCallbackInfo.FunctionCallback();
然后在你的方法里面
var mre = new ManualResetEvent(false); // created unsignaled
using (var rdr = new Mp3FileReader(FileName))
using (var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr))
using (var baStream = new BlockAlignReductionStream(wavStream))
using (var waveOut = new WaveOut(callbackInfo))
waveOut.Init(baStream);
waveOut.Play();
waveOut.PlaybackStopped += (sender,e) => mre.Set(); ;
mre.WaitOne();
编辑:本机代码需要某种句柄才能运行。这实际上意味着当本机代码仍在运行时,句柄永远不会消失。
此代码的当前问题是很难判断何时(如果有的话)您需要创建一个新的回调信息对象。你也可以试试:
static WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
static ManualResetEvent waveEvent = new ManualResetEvent(false);
static Player()
waveOut.PlaybackStopped += (sender, e) => waveEvent.Set(); ;
然后在您的方法中(假设 waveOut 可以多次初始化):
waveEvent.Reset();
using (var rdr = new Mp3FileReader(FileName))
using (var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr))
using (var baStream = new BlockAlignReductionStream(wavStream))
waveOut.Init(baStream);
waveOut.Play();
waveEvent.WaitOne();
【讨论】:
非常感谢!我尝试了您的代码,第一次运行正常,但如果我再次单击该按钮,第一个 mp3 播放,然后第二个抛出问题中提到的异常。 @Tony:我一直在阅读 NAudio 库,并在WaveCallbackInfo
上找到了 Disconnect
方法。你能试试我更新的代码吗?
是的,我再次尝试使用您的示例,但现在出现此错误:“NAudio.Wave.WaveCallbackInfo”不包含“断开连接”的定义,并且没有扩展方法“断开连接”接受第一个可以找到“NAudio.Wave.WaveCallbackInfo”类型的参数(您是否缺少 using 指令或程序集引用?)
@Tony:我正在查看他们的源代码控制中的代码,这些代码似乎与发布不匹配。我已经发布了一些新代码。我相信如果waveOut
被处理掉,它就会破坏callbackInfo
。您需要一个带有静态 WaveOut
的策略。
很抱歉,我无法让它工作。能否请您尝试我的最后一种方法并查看错误,看看是否有解决方案?我正在尝试的另一件事是在 C# 上使用 MediaPlayer,但它只播放异步。我怎样才能使用 MediaPlayer 播放同步?【参考方案2】:
您看起来使用的是旧版本的 NAudio。从 1.4 开始,Mp3FileReader 从 Read 方法返回 PCM 格式的音频,不再需要 WaveFormatConversionStream 和 BlockAlignReductionStream。我建议你升级。
此外,如果可以避免使用回调函数(即 WinForms 和 WPF),我倾向于建议不要使用它们,因为我发现不同的声卡驱动程序会在不同的意外时间调用它们。当您不为非托管代码提供指向托管函数的函数指针时,生活会容易得多。使用默认 WaveOut 构造函数并让它使用窗口回调。这是一种更可靠的工作方式。有关更多信息,请参阅我在 NAudio output devices 上的博客文章。
【讨论】:
感谢您的回答。您能否使用 PlayMP3(string FileName) 之类的线程安全函数制作一个 C# WPF 代码,在播放完成后播放并返回。这个函数会被依次调用多次,并且不能返回问题中提到的那个奇怪的异常。我将调用 PlayMP3("file1.mp3");播放MP3("file2.mp3");播放MP3("file3.mp3");播放MP3("file4.mp3"); 我没有为 WPF 中的 NAudio 制作阻塞播放示例的原因是它的用户体验非常糟糕。通常你将 WaveOut 保存在一个私有变量中,让你的 GUI 保持响应,并随时取消播放。 你应该做一个阻止播放。如果我想不阻塞 UI,我可以将 Play 包装在一个新线程中。【参考方案3】:现在我正在使用这个解决方案:WPF MediaPlayer: How to play in sequence, sync?
【讨论】:
【参考方案4】:我遇到了这个话题,我实际上也在使用 NAudio 来播放 TTS mp3。所以我希望它们同步。这是我多次尝试后的解决方案。
var rdr = new Mp3FileReader(sFilePath);
var wavStream = WaveFormatConversionStream.CreatePcmStream(rdr);
var waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
waveOut.Init(wavStream);
waveOut.Play();
while (wavStream.Position != wavStream.Length)
Thread.Sleep(100);
希望它对其他人有所帮助,检查 waveOut.PlaybackState == PlaybackState.Playing 似乎不起作用,因为它一直保持在 Playing 状态。检查波流可以解决问题,但请记住,如果在它完成播放音频之前停止它,代码将永远休眠,请确保在那里进行一些检查。
【讨论】:
以上是关于同步播放 3 个 MP3 声音文件时,我收到一个奇怪的异常的主要内容,如果未能解决你的问题,请参考以下文章