转自大楚网,微软TTS语音引擎编程入门
分类:面向对象

一、SAPI简介

1.首先开发得需要Microsoft Speech SDK的支持,以下是下载地址

原文链接地址:http://www.jizhuomi.com/software/135.html

【IT168专稿】“没声音,再好的戏也出不来。”这虽然是一句广告,但是也说出了一个道理,我们所开发的软件,特别是一些多媒体软件,要是能够发 出声音,能说会道,将为我们的软件增添不少光彩。同时,我们面临的是一个老龄化的社会,将会有越来越多的视力不太好的老年人成为我们的用户,开始使用我们 的软件,如果我们的软件能说会道,可以用语音的方式提示用户进行操作,这将大大增加软件的可用性,从而获得用户的喜爱。

软件中的语音技术包括两方面的内容,一个是语音识别(speech recognition) 和语音合成(speech synthesis)。这两个技术都需要语音引擎的支持。微软推出的应用编程接口API,虽然现在不是业界标准,但是应用比较广泛。

     
  Speech  SDK   5.1   (68   MB)    
     
  5.1   Language   Pack   (81.5   MB)    
     
  Redistributables   (128   MB)    
     
  Documentation   (2.28   MB)    
 

  我们都使用过一些某某词霸的英语学习工具软件,它们大多都有朗读的功能,其实这就是利用的Windows的TTS(Text To Speech)语音引擎。它包含在Windows Speech SDK开发包中。我们也可以使用此开发包根据自己的需要开发程序。鸡啄米下面对TTS功能的软件开发过程进行详细介绍。

  那么如何才能让我们的软件能说会道呢?别着急,微软有解决办法,用微软提供的SAPI就可以让我们的软件能说会道。

SAPI全称 The Microsoft Speech API.相关的SR和SS引擎位于Speech SDK开发包中。这个语音引擎支持多种语言的识别和朗读,包括英文、中文、日文等。

2.下载后,执行安装

一.SAPI SDK的介绍

  什么是SAPI?

SAPI包括以下组件对象(接口):

下载完毕后首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,然后展开
speechsdk51MSM.exe,这些都是自解压文件,解压后执行相应的setup程序到你要的目录,默认C:/Microsoft Speech SDK 5.1.对应的开发参考手册为sapi.chm,详细描述了各个函数的细节等.

       SAPI,全称是The Microsoft Speech API。就是微软的语音API。由Windows Speech SDK提供。

  软件中的语音技术主要包括两方面的内容,一个是语音识别(speech recognition) ,另外一个是语音合成(speech synthesis),也即是文本语音转换系统(TTS)。TTS系统使用合成语音合成文本字符串和文件到声音音频流。而语音识别系统则是转换人类的声音 语音流到可读的文本字符串或者文件。这两个工作,都是通过各种语音引擎来完成的。微软所提供的SAPI (全称The Microsoft Speech API),正是在应用程序和语音引擎之间提供一个高级别的接口,它实现了所有必需的对各种语音引擎的实时的控制和管理等低级别的细节。语音引擎通过DDI 层(设备驱动接口)和SAPI进行交互,应用程序通过API层和SAPI通信。通过使用这些API,我们可以快速开发在语音识别或语音合成方面应用程序。 SAPI 应用程序编程接口(API)明显的减少了构建一个使用语音识别和文本语音转换的应用程序所需要的高层代码,使语音技术更加容易使用并且更加扩大了应用的范 围。虽然现在SAPI不是业界标准,但是应用非常广泛。

(1)Voice Commands API。对应用程序进行控制,一般用于语音识别系统中。识别某个命令后,会调用相关接口是应用程序完成对应的功能。如果程序想实现语音控制,必须使用此组对象。
(2)Voice Dictation API。听写输入,即语音识别接口。
(3)Voice Text API。完成从文字到语音的转换,即语音合成。
(4)Voice Telephone API。语音识别和语音合成综合运用到电话系统之上,利用此接口可以建立一个电话应答系统,甚至可以通过电话控制计算机。
(5)Audio Objects API。封装了计算机发音系统。

3.VC的环境配置

       Windows Speech SDK包含语音识别SR引擎和语音合成SS引擎两种语音引擎。语音识别引擎用于识别语音命令,调用接口完成某个功能,实现语音控制。语音合成引擎用于将文字转换成语音输出。

  SAPI包括以下组件对象(接口):

SAPI是架构在COM基础上的,微软还提供了ActiveX控件,所以不仅可用于一般的windows程序,还可以用于网页、VBA甚至EXCEL的图表中。如果对COM感到陌生,还可以使用微软的C++ WRAPPERS,它用C++类封装了语音SDK COM对象。

在应用SDK的开发前当然得需要对工程环境进行配置,我用的是VS2003(其他情况类似),配置的过程如下:

       SAPI包括以下几类接口:Voice Commands API、Voice Dictation API、Voice Text API、Voice Telephone API和Audio Objects API。我们要实现语音合成需要的是Voice Text API。

  (1)Voice Commands API。对应用程序进行控制,一般用于语音识别系统中。识别某个命令后,会调用相关接口是应用程序完成对应的功能。如果程序想实现语音控制,必须使用此组对象。

二、安装SAPI SDK。

工具->选项->项目->VC++目录,在"显示以下内容的目录"下拉框中选择"包含目录"项,添加一项C:/Program   Files/Microsoft   Speech   SDK   5.1/Include到目录中去。再选择"库文件"项,添加一项C:/Program   Files/Microsoft   Speech   SDK   5.1/Lib/i386到目录中去.

       目前最常用的Windows Speech SDK版本有三种:5.1、5.3和5.4。

  (2)Voice Dictation API。听写输入,即语音识别接口。

首先从这个站点下载开发包:http://www.microsoft.com/speech/download/sdk51

4.其他准备项

       Windows Speech SDK 5.1版本支持xp系统和server 2003系统,需要下载安装。XP系统默认只带了个Microsoft Sam英文男声语音库,想要中文引擎就需要安装Windows Speech SDK 5.1。

  (3)Voice Text API。完成从文字到语音的转换,即语音合成。

Microsoft Speech SDK 5.1添加了Automation支持。所以可以在VB,ECMAScript等支持Automation的语言中使用。

基础的配置已经完成,那么接下来的工作就是要包含编译的头文件了,所以先将头文件和库文件包含进来

       Windows Speech SDK 5.3版本支持Vista系统和Server 2008系统,已经集成到系统里。Vista和Server 2003默认带Microsoft lili中文女声语音库和Microsoft Anna英文女声语音库。

  (4)Voice Telephone API。语音识别和语音合成综合运用到电话系统之上,利用此接口可以建立一个电话应答系统,甚至可以通过电话控制计算机。

版本说明:
Version: 5.1
发布日期: 8/8/2001
语音: English
下载尺寸: 2.0 MB - 288.8 MB

