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

语音控制的网络浏览

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (9投票s)

2004年1月22日

6分钟阅读

viewsIcon

90639

downloadIcon

5505

本文介绍了如何使用 SAPI 5.1 和 ActiveX 来语音控制您的网站。

引言

本文介绍了一个 ActiveX 控件,它可以嵌入到 html 网页中,提供语音控制的菜单树。

要编译代码,您将需要 VC6、Microsoft 的 Speech SDK 5.1Internet Explorer 头文件。(如果您有 WINXP,您可能已经拥有所需的文件)

演示程序

此软件包的演示是一个简单的网页,其中包含两个 <iframe> 元素:第一个 <iframe> 嵌入了 ActiveX 控件,第二个显示页面内容。

编译并注册 WebVoiceCtl.dll 后,查找名为 demo 的文件夹,然后双击其中的名为 WebVoice.html 的文件。您应该会在左侧框架中看到树形控件,如上所示。按 Voice 按钮,然后耐心等待大型语音引擎加载。

加载完成后,您可以说 "go to class one" 开始导航。控件应响应 "Please confirm class one",您可以回答 "positive"。然后,请求的项目将显示在右侧框架中。

随时说 "help" 以获取活动命令列表。如果您刚导航到一个页面,帮助响应将是 "[scroll] up, down, top bottom; go back or navigate"。说出您的滚动命令,然后说:"navigate" 返回导航模式。

提示: 将扬声器的音量调低,以避免麦克风产生反馈。

背景

本文附带的代码演示了以下技术:

  • ATL、ActiveX(以及宽字符字符串操作)
  • 树形视图的搜索、展开和折叠
  • 所有者绘制按钮、编辑控件和静态控件
  • 图像列表、叠加(以及在 ATL 复合控件内部绘制)
  • 使用 Microsoft 的 MSXML 解析器加载和操作 XML 文件
  • 使用 C++ 与 Web 浏览器和 html 页面进行交互
  • SAPI 5.1、语音识别和文本到语音引擎以及 Visemes

当然,您不必理解以上所有项目即可在您的项目中使用此控件,但您可能会发现其中一些解决方案(其中几个归功于其他 Code Project 文章)很有趣。

创建您自己的菜单树

您的菜单项将从文件 "data/WebVoice.xml"(名称当前是硬编码的)中读取,该文件包含菜单树和 SAPI 语法的信息。其内容存储在 KEY 结构数组中以供稍后检索。下面显示了一个简短的 XML 示例文件和 KEY 结构。

  <!-- WebVoice.xml -->
  <menu>
    <item>
      <mid>1</mid>                    <!- menu item id -->        
      <pid>0</pid>                    <!- parent id -->        
      <txt>Class One</txt>            <!- menu text and grammar phrase -->   
      <ref>../html/class1.html</ref>  <!- hyperlink reference -->
    </item>
    <item>
      <mid>2</mid>
      <pid>1</pid>
      <txt>Source One</txt>
      <ref>../html/src1.html</ref>
    </item>
    <!- more items here -- >
</menu>
typedef struct tag_key
{
  int mid;
  int pid;
  int chd;
  HTREEITEM hItem;
  HTREEITEM hParent;
  char txt[32];
  char ref[128];
}KEY;

KEY aKeys[NUMBER_OF_KEYS];

您必须仔细确保菜单项 ID 按顺序编号,并且父 ID 指向树中位于当前项之上的项。加载时目前不执行任何错误检查,因此无效的 XML 文件将导致控件崩溃。

SAPI 初始化

WebVoice 控件在 InitSapi() 函数中按如下方式处理 SAPI 初始化:

  1. 创建语音引擎。
  2. 创建识别上下文。
  3. 设置一个通知机制(Windows 消息),用于从识别引擎进行回调。
  4. 设置识别事件兴趣。
  5. 加载特定的语法文件。
  6. 创建文本到语音引擎 (TTS)。
  7. 设置 TTS 事件兴趣。
  8. 设置一个通知机制(Windows 消息),用于从 TTS 引擎进行回调。
  9. 设置活动规则。

Speech SDK 文档和示例清楚地展示了所需的 SAPI 初始化调用,因此我在此不再赘述。但是,静态语法文件和动态语法需要一些解释。

SAPI 语法

SAPI 语法可以从 XML 文件静态加载,也可以在运行时动态加载。WebVoice 控件同时使用这两种方法。静态部分从 Grammar.xml 加载,其格式如下:

<GRAMMAR LANGID="409">
  <DEFINE>
    <ID NAME="RID_Tree"     VAL="1001"/>
    <ID NAME="RID_MenuItem" VAL="1004"/>
  </DEFINE>
  <RULE ID="RID_Tree" TOPLEVEL="ACTIVE">
    <L>
      <P>open</P>
      <P>go to</P>
    </L>
    <RULEREF REFID="RID_MenuItem" />
  </RULE>
  <RULE ID="RID_MenuItem"  DYNAMIC="TRUE">
    <L PROPID="RID_MenuItem">
      <P VAL="1">Dummy Item</P>
    </L>
  </RULE>
  <!-more rules -->
</GRAMMAR>

如您所见,此文件片段创建了两个规则:第一个规则 RID_Tree 定义了起始导航短语,然后引用第二个规则 RID_MenuItem。第二个规则包含一个占位符短语,该短语将在运行时替换为您的菜单项名称。此文件由 SAPI 的 gc.exe 编译为 Grammar.cfg,然后加载到 DLL 中的资源中。动态规则添加如下:

