前几年因工作的需要,想在网上找一款 TTS(Text To Speech) 软件,用于将文字转换为特定格式(ALaw、8000Hz、8bit)的语音文件,但实在找不到合适的。最后想着,干脆自己写一个得了,成果就是本文所要描述的基于微软 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。
操作系统: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,立体声设置为2
waveFormate.nSamplesPerSec = nSamplesPreSec; //采样率,8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz
waveFormate.nAvgBytesPerSec = nSamplesPreSec * nBlockAlign; //每秒的数据率,就是每秒能采集多少字节的数据,nSamplesPerSec × nBlockAlign
waveFormate.nBlockAlign = nBlockAlign; //一个块的大小,采样bit的字节数乘以声道数, (nChannels × wBitsPerSample) / 8
waveFormate.wBitsPerSample = nBitsPerSample; //8 or 16,采样比特 8bits/次
waveFormate.cbSize = 0; //一般为0
CComPtr<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