背景描述

前几年因工作的需要,想在网上找一款 TTS(Text To Speech) 软件,用于将文字转换为特定格式(ALaw、8000Hz、8bit)的语音文件,但实在找不到合适的。最后想着,干脆自己写一个得了,成果就是本文所要描述的基于微软 SAPI的文字转语音的小程序。在开发的过程中,主要参考了 “鸡啄米”同学的博客,在此表示感谢。现把开发过程重新整理了下,希望能对有需要的同学提供帮助。

SAPI 概述

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

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

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

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

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

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

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

我们要实现语音合成需要的是Voice Text API。

TTS开发程序举例

开发环境

操作系统:Windows 7 X64

IDE:Visual Studio 2013 Update 5(含Windows Kits 8.1)

开发语言:C++(& MFC)

语音包:Windows 7系统默认包含的Microsoft Lili - Chinese (China)和Microsoft Anna - English (United States)两个语音包,Microsoft Lili支持中英文混读。Windows XP系统或需要其他语音包可到微软官网下载。

示例代码

(1)包含头文件和lib库

使用SAPI语音引擎前需包含以下头文件和lib库:

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

(2)文字转 WAV文件示例

直接看代码,有疑问先保留,函数说明后续分解。

::CoInitialize(NULL);         // COM初始化//获取ISpVoice接口if (FAILED(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL/*CLSCTX_INPROC_SERVER*/, IID_ISpVoice, (void**)&m_pISpVoice))){MessageBox(L"获取ISpVoice接口失败!", L"提示", MB_OK | MB_ICONWARNING);return;}//语音文件保存的目录this->CreateSaveDirectory();CString strChannel;CString strFormat;CString strSamplesPreSec;CString strBitsPerSample;m_ComboxChannel.GetLBText(m_ComboxChannel.GetCurSel(), strChannel);   m_ComboxFormat.GetLBText(m_ComboxFormat.GetCurSel(), strFormat);    m_ComboxSamplesPreSec.GetLBText(m_ComboxSamplesPreSec.GetCurSel(), strSamplesPreSec);   m_ComboxBitsPreSamples.GetLBText(m_ComboxBitsPreSamples.GetCurSel(), strBitsPerSample);//编码设置,此处只列举ALaw、ULaw和PCM//其他编码参见 mmreg.h 文件WORD wFormatTag = WAVE_FORMAT_ALAW;if (strFormat.CompareNoCase(_T("ALaw")) == 0){wFormatTag = WAVE_FORMAT_ALAW;} else if (strFormat.CompareNoCase(_T("ULaw")) == 0){wFormatTag = WAVE_FORMAT_MULAW;}else{wFormatTag = WAVE_FORMAT_PCM;}int nChannel = strChannel.CompareNoCase(_T("单声道")) == 0 ? 1 : 2;  //声道设置int nSamplesPreSec = _ttoi(strSamplesPreSec.GetBuffer());            //采样率设置int nBitsPerSample = _ttoi(strBitsPerSample.GetBuffer());           //采样比特设置int nBlockAlign = (nChannel * nBitsPerSample) / 8;//此处很重要,录音文件的参数在此配置WAVEFORMATEX waveFormate;waveFormate.wFormatTag = wFormatTag; //编码,WAVE_FORMAT_ALAW、WAVE_FORMAT_MULAW、WAVE_FORMAT_PCM...
waveFormate.nChannels = nChannel;  //采样声道数,对于单声道音频设置为1,立体声设置为2waveFormate.nSamplesPerSec = nSamplesPreSec; //采样率,8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHzwaveFormate.nAvgBytesPerSec = nSamplesPreSec * nBlockAlign;   //每秒的数据率,就是每秒能采集多少字节的数据,nSamplesPerSec × nBlockAlignwaveFormate.nBlockAlign = nBlockAlign; //一个块的大小,采样bit的字节数乘以声道数, (nChannels × wBitsPerSample) / 8waveFormate.wBitsPerSample = nBitsPerSample; //8 or 16,采样比特  8bits/次waveFormate.cbSize = 0;                        //一般为0CComPtr<ISpStream> cpISpStream;CComPtr<ISpStreamFormat> cpISpStreamFormat;CSpStreamFormat spStreamFormat;m_pISpVoice->GetOutputStream(&cpISpStreamFormat); spStreamFormat.AssignFormat(cpISpStreamFormat);HRESULT hResult = SPBindToFile(this->GetFileDirectory().AllocSysString(),SPFM_CREATE_ALWAYS,&cpISpStream,&spStreamFormat.FormatId(),&waveFormate/*spStreamFormat.WaveFormatExPtr()*/);if (SUCCEEDED(hResult)){//语音包、音量、语速设置this->SetVoiceParam();m_pISpVoice->SetOutput(cpISpStream, TRUE);m_pISpVoice->Speak(m_EditContent.AllocSysString(), SPF_DEFAULT, NULL);MessageBox(_T("保存 WAV 文件成功!"), _T("提示"), MB_OK);ShellExecute(NULL, _T("open"), m_strFileDir, NULL, NULL, SW_SHOWNORMAL);}else{MessageBox(_T("保存 WAV 文件失败!"), _T("提示"), MB_OK | MB_ICONWARNING);}m_pISpVoice->Release();
::CoUninitialize();