HRESULT CWebVoice::LoadGrammar()
{
  USES_CONVERSION;  
  HRESULT hr;

  SPPROPERTYINFO pi; 
  ZeroMemory(&pi,sizeof(SPPROPERTYINFO));
  pi.ulId      = RID_MenuItem;  // property ID
  pi.vValue.vt = VT_UI4;

  // add menu items to the dynamic grammar rule
  for(int i=0; i < m_nNumKeys; i++) {
    pi.vValue.ulVal = i+1;     // Property_Value == data_index + 1
    hr=m_cpGrammar->AddWordTransition(hRule,NULL,
         T2W(aKeys[i].txt),L" ",SPWT_LEXICAL,1,&pi);
    if(FAILED(hr)) return hr;
  }

  // add a wildcard phrase
  pi.vValue.ulVal = 0;
  hr=m_cpGrammar->AddWordTransition(hRule, 
     NULL, L"*", L" ", SPWT_LEXICAL, 1, &pi);
  if(FAILED(hr)) return hr;

  hr=m_cpGrammar->Commit(NULL);                  if(FAILED(hr)) return hr;
  hr=m_cpGrammar->SetGrammarState(SPGS_ENABLED); if(FAILED(hr)) return hr;
  return hr;
}

请注意,每个新短语(取自 aKeys[i].txt)都被分配了属性 ID RID_MenuItem 和一个唯一的属性值(介于 1 和 m_nNumKeys 之间),然后使用 AddWordTransition() 函数添加到语法中。另请注意,在末尾添加了一个通配符规则 ("*") 以捕获语法未涵盖的语音短语。

致谢

识别引擎将您的语音与活动语法规则进行比较。当引擎进行识别或误识别时,将调用您的回调例程来处理请求。以下显示了识别处理程序的一部分:

void CWebVoice::ExecuteCommand(ISpRecoResult *pPhrase, HWND hWnd)
{
  USES_CONVERSION;
  SPPHRASE *pElements;
  static int ind;
  int pos;

  if (SUCCEEDED(pPhrase->GetPhrase(&pElements))) {  
    m_cpRecoCtxt->Pause(NULL);           // pause recognition while loading
    
    switch (pElements->Rule.ulId ) {
    case RID_Tree:
      pos=pElements->pProperties->vValue.ulVal;
      ind=pos-1;              // store the index into the data array
      SetActiveRule(RID_Confirm);        // change the active rule
      wcscpy(wcs,L"Please confirm: \r\n");
      wcscat(wcs,T2W(aKeys[ind].txt));
      HandleReply(0,wcs);
      break;
    case RID_Confirm:
      pos=pElements->pProperties->vValue.ulVal;
      switch(pos) {
      case 1: 
        HandleConfirm(ind);       // expand the tree and navigate to item 
        SetActiveRule(RID_View);  // change the active rule
        break;
      case 2:
      default:SetActiveRule(RID_Tree); HandleReply(MID_Tree,NULL); break; 
      break;
      }
    // more cases for other rules
    default:  SetActiveRule(RID_Tree); HandleReply(RID_Tree,NULL);  break;
    }
    ::CoTaskMemFree(pElements);
    m_cpRecoCtxt->Resume(NULL);
  }
}

当匹配到导航规则时,其属性值将存储在静态变量 ind 中,并在确认后传递给 HandleConfirm(ind) 函数,该函数使用它来索引数据数组 (aKeys[ind]) 并检索正确的数据项。如果成功,树形视图将打开以显示选择,并且超链接将被导航。

关注点

每次我使用 ATL 编写 ActiveX 控件或 Web 浏览器插件时,我都必须重新学习如何使用宽字符字符串;SAPI 完全使用宽字符字符串。如果您的代码不需要在 Win98 上运行,您可以只定义 UNICODE,只要您的字符串定义为 TCHAR*,常规的 API 调用就可以正常工作。但是,如果不能放弃 Win98 用户,那么在每次使用 Win32 API 时,您都将被迫从多字节字符串转换为宽字符串。幸运的是,ATL 在 <atlbase.h> 中定义了一套出色的转换宏。您只需在需要转换字符串的每个函数开头放置宏 USES_CONVERSION,然后使用 W2T()T2W() 宏执行转换。我毫不怀疑这些宏的开销会很高——毕竟,它们每次转换都需要分配内存、复制字符串然后释放内存。然而,这些宏非常方便和整洁,以至于我甚至开始将 <atlbase.h> 包含到我的 MFC 程序中。

我遇到的另一个问题是需要使用所有者绘制按钮——标准的对话框灰色在网页上无法接受。在 MFC 中,我会覆盖 WM_CTLCOLOR 消息并在那里更改背景颜色。在 ATL 中,我发现我必须将按钮设置为所有者绘制,然后处理 WM_DRAWITEM 消息。一切都很好,但后来我发现我既需要一个切换按钮也需要一个瞬时按钮,并且现在我需要自己编写所需的响应。这一切都很有趣,但花了一些时间我才能够进入代码的 SAPI 部分。

Microsoft Speech SDK 5.1 是一个 68 MB 的下载文件,如果您需要将 SAPI 运行时模块与您的代码一起打包,则必须下载完整的重新分发包,该包为 131.58 MB。

不幸的是,Microsoft 不单独打包运行时模块。您的客户必须下载 SDK(包括额外的 30 MB 的开发代码和文档),或者您必须准备一个运行时模块包,作为您应用程序的单独下载。

修订

  • 2004 年 1 月 29 日 - 子类化图片控件以避免 Win98 问题,并修复了演示中的小脚本错误。
© . All rights reserved.