TTS开发步骤_基于图形界面

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

事件驱动的文本语音转换(TTS)实例
介绍一个基本的文字转语音的例子,应用程序使用图形界面(非控制台应用程序)建立项目
首先,创建一个新的windows 32平台应用程序项目,在向导中,选择一个典型的“Hello world!”应用。

由此产生的项目代码比命令行版本的长,且大多数新的复杂代码与sapi关系不大,但是是实现图形界面所必须的代码。

设置sapi的路径
与SAPI相关的文件路径必须在项目中声明。

以下是设置“sapi.h”路径的步骤:
1,在菜单上,选择工具“Tools”,然后单击选项“Options”。

2,单击“Directories”选项卡。

3,选择“Include Files”下拉式选单
4,单击最后一行闲置在路径清单,并输入“sapi.h”的文件路径:“C:/Program
Files/Microsoft Speech SDK 5.1/Include”
5,选择“Library Files”下拉式选单,并输入“sapi.lib”的文件路径:“C:/Program
Files/Microsoft Speech SDK 5.1/Lib/i386”。

最后单击确定。

创建“Speak”菜单命令
为了使应用程序响应我们发出“Speak”命令,并且发出声音,需要对自动生成的菜单项做一些修改。

读者可以通过VC6.0的资源管理器,在“File”菜单下面,增加一个“Speak”命令项,并且将该命令项的资源ID设置为“IDM_SPEAK”。

后文再讲述如何处理这个命令事件的代码。

现在先来编译和运行这个应用程序,确保以上的设置准确无误。

程序除了在屏幕上面显示“Hello world”之外,其它什么事情也没做,但是它确实可以运行起来了。

使用这个实例
第1步:初始化com
第2步:设置声音
第3步:讲!
第4步:设置感兴趣的事件
第5步:确定事件类型
第6步:响应事件
第1步:初始化com
用一个winmain中的程序段说明。

唯一的限制是在使用任何sapi的特定代码前com必须是初始化完成了,并且在运行sapi过程中,COM一直都是活动的。

既然sapi是用initinstance来实例化的,COM的初始化就应该在initinstance之前被初始化,并在消息环之后被撤销。

程序清单1:粗体字代表着这个例子的新代码
if( FAILED( CoInitialize(NULL) ) )
{
return FALSE;
}
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GUIAPP); // Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CoUninitialize();
第2步:设置声音
一旦com运行后,下一步就是要创建语音对象。

这里需要用到cocreateinstance()。

正如上一篇中的命令例子中说, sapi使用很多智能的默认配置。

所以只要进行很少的初始化,你即时可以使用语音对象。

这些默认配置放在在语音属性,控制面板,一个可选的声音模式(如果你的系统有一个以
上的),和语言(英文,日文等)。

有些则默认是显而易见的,其他的就没这么明显(如讲话速率,基音周期等)。

不过,你可以以编程的方式或通过控制面板来改变所有的默认配置。

为求简洁和方便起见,这个例子使用了特别的处理方式。

首先,它使用initinstance ()来初始化的声音。

这是最简单的初始化过程。

在其他一些应用中,特别是使用语音识别(SR)实例中,程序可能有专门的初始化模块,这样可以使SAPI相关的代码与普通的程序代码独立开来。

第二,这个语音对象是全局性的。

根据你的应用程序的设计和要求,你可能不需要一个全局的语音对象。

第三,对下面的代码来说,语音对象建立之后,对象本省和用到的内存立即被释放。

显然,如果这个对象有其它用途的话,它是不能被释放的。

这过渡性的代码,只是为了做一个简单的说明。

最后,如果初始化失败,应用程序应该有更强有力的检查错误机制,更广泛的报告和更详细的资料。

程序清单2:
ISpVoice *pVoice; //SAPI voice
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
//Initialize SAPI
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL,
IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
pVoice->Release();
pVoice = NULL;
}
else
return FALSE;
return TRUE;
}
第3步:讲!
要讲话的内容用一个参数传给语音对象。

这些内容可以根据程序的具体应用来改变。

如前所述,常是从一个对话框或一个文件获取字符串的。

另外,字符串也可以从一个流中获取,但就要使用另外一个调用 ispvoice:: speakstream 。

这个例子使用一个简单的,代码化的句子。

