如何查找SpeechSynthesizer所选语音的音频格式

本文关键字:语音 音频 格式 SpeechSynthesizer 何查找 查找 | 更新日期: 2023-09-27 18:26:13

在C#的文本到语音应用程序中,我使用SpeechSynthesizer类,它有一个名为SpeakProgress的事件,它会为每个口语单词触发。但对于某些语音,参数e.AudioPosition与输出音频流不同步,并且输出波形文件的播放速度比该位置显示的要快(请参阅此相关问题)。

无论如何,我正在努力寻找关于比特率的确切信息以及与所选语音相关的其他信息。正如我所经历的那样,如果我能用这些信息初始化wave文件,同步问题就会得到解决。然而,如果我在SupportedAudioFormat中找不到这样的信息,我就不知道其他方法可以找到它们。例如,"Microsoft David Desktop"语音在VoiceInfo中不提供支持的格式,但它似乎支持PCM 16000赫兹、16位格式。

如何找到语音合成器所选语音的音频格式

 var formats = CurVoice.VoiceInfo.SupportedAudioFormats;
 if (formats.Count > 0)
 {
     var format = formats[0];
     reader.SetOutputToWaveFile(CurAudioFile, format);
 }
 else
 {
        var format = // How can I find it, if the audio hasn't provided it?           
        reader.SetOutputToWaveFile(CurAudioFile, format );
}

如何查找SpeechSynthesizer所选语音的音频格式

更新:此答案已在调查后编辑。最初,我从内存中建议SupportedAudioFormats可能只是来自(可能配置错误)注册表数据;调查表明,对我来说,在Windows7上,情况确实如此,而且在Windows8上备份得很好。

受支持的音频格式的问题

System.Speech包装了古老的COM语音API(SAPI),一些语音是32比64位的,或者可能被错误配置(在64位机器的注册表上,HKLM/Software/Microsoft/Speech/VoicesHKLM/Software/Wow6432Node/Microsoft/Speech/Voices

我已经将ILSpy指向了System.Speech及其VoiceInfo类,我非常确信SupportedAudioFormats仅来自注册表数据,因此,如果您的TTS引擎没有为应用程序的平台目标(x86、Any或64位)正确注册,则在枚举SupportedAudioFormats时可能会得到零结果,或者如果供应商只是没有在注册表中提供这些信息。

语音可能仍然支持不同的、附加的或更少的格式,因为这取决于语音引擎(代码)而不是注册表(数据)。所以这可能是在黑暗中拍摄的。在这方面,标准Windows语音通常比第三方语音更一致,但它们仍然不一定能有效地提供SupportedAudioFormats

很难找到这些信息

我发现仍然可以获得当前语音的当前格式,但这确实依赖于反射来访问System.Speech SAPI包装器的内部。

因此,这是一个相当脆弱的代码!我不建议在生产中使用。

注意:下面的代码需要您调用Speak()一次才能进行设置;如果没有Speak(),则需要更多的调用来强制设置。然而,我可以打电话给Speak("")什么也不说,这很好。

实施:

[StructLayout(LayoutKind.Sequential)]
struct WAVEFORMATEX
{
    public ushort wFormatTag;
    public ushort nChannels;
    public uint nSamplesPerSec;
    public uint nAvgBytesPerSec;
    public ushort nBlockAlign;
    public ushort wBitsPerSample;
    public ushort cbSize;
}
WAVEFORMATEX GetCurrentWaveFormat(SpeechSynthesizer synthesizer)
{
    var voiceSynthesis = synthesizer.GetType()
                                    .GetProperty("VoiceSynthesizer", BindingFlags.Instance | BindingFlags.NonPublic)
                                    .GetValue(synthesizer, null);
    var ttsVoice = voiceSynthesis.GetType()
                                 .GetMethod("CurrentVoice", BindingFlags.Instance | BindingFlags.NonPublic)
                                 .Invoke(voiceSynthesis, new object[] { false });
    var waveFormat = (byte[])ttsVoice.GetType()
                                     .GetField("_waveFormat", BindingFlags.Instance | BindingFlags.NonPublic)
                                     .GetValue(ttsVoice);
    var pin = GCHandle.Alloc(waveFormat, GCHandleType.Pinned);
    var format = (WAVEFORMATEX)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(WAVEFORMATEX));
    pin.Free();
    return format;
}

用法:

SpeechSynthesizer s = new SpeechSynthesizer();
s.Speak("Hello");
var format = GetCurrentWaveFormat(s);
Debug.WriteLine($"{s.Voice.SupportedAudioFormats.Count} formats are claimed as supported.");
Debug.WriteLine($"Actual format: {format.nChannels} channel {format.nSamplesPerSec} Hz {format.wBitsPerSample} audio");

为了测试它,我在HKLM/Software/Wow6432Node/Microsoft/Speech/Voices/Tokens/MS-Anna-1033-20-Dsk/Attributes下重命名了MicrosoftAnna的AudioFormats注册表项,导致SpeechSynthesizer.Voice.SupportedAudioFormats在查询时没有元素。以下是这种情况下的输出:

0 formats are claimed as supported.
Actual format: 1 channel 16000 Hz 16 audio

您无法从代码中获得这些信息。你只能听所有格式(从8 kHz这样的糟糕格式到48 kHz这样的高质量格式),并观察它在哪里停止变得更好,我认为这就是你所做的。

在内部,语音引擎只向语音"询问"原始音频格式一次,我相信这个值只在内部由语音引擎使用,语音引擎不会以任何方式暴露这个值。

更多信息:

假设你是一家语音公司。您已将计算机语音录制为16 kHz、16位、单声道。

用户可以让你的声音在48千赫,32位,立体声。语音引擎进行这种转换。语音引擎并不关心它是否真的听起来更好,它只是进行格式转换。

假设用户想让你的声音说话。他要求将文件保存为48千赫,16位立体声。

SAPI/System.Speech用这种方法调用您的语音:

STDMETHODIMP SpeechEngine::GetOutputFormat(const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx,
GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx)
{
    HRESULT hr = S_OK;
    //Here we need to return which format our audio data will be that we pass to the speech engine.
    //Our format (16 kHz, 16 bit, mono) will be converted to the format that the user requested. This will be done by the SAPI engine.
    enum SPSTREAMFORMAT sample_rate_at_which_this_voice_was_recorded = SPSF_16kHz16BitMono; //Here you tell the speech engine which format the data has that you will pass back. This way the engine knows if it should upsample you voice data or downsample to match the format that the user requested.
    hr = SpConvertStreamFormatEnum(sample_rate_at_which_this_voice_was_recorded, pDesiredFormatId, ppCoMemDesiredWaveFormatEx);
    return hr;
}

这是唯一一个你必须"揭示"你的声音录制格式的地方

所有的"可用格式"都会告诉你你的声卡/Windows可以进行哪些转换。

我希望我解释得很好?作为一个语音供应商,您不支持任何格式。你只需要告诉语音引擎你的音频数据是什么格式,这样它就可以进行进一步的转换。