65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.38/5 (21投票s)

2001年11月28日

4分钟阅读

viewsIcon

213512

downloadIcon

6246

此程序展示了如何将文本转换为语音并使用口部运动

引言

此代码部分基于 James Matthews 的原创文章 Visemes: Representing Mouth Positions

图 1:应用程序 GUI

本项目展示了如何将文本转换为语音,并随后进行嘴部运动。在构建本项目之前,我建议您阅读我的文章,即《使用 SAPI 进行文本到语音的简单程序》。以下是构建项目的步骤:

  1. 新建应用程序

  2. 在项目中设置 SAPI

  3. 构建项目的 GUI

  4. 编码

  5. 测试

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

以下是编码过程的步骤:

  1. StdAfx.h 文件中,添加以下代码行以访问 SAPI COM:
  2.  #include <atlbase.h>
     extern CComModule _Module;
     #include <atlcom.h>
    
  3. CSpeakMouthDlg 类中,在 SpeakMouthDlg.h 文件的顶部添加代码行:
  4. #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
     };
    
     ////////////////////////////////////////////////////////////
    
  5. CSpeakMouthDlg 类中,添加成员方法(只需右键单击,然后选择 Add Member Method)。方法:InitMouthImageList()::BOOLInitializationSAPI()::BOOLDestroySAPI()::BOOL,全部在 protected 模式下。此外,添加变量,代码如下:
  6.    CString m_sError;
       BOOL InitMouthImageList();
       BOOL DestroySAPI();
       BOOL InitializationSAPI();
    	
       ///////////////////////////////////////////////////////
       // Speech API Variables
    	
       CComPtr<ISpVoice> IpVoice;
       CImageList m_cMouthList;
       int	m_iMouthBmp;
       CRect m_cMouthRect;
       ///////////////////////////////////////////////////////
    
  7. 现在,您可以实现您创建的方法了。
  8.  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;
    }
    
  9. CSpeakMouthDlg 类中,OnInitDialog() 方法必须包含以下用于 SAPI 初始化 的代码:
  10. // 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;  
    
    
  11. CSpeakMouthDlg::CSpeakMouthDlg(CWnd* pParent=NULL) 类的构造函数中:
  12.  m_iMouthBmp = 0;
    
  13. 现在,您必须创建方法来处理将语音转换为嘴部运动的事件。您可以发送消息处理程序,即:您已定义为 WM_USER + 101 WM_RECOEVENT。在 SpeakMouthDlg.h 文件中,键入:
  14. // 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;
    }
    
  15. 现在,添加一个方法来处理按钮单击(IDC_SPEAK_BTN)事件。键入 OnSpeak 作为方法的名称,并添加以下代码行作为其实现:
  16.  void CSpeakMouthDlg::OnSpeak() 
     {
        UpdateData();
    	
        USES_CONVERSION;
        IpVoice->Speak(m_sText.AllocSysString(), SPF_ASYNC, 
            NULL);		
     }
    
  17. 这样,您就可以调试并运行您的程序了。

5. 测试

要测试您的应用程序,您可以运行它,在编辑框中键入单词“a”。然后,单击“Speak”按钮。

参考

Microsoft Inc. 的 Speech SDK 5.1。

© . All rights reserved.