• 【Win10 应用开发】集成语音命令


    记得老周以前在写WP8应用开发的文章时,曾经写过语音命令集成的文章,后来8.1的时候“小娜”问世,但考虑到其变化不大,故老周没有补写相应的文章。

    今天,老周打算补一下Win 10通用应用开发中,有关语音命令集成相关的内容。虽然还是一脉相承,大的变化没有,不过Win10 sdk在语音命令定义文件中添加了新内容,而且现在不仅能在手机应用中加入语音集成,在面向PC和板子的应用中也能如愿,因为应用程序已经通用。

    同理,在开始之前,老周仍然先给大家讲个故事。

    话说10166的SDK已经发布,当然如果你网速飞快并有兴趣的话可以下来装装,不下也无妨,毕竟是可选的。上回老周告诉大家如何通过修改VS的项目模板来匹配SDK版本号,要是大家装了10166的SDK,也可以去改改,方法我就不重复了。

    这一次再给大家介绍一个技巧。或许细心的各位已经发现,UAP项目的引用列表中包含了两套程序集,分别是:

    1、用于遥测的ApplicationInsights类库。

    2、用于特珠数值类型的库,比如矩阵,一般是在DX绘图中用到,程序集为System.Numerics.Vectors。

    这两个玩意儿属于NuGet包,引用它们会增大项目的体积。而且我们可能用不上它们,但在创建项目时它们会被默认引用。一种方法你可以在创建项目后手动删除它们,然后把项目导出为自定义的项目模板,这样以后你用自定义的应用项目模板来创建项目,就会带有这些引用了。

    如果你想一劳永逸,又不想导出自定义模板,其实也可以和上次一样,直接在VS目录中修改UAP项目模板来实现。打开C:Program Files (x86)Microsoft Visual Studio 14.0Common7IDEProjectTemplatesCSharpWindows RootWindows UAP1033目录,我们只需修改常用的几个项目就行了。

    a、先改BlankApplication项目(空白应用),打开BlankApplication目录,找到BlankApplication.vstemplate文件,用文本编辑器打开(记事本就行了,右击,从上下文菜单中选择[编辑]),打开文件后,一直滚动到XML文档的最后,你会看到有这么几段:

      <WizardExtension>
        <Assembly>Microsoft.VisualStudio.WinRT.TemplateWizards, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>Microsoft.VisualStudio.WinRT.TemplateWizards.CreateProjectCertificate.Wizard</FullClassName>
      </WizardExtension>
      <WizardExtension>
        <Assembly>Microsoft.VisualStudio.WinRT.TemplateWizards, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>Microsoft.VisualStudio.WinRT.TemplateWizards.ApplicationInsights.Wizard</FullClassName>
      </WizardExtension>
      <WizardExtension>
        <Assembly>NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>NuGet.VisualStudio.TemplateWizard</FullClassName>
      </WizardExtension>
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>

    其中,有两段就是和遥测库、Numerics.Vetors相关,即以下两个节点:

      <WizardExtension>
        <Assembly>Microsoft.VisualStudio.WinRT.TemplateWizards, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>Microsoft.VisualStudio.WinRT.TemplateWizards.ApplicationInsights.Wizard</FullClassName>
      </WizardExtension>
    
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>

    在XML文档中找到以上两个节点,然后把它们注释掉即可,不建议直接删除。因为一旦发现不正常或者你以后想使用这些扩展库时,就可以取消注释来还原。

      <WizardExtension>
        <Assembly>Microsoft.VisualStudio.WinRT.TemplateWizards, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>Microsoft.VisualStudio.WinRT.TemplateWizards.CreateProjectCertificate.Wizard</FullClassName>
      </WizardExtension>
    <!--
      <WizardExtension>
        <Assembly>Microsoft.VisualStudio.WinRT.TemplateWizards, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>Microsoft.VisualStudio.WinRT.TemplateWizards.ApplicationInsights.Wizard</FullClassName>
      </WizardExtension>
    -->
      <WizardExtension>
        <Assembly>NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
        <FullClassName>NuGet.VisualStudio.TemplateWizard</FullClassName>
      </WizardExtension>
    <!-- 
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>
    -->

    这样就可以了,然后保存。注意权限,你可以在父目录的权限上加上你当前的登录用户并完全控制,等修改完后再把父目录上的当前用户权限删掉即可,因为容器上的权限会自动应用到子对象上。修改权限有不少人的坏惯是直接把所有者改掉,这是不合理的,对于需要保护的系统文件或程序文件,不要动不动就改掉人家的所有者帐户。

    b、类库项目。打开ClassLibrary目录,然后用文本编辑器打开ClassLibrary.vstemplate文件,把下面内容注释掉,然后保存。

    <!--
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>
    -->


    c、Runtime组件项目。打开RuntimeComponent目录,再打开RuntimeComponent.vstemplate文件,把下面内容注释掉。

    <!--
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>
    -->


    d、单元测试项目。打开UnitTestApp目录,打开UnitTestApp.vstemplate文件,把下面内容注释掉,然后保存。

    <!--
      <WizardData>
        <packages repository="registry" keyName="UAPFrameworkDependenciesLocationVS14" isPreunzipped="true">
          <package id="System.Numerics.Vectors" version="4.0.0" skipAssemblyReferences="false" />
        </packages>
      </WizardData>
    -->

    ========================================================

     好了,故事讲完了,下面我们开始干正事。其实,实现语音命令主要的难度在于定义语音命令,所以,下面老周就从头到尾给大家演示一下如何编写VCD文件。

    往项目中添加一个xml文件。默认在新的XML文件中会生成以下行:

    <?xml version="1.0" encoding="utf-8" ?>

    不用管他,这是XML文件通用的文档标记,首先,我们定义文档的根VoiceCommands。

    <VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
      
    </VoiceCommands>

    每类XML文档都有相应的规范,这些规范一般以.xsd文件定义,我们在编写XML文档时,通常是引入相应的命名空间来进行验证,就好像我们在C#中要使用某个类型可以先using其所在命名空间(VB中为Import)一样。在VCD(语音命令定义)文件中我们要引入http://schemas.microsoft.com/voicecommands/1.2命名空间。

    这跟以前的VCD文件结构一样,只是注意后的版本号要改为1.2,WP8.1的时候是1.1,现在是1.2。VoiceCommands是整个文档的根节点,它下面可以包含多个CommandSet节点,最少1个,最多15个,至少目前来说xsd文件中是这样定义。通常,CommandSet节点将作为一个命令集合存在,以语言为划分,比如中文的命令归到一个CommandSet中,安哥拉语归一个CommandSet,鸟语也归到一个CommandSet中。

    这里我只定义zh-cn语言的命令集:

    <VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
      <CommandSet xml:lang="zh-cn" Name="set">
        
      </CommandSet>
    </VoiceCommands>

    CommandSet既然是命令集了,就说明它下面可以包含N条语音命令。是的,它可以包含最多100条语音命令,用Command元素表示。但是,CommandSet的子元素在设置时必须按顺序进行,你不能乱来。

    首先必须放一个CommandPrefix元素或者AppName元素,CommandPrefix元素是以前就有的,AppName元素是现在新增的,根据xsd文件的定义,这两个元素都是一样的,所以你不能同时,只能任选其一。大概是为兼容早期版本的VCD文件,所以保留CommandPrefix元素。CommandPrefix和AppName元素的作用是给你的程序起一个别名,这个名字一定要方便用户用嘴巴去说的,比如你的应用叫“高大上_v2.0.1”,这个你让用户怎么念呢,所以这时候你可以为程序起一个好名字。

      <CommandSet xml:lang="zh-cn" Name="set">
        <AppName>高大上</AppName>
        
      </CommandSet>

    这样一来,用户在使用语音命令时,只要说出“高大上”就能识别出是你的高大上应用了。

    AppName之后,要定义一个Example元素,用来告诉用户如何使用你的程序的语音命令,比如“微博 发微博。”。

      <CommandSet xml:lang="zh-cn" Name="set">
        <AppName>高大上</AppName>
        <Example>“高大上 红色”,或者“高大上 左对齐”</Example>
        
      </CommandSet>

    接下来就是定义语音命令了,本例就定义两个命令,第一个命令叫color,通过它可以改变界面上文本的颜色;第二个命令叫align,通过它可以修改文本在水平方向上的对齐方式(左、中、右)。

        <Command Name="color">
          <Example>“红色”或者“改为红色”</Example>
          <ListenFor>[改为]{coloritem}</ListenFor>
          <Feedback>正在修改颜色……</Feedback>
          <Navigate />
        </Command>

    Example与上面的Example功能一样,但意义不同,上面的Example元素是面向整个命令集的,而Command上的Example元素是针对当前命令的。

    ListenFor表示语音识别系统要聆听的内容,“改为”被中括号包围,表示可选,即就算你没有说出“改为”两字也能进行识别,后面的coloritem放在一对大括号中,表示它引用一个短语列表,内容可以是短语列表中的任何一项,比如:

        <PhraseList Label="coloritem">
          <Item>红色</Item>
          <Item>蓝色</Item>
          <Item>绿色</Item>
          <Item>紫色</Item>
        </PhraseList>

    PhraseList元素定义可以被识别的列表候选项,Label属性是必须的,它的名字要与前面Command中ListenFor元素中的引用对应,不然ListenFor无法找到相应的列表项。PhraseList元素必须放在Command元素后面。

    下面我们来定义第二条命令,用于设置文本的水平对齐方式。

        <Command Name="align">
          <Example>“左对齐”、“居中”、“右对齐”</Example>
          <ListenFor>{alignitem}[对齐]</ListenFor>
          <Feedback>正在设置对齐方式……</Feedback>
          <Navigate/>
        </Command>
        <PhraseList Label="alignitem">
          <Item></Item>
          <Item>居中</Item>
          <Item></Item>
        </PhraseList>


    Navigate元素虽然是必须的,但在RuntimeApp中用不上,所以Target属性不必设置。FeedBack是当识别成功后,在小娜面板上显示的内容(小娜会读出它),以向用户提供操作反馈。

    整个VCD文件的内容如下:

    <?xml version="1.0" encoding="utf-8" ?>
    
    <VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
      <CommandSet xml:lang="zh-cn" Name="set">
        <AppName>高大上</AppName>
        <Example>“高大上 红色”,或者“高大上 左对齐”</Example>
        <Command Name="color">
          <Example>“红色”或者“改为红色”</Example>
          <ListenFor>[改为]{coloritem}</ListenFor>
          <Feedback>正在修改颜色……</Feedback>
          <Navigate />
        </Command>
        <Command Name="align">
          <Example>“左对齐”、“居中”、“右对齐”</Example>
          <ListenFor>{alignitem}[对齐]</ListenFor>
          <Feedback>正在设置对齐方式……</Feedback>
          <Navigate/>
        </Command>
        <PhraseList Label="coloritem">
          <Item>红色</Item>
          <Item>蓝色</Item>
          <Item>绿色</Item>
          <Item>紫色</Item>
        </PhraseList>
        <PhraseList Label="alignitem">
          <Item></Item>
          <Item>居中</Item>
          <Item></Item>
        </PhraseList>
      </CommandSet>
    </VoiceCommands>

    确保该XML文件的生成操作为“内容”,不复制到输出目录

    在应用程序运行时,应当安装语音命令文件。在App类中重写的OnLaunched方法中加入安装VCD文件的代码。

                // 获取安装包中的VCD文件
                StorageFile vcd = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///vcd.xml"));
                // 安装VCD文件
                await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcd);

    注意using以下命名空间:
     Windows.Storage

     Windows.ApplicationModel.VoiceCommands - 操作VCD文件的API现已移到这里

    当小娜成功识别语音命令后,会激活我们的高大上应用程序,这时候Application类的OnActived方法会被调用,我们在App类中应当重写该方法,并处理语音命令识别。

            protected override void OnActivated(IActivatedEventArgs e)
            {
                // 如果程序不是因为语音命令而激活的,就不处理
                if (e.Kind != ActivationKind.VoiceCommand) return;
    
                VoiceCommandActivatedEventArgs vcargs = (VoiceCommandActivatedEventArgs)e;
                // 分析被识别的命令
                var res = vcargs.Result;
    
                /*
                输出调试信息
                System.Text.StringBuilder strbd = new System.Text.StringBuilder();
                strbd.AppendLine("触发识别的规则:");
                foreach (string ru in res.RulePath)
                {
                    strbd.AppendFormat("	{0}
    ", ru);
                }
                strbd.AppendLine("
    语义属性列表:");
                foreach (var kp in res.SemanticInterpretation.Properties)
                {
                    strbd.AppendFormat("{0} - {1}
    ", kp.Key, string.Join(", ", kp.Value.ToArray()));
                }
                System.Diagnostics.Debug.WriteLine("
    ===============================
    " + strbd.ToString() + "/n==============================================");
                */
    
                // 获取被识别的命令的名字
                string cmdName = res.RulePath[0];
    
                     ……
    
                if (cmdName == "color") //设置颜色
                {
                    // 获取被识别出来的短语项
                    string coloritem = res.SemanticInterpretation.Properties["coloritem"][0];
                    Color color = Colors.Black;
                    switch (coloritem)
                    {
                        case "红色":
                            color = Colors.Red;
                            break;
                        case "蓝色":
                            color = Colors.Blue;
                            break;
                        case "绿色":
                            color = Colors.Green;
                            break;
                        case "紫色":
                            color = Colors.Purple;
                            break;
                    }
                    uc.SetColor(color);
                }
                else if (cmdName == "align") //设置对齐方式
                {
                    // 获取被识别的短语
                    string alignitem = res.SemanticInterpretation.Properties["alignitem"][0];
                    HorizontalAlignment align = HorizontalAlignment.Left;
                    switch (alignitem)
                    {
                        case "":
                            align = HorizontalAlignment.Left;
                            break;
                        case "居中":
                            align = HorizontalAlignment.Center;
                            break;
                        case "":
                            align = HorizontalAlignment.Right;
                            break;
                    }
                    uc.SetAlignment(align);
                }
    
                Window.Current.Activate();
            }

    识别结果由SpeechRecognitionResult类封装,其中,被识别的语音命令的名字会存放到RulePath属性中,它是一个只读的字符串列表,一般来说,应用程序每次仅处理一条语音命令,所以只要访问RulePath属性的第一个元素就可以知道被识别的语音命令的名字。该命令名字是在VCD文件的Command元素的Name属性上定义的。

    要知道用户说出了ListenFor元素所引用的PhraseList列表中的某个短语,可以访问SpeechRecognitionResult对象的SemanticInterpretation.Properties 属性值,它是一个字典集合,通过key的名字可以找出被识别的项。

    这个key和VCD文件中PhraseList元素的Label属性对应。比如用户对着小娜说:“高大上 左对齐”,那么在VCD文件中定义对齐命令的PhraseList的Label值为alignitem,所以要访问.SemanticInterpretation.Properties["alignitem"]来获取,因为用户说了“左对齐”,“对齐”是可选的,而“左”是在短语列表中的,所以.Properties["alignitem"]字典所返回的字符串列表中就包含一个“左”值。

    对已识别的语音命令进行分析后,程序就要做出相应的处理了。就像本例中,用于修改文本颜色或设置文本的对齐方式。

    接下来,可以运行一下“高大上”应用,当应用顺利运行后,表明语音命令文件已经注册。这时候可以对着小娜说话了,现在小娜是无处不在的,所以你不必要在手机上测试,只要你有话筒,在桌面上就可以开工。

    比如,对着小娜讲“高大上 紫色”,这时候小娜会响应,并且会把应用界面上的文本改为紫色。

    接着,你可以试着对小娜说:“高大上 右对齐”,然后文本会设置为右对齐。

    OK,今天的牛皮暂时吹到这里,明天如果有空,老周继续吹语音命令相关的,下一篇烂文会说一说如何让语音命令集成结合到App Service中使用。

    示例源码下载:http://files.cnblogs.com/files/tcjiaan/VoicecommandApp.zip


     

  • 相关阅读:
    【Java EE 学习 81】【CXF框架】【CXF整合Spring】
    【Java EE 学习 80 下】【调用WebService服务的四种方式】【WebService中的注解】
    【Java EE 学习 80 上】【WebService】
    【Java EE 学习 79 下】【动态SQL】【mybatis和spring的整合】
    【Java EE 学习 79 上】【mybatis 基本使用方法】
    【Java EE 学习 78 下】【数据采集系统第十天】【数据采集系统完成】
    【Java EE 学习 78 中】【数据采集系统第十天】【Spring远程调用】
    【Java EE 学习 78 上】【数据采集系统第十天】【Service使用Spring缓存模块】
    【Java EE 学习 77 下】【数据采集系统第九天】【使用spring实现答案水平分库】【未解决问题:分库查询问题】
    【Java EE 学习 77 上】【数据采集系统第九天】【通过AOP实现日志管理】【通过Spring石英调度动态生成日志表】【日志分表和查询】
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/4638246.html
Copyright © 2020-2023  润新知