#include <sapi.h>
#include <sphelper.h>
#include <spuihelp.h>

       Windows Speech SDK 5.4版本支持Windows7系统,也已经集成到系统里,不需要下载安装。Win7系统同样带了Microsoft lili中文女声语音库和Microsoft Anna英文女声语音库。Microsoft lili支持中英文混读。

  (5)Audio Objects API。封装了计算机发音系统。

这个SDK开发包还包括了可以随便发布的英文和中文的语音合成引擎(TTS),和英文、中文、日文的语音识别引擎(SR)。

#pragma comment(lib,"ole32.lib")   //CoInitialize CoCreateInstance需要调用ole32.dll
#pragma comment(lib,"sapi.lib")    //sapi.lib在SDK的lib目录,必需正确配置

二.SAPI SDK的下载和安装

  其中Voice Text API,就是微软TTS引擎的接口,通过它我们可以很容易地建立功能强大的文本语音程序,金山词霸的单词朗读功能就用到了这些API,而目前几乎所有的文 本朗读工具都是用SAPI开发的。在这里,我们使用的主要就是Voice Text API。

系统要求98以上版本。编译开发包中的例子程序需要vc6以上环境。

具体其他函数所需要的头文件可参考sapi.chm手册.

       如果是在XP系统下进行开发则需要下载Microsoft Speech SDK 5.1,下载地址为:。

  安装SAPI SDK

******下载说明******:
(1)如果要下载例子程序,说明文档,SAPI以及用于开发的美国英语语音引擎,需要下载SpeechSDK51.exe,大约68M。
(2)如果想要使用简体中文和日文的语音引擎,需要下载SpeechSDK51LangPack.exe。大约82M。
(3)如果想要和自己的软件一起发布语音引擎,需要下载SpeechSDK51MSM.exe,大约132M。
(在这个地址,我未能成功下载)。
(4)如果要获取XP下的 Mike 和 Mary 语音,下载Sp5TTIntXP.exe。大约3.5M。
(5)如果要获取开发包的文档说明,请下载sapi.chm。大约2.3M。这个在sdk51里面已经包含。

5.源文件修改项

       根据微软下载说明,有几点需要注意:

  要使用SAPI让我们的软件能说会道,我们首先需要下载并安装SAPI SDK。首先从微软的网站上下载开发包:

下载完毕后,首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,然后展开
msttss22l,自动将所需dll安装到系统目录。

看上去上面的部分配置完成后就大功告成了,其实还不全是,当你编译时就会出错:

       1.如果你想下载例子程序、文档、SAPI和用于开发的美国英文语音引擎,请下载SpeechSDK51.exe。
       2.如果你想使用日文和简体中文引擎用于开发,请下载SpeechSDK51.exe和SpeechSDK51LangPach.exe。
       3.如果你想将语音引擎集成到你的产品跟产品一起发布,就下载SpeechSDK51MSM.exe。
       4.如果你仅想获得XP系统下的Mike和Mary语音,就下载Sp5TTIntXP.exe。
       5.如果你只想要文档请下载sapi.chm。

  下载完毕后,首先安装SpeechSDK51.exe,然后安装中文语言补丁包SpeechSDK51LangPack,If 如果我们想将SAPI作为我们软件的一部分,随着我们的软件重新发布,我们还需要安装SpeechSDK51MSM.exe。

三、配置vc环境

c:/program files/microsoft speech sdk 5.1/include/sphelper.h(769) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int

       下载完成后可先安装引擎SpeechSDK51.exe,再安装中文语言补丁包SpeechSDK51LangPach.exe,这样就可以使用其中的中文男声语音库了。如果想要在Vista或Win7系统下使用Mike、Mary和Microsoft Simplified Chinese中文男声语音库也可以下载相应的文件安装。

  安装好SAPI SDK后,即可开始在VS2010中使用SAPI让我们的软件能说会道了。

在vc6.0的环境下编译语音工程,首先要配置编译环境。假设sdk安装在d:Microsoft Speech SDK 5.1路径下,打开工程设置对话框,在c/c++栏中选择Preprocessor分类,然后在"附加包含路径"中输入
d:Microsoft Speech SDK 5.1include
告诉vc编译程序所需的SAPI头文件的位置。
然后切换到LINK栏,在Input分类下的附加库路径中输入:
d:Microsoft Speech SDK 5.1libi386
使vc在链接的时候能够找到sapi.lib。

c:/program files/microsoft speech sdk 5.1/include/sphelper.h(1419) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2373) : error C2065: 'psz' : undeclared identifier

三.VC++环境配置

  创建项目,添加SAPI的引用

四、语音合成的应用。即使用SAPI实现TTS(Text to Speech)。

c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2559) : error C2440: 'initializing' : cannot convert from 'CSpDynamicString' to 'SPPHONEID *'
 No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
c:/program files/microsoft speech sdk 5.1/include/sphelper.h(2633) : error C2664: 'wcslen' : cannot convert parameter 1 from 'SPPHONEID *' to 'const wchar_t *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

       如果是在XP系统下开发,先安装SpeechSDK51.exe再安装SpeechSDK51LangPach.exe,假设安装路径为默认的C:Program FilesMicrosoft Speech SDK 5.1,则接下来需要配置VC++,以VS2010为例,在Solution Exporer中的工程名上点右键,在右键菜单中选择properties,弹出Property Pages对话框,然后在左侧树中选择节点”VC++Directories“,在右侧列表的Include Directories处输入”C:Program FilesMicrosoft Speech SDK 5.1Include“,Library Directories处输入”C:Program FilesMicrosoft Speech SDK 5.1libi386“。VS2005、VS2008和VC6.0可以按照各自的方法设置。

  这里,我们将创建一个普通的WinForm程序,它可以利用TTS朗读文本,也可以将文本文件通过TTS转换为声音文件,真正是一个“能说会道”的软件。首先,我们在VS2010中创建一个WinForm程序,并且将窗体设计如下:

1、首先要初始化语音接口,一般有两种方式:
ISpVoice* pVoice;
::CoInitialize(NULL);
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice,
(void **)&pVoice);
然后就可以使用这个指针调用SAPI函数了,例如
pVoice->SetVolume(50);//设置音量
pVoice->Speak(str.AllocSysString(),SPF_ASYNC,NULL);

Speech代码编写时间太早,语法不严密。而VS2003(及以上)对于语法检查非常严格,导致编译无法通过。修改头文件中的以下行即可正常编译:

       最后,在程序中使用语音引擎以前包含头文件和lib库:

  中的Text Box用于显示我们要阅读的文本,而Combo Box控件用于显示系统中已经安装的所有语音,用户可以从中选择当前TTS使用的语音。

另外也可以使用如下方式:
CComPtr<ISpVoice> m_cpVoice;
HRESULT hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
在下面的例子中都用这个m_cpVoice变量。

Line 769

