语音编程之Text-To-Speech编程技术(4)

如果不想处理有关语言的细节问题,只想显示和选择系统可提供的语音引擎,则可以直接调用Speech SDK提供的两个帮助函数SpInitTokenComboBoxSpInitTokenListBox来实现语音语言的显示和选择,其代码如下:

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.HText2Speech.CPP增加到Reciter项目中。

3)在资源编辑器中编辑好响应的控件。

4)用ClassWizard为控件在CReciterDlg 类中生成相应的成员。

5)修改ReciterDlg.h文件,为类CReciterDlg增加相应的变量和函数。

6)用ClassWizardCReciterDlg 类添加对控件和消息的响应函数。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);

}

成员函数InitText2SpeechInitMouthImageList都在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。该消息指示出一个语音朗读事件已完成,即已发出某个音节。通过CSpEventGetFrom函数可以获得当前的事件信息,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++编程环境中设置好相应的includelib设置,如下所述。

设置include路径

    通过ProjectSettings菜单项打开Project Settings对话框;

    点击C/C++项;

    Category下拉列表中选取Preprocessor

    在“Additional include directories”编辑框中输入安装Speech SDKinclude的路径,默认的路径是C:/Program Files/Microsoft Speech SDK 5.1/Include

设置lib信息

    通过ProjectSettings菜单项打开Project Settings对话框;

    选择Link项;

    Category下拉列表中选取Input

    在“Additional library path”编辑框中输入安装Speech SDKlib的路径,默认的路径是C:/Program Files/Microsoft Speech SDK 5.1/ Lib/i386

    将“sapi.lib输入“Object/library modules所标识的编辑框中

9)编译连接该项目,你就可欣赏朗读者的风采了。

Reciter项目的所有源代码都存放在附盘的/Source/Reciter目录下。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值