天天看點

【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.0\Common7\IDE\ProjectTemplates\CSharp\Windows Root\Windows UAP\1033目錄,我們隻需修改常用的幾個項目就行了。

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("\t{0}\n", ru);
            }
            strbd.AppendLine("\n語義屬性清單:");
            foreach (var kp in res.SemanticInterpretation.Properties)
            {
                strbd.AppendFormat("{0} - {1}\n", kp.Key, string.Join(", ", kp.Value.ToArray()));
            }
            System.Diagnostics.Debug.WriteLine("\n===============================\n" + 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"]字典所傳回的字元串清單中就包含一個“左”值。

對已識别的語音指令進行分析後,程式就要做出相應的處理了。就像本例中,用于修改文本顔色或設定文本的對齊方式。

接下來,可以運作一下“高大上”應用,當應用順利運作後,表明語音指令檔案已經注冊。這時候可以對着小娜說話了,現在小娜是無處不在的,是以你不必要在手機上測試,隻要你有話筒,在桌面上就可以開工。

比如,對着小娜講“高大上 紫色”,這時候小娜會響應,并且會把應用界面上的文本改為紫色。

【Win10 應用開發】內建語音指令

接着,你可以試着對小娜說:“高大上 右對齊”,然後文本會設定為右對齊。

【Win10 應用開發】內建語音指令

OK,今天的牛皮暫時吹到這裡,明天如果有空,老周繼續吹語音指令相關的,下一篇爛文會說一說如何讓語音指令內建結合到App Service中使用。

示例源碼下載下傳:https://files.cnblogs.com/files/tcjiaan/VoicecommandApp.zip

繼續閱讀