#include "sapi.h"
#include "sphelper.h"
#pragma comment(lib, "sapi.lib")

  要在我们的项目中使用SAPI,我们还需要给项目添加SAPI的引用。用VS2010提供的添加引用功能,在添加引用对话框的COM标签页面中找到Microsoft Speech Object Library,将其添加到项目中。

CLSID_SpVoice的定义位于SPAI.H中。

   修改前: const ulLenVendorPreferred = wcslen(pszVendorPreferred);
   修改后: const unsigned long ulLenVendorPreferred = wcslen(pszVendorPreferred);

       如果是在Vista或者Win7系统中开发的话,因为头文件和lib库所在路径已默认附加到编译器了,所以不需手动添加,直接在程序中包含头文件和lib库即可。

  SAPI所提供的各个类都在名字空间SpeechLib之下,所以在代码中我们还需要使用using SpeechLib,表示我们将使用这个名字空间。这样我们就可以使用SAPI所提供的各种类来实现语音合成或者是语音识别了。

2、获取/设置输出频率。

Line 1418

四.SAPI接口的使用说明

  创建SpVoice对象,初始化SAPI

SAPI朗读文字的时候,可以采用多种频率方式输出声音,比如:
8kHz 8Bit Mono、8kHz 8Bit Stereo、44kHz 16Bit Mono、44kHz 16Bit Stereo等。在音调上有所差别。具体可以参考sapi.h。

    修改前: static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)

  1.基本朗读过程的实现

  SAPI的TTS都是通过SpVoice对象来完成的。SpVoice类是支持语音合成(TTS)的核心类。通过SpVoice对象调用TTS引擎,从而实现朗读功能。 SpVoice类有以下主要属性:

可以使用如下代码获取当前的配置:
CComPtr<ISpStreamFormat> cpStream;
HRESULT hrOutputStream = m_cpVoice->GetOutputStream(&cpStream);
if (hrOutputStream == S_OK)
{
CSpStreamFormat Fmt;
hr = Fmt.AssignFormat(cpStream);
if (SUCCEEDED(hr))
{
SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum();
}
}
SPSTREAMFORMAT 是一个ENUM类型,定义位于SPAI.H中。每一个值对应了不同的频率设置。例如 SPSF_8kHz8BitStereo = 5

    修改后: static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)

       在使用语音引擎之前进行初始化:

  ? Voice:表示发音类型,相当于进行朗读的人,通常我们可以通过安装相应的语音引擎来增加相应的语音。

通过如下代码设置当前朗读频率:
CComPtr<ISpAudio> m_cpOutAudio; //声音输出接口
SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &m_cpOutAudio ); //创建接口

Line 2372

ISpVoice *pSpVoice;        // 重要COM接口
::CoInitialize(NULL);         // COM初始化

// 获取ISpVoice接口
CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&pSpVoice);

  ? Rate:语音朗读速度,取值范围为-10到+10。数值越大,速度越快。

SPSTREAMFORMAT eFmt = 21; //SPSF_22kHz 8Bit Stereo

    修改前: for (const WCHAR * psz = (const WCHAR *)lParam; *psz; psz++) {}

       获取到ISpVoice接口以后,我们就可以通过pSpVoice指针调用SAPI接口了。

  ? Volume:音量,取值范围为0到100。数值越大,音量越大。

CSpStreamFormat Fmt;
Fmt.AssignFormat(eFmt);
if ( m_cpOutAudio )
{
hr = m_cpOutAudio->SetFormat( Fmt.FormatId(), Fmt.WaveFormatExPtr() );
}
else hr = E_FAIL;

    修改后: const WCHAR * psz; for (psz = (const WCHAR *)lParam; *psz; psz++) {}

       我们可以设置音量:pSpVoice->SetVolume(80);。SetVolume的参数即音量的范围在0到100之间。

  SpVoice有以下主要方法:

if( SUCCEEDED( hr ) )
{
m_cpVoice->SetOutput( m_cpOutAudio, FALSE );
}

Line 2559

       可以这样朗读字符串内容:pSpVoice->Speak(string, SPF_DEFAULT, NULL);。这样string里的内容就会被朗读出来了,第二个参数SPF_DEFAULT表示使用默认设置,包括同步朗读的设置。异步朗读可以设置成SPF_ASYNC。同步朗读表示读完string中的内容,speak函数才会返回,而异步朗读则将字符串送进去就返回,不会阻塞。

  ? Speak():完成将文本信息转换为语音并按照指定的参数进行朗读,该方法有Text和Flags两个参数,分别指定要朗读的文本和朗读方式(同步或异步等)。

3、获取/设置播放所用语音。

    修改前: SPPHONEID* pphoneId = dsPhoneId;

       使用完语音引擎后应执行:

  ? GetVoices():获取系统中的语音,用于指定SpVoice的Voice属性。

引擎中所用的语音数据文件一般保存在SpeechEngines下的spd或者vce文件中。安装sdk后,在注册表中保存了可用的语音,比如英文的男/女,简体中文的男音等。位置是:
HKEY_LOCAL_MACHINESoftwareMicrosoftSpeechVoicesTokens
如果安装在中文操作系统下,则缺省所用的朗读语音是简体中文。SAPI的缺点是不能支持中英文混读,在朗读中文的时候,遇到英文,只能逐个字母读出。所以需要程序自己进行语音切换。

    修改后: SPPHONEID* pphoneId = (SPPHONEID*)((WCHAR *)dsPhoneId);
Line 2633
    修改前: pphoneId += wcslen(pphoneId) + 1;
    修改后: pphoneId += wcslen((const wchar_t *)pphoneId) + 1;

pSpVoice->Release();
::CoUninitialize();

  ? Pause():暂停使用该对象的所有朗读进程。该方法没有参数。

(1) 可以采用如下的函数把当前SDK支持的语音填充在一个组合框中:
// SAPI5 helper function in sphelper.h
HWND hWndCombo = GetDlgItem( hWnd, IDC_COMBO_VOICES ); //组合框句柄
HRESULT hr = SpInitTokenComboBox( hWndCombo , SPCAT_VOICES );
这个函数是通过IEnumSpObjectTokens接口枚举当前可用的语音接口,把接口的说明文字添加到组合框中,并且把接口的指针作为LPARAM
保存在组合框中。
一定要记住最后程序退出的时候,释放组合框中保存的接口:
SpDestroyTokenComboBox( hWndCombo );
这个函数的原理就是逐个取得combo里面每一项的LPARAM数据,转换成IUnknown接口指针,然后调用Release函数。
(2) 当组合框选择变化的时候,可以用下面的函数获取用户选择的语音:
ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo );

好了,编译通过,下面可以正式编写程序了。
6.SAPI实现TTS(Text to Speech)

       这样资源被释放,语音朗读过程结束。

  ? Resume():恢复该对象所对应的被暂停的朗读进程。该方法没有参数。

