SpeechSynthesizerで作成したWAVファイルにノイズが発生

SpeechSynthesizer (System.Speech.Synthesis.SpeechSynthesizer) で実装していたテキスト読み上げをWAVファイルにアウトプットするようにしてみたところ、かなり耳に障るノイズが発生しました。SpeechSynthesizerから直接PCのオーディオで再生 (Speak) したときは特にノイズもなく再生されるのに…

問題が発生したのはこんな感じのC#コードです。

var synth = new SpeechSynthesizer();  
synth.SetOutputToWaveFile("foo.wav", new SpeechAudioFormatInfo(22050, AudioBitsPerSample.Sixteen, AudioChannel.Mono));
synth.Speak("こんにちは。今日はいい天気です。");

SpeechAudioFormatInfoのパラメーターを変えてみても改善せず。

ググってみたところ情報も少なかったのですが、stack overflowで何やら「System.Speech.dllの問題なのでCOMで呼び出しては?」みたいな投稿を見つけたので試してみることにしました。

実装 (C#)

まず、COM参照「Microsoft Speech Object Library」を追加します。私の環境 (Win10 20H2) ではバージョンは5.4でした。同じ名前・バージョンで候補が2つありましたので、とりあえずファイルバージョンが新しい方を選択。

あとはWavファイルを作るのに必要そうなパッケージを nuget で追加します。nuget パッケージマネージャーで「NAudio.Core」をプロジェクトに追加しておきます。NAudioなんとかという名前のパッケージがたくさんありますが、.Core だけあればとりあえず大丈夫そうです。

参照の設定が諸々できたら、SpeechSynthesizerで実装していた部分を次のようなコードに書き換えます。

var synth = new SpeechLib.SpVoice();
var wav = new SpeechLib.SpMemoryStream();
var voices = synth.GetVoices();

try
{
    synth.Volume = 100;
    synth.Rate = 0;

    foreach (SpeechLib.SpObjectToken voice in voices)
    {
        // 411=ja-JP
        if (voice.GetAttribute("Language").Equals("411"))
        {
            synth.Voice = voice;
            break;
        }
    }

    wav.Format.Type = SpeechLib.SpeechAudioFormatType.SAFT22kHz16BitMono;
    synth.AudioOutputStream = wav;

    synth.Speak("こんにちは。いい天気ですね。", SpeechLib.SpeechVoiceSpeakFlags.SVSFlagsAsync);
    synth.WaitUntilDone(System.Threading.Timeout.Infinite);

    var wf = new NAudio.Wave.WaveFormat(22050, 16, 1);
    using (var ms = new MemoryStream((byte[])wav.GetData()))
    using (var reader = new NAudio.Wave.RawSourceWaveStream(ms, wf))
    using (var fs = new FileStream("foo.wav", FileMode.Create))
    using (var writer = new NAudio.Wave.WaveFileWriter(fs, wf))
    {
        reader.CopyTo(writer);
    }
}
finally
{
    System.Runtime.InteropServices.Marshal.ReleaseComObject(voices);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(wave);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(synth);
}

netstandard」の参照が必要と警告されるので、そうしたらそれも追加します。(たぶんRawSourceWaveStreamのところだったと思うけれど忘れた。)

こんな感じで書き換えると、SpeechSynthesizerで作成したWAVファイルよりはノイズがなくなったような気がします。個人的にはまだ若干のノイズがある気もするけれど。

余裕があったら GCP の Text-to-Speech にでも書き換えようかなと思います。