TTS для потоковой передачи с помощью SpeechAudioFormatInfo с использованием SpeechSynthesizer

Я использую System.Speech.Synthesis.SpeechSynthesizer для преобразования текста в речь. . И из-за анемичной документации Microsoft (см. мою ссылку, там нет замечаний или примеров кода) у меня возникают проблемы с определением разницы между двумя методами:

SetOutputToAudioStream и SetOutputToWaveStream.

Вот что я вывел:

SetOutputToAudioStream принимает поток и экземпляр SpeechAudioFormatInfo, который определяет формат волнового файла (выборки в секунду, биты в секунду, аудиоканалы и т. д.) и записывает текст в поток.

SetOutputToWaveStream берет только поток и записывает в поток 16-битный, моно, 22 кГц, волновой файл PCM. Нет возможности пройти в SpeechAudioFormatInfo.

Моя проблема в том, что SetOutputToAudioStream не записывает в поток действительный волновой файл. Например, я получаю InvalidOperationException ("Заголовок волны поврежден") при передаче потока в System.Media.SoundPlayer. Если я записываю поток на диск и пытаюсь воспроизвести его с помощью WMP, я получаю сообщение об ошибке «Проигрыватель Windows Media не может воспроизвести файл...», но поток, записанный SetOutputToWaveStream, воспроизводится правильно в обоих случаях. Моя теория заключается в том, что SetOutputToAudioStream не записывает (действительный) заголовок.

Как ни странно, соглашения об именах для SetOutputTo*Blah* непоследовательны. SetOutputToWaveFile принимает SpeechAudioFormatInfo, а SetOutputToWaveStream — нет.

Мне нужно иметь возможность записывать 8 кГц, 16-битный, моно волновой файл в поток, что не позволяют мне ни SetOutputToAudioStream, ни SetOutputToWaveStream. Кто-нибудь разбирается в SpeechSynthesizer и этих двух методах?

Для справки, вот код:

Stream ret = new MemoryStream();
using (SpeechSynthesizer synth = new SpeechSynthesizer())
{
  synth.SelectVoice(voiceName);
  synth.SetOutputToWaveStream(ret);
  //synth.SetOutputToAudioStream(ret, new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Sixteen, AudioChannel.Mono));
  synth.Speak(textToSpeak);
}

Решение:

Большое спасибо @Hans Passant, вот суть того, что я сейчас использую:

Stream ret = new MemoryStream();
using (SpeechSynthesizer synth = new SpeechSynthesizer())
{
  var mi = synth.GetType().GetMethod("SetOutputStream", BindingFlags.Instance | BindingFlags.NonPublic);
  var fmt = new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Sixteen, AudioChannel.Mono);
  mi.Invoke(synth, new object[] { ret, fmt, true, true });
  synth.SelectVoice(voiceName);
  synth.Speak(textToSpeak);
}
return ret;

Для моего грубого тестирования это работает отлично, хотя использование отражения немного непривычно, это лучше, чем запись файла на диск и открытие потока.


person AceJordin    schedule 06.10.2010    source источник


Ответы (1)


Ваш фрагмент кода недоработан, вы используете synth после его удаления. Но это не настоящая проблема, я уверен. SetOutputToAudioStream производит необработанный звук PCM, «числа». Без формата файла-контейнера (заголовков), подобного тому, что используется в файле .wav. Да, это невозможно воспроизвести с помощью обычной мультимедийной программы.

Отсутствующая перегрузка для SetOutputToWaveStream, которая принимает SpeechAudioFormatInfo, выглядит странно. Мне это действительно кажется недосмотром, хотя в среде .NET такое случается крайне редко. Нет веских причин, по которым он не должен работать, базовый интерфейс SAPI его поддерживает. Его можно взломать с помощью отражения, чтобы вызвать закрытый метод SetOutputStream. Это работало нормально, когда я тестировал это, но я не могу поручиться за это:

using System.Reflection;
...
            using (Stream ret = new MemoryStream())
            using (SpeechSynthesizer synth = new SpeechSynthesizer()) {
                var mi = synth.GetType().GetMethod("SetOutputStream", BindingFlags.Instance | BindingFlags.NonPublic);
                var fmt = new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Eight, AudioChannel.Mono);
                mi.Invoke(synth, new object[] { ret, fmt, true, true });
                synth.Speak("Greetings from stack overflow");
                // Testing code:
                using (var fs = new FileStream(@"c:\temp\test.wav", FileMode.Create, FileAccess.Write, FileShare.None)) {
                    ret.Position = 0;
                    byte[] buffer = new byte[4096];
                    for (;;) {
                        int len = ret.Read(buffer, 0, buffer.Length);
                        if (len == 0) break;
                        fs.Write(buffer, 0, len);
                    }
                }
            }

Если вам не нравится хак, то использование Path.GetTempFileName() для временной передачи его в файл, безусловно, сработает.

person Hans Passant    schedule 06.10.2010
comment
Если подумать, последний аргумент, вероятно, должен быть ложным, чтобы он не закрывал поток. Однако для MemoryStream это не имело бы значения. - person Hans Passant; 06.10.2010
comment
Вы правы, synth.Speak() был внутри использования в моем коде. Я отредактировал фрагмент кода. Я попробую ваш код, похоже, он выполнит то, о чем я прошу. Я согласен, что это выглядит как оплошность. Спасибо! - person AceJordin; 06.10.2010