SAPI
SAPI
SAPI是軟體中的語音技術包括兩方面的內容,一個是語音識別(speech recognition) 和語音合成(speech synthesis)。這兩個技術都需要語音引擎的支持。
微軟推出的應用編程介面API,雖然不是業界標準,但是應用比較廣泛。
SAPI包括以下組件對象(介面):
(1)Voice Commands API。對應用程序進行控制,一般用於語音識別系統中。識別某個命令后,會調用相關介面是應用程序完成對應的功能。如果程序想實現語音控制,必須使用此組對象。
(2)Voice Dictation API。聽寫輸入,即語音識別介面。
(3)Voice Text API。完成從文字到語音的轉換,即語音合成。
(4)Voice Telephone API。語音識別和語音合成綜合運用到電話系統之上,利用此介面可以建立一個電話應答系統,甚至可以通過電話控制計算機。
(5)Audio Objects API。封裝了計算機發音系統。
SAPI是架構在COM基礎上的,微軟還提供了ActiveX控制項,所以不僅可用於一般的windows程序,還可以用於網頁、VBA甚至EXCEL的圖表中。如果對COM感到陌生,還可以使用微軟的C++ WRAPPERS,它用C++類封裝了語音SDK COM對象。
首先下載開發包
Microsoft Speech SDK 5.1添加了Automation支持。所以可以在VB,ECMAScript等支持Automation的語言中使用。
版本說明:
Version: 5.1
語音: English
下載尺寸: 2.0 MB - 288.8 MB
這個SDK開發包還包括了可以隨便發布的英文和中文的語音合成引擎(TTS),和英文、中文、日文的語音識別引擎(SR)。
系統要求98以上版本。編譯開發包中的例子程序需要vc6以上環境。
******下載說明******:
(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裡面已經包含。
下載完畢后,首先安裝SpeechSDK51.exe,然後安裝中文語言補丁包SpeechSDK51LangPack,然後展開
msttss22l,自動將所需dll安裝到系統目錄。
在vc6.0的環境下編譯語音工程,首先要配置編譯環境。假設sdk安裝在d:\Microsoft Speech SDK 5.1\路徑下,打開工程設置對話框,在c/c++欄中選擇Preprocessor分類,然後在"附加包含路徑"中輸入
d:\Microsoft Speech SDK 5.1\include
告訴vc編譯程序所需的SAPI頭文件的位置。
然後切換到LINK欄,在Input分類下的附加庫路徑中輸入:
d:\Microsoft Speech SDK 5.1\lib\i386
使vc在鏈接的時候能夠找到sapi.lib。
即使用SAPI實現TTS(Text to Speech)。
首先要初始化語音介面
ISpVoice* pVoice;
::CoInitialize(NULL);
(void **)&pVoice);
然後就可以使用這個指針調用SAPI函數了,例如
pVoice->SetVolume(50);//設置音量
pVoice->Speak(str.AllocSysString(),SPF_ASYNC,NULL);
另外也可以使用如下方式:
CComPtr m_cpVoice;
HRESULT hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
在下面的例子中都用這個m_cpVoice變數。
CLSID_SpVoice的定義位於SPAI.H中。
獲取/設置輸出頻率
SAPI朗讀文字的時候,可以採用多種頻率方式輸出聲音,比如:
8kHz 8Bit Mono、8kHz 8Bit Stereo、44kHz 16Bit Mono、44kHz 16Bit Stereo等。在音調上有所差別。具體可以參考sapi.h。
可以使用如下代碼獲取當前的配置:
CComPtr 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
3 通過如下代碼設置當前朗讀頻率
:
CComPtr m_cpOutAudio; //聲音輸出介面
SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &m_cpOutAudio ); //創建介面
SPSTREAMFORMAT eFmt = 21; //SPSF_22kHz 8Bit Stereo
CSpStreamFormat Fmt;
Fmt.AssignFormat(eFmt);
if ( m_cpOutAudio )
{
}
else hr = E_FAIL;
if( SUCCEEDED( hr ) )
{
m_cpVoice->SetOutput( m_cpOutAudio, FALSE );
}
獲取/設置播放所用語音
引擎中所用的語音數據文件一般保存在SpeechEngines下的spd或者vce文件中。安裝sdk后,在註冊表中保存了可用的語音,比如英文的男/女,簡體中文的男音等。位置是:
HKEY_LOCAL_MACHINE\Software\Microsoft\Speech\Voices\Tokens
如果安裝在中文操作系統下,則預設所用的朗讀語音是簡體中文。SAPI的缺點是不能支持中英文混讀,在朗讀中文的時候,遇到英文,只能逐個字母讀出。所以需要程序自己進行語音切換。
(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 );
(3) 用下面的函數獲取當前正在使用的語音:
CComPtr 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);
開始/暫停/恢復/結束當前的朗讀
要朗讀的文字必須位於寬字元串中,假設位於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 );
暫停的代碼: m_cpVoice->Pause();
恢復的代碼: m_cpVoice->Resume();
結束的代碼:(上面的例子中已經給出了)
hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);
跳過部分朗讀的文字
在朗讀的過程中,可以跳過部分文字繼續後面的朗讀,代碼如下:
ULONG ulGarbage = 0;
WCHAR szGarbage[] = L"Sentence";
hr = m_cpVoice->Skip( szGarbage, SkipNum, &ulGarbage );
SkipNum是設置要跳過的句子數量,值可以是正/負。
根據sdk的說明,SAPI僅僅支持SENTENCE這個類型。SAPI是通過標點符號來區分句子的。
6、播放WAV文件。SAPI可以播放WAV文件,這是通過ISpStream介面實現的:
CComPtr cpWavStream;
WCHAR szwWavFileName[NORM_SIZE] = L"";;
USES_CONVERSION;
wcscpy( szwWavFileName, T2W( szAFileName ) );//從ANSI將WAV文件的名字轉換成寬字元串
//使用sphelper.h 提供的這個函數打開 wav 文件,並得到一個 IStream指針
hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream );
if( SUCCEEDED( hr ) )
{
m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL );//播放WAV文件
}
將朗讀的結果保存到wav文件
TCHAR szFileName[256];//假設這裡面保存著目標文件的路徑
USES_CONVERSION;
WCHAR m_szWFileName[MAX_FILE_PATH];
wcscpy( m_szWFileName, T2W(szFileName) );//轉換成寬字元串
//創建一個輸出流,綁定到wav文件
CSpStreamFormat OriginalFmt;
CComPtr cpWavStream;
CComPtr 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 );
//等待朗讀結束
m_cpVoice->WaitUntilDone( INFINITE );
cpWavStream.Release();
//把輸出重新定位到原來的流
m_cpVoice->SetOutput( cpOldStream, FALSE );
設置朗讀音量和速度
m_cpVoice->SetVolume((USHORT)hpos); //設置音量,範圍是 0 - 100
m_cpVoice->SetRate(hpos); //設置速度,範圍是 -10 - 10
hpos的值一般位於
設置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表示朗讀結束等。
可以根據需要進行判斷使用。
C#調用SAPI實現語音合成的兩種方法:
我們都知道現在的語音合成TTS是可以通過微軟的SAPI實現的,好處我就不多說了,方便而已,因為在微軟的操作系統裡面就自帶了這個玩意,主要的方式有兩種:
2、使用WIN7的windows api,其實最終還是調用了SAPI,所以開發出來的東西就只能在WIN7上面跑。
其實不管是哪一種,都是調用SAPI,可能后一種代碼比較簡單,使用已經安裝的TTS引擎,現在一般用NeoSpeech,這個就不解釋了,太強大了這個發音。。。
COM組件技術:
C#代碼
public class Speach
{
private SpeechLib.SpVoiceClass voice =null; //SAPI5.1
private SpeechLib.SpVoice voice = null;//SAPI 5.4
private Speach()
{
BuildSpeach() ;
}
public static Speach instance()
{
if (_Instance == null)
_Instance = new Speach() ;
return _Instance ;
}
private void SetChinaVoice()
{
voice.Voice = voice.GetVoices(string.Empty,string.Empty).Item(0) ;
}
private void SetEnglishVoice()
{
voice.Voice = voice.GetVoices(string.Empty,string.Empty).Item(1) ;
}
private void SpeakChina(string strSpeak)
{
SetChinaVoice() ;
Speak(strSpeak) ;
}
private void SpeakEnglishi(string strSpeak)
{
SetEnglishVoice() ;
Speak(strSpeak) ;
}
public void AnalyseSpeak(string strSpeak)
{
int iCbeg = 0 ;
int iEbeg = 0 ;
bool IsChina = true ;
for(int i=0;i
{
char chr = strSpeak[i] ;
if (IsChina)
{
if (chr<=122&&chr>=65)
{
int iLen = i - iCbeg ;
string strValue = strSpeak.Substring(iCbeg,iLen) ;
SpeakChina(strValue) ;
iEbeg = i ;
IsChina = false ;
}
}
else
{
if (chr>122||chr<65)
{
int iLen = i - iEbeg ;
string strValue = strSpeak.Substring(iEbeg,iLen) ;
this.SpeakEnglishi(strValue) ;
iCbeg = i ;
IsChina = true ;
}
}
}//end for
if (IsChina)
{
int iLen = strSpeak.Length - iCbeg ;
string strValue = strSpeak.Substring(iCbeg,iLen) ;
SpeakChina(strValue) ;
}
else
{
int iLen = strSpeak.Length - iEbeg ;
string strValue = strSpeak.Substring(iEbeg,iLen) ;
SpeakEnglishi(strValue) ;
}
}
private void BuildSpeach()
{
if (voice == null)
voice = new SpVoiceClass() ;
}
public int Volume
{
get
{
return voice.Volume ;
}
set
{
voice.SetVolume((ushort)(value)) ;
}
}
public int Rate
{
get
{
return voice.Rate ;
}
set
{
voice.SetRate(value) ;
}
}
private void Speak(string strSpeack)
{
try
{
voice.Speak(strSpeack,SpeechVoiceSpeakFlags.SVSFlagsAsync) ;
}
catch(Exception err)
{
throw(new Exception("發生一個錯誤:"+err.Message)) ;
}
}
public void Stop()
{
voice.Speak(string.Empty,SpeechLib.SpeechVoiceSpeakFlags.SVSFPurgeBeforeSpeak) ;
}
public void Pause()
{
voice.Pause() ;
}
public void Continue()
{
voice.Resume() ;
}
}//end class
在 private SpeechLib.SpVoiceClass voice =null;這裡,我們定義個一個用來發音的類,並且在第一次調用該類時,對它用BuildSpeach方法進行了初始化。
我們還定義了兩個屬性Volume和Rate,能夠設置音量和語速。
我們知道,SpVoiceClass 有一個Speak方法,我們發音主要就是給他傳遞一個字元串,它負責讀出該字元串,如下所示。
C#代碼
private void Speak(string strSpeack)
{
try
{
voice.Speak(strSpeack,SpeechVoiceSpeakFlags.SVSFlagsAsync) ;
}
catch(Exception err)
{
throw(new Exception("發生一個錯誤:"+err.Message)) ;
}
}
第二種使用.NET類庫和系統API的代碼如下:
C#代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Speech.Synthesis;
using System.Speech;
namespace StudyBeta
{
public class SRead
{
public SpeechSynthesizer synth; //語音合成對象
public SRead()
{
synth = new SpeechSynthesizer();
}
public SRead(int m, int n)
{
//使用 synth 設置朗讀音量 [範圍 0 ~ 100]
synth.Volume = m;
//使用 synth 設置朗讀頻率 [範圍 -10 ~ 10]
synth.Rate = n;
}
public void SpeakChina(string ggg)
{
//SpVoice Voice = new SpVoice();
synth.SelectVoice("Microsoft Lili");
//Voice.Speak(ggg, SpFlags);
synth.SpeakAsync(ggg);
//String speechPeople = synth.Voice;
//使用 synth 設置朗讀音量 [範圍 0 ~ 100]
// synth.Volume = 80;
//使用 synth 設置朗讀頻率 [範圍 -10 ~ 10]
// synth.Rate = 0;
//使用synth 合成 wav 音頻文件:
//synth.SetOutputToWaveFile(string path);
}
public void SpeakEnglish(string ggg)
{
//SpVoice Voice = new SpVoice();
synth.SelectVoice("VW Julie");
synth.Speak(ggg); //ggg為要合成的內容
}
public int m
{
get
{
return synth.Volume;
}
set
{
synth.Volume = value;
}
}
public int n
{
get
{
return synth.Rate;
}
set
{
synth.Rate = value;
}
}
}
SAPI的功能很多,比如語音識別、使用語法分析等,由於條件和精力有限,我未能一一嘗試,感興趣的朋友可以自己安裝一個研究一下。
目錄