播放WAV音频
在.NET中播放音频文件的最简单方式是使用不很起眼的 SoundPlayer 类,该类位于同样不很起眼的 System.Media名称空间中。SoundPlayer 类的功能非常有限:只能播放 WAV 音频文件不支持同时播放多个声音,并且没有提供控制音频播放任何方面的能力(例如,音量和平衡等细节)。
如果能够忍受 SoundPlayer 类极大的局限性,那么这仍是为应用程序添加音频功能的最简单、最轻量级的方法。SoundPlayerAction类又对 SoundPlayer 类进行了封装,使用该类可通过声明的触发器播放声音(而不是在事件处理程序中编写几行 C#代码)
SoundPlayer类
为使用SoundPlayer类播放声音,需要执行以下几个步骤:
- 创建
SoundPlayer
实例 - 通过设置
SoundLocation
或Stream
属性来指定声音内容。如果有指向WAV文件的文件路径,就使用SoundLocation属性。如果有基于流的包含WAV音频内容的对象,就使用Stream属性 - 一旦设置 Stream 或 SoundLocation 属性,就可以通过调用
Load( )
或LoadAsync( )
方法通知 SoundPlayer 实例实际加载音频数据。Load()方法最简单–暂停代码执行,直到所有音频数据都被加载到内存为止。LoadAsync()
方法不加通告地在另一个线程中进行工作,并当完成工作时引发 LoadCompleted 事件。 - 现在,当音频播放时可调用
PlaySync()
方法以暂停代码,也可使用 Play()方法在另一个线程中播放音频,确保应用程序的界面保持响应。唯一的另一个选择是使用PlayLooping()方法,该方法永无休止地异步播放音频(对于那些讨厌声道的人而言,这是很完美的)。想要在任何时间停止播放,只需要调用 Stop()方法即可。
如果将音频内容存储在二进制资源中并嵌入到应用程序中,将需要作为流访问音频并使用 SoundPlayer.Stream 属性,这是因为 SoundPlayer 类不支持 WPF 的 pack URI语法。
下面的代码片段显示了异步加载和播放声音的最简单方法:
1 | SoundPlayer player = new SoundPlayer(); |
如果已经创建了一些在应用程序中的几个位置播放的小音频,那么更合理的做法是将声音文件嵌入到编译过的程序集中,作为二进制资源(不要和声明式资源相混淆,声明式资源是在 XAML标记中定义的资源)。
1 | SoundPlayer player = new SoundPlayer(); |
SoundPlayer 类不能很好地处理较大的音频数据,因为它需要一次性地将整个文件加载到内存中,您可能会考虑可通过以更小的块提交大的音频文件来解决这个问题,但 SoundPlayer 类不支持这种技术。没有比较简单的方法可以同步 SoundPlayer 对象,从而逐块播放多个音频片断,因为它没有提供任何排队功能。每次调用 PlaySync()或 Play()方法时,会停止播放当前音频。可采用一些变通方法,但使用本章后面讨论的 MediaElement类会更好一些。
SoundPlayerAction类
SoundPlayerAction 类使得使用 SoundPlayer 类更加便捷。SoundPlayerAction 类继承自 TriggerAction 类,可使用该类响应任何事件。
下面的按钮使用 SoundPlayerAction 对象将 Click 事件连接到声音。触发器被封装到一个样式中,如果将该样式从按钮中提取出来并放到Resources集合中,就可以将该样式应用到多个按钮:
1 | <Button Margin="50"> |
当使用 SoundPlayerAction时,总是异步地播放声音。
系统声音
Windows操作系统能将音频文件映射到特定的系统事件,当然这只是一个华而不实的功能。除了 SoundPlayer 类之外,WPF 还提供了 System,Media.SystemSounds类,通过该类可以访问其中最常见的声音,并且可以在自己的应用程序中使用它们。如果希望使用简单的声音信号指示某个长时间运行的操作已经结束,或使用报警声音提示警告情况,这种技术是最有用的。
但 SystemSounds 类构建在 MessageBeep 这个 Win32API函数的基础上,因此只能访问以下通用的系统声音:
- Asterisk
- Beep
- Exclamation
- Hand
- Question
SystemSounds 类为这些声音中的每个声音都提供了一个属性,该属性返回一个 SystemSound对象,可使用该对象通过 Play()方法播放声音。例如,为使用代码播放蜂鸣声,只需要执行下面这行代码:
1 | SystemSounds.Beep.Play(); |
为给每个声音配置使用的 WAV 文件,首先打开控制面板,然后双击 Sound 图标。
MediaPlayer类
使用 SoundPlayer、SoundPlayerAction 以及 SystemSounds 类都很容易,但功能相对不是很强大。在当今世界,除了最简单的声音外,使用压缩的MP3 音频更加普遍,而不是使用原始的WAV 格式。但如果希望播放 MP3 音频或 MPEG 视频,就需要使用两个不同的类:MediaPlayer和 MediaElement。这两个类都依赖于 Windows媒体播放器提供的技术的关键部分。
MediaPlayer类(位于特定于 WPF的 System.Windows.Media名称空间)是WPF 中与SoundPlayer类等效的类。尽管明显不是轻量级的类,但它以类似方式工作——创建 MediaPlayer 对象,调用Open()方法加载音频文件,并调用 Play()方法开始异步播放声音(没有提供同步播放选项)。下面是一个最简单的示例:
1 | private MediaPlayer _player = new MediaPlayer(); |
在这个示例中有一些重要的细节需要注意:
- 在事件处理程序的外部创建 MediaPlayer 对象,从而使用该对象在整个窗口的生命期中都处于存活状态。这是因为当 MediaPlayer 对象被从内存中释放时会调用 MediaPlayer.Close()方法。如果在事件处理程序的内部创建 MediaPlayer 对象,那么该对象几乎会被立即从内存中释放并且稍后可能会被垃圾回收器回收,这时会调用Close()方法并且会停止播放。
- 通过 URI提供文件位置。不过,URI不能使用应用程序包语法,所以不可能嵌入音频文件并使用 MediaPlayer 类播放。这一限制是因为 MediaPlayer 类的底层功能不是 WPF 的内置功能–相反,它们是由不同的、非托管的 Windows媒体播放器组件提供的。
- **没有异常处理代码。**令人不快的是,Open()和Play()方法不会抛出异常(异步加载和播放过程有部分责任)。相反,如果希望确定是否正在播放音频,您需要负责处理
MediaOpened
和MediaFailed
事件。
|Balance|使用-1~1
之间的数值设置左声道和右声道之间的平衡,-1表示只使用左声道,1表示只使用右声道|
|Volume|使用0~1
之间的数值设置音量,0表示没有声音,1表示最大声音。默认值是0.5|
|SpeedRatio|为播放音频(或视频)设置速度倍数,从而使用比正常速度更快或更慢的速度进行播放。默认值1表示正常速度,而2表示正常速度的2倍,10表示正常速度的10倍,0.5表示正常速度的一半,等等。可以使用任意正的双精度数值|
|HasAudio HasVideo|指示当前加载的媒体文件是否包含音频或视频。为了显示视频,需要使用MediaElement类|
|NaturalDuration NaturalVideoHeight NaturalVideoWidth|指定使用正常播放速度进行播放时的持续时间和视频窗口的尺寸(正如将在稍后看到的可以缩放或扭曲视频以适应不同的窗口尺寸)|
|Position|指定在媒体文件中当前位置的TimeSpan对象。可通过设置这个属性跳到特定的时间位置|
|DownloadProgress BufferingProgress|指示文件已经下载(当 Source 是指向 Web 或远程计算机的 URL, 时非常有用)或缓冲(假设正在使用的媒体文件使用的是流格式编码,以便在全部下载之前就可以开始播放)的百分比。百分数使用 0~1之间的数值表示|
|Clock|获取或设置与这个播放器相关联的 MediaClock 对象。只有当正在将音频同步到时间线时(使用非常类似于将动画同步到时间线的方式),才使用MediaClock 对象。如果正在使用 MediaPlayer类提供的方法执行手动播放,这个属性就为null|
|Open()加载新的媒体文件||
|Play()|开始播放。如果文件已经开始播放,该方法不起作用|
|Pause()|暂停播放,但不会改变播放位置。如果再次调用Play()方法,会从当前位置开始播放。如果没有正在播放音频,该方法不起作用|
|Stop()|停止播放并将播放位置重新设置到文件的开头。如果再次调用Play()方法,将会从文件的开头开始播放。如果音频播放已经被停止,该方法不起作用|
使用这些成员,可构建基本的但是具有完整功能的媒体播放器。然而,WPF开发人员通常使用另一个非常类似的元素–MediaElement类
MediaElement类
MediaElement类是 WPF 元素,它封装了MediaPlayer 类的全部功能。与所有元素相同MediaElement 元素可被直接放置到用户界面中。如果正在使用MediaElement 元素播放音频,这不是很重要;但如果正在使用 MediaElement 元素播放视频,那么可在显示视频窗口的位置放置该元素。
播放声音只需要简单的MediaElement标签
使用代码播放音频
通常希望能更精确地控制播放。例如,可能希望在特定时间触发播放、永无休止地重复播放等。实现该目标的一种方法是在恰当的时间调用MediaElement类的方法。
MediaElement 元素的初始行为由 LoadedBehavior属性决定,该属性是 MediaElemet 类添加的几个属性之一,在 MeidaPlayer 类中没有该属性。LoadedBehavior 属性可使用任意 MediaState枚举值。该属性的默认值是 Play,不过也可使用 Manual,对于这种情况,音频文件被加载,并由代码负责在正确的时间开始播放。另一个选择是使用Pause,这会中断播放,但不允许使用回放方法开始播放(相反,需要使用触发器和故事板开始播放)。
MediaElement类还提供了 UnloadedBehavior属性,该属性决定了当元素被卸载之后应当执行的操作。对于这种情况,Close 是唯一有意义的选择,因为它关闭文件并且释放所有系统资源。
所以为了通过代码播放音频,必须修改
LoadedBehavior
属性,如下所示:
1 | <StackPanel> |
1 | private void CmdPlay_OnClick(object sender, RoutedEventArgs e) |
如果当正在播放时运行上面的代码,第一行代码会将播放位置重新设置到开始位置,然后继续从开始位置进行播放。第二行不起作用,因为媒体文件已经在播放。如果试图为没有将LoadedBehavior 属性设置为 Manual 的 MediaElement 元素使用上面的代码,将收到异常。
处理错误
如果没有发现文件或者加载文件失败,MediaElement元素不会抛出异常,而由您负责处理MediaFailed 事件。幸运的是,这个任务很容易完成。只需要修改 MediaElement 标签即可:
1 | <MediaElement |
另外,在事件处理程序中,使用 ExceptionRoutedEventArgs.ErrorException属性获取描述问题的异常对象:
1 | private void Media_OnMediaFailed(object? sender, ExceptionRoutedEventArgs e) |
使用触发器播放音频
使用MediaElement 类,还能够以声明的方式,使用 XAML 标记而不是使用代码控制音频。可使用触发器和故事板完成该工作。在此唯一的新要素是 MediaTimeline
类,该类控制播放音频或视频文件的时间,并且和MediaElement元素协调其播放。MediaTimeline 类继承自Timeline 类,并且增加了 Source 属性(确定希望播放的音频文件)。
下面的标记演示了一个简单示例。当使用鼠标单击按钮时,该例使用 BeginStoryboard 动作开始播放声音(显然,同样可很好地响应其他鼠标事件和键盘事件)。
1 | <StackPanel> |
因为这个示例播放的是音频,所以 MediaElement元素的位置并不重要。在这个示例中,MediaElement 元素被放在了一个 StackPanel 控件中,并在一个 Button 控件之后(顺序并不重要,因为 MediaElement 元素在运行时没有任何可视化外观)。当单击按钮时,创建包含 MediaTimeline对象的 Storyboard 对象。注意,没有使用MediaElement.Source 属性指定源,而是通过MediaTimeline.Source属性指定源。
当使用 MediaElement 元素作为 MediaTimeline 对象的目标时,就不必再关心将 LoadedBehavior和 UnloadedBehavior 属性设置为什么值。一旦使用 MediaTimeline 对象,就会由 WPF 动画时钟驱动音频或视频(从技术角度看,是由 MediaClock 类的实例驱动播放,该实例是通过MediaElement.Clock属性提供的)。
可使用故事板控制 MediaElement 元素的播放–换句话说,不仅可以播放,还可以酌情暂停、重新开始以及停止播放。例如,下图中显示的媒体播放器,该播放器非常简单,有4 个按钮。
1 | <Window.Resources> |
注意,尽管 MediaElement和 MediaPlayer 类的实现允许在暂停播放之后,通过调用 Play( )方法恢复播放,但 Storyboard 对象不按同样的方式工作。相反,需要单独的 ResumeStoryboard动作。如果这不是所希望的行为,可考虑为播放按钮添加一些代码,而不是使用声明式方法。
播放多个声音
尽管前面的示例显示了如何控制播放单个媒体文件,但也完全可以扩展到播放多个音频文件。下面的示例包含两个按钮,每个按钮播放自己的声音。当单击按钮时,创建新的 Storyboard对象,并使用新的 MediaTimeline 对象通过同一个 MediaElement 元素播放不同的音频文件。
1 | <Grid> |
在使用同一个MediaElement的情况下,如果快速地连续单击这两个按钮,就会发现第二个声音中断了第一个声音的播放。这是由于为两个时间线使用相同的 MediaElement 元素而导致的。更圆滑(但占用资源也更多)的方法是为每个按钮使用单独的 MediaElement元素,并将 MediaTimeline 对象指向相应的MediaElement 元素(对于这种情况,可直接在 MediaElement 标签中指定 Source 属性,因为它是不变的)。现在,如果快速地连续单击两个按钮,就会同时播放两个声音。
可为 MediaPlayer 类应用相同的方法–如果希望播放多个音频文件,就需要使用多个MediaPlayer 对象。如果决定通过代码使用 MediaPlayer 对象或 MediaElement 元素,还可以使用更加智能的优化方案,精确地同时播放两个声音,但只能同时播放两个声音。基本技术是定义两个 MediaPlayer 对象,并且每次播放新声音时在它们之间进行翻转(可通过 Boolean 变量跟踪上一次使用的是哪个对象)。为使这种技术更加轻松,可在恰当元素的 Tag 属性中保存音频文件的名称,从而所有事件处理代码需要做的工作是,査找要使用的正确 MediaPlayer 对象,设置其 Source属性,并调用它的 Play()方法。
改变音量、平衡、速度以及位置
MediaElement 类为控制音量、平衡、速度以及在媒体文件中的当前位置提供了与 MediaPlayer类相同的属性。
Volume和 Balance 滑动条是最容易关联的两个控件。因为 Volume 和 Balance 是依赖项属性所以可使用双向绑定表达式将滑动条连接到 MediaElement 元素。
1 | <Window.Resources> |
1 | private void Media_OnMediaOpened(object sender, RoutedEventArgs e) |
但在此有一些技巧。首先,SpeedRatio属性不能用于时钟驱动的音频(即使用 MediaTimeline对象的音频)。为使用 SpeedRatio 属性,需要将 SpecdRatio 的LoadedBehavior 属性设置为 Manual,并通过播放方法手动控制音频的播放。
其次,SpeedRatio不是依赖项属性,当它发生改变时,WPF不会接收到更改通知。这意味着如果包含了修改 SpeedRatio 属性的代码,滑动条不会相应进行更新(一种变通的方法是在代码中修改滑动条,而非直接修改 MediaElement 元素)。
最后的细节是当前位置,它由 Position属性提供。同样,在设置 Position 属性前,MediaElement元素需要处于 Manual 模式,这意味着不能使用 MediaTimeline 对象(如果使用 MediaTimeline 对象,可考虑使用具有(相对于期望位置的)偏移的 BeginStoryboard 动作)。
在此存在的缺点是:当媒体向前播放时不能更新滑动条。如果希望实现该功能,需要使用一种合适的变通方法(例如,当播放时使用 DispatcherTimer 对象触发周期性的检查,并更新滑动条)。如果使用 MediaTimeline 对象,也是如此。由于多种原因,不能直接绑定到MediaElement,Clock 信息;而是需要处理 Storyboard.CurrentTimelnvalidated 事件,就像第 15 章中的AnimationPlayer 示例所演示的那样。
将动画同步到音频
在某些情况下,您可能希望将其他动画同步到媒体文件(音频或视频)中的特定位置。例如,如果有一个很长的、展示一个人描述一系列步骤的音频文件,可能希望在每个暂停之后淡入不同的图像。
根据您的需要,设计方案可能非常复杂,并且通过将音频分割成单独的文件可能会得到性能更好并且更加简单的设计。这样,可以加载新的音频并且同时通过响应 MediaEnded 事件执行相应的动作。在其他一些情况下,需要在持续播放的、不能分割的媒体文件播放过程中同步-些内容。
可将其他动作和播放过程相配合的一种技术是关键帧动画。然后可将关键帧动画和 MediaTimeline 对象封装到故事板中。使用这种方法可为动画提供特定的时间偏移,然后可匹配到音频文件中的精确时间。实际上,甚至可使用能够批注音的第三方程序,并导出重要时间的列表。然后可使用这一信息为每个关键设置时间。
当使用关键帧动画时,将 Storyboard.SlipBehavior 属性设置为 Slip 是很重要的。这指定如果媒体文件被延迟,关键帧动画不应当在 MediaTimeline 前面运行。这是很重要的,因为 MediaTimeline会由于缓冲(如果是从服务器通过流加载数据)而被延迟,或更普通的情况是由于加载时间而被延迟。
下面的标记演示了一个具有两个同步动画的音频文件的基本示例。当到达音频文件的特定部分时,第一个动画改变标签中的文本。第二个动画在播放音频的过程中,通过改变小圆的Opacity 属性使小圆跳动。
1 | <Window.Resources> |
播放视频
当使用视频文件而不是音频文件时,上面学习过的使用 MediaElement类的所有内容同样适用。正如您所期望的,MediaElement类支持 Windows 媒体播放器支持的所有视频格式。尽管对视频格式的支持依赖于安装的解码器,但不用考虑对 WMV、MPEG 以及 AVI文件的基本支持。
使用视频文件的重要区别是,在 MediaElement 类中与可视化和布局相关的属性突然变得很重要了。最重要的是,Stretch和StretchDirection属性决定了如何缩放视频窗口以使其适应容器(并且和已经学习过的Shape类的所有派生类的Stretch和 StretchDirection属性的工作方式相同)。当设置 Stretch 值时,可使用 None 值保持原来的尺寸;可使用 Uniform 值进行拉伸以适应其容器,但不改变纵横比;可使用Fi 值进行拉伸以在两个方向上适应其容器(即使这意味着拉伸图片);可使用 UniformToFi 值改变图片尺寸以适应容器的最大尺寸而保持其纵横比(如果容器的纵横比和视频的纵横比不相同,这肯定会裁剪掉部分视频窗口)。
1 | <Grid> |
根据本来的视频范围改变 MediaElement 元素的尺寸会更好。例如,如果创建一个使用Stretch 属性值为 Uniform(默认值)的 MediaElement元素,并将它放到 Grid 控件中 Hight 属性值为 Auto 的行中,就会调整该行的尺寸以足够包含标准尺寸的视频,这样就不需要进行缩放。
视频效果
因为 MediaElement 元素的工作方式和其他任何 WPF 元素的工作方式类似,所以可使用几种令人称奇的方式控制 MediaElement元素。下面是一些例子:
- 可使用 MediaElement 元素作为内容控件(如按钮)的内容
- 可同时使用多个MediaElement元素为数千个内容控件设置内容–尽管CPU可能不能很好地承受这种压力。
- 还可通过 LayoutTransform 或 RenderTransform 属性结合使用视频和变换,从而可以移动、拉伸、扭曲或旋转视频窗口。
- 可设置 MediaElement 元素的 Clipping属性,将视频窗口剪裁为特定的形状或路径,并且只显示整个窗口的一部分。
- 可设置 Opacity 属性,从而使其他内容能够透过视频窗口显示。实际上,甚至可将多个半透明的视频窗口重叠在一起(当然这会严重地影响性能)。
- 可使用动画动态改变 MediaElement 元素或它的其中一个转换的属性。
- 可使用 VisualBrush 画刷,将视频窗口中的当前内容复制到用户界面中的其他地方,从而可以创建类似反射的特殊效果。
- 可在三维对象的表面放置视频窗口,并且可在播放视频时使用动画移动视频窗口
通常,对于 MediaElement 元素使用 RenderTransform 属性比使用 LayoutTransform 属性更好因为 RenderTransform 属性是轻量级的,而且可以使用方便的 RenderTransformOrigin 属性值,从而为特定的变换(如旋转)使用相对坐标。
例如,下面的标记创建了反射效果。该效果是通过创建具有两行的 Grid控件实现的。顶部的行包含了播放视频文件的MediaElement 元素。底部的行包含了使用VisualBrush 画刷绘制的 Rectangle 形状。技巧是 VisualBrush 画刷使用绑定表达式,从上面的视频窗口获取其内容。然后通过使用 RelativeTransform 属性翻转视频内容,再使用 OpacityMask渐变向下逐步淡出。
1 | <Grid HorizontalAlignment="Center" Margin="15"> |
这个示例运行得非常好。渲染反射效果的开销与渲染两个视频窗口的开销差不多,因为每-帧都必须被复制到下面的矩形中。此外,为创建反射效果,还需要翻转和淡化每一帧图像(WPF使用临时渲染表面执行这些变换)。但在现代计算机中,额外的开销造成的影响并不明显。
如果使用其他视频效果,情况就不同了。实际上,在 WPF 中,视频是为数不多的会非常容易地使 CPU 任务过重的领域之一,从而使创建的界面的执行很差。普通计算机不能处理多个同时播放的视频窗口(显然,这取决于视频文件的尺寸–更高的分辨率和更高的帧率意味着更多的数据,为进行处理需要消耗更多时间)。
语音
对音频和视频的支持是 WPF 平台的核心支柱。然而,WPF 还提供了用于封装两个不常用的多媒体功能的库:语音合成和语音识别。
这两个功能都是通过 System.Speech.dll
程序集中的类提供支持。默认情况下,Visual Studio没有为新的 WPF 项目添加对这个程序集的引用,由您负责为项目添加该引用。
语音是 WPF 的外围部分。尽管从技术角度看,语音支持是 WPF 的一部分,并且它和.NETFramework 3.0中的 WPF 一起发布,但语音名称空间是以 System,Speech 开头,而不是以System.Windows 开头。
语音合成
语音合成是根据提供的文本生成语音音频的功能。语音合成没有被构建进 WPF–不过,它是 Windows 可访问的功能。系统实用工具,如 Narrator(Windows 提供的一个轻量级的屏幕阅读器),使用语音合成帮助盲人用户导航基本的对话框。更常见的情形是,语音合成能用于创建音频辅导和语音指令,尽管事先录制好的音频质量更好。
当需要为动态文本创建音频时–换句话说,当在编译时不知道在运行时讲话的内容时,语音合成是很有意义的。但如果音频是固定的,事先录制好的音频则更易用,也更高效,并且声音效果也更好。唯一的另一个可能考虑使用语音合成的原因是,如果需要叙述非常多的文本,事先录制全部内容是不切实际的。
所有现代版本的 Windows都内置了语音合成功能,它们使用更自然的被命名为 Anna的女性声音。当然,您可以下载并安装其他声音。
播放合成语在表面上看起来很简单。需要做的全部工作就是创建位于System.SpeechSynthesis名称空间中的 SpeechSynthesizer类的一个实例,并使用一个文本字符串调用其 Speak()方法。下面是一个示例:
1 | SpeechSynthesizer synthesizer = new SpeechSynthesizer(); |
当使用这种方法时–向 SpeechSynthesizer 对象传递纯文本–会放弃一些控制。有些单词可能发音有误、不能相应地强调或者不能以正确的语速讲话。为更好地控制朗诵文本,需要使用 PromptBuilder 类构造语音定义。下面的代码演示了如何使用 PromptBuilder 类修改上面的示例,结果是完全相同的:
1 | PromptBuilder prompt = new PromptBuilder(); |
上面的代码没有提供任何优点。但PromptBuilder 类具有大量的其他方法,可使用这些方法自定义朗读文本的方式。例如,可使用重载版本的AppendText()方法强调指定的一个单词或几个单词,该版本的 AppendText()方法使用了PromptEmphasis 枚举值。尽管强调单词的精确效果”句子中的单词“are”:取决于使用的语音,但下面的代码重读“How are you”
1 | PromptBuilder prompt = new PromptBuilder(); |
AppendText()方法还有其他两个重载版本一一个使用 PromptRate 值,可增加或降低语速:另-个使用 PromptVolume值,可调高或调低音量。
如果希望同时改变这些细节中的多个细节,那么需要使用PromptStyle对象。PromptStyle类封装了 PromptEmphasis、PromptRate 以及 PromptVolume 值。可为这三个细节提供值或只使用其中的一个或两个。
为使用 PromptStyle 对象,需要调用PromptBuilder.BeginStyle()方法。然后将创建的PromptStyle 对象应用到所有朗读的文本,直到调用 BndStyle()方法为止。下面是修改后的示例,在语音中重读单词“are”,使用了强调并改变了语速:
1 | PromptBuilder prompt = new PromptBuilder(); |
PromptEmphasis、PromptRate 以及 PromptVolume 枚举为修改语音提供的方法不够精细。它们无法对语音进行精细控制,也不能为朗读的文本使用更加细微的特定语音模式。然而,PromptBuilder 类提供了 AppendTextWithHint()方法,该方法可处理电话号码、日期、时间以及需要拼写的单词。可使用 SayAs枚举提供选择。下面是一个示例:
1 | prompt.AppendText("The word laser is spelled "); |
语音识别
语音识别是将用户朗读的音频转换为文本的功能。与语音合成一样,语音识别是 Windows操作系统内置的功能。
语音识别也是 Windows访问功能。例如,它允许残疾用户通过语音与常用控件进行交互。语音识别还可用于非手动操作的计算机,在特定的环境下这是很有用的。
使用语音识别的最简单方法是创建SpeechRecognizer类的一个实例,该类位于SystemSpeech.Recognition名称空间。然后可为 SpeechRecognized 事件关联事件处理程序,无论何时,当朗读的单词被成功地转换为文本时都会引发该事件:
1 | SpeechRecognizer recognizer = new SpeechRecognizer(); |
然后可在事件处理程序中,通过SpeechRecognizedEventArgs.Result属性检索文本:
1 | private void recognizer_SpeechReconized(object sender,SpeechRecognizeEventArgs e) |
SpeechRecognizer 类封装了一个 COM 对象。为避免不恰当的误操作,应将它声明为窗口类的成员变量(这样,只要窗口存在,该对象就会保持为活动状态),并且当窗口关闭时调用它的Dispose()方法(以删除语音识别钩子)。
通常不推荐使用这种语音识别方式。因为WPF 具有它自己的用户界面自动化特性,能够和语音识别引擎无缝地工作。当配置该特性时,它允许用户在文本控件中输入文本,并且通过说出它们的自动化名称触发按钮控件。然而,可使用SpeechRecognition类为更加特殊的命令添加支持,以支持特定的情况。可通过指定基于SRGS(Speech Recognition Grammar Specification,语音识别语法规范)的语法完成该工作。
可使用两种方式构造SRGS语法。可从SRGS文档中加载,该文档使用基于XML的语法指定了语法规则。为此,需要使用 System.Speech.Recognition.SrgsGrammar 名称空间中的SrgsDocument 类:
1 | SrgsDocument doc = new SrgsDocument("app_grammar.xaml"); |