代码解释

SAPI是架构在COM基础上的,因此在使用语音引擎之前需进行COM初始化:

::CoInitialize(NULL);

当然,使用结束后记得释放资源:

::CoUninitialize();

COM初始化后,调用函数

STDAPI CoCreateInstance(

REFCLSID rclsid, //创建的Com对象的类标识符(CLSID)

LPUNKNOWN pUnkOuter, //指向接口IUnknown的指针

DWORD dwClsContext, //运行可执行代码的上下文

REFIID riid, //创建的Com对象的接口标识符

LPVOID * ppv //用来接收指向Com对象接口地址的指针变量

)

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

需要输出自定义格式WAV文件的,下面是关键点,请留意!!!

WAV文件的格式可通过结构体WAVEFORMATEX来设置,WAVEFORMATEX 各字段解释如下:

typedef struct tWAVEFORMATEX

{

WORD        wFormatTag;        //编码,取WAVE_FORMAT_ALAW\WAVE_FORMAT_MULAW\WAVE_FORMAT_PCM...等值

WORD        nChannels;          //采样声道数,对于单声道音频设置为1,立体声设置为2

DWORD       nSamplesPerSec;     //采样率,8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz等

DWORD       nAvgBytesPerSec;    / //每秒的数据率,就是每秒能采集多少字节的数据(nSamplesPerSec × nBlockAlign)

WORD        nBlockAlign;        /一个块的大小,采样bit的字节数乘以声道数, (nChannels × wBitsPerSample) / 8

WORD        wBitsPerSample;     //8 or 16,采样比特  8bits/次

WORD        cbSize;            //一般为0

} WAVEFORMATEX, *PWAVEFORMATEX, NEAR *NPWAVEFORMATEX, FAR *LPWAVEFORMATEX;

前面都是准备工作,函数SPBindToFile将音频流绑定到指定的文件,原型如下:

HRESULT SPBindToFile( LPCWSTR pFileName, SPFILEMODE eMode, ISpStream ** ppStream, const GUID * pFormatId = NULL, const WAVEFORMATEX * pWaveFormatEx = NULL, ULONGLONG ullEventInterest = SPFEI_ALL_EVENTS)