(3) 用下面的函数获取当前正在使用的语音:
CComPtr<ISpObjectToken> pOldToken;
HRESULT hr = m_cpVoice->GetVoice( &pOldToken );
(4) 当用户选择的语音和当前正在使用的不一致的时候,用下面的函数修改:
if (pOldToken != pToken)
{
// 首先结束当前的朗读,这个不是必须的。
HRESULT hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);
if (SUCCEEDED (hr) )
{
hr = m_cpVoice->SetVoice( pToken );
}
}
(5) 也可以直接使用函数SpGetTokenFromId获取指定voice的Token指针,例如:
WCHAR pszTokenId[] = L"HKEY_LOCAL_MACHINE\Software\Microsoft\Speech\Voices\Tokens\MSSimplifiedChineseVoice";
SpGetTokenFromId(pszTokenID , &pChineseToken);

    1.  首先要初始化语音接口,一般有两种方式:
         ISpVoice* pVoice;
         ::CoInitialize(NULL);
         HRESULT hr =CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL,IID_ISpVoice, (void **)&pVoice);
         然后就可以使用这个指针调用SAPI函数了,例如
        pVoice->SetVolume(50);//设置音量
        pVoice->Speak(str.AllocSysString(),SPF_ASYNC,NULL);

       以上就完成了一个简单的语音合成朗读的功能。

  所以我们在窗体的构造函数中,首先需要完成SpVoice对象的创建,然后才能使用这个对象来朗读文本。 因为系统中可能有多个语音可供选择,所以我们在创建窗体的时候,同时需要用一个Combo Box控件列举出系统中所有的语音,并且选中默认的第一个语音。当窗体创建后,用户可以在这个Combo Box选择自己喜欢的语音来朗读文本。

4、开始/暂停/恢复/结束当前的朗读

   另外也可以使用如下方式:
   CComPtr<ISpVoice>  m_cpVoice;
   HRESULT  hr = m_cpVoice.CoCreateInstance(CLSID_SpVoice );
   在下面的例子中都用这个m_cpVoice变量。CLSID_SpVoice的定义位于sapi.h中。

2.ISpVoice的成员函数

  

要朗读的文字必须位于宽字符串中,假设位于szWTextString中,则:
开始朗读的代码:
hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );
如果要解读一个XML文本,用:
hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_XML, 0 );

  • 2.  获取/设置输出频率。

       鸡啄米再简单说明几个ISpVoice接口的成员函数:

  // SpVoice对象,我们将使用这个对象来朗读文本

暂停的代码: m_cpVoice->Pause();
恢复的代码: m_cpVoice->Resume();
结束的代码:(上面的例子中已经给出了)
hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);

  SAPI朗读文字的时候,可以采用多种频率方式输出声音,比如:8kHz 8Bit Mono、8kHz 8BitStereo、44kHz 16BitStereo等,在音调上有所差别。具体可以参考sapi.h。

HRESULT Speak(LPCWSTR *pwcs, DWORD dwFlags, ULONG *pulStreamNumber);
//用于读取字符串pwcs里的内容。参数pwcs为要朗读的字符串。dwFlags是用于控制朗读方式的标志,具体意义可以查看文档中的枚举SPEAKFLAGS。pulStreamNumber为输出参数,它指向本次朗读请求对应的当前输入流编号,每次朗读一个字符串时都会有一个流编号返回,异步朗读时使用。

HRESULT SetRate( long   RateAdjust);         // 设置朗读速度,取值范围:-10到10
HRESULT GetRate(long *pRateAdjust);        // 获取朗读速度   

HRESULT SetVoice(ISpObjectToken   *pToken);    // 设置使用的语音库
HRESULT GetVoice(ISpObjectToken** ppToken);  // 获取语音库

HRESULT Pause ( void );                              // 暂停朗读
HRESULT Resume ( void );                          // 恢复朗读 

//  在当前朗读文本中根据lNumItems的符号向前或者向后跳过指定数量(lNumItems的绝对值)的句子。
HRESULT Skip(LPCWSTR  *pItemType, long  lNumItems, ULONG *pulNumSkipped);

//  播放WAV文件
HRESULT SpeakStream(IStream   *pStream, DWORD      dwFlags, ULONG     *pulStreamNumber);

// 将声音输出到WAV文件
HRESULT SetOutput(IUnknown *pUnkOutput,BOOL fAllowFormatChanges); 

HRESULT SetVolume(USHORT usVolume);      // 设置音量,范围:0到100
HRESULT GetVolume(USHORT *pusVolume);  // 获取音量

HRESULT SetSyncSpeakTimeout(ULONG msTimeout);      // 设置同步朗读超时时间,单位为毫秒
HRESULT GetSyncSpeakTimeout(ULONG *pmsTimeout);  // 获取同步朗读超时时间
//因为在同步朗读时,speak函数是阻塞的,如果语音输出设备被其他程序占用,则speak则会一直等待,所以最好设置好超时时间,超时后speak函数自行返回。

  private SpVoice m_spVoice;

5、跳过部分朗读的文字

   可以使用如下代码获取当前的频率配置:
  CComPtr<ISpStreamFormat> cpStream;
   HRESULT hrOutputStream =m_cpVoice->GetOutputStream(&cpStream);
   if (hrOutputStream ==S_OK)
   {
      CSpStreamFormat Fmt;
      hr = Fmt.AssignFormat(cpStream);
      if (SUCCEEDED(hr))
      {
          SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum();
      }
   }
   SPSTREAMFORMAT 是一个ENUM类型,定义位于sapi.h中,这样eFmt就保存了获得的当前频率设置值。每一个值对应了不同的频率设置。

3.使用XML朗读

  private void Init()

在朗读的过程中,可以跳过部分文字继续后面的朗读,代码如下:
ULONG ulGarbage = 0;
WCHAR szGarbage[] = L"Sentence";
hr = m_cpVoice->Skip( szGarbage, SkipNum, &ulGarbage );
SkipNum是设置要跳过的句子数量,值可以是正/负。
根据sdk的说明,目前SAPI仅仅支持SENTENCE这个类型。SAPI是通过标点符号来区分句子的。

   通过如下代码设置当前朗读频率:
   CComPtr<ISpAudio>  m_cpOutAudio; //声音输出接口
   SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT,&m_cpOutAudio ); //创建接口

//在进行TTS开发时可以使用XML,SAPI可以分析XML标签,通过XML能够实现一些ISpVoice的成员函数的功能。比如设置语音库、音量、语速等。此时speak函数的dwFlags参数要设置为包含SPF_IS_XML

// 选择语音库Microsoft Sam
pSpVoice->speak(L"<VOICE REQUIRED='NAME=Microsoft Sam'/>鸡啄米", SPF_DEFAULT | SPF_IS_XML, NULL);

// 设置音量
<VOLUME LEVEL='90'>鸡啄米</VOLUME>

