TTS для потоковой передачи с SpeechAudioFormatInfo с использованием SpeechSynthesizer - PullRequest
7 голосов
/ 06 октября 2010

Я использую 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 - нет.

Мне нужно иметь возможность записать 16-битный моноволновой файл 8 кГц в поток, что мне не позволяют ни 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;

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

1 Ответ

7 голосов
/ 06 октября 2010

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

Отсутствующая перегрузка для SetOutputToWaveStream, которая принимает SpeechAudioFormatInfo, странная.Это действительно выглядит для меня упущением, хотя это очень редко встречается в .NET Framework.Нет убедительной причины, по которой он не должен работать, базовый интерфейс 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 () для временной передачи его в файл, безусловно, будет работать.

...