开发者

When playing 3 MP3 sound files in synchronously I receive a strange exception

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 = ne开发者_Go百科w 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");


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


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.


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