pFileName将流绑定到的文件的文件名;

  eMode:用于定义文件打开模式;打开音频文件时,该文件必须为SPFM_OPEN_READONLY或SPFM_CREATE_ALWAYS,否则调用将失败;

  ppStream:ISpStream指针的地址;如果函数成功,则使用新创建的ISpStream接口填充该值;

  pFormatId:与流关联的数据格式标识符;可以为NULL(默认值),并且格式将由提供的wave文件确定(如果文件具有'.wav'扩展名)。如果不是,则假定该文件是文本文件;

  pWaveFormatEx:包含wave文件格式信息的WAVEFORMATEX结构;如果guidFormatId为SPDFID_WaveFormatEx,则必须指向有效的 WAVEFORMATEX结构;对于其他格式,它应该为NULL;

  ullEventInterest:所需事件的类型为SPEVENTENUM的标志。

在输出WAV文件前,需要选择语音库,通过函数SpEnumTokens来枚举所有语音库:

IEnumSpObjectTokens *m_pIEnumSpObjectTokens

SpEnumTokens(SPCAT_VOICES, NULL, NULL, &m_pIEnumSpObjectTokens)

语音库保存在 pIEnumSpObjectTokens 里,可通过函数Item来选择需要的语音库:

ISpObjectToken      *m_pISpObjectToken;

m_pIEnumSpObjectTokens->Item(Index, &m_pISpObjectToken);

通过函数HRESULT SetVolume(USHORT    usVolume)来设置使用的语音库:

ISpVoice            *m_pISpVoice;

m_pISpVoice->SetVoice(m_pISpObjectToken);

注意,如果使用不支持中文的语音库来操作中文字符串,WAV 文件将会输出失败。

通过函数HRESULT SetRate( long   RateAdjust)来设置朗读速度,取值范围:-10到10。

m_pISpVoice->SetRate(m_SliderVoiceSpeed.GetPos() - 10);

通过函数HRESULT SetVolume(USHORT usVolume)设置音量,范围:0到100。

m_pISpVoice->SetVolume(100 - m_SliderVoiceSize.GetPos());

将音频流输出到WAV文件:

HRESULT SetOutput(IUnknown *pUnkOutput,BOOL fAllowFormatChanges);

HRESULT Speak(LPCWSTR *pwcs, DWORD dwFlags, ULONG *pulStreamNumber)

pwcs:待转换的字符串

dwFlags:是用于控制朗读方式的标志,具体意义可以查看文档中的枚举 SPEAKFLAGS。SPF_DEFAULT表示使用默认设置,包括同步朗读的设置。异步朗读可以设置成 SPF_ASYNC。同步朗读表示读完string中的内容,speak函数才会返回,而异步朗读则将字符串送进去就返回,不会阻塞。

pulStreamNumber:转换WAV文件时值为NULL;当用于朗读时为输出参数,它指向本次朗读请求对应的当前输入流编号,每次朗读一个字符串时都会有一个流编号返回,异步朗读时使用。

ISpVoice接口使用完毕后也要记得释放:

m_pISpVoice->Release();

朗读接口实现参见 Demo 的 void CTTSTestDlg::OnBnClickedButtonSpeak() 函数。

Demo下载:https://download.csdn.net/download/psy361212/12035482

