1、Microsoft Speech SDK 提供关于语音(Speech)处理的一套应用程序编程接口SAPI( Speech Application Programming Interface) 。SAPI 提供了实现文字-语音转换(Text-to-Speech)和语音识别(Speech Recognition)程序的基本函数,大大简化了语音编程的难度,降低了语音编程的工作量。Speech SDK 可以免费从如下网址下载:http:/ Speech SDK 是以 COM 接口的方式提供服务的,所以首先介绍 COM 的有关基础知识。11.1.1 COM 基础Speech SDK 提供了完善的 COM
2、 接口,所以具备一定的 COM 编程基础对进行 Speech SDK编程来说是非常必要的。笔者将简要介绍 COM 编程的基础知识。虽然这些知识对阅读本书来说是足够了,但是如果你没有进行过任何的 COM 编程实践,笔者还是建议你先阅读一本 COM 的教科书。1什么是 COM组件对象模型(Component Object Model,COM )对象是符合 COM 规范的可重用的软件组件。符合 COM 规范的 COM 对象相互之间可以很好地工作,并且可以很容易地集成到应用程序中。从应用的观点来看,一个 COM 对象就是一个黑箱,应用程序可以使用它来创建一项或多项任务。COM 对象常常用动态链接库(D
3、ynamic Link Libraries, DLLs)的形式来实现。与传统的DLL 一样,COM 对象暴露其方法,应用程序能调用这些方法来实现对象所支持的功能。应用程序与 COM 对象的关系就像应用程序与 C+对象的关系,但其中也存在一些区别。1)COM 对象执行严格的封装。你不能简单地创建一个对象就调用其中的公用方法。COM对象的公用方法聚合为一个或多个接口。为了使用一个方法,必须先创建一个对象,并从对象中获得相应的接口。一个接口一般包含一组方法,通过它们能使用对象的特定功能。不能通过接口来调用不属于该接口的方法。2) 创建 COM 对象的方法与创建 C+对象的方法不同。有多种方法可以创建
4、 COM 对象,但所有的方法都需要使用 COM 的细节技术。Speech SDK 应用程序编程接口(API )包括许多的帮助函数和方法,它们简化了创建大部分 Speech 对象的工作。3) 必须使用 COM 的细节技术来控制对象的生命期。4) COM 对象不需要明确地装载。COM 对象一般包含在 DLL 中。然而,与使用普通DLL 中的方法不同,使用 COM 对象时,不需要明确地装载 DLL 或链接静态库。每一个COM 对象都具有一个惟一的注册标识。用该标识来创建对象时,COM 将自动地装载正确的 DLL。5)COM 是一种二进制规范。COM 对象可用许多种编程语言来编写和调用。对于使用者来说
5、,并不需要了解对象的源程序的任何信息。比如,Microsoft Visual Basic 编写的应用程序可以很好地调用 C+编写的 COM 对象。下面先介绍 COM 的几个基本概念,包括对象与接口、GUID、HRESULT 类型和指针地址等。 (1)对象与接口理解对象与接口的区别是非常重要的。通常用其主要接口的名称来称呼对象。但是,严格地说,这两个术语是不能互换使用的。一个对象能暴露任何数量的接口。例如,所有的对象都必须暴露 IUnknown 接口,它们一般还暴露至少一个其他的接口,它们也可能暴露更多的接口。为了使用一个特定的方法,首先必须获得正确的接口指针。 多个不同的接口可以暴露同一个接口
6、。一个接口就是一组执行特定操作的方法。接口定义只是指定了方法的调用语法和它们的一般功能。任何需要支持一组特定操作的 COM 对象都可通过暴露一个合适的接口来实现这些特定的操作。有些接口非常专业,仅仅由单一的对象来暴露。有些接口在许多场合下都非常有用,它们可由许多的对象来暴露。最极端的例子是 IUnknown 接口,所有的 COM 对象都需要暴露它。如果一个对象暴露一个接口,它必须支持接口定义的每一个方法。但是,不同的对象实现一个特定方法的方式是不同的。不同的对象可能使用不同的算法来实现最后的结果。有时一个对象暴露一组通用的方法,但往往只需要支持其中的一部分方法。可是其他的未被支持的方法也需要能
7、被成功调用,只是它们都只返回 E_NOTIMPL。COM 标准要求接口一旦发布,其定义就不能再改变。例如,不能在一个已有的接口中增加一个新的方法,而必须创建一个新的接口。如果没有限制接口中必须有什么样的方法,通常的做法是在其下一代接口中包括原有接口的所有方法和其他的新方法。但是一个接口有几代版本的情况并不常见。通常所有的版本实现完全相同的功能,只是在具体实现细节上不同。一个对象经常暴露所有版本的接口。这样做使较老版本的应用程序能继续使用对象的较老版本的接口,而较新版本的应用程序能使用较新版本接口的强大功能。一般来说,同一家族的接口具有相同的名称,在名称后加一个整数来表示不同的版本。比如,原来的
8、接口叫做 IMyInterface,接下来的两代版本就分别叫做 IMyInterface2 和IMyInterface3。(2)GUID全球惟一标识符(Globally Unique Identifiers,GUIDs )是 COM 编程模型的关键部分。从本质上来说, GUID 是一个 128 位的结构。然而,GUID 在创建时必须保证不能出现两个相同的 GUID。 COM 在如下的两个方面广泛地使用 GUID:1)惟一地标识一个特定的 COM 对象。赋予一个 COM 对象的 GUID 叫做一个类标识(class ID,CLSID) 。当需要创建一个相关的 COM 对象的实例时,需要使用 CL
9、SID。2)惟一地标识一个特定的 COM 接口。与一个 COM 接口相关的 GUID 叫做接口标识(interface ID, IID) 。当从一个对象中请求一个特定接口时,需要使用 IID。不管哪个对象暴露接口,该接口的 IID 都是相同的。 为了叙述方便,在文档中经常使用对象和接口的描述名称来引用对象和接口。虽然这样做并不会在文档中引起混乱,但是,严格地说,并不能保证不会有两个或多个不同的对象或接口具有相同的描述名。惟一的不能引起歧义的方法是用 GUID 来引用对象或接口。虽然 GUID 是一种结构,但却经常表示为对应的字符串。最常用的 GUID 字符串形式是“xxxxxxxx-xxxx-
10、xxxx-xxxx-xxxxxxxxxxxx”, 其中 x 为一个十六进制整数。由于实际的 GUID 很长,并且很容易写错,所以一般还提供一个等价的名称。在调用CoCreateInstance 之类的函数时,可以使用这个名称而不使用实际的 GUID 结构。通常的命名惯例是分别在对象或接口的描述名称前加上 CLSID_或 IID_作为前缀。例如,ISpVoice接口的 CLSID 的名称是 CLSID_ISpVoice。(3)HRESULT 类型值所有的 COM 方法都返回一个 32 位的 HRESULT 类型的值。对大部分方法而言,HRESULT 本质上是一个包含独立的两部分信息的结构:1)方
11、法调用成功了还是失败了;2)关于方法所支持操作的结果的更详细信息。有些方法仅仅返回在 Winerror.h 中定义的标准的 HRESULT 类型值。然而,方法可以返回自己定义的 HRESULT 类型值,以提供更专用的信息。当然这样的自定义值一般会在方法的参考文档中说明。需要注意的是,参考文档可能不会列出标准的 HRESULT 类型值,但方法却可能返回标准值。虽然 HRESULT 类型值经常用来返回错误信息,但不要将它们看成错误码。由于说明成功或失败的位在包含详细信息的数据中是分别存储的,所以 HRESULT 类型值可以包含任何数量的成功和失败代码。作为一种惯例,成功码的名称具有 S_前缀,错误
12、码的名称具有E_前缀。比如,最常用的成功码和错误码分别是 S_OK 和 E_FAIL。由于 COM 方法可能返回许多不同的成功码或错误码,因此必须很小心地测试 HRESULT类型的值。例如,假设一个方法在文档中说明成功,返回 S_OK,不成功则返回 E_FAIL。这时,请注意,该方法可能还会返回其他的成功码或错误码。下面的代码段说明了使用简单测试的危险。/ hr 是该方法返回的 HRESULT 类型值if(hr = E_FAIL)/ 处理错误else/ 处理成功只有在该方法只返回 E_FAIL 来指示失败时,上面的测试才能正常工作。然而,该方法还可能返回一个 E_NOTIMPL 或 E_INV
13、ALIDARG 之类的错误值。上面的代码将会将它们解释为成功,这将可能导致程序的失败。如果需要关于方法运行结果的更详细的信息,必须测试每一个相关的 HRESULT 值。但经常只关心方法是成功的还是失败的。一种可靠的测试 HRESULT 类型值说明成功还是失败的方法是利用如下的宏来判断,这些宏定义在 Winerror.h 中。1)宏 SUCCEEDED 返回 TRUE 作为成功码,返回 FALSE 作为失败码;2)宏 FAILED 返回 TRUE 作为失败码,返回 FALSE 作为成功码;可以使用宏 FAILED 来修改上面的代码段:/ hr 是该方法返回的 HRESULT 类型值if(FAIL
14、ED(hr)/ 处理错误else/ 处理成功这段代码能合理地处理 E_NOTIMPL 和 E_INVALIDARG 之类的失败码。大多数的 COM 方法返回结构化的 HRESULT 类型值,只有很少数量的方法使用HRESULT 来返回简单的整数值,这类方法经常是成功的。如果将这类整数值传给宏SUCCESS,该宏将总是返回 TRUE。常用的例子是 IUnknown:Release 方法,它减少一次对象的引用计数并返回当前的引用计数。将在管理对象的生命期一节中讨论引用计数的问(4)指针地址阅读一些 COM 方法的参考文档时,经常看到如下的说明:HRESULT CreateDevice(IDirec
15、t3DDevice8 *ppReturnedDeviceInterface );C 或 C+开发人员熟悉普通的指针,但是 COM 经常使用另外的间接层。这种间接的第二层用两个星号(*)跟着类型声明来表示。变量名一般使用“pp”作为前缀。在上面的例子中,参数 ppReturnedDeviceInterface 表示指向 IDirect3DDevice8 接口的指针的地址。与 C+不同,不需要直接访问 COM 对象的方法,而必须获取指向方法的接口的指针。然后像调用指向 C+方法的指针一样来调用方法。例如,使用如下的语法来调用方法IMyInterface:DoSomething method:IMy
16、Interface *pMyIface;.pMyIface-DoSomething(.);这样做的原因是我们并不直接创建接口指针,而是必须调用不同的方法(例如上述的CreateDevice)来创建接口指针。为了使用这种方法来获取接口指针,应声明一个指向需要的接口的指针变量,并将该指针变量的地址,即一个指针的地址,传递给该方法。当该方法返回时,该变量将指向你要求的接口,可以使用该指针来调用接口的任何方法。将在使用 COM 接口一节中更详细地讨论接口指针的问题。2创建 COM 对象有多种方法可以创建 COM 对象。以下是在编程中最常用的两种方法。1)直接法: 将对象的 CLSID 传给 CoCre
17、ateInstance 函数。该函数将创建对象的一个实例,并返回指向你所指定接口的指针。2)间接法: 调用一个特殊的方法或函数来创建对象。这类方法创建对象并返回该对象的接口。使用这种方式来创建对象时,通常并不能指定需要返回的接口。 创建对象之前,必须调用 CoInitialize 函数来初始化 COM。如果使用间接法来创建对象,对象的创建方法将自动完成 COM 初始化。如果使用 CoCreateInstance 来创建对象,则必须明确地调用 CoInitialize。 当完成了所有的 COM 工作后,必须调用 CoUninitialize 来卸载 COM。如果调用了 CoInitialize,
18、则必须对应地调用一次 CoUninitialize。一般地说,需要明确,初始化 COM 的应用程序在其启动过程中初始化 COM,在其清除过程中卸载COM。用 CoCreateInstance 来创建一个 COM 对象的实例需要使用该对象的 CLSID。如果其CLSID 是公开的,可以在其参考手册或相应的头文件中找到。如果其 CLSID 不是公开的,则不能使用直接法来创建该对象。CoCreateInstance 函数有 5 个参数,一般可以按如下方式来设置其参数。1)rclsid:将该参数设为需要创建的对象的 CLSID。2)pUnkOuter:将该参数设为 NULL。只有在聚合对象时才需要使用
19、该参数。参见关于聚合的讨论。3)dwClsContext:将该参数设为 CLSCTX_INPROC_SERVER。该值说明对象是在DLL 中实现的,将作为你的应用程序进程的一部分来运行。4)riid :将该参数设为需要返回的接口的 IID。该函数将创建指定的对象,并通过参数ppv 返回所请求的接口指针。5)ppv:将该参数设为 riid 所指定的接口的指针地址。该变量应该声明为一个指向请求的接口的指针。在参数表中,该参数应被强制为(LPVOID *)类型。例如,下面的代码段创建了 ISpVoice 对象的一个新的实例,函数返回时, m_IpVoice 变量是指向 ISpVoice 接口的指针。
20、如果发生错误,程序将终止,并显示一个消息框。CComPtr m_IpVoice = NULL;HRESULT hr;hr = m_IpVoice.CoCreateInstance(CLSID_SpVoice);if (FAILED(hr) AfxMessageBox(“Error creating voice“);return FALSE;用间接法创建对象往往简单得多。只要将接口指针的地址传给对象的创建方法,该方法就会创建对象并返回接口指针。但间接地创建对象时,一般不能选择返回哪个接口。但是可以指定如何来创建对象。3IUnknown 接口所有的 COM 对象都支持一个叫做 IUnknown 的
21、接口。该接口提供了对对象的生命期的控制和检索对象的其他接口的方法。IUnknown 接口有以下 3 个方法。1)AddRef:当一个接口或另一个应用程序与对象绑定时,对对象的引用计数加 1。2)QueryInterface :查询对象所支持的功能,并请求指向指定的接口的指针。3)Release:对对象的引用计数减 1。当引用计数变为 0 时,对象将被释放。 AddRef 和 Release 方法维护对象的引用计数。例如,当创建一个对象时,该对象的引用计数变为 1。每次一个函数返回一个指向该对象的接口的指针时,该函数都必须调用AddRef 来增加其引用计数。AddRef 的每一次调用都必须与 R
22、elease 的调用相匹配。在指针被释放前必须对该指针调用 Release。当一个对象的引用计数变为 0 时,该对象将被释放,它的所有接口都将变为无效接口。QueryInterface 方法用来确定一个对象是否支持指定的接口。如果一个对象支持一个接口,QueryInterface 返回一个指向该接口的指针。然后就可以使用该接口的方法来与对象进行通信。如果 QueryInterface 成功地返回一个指向接口的指针,它将明确地调用 AddRef 来增加引用计数。因此在释放该接口的指针之前,应用程序必须调用 Release 来减少引用计数。4使用 COM 接口获得接口指针后,可以用该指针来访问接口
23、的任何方法。在许多情形中,从创建方法接收到的接口指针就是所需要的。实际上,只暴露一个除IUnknown 之外的接口的对象是很不常见的。相反,许多的对象暴露多个接口,需要指向这些接口的多个指针。如果需要创建方法所返回的接口之外的更多的接口,则并不需要再创建一个新的对象。可以使用对象的 IUnknown:QueryInterface 方法来请求其他接口的指针。如果使用 CoCreateInstance 来创建对象,则可以请求一个 IUnknown 接口指针,然后调用 IUnknown:QueryInterface 方法来请求需要的每一个接口。然而,当只需要一个接口时,这种方法显得很不方便。而且,如
24、果使用不允许指定哪个接口应该返回的创建方法时,这种方法更不能工作。在实践中,经常不需要获得一个明确的 IUnknown 指针,因为所有的COM 接口都是从 IUnknown 接口继承或扩展而来的。扩展一个接口很像继承一个 C+类。子接口暴露父接口的所有方法,并增加一个或多个自己的方法。事实上,经常看见“继承”而不是“扩展” 。需要记住的是,继承只能出现在对象的内部。应用程序不能继承或扩展另一个对象的接口,但是可以使用子接口来调用它自己或其父接口的方法。由于所有接口都是 IUnknown 的子接口,因此可以使用已经获得的任何该对象接口的指针来调用 QueryInterface。这样做时,需要提供
25、你请求的接口的 IID 和当方法返回时存放接口指针的指针。5管理 COM 对象的生命期当对象被创建时,系统将分配必需的内存资源。当一个对象不再需要时,应该删除它。系统将收回它所占有的内存,以用于其他目的。对于 C+对象,应直接使用 new 和 delete 操作符来控制对象的生命期。COM 不允许直接创建或删除对象。其原因是同一对象可能被多个应用程序所使用。如果其中的一个应用程序要删除该对象,其他的应用程序就可能失败。实际上,COM 采用引用计数系统来控制对象的生命期。对象的引用计数就是其中的接口被请求的次数。接口每被请求一次,其引用计数都将增加。当不再需要接口时,应用程序将释放该接口,并减少
26、其引用计数。只要引用计数大于 0,对象将保留在内存中。当引用计数变为 0 时,对象将释放自己。我们不必关心对象的引用计数,只需要正确地获取和释放对象的接口,对象将具有适当的生命期。合理地处理引用计数对 COM 编程来说是非常重要的。处理不当将导致内存泄漏。COM 编程人员最常见的错误是不释放接口。当出现这样的错误时,引用计数将永远不能变为 0,对象将不确定地保留在内存中。只要获得一个新的接口指针,引用计数就必须调用 IUnknown:AddRef 来增加。但是,应用程序通常不需要调用该方法。如果通过调用一个对象创建方法或IUnknown:QueryInterface 来获得接口指针,对象将自动
27、地增加引用计数。如果用其他的方法来创建接口指针,比如拷贝已有的指针,就必须明确地调用 IUnknown:AddRef。否则,当释放原来的接口指针时,你得到的对象可能被破坏,即使你还需要使用该指针的拷贝。不论明确地或对象自动地增加了引用计数,都必须释放接口指针。当不再需要接口指针时,调用 IUnknown:Release 来减少引用计数。一种常用的方法是,将所有的接口指针初始化为NULL,并在释放接口后将它们重新设为 NULL。这样可以在清除代码中测试所有的接口指针。那些不为 NULL 的接口指针就是仍然活动的,需要在退出应用程序之前释放它们。6用 C 来操作 COM 对象虽然 C+是最常用的
28、COM 编程语言,有些时候也需要使用 C 语言来访问 COM 对象。这样做更加直截了当,但也要用到更加复杂的语法。所有的方法都需要一个额外的参数,放在参数表的最前面。这个参数必须设为接口指针。而且,必须明确地引用对象的 vtable。 每个 COM 对象都有一个包含指向对象所暴露的方法的指针的列表。接口指针指向 vtable 的相应位置,该位置包含了指向你要调用的特定方法的指针。使用 C+时,不必关心vtable,因为它在 C+中是不可见的。当然,如果需要使用 C 来调用 COM 方法,就必须包括一个明确引用 vtable 的间接的额外层。有些组件在它们的头文件中定义了一些宏,它们能很好地解决
29、如何正确地调用 COM。7用 ATL 来处理 COM 接口如果使用活动模板库(Active Template Library,ATL)来处理 Microsoft Speech,为了兼容ATL,必须首先重新声明接口。这将允许你合理地使用 CComQIPtr 类来获取指向接口的指针。如果没有为 ATL 重新声明接口,则会得到下面的错误信息:C:Program FilesMicrosoft Visual StudioVC98ATLINCLUDEatlbase.h(566) :error C2787: ISpVoice : no GUID has been associated with this o
30、bject11.1.2 SAPI 接口SAPI 提供应用程序和语音引擎之间的高层接口,它实现并隐藏了控制和管理不同语音引擎的实时操作的底层技术细节。SAPI 的结构如图 11-1 所示。最基本的语音引擎包括 Text-To-Speech(TTS )和语音识别器。 TTS 通过合成声音来朗读文本字符串和文本文件。语音识别器将人类的语音转换成可阅读的字符串和文件。1Text-To-Speech API应用程序通过 ISpVoice 组件对象接口(Component Object Model Interface)来控制 Text-To-Speech (TTS)。一旦应用程序创建了 IspVoice
31、对象,调用接口 IspVoice 的方法ISpVoice:Speak,就能产生朗读指定的文字的声音。 IspVoice 接口还提供了其他一系列的方法来改变声音和其合成特征,比如控制语速的 ISpVoice:SetRate,控制输出音量的ISpVoice:SetVolume 和改变当前语音的 ISpVoice:SetVoice。在输入用于朗读的文字中还可插入一系列的特殊 SAPI 控制符,用来控制输出声音的实时合成特性,如语音、语调、重音、语速和音量等。合成标志文件 sapi.xsd 用来说明声音的合成特性。sapi.xsd 是一种标准 XML 格式文件,它与特定的引擎或当前正在使用的语音无关,
32、是一种简单而功能强大的定制 TTS 语音的方法。IspVoice:Speak 方法既能同步地(在语音播放完之后才返回)也能异步地(语音开始播放就返回,语音播放在后台处理)操作语音。 指定 SPF_ASYNC 作为播放方式时,语音异步播放。这时可调用 ISpVoice:GetStatus 方法来获取实时状态信息,如播放状态、当前播放的文字位置等。同时,既可以打断当前的播放而立即播放新的文字(需指定SPF_PURGEBEFORESPEAK 参数) ,也可以在播放完当前文字之后再自动播放新的文字。除了 IspVoice 接口以外, SAPI 还提供了许多有用的 COM 接口来实现高级的 TTS 应用
33、程序。(1)事件(Events)SAPI 通过使用标准的回调机制(窗口消息、回调函数或 Win32 事件)来与应用程序传送事件。对于 TTS,事件主要用来同步语音输出。应用程序能同步处理语音输出和实时动作,比如词语分界、音位或嘴形动画分界及应用程序定制的分界等。应用程序可通过调用IspNotifySource,IspNotifySink,IspNotifyTranslator,IspEventSink,IspEventSource 和ISpNotifyCallbackcan 来初始化和处理这些实时事件。(2)词典(Lexicons)通过调用 IspContainerLexicon,IspLex
34、icon 和 IspPhoneConverter 接口提供的方法,应用程序能为语音合成引擎设置定制的词汇发音。(3)资源(Resources)下面的 COM 接口用于处理 SAPI 语音数据(比如声音文件和发音词典):IspDataKey,IspRegDataKey , IspObjectTokenInit,IspObjectTokenCategory ,IspObjectToken,IenumSpObjectTokens,IspObjectWithToken,IspResourceManager 和 IspTask。(4)声音(Audio)SAPI 还提供了定制声音输出到特定目标(如电话和客
35、户硬件)的接口,包括IspAudio,ISpMMSysAudio,IspStream,IspStreamFormat 和 IspStreamFormatConverter。2语音识别 API正如 IspVoice 是主要的语音合成接口一样, IspRecoContext 是语音识别的主要接口。与IspVoice 一样,它也是一种 ISpEventSource 接口,提供了为请求的语音识别事件接收通知消息的基本载体。有两种不同的语音识别引擎(ISpRecognizer) ,即共享语音识别引擎( shared speech recognition engine)和进程内语音识别引擎(InProc
36、speech recognition engine) 。应用程序可以选择其中的一种。一般推荐使用共享语音识别引擎,这种引擎能被多个应用程序共享。创建共享IspRecognizer 的 IspRecoContext 接口很简单,应用程序只需指定参数为组件的CLSID_SpSharedRecoContext 并调用 COM 的 CoCreateInstance 函数即可。这时,SAPI 将设置音频输入流为 SAPI 的默认音频输入流。对于单独运行于一个系统中的大型服务器应用程序,其运行效率是很重要的。这时使用进程内语音识别引擎更合适。使用进程内语音识别引擎有 3 个步骤:首先,应用程序需指定参数为
37、组件的 CLSID_ SpInprocRecoInstance 并调用 COM 的 CoCreateInstance 函数来创建其自己的进程内语音识别 IspRecognizer;其次,应用程序需调用 ISpRecognizer:SetInput 方法(参见 IspObjectToken 接口的说明)来设置音频输入流;最后,应用程序可调用ISpRecognizer:CreateRecoContext 来获取 IspRecoContext 接口。下一步需要为应用程序感兴趣的事件设置通知消息。IspRecognizer 也是一种IspEventSource 接口,自然是一种 IspNotifySo
38、urce 接口,因此,应用程序能够从其IspRecoContext 接口中调用 IspNotifySource 的方法来指定 IspRecoContext 所需的消息应通知到何处。调用 ISpEventSource:SetInterest 方法可以设定什么样的事件需要被通知。最重要的事件是 SPEI_RECOGNITION,它标识了 IspRecognizer 已从 IspRecoContext 中识别了一些语音。Speech SDK 文档中 SPEVENTENUM 的说明提供了其他语音识别事件的详细说明。最后需要说明的是,应用程序必须创建、装载并激活一个 IspRecoGrammar 接口。
39、该接口从本质上说明了什么语音类型,即口述或命令和控制语法。应用程序首先应调用ISpRecoContext:CreateGrammar 方法创建一个 IspRecoGrammar 接口。然后装载合适的语法,调用 ISpRecoGrammar:LoadDictation 方法可装载口述语法,调用ISpRecoGrammar:LoadCmdxxx 方法可装载命令和控制语法。最后,为了激活语法并启动识别,应用程序应该调用 ISpRecoGrammar:SetDictationState 方法设置口述状态,或者调用ISpRecoGrammar:SetRuleState 方法或 ISpRecoGramma
40、r:SetRuleIdState 方法设置命令和控制状态。当应用程序通过请求的通知机制得到通知消息时,SPEVENT 结构的 lParam 成员包含了一个 IspRecoResult 接口,应用程序能从中确定用 IspRecoContext 中的哪个 IspRecoGrammar接口已识别了什么语音。 无论共享的还是进程内的 IspRecognizer 接口都能拥有多个与其关联的 IspRecoContexts 接口,并且每一个接口都能通过自己的事件通知方式得到相应的消息。可以从一个IspRecoContext 接口中创建多个 IspRecoGrammars 接口,不同的接口可用于识别不同的语
41、音类型。11.1.3 安装 Speech SDK进行 Text-To-Speech 编程之前,必须先下载 Miscrosoft Speech SDK,并将它安装到你的系统中。Miscrosoft Speech SDK 的下载网址是 http:/ Speech SDK 版本是 5.1 版。下载的 speechsdk51.exe 是一个可执行的文件包压缩文件。运行它,将安装文件释放到一个临时目录中,执行其中的 Microsoft Speech SDK 5.1.msi,将 Speech SDK 安装到相应的目录中。一般选用默认的安装目录(C:Program FilesMicrosoft Speech
42、 SDK 5.1) 。Speech SDK 支持的默认语言是英语,即安装 Speech SDK 后,系统还只能支持英语的语音。要使系统支持中文和日文语音,还需要下载安装相应的语言包。从相同的网址中下载语言包 speechsdk51LangPack.exe。运行它,将安装文件释放到一个临时目录中,执行其中的Microsoft Speech SDK 5.1 Language Pack.msi,将中、日文支持安装到系统中。安装好 Speech SDK 后,语音控制程序将被添加到系统的控制面板中。利用该控制程序可以设置语音识别和文字-语音转换的各项属性,包括语言 /语音、语速和输入设备等,如图11-2
43、 所示。 至 此 已 做 好 了 编 写 语 音 程 序 的 准 备 工 作 , 可 以 开 始 编 写 语 音 程 序 了 。 下 面 首 先 介 绍文本- 语音转换的 编 程 技 术 。11.2.1 构造 CText2Speech 类为了便于使用 Speech SDK 提供的文本- 语音转换 COM 接口,笔者编写了一个类CText2Speech,其中封装了文本- 语音转换 COM 接口的基本方法。借助该类来编写文本-语音转换程序非常方便。先来讨论该 CText2Speech 类的设计,其定义文件列举如下:/ active speech engine/#include extern CCo
44、mModule _Module;#include #include “sapi.h“#include / speech message/#define WM_TTSEVENT WM_USER+101/ text-to-speech class/class CText2Speech public:CText2Speech();virtual CText2Speech();/ initializeBOOL Initialize(HWND hWnd = NULL);void Destroy();/ speakHRESULT Speak(const WCHAR *pwcs, DWORD dwFlags
45、 = SPF_DEFAULT);HRESULT Pause();HRESULT Resume();/ rateHRESULT SetRate(long lRateAdjust);HRESULT GetRate(long* plRateAdjust);/ volumeHRESULT SetVolume(USHORT usVolume);HRESULT GetVolume(USHORT* pusVolume);/ voiceULONG GetVoiceCount();HRESULT GetVoice(WCHAR *ppszDescription, ULONG lIndex = -1);HRESUL
46、T SetVoice(WCHAR *ppszDescription);/ error stringCString GetErrorString()return m_sError;/ interfaceCComPtr m_IpVoice;private:CString m_sError;文件的开始几行语句:#include extern CComModule _Module;#include #include “sapi.h“#include 用于使我们的代码能操作 Speech SDK 中的相关的接口、函数和常量。Speech SDK 支持事件。为了与窗口交互,这里在类中定义了消息WM_TTS
47、EVENT。当发生 Speech 事件时,向相应的窗口发送 WM_TTSEVENT 消息。在窗口中响应该消息就响应了相应的事件。CText2Speech 类中定义了一个操作 Text-To-Speech 引擎的接口指针m_IpVoice,作为数据成员,其定义如下:CComPtr m_IpVoice;几乎所有的 Text-To-Speech 操作都是借助该指针来调用 IspVoice 接口的方法而实现的。CText2Speech 类实现了如下的方法:/ 初始化和释放函数BOOL Initialize(HWND hWnd = NULL);void Destroy();/ 语音操作函数HRESULT
48、 Speak(const WCHAR *pwcs, DWORD dwFlags = SPF_DEFAULT);HRESULT Pause();HRESULT Resume();/ 语速函数HRESULT SetRate(long lRateAdjust);HRESULT GetRate(long* plRateAdjust);/ 音量函数HRESULT SetVolume(USHORT usVolume);HRESULT GetVolume(USHORT* pusVolume);/ 语言函数ULONG GetVoiceCount();HRESULT GetVoice(WCHAR *ppszDescription, ULONG lIndex = -1);HRESULT SetVoice(WCHAR *ppszDesc);/ 获取错误信息函数CString GetErrorString()CText2Speech 类的构造函数用于初始化 Text-To-Speech 引擎接口指针m_IpVoice 和错误字符串;析构函数则调用释放引擎的 Destroy()函数释放语音引擎,其代码如下:CText2Speech:CText2Speech()m_IpVoice