Speak函数可以使用类似于下面的文本
Speak(L”I am glad to speak.”,spf_async ,NULL);
这个调用代码是放在windows的消息处理函数wndproc ()相应的消息处理中的。

选择从文件菜单,选择Speak命令,计算机将开始讲: "我很高兴发言。

"
程序代码3:
ISpVoice *pVoice; //SAPI voice
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
//Initialize SAPI
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL,
IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
pVoice->Release();
pVoice = NULL;
}
else
return FALSE;
return TRUE;
}
第4步:设置事件
在COM组件中有很多交互动作,这些交互信息将以信息形式出现。

sapi是也一样。

信息是在TTS或SR引擎中产生的,某个特定的事件也是在那里开始和结束的.很多时候, sapi或sapi的引擎产生的事件是对应用程序有用的。

举例来说,当一个识别过程开始的时候,应用程序会得到相应的通知或消息,最终的用户也可以得到相应的通知。

同样,应用或许会有兴趣知道什么时候完成了所有的处理,处理完成后,也许要通知用户,甚至关闭引擎或应用本身时。

应用程序处理这些消息时要进行两个步骤的操作。

首先,它需要接收一个来自sapi的或sapi的引擎的消息。

这个消息是程序自定义的.但是,所有的活动sapi都使用相同的消息通知应用程序。

要确定确切的正在发生的活动,更多的资料是由sapi的提供的。

一个完整的清单,请参见speventenum。

第二个步骤。

得到通知后,应用程序要检查一个事件结构,这个事件结构是由SAPI完成的.利用这个结构来确定具体是发生了什么事件(见第五步骤).
设定感兴趣的事件:
程序可以用setinterest 来设置感兴趣的事件。

默认情况下,TTS不向程序发出任何事件通知,而语音识别只发出( spei_recognition )消息。

也就是说,如果程序忽略了setinterest的调用,TTS应用程序是得不到任何事件通知的,而SR程序只能得到识别完成的通知和消息。

在任何时候都可以改变感兴趣的事件。

设定消息
不论以何种事件,应用程序都应该将一个消息与sapi联系起来。

程序通过调用setnotifywindowmessage来完成这个任务。

有三类消息可以使用,但至少少用其中一种。

第四类是为多线程应用的,在这里没有用到。

这四种消息可以参见
ispnotifysource节的介绍。

实际使用的消息名字和值由程序决定,本例子使用了WM_USER消息。

程序清单4:
//Initialize SAPI
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL,
IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
pVoice->SetInterest( SPFEI(SPEI_WORD_BOUNDARY),SPFEI(SPEI_WORD _BOUNDARY) );
pVoice->SetNotifyWindowMessage( hWnd, WM_USER, 0, 0 );
}
else
return FALSE;
第5步:确定事件
如前所述,使用事件来工作需要2个步骤。

首先是使用一个简单和标准的做法社设置和使用Windows消息。

消息是被发送到程序的消息环中的。

在这个例子里,wndproc()接收wm_user讯息。

一旦讯息是捕获,其余事情靠sapi函数来完成。

第二个步骤是,以确定哪些消息发生了。

Sapi使用 spevent和getevents方法来确定具体的事件和消息。

通过这两个函数,你可以获取指定的事件信息和事件类型。

在SPEVENT的成员eeventid恰恰是setinterest所设定的值。

该spevent 结构必须在首次使用时被初始化。

而且重新使用之前也要被清除。

可以使用spclearevent来完成清除工作。

有可能事件和消息发生的速度比应用处理速度要快。

这是一个常见的情况。

getevents可以一次性取出一个以上事件。

这程序对这些这些事件进行批处理,一般专业应用程序是需要这样做的。

另一种方式来处理这种情况,是用一个while循环。

一个一个的检索出每一事件。

请看下例。

