同步播放 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 声音文件时,我收到一个奇怪的异常的主要内容,如果未能解决你的问题,请参考以下文章

MP3 播放器,播放按钮没有声音

在 Linux 上播放 mp3 声音缓冲区

手机进入睡眠状态时不播放自定义通知声音

在 iOS 应用中播放多种声音(mp3、wav 等)

iOS - mp3 文件不播放,没有错误?

如何在 ActionScript 3 中从缓冲区 (ByteArray/Stream) 播放 MP3 声音?