如果不想处理有关语言的细节问题,只想显示和选择系统可提供的语音引擎,则可以直接调用Speech SDK提供的两个帮助函数SpInitTokenComboBox和SpInitTokenListBox来实现语音语言的显示和选择,其代码如下:
HRESULT SpInitTokenComboBox(
HWND hwnd,
const WCHAR* pszCatName,
const WCHAR* pszRequiredAttrib = NULL,
const WCHAR* pszOptionalAttrib = NULL
);
HRESULT SpInitTokenListBox(
HWND hwnd,
const WCHAR* pszCatName,
const WCHAR* pszRequiredAttrib = NULL,
const WCHAR* pszOptionalAttrib = NULL
);
CText2Speech类具有很好的错误处理机制。一旦调用某个函数发生了错误,响应的错误信息都将存放在m_sError数据成员中。可通过GetErrorString函数来获得错误描述。
11.2.2 示例:用CText2Speech类编制文字朗读程序
下面使用CText2Speech类来编写一个文字朗读程序Reciter,其界面如图11-3所示。
用Visual C++编制Reciter的步骤和要点如下:
1)使用AppWizard生成一个基于对话框的项目Reciter。
2)将Text2Speech.H,Text2Speech.CPP增加到Reciter项目中。
3)在资源编辑器中编辑好响应的控件。
4)用ClassWizard为控件在CReciterDlg 类中生成相应的成员。
5)修改ReciterDlg.h文件,为类CReciterDlg增加相应的变量和函数。
6)用ClassWizard为CReciterDlg 类添加对控件和消息的响应函数。ReciterDlg.h的代码如下所示:
#include "Text2Speech.h"
// CONTANTS OF MOUTH
#define CHARACTER_WIDTH 128
#define CHARACTER_HEIGHT 128
#define WEYESNAR 14 // eye positions
#define WEYESCLO 15
/
// Mouth Mapping Array (from Microsoft's TTSApp Example)
//
const int g_iMapVisemeToImage[22] =
{
0, // SP_VISEME_0 = 0, // Silence
11, // SP_VISEME_1, // AE, AX, AH
11, // SP_VISEME_2, // AA
11, // SP_VISEME_3, // AO
10, // SP_VISEME_4, // EY, EH, UH
11, // SP_VISEME_5, // ER
9, // SP_VISEME_6, // y, IY, IH, IX
2, // SP_VISEME_7, // w, UW
13, // SP_VISEME_8, // OW
9, // SP_VISEME_9, // AW
12, // SP_VISEME_10, // OY
11, // SP_VISEME_11, // AY
9, // SP_VISEME_12, // h
3, // SP_VISEME_13, // r
6, // SP_VISEME_14, // l
7, // SP_VISEME_15, // s, z
8, // SP_VISEME_16, // SH, CH, JH, ZH
5, // SP_VISEME_17, // TH, DH
4, // SP_VISEME_18, // f, v
7, // SP_VISEME_19, // d, t, n
9, // SP_VISEME_20, // k, g, NG
1 // SP_VISEME_21, // p, b, m
};
/
// CReciterDlg dialog
class CReciterDlg : public CDialog
{
// Construction
public:
CReciterDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CReciterDlg)
enum { IDD = IDD_RECITER_DIALOG };
CStatic m_cMouth;
CListBox m_ListVoices;
CString m_strText;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CReciterDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
CText2Speech m_Text2Speech;
void InitText2Speech();
void InitMouthImageList();
private:
CImageList m_cMouthList;
Int m_iMouthBmp;
CRect m_cMouthRect;
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
//{{AFX_MSG(CReciterDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnButtonSpeak();
afx_msg void OnSelchangeList1();
afx_msg void OnButtonStop();
afx_msg void OnButtonResume();
//}}AFX_MSG
afx_msg LRESULT OnMouthEvent(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
};
注意,在CReciterDlg类中定义了一个CText2Speech类的对象。
7)在ReciterDlg.cpp中编写各成员函数的代码。成员函数InitText2Speech用于初始化语音引擎并找出系统中的所有语音语言,显示在语音列表中,其代码如下所示:
void CReciterDlg::InitText2Speech()
{
if (! m_Text2Speech.Initialize(m_hWnd))
AfxMessageBox(m_Text2Speech.GetErrorString());
long lCount = m_Text2Speech.GetVoiceCount();
WCHAR* pszID;
for (long l=0; l<lCount; ++l)
{
m_Text2Speech.GetVoice(&pszID, l);
m_ListVoices.AddString(CString(pszID));
}
m_Text2Speech.GetVoice(&pszID, -1);
m_ListVoices.SelectString(0, CString(pszID));
}
成员函数InitMouthImageList用于初始化朗读者图像列表,其代码如下:
void CReciterDlg::InitMouthImageList()
{
m_cMouth.GetClientRect(&m_cMouthRect);
m_cMouth.ClientToScreen(&m_cMouthRect);
ScreenToClient(&m_cMouthRect);
m_cMouth.ShowWindow(SW_HIDE);
CBitmap bmp;
m_cMouthList.Create(CHARACTER_WIDTH, CHARACTER_HEIGHT, ILC_COLOR32 | ILC_MASK, 1, 0);
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICFULL));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH2));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH3));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH4));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH5));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH6));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH7));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH8));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH9));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH10));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH11));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH12));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICMOUTH13));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICEYESNAR));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
bmp.LoadBitmap(MAKEINTRESOURCE(IDB_MICEYESCLO));
m_cMouthList.Add(&bmp, RGB(255,0,255));
bmp.Detach();
m_cMouthList.SetOverlayImage(1, 1);
m_cMouthList.SetOverlayImage(2, 2);
m_cMouthList.SetOverlayImage(3, 3);
m_cMouthList.SetOverlayImage(4, 4);
m_cMouthList.SetOverlayImage(5, 5);
m_cMouthList.SetOverlayImage(6, 6);
m_cMouthList.SetOverlayImage(7, 7);
m_cMouthList.SetOverlayImage(8, 8);
m_cMouthList.SetOverlayImage(9, 9);
m_cMouthList.SetOverlayImage(10, 10);
m_cMouthList.SetOverlayImage(11, 11);
m_cMouthList.SetOverlayImage(12, 12);
m_cMouthList.SetOverlayImage(13, 13);
m_cMouthList.SetOverlayImage(14, WEYESNAR);
m_cMouthList.SetOverlayImage(15, WEYESCLO);
}
成员函数InitText2Speech和InitMouthImageList都在OnInitDialog函数中被调用。
响应朗读、暂停和继续的函数较简单,其代码如下:
void CReciterDlg::OnButtonSpeak()
{
// TODO: Add your control notification handler code here
UpdateData();
if (FAILED(m_Text2Speech.Speak(m_strText.AllocSysString(), SPF_ASYNC)))
AfxMessageBox(m_Text2Speech.GetErrorString());
}
void CReciterDlg::OnButtonStop()
{m_Text2Speech.Pause();}
void CReciterDlg::OnButtonResume()
{ m_Text2Speech.Resume(); }
选择列表中的语音语言并设为当前的语音设置是通过响应该列表的LBN_SELCHANGE消息来实现的。其消息响应函数为:
void CReciterDlg::OnSelchangeList1()
{
CString sVoice;
int nIndex = m_ListVoices.GetCurSel();
m_ListVoices.GetText(nIndex, sVoice);
BSTR bstr = sVoice.AllocSysString();
if (FAILED(m_Text2Speech.SetVoice(&bstr)))
AfxMessageBox(m_Text2Speech.GetErrorString());
}
为了显示朗读者肖像,需要将其肖像序列在OnPaint函数中画出来:
void CReciterDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this); // device context for painting
CDialog::OnPaint();
// Draw into memory DC
m_cMouthList.Draw(&dc, 0, m_cMouthRect.TopLeft(),
INDEXTOOVERLAYMASK(m_iMouthBmp));
if (m_iMouthBmp % 6 == 2)
{
m_cMouthList.Draw(&dc, WEYESNAR, m_cMouthRect.TopLeft(), 0 );
}
else if (m_iMouthBmp % 6 == 5)
{
m_cMouthList.Draw(&dc, WEYESCLO, m_cMouthRect.TopLeft(), 0 );
}
}
}
使朗读者的嘴型随着所朗读的文字变化的关键是响应CText2Speech类定义的消息WM_TTSEVENT。该消息指示出一个语音朗读事件已完成,即已发出某个音节。通过CSpEvent的GetFrom函数可以获得当前的事件信息,eEventId成员中记录了朗读的音节的代号。数组g_iMapVisemeToImage定义了音节代码和对应嘴形位图序列号的对应关系:
const int g_iMapVisemeToImage[22] =
{
0, // SP_VISEME_0 = 0, // Silence
11, // SP_VISEME_1, // AE, AX, AH
11, // SP_VISEME_2, // AA
11, // SP_VISEME_3, // AO
10, // SP_VISEME_4, // EY, EH, UH
11, // SP_VISEME_5, // ER
9, // SP_VISEME_6, // y, IY, IH, IX
2, // SP_VISEME_7, // w, UW
13, // SP_VISEME_8, // OW
9, // SP_VISEME_9, // AW
12, // SP_VISEME_10, // OY
11, // SP_VISEME_11, // AY
9, // SP_VISEME_12, // h
3, // SP_VISEME_13, // r
6, // SP_VISEME_14, // l
7, // SP_VISEME_15, // s, z
8, // SP_VISEME_16, // SH, CH, JH, ZH
5, // SP_VISEME_17, // TH, DH
4, // SP_VISEME_18, // f, v
7, // SP_VISEME_19, // d, t, n
9, // SP_VISEME_20, // k, g, NG
1 // SP_VISEME_21, // p, b, m
};
为了响应消息WM_TTSEVENT,需要添加相应的消息响应函数:
BEGIN_MESSAGE_MAP(CReciterDlg, CDialog)
//{{AFX_MSG_MAP(CReciterDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_SPEAK, OnButtonSpeak)
ON_LBN_SELCHANGE(IDC_LIST1, OnSelchangeList1)
ON_BN_CLICKED(IDC_BUTTON_STOP, OnButtonStop)
ON_BN_CLICKED(IDC_BUTTON_RESUME, OnButtonResume)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_TTSEVENT, OnMouthEvent)
END_MESSAGE_MAP()
LRESULT CReciterDlg::OnMouthEvent(WPARAM wParam, LPARAM lParam)
{
CSpEvent event;
while (event.GetFrom(m_Text2Speech.m_IpVoice) == S_OK) {
switch (event.eEventId) {
case SPEI_VISEME:
m_iMouthBmp = g_iMapVisemeToImage[event.Viseme()];
InvalidateRect(m_cMouthRect, false);
break;
}
}
return 0;
}
函数OnMouthEvent分析出当前事件,如果是SPEI_VISEME事件,则取得所读音节的嘴形位图序号,并重画朗读者的嘴部位置。
8)为了调用Speech引擎,应该在Microsoft Visual C++编程环境中设置好相应的include和lib设置,如下所述。
① 设置include路径
● 通过Project→Settings菜单项打开Project Settings对话框;
● 点击C/C++项;
● 在Category下拉列表中选取Preprocessor;
● 在“Additional include directories”编辑框中输入安装Speech SDK的include的路径,默认的路径是C:/Program Files/Microsoft Speech SDK 5.1/Include。
② 设置lib信息
● 通过Project→Settings菜单项打开Project Settings对话框;
● 选择Link项;
● 在Category下拉列表中选取Input项;
● 在“Additional library path”编辑框中输入安装Speech SDK的lib的路径,默认的路径是C:/Program Files/Microsoft Speech SDK 5.1/ Lib/i386;
● 将“sapi.lib”输入“Object/library modules”所标识的编辑框中。
9)编译连接该项目,你就可欣赏朗读者的风采了。
Reciter项目的所有源代码都存放在附盘的/Source/Reciter目录下。