// 设置语言
<lang langid='804'>鸡啄米</lang>
//804代表中文,409代表英文。如果用函数SpGetLanguageFromToken获取语言时,0x804表示中文,0x409表示英文。

  {

6、播放WAV文件。SAPI可以播放WAV文件,这是通过ISpStream接口实现的:

   SPSTREAMFORMAT eFmt = SPSF_8kHz8BitMono; //SPSF_8kHz 8Bit Mono这个参数可以参考sapi.chm手册

五.Microsofot Speech SDK开发程序举例

  // 创建SpVoice对象

CComPtr<ISpStream> cpWavStream;
WCHAR szwWavFileName[NORM_SIZE] = L"";;

   CSpStreamFormat Fmt;
   Fmt.AssignFormat(eFmt);
    if (m_cpOutAudio )
    {
       hr = m_cpOutAudio->SetFormat(Fmt.FormatId(), Fmt.WaveFormatExPtr() );
    }
   else  hr = E_FAIL;

::CoInitialize(NULL);             // COM初始化
CLSID CLSID_SpVoice;
CLSIDFromProgID(_T("SAPI.SpVoice"), &CLSID_SpVoice);
ISpVoice *pSpVoice = NULL;
IEnumSpObjectTokens *pSpEnumTokens = NULL;

// 获取ISpVoice接口
if (FAILED(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&pSpVoice)))
{
    return -1;
}
// 列举所有的语音token,可以通过pSpEnumTokens指向的接口得到
if (SUCCEEDED(SpEnumTokens(SPCAT_VOICES, NULL, NULL, &pSpEnumTokens)))
{
    ISpObjectToken *pSpToken = NULL;
    // 依次获取每个token并朗读字符串
    while (SUCCEEDED(pSpEnumTokens->Next(1, &pSpToken, NULL)) && pSpToken != NULL)
    {
        pSpVoice->SetVoice(pSpToken);      // 设置当前语音token为pSpToken
        pSpVoice->Speak(L"Hello Word 世界你好", SPF_DEFAULT, NULL);     // 朗读中文和英文的混合字符串
        pSpToken->Release();       // 释放token

    }
    pSpEnumTokens->Release();        // 释放pSpEnumTokens接口
}
pSpVoice->Release();
::CoUninitialize();

  m_spVoice = new SpVoice();

USES_CONVERSION;
wcscpy( szwWavFileName, T2W( szAFileName ) );//从ANSI将WAV文件的名字转换成宽字符串

    if(SUCCEEDED( hr ) )
   {
      m_cpVoice->SetOutput( m_cpOutAudio, FALSE );
   }

    
       鸡啄米通过调试和朗读效果得出结论,Vista和Win7上的Microsoft Lili语音库可以中英文混读,Microsoft Speech SDK 5.1中的中文男声Microsoft Simplified Chinese语音库朗读英文的时候只能一个字母一个字母的读,Anna、Mike、Sam只能读英文,中文略过。

  // 枚举出系统中已经安装的语音,并将其填充到Combo Box控件中

//使用sphelper.h 提供的这个函数打开 wav 文件,并得到一个 IStream 指针
hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream );
if( SUCCEEDED( hr ) )
{
m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL );//播放WAV文件
}
7、将朗读的结果保存到wav文件
TCHAR szFileName[256];//假设这里面保存着目标文件的路径
USES_CONVERSION;
WCHAR m_szWFileName[MAX_FILE_PATH];
wcscpy( m_szWFileName, T2W(szFileName) );//转换成宽字符串

  • 3.  获取/设置播放所用语音。

       那么Microsoft Simplified Chinese、Anna、Mike、Sam怎样实现中英文混读呢?鸡啄米告诉大家,可以修改字符串,加入XML标记,将中文和英文分别处理,上面程序中的字符串可以修改为:L"<lang langid='409'>Hello Word</lang> <lang langid='804'>世界你好</lang>"。这样当前语音库不能读的语言会自动选择同性别的其他语音来读。

  foreach (ISpeechObjectToken Token in m_spVoice.GetVoices(string.Empty, string.Empty))

//创建一个输出流,绑定到wav文件
CSpStreamFormat OriginalFmt;
CComPtr<ISpStream> cpWavStream;
CComPtr<ISpStreamFormat> cpOldStream;
HRESULT hr = m_cpVoice->GetOutputStream( &cpOldStream );
if (hr == S_OK) hr = OriginalFmt.AssignFormat(cpOldStream);
else hr = E_FAIL;
// 使用sphelper.h中提供的函数创建 wav 文件
if (SUCCEEDED(hr))
{
hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS, &cpWavStream,
&OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() );
}
if( SUCCEEDED( hr ) )
{
//设置声音的输出到 wav 文件,而不是 speakers
m_cpVoice->SetOutput(cpWavStream, TRUE);
}
//开始朗读
m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );

  引擎中所用的语音数据文件一般保存在SpeechEngines下的spd或者vce文件中。安装sdk后,在注册表中保存了可用的语音,比如英文的男/女,简体中文的男音等。位置是:
  HKEY_LOCAL_MACHINE/Software/Microsoft/Speech/Voices/Tokens
  SAPI的缺点是不能支持中英文混读,在朗读中文的时候,遇到英文,只能逐个字母读出。所以需要程序自己进行语音切换。

六.如何制作SAPI组件安装包

  {

//等待朗读结束
m_cpVoice->WaitUntilDone( INFINITE );
cpWavStream.Release();

(1) 可以采用如下的函数把当前SDK支持的语音填充在一个组合框中:
    // SAPI5helper function in sphelper.h

       SAPI开发的程序想要正常运行,必须保证计算机上安装了SAPI组件,所以我们的软件发布时最好同时发布SAPI核心组件的安装程序,如果需要安装新的语言,还要有语言模块。这就需要用到讲SDK下载时提到的SpeechSDK51MSM.exe。

  this.cmbVoices.Items.Add(Token.GetDescription(49));

//把输出重新定位到原来的流
m_cpVoice->SetOutput( cpOldStream, FALSE );

     CWnd* m_wnd = GetDlgItem(IDC_COMBO_VOICES);
     HWND  hWndCombo = m_wnd->m_hWnd; //组合框句柄
     HRESULT hr =SpInitTokenComboBox( hWndCombo , SPCAT_VOICES );
   这个函数是通过IEnumSpObjectTokens接口枚举当前可用的语音接口,把接口的说明文字添加到组合框中,并且把接口的指针作为LPARAM 保存在组合框中。
   一定要记住最后程序退出的时候,释放组合框中保存的接口:
   SpDestroyTokenComboBox( hWndCombo );
   这个函数的原理就是逐个取得combo里面每一项的LPARAM数据,转换成IUnknown接口指针,然后调用Release函数。
(2) 当组合框选择变化的时候,可以用下面的函数获取用户选择的语音:
   ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo );

       SpeechSDK51MSM.exe安装完以后会生成三个文件夹:1033、1041和2052。其中,1033下主要是用于英文的TTS和SR的.msm文件,1041下主要是用于日文SR的.msm文件,2052下是用于中文TTS和SR的msm文件。

  }

