2010-10-06 13 views
5

Używam System.Speech.Synthesis.SpeechSynthesizer do konwersji tekstu na mowę. I z powodu anemicznej dokumentacji Microsoftu (zobacz mój link, nie ma uwag ani przykładów kodu) Mam problem z rozróżnieniem dwóch metod:TTS do strumieniowego z SpeechAudioFormatInfo przy użyciu SpeechSynthesizer

SetOutputToAudioStream i SetOutputToWaveStream.

Oto co mam wydedukować:

SetOutputToAudioStream zajmuje strumień oraz instancję SpeechAudioFormatInfo, który określa format pliku wave (próbek na sekundę, bitów na sekundę, kanały audio, etc.) i pisze tekst strumień.

SetOutputToWaveStream pobiera tylko strumień i zapisuje 16-bitowy, mono, 22kHz, plik wave PCM do strumienia. Nie ma sposobu, aby przekazać w SpeechAudioFormatInfo.

Mój problem to SetOutputToAudioStream nie zapisuje prawidłowego pliku wave do strumienia. Na przykład otrzymuję wyjątek InvalidOperationException ("Nagłówek fali jest uszkodzony") podczas przesyłania strumienia do System.Media.SoundPlayer. Jeśli napiszę strumień na dysk i spróbuję go odtworzyć przy pomocy WMP, otrzymam komunikat "Windows Media Player nie może odtwarzać pliku ...", ale strumień napisany przez SetOutputToWaveStream będzie poprawnie odtwarzany w obu. Moja teoria mówi, że SetOutputToAudioStream nie pisze (poprawnego) nagłówka.

Co dziwne konwencje nazewnictwa SetOutputTo * Blah * są niespójne. SetOutputToWaveFile pobiera SpeechAudioFormatInfo, gdy SetOutputToWaveStream nie.

Potrzebuję móc napisać plik fali mono 8kHz, 16-bitowy, do strumienia, coś, co ani SetOutputToAudioStream ani SetOutputToWaveStream nie pozwalają mi zrobić. Czy ktokolwiek ma wgląd w SpeechSynthesizer i te dwie metody?

Dla porównania, oto niektóre kod:

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

Rozwiązanie:

Wiele dzięki @Hans Passant, tu jest sedno tego, co używam teraz:

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; 

W moim surowym testowaniu działa świetnie, chociaż użycie refleksji jest trochę niefortunne, lepsze to niż zapisanie pliku na dysk i otwarcie strumienia.

Odpowiedz

6

Twój fragment kodu jest zakorkowany, po jego usunięciu używasz synth. Ale to nie jest prawdziwy problem, jestem pewien. SetOutputToAudioStream generuje surowy dźwięk PCM, "liczby". Bez formatu pliku kontenerowego (nagłówków), jak to, co jest używane w pliku .wav. Tak, tego nie można odtworzyć za pomocą zwykłego programu multimedialnego.

Brakujące przeciążenie dla SetOutputToWaveStream, które pobiera SpeechAudioFormatInfo jest dziwne. To naprawdę wygląda na niedopatrzenie, nawet jeśli jest to niezwykle rzadkie w środowisku .NET. Nie ma żadnego ważnego powodu, dla którego nie powinien działać, bazujący na nim interfejs SAPI go obsługuje. Można go zhackować za pomocą refleksji, aby wywołać prywatną metodę SetOutputStream. To działało dobrze, gdy testowałem go, ale nie mogę ręczyć za to:

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

Jeśli jesteś niewygodne z hack następnie używając Path.GetTempFileName(), aby tymczasowo przesyłać je do pliku na pewno działa.

+0

Chodźmy o tym myśleć, ostatni argument prawdopodobnie powinien być fałszywy, aby nie zamykać strumienia. Nie ma znaczenia dla MemoryStream. –

+0

Masz rację, synth.Speak() był w użyciu w moim kodzie. Edytowałem fragment kodu. Daję twojemu kodowi strzał, wygląda na to, że spełni to, o co proszę. Zgadzam się, że wygląda na niedopatrzenie. Dzięki! – AceJordin

Powiązane problemy