記得老周以前在寫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"]字典所返回的字符串列表中就包含一個“左”值。
對已識別的語音命令進行分析后,程序就要做出相應的處理了。就像本例中,用於修改文本顏色或設置文本的對齊方式。
接下來,可以運行一下“高大上”應用,當應用順利運行后,表明語音命令文件已經注冊。這時候可以對着小娜說話了,現在小娜是無處不在的,所以你不必要在手機上測試,只要你有話筒,在桌面上就可以開工。
比如,對着小娜講“高大上 紫色”,這時候小娜會響應,並且會把應用界面上的文本改為紫色。
接着,你可以試着對小娜說:“高大上 右對齊”,然后文本會設置為右對齊。
OK,今天的牛皮暫時吹到這里,明天如果有空,老周繼續吹語音命令相關的,下一篇爛文會說一說如何讓語音命令集成結合到App Service中使用。
示例源碼下載:http://files.cnblogs.com/files/tcjiaan/VoicecommandApp.zip