zoukankan      html  css  js  c++  java
  • 【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


     

  • 相关阅读:
    BZOJ3456: 城市规划
    BZOJ4555: [Tjoi2016&Heoi2016]求和
    关于wqs二分(凸优化) 实数二分和整数二分的一些讨论
    求逆矩阵模板
    再探模拟费用流一类问题
    「九省联考 2018」秘密袭击(差分+生成函数+拉格朗日插值+整体dp+线段树合并):
    「九省联考 2018」IIIDX(贪心+线段树(平衡树)):
    【GDOI2020模拟03.28】数二数(two) (计数动态规划):
    「十二省联考 2019」骗分过样例(提答+数论+乱搞):
    二进制分组学习小记:
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/4638246.html
Copyright © 2011-2022 走看看