1.引言
随着计算机网络,智能家电,多通道用户界面的飞速发展,人脸与语音相结合的人性化的交互方式,将成为未来人们使用计算机的主要趋势。在基于网络的人-人交互系统中,用户的语音输入可以直接在网络上作为音频流传输,在播放的一端进行音素切分,驱动人脸动画。这样做的优点是直接播放原始声音,声音失真小,缺点是传输的数据量大、需要占用较大的网络带宽,当虚拟环境中用户数量较多时网络和服务器可能不堪重负。另一种可行的方法是在发言的用户一端将语音输入切分为音素流,在播放的一端将音素流重新合成为语音,驱动人脸动画。这样做需要传输的数据量就小得多,网络和服务器的负载都要小得多,缺点是用户听到的是合成语音。本文旨在说明如何利用SAPI5.0对输入音频进行音素切分。
2.SAPI5.0 及其语音识别(SR)简介
微软的 Speech SDK 5.0是微软视窗环境的开发工具包。该开发工具包包括了先前的以"Whistler"和 "Whisper"命名的语音识别和语音合成引擎的最新版本。这个SDK中含有语音应用设计接口(SAPI)、微软的连续语音识别引擎(MCSR)以及微软的串联语音合成(又称语音到文本(TTS))引擎等等。SAPI中还包括对于低层控制和高度适应性的直接语音管理、训练向导、事件、语法编译、资源、语音识别(SR)管理以及TTS管理,其中应用程序接口(API)和设备驱动接口(DDI),结构如图2所示。应用程序通过API层和SAPI(SpeechAPI)通信,语音引擎则通过DDI层和SAPI(SpeechAPI)进行交互。通过使用这些API,可以加快在语音识别或语音合成方面应用程序的开发。
SAPI5在语音识别方面提供的基本服务:
a)管理语音输入,诸如从麦克风,文件等方式,并负责将语音转化成引擎所能接受的特定格式。
b)加载文法并负责解析和编辑。
c)编译用标准xml文件定义的文法,转换定制文法等。
d)使多个应用共享一个识别引擎.
f)返回结果和必要的信息给应用程序。
g)保存输入音频和序列化结果以便分析。
h)进行适当的错误异常处理,增加应用程序的健壮性。
SR引擎提供的基本服务:
a)可使用SAPI的文法接口,加载所需文法。
b)进行语音识别
c)可调用SAPI来处理文法和识别状态的改变。
d)产生识别结果并得到相应事件,以便给应用开发提供必要的信息。
3. 设计思想
由于SAPI5.0不提供直接的方法将中文语音输入直接分解成相应的音素,故采用这种折衷的办法来处理。
具体步骤:
a)初始化引擎并使其工作在连续语音识别方式下(Dictation Mode),即非特定词汇的连续语音识别,同时建立一个从汉字到拼音的映射数据库。然后进入消息循环处理阶段,响应SPEI_SOUND_START消息,开始识别输入语音,在得到SPEI_SOUND_END消息后,若在此声音开关其间无任何识别结果,则认为是噪声信号,不作任何处理。若期间得到SPEI_RECOGNITION消息,则在成功取得识别汉字后,执行b。
b)若处理完毕所有汉字,则输出队列中的全部元素,否则,对识别结果中的每一个汉字,重复执行c-e。
c)在识别的汉字中查询相应的拼音。
d)按照一定的规则分解拼音为可视音素。
e)将该组可视音素入队列。
用户对麦克风连续讲话,按上述思路,可完成其语音音素分解工作。其中涉及中文可视音素的划分,在MPEG-4标准中,划分14组可明显区分的英文音素。我们根据汉语的发音特点,参照科大讯飞公司的标准及其其他相关文献把汉语的可视音素划分为15组。如下表所示:
可视音素标号
音素
可视音素标号
音素
1
A
9
O
2
P, b, m
10
R
3
D,t,n,l
11
U,v
4
E.
12
Z,c,s
5
F
13
Zh,ch,sh
6
G,k,h
14
N
7
I
15
Ng
8
J,q,x
每一组都代表一种可视音素的基本的口型,任何一个汉字拼音都可以分解为这些可视音素的组合。这样,在输出端就可使用音素流来驱动虚拟人脸了。
4. 具体实现
4.1)初始化COM
if (SUCCEEDED(::CoInitialize(NULL)))
{//进入主消息循环,直到收到退出消息为止
while(GetMessage(&msg,NULL,0,0)
{//消息处理代码
......
}
::CoUninitialize();//退出时,释放相关资源
}
4.2)初始化识别引擎
CComPtr<ISpRecoContext> cpRecoCtxt;
CComPtr<ISpRecoGrammar> cpDictationGrammar;
CComPtr<ISpRecognizer> cpRecoEngine;
CComPtr<ISpAudio> cpAudio;
hr = cpRecoEngine.CoCreateInstance(CLSID_SpInprocRecognizer);
//创建一个识别引擎对象,并使其工作在排他方式,只允许该应用访问此识别引擎。
if( SUCCEEDED( hr ) )//
{
hr = cpRecoEngine->CreateRecoContext( &cpRecoCtxt );
//为该识别引擎实例创建一个识别上下文;
}
if (SUCCEEDED(hr))
{
hr = cpRecoCtxt->SetNotifyWindowMessage( hWnd, WM_USER_SR_MSG, 0, 0 );
//设定识别通知消息为WM_USER_SR_MSG(自定义消息),并由该消息所指定的函数处理;
}
if (SUCCEEDED(hr))
{//设定哪些引擎识别事件(消息)可触发识别通知消息;
//在此,我们仅关心正确的识别消息(SPEI_RECOGNITION),而不关心假设识别和错误识别消息(即SPEI_HYPOTHESIS和SPEI_FALSE_RECOGNITION);
const ULONGLONG ullMyInterest = SPFEI(SPEI_RECOGNITION);
hr = m_cpRecoCtxt->SetInterest(ullMyInterest, ullMyInterest);
}
// 创建默认的音频对象;
hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &cpAudio);
// 设定引擎的输入到cpAudio对象,以便处理SPEI_SOUND_START和SPEI_SOUND_END消息;
hr = cpRecoEngine->SetInput(cpAudio, TRUE);
hr = cpRecoEngine->SetRecoState( SPRST_ACTIVE );
if (SUCCEEDED(hr))
{
// 设定需要的文法并使其有效
hr = cpRecoCtxt->CreateGrammar( 0, &cpDictationGrammar );
}
if (SUCCEEDED(hr))
{
hr = cpDictationGrammar->LoadDictation(NULL, SPLO_STATIC);
}
if (SUCCEEDED(hr))
{
hr = m_cpDictationGrammar->SetDictationState( SPRS_ACTIVE );
}
4.3) 响应WM_USER_SR_MSG消息,并处理如下:
ProcessSapiMessage(......)
{
USES_CONVERSION;
CSpEvent event;
// 处理程序所关心的消息
while (event.GetFrom(cpRecoCtxt) == S_OK )
{
switch (event.eEventId)
{
case SPEI_SOUND_START:
bInputSound = TRUE;
break;
case SPEI_SOUND_END:
if (bInputSound)
{
bInputSound = FALSE;
if (!bGotReco)//是否识别到汉字?
{
// 一段语音输入已完成,即检测到了语音的开始和结束
// 但是识别引擎没有成功识别任何东西,特殊处理
........
}
bGotReco = FALSE;
}
break;
case SPEI_RECOGNITION:
// 得到识别结果,可能是一个字,也可能是一个词组,统一处理
{
bGotReco = TRUE;
CSpDynamicString dstrText;
if(SUCCEEDED(event.RecoResult()->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL)))
{
GetWordViseme(dstrText);//自定义函数,得到dstrText中汉字的可视音素并输出。
}
break;
}
}//switch over
}//while loop over;
}
5. 结束语和进一步的工作
由于使用了连续的语音识别模式,就要求对讲话者进行大量的语音训练,否则识别效果堪忧,则相应的音素分解也就不言尔喻了。加之整个分解是建立在识别基础上的,对机器的性能,速度要求也比较高。因此,我们拟采用更直接的方法,实际上,对音素表示而言,在人脸动画应用方面,用它来合成语音是不太合适的。由于音素只是从声学角度来区别发音高低,传递有用的语言成份,它忽略了发声和脸形之间的联系,脸形的幅度(大小)和发音能量之间的联系,发音时和唇形的联系等等。所以,我们希望能从音频信号中直接产生唇形,以产生更加真实感的人脸动画,这是下一步的工作。
……