Как узнать, когда SoundPlayer закончил воспроизведение звука

Я использую следующий код для динамического создания частотного тона в памяти и асинхронного воспроизведения тона:

public static void PlayTone(UInt16 frequency, int msDuration, UInt16 volume = 16383)
{
    using (var mStrm = new MemoryStream())
    {
        using (var writer = new BinaryWriter(mStrm))
        {
            const double tau = 2*Math.PI;
            const int formatChunkSize = 16;
            const int headerSize = 8;
            const short formatType = 1;
            const short tracks = 1;
            const int samplesPerSecond = 44100;
            const short bitsPerSample = 16;
            const short frameSize = (short) (tracks*((bitsPerSample + 7)/8));
            const int bytesPerSecond = samplesPerSecond*frameSize;
            const int waveSize = 4;
            var samples = (int) ((decimal) samplesPerSecond*msDuration/1000);
            int dataChunkSize = samples*frameSize;
            int fileSize = waveSize + headerSize + formatChunkSize + headerSize + dataChunkSize;

            writer.Write(0x46464952);
            writer.Write(fileSize);
            writer.Write(0x45564157);
            writer.Write(0x20746D66);
            writer.Write(formatChunkSize);
            writer.Write(formatType);
            writer.Write(tracks);
            writer.Write(samplesPerSecond);
            writer.Write(bytesPerSecond);
            writer.Write(frameSize);
            writer.Write(bitsPerSample);
            writer.Write(0x61746164);
            writer.Write(dataChunkSize);

            double theta = frequency*tau/samplesPerSecond;
            double amp = volume >> 2;
            for (int step = 0; step < samples; step++)
            {
                writer.Write((short) (amp*Math.Sin(theta*step)));
            }

            mStrm.Seek(0, SeekOrigin.Begin);
            using (var player = new System.Media.SoundPlayer(mStrm))
            {
                player.Play();
            }
        }
    }
}

Код работает нормально. Единственная проблема в том, как я узнаю, что тон перестал играть? Похоже, в классе SoundPlayer нет события Completed, на которое я могу подписаться.


person Icemanind    schedule 10.12.2014    source источник
comment
Вы правы, у вас нет завершенного события, и если оно вам нужно, вам, скорее всего, потребуется использовать файл Элемент ActiveX проигрывателя Windows Media.   -  person Mark Hall    schedule 10.12.2014
comment
@MarkHall - Может ли элемент управления ActiveX получить источник из MemoryStream или он должен исходить из URL-адреса/файла?   -  person Icemanind    schedule 10.12.2014
comment
Я не уверен, так это или нет, одна из причин, по которой я разместил это как комментарий. Прошло несколько лет с тех пор, как мне пришлось его использовать, и я просто использовал файл.   -  person Mark Hall    schedule 10.12.2014


Ответы (1)


Вы знаете, если все, что вы хотите сделать, это воспроизвести один тон, есть Console.Beep. Конечно, он не делает этого в фоновом режиме, но метод, который я описываю ниже, отлично работает для Console.Beep и избавляет вас от необходимости создавать поток памяти только для воспроизведения тона.

В любом случае, SoundPlayer не имеет нужной вам функциональности, но вы можете смоделировать ее.

Сначала создайте обработчик событий:

void SoundPlayed(object sender, EventArgs e)
{
    // do whatever here
}

Измените свой метод PlayTone так, чтобы он принимал параметр функции обратного вызова:

public static void PlayTone(UInt16 frequency, int msDuration, UInt16 volume = 16383, EventHandler doneCallback = null)

Затем измените конец метода так, чтобы он вызывал PlaySync, а не Play, и вызывал doneCallback после завершения:

using (var player = new System.Media.SoundPlayer(mStrm))
{
    player.PlaySync();
}    
if (doneCallback != null)
{
    // the callback is executed on the thread.
    doneCallback(this, new EventArgs());
}

Затем выполните его в потоке или задаче. Например:

var t = Task.Factory.StartNew(() => {PlayTone(100, 1000, 16383, SoundPlayed));

Основная проблема заключается в том, что уведомление о событии происходит в фоновом потоке. Вероятно, лучшее, что можно сделать, если вам нужно повлиять на пользовательский интерфейс, — это синхронизировать обработчик событий с потоком пользовательского интерфейса. Таким образом, в методе SoundPlayed вы должны вызывать Form.Invoke (для WinForms) или Dispatcher.Invoke (WPF) для выполнения любых действий пользовательского интерфейса.

person Jim Mischel    schedule 10.12.2014
comment
Воспроизводит ли Console.Beep звук через звуковую карту или динамик ПК? - person Icemanind; 10.12.2014
comment
Это сработало! Я использую .NET 3.5, поэтому мне пришлось вызывать его так: var t = new Thread(() => PlayTone(1000, 1000, Done));, а затем запускать: t.Start();. Но он делает то, что мне нужно. - person Icemanind; 10.12.2014
comment
@icemanind: в документации говорится, что он воспроизводится через динамик консоли. На моей машине он воспроизводится через звуковую карту. Небольшой осмотр вокруг, кажется, говорит о том, что он будет воспроизводиться через звуковую карту, если она доступна. В противном случае он издает звуковой сигнал во внутренний динамик. Но я не проверял, поэтому точно сказать не могу. - person Jim Mischel; 10.12.2014
comment
@icemanind: Вместо того, чтобы начинать новый поток, вы можете вместо этого использовать ThreadPool.QueueUserWorkItem. Таким образом, у вас не будет кучи Thread экземпляров, которые вам нужно Join. - person Jim Mischel; 10.12.2014
comment
Что касается того, где воспроизводится звук, большинство компьютеров сегодня на самом деле больше не имеют динамика ПК, и в знак признания этого, начиная с Windows 7, Windows оснащена стандартной системной службой под названием Beep, которая перехватывает попытки издать звуковой сигнал динамика ПК. и имитирует их, используя сигнал, выводимый на звуковую систему. - person Jonathan Gilbert; 08.02.2018