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

发布于 2024-11-11 16:40:46 字数 2735 浏览 3 评论 0原文

我想这样做:

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

I would like to do this:

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

The problem is that is I try sometimes, it throws an error:

CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'NAudio!NAudio.Wave.WaveInterop+WaveCallback::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

UPDATE: I tried this, but the error still happen on 3 time. Could you please try to read this code:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

撩心不撩汉 2024-11-18 16:40:46

“拥有”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();
}

The WaveOut object, which "owns" the WaveCallbackInfo.FunctionCallback() delegate is getting disposed of and garbage collected at the end of the using-block. It appears your while loop offers no protection against the use of the delegate after the fact (sounds like native code ends up calling it, weird architecture).

You could use a ManualResetEvent to achieve the waiting:

// lifetime as long as your application
static WaveCallbackInfo callbackInfo = WaveCallbackInfo.FunctionCallback();

Then inside your method

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

Edit: the Native code requires a handle of some kind in order to run. What this effectively means is the handle can never go away while the native code is still running.

The current issue with this code is it is hard to tell when (if ever) you would need to make a NEW callback info object. You might also try:

static WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
static ManualResetEvent waveEvent = new ManualResetEvent(false);

static Player()
{
    waveOut.PlaybackStopped += (sender, e) => { waveEvent.Set(); };
}

Then in your method (this assumes waveOut can be initialized more than once):

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();
}
同尘 2024-11-18 16:40:46

您看起来使用的是旧版本的 NAudio。从 1.4 开始,Mp3FileReader 从 Read 方法返回 PCM 格式的音频,从而不再需要 WaveFormatConversionStream 和 BlockAlignReductionStream。我建议你升级。

另外,如果可以避免的话,我倾向于建议不要使用函数回调(即 WinForms 和 WPF),因为我发现不同的声卡驱动程序会在不同的意外时间调用它们。当您不向非托管代码提供指向托管函数的函数指针时,生活会容易得多。使用默认的 WaveOut 构造函数并让它使用窗口回调。这是一种更可靠的工作方式。请参阅我关于 NAudio 输出设备 了解更多信息。

You look like you are using an old version of NAudio. Since 1.4 the Mp3FileReader returns audio in PCM format from the Read method, removing the need for the WaveFormatConversionStream and the BlockAlignReductionStream. I recommend you upgrade.

Also, I tend to advise against using the function callbacks, if you can avoid them (i.e. WinForms and WPF), since I have found different audio card drivers call them at different and unexpected times. Life is a lot easier when you don't give unmanaged code a function pointer to a managed function. Use the default WaveOut constructor and let it use Windowed callbacks. It is a much more reliable way of working. See my blog post on NAudio output devices for more information.

待"谢繁草 2024-11-18 16:40:46

现在我正在使用这个解决方案: WPF MediaPlayer:如何播放按顺序,同步?

For now I am using this solution: WPF MediaPlayer: How to play in sequence, sync?

半衾梦 2024-11-18 16:40:46

我遇到这个主题时,我实际上也在使用 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 状态。检查波形流可以解决问题,但请记住,如果您在播放完音频之前停止它,代码将永远休眠,请确保在那里进行一些检查。

I come across this topic where I'm actually using NAudio to play TTS mp3 too. so I want them in sync. Here is my solution after several try.

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

Hope it help other, checking on waveOut.PlaybackState == PlaybackState.Playing doesn't seems to work as it keep staying at Playing state. Checking on wave stream does the trick but bare in mind if you stop it before it finish playing the audio, the code will sleep forever, make sure you do some checking there.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文