1. SpeechSynthesizer概述

核心功能

System.Speech.Synthesis命名空间中的SpeechSynthesizer类提供了文本转语音(TTS)功能,主要特点包括:

  • 支持多种语音和语言
  • 可调节语速、音量和音调
  • 支持同步和异步语音合成
  • 提供SSML(语音合成标记语言)支持

技术规格

项目 详情
命名空间 System.Speech.Synthesis
程序集 System.Speech.dll
NuGet包 System.Speech v9.0.0-rc.1.24431.7
源代码 SpeechSynthesizer.cs

系统要求

  • Windows操作系统
  • 已安装语音合成引擎
  • .NET Framework 4.8+ 或 .NET Core 3.1+

2. 基础使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Speech.Synthesis;

namespace SampleSynthesis
{
class Program
{
static void Main(string[] args)
{
// 初始化语音合成器
using (var synth = new SpeechSynthesizer())
{
// 配置音频输出到默认设备
synth.SetOutputToDefaultAudioDevice();

// 设置语音属性
synth.Rate = -2; // 语速(-10到10)
synth.Volume = 80; // 音量(0-100)

// 同步朗读文本
synth.Speak("欢迎使用WPF语音合成功能");

// 异步朗读示例
synth.SpeakAsync("这段文本将异步朗读");
}
}
}
}

关键方法说明

  • SetOutputToDefaultAudioDevice(): 设置输出到默认音频设备
  • Speak(): 同步朗读文本
  • SpeakAsync(): 异步朗读文本
  • SelectVoice(): 选择特定语音
  • GetInstalledVoices(): 获取已安装语音列表

注解

创建新 SpeechSynthesizer 对象时,它使用默认的系统语音。 若要将 配置为 SpeechSynthesizer 使用已安装的语音合成 (文本转语音) 语音之一,请使用 SelectVoice 或 SelectVoiceByHints 方法。 若要获取有关已安装哪些语音的信息,请使用 GetInstalledVoices 方法和 VoiceInfo 类。

此类还提供对语音合成的以下方面的控制:

  • 若要配置 SpeechSynthesizer 对象的输出,请使用 SetOutputToAudioStream、 SetOutputToDefaultAudioDevice、 SetOutputToNull和 SetOutputToWaveFile 方法。
  • 若要生成语音,请使用 Speak、 SpeakAsync、 SpeakSsml或 SpeakSsmlAsync 方法。 SpeechSynthesizer可以从文本、Prompt或 PromptBuilder 对象或语音合成标记语言 (SSML) 版本 1.0 生成语音。
  • 若要暂停和恢复语音合成,请使用 Pause 和 Resume 方法。
  • 若要添加或删除词典,请使用 AddLexicon 和 RemoveLexicon 方法。 SpeechSynthesizer可以使用一个或多个词典来指导其字词的发音。
  • 若要修改语音输出的传递,请使用 Rate 和 Volume 属性。

遇到提示中的某些功能时,会 SpeechSynthesizer 引发事件:(BookmarkReached、 PhonemeReached、 VisemeReached和 SpeakProgress) 。
它还引发事件,报告说话操作的开始 (SpeakStarted) 和结束 (SpeakCompleted) 以及语音 (VoiceChange ) 的变化。

3. 完整工具类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.Speech.Synthesis;
using System.Collections.ObjectModel;

namespace WPF.Utilities
{
/// <summary>
/// 语音合成工具类 (线程安全单例模式)
/// </summary>
public sealed class SpeakerHelper : IDisposable
{
private static readonly Lazy<SpeakerHelper> _instance =
new Lazy<SpeakerHelper>(() => new SpeakerHelper());

private readonly SpeechSynthesizer _synth = new SpeechSynthesizer();
private readonly object _lock = new object();
private bool _disposed;

/// <summary>
/// 获取单例实例
/// </summary>
public static SpeakerHelper Instance => _instance.Value;

private SpeakerHelper()
{
// 初始化默认配置
_synth.SetOutputToDefaultAudioDevice();
_synth.Rate = -2; // 默认语速
_synth.Volume = 80; // 默认音量

// 尝试选择中文语音
foreach (var voice in _synth.GetInstalledVoices())
{
if (voice.VoiceInfo.Culture.Name.StartsWith("zh"))
{
_synth.SelectVoice(voice.VoiceInfo.Name);
break;
}
}
}

/// <summary>
/// 获取已安装的语音列表
/// </summary>
public ReadOnlyCollection<InstalledVoice> GetInstalledVoices()
{
lock (_lock)
{
return _disposed ?
throw new ObjectDisposedException(nameof(SpeakerHelper)) :
_synth.GetInstalledVoices();
}
}

/// <summary>
/// 同步朗读文本
/// </summary>
/// <param name="text">要朗读的文本</param>
/// <param name="rate">语速(-10到10)</param>
public void Speak(string text, int rate = -2)
{
if (string.IsNullOrWhiteSpace(text)) return;

lock (_lock)
{
if (_disposed)
throw new ObjectDisposedException(nameof(SpeakerHelper));

try
{
_synth.Rate = Math.Clamp(rate, -10, 10);
_synth.Speak(text);
}
catch (Exception ex)
{
// 记录日志或处理异常
Console.WriteLine($"语音合成错误: {ex.Message}");
}
}
}

/// <summary>
/// 异步朗读文本
/// </summary>
public void SpeakAsync(string text, int rate = -2)
{
if (string.IsNullOrWhiteSpace(text)) return;

lock (_lock)
{
if (_disposed)
throw new ObjectDisposedException(nameof(SpeakerHelper));

try
{
_synth.Rate = Math.Clamp(rate, -10, 10);
_synth.SpeakAsyncCancelAll();
_synth.SpeakAsync(text);
}
catch (Exception ex)
{
Console.WriteLine($"异步语音合成错误: {ex.Message}");
}
}
}

/// <summary>
/// 设置音量(0-100)
/// </summary>
public void SetVolume(int volume)
{
lock (_lock)
{
if (_disposed)
throw new ObjectDisposedException(nameof(SpeakerHelper));

_synth.Volume = Math.Clamp(volume, 0, 100);
}
}

/// <summary>
/// 将文本保存为WAV文件
/// </summary>
public void SaveToWav(string text, string filePath, int rate = -2)
{
if (string.IsNullOrWhiteSpace(text)) return;

lock (_lock)
{
if (_disposed)
throw new ObjectDisposedException(nameof(SpeakerHelper));

try
{
_synth.Rate = Math.Clamp(rate, -10, 10);
_synth.SetOutputToWaveFile(filePath);
_synth.Speak(text);
_synth.SetOutputToDefaultAudioDevice(); // 恢复默认输出
}
catch (Exception ex)
{
Console.WriteLine($"保存语音文件失败: {ex.Message}");
throw;
}
}
}

public void Dispose()
{
lock (_lock)
{
if (!_disposed)
{
_synth?.Dispose();
_disposed = true;
}
}
}
}
}

