語音指令就是通過語音來完成一些正常操作,如啟動某個應用,打開或關閉飛行模式等,随着智能助理Cortana的推出,語音操作的重要地位也逐漸顯現。若能在應用程式中提供适當的語音操作支援,不僅能夠提升應用的使用者體驗,也給使用者的使用帶來不少便捷。
語音指令是通過一種名為VoiceCommandDefinition(VCD)檔案來定義,當應用程式運作後通過相關API進行安裝注冊。VCD檔案安裝成功後,開發者為應用所定義的語音指令就會被作業系統的語音識别引擎發現,并內建到Cortana中。使用者隻需要打開Cortana應用,并且說出應用程式預先定義好的指令,就可以完成相應的操作了。
VCD檔案實質上是一個XML文檔。
示例VCD檔案内容:
<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
<CommandSet xml:>
<CommandPrefix>測試應用</CommandPrefix>
<Example>"打開 首頁"或"打開 我的音樂"或"我的音樂"或"打開 我的視訊"或"我的視訊"</Example>
<Command Name="open">
<Example>"打開 我的音樂"或"我的音樂"</Example>
<ListenFor>[打開]{pages}</ListenFor>
<Feedback>好的,正在打開中。。。</Feedback>
<Navigate/>
</Command>
<PhraseList Label="pages">
<Item>首頁</Item>
<Item>我的音樂</Item>
<Item>我的視訊</Item>
<Item>我的照片</Item>
</PhraseList>
</CommandSet>
</VoiceCommands>
VCD文檔的根元素為VoiceCommands,xmlns所指定的命名空間必須為 http://schemas.microsoft.com/voicdecommands/1.2。在VoiceCommands元素下,包含1~15個CommandSet元素,也就是說,CommandSet元素必須出現,至少要有一個。
CommandSet表示一個語音指令集,需要通過xml:lang屬性設定所屬的區域語言,例如面向簡體中文的指令集可以使用語言标記zh-cn。Name屬性是可選的,可以不設定,但是,如果開發者打算要在應用程式運作階段通過代碼來動态修改VCD檔案,則應該為其命名。如果應用程式支援多種語言的指令,可以定義多個CommandSet。
每個CommandSet元素下都可以設定一個CommandPrefix元素,該元素是可選的,主要用于對應用程式進行辨別。例如,應用的名字為Shaken_App,當使用者使用語音指令調用該應用時,顯然這個名字用口頭語言不好說,此時可以通過CommandPrefix元素指定一個名字如"搖一搖",隻要使用者說出"搖一搖"。語音引擎就會識别出是Shaken_App應用。在版本1.2中,可以用APP Name元素來取代CommandPrefix元素。
CommandSet元素可以包含1~100個Command元素,一個Command元素就表示一條語音指令。必須通過Name屬性為每條指令命名,而且在同一個CommandSet集合中不能出現名字重複的指令。Command勻速必須包含一個Example元素,該元素中的内容會顯示在Cortana的操作界面上,用來提示使用者如何使用該指令。随後還要包括不超過10個ListenFor元素,表示語音引擎應該收聽的内容,即語音指令的内容。
PhraseList和PhraseTopic允許包含在CommandSet元素中,必須通過Label屬性進行命名。PhraseList會提供一系列短語,語音引擎可以偵聽其中任何一短語,這與前面介紹過的SpeechRecognitionListConstraint限制相似。而PhraseTopic則類似于SpeechRecognitionTopicConstraint限制,隻是确定一個主題,來提高識别的精确度。
Feedback元素指定回報資訊,當語音識别引擎識别出指令後,在執行指令前向使用者展示的回報資訊,如"好的,正在進入應用,請稍後..."。Navigate勻速指定當語音指令執行後要導航到應用程式中的哪個頁面,雖然該元素是必須的,但是在Runtime App中可以不指定具體的頁面。
當語音指令被成功識别并執行後,會激活目前應用程式,App類的OnActivated方法會被調用,開發者應當重寫該方法以進行進一步處理,響應語音指令完成相關操作。這就是上面提到過Navigate元素在UWP App中不需要指定具體的頁面的原因,因為開發者可以在OnActivated方法中處理。
該示例包含四個頁面,分别為"首頁"、"我的音樂"、"我的視訊"和"我的照片"。通過語音指令操作應用程式進入特定的頁面。例如,使用者說出"測試應用我的視訊",就會打開目前應用程式,并導航進入"我的視訊"頁面。
ComamndPrefix元素定義了該應用的别稱為"測試應用",隻要使用者說出"測試應用"系統就能夠知道使用者要操作的就是目前應用。
在CommandSet元素下面的Example元素向使用者展示的是針對真個指令集的使用說明,而在Command元素下的Example元素表示的隻是針對單條指令的使用說明。
在ListenFor元素中,"打開"二字被一對中括号([ ])包起來,表示該内容為可選,也就是說,不管使用者是否說出"打開"二字,該指令都能夠比對。随後的pages放在一對大括号中,它表示引用了後面的Label為pages的PhraseList元素。隻要使用者說出PhraseList中任意一個Item元素的内容都可以進行識别。
接下來,在App類中重寫OnActivated方法,當使用者通過語音操作激活目前應用程式後應該進行的相關處理。
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
var varg = args as VoiceCommandActivatedEventArgs;
//處理識别結果
SpeechRecognitionResult result = varg.Result;
//擷取已識别的指令名字
string cmdName = result.RulePath[0];
if (cmdName is "open")
{
//擷取 PhraseList中被識别出來的項
var interpretation = result.SemanticInterpretation;
if (interpretation != null)
{
//通過 PhraseList的Label屬性可以查詢出被識别Item
string item = interpretation.Properties["pages"].FirstOrDefault();
if (!string.IsNullOrEmpty(item))
{
//導航到對應頁面
Frame root = Window.Current.Content as Frame;
if (root is null)
{
root = new Frame();
Window.Current.Content = root;
}
switch (item)
{
case "我的音樂":
root.Navigate(typeof(MyMusicPage));
break;
case "我的視訊":
root.Navigate(typeof(MyVideoPage));
break;
case "我的照片":
root.Navigate(typeof(MyPhotoPage));
break;
case "首頁":
root.Navigate(typeof(MainPage));
break;
default:
root.Navigate(typeof(MainPage));
break;
}
}
}
}
Window.Current.Activate();
}
由于許多行為都可以激活應用程式(如協定激活),是以必須通過Kind屬性來判斷一下,是否因語音指令操作而激活應用程式。
判斷成立後,可以将方法參數轉換為VoiceCommandActivatedEventArgs類型進行操作,并從Result屬性中擷取到SpeechRecognitionReuslt執行個體。SpeechRecognitionResult對象的RulePath中會包含别識别的語音指令的名字,該名字就是VCD檔案中Command元素的Name屬性值。由于本示例之定義了一個Command元素,是以RulePath中包含的元素應當為open。
随後,應用代碼還應該知道使用者說出了名為pages的PhraseList元素中哪個Item值。可以從SemanticInterpretation屬性的Properties集合中找到被識别的PhraseList值。該集合是以字典形式存儲的,要在其中檢索PhraseList元素的内容,可以用其名字(本例中為pages)作為Key來查找。得到的結果是一個字元串清單,其中就包含被識别的Item元素的内容了。
最後,代碼通過分析被識别的Item元素的内容來确定要導航到哪個頁面。
為了讓自定義的VCD檔案能夠與Cortana內建,在App類的OnLaunched方法中加入以下代碼,以便應用程式在啟動時安裝VCD檔案。
StorageFile vcdFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///vcd.xml"));
//使用VoiceCommandManager API, 需要引用 Windows Mobile Extension SDK (introduced in 10.0.10240.0)
//await VoiceCommandManager.InstallCommandSetsFromStorageFileAsync(vcdFile);
await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdFile);
從項目目錄中擷取到VCD檔案的引用後,直接調用靜态的VoiceCommandManager.InstalCommandSetsFromStorageFileAsync方法就可以完成VCD檔案的安裝與注冊了。