C# 在同步播放3个MP3声音文件时,我收到一个奇怪的异常

C# 在同步播放3个MP3声音文件时,我收到一个奇怪的异常,c#,wpf,naudio,C#,Wpf,Naudio,我想这样做: 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(); p

我想这样做:

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

WaveOut
对象,它“拥有”WaveCallbackInfo.FunctionCallback()委托,正在使用
-block在
结束时进行处理和垃圾收集。看起来您的
while
循环没有提供任何保护来防止在事后使用委托(听起来像是本机代码最终调用了它,奇怪的体系结构)

您可以使用以下方法来实现等待:

// 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可以多次初始化):


你看起来像是在使用老版本的NAudio。由于1.4,Mp3FileReader从Read方法返回PCM格式的音频,因此不需要波形转换流和BlockAlignReductionStream。我建议你升级


此外,如果可以避免使用函数回调(例如WinForms和WPF),我倾向于建议不要使用函数回调,因为我发现不同的声卡驱动程序在不同和意外的时间调用它们。如果不给非托管代码一个指向托管函数的函数指针,生活就会简单得多。使用默认的WaveOut构造函数,让它使用窗口回调。这是一种更可靠的工作方式。有关更多信息,请参阅我的博客文章

现在我正在使用这个解决方案:

我遇到了这个主题,我实际上也在使用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。播放似乎不起作用,因为它一直保持播放状态。检查wave stream可以做到这一点,但请记住,如果您在wave stream播放完音频之前停止它,代码将永远休眠,请确保您在那里进行了一些检查。

非常感谢!我第一次尝试了你的代码,它运行正常,但是如果我再次单击按钮,第一个mp3播放,然后第二个抛出问题中提到的异常。@托尼:我一直在阅读NAudio库,在
WaveCallbackInfo
上发现了一个
Disconnect
方法。你能试试我更新的代码吗?是的,我用你的例子再试了一次,但现在出现了错误:“NAudio.Wave.WaveCallbackInfo”不包含“Disconnect”的定义,并且找不到接受第一个参数类型为“NAudio.Wave.WaveCallbackInfo”的扩展方法“Disconnect”(是否缺少using指令或程序集引用?)@托尼:我在他们的源代码管理中看到的代码似乎与发行版不匹配。我发布了一些新代码。我相信如果
waveOut
被处理掉,它会破坏
callbackInfo
。你需要一个静态
waveOut
的策略。很抱歉,我无法让它工作。你能试试我上次遇到的吗hod并查看错误,看看是否有解决方案?我正在尝试的另一件事是在C#上使用MediaPlayer,但它只播放异步。我如何使用MediaPlayer来播放同步?谢谢你的回答。你能用一个线程安全的函数(如Playm3(字符串文件名))生成一个C#WPF代码吗,播放并在播放完成后返回。此函数将按顺序调用多次,不能返回问题中提到的奇怪异常。我将调用Playm3(“file1.mp3”);Playm3(“file2.mp3”);Playm3(“file3.mp3”);Playm3(“file4.mp3”);我没有在WPF中为NAudio生成阻塞播放示例的原因是,这是一种非常糟糕的用户体验。通常,你将WaveOut保存在一个私有变量中,这样你的GUI就可以保持响应,并且可以随时取消播放。你应该进行阻塞播放。如果我想不阻塞UI,我可以将播放包装在一个n中电子战线程。
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);