基于微软 SAPI 的 TTS 程序实现相关推荐

  1. 基于微软DEVCON的[一键禁用前面板插孔检测]程序

    一.问题概述 许多带有还原卡的计算机(比如学校机房的电脑),没有音响设备,需要佩戴耳机才能听到声音.但是由于初始配置问题,有些机房的电脑插入耳机仍然听不到声音. 电脑右下角任务栏音量调节图标状态如下图 ...

  2. speech开源框架_微软SAPI(The Microsoft Speech API):让你的软件能说会道

    "没声音,再好的戏也出不来."这虽然是一句广告,但是也说出了一个道理,我们所开发的软件,特别是一些多媒体软件,要是能够发出声音,能说会道,将为我们的软件增添不少光彩.同时,我们面临 ...

  3. 微软SAPI:让你的软件能说会道

    微软SAPI:让你的软件能说会道 2011年01月04日00:05 it168网站原创 作者:陈良乔 编辑: 胡铭娅 评论: 0条 [IT168专稿]"没声音,再好的戏也出不来." ...

  4. 基于微软开发平台构建和使用私有NuGet托管库

    本篇blog包含使用TFS2017,VS2017等平台和工具搭建和使用NuGet库等基本过程,为团体提供更加自动化和高效的研发活动支持. 作为以产品线或者以专属业务为扩展的项目类型的软件研发团体,都会 ...

  5. 带你走近微软最“动听”的程序媛 | 女神节特辑

    牛了,这几个案例让你迅速掌握AI技术! https://edu.csdn.net/topic/ai30?utm_source=cxrs_bw 今天是三月八日国际妇女节,人们和往常一样,将目光聚焦女性, ...

  6. 基于VBS的恶搞/表白程序

    引言--什么是VBS VBS是Visual Basic Script的缩写,是一种基于微软Windows操作系统的脚本语言.(就是我们俗称的脚本)它可以在Windows系统上执行许多常见任务,如文件操 ...

  7. 微软账号登陆不上_企业信息化面临的问题,看看解决方案,基于微软平台的IT架构...

    哈喽,今日头条的小伙伴们大家好,我是你们的好朋友IT咨询顾问.小编曾经就工作过程中接触的企业IT环境存在的问题,陆陆续续搜集过一些案例,当时做的笔记是零散的,现在为了方便小伙伴们阅读,整理了一下笔记并 ...

  8. 基于微软Office Communicator 2007 Automation API开发应用

    Automation API 是微软Office Communicator 2007供第三方应用程序集成的 OC API. 它的作用是微软为第三方应用程序调用OC 功能准备,基于 COM 的 API, ...

  9. 无需编程,基于微软mssql数据库零代码生成CRUD增删改查RESTful API接口

    无需编程,基于微软mssql数据库零代码生成CRUD增删改查RESTful API接口 回顾 通过之前一篇文章 无需编程,基于甲骨文oracle数据库零代码生成CRUD增删改查RESTful API接 ...

最新文章

  1. Spring Boot Web Error Page处理
  2. [Spring cloud 一步步实现广告系统] 22. 广告系统回顾总结
  3. 读“Agile Method – by Martin Fowler”总结和感想
  4. lombok @EqualsAndHashCode 注解的影响
  5. c++ 反射_Java代码审计基础之反射
  6. java读取gxk文件,Java中常见的IO流及其使用
  7. 史上最萌最认真的机器学习/深度学习/模式识别入门指导手册(二)
  8. MyEclipse编码设置,中文乱码解决方法,UTF-8,GBK
  9. 前端Swiper滑动的时候最右一个反弹回去了
  10. vue UI框架比较
  11. matlab卷积代码,卷积的Matlab代码实现
  12. xposed+justTrustme使用与分析
  13. qt中clicked()和toggled()的区别
  14. Dynamic resolution 动态分辨率 相机系列6
  15. 初中数学抽象教学的案例_初中数学教学案例分析-初中数学教学案例分析100例...
  16. Ubuntu 16.04下DNW的安装及使用
  17. 网络编程+go+java,Go语言中的TCP/IP网络编程
  18. FTP之PASV与PORT
  19. 联想Y400win10基础上在安装win7
  20. 国美电商,别误入歧途!

热门文章

  1. Python数据类型(一)数字类型
  2. SeedLab1: Sniffing Spoofing Lab
  3. 话说程序员的职业生涯
  4. 解决:SpringBoot--获取自动注入属性为空失败(注解无误情况下)
  5. Prometheus技术系列文章——prometheus调研总结
  6. vscode正则替换:大写改小写
  7. 为什么“码农”需要自我营销?
  8. “美国国家标准化组织(ANSI)”是一个核准多种行业标准的组织,我们可以把数据库看成这样一种有组织的机制
  9. IOS APP资源网站汇总
  10. CES2020 | 小牛电动成为科技出行的“另类”标杆