程序清单5:
case WM_USER:
SPEVENT eventItem;
memset( &eventItem, 0,sizeof(SPEVENT));
while( pVoice->GetEvents(1, &eventItem, NULL ) == S_OK )
{
switch(eventItem.eEventId )
{
case SPEI_WORD_BOUNDARY :
break;
default:
break;
}
SpClearEvent( eventItem );
第6步:响应事件
一旦事件和消息是确定后,编程变得更加标准了。

在这个例子中,应用程序使用spei_word_boundary消息来取定每一个单词。

可以用getstatus函数和spvoicestatus结构。

来获取更详细的信息。

一个独立的单词,可以用事件发生时的第一个字母和最后一个字母的位置来确定。

在这个例子中,使用MessageBoxW来显示所讲的单词。

在说话过程中,屏幕是实时更新的,并显示实际发言的文字。

这个特点是由
spf_async标志控制的。

如果在Speak函数中使用NULL参数,而不是SPF_ASYNC 参数,那样就要等整句话说完之后,才显示消息框。

pVoice->Speak( theString, SPF_ASYNC, NULL);
程序代码6:
case SPEI_WORD_BOUNDARY :
SPVOICESTATUS eventStatus;
pVoice->GetStatus( &eventStatus, NULL );
ULONG start, end;
start = eventStatus.ulInputWordPos;
end = eventStatus.ulInputWordLen;
wcsncpy( tempString, theString + start , end );
tempString[ end ] = '/0';
MessageBoxW( hWnd, tempString, L"GUIApp",
MB_OK|MB_ICONWARNING );
break;
总结:
最后,总结一下这个例子的主要知识点:
1,初始化COM:CoInitialize和CoUninitialize
2,建立语音对象实例:CoCreateInstance
3,讲话:Speck
4,设置感兴趣的事件:SetInterest
设定SAPI返回的消息:setnotifywindowmessage
5,消息来时,确定事件类型:GetEvents
清除事件结构:SpClearEvent
6,相应事件时,用GetStatus确定当前SAPI的状态
最后完整的实例代码:
// GUIApp.cpp : Defines the entry point for the application.
#include "stdafx.h"
#include <sapi.h〉
#include "string.h"
#include "resource.h"
#include "sphelper.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING] = _T ("Speak Hello world App"); // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING] = _T ("SpeakWinClass"); // The title bar text
//For SAPI
WCHAR theString[30];
ISpVoice *pVoice; //SAPI voice
// Forward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
MSG msg;
// HACCEL hAccelTable;
// Initialize global strings
// LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
//LoadString(hInstance, IDC_GUIAPP, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
if( FAILED( CoInitialize(NULL) ) )
{
return FALSE;
}
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
//hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GUIAPP);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
//if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CoUninitialize();
return msg.wParam;
}
// FUNCTION: MyRegisterClass()
// PURPOSE: Registers the window class.
// COMMENTS:
// This function and its usage is only necessary if you want this code // to be compatible with Win32 systems prior to the 'RegisterClassEx' // function that was added to Windows 95. It is important to call this function
// so that the application will get 'well formed' small icons associated
// with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL; //LoadIcon(hInstance,
(LPCTSTR)IDI_GUIAPP);
wcex.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL; //MAKEINTRESOURCE(IDC_TEST);
//(LPCSTR)IDC_GUIAPP;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL; //LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HANDLE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
// Instead of using IDC_TEST, use the identifier of menu resource // of the current application.
SetMenu(hWnd, LoadMenu(hInstance, MAKEINTRESOURCE(IDC_TEST)));
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
//Initialize SAPI
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL,
IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
pVoice->SetInterest( SPFEI(SPEI_WORD_BOUNDARY),SPFEI(SPEI_WORD _BOUNDARY) );
pVoice->SetNotifyWindowMessage( hWnd, WM_USER, 0, 0 );
}
else
return FALSE;
return TRUE;
}
//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
WCHAR tempString[30];
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDM_SPEAK:
wcscpy( theString, L"I am glad to speak." );
pVoice->Speak( theString, SPF_ASYNC, NULL);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam); }
break;
case WM_USER:
SPEVENT eventItem;
memset( &eventItem, 0,sizeof(SPEVENT));
while( pVoice->GetEvents(1, &eventItem, NULL ) == S_OK )
{
switch(eventItem.eEventId )
{
case SPEI_WORD_BOUNDARY :
SPVOICESTATUS eventStatus;
pVoice->GetStatus( &eventStatus, NULL );
ULONG start, end;
start = eventStatus.ulInputWordPos;
end = eventStatus.ulInputWordLen;
wcsncpy( tempString, theString + start , end );
tempString[ end ] = '/0';
MessageBoxW( hWnd, tempString, L"GUIApp",
MB_OK|MB_ICONWARNING );
break;
default:
break;
}
SpClearEvent( &eventItem );
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
if (pVoice)
{
pVoice->Release();
pVoice = NULL;
}
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
}。

相关文档
最新文档