Pregunta

Estoy utilizando System.Speech.Synthesis.SpeechSynthesizer para convertir texto a voz. Y debido a la documentación anémica de Microsoft (ver mi enlace, no hay comentarios o ejemplos de código) que estoy teniendo cabezas de causar problemas o cruz de la diferencia entre estos dos métodos:

SetOutputToAudioStream y SetOutputToWaveStream.

Esto es lo que he deducido:

SetOutputToAudioStream toma una corriente y una instancia SpeechAudioFormatInfo que define el formato del archivo de onda (muestras por segundo, bits por segundo, canales de audio, etc.) y escribe el texto a la corriente.

SetOutputToWaveStream tarda sólo un arroyo y escribe un, 22kHz, archivo de 16 bits, mono de onda PCM a la corriente. No hay manera de pasar en SpeechAudioFormatInfo.

Mi problema es SetOutputToAudioStream no escribe un archivo de onda válida para la corriente. Por ejemplo consigo un InvalidOperationException ( "La cabecera de onda está dañado") al pasar la corriente a System.Media.SoundPlayer. Si escribo la corriente en el disco e intento jugar con WMP me sale un "Reproductor de Windows Media no puede reproducir el archivo ..." error, pero la corriente escrito por SetOutputToWaveStream desempeña adecuadamente en ambos. Mi teoría es que SetOutputToAudioStream no está escribiendo un (válida) cabecea.

Por extraño que las convenciones de nombres para el SetOutputTo * * Bla es inconsistente. SetOutputToWaveFile toma una SpeechAudioFormatInfo mientras SetOutputToWaveStream no lo hace.

Tengo que ser capaz de escribir un 8 kHz, 16 bits, mono onda archivo a un arroyo, algo que ni SetOutputToAudioStream o SetOutputToWaveStream permiten que haga. ¿Alguien tiene una idea de SpeechSynthesizer y estos dos métodos?

Para referencia, aquí hay algo de código:

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

Solución:

Muchas gracias a @Hans Passant, aquí es la esencia de lo que estoy usando ahora:

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;

En mis pruebas áspera funciona muy bien, aunque el uso de la reflexión es un poco repulsivo que es mejor que escribir el archivo en disco y la apertura de una corriente.

¿Fue útil?

Solución

Su fragmento de código se borked, que está utilizando sintetizador después de que se dispone. Pero ese no es el verdadero problema, estoy seguro. SetOutputToAudioStream produce el audio PCM en bruto, los números ''. Sin un formato de archivo contenedor (cabeceras), como lo que se usa en un archivo .wav. Sí, que no se pueden reproducir con un programa de medios de comunicación regular.

La sobrecarga que falta para SetOutputToWaveStream que toma un SpeechAudioFormatInfo es extraño. Realmente se parece a un descuido para mí, a pesar de que eso es extremadamente raro en el marco .NET. No hay ninguna razón de peso por la que no debería trabajo, la interfaz SAPI subyacente no apoyarlo. Puede ser pirateado con la reflexión en torno a llamar al método SetOutputStream privado. Esto funcionaba bien cuando lo probé, pero no puedo dar fe de ello:

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

Si se siente incómodo con el truco a continuación, utilizando Path.GetTempFileName () para transmitir temporalmente en un archivo será sin duda el trabajo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top