8、设置朗读音量和速度
m_cpVoice->SetVolume((USHORT)hpos); //设置音量,范围是 0 - 100
m_cpVoice->SetRate(hpos); //设置速度,范围是 -10 - 10

(3) 用下面的函数获取当前正在使用的语音:
   CComPtr<ISpObjectToken> pOldToken;
    HRESULT hr = m_cpVoice->GetVoice( &pOldToken);
(4) 当用户选择的语音和当前正在使用的不一致的时候,用下面的函数修改:
    if(pOldToken != pToken)
   {       
        // 首先结束当前的朗读,这个不是必须的。
        HRESULT hr = m_cpVoice->Speak( NULL,SPF_PURGEBEFORESPEAK, 0);
        if (SUCCEEDED (hr) )
           hr = m_cpVoice->SetVoice( pToken );
    }
(5) 也可以直接使用函数SpGetTokenFromId获取指定voice的Token指针,例如:
     WCHAR pszTokenId[] =L"HKEY_LOCAL_MACHINE//Software//Microsoft//Speech//Voices//Tokens//MSSimplifiedChineseVoice";
   SpGetTokenFromId(pszTokenID , &pChineseToken);

       我们要开发的是TTS程序,所以不需要SR的相关文件。英文TTS需要包含1033下的这些文件:Sp5.msm、Sp5Intl.msm、Sp5itn.msm、Sp5TTInt.msm、SpCommon.msm,如果需要Mike和Mary的语音还需Sp5TTIntXP.msm文件。如果还要支持中文TTS则需再包含2052下的文件:Sp5Intl.msm、Sp5itn.msm、SP5TTINTr.msm。

  // 默认选中第一个语音

hpos的值一般位于

  • 4  开始/暂停/恢复/结束当前的朗读

       仍以VS2010为例,安装包制作的具体过程如下:

  cmbVoices.SelectedIndex = 0;

9、设置SAPI通知消息。SAPI在朗读的过程中,会给指定窗口发送消息,窗口收到消息后,可以主动获取SAPI的事件,
根据事件的不同,用户可以得到当前SAPI的一些信息,比如正在朗读的单词的位置,当前的朗读口型值(用于显
示动画口型,中文语音的情况下并不提供这个事件)等等。

要朗读的文字必须位于宽字符串中,所以从文本框中读取的字符串类型CString必须转换成为WCHAR型,如下(m_strText为文本框变量):
   CString strSpeak;
 m_strText.GetWindowText(strSpeak);
 WCHAR   wChar[256]; 
 memset(wChar ,0,256);
 MultiByteToWideChar( CP_ACP , 0 , strSpeak , strSpeak.GetLength() , wChar , 256);
  这样就将文本框中的字符串strSpeak转化为WCHAR型的wChar变量中了.
   开始朗读的代码:
   hr =m_cpVoice->Speak( wChar, SPF_ASYNC |SPF_IS_NOT_XML, 0 );
   如果要解读一个XML文本,用:
   hr =m_cpVoice->Speak( wChar, SPF_ASYNC |SPF_IS_XML, 0 );

       1.选择File->new->project,弹出New Project对话框,在左侧面板中的Installed Templates下展开Other Project Types,继续展开起子节点Setup and Deployment,最后选择Visual Studio Installer,右侧面板中选择Setup Project,最后在对话框下部,设置好名称和路径点“OK”就生成工程了。对于没有集成Visual Studio Installer的老版本的编译器,可以在网上下载Microsoft Visual Studio Installer。

  }

要获取SAPI的通知,首先要注册一个消息:
m_cpVoice->SetNotifyWindowMessage( hWnd, WM_TTSAPPCUSTOMEVENT, 0, 0 );
这个代码一般是在主窗口初始化的时候调用,hWnd是主窗口(或者接收消息的窗口)句柄。WM_TTSAPPCUSTOMEVENT
是用户自定义消息。

  暂停的代码:  m_cpVoice->Pause();
  恢复的代码:  m_cpVoice->Resume();
   结束的代码:(上面的例子中已经给出了)
   hr =m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK,0);

       2.在Solution Explorer中的工程名上点右键,然后在右键菜单中选择Add->Merge Module,最后在弹出的对话框中选择上面提到的所要包含的.msm文件即可。

朗读文本

在窗口响应WM_TTSAPPCUSTOMEVENT消息的函数中,通过如下代码获取sapi的通知事件:

  • 5  跳过部分朗读的文字

       3.编译运行工程,若是Debug模式则会在工程目录的Debug文件夹下生成相应的msi文件,若是Release模式则会在Release目录下声称msi文件。

  完成窗体的初始化,创建SpVoice对象之后,接下来我们就可以利用这个对象的Speak()方法来阅读Text Box控件中的文本了。

CSpEvent event; // 使用这个类,比用 SPEVENT结构更方便

  在朗读的过程中,可以跳过部分文字继续后面的朗读,代码如下:
   ULONG ulGarbage = 0;
   WCHAR szGarbage[] =L"Sentence";
   hr =m_cpVoice->Skip( szGarbage, SkipNum,&ulGarbage );
  SkipNum是设置要跳过的句子数量,值可以是正/负。
  根据sdk的说明,目前SAPI仅仅支持SENTENCE这个类型。SAPI是通过标点符号来区分句子的。

       SAPI安装包到此就制作好了,我们也可以将我们的应用程序exe文件也一同打到安装包里。这样运行安装程序后就会将应用程序和SAPI组件都装到目标主机里,应用程序可以直接运行。

  

while( event.GetFrom(m_cpVoice) == S_OK )
{
switch( event.eEventId )
{
。。。
}
}

  • 6 播放WAV文件。SAPI可以播放WAV文件,这是通过ISpStream接口实现的:

       关于Windows TTS语音引擎编程入门的知识鸡啄米就总结完了,大家可以继续探索,开发出各种有趣的语音程序。

  private void btnSpeak_Click(object sender, EventArgs e)

