2010-10-06 11 views
5

Ich verwende System.Speech.Synthesis.SpeechSynthesizer, um Text in Sprache zu konvertieren. Und aufgrund der anämischen Dokumentation von Microsoft (siehe meinen Link, es gibt keine Bemerkungen oder Codebeispiele) habe ich Probleme, den Unterschied zwischen zwei Methoden zu unterscheiden:TTS zum Streamen mit SpeechAudioFormatInfo mit SpeechSynthesizer

SetOutputToAudioStream und SetOutputToWaveStream.

Hier ist, was ich abgeleitet haben:

SetOutputToAudioStream einen Strom und eine SpeechAudioFormatInfo Beispiel nimmt, die das Format der Wave-Datei definiert (Samples pro Sekunde, Bits pro Sekunde, Audio-Kanäle, etc.) und schreibt den Text der Strom.

SetOutputToWaveStream nimmt nur einen Stream und schreibt eine 16 Bit, Mono, 22kHz, PCM-Wave-Datei in den Stream. Es gibt keine Möglichkeit SpeechAudioFormatInfo zu übergeben.

Mein Problem ist SetOutputToAudioStream schreibt keine gültige Wave-Datei in den Stream. Zum Beispiel bekomme ich eine InvalidOperationException ("Der Wave-Header ist beschädigt"), wenn ich den Stream an System.Media.SoundPlayer übergebe. Wenn ich den Stream auf den Datenträger schreibe und versuche, ihn mit WMP abzuspielen, erhalte ich den Fehler "Windows Media Player kann die Datei nicht abspielen ...", aber der von SetOutputToWaveStream geschriebene Stream wird in beiden korrekt wiedergegeben. Meine Theorie ist, dass SetOutputToAudioStream keinen (gültigen) Header schreibt.

Seltsamerweise sind die Namenskonventionen für SetOutputTo * Blah * inkonsistent. SetOutputToWaveFile verwendet SpeechAudioFormatInfo, SetOutputToWaveStreet dagegen nicht.

Ich muss in der Lage sein, eine 8kHz, 16-Bit, Mono-Wave-Datei in einen Stream zu schreiben, was weder SetOutputToAudioStream noch SetOutputToWaveStream mir erlaubt. Hat jemand Einblick in SpeechSynthesizer und diese beiden Methoden?

Als Referenz ist hier ein Code:

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

Lösung:

Vielen Dank an @Hans Passant, hier ist der Kern dessen, was ich bin jetzt mit:

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; 

Für meine grobe Tests funktioniert es gut, obwohl die Verwendung von Reflektion ein bisschen eklig ist, ist es besser, als die Datei auf die Festplatte zu schreiben und einen Stream zu öffnen.

Antwort

6

Ihr Code-Snippet ist geborstet, Sie verwenden Synth, nachdem es entsorgt wurde. Aber das ist nicht das wirkliche Problem, da bin ich mir sicher. SetOutputToAudioStream erzeugt das rohe PCM-Audio, die "Nummern". Ohne ein Containerdateiformat (Header), wie es in einer WAV-Datei verwendet wird. Ja, das kann nicht mit einem normalen Medienprogramm wiedergegeben werden.

Die fehlende Überladung für SetOutputToWaveStream, die ein SpeechAudioFormatInfo nimmt, ist seltsam. Es sieht wirklich wie ein Versehen für mich aus, obwohl das im .NET Framework extrem selten ist. Es gibt keinen zwingenden Grund, warum es nicht funktionieren sollte, die zugrunde liegende SAPI-Schnittstelle unterstützt es. Es kann mit Reflection gehackt werden, um die private SetOutputStream-Methode aufzurufen. Das funktionierte gut, wenn ich es getestet, aber ich kann es nicht bürgen:

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

Wenn Sie mit dem Hack unangenehm sind, dann Path.GetTempFileName() unter Verwendung vorübergehend in eine Datei zu streamen sicher arbeiten.

+0

Wenn ich darüber nachdenke, sollte das letzte Argument wahrscheinlich falsch sein, damit es den Stream nicht schließt. Wäre für einen MemoryStream nicht wichtig. –

+0

Sie haben Recht, synth.Speak() war in der Verwendung in meinem Code. Ich habe das Code-Snippet bearbeitet. Ich gebe deinem Code eine Chance, es sieht so aus, als könnte er erreichen, was ich verlange. Ich stimme zu, dass es wie ein Versehen aussieht. Vielen Dank! – AceJordin