工具类使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 在WPF窗口中使用
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void SpeakButton_Click(object sender, RoutedEventArgs e)
{
// 使用默认设置朗读
SpeakerHelper.Instance.Speak("欢迎使用语音合成功能");

// 使用自定义设置
SpeakerHelper.Instance.SpeakAsync(txtContent.Text, rate: 0);
}

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new SaveFileDialog
{
Filter = "WAV文件|*.wav",
Title = "保存语音文件"
};

if (dialog.ShowDialog() == true)
{
SpeakerHelper.Instance.SaveToWav(
txtContent.Text,
dialog.FileName,
rate: 1);
}
}
}

最佳实践建议

  1. 资源管理

    • 在应用程序退出时调用SpeakerHelper.Instance.Dispose()
    • 使用using语句处理临时实例
  2. 线程安全

    • 所有方法都已内置线程安全锁
    • 可以从任何线程调用
  3. 异常处理

    • 方法内部已处理基本异常
    • 关键操作仍建议添加try-catch
  4. 性能考虑

    • 避免频繁创建/销毁实例
    • 对于长时间运行的应用,定期检查语音引擎状态

4. 高级功能配置

SSML标记语言支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用SSML实现更精细的语音控制
string ssmlText = @"<?xml version=""1.0""?>
<speak version=""1.0""
xmlns=""http://www.w3.org/2001/10/synthesis""
xml:lang=""zh-CN"">
<voice name=""Microsoft Huihui Desktop"">
<prosody rate=""-2"" volume=""80"">
这是使用SSML标记的文本。
</prosody>
<break time=""500ms""/>
<prosody pitch=""high"" rate=""-4"">
我可以改变音调和语速。
</prosody>
</voice>
</speak>";

SpeakerHelper.Instance.SpeakSsml(ssmlText);

自定义语音选择

1
2
3
4
5
6
7
8
9
10
11
12
// 获取所有可用语音
var voices = SpeakerHelper.Instance.GetInstalledVoices();

// 选择特定语音
foreach (var voice in voices)
{
if (voice.VoiceInfo.Culture.Name == "zh-CN")
{
_synth.SelectVoice(voice.VoiceInfo.Name);
break;
}
}

音频格式配置

1
2
3
// 设置WAV文件格式
_synth.SetOutputToWaveFile("output.wav",
new SpeechAudioFormatInfo(32000, AudioBitsPerSample.Sixteen, AudioChannel.Mono));

5. 性能优化建议

内存管理

  • 避免频繁创建/销毁SpeechSynthesizer实例
  • 对长时间运行的应用,定期检查内存使用
  • 使用using语句或显式调用Dispose()

并发处理

  • 异步方法(SpeakAsync)适合UI应用
  • 同步方法(Speak)适合后台服务
  • 避免同时播放多个语音流

资源释放

1
2
3
4
5
// 正确释放资源示例
using (var synth = new SpeechSynthesizer())
{
synth.Speak("这段语音播放后会自动释放资源");
}

6. 常见问题解答

Q1: 为什么没有中文语音?

  • 检查是否安装了中文语音包
  • 在Windows设置中添加中文TTS语音
  • 运行SpeakerHelper.Instance.GetInstalledVoices()确认

Q2: 语音输出不清晰?

  • 尝试调整语速(Rate属性)
  • 检查音频设备设置
  • 使用SSML优化发音

Q3: 如何跨平台使用?

  • Windows: 使用System.Speech.Synthesis
  • 跨平台: 考虑Azure Cognitive Services语音SDK
  • 移动端: 使用各平台原生TTS API

7. 参考文献