eEventID有很多种,比如SPEI_START_INPUT_STREAM表示开始朗读,SPEI_END_INPUT_STREAM表示朗读结束等。
可以根据需要进行判断使用。

  CComPtr<ISpStream>   cpWavStream;
  WCHAR     szwWavFileName[NORM_SIZE] = L"";

  {

   USES_CONVERSION;
   wcscpy( szwWavFileName, T2W(szAFileName ) );//从ANSI将WAV文件的名字转换成宽字符串

  // 获取用户在Combo Box中选择的语音索引

   //使用sphelper.h 提供的这个函数打开wav 文件,并得到一个 IStream 指针
   hr = SPBindToFile(szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream);
   if( SUCCEEDED( hr ) )
   {
       m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL);//播放WAV文件
   }

  int nVoiceIndex = this.cmbVoices.SelectedIndex;

  • 7 将朗读的结果保存到wav文件
       TCHARszFileName[256];//假设这里面保存着目标文件的路径
       USES_CONVERSION;
       WCHAR m_szWFileName[MAX_FILE_PATH];
       wcscpy( m_szWFileName,T2W(szFileName) );//转换成宽字符串

  // 根据语音索引指定SpVoice的Voice属性,也就是指定使用何种语音

   //创建一个输出流,绑定到wav文件
   CSpStreamFormat  OriginalFmt;
  CComPtr<ISpStream> cpWavStream;
  CComPtr<ISpStreamFormat>   cpOldStream;
   HRESULT hr =m_cpVoice->GetOutputStream(&cpOldStream );
   if (hr == S_OK) hr =OriginalFmt.AssignFormat(cpOldStream);
   else  hr =E_FAIL;
   // 使用sphelper.h中提供的函数创建 wav文件
   if (SUCCEEDED(hr))
   {
     hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS,&cpWavStream,&OriginalFmt.FormatId(),OriginalFmt.WaveFormatExPtr() );
    }
   if( SUCCEEDED( hr ) )
   {
     //设置声音的输出到 wav 文件,而不是speakers
     m_cpVoice->SetOutput(cpWavStream, TRUE);
    }
    //开始朗读
   m_cpVoice->Speak( wChar, SPF_ASYNC |SPF_IS_NOT_XML, 0 );

  m_spVoice.Voice = m_spVoice.GetVoices(string.Empty, string.Empty).Item(nVoiceIndex);

   //等待朗读结束
   m_cpVoice->WaitUntilDone( INFINITE );
   cpWavStream.Release();

  // 使用SpVoice的Speak()方法阅读Text Box中文本

   //把输出重新定位到原来的流
   m_cpVoice->SetOutput( cpOldStream, FALSE );

  m_spVoice.Speak(this.textPreview.Text, SpeechVoiceSpeakFlags.SVSFlagsAsync);

  • 8 设置朗读音量和速度
      m_cpVoice->SetVolume((USHORT)hpos); //设置音量,范围是 0 -100
      m_cpVoice->SetRate(hpos); //设置速度,范围是 -10 - 10

  }

 

  在这里我们使用了SpVoice对象的一个最重要的函数Speak(),它的第一个参数就是我们要朗读的文 本,而第二个参数则是朗读的方式,有同步,异步,XML文件等等。 这样,通过SpVoice对象的一个简单函数,我们就可以朗读Text Box控件中的文本内容了。

  • 9 设置SAPI通知消息。

  朗读文本文件

      SAPI在朗读的过程中,会给指定窗口发送消息,窗口收到消息后,可以主动获取SAPI的事件,根据事件的不同,用户可以得到当前SAPI的一些信息,比如正在朗读的单词的位置,当前的朗读口型值(用于显示动画口型,中文语音的情况下并不提供这个事件)等等。要获取SAPI的通知,首先要注册一个消息:
  m_cpVoice->SetNotifyWindowMessage( hWnd,WM_TTSAPPCUSTOMEVENT, 0, 0 );
  这个代码一般是在主窗口初始化的时候调用,hWnd是主窗口(或者接收消息的窗口)句柄。WM_TTSAPPCUSTOMEVENT是用户自定义消息。在窗口响应WM_TTSAPPCUSTOMEVENT消息的函数中,通过如下代码获取sapi的通知事件:

  更多时候,我们不是阅读Text Box控件中输入的文本,而是阅读某些文本文件中的文字,这样,读取文本文件并将文字填充到Text Box控件中就成为一种必要了。

   CSpEvent       event;  // 使用这个类,比用 SPEVENT结构更方便

  

    while(event.GetFrom(m_cpVoice) == S_OK )
    {
       switch( event.eEventId )
       {
         ...
       }
    }

  private void btnFileSelect_Click(object sender, EventArgs e)

  eEventID有很多种,比如SPEI_START_INPUT_STREAM表示开始朗读,SPEI_END_INPUT_STREAM表示朗读结束等。
   可以根据需要进行判断使用。

  {

 7.总结

  // 使用打开文件对话框选择文本文件

还有一些关于xml的支持可以参考sapi.chm帮助手册,感谢网络原作提供的资源,有iwaswzq,yaooo等。

  OpenFileDialog openFileDialog1 = new OpenFileDialog();

  openFileDialog1.InitialDirectory = e:\;

  openFileDialog1.Filter = txt files (*.txt)|*.txt|All files (*.*)|*.*;

  openFileDialog1.FilterIndex = 2;

  openFileDialog1.RestoreDirectory = true;

  if (openFileDialog1.ShowDialog() == DialogResult.OK)

  {

  // 读取文本文件并将其填充到Text Box控件

  StreamReader objReader = new StreamReader( openFileDialog1.FileName);

  string sLine = ;

  string sPreview = ;

  while (sLine != null)

  {

  sLine = objReader.ReadLine();

  if (sLine != null)

  {

  // 这里需要添加Environment.NewLine表示换行

  sPreview += sLine + Environment.NewLine;

  }

  }

  // 将文本文件中的内容显示到Text Box控件

  this.textPreview.Text = sPreview;

  // 关闭文件读取器

  objReader.Close();

  }

  }

  这样,我们就可以通过读取文本文件中的内容,将其显示到Text Box控件中,然后SpVoice就可以阅读Text Box控件中的内容,也就是间接地朗读了文本文件。

  将文本转换成声音文件

  除了直接朗读文本之外,更多的时候,我们还需要将文本转换成声音文件。这样我们可以将这些声音文件随身携带,想听就听。要将文本转换为声音文 件,我们需要用到SpVoice的另外一个重要的函数SetOutput(),我们可以利用它将SpVoice的语音输出某个WAV文件,从而实现将文本 文件转换为声音文件。

  因为 将一段比较长的文本转换成声音文件,通常是一个比较长的过程,所以在这里我们创建一个专门的工作者线程来负责文本的转换,而界面线程则负责显示转换的进度。

  

  // 工作者线程类

  public class WorkerThread

  {

  // 用户选择的语音

  private int nVoiceIndex;

  // 保存的文件名

  private string strFileName;

  // 需要转换的文本

  private ArrayList arrText;

  // 构造函数,利用构造函数向线程传递参数

  public WorkerThread(int nIndex, ArrayList aText, string sFileName )

  {

  nVoiceIndex = nIndex;

  arrText = aText;

  strFileName = sFileName;

  }

  // 线程开始事件

  public event EventHandler threadStartEvent;

  // 线程执行时的事件

  public event EventHandler threadEvent;

  // 线程结束事件

  public event EventHandler threadEndEvent;

  // 线程函数

  public void runMethod()

  {

  // 创建SpVoice对象,并选择用户选中的语音

  SpVoice voice = new SpVoice();

  voice.Voice = voice.GetVoices(string.Empty, string.Empty).Item(nVoiceIndex);

  try

  {

  // 创建流媒体文件

  SpeechStreamFileMode SpFileMode =

  SpeechStreamFileMode.SSFMCreateForWrite;

  SpFileStream SpFileStream = new SpFileStream();

  // 这里我们设置输出的频率,这样可以决定输出文件的大小

  SpFileStream.Format.Type = SpeechAudioFormatType.SAFTCCITT_ALaw_8kHzMono;

  // 还可以选择更高品质的格式,不过产生的文件体积更大

  // SpFileStream.Format.Type = SpeechAudioFormatType.SAFT11kHz16BitMono;

  // 创建文件,并将SpVoice的输出流指定为当前文件

  voice.AudioOutputStream = SpFileStream;

  // 发送线程开始事件,通知主界面,设定进度条的最大值为Count

  threadStartEvent.Invoke(arrText.Count, new EventArgs());

  // 开始将文本输出到音频文件

  int nCount = 0;

  foreach (string sOutput in arrText)

  {

  voice.Speak(sOutput, SpeechVoiceSpeakFlags.SVSFlagsAsync);

  // 发送线程运行时事件,移动进度条的位置

  threadEvent.Invoke(nCount, new EventArgs());

  voice.WaitUntilDone(-1);

  ++nCount;

  }

  // 关闭音频文件

  SpFileStream.Close();

  }

  catch

  {

  }

  // 发送线程结束事件,通知主界面关闭进度条

  threadEndEvent.Invoke(new object(), new EventArgs());

  }

  }

  跟直接朗读文本相似,我们仍旧使用SpVoice的Speak()函数朗读文本,只是我们通过指定SpVoice的AudioOutputStream属性,将语音输出到一个音频文件,这样就完成了文本文件到音频文件的转换。

  完成转换工作者线程的创建后,我们就可以利用它来完成具体的转换工作。在窗体的保存按钮的单击响应函数中,我们创建相应的工作者线程来进行文本的转换。

  

  private void btnSavetoWAV_Click(object sender, EventArgs e)

  {

  string strWAVFile = ;

  try

  {

  // 使用保存文件对话框,选择保存的文件

  SaveFileDialog sfd = new SaveFileDialog();

  sfd.Filter = All files (*.*)|*.*|wav files (*.wav)|*.wav;

  sfd.Title = Save to a wave file;

  sfd.FilterIndex = 2;

  sfd.RestoreDirectory = true;

  if (sfd.ShowDialog() == DialogResult.OK)

  {

  // 获取用户输入的文件名

  strWAVFile = sfd.FileName;

  // 从Text Box控件获取要转换的文本

  ArrayList arrText = new ArrayList();

  foreach (String sLine in this.textPreview.Lines)

  arrText.Add(sLine);

  // 显示进度条

  progressForm = new Form2();

  progressForm.Show();

  // 创建工作者线程,并向工作者线程传递要转换的文本

  WorkerThread myThreadFun = new WorkerThread(

  this.cmbVoices.SelectedIndex, arrText, strWAVFile);

  // 注册线程事件

  myThreadFun.threadStartEvent += new EventHandler(method_threadStartEvent);

  myThreadFun.threadEvent += new EventHandler(method_threadEvent);

  myThreadFun.threadEndEvent += new EventHandler(method_threadEndEvent);

  // 创建线程,执行工作者线程

  Thread thread = new Thread(new ThreadStart(myThreadFun.runMethod));

  // 启动线程

  thread.Start();

  }

  }

  catch

  {

  }

  }

  除了创建线程进行文本的转换之外,为了让我们的软件更加易用,更加人性化,我们还需要响应线程事件,移动进度条的位置以反映转换的进度,免得用户以为软件在比较长的转换过程中死掉了。

  

  // 线程开始的时候调用的委托

  private delegate void maxValueDelegate(int maxValue);

  // 线程执行中调用的委托

  private delegate void nowValueDelegate(int nowValue);

  // 线程结束的时候调用的委托

  private delegate void hideProgressDelegate(int n);

  /// 线程完成事件,隐藏进度条窗口

  /// 但是我们不能直接操作进度条,需要一个委托来替我们完成

  void method_threadEndEvent(object sender, EventArgs e)

  {

  hideProgressDelegate hide = new hideProgressDelegate(hideProgress);

  this.Invoke(hide, 0);

  }

  /// 线程执行中的事件,设置进度条当前进度

  /// 这里的sender,是WorkerThread 函数中传过来的当前值

  void method_threadEvent(object sender, EventArgs e)

  {

  int nowValue = Convert.ToInt32(sender);

  nowValueDelegate now = new nowValueDelegate(setNow);

  this.Invoke(now, nowValue);

  }

  /// 线程开始事件,设置进度条最大值

  /// 但是我不能直接操作进度条,需要一个委托来替我完成

  /// 这里的sender,是WorkerThread 函数中传过来的最大值

  void method_threadStartEvent(object sender, EventArgs e)

  {

  int maxValue = Convert.ToInt32(sender);

  maxValueDelegate max = new maxValueDelegate(setMax);

  this.Invoke(max, maxValue);

  }

  /// 被委托调用的函数,专门操作进度条

  private void setMax(int maxValue)

  {

  progressForm.progressBar1.Maximum = maxValue;

  }

  private void setNow(int nowValue)

  {

  }

  private void hideProgress(int n)

  {

  progressForm.Hide();

  }

