使用微軟TTS語音引擎實現文本朗讀
版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/byxdaz/article/details/78443954
TTS(Text-To-Speech)是指文本語音的簡稱,即通過TTS引擎把文本轉化爲語音輸出。TTS語音引擎有微軟TTS語音引擎、科大訊飛語音引擎等。科大訊飛tts sdk參考這個頁面http://www.xfyun.cn/sdk/dispatcher
文本主要介紹如何使用微軟TTS語音引擎實現文本朗讀,以及生成wav格式的聲音文件。
1、語音引擎及語音庫的安裝
微軟TTS語音引擎提供了Windows Speech SDK開發包供編程者使用。Windows Speech SDK包含語音合成SS引擎和語音識別SR引擎兩種,語音合成引擎用於將文字轉換成語音輸出,語音識別引擎用於識別語音命令。
Windows Speech SDK可以在微軟的官網上免費下載,下載地址爲:http://www.microsoft.com/download/en/details.aspx?id=10121
在該下載界面中,選擇下載SpeechSDK51.exe、SpeechSDK51LangPach.exe和sapi.chm 即可。
SpeechSDK51.exe |
語音合成引擎 |
SpeechSDK51LangPach.exe |
語音庫,支持日語和簡體中文需要這個支持。 |
sapi.chm |
幫助文檔 |
speechsdk51MSM.exe |
語音引擎集成到你的產品跟產品一起發佈。解壓出來三個文件夾1033、1041和2052。其中,1033下主要是用於英文的TTS和SR的.msm文件,1041下主要是用於日文SR的.msm文件,2052下是用於中文TTS和SR的msm文件。 |
Sp5TTintXP.exe |
XP下Mike和Mary語音。 |
下載完成後,先安裝語音引擎SpeechSDK51.exe,再安裝中文語音庫SpeechSDK51LangPach.exe。
目前最常用的Windows Speech SDK版本有三種:5.1、5.3和5.4。
Windows Speech SDK 5.1版本支持xp系統和server 2003系統,需要下載安裝。XP系統默認只帶了個Microsoft Sam英文男聲語音庫,想要中文引擎就需要安裝Windows Speech SDK 5.1。
Windows Speech SDK 5.3版本支持Vista系統和Server 2008系統,已經集成到系統裏。Vista和Server 2003默認帶Microsoft lili中文女聲語音庫和Microsoft Anna英文女聲語音庫。
Windows Speech SDK 5.4版本支持Windows7系統,也已經集成到系統裏,不需要下載安裝。Win7系統同樣帶了Microsoft lili中文女聲語音庫和Microsoft Anna英文女聲語音庫。Microsoft lili支持中英文混讀。
2、SAPI接口的使用說明
1)、基本朗讀過程的實現
在使用語音引擎之前進行初始化:
ISpVoice *pSpVoice; // 重要COM接口 ::CoInitialize(NULL); // COM初始化 // 獲取ISpVoice接口 CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&pSpVoice);
獲取到ISpVoice接口以後,我們就可以通過pSpVoice指針調用SAPI接口了。
我們可以設置音量:pSpVoice->SetVolume(80);。SetVolume的參數即音量的範圍在0到100之間。
可以這樣朗讀字符串內容:pSpVoice->Speak(string, SPF_DEFAULT, NULL);。這樣string裏的內容就會被朗讀出來了,第二個參數SPF_DEFAULT表示使用默認設置,包括同步朗讀的設置。異步朗讀可以設置成 SPF_ASYNC。同步朗讀表示讀完string中的內容,speak函數纔會返回,而異步朗讀則將字符串送進去就返回,不會阻塞。
使用完語音引擎後應執行:
pSpVoice->Release(); ::CoUninitialize();
這樣資源被釋放,語音朗讀過程結束。
以上就完成了一個簡單的語音合成朗讀的功能。
2)、ISpVoice的成員函數
雞啄米再簡單說明幾個ISpVoice接口的成員函數:
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函數自行返回。
3)、使用XML朗讀
在進行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表示英文。
4)、設置SAPI通知消息。
SAPI在朗讀的過程中,會給指定窗口發送消息,窗口收到消息後,可以主動獲取SAPI的事件,根據事件的不同,用戶可以得到當前SAPI的一些信息,比如正在朗讀的單詞的位置,當前的朗讀口型值(用於顯示動畫口型,中文語音的情況下並不提供這個事件)等等。要獲取SAPI的通知,首先要註冊一個消息:
m_cpVoice->SetNotifyWindowMessage( hWnd,WM_TTSAPPCUSTOMEVENT, 0, 0 );
這個代碼一般是在主窗口初始化的時候調用,hWnd是主窗口(或者接收消息的窗口)句柄。WM_TTSAPPCUSTOMEVENT是用戶自定義消息。在窗口響應WM_TTSAPPCUSTOMEVENT消息的函數中,通過如下代碼獲取sapi的通知事件:
CSpEvent event; // 使用這個類,比用 SPEVENT結構更方便
while(event.GetFrom(m_cpVoice) == S_OK )
{
switch( event.eEventId )
{
...
}
}
eEventID有很多種,比如SPEI_START_INPUT_STREAM表示開始朗讀,SPEI_END_INPUT_STREAM表示朗讀結束等。
可以根據需要進行判斷使用。
5)、speech sdk語音識別,識別語音生成英文/中文等字符串。
具體參考這篇文章:http://blog.csdn.net/artemisrj/article/details/8723095
3、編程實例
1)、首先將需要將Windows Speech SDK開發包的頭文件和庫文件所在路徑添加到編譯器中。
2)、封裝tts操作類。
//TextToSpeech.h文件
-
//tts
-
#pragma once
-
#include <sapi.h> //包含TTS語音引擎頭文件和庫文件
-
#include <sphelper.h>
-
#include <string.h>
-
#pragma comment(lib, "sapi.lib")
-
class TextToSpeech
-
{
-
public:
-
TextToSpeech(void);
-
virtual ~TextToSpeech(void);
-
int Init();
-
int UnInit();
-
//枚舉所有語音Token
-
int EnumAudioToken(CString arrayVoicePackageName[],int nVoicePackageNameCount);
-
//創建SpVoice
-
int CreateSpVoice();
-
//釋放SpVoice
-
int DeleteSpVoice();
-
//重置SpVoice(用於臨時清除朗讀數據)
-
int ResetSpVoice();
-
//設置朗讀速度(取值範圍:-10到10)
-
int SetRate( long RateAdjust);
-
//獲取朗讀速度
-
int GetRate(long *pRateAdjust);
-
//設置使用的語音庫
-
int SetVoice(ISpObjectToken *pToken);
-
//獲取語音庫
-
int GetVoice(unsigned int nIndex,ISpObjectToken** ppToken);
-
//設置音量(取值範圍:0到100)
-
int SetVolume(USHORT usVolume);
-
//獲取音量
-
int GetVolume(USHORT *pusVolume);
-
//朗讀
-
int Speak(CString strContent,DWORD dwFlags=SPF_DEFAULT);
-
//朗讀生成文件
-
int SpeakToWaveFile(CString strContent,char *pFilePathName,DWORD dwFlags=SPF_DEFAULT);
-
//暫停朗讀
-
int Pause();
-
//繼續朗讀
-
int Resume();
-
//跳過部分朗讀
-
int Skip(CString strItemType="Sentence",long lNumItems=65535, ULONG *pulNumSkipped=NULL);
-
protected:
-
IEnumSpObjectTokens * m_pIEnumSpObjectTokens;
-
ISpObjectToken * m_pISpObjectToken;
-
ISpVoice * m_pISpVoice;
-
BOOL m_bComInit;
-
};
//TextToSpeech.cpp文件
-
#include "StdAfx.h"
-
#include "TextToSpeech.h"
-
TextToSpeech::TextToSpeech(void)
-
{
-
m_pIEnumSpObjectTokens = NULL;
-
m_pISpObjectToken = NULL;
-
m_pISpVoice = NULL;
-
m_bComInit = FALSE;
-
}
-
TextToSpeech::~TextToSpeech(void)
-
{
-
}
-
int TextToSpeech::Init()
-
{
-
//初始化COM組件
-
if(FAILED(::CoInitializeEx(NULL,0)))
-
{
-
//MessageBox("初始化COM組件失敗!", "提示", MB_OK|MB_ICONWARNING);
-
return -1;
-
}
-
m_bComInit = TRUE;
-
return 0;
-
}
-
int TextToSpeech::UnInit()
-
{
-
if(m_bComInit)
-
{
-
::CoUninitialize();
-
}
-
return 0;
-
}
-
int TextToSpeech::EnumAudioToken(CString arrayVoicePackageName[],int nVoicePackageNameCount)
-
{
-
//枚舉所有語音Token
-
if(SUCCEEDED(SpEnumTokens(SPCAT_VOICES, NULL, NULL, &m_pIEnumSpObjectTokens)))
-
{
-
//得到所有語音Token的個數
-
ULONG ulTokensNumber = 0;
-
m_pIEnumSpObjectTokens->GetCount(&ulTokensNumber);
-
//檢測該機器是否安裝有語音包
-
if(ulTokensNumber == 0)
-
{
-
//MessageBox("該機器沒有安裝語音包!", "提示", MB_OK|MB_ICONWARNING);
-
return -1;
-
}
-
if(ulTokensNumber > nVoicePackageNameCount)
-
{
-
//緩衝區過小
-
return 0;
-
}
-
//將語音包的名字加入數組中
-
CString strVoicePackageName = _T("");
-
CString strTokenPrefixText = _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\");
-
for(ULONG i=0; i<ulTokensNumber; i++)
-
{
-
m_pIEnumSpObjectTokens->Item(i, &m_pISpObjectToken);
-
WCHAR* pChar;
-
m_pISpObjectToken->GetId(&pChar);
-
strVoicePackageName = pChar;
-
strVoicePackageName.Delete(0, strTokenPrefixText.GetLength());
-
arrayVoicePackageName[i] = strVoicePackageName;
-
}
-
return ulTokensNumber;
-
}
-
return -1;
-
}
-
//創建SpVoice
-
int TextToSpeech::CreateSpVoice()
-
{
-
//獲取ISpVoice接口
-
if(FAILED(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&m_pISpVoice)))
-
{
-
//MessageBox("獲取ISpVoice接口失敗!", "提示", MB_OK|MB_ICONWARNING);
-
return -1;
-
}
-
return 0;
-
}
-
//釋放SpVoice
-
int TextToSpeech::DeleteSpVoice()
-
{
-
if(m_pISpVoice != NULL)
-
{
-
m_pISpVoice->Release();
-
}
-
m_pISpVoice = NULL;
-
return 0;
-
}
-
//重置SpVoice
-
int TextToSpeech::ResetSpVoice()
-
{
-
DeleteSpVoice();
-
return CreateSpVoice();
-
}
-
//設置朗讀速度(取值範圍:-10到10)
-
int TextToSpeech::SetRate( long RateAdjust)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
//設置播放速度
-
m_pISpVoice->SetRate(RateAdjust);
-
return 0;
-
}
-
//獲取朗讀速度
-
int TextToSpeech::GetRate(long *pRateAdjust)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
m_pISpVoice->GetRate(pRateAdjust);
-
return 0;
-
}
-
//設置使用的語音庫
-
int TextToSpeech::SetVoice(ISpObjectToken *pToken)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
m_pISpVoice->SetVoice(pToken);
-
return 0;
-
}
-
//獲取語音庫
-
int TextToSpeech::GetVoice(unsigned int nIndex,ISpObjectToken** ppToken)
-
{
-
if(m_pIEnumSpObjectTokens == NULL)
-
return -1;
-
//設置語言
-
m_pIEnumSpObjectTokens->Item(nIndex, ppToken);
-
m_pISpObjectToken = *ppToken;
-
return 0;
-
}
-
//設置音量(取值範圍:0到100)
-
int TextToSpeech::SetVolume(USHORT usVolume)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
//設置音量大小
-
m_pISpVoice->SetVolume(usVolume);
-
return 0;
-
}
-
//獲取音量
-
int TextToSpeech::GetVolume(USHORT *pusVolume)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
//設置音量大小
-
m_pISpVoice->GetVolume(pusVolume);
-
return 0;
-
}
-
//開始朗讀
-
int TextToSpeech::Speak(CString strContent, DWORD dwFlags)
-
{
-
if(m_pISpVoice == NULL)
-
return -1;
-
//開始進行朗讀
-
HRESULT hSucess = m_pISpVoice->Speak(strContent.AllocSysString(), dwFlags, NULL);
-
return 0;
-
}
-
//朗讀生成文件
-
int TextToSpeech::SpeakToWaveFile(CString strContent,char *pFilePathName,DWORD dwFlags)
-
{
-
if(m_pISpVoice == NULL || pFilePathName == NULL)
-
return -1;
-
//生成WAV文件
-
CComPtr<ISpStream> cpISpStream;
-
CComPtr<ISpStreamFormat> cpISpStreamFormat;
-
CSpStreamFormat spStreamFormat;
-
m_pISpVoice->GetOutputStream(&cpISpStreamFormat);
-
spStreamFormat.AssignFormat(cpISpStreamFormat);
-
HRESULT hResult = SPBindToFile(pFilePathName, SPFM_CREATE_ALWAYS,
-
&cpISpStream, &spStreamFormat.FormatId(), spStreamFormat.WaveFormatExPtr());
-
if(SUCCEEDED(hResult))
-
{
-
m_pISpVoice->SetOutput(cpISpStream, TRUE);
-
m_pISpVoice->Speak(strContent.AllocSysString(), dwFlags, NULL);
-
return 0;
-
//MessageBox("生成WAV文件成功!", "提示", MB_OK);
-
}
-
else
-
{
-
//MessageBox("生成WAV文件失敗!", "提示", MB_OK|MB_ICONWARNING);
-
return 1;
-
}
-
}
-
//暫停朗讀
-
int TextToSpeech::Pause()
-
{
-
if(m_pISpVoice != NULL)
-
{
-
m_pISpVoice->Pause();
-
}
-
return 0;
-
}
-
//繼續朗讀
-
int TextToSpeech::Resume()
-
{
-
if(m_pISpVoice != NULL)
-
{
-
m_pISpVoice->Resume();
-
}
-
return 0;
-
}
//跳過部分朗讀 int TextToSpeech::Skip(CString strItemType,long lNumItems, ULONG *pulNumSkipped) { if(m_pISpVoice == NULL || strItemType.GetLength() == 0) return -1;
m_pISpVoice->Skip(strItemType.AllocSysString(), lNumItems,pulNumSkipped); return 0; }
3)調用實例代碼。
-
TextToSpeech ttsSpeech;
-
ttsSpeech.Init();
-
CString arrayVoicePackageName[50] = {0};
-
int nVoicePackageNameCount = 50;
-
int nCount = ttsSpeech.EnumAudioToken(arrayVoicePackageName,nVoicePackageNameCount);
-
ttsSpeech.CreateSpVoice();
-
ISpObjectToken* ppToken = NULL;
-
ttsSpeech.GetVoice(0,&ppToken);
-
ttsSpeech.SetVoice(ppToken);
-
ttsSpeech.SetRate(0);
-
ttsSpeech.SetVolume(100);
-
ttsSpeech.Speak("我是中國人");
-
//ttsSpeech.SpeakToWaveFile("我是中國人","d:\\11.wav");
-
ttsSpeech.DeleteSpVoice();
-
ttsSpeech.UnInit();
4、注意事項
1)、sphelper.h編譯錯誤解決方案
SAPI 包含sphelper.h編譯錯誤解決方案 在使用Microsoft Speech SDK 5.1開發語音識別程序時,包含了頭文件“sphelper.h”和庫文件“sapi.lib”。編譯時出錯: 1>c:\program files\microsoft speech sdk 5.1\include\sphelper.h(769): error C4430: missing type specifier - int assumed. Note: C++ does not supportdefault-int 1>c:\program files\microsoft speech sdk5.1\include\sphelper.h(1419) : error C4430: missing type specifier - intassumed. Note: C++ does not support default-int 1>c:\program files\microsoftspeech sdk 5.1\include\sphelper.h(2373) : error C2065: 'psz' : undeclaredidentifier 1>c:\program files\microsoft speech sdk5.1\include\sphelper.h(2559) : error C2440: 'initializing' : cannot convert from'CSpDynamicString' to 'SPPHONEID *' 1> No user-defined-conversion operatoravailable that can perform this conversion, or the operator cannot be called1>c:\program files\microsoft speech sdk 5.1\include\sphelper.h(2633) : errorC2664: 'wcslen' : cannot convert parameter 1 from 'SPPHONEID *' to 'constwchar_t *' 1> Types pointed to are unrelated; conversion requiresreinterpret_cast, C-style cast or function-style cast 搜索了一圈,根據大家的經驗彙總,應該是Speech代碼編寫時間太早,語法不嚴密。而VS2008對於語法檢查非常嚴格,導致編譯無法通過。修改頭文件中的以下行即可正常編譯:
Ln769 const ulLenVendorPreferred = wcslen(pszVendorPreferred);
const unsigned long ulLenVendorPreferred = wcslen(pszVendorPreferred);
Ln 1418static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
Ln 2372for (const WCHAR * psz = (const WCHAR *)lParam; *psz; psz++) {}
const WCHAR * psz; for (psz = (const WCHAR *)lParam; *psz; psz++) {}
Ln 2559SPPHONEID* pphoneId = dsPhoneId;
SPPHONEID* pphoneId = (SPPHONEID*)((WCHAR *)dsPhoneId);
Ln 2633pphoneId += wcslen(pphoneId) + 1;
pphoneId+= wcslen((const wchar_t *)pphoneId) + 1;
2)、Speak指定爲SPF_ASYNC(異步)時,不要過早的釋放ISpVoice對象,否則就沒有聲音,因爲ISpVoice生命週期結束了,就不會播放。一般將ISpVoice對象放到類的成員變量中,類析構時才釋放ISpVoice對象。
3)、Speak第一次朗讀時很慢,因爲加載引擎需要一段時間,可以使用線程預先Speak("",SPF_ASYNC)而加載引擎,但需要注意的是在初始化COM的時候使用CoInitializeEx,而不要使用CoInitialize。