При синхронном воспроизведении 3 звуковых файлов MP3 я получаю странное исключение

Я хотел бы сделать это:

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

person Tony    schedule 03.06.2011    source источник


Ответы (4)


Объект WaveOut, который «владеет» делегатом WaveCallbackInfo.FunctionCallback(), удаляется, а мусор собирается в конце блока 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();
}
person user7116    schedule 03.06.2011
comment
Большое спасибо! Я попробовал ваш код, в первый раз он работает нормально, но если я снова нажму кнопку, воспроизводится первый mp3, а затем второй выдает исключение, упомянутое в вопросе. - person Tony; 03.06.2011
comment
@Tony: я просматривал библиотеку NAudio и нашел метод Disconnect для WaveCallbackInfo. Можете ли вы попробовать мой обновленный код? - person user7116; 03.06.2011
comment
Да, я попробовал снова с вашим примером, но теперь есть эта ошибка: «NAudio.Wave.WaveCallbackInfo» не содержит определения для «Disconnect» и метода расширения «Disconnect», принимающего первый аргумент типа «NAudio.Wave. WaveCallbackInfo' может быть найден (вам не хватает директивы using или ссылки на сборку?) - person Tony; 03.06.2011
comment
@Tony: я смотрел код в их системе управления версиями, который, похоже, не соответствует версии. Я разместил новый код. Я считаю, что если waveOut утилизируется, он уничтожает callbackInfo. Вам нужна стратегия со статическим WaveOut. - person user7116; 03.06.2011
comment
Извините, но я не мог заставить его работать. Не могли бы вы попробовать мой последний метод и увидеть ошибку и посмотреть, есть ли решение? Еще я пытаюсь использовать MediaPlayer на С#, но он воспроизводит только асинхронно. Как я могу настроить MediaPlayer для воспроизведения синхронизации? - person Tony; 04.06.2011
comment
быстрое и грязное решение - поставить короткий сон после mre.WaitOne. Событие PlaybackStopped не означает, что вы получили последнее сообщение обратного вызова, которое собираетесь получить от WaveOut, это просто означает, что все буферы были возвращены с пометкой «Готово». WaveOut также вызовет обратный вызов в последний раз, когда вы закроете устройство. Если вы удалите объект WaveOut слишком рано, сама функция обратного вызова будет удалена. Смотрите мой ответ для лучшего способа. - person Mark Heath; 04.06.2011

Похоже, вы используете старую версию NAudio. Начиная с версии 1.4 Mp3FileReader возвращает звук в формате PCM из метода Read, устраняя необходимость в потоках WaveFormatConversionStream и BlockAlignReductionStream. Я рекомендую вам обновить.

Кроме того, я склонен не советовать использовать обратные вызовы функций, если вы можете их избежать (например, WinForms и WPF), поскольку я обнаружил, что разные драйверы звуковых карт вызывают их в разное и неожиданное время. Жизнь становится намного проще, когда вы не даете неуправляемому коду указатель функции на управляемую функцию. Используйте конструктор WaveOut по умолчанию и разрешите ему использовать оконные обратные вызовы. Это гораздо более надежный способ работы. См. мою запись в блоге об устройствах вывода звука для получения дополнительной информации.

person Mark Heath    schedule 03.06.2011
comment
Спасибо за ваш ответ. Не могли бы вы создать код C# WPF с потокобезопасной функцией, такой как PlayMP3(string FileName) , которая воспроизводится и возвращается после завершения воспроизведения. Эта функция будет вызываться несколько раз подряд и не сможет вернуть это странное исключение, упомянутое в вопросе. Я вызову PlayMP3(file1.mp3); Воспроизвести MP3 (файл2.mp3); Воспроизвести MP3 (файл3.mp3); Воспроизвести MP3 (файл4.mp3); - person Tony; 04.06.2011
comment
Причина, по которой я не создал примеры блокировки Play для NAudio в WPF, заключается в том, что это действительно плохой пользовательский интерфейс. Обычно вы храните свой WaveOut в частной переменной, что позволяет вашему графическому интерфейсу оставаться отзывчивым, а воспроизведение может быть отменено в любое время. - person Mark Heath; 06.06.2011
comment
Вы должны сделать блокирующий розыгрыш. Если я хочу не блокировать пользовательский интерфейс, я могу просто обернуть Play внутри нового потока. - person Tony; 06.06.2011

На данный момент я использую это решение: WPF MediaPlayer: как играть последовательно, синхронизировать?

person Tony    schedule 06.06.2011

Я наткнулся на эту тему, где я также использую 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. Проверка волнового потока делает свое дело, но имейте в виду, что если вы остановите его до того, как он закончит воспроизведение звука, код будет спать вечно, убедитесь, что вы сделали некоторую проверку там.

person Richie86    schedule 02.06.2013