控制SpVoice的阅读

  到这里,一个能说会道的软件基本上已经完成了,但是,为了让我们的软件更加易用,我们还可以通过SpVoice提供的函数对SpVoice的行为进行控制,让她更加符合我们的心意。例如,我们可以控制SpVoice的暂停和继续。

  

  private void btnPause_Click(object sender, EventArgs e)

  {

  if (this.btnPause.Text == 暂停)

  {

  // 让SpVoice暂停朗读

  m_spVoice.Pause();

  this.btnPause.Text =继续;

  }

  else

  {

  // 让SpVoice继续朗读

  m_spVoice.Resume();

  this.btnPause.Text = 暂停;

  }

  }

  通过SpVoice提供的函数,对SpVoice的行为进行控制就是这么简单。除了阅读的暂停和继续之外,我们还可以通过SetRate()函数设置声音的语调,通过SetVolume()函数设置声音的音量等等。这些函数就不在这里一一介绍了,留给大家自己去尝试。

  现在,使用SAPI,即刻让你的软件能说会道。

本文由10bet手机官网发布于面向对象,转载请注明出处:转自大楚网,微软TTS语音引擎编程入门

上一篇:Spring调用spymemcached客户端的例子,才可以在Action中使用依赖注入 下一篇:Java框架整合,MyBatis解决Java实体类和数据库表字段不一致方法总结
猜你喜欢
热门排行
精彩图文