文本转语音转换和口部运动动画的使用






3.38/5 (21投票s)
2001年11月28日
4分钟阅读

213512

6246
此程序展示了如何将文本转换为语音并使用口部运动
引言
此代码部分基于 James Matthews 的原创文章 Visemes: Representing Mouth Positions。
图 1:应用程序 GUI
本项目展示了如何将文本转换为语音,并随后进行嘴部运动。在构建本项目之前,我建议您阅读我的文章,即《使用 SAPI 进行文本到语音的简单程序》。以下是构建项目的步骤:
- 新建应用程序
- 在项目中设置 SAPI
- 构建项目的 GUI
- 编码
- 测试
1. 新建应用程序
使用 MFC AppWizard (exe) 创建您的应用程序,并将项目名称键入为 SpeakMouth。单击 OK 按钮。在 MFC AppWizard 的第一步中,选择 Dialog based,然后单击 Finish 按钮。
图 2:新建应用程序。
图 3:基于对话框的应用程序。单击 Finish 按钮。
2. 在项目中设置 SAPI
在此项目中设置 SAPI 的方法与文章《使用 SAPI 进行文本到语音的简单程序》中介绍的相同。
3. 构建项目的 GUI
图 4:GUI 模型
根据图 4,您可以像上面那样构建和设计。以下是 GUI 组件的 ID 和属性:
否 | 组件名称 | ID | 使用 ClassWizard 添加变量 |
1 | 图片 | IDC_MOUTH_IMG | CStatic :: m_cMouth |
2 | 分组框 | IDC_STATIC | - |
3 | 编辑框 | IDC_TEXT | CString :: m_sText |
4 | Button | IDC_SPEAK_BTN | - |
本项目 GUI 模型如图 4 所示。构建 GUI 后,您必须在项目的 Resource View 中导入位图文件,其中包含嘴部的图形(图 5)。在项目中的 res 文件夹中选择位图文件(图 6)。我在本项目 res 文件夹中放置了嘴部形状的位图文件,您也可以在 Speech SDK 中找到这些位图文件。您必须导入这些位图文件。
- mic eyes closed.bmp
- mic.bmp
- mic_eyes_narrow.bmp
- mic_mouth_2.bmp
- mic_mouth_3.bmp
- mic_mouth_4.bmp
- mic_mouth_5.bmp
- mic_mouth_6.bmp
- mic_mouth_7.bmp
- mic_mouth_8.bmp
- mic_mouth_9.bmp
- mic_mouth_10.bmp
- mic_mouth_11.bmp
- mic_mouth_12.bmp
- mic_mouth_13.bmp
图 5:将位图文件导入项目
图 6:选择位图文件
将所有位图文件放入项目的 Resource View 后,您还必须设置这些位图文件的属性,即分配位图文件的 ID。您可以右键单击每个位图文件(图 7)来执行此操作,您将看到类似图 8 的窗口。以下是位图文件的 ID:
位图文件 | ID |
mic eyes closed.bmp | IDB_MICEYESCLO |
mic.bmp | IDB_MICFULL |
mic_eyes_narrow.bmp | IDB_MICEYESNAR |
mic_mouth_2.bmp | IDB_MICMOUTH2 |
mic_mouth_3.bmp | IDB_MICMOUTH3 |
mic_mouth_4.bmp | IDB_MICMOUTH4 |
mic_mouth_5.bmp | IDB_MICMOUTH5 |
mic_mouth_6.bmp | IDB_MICMOUTH6 |
mic_mouth_7.bmp | IDB_MICMOUTH7 |
mic_mouth_8.bmp | IDB_MICMOUTH8 |
mic_mouth_9.bmp | IDB_MICMOUTH9 |
mic_mouth_10.bmp | IDB_MICMOUTH10 |
mic_mouth_11.bmp | IDB_MICMOUTH11 |
mic_mouth_12.bmp | IDB_MICMOUTH12 |
mic_mouth_13.bmp | IDB_MICMOUTH13 |
图 7:设置位图文件属性
图 8:位图文件属性
4. 编码
本项目有三个类(图 8),即:
CAboutDlg
CSpeakMouthApp
CSpeakMouthDlg
图 9:项目的类
调用 ClassWizard 为 CSpeakMouthDlg
类添加变量,将变量名键入为 m_sError
,其类型为 CString
,设置为 protected
。
以下是编码过程的步骤:
- 在 StdAfx.h 文件中,添加以下代码行以访问 SAPI COM:
- 在
CSpeakMouthDlg
类中,在 SpeakMouthDlg.h 文件的顶部添加代码行: - 在
CSpeakMouthDlg
类中,添加成员方法(只需右键单击,然后选择 Add Member Method)。方法:InitMouthImageList()::BOOL
、InitializationSAPI()::BOOL
和DestroySAPI()::BOOL
,全部在 protected 模式下。此外,添加变量,代码如下: - 现在,您可以实现您创建的方法了。
- 在
CSpeakMouthDlg
类中,OnInitDialog()
方法必须包含以下用于 SAPI 初始化 的代码: - 在
CSpeakMouthDlg::CSpeakMouthDlg(CWnd* pParent=NULL)
类的构造函数中: - 现在,您必须创建方法来处理将语音转换为嘴部运动的事件。您可以发送消息处理程序,即:您已定义为
WM_USER + 101
的WM_RECOEVENT
。在 SpeakMouthDlg.h 文件中,键入: - 现在,添加一个方法来处理按钮单击(
IDC_SPEAK_BTN
)事件。键入OnSpeak
作为方法的名称,并添加以下代码行作为其实现: - 这样,您就可以调试并运行您的程序了。
#include <atlbase.h> extern CComModule _Module; #include <atlcom.h>
#include "sapi.h" #include <sphelper.h> // CONTANTS OF MOUTH #define CHARACTER_WIDTH 128 #define CHARACTER_HEIGHT 128 #define WEYESNAR 14 // eye positions #define WEYESCLO 15此外,在
CDialog
类(SpeakMouthDlg.cpp 文件)的顶部添加代码行:#define WM_RECOEVENT WM_USER+101 ///////////////////////////////////////////////////////// // 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 }; ////////////////////////////////////////////////////////////
CString m_sError; BOOL InitMouthImageList(); BOOL DestroySAPI(); BOOL InitializationSAPI(); /////////////////////////////////////////////////////// // Speech API Variables CComPtr<ISpVoice> IpVoice; CImageList m_cMouthList; int m_iMouthBmp; CRect m_cMouthRect; ///////////////////////////////////////////////////////
void CSpeakMouthDlg::OnDestroy() { DestroySAPI(); CDialog::OnDestroy(); } BOOL CSpeakMouthDlg::InitializationSAPI() { if (FAILED(CoInitialize(NULL))) { m_sError=_T("Error intialization COM"); return FALSE; } HRESULT hRes; hRes = IpVoice.CoCreateInstance(CLSID_SpVoice); if (FAILED(hRes)) { m_sError=_T("Error creating voice"); return FALSE; } hRes = IpVoice->SetInterest(SPFEI(SPEI_VISEME), SPFEI(SPEI_VISEME)); if (FAILED(hRes)) { m_sError=_T("Error creating interest...seriously"); return FALSE; } hRes = IpVoice->SetNotifyWindowMessage(m_hWnd, WM_RECOEVENT, 0, 0); if (FAILED(hRes)) { m_sError=_T("Error setting notification window"); return FALSE; } return TRUE; } BOOL CSpeakMouthDlg::DestroySAPI() { if (IpVoice) { IpVoice.Release(); } return TRUE; } BOOL CSpeakMouthDlg::InitMouthImageList() { m_cMouth.GetClientRect(&m_cMouthRect); m_cMouth.ClientToScreen(&m_cMouthRect); ScreenToClient(&m_cMouthRect); 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); return TRUE; }
// Set the icon for this dialog. // The framework does this automatically // when the application's main window // is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon((HICON)(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, 16, 16, 0)), FALSE); m_sError=_T(""); if (!InitializationSAPI()) { AfxMessageBox(m_sError); DestroySAPI(); } InitMouthImageList(); return TRUE;
m_iMouthBmp = 0;
// Generated message map functions //{{AFX_MSG(CSpeakMouthDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnDestroy(); afx_msg void OnSpeak(); //}}AFX_MSG // add message handler afx_msg LRESULT OnMouthEvent(WPARAM, LPARAM); DECLARE_MESSAGE_MAP()并在 SpeakMouthDlg.cpp 文件中,添加以下代码行:
BEGIN_MESSAGE_MAP(CSpeakMouthDlg, CDialog) //{{AFX_MSG_MAP(CSpeakMouthDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_DESTROY() ON_BN_CLICKED(IDC_SPEAK_BTN, OnSpeak) //}}AFX_MSG_MAP // add message handler ON_MESSAGE(WM_RECOEVENT, OnMouthEvent) END_MESSAGE_MAP()
这是 OnMouthEvent()
方法的实现:
LRESULT CSpeakMouthDlg::OnMouthEvent(WPARAM wParam, LPARAM lParam) { CSpEvent event; while (event.GetFrom(IpVoice) == S_OK) { switch (event.eEventId) { case SPEI_VISEME: m_iMouthBmp = g_iMapVisemeToImage[event.Viseme()]; InvalidateRect(m_cMouthRect, false); break; } } return 0; }
void CSpeakMouthDlg::OnSpeak()
{
UpdateData();
USES_CONVERSION;
IpVoice->Speak(m_sText.AllocSysString(), SPF_ASYNC,
NULL);
}
5. 测试
要测试您的应用程序,您可以运行它,在编辑框中键入单词“a”。然后,单击“Speak”按钮。
参考
Microsoft Inc. 的 Speech SDK 5.1。