語音指令

綜述

Windows Phone 操作系統先前的版本 (Windows Phone 7.0) 僅支援非常有限的語音指令功能:使用者可以靠著說出特定 App 名稱 + "Start" 或 "Open"來啟動 App。在 Windows Phone 8,你可以擴展和自訂其他的語句,並將這些語句作為參數提供給你的 App 使用者,讓他們能藉由這些語音指令快速進入 App 的功能。例如,使用者能夠:

  • 開啟 App 中一個特定的頁面。例如,用戶可以按下開始鍵並說出 "Start MyApp, navigate to MyPage",或是簡單的 "Start MyApp go to MyPage."。

  • 啟動 App 並進行動作。例如,用戶可以按下開始鍵並說出 "Start MyApp show MyItem" ( MyItem 是特定 App 的資料如同我的最愛列表中的其中一項)

發現性也是語音指令當中重要的特質。這有助於當你擴展並自訂語音指令,客戶端可以透過系統的 help 及 "What Can I Say" 中發現 App 支援的語句。更多相關資訊請看 Voice commands for Windows Phone

這個討論中我們要讓使用者可以靠著語音指令打開 Contoso Cookbook App 並操作功能。

我們將練習修改 Contoso Cookbook App 達到可以使用 Windows Phone 的語音辨別功能。

目標

此實驗將引導我們達到下列目標:

  • 使用語音指令打開 App

  • 使用語音指令操作進一步的 App 功能

系統需求

您必須符合以下系統需求:

  • Microsoft Visual Studio 2012 or Microsoft Visual Studio 2012 Express for Windows Phone

  • Windows phone 開發經驗

實驗架構

這個實驗包含完成兩個練習及以下的任務:

  1. 更改應用程式清單

  2. 支援定義的語音指令

  3. 支援動態 (不限數量的) 語音指令

  4. 測試 App

預計完成時間

預計完成時間至少 60 分鐘

練習 1

這個練習中,我們要使得語音指令功能可以在 Contoso Cookbook App 中使用,並支援基本的瀏覽 App 頁面的功能。

任務 1– 更改應用程式清單檔案

要支援語音指令必須在應用程式清單中聲明功能。

  1. 打開 Visual Studio 2012。

  2. 瀏覽到 EX1\Begin 資料夾。

  3. 打開 ContosoBookbook.sln solution。

  4. 切換到 Visual Studio Solution Explorer, 在Properties 資料夾內選擇 WMAppManifest.xml 檔案。

  5. 打開 Windows Phone Manifest Designer。如果另一個視窗 (XML Editor或 Binary Editor) 打開,按右鍵點擊 WMAppManifest.xml 並選擇“Open with…”


    圖 1
    選擇 Open With…

  6. 在 “Open With” 對話框選擇 “Windows Phone Manifest Designer”:


    圖 2
    選擇 Windows Phone Manifest Designer

  7. 在 Windows Phone Manifest Designer 內選擇 "Capabilities" 分頁。

  8. 勾取 "ID_CAP_SPEECH_RECOGNITION" 選項 。


    圖 3
    宣告語音辨識功能

    註 : 你也可以用另一個方法:打開 XML 編輯模式的應用程式清單 ,找到 Capabiliti 項並增加下方XML元素: <Capability Name="ID_CAP_SPEECH_RECOGNITION"/>

任務 2 – 宣告支援的語音指令

這個任務中,將使用一個 XML 定義檔案宣告我們的 App 支援的語音指令。

  1. 加入新項目到專案中。

  2. 選擇項目類別為 "XML File"。


    圖 4
    建立一個新的 XML File

  3. 將新的項目取名為 SupportedVoiceCommands.xml, 並點擊 Add。

  4. 打開新建的 XML 檔案並增加下列內容:

    XML
    <VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.0">
    <CommandSet xml:lang="en-us">
    <CommandPrefix> Contoso Cookbook </CommandPrefix>
    <Example> Show recipes for (Country Name) </Example>
    <Command Name="Start">
    <Example> Show recipes </Example>
    <ListenFor> Show recipes </ListenFor>
    <Feedback> Showing recipes collection </Feedback>
    <Navigate Target="MainPage.xaml" />
    </Command>
    <Command Name="ShowRecipes">
    <Example> Show Chinese recipes </Example>
    <ListenFor> Show [me] [a] {groupName} recipes</ListenFor>
    <Feedback> Showing a {groupName} recipes </Feedback>
    <Navigate Target="GroupDetailPage.xaml" />
    </Command>
    <PhraseList Label="groupName">
    <Item> Chinese </Item>
    <Item> French </Item>
    <Item> German </Item>
    <Item> Indian </Item>
    <Item> Italian </Item>
    <Item> Mexican </Item>
    </PhraseList>
    </CommandSet>
    </VoiceCommands>

    前方的 XML 片段定義了一組包含兩個指令的美語。每個指令宣告了當 ListenFor XML 元素觸發指令時的口語文字。ListenTo 元素以矩形方括號 ("[ ]") 表示可選擇的部分或者以大括號 ("{ }") 支援關鍵的部分。App 接收這些部分的瀏覽參數,並利用他們提供使用者回應、瀏覽到特定頁面、或者執行特定的操作。

    註 : 整套特殊情況的符號參請閱說明文件

    指令聲明應該也要包含展示給使用者的範例語句 (在 Example 元素),系統反饋的訊息為確認獲取到正確的指令 (在 Feedback 元素),當成功辨識時應使用 App 中的瀏覽 URI (在 Navigate 元素的 Target 屬性)。但要注意,所有導向 Contoso Cookbook App 的指令詞組必須把“Contoso Cookbook”放在詞頭-這是在前面的聲明中使用 CommandPrefix XML 元素的結果。

    Contoso Cookbook App 使用“Contoso Cookbook Show recipes”語句支援啟動瀏覽 App 主頁。此外,像是“Contoso Cookbook Show Indian recipes”的語句將觸發瀏覽到內頁顯示特定的美食型態類別資訊。

任務 3 – 註冊語音指令

透過 VoiceCommandService 類別提供 App 語音指令服務,它的靜態方法 InstallCommandSetsFromFileAsync 允許 App 從 XML 檔案下載一套自訂語音指令 (如先前任務中建立的),只需要進行一次初始化 (當啟動 App 時),這就是為什麼我們將它加到 App 類別。

  1. 打開 App.xaml.cs 檔案

  2. 在檔案頂端增加下列“using”聲明

    C#
    using Windows.Phone.Speech.VoiceCommands;
    using Windows.Phone.Speech.Recognition;

  3. 增加以下 方法到 App 類別:

    C#
    async private static void InitializeVoiceCommands()
    {
    var filename = "SupportedVoiceCommands.xml";

    try
    {
    var location = Package.Current.InstalledLocation.Path;
    var fileUriString = String.Format("file://{0}/{1}", location, filename);
    await VoiceCommandService.InstallCommandSetsFromFileAsync(new
    Uri(fileUriString));
    }
    catch (Exception ex)
    {
    System.Diagnostics.Debug.WriteLine(ex.Message);
    }
    }

    前方的代碼註冊了系統內語音指令服務中 Contoso Cookbook App 支援的語音指令。

  4. 找到構造函數並在開始的地方增加以下幾行:

    C#
    /// <summary>
    /// Constructor for the Application object.
    /// </summary>
    public App()
    {
    InitializeVoiceCommands( );

    其中,我們添加了一組命令讓使用者提供群組名稱作為參數。語音指令引擎反饋認可的文字並將資訊傳導給 App。我們必須將食譜群組名稱分析為 App 內部使用的群組 id,這需要一個輔助方法從名稱轉換到群組。

  5. 在 DataModel 資料夾找到 RecipeDataSource.cs 檔案

  6. 增加以下方法到 RecipeDataSource 類別:

    C#
    public RecipeDataGroup FindGroupByName(string groupName)
    {
    return (from g in ItemGroups
    where g.Title == groupName
    select g).SingleOrDefault();
    }

    最後,我們必須更改 GroupDetailPage.xaml 頁面中的 OnNavigatedTo 方法以支援可能被語音指令引擎加入詢問串中的 "groupName" 參數。當使用者說出“Show Indian recipes”,語音指令引擎將導向以下URI:GroupDetailPage.xaml?groupName=Indian.

  7. 打開 GroupDetailPage.xaml.cs 檔案

  8. 以下列代碼取代 OnNavigatedTo method :

    C#
    protected async override void OnNavigatedTo(NavigationEventArgs e)
    {
    if (!App.Recipes.IsLoaded)
    await App.Recipes.LoadLocalDataAsync();

    if (NavigationContext.QueryString.ContainsKey("groupName"))
    {
    string groupName = NavigationContext.QueryString["groupName"];

    group = App.Recipes.FindGroupByName(groupName);
    pivot.DataContext = group;
    }
    else
    {
    string UniqueId = NavigationContext.QueryString["ID"];
    group = App.Recipes.FindGroup(UniqueId);
    pivot.DataContext = group;
    }

    SetPinBar();

    //Update main tile with recently visited group
    Features.Tile.UpdateMainTile(group);

    base.OnNavigatedTo(e);
    }

    註 : 詢問串參數名稱 (前方代碼的“groupName”) 與語音指令定義檔中大括號內的參數是一樣的,也必須區分大小寫。

任務 4 – 測試語音指令

App 支援基本的語音指令功能現在已經完成,這一任務中,我們將執行 App 並測試幾種指令來測試看看。

  1. 編譯、設定並執行 App

  2. 按住 "Start" 按鈕直到出現 "Listening…" 字樣


    圖 5
    語音指令啟動" Listening” 的螢幕模式

  3. 按壓螢幕右上角的問號可以看到常見的詞組清單。


    圖 6
    常見的語音指令清單

    想用 App 的自定義語句則點選 "Apps" 分頁並找到 Contoso Cookbook App:


    圖 7
    個別應用程式自定義的語音指令

  4. 按下 "speak" 按鍵並說出 "Contoso Cookbook show recipes"

  5. 確認 Contoso Cookbook App 打開了主頁,如果系統辨識失敗則重試一遍。


    圖 8
    語音指令辨識結果

  6. 回到首頁

  7. 按住 "Start" 按鍵直到畫面出現 "Listening…"

  8. 說出 "Contoso Cookbook show me Italian recipes".並確認應用程式打開 Italian recipes 頁面


    圖 9
    語音指令辨識畫面

    如果語音指令引擎無法辨識你在說甚麼,將會出現另一個畫面顯示幾種可供選擇的選項。例如:上一個例子中你必須由 Italian 或 Indian recipes 中擇一。


    圖 10
    系統無法明確辨識語音指令時會顯示選單供選擇

練習 2

現在,App 已經可以回應語音指令並瀏覽到使用者要求的食譜類型,我們將繼續增加 App 內的語音指令。在本練習中,要新增一個 "Search" 按鍵,讓使用者可以在說出食譜標題後就直接瀏覽到要尋找的食譜資料

任務 1 - 更改 Data Source

首先,我們要新增一個方法將食譜標題翻譯成內部的食譜 id,要這麼做,我們必須增加一個協助用的方法到 RecipeDataSource 類別。

  1. 打開 DataModel 資料夾中的 RecipeDataSource.cs 檔案

  2. 增加以下方法到 RecipeDataSource 類別中

    C#
    public RecipeDataItem FindRecipeByText(string text)
    {
    return ItemGroups.SelectMany(g => g.Items).
    SingleOrDefault(i => i.Title.ToLower().Contains(text.ToLower( )));
    }

    註 : 上面的方法依賴一個有效的食譜標題作為它的參數,若找不到對應的食譜則拋回一個異常通知。一個商業付費的 App 應該要考慮到辨識失敗的問題並尋求改善,為了簡單起見,這個討論終將不會探討辨識失敗的相關問題。

任務 2 – 增加動態語音指令功能

App 中支援語音辨識最快速簡單的方式是使用 Windows Phone 8 內建的聽寫語法。一種語言中,當一個語音辨識對象是有具體實例的,Windows Phone 8 內建的聽寫語法可以辨識最多單字及短句。因此,使用內建的聽寫語法可以讓語音辨識只利用幾行簡短的代碼完成功能。

  1. 打開 App.xaml.cs 檔案

  2. 找到建構函數並在末端增加以下幾行:

    C#
    Recipes.RecipesLoaded += Recipes_RecipesLoaded;

  3. 接著,增加 RecipesLoaded 事件處理器,它可以在食譜資料傳輸到記憶體後初始化語音辨識。

    C#
    void Recipes_RecipesLoaded(object sender, EventArgs e)
    {
    InitializeVoiceRecognition();
    }

  4. 接下來新增一個新的靜態成員

    C#
    public static SpeechRecognizerUI SpeechRecognizerWithUI { get; private set; }

  5. 再新增方法將聲音辨識初始化

    C#
    private void InitializeVoiceRecognition()
    {
    SpeechRecognizerWithUI = new SpeechRecognizerUI();
    List<string> searchTerms = ExtractSearchTerms();
    SpeechRecognizerWithUI.Recognizer.Grammars.AddGrammarFromList(
    "SearchTerms", searchTerms);
    }

  6. 最後,添加可以新增搜索條件的方法

    C#
    private List<string> ExtractSearchTerms()
    {
    List<string> terms = new List<string>(20)
    {
    "shrimp",
    "noodle",
    "rice",
    "dumpling",
    "macaroons",
    "bacon",
    "fish",
    "meatballs",
    "cucumber",
    "lamb",
    "onion",
    "chili",
    "bean",
    "oil",
    "clams",
    "lemon",
    "risotto",
    "taco",
    "pork",
    "salsa"
    };

    return terms;
    }

    註 : 前方的方法建立了一個靜態的術語列表。語音指令服務支援不同語言複雜的語法選擇將不包含在這個章節內討論。然而 App 執行時若術語列表動態地改變時,和 App 內建下載來自 XML 檔案的靜態術語列表相比,上述的方法就很有用。

任務 3 – 新增一個 Search 按鍵

  1. 打開 MainPage.xaml 檔案

  2. 增加以下代碼到 LayoutRoot Grid 元素後方 :

    XAML
    <phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="False"
    Mode="Minimized">
    <shell:ApplicationBarIconButton x:Name="btnSearch"
    IconUri="/Assets/Icons/mic.png"
    Click="btnSearch_Click" Text="Search"/>
    </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

  3. 上方的代碼聲明了主頁裡應用程式列 (Application Bar ) 的 "Search" 按鈕。

  4. 接著,在隱藏代碼檔案新增內容,打開 MainPage.xaml.cs 檔案

  5. 在 MainPage 類別中新增下列事件處理器

    C#
    private async void btnSearch_Click(object sender, EventArgs e)
    {
    App.SpeechRecognizerWithUI.Settings.ExampleText = "salad";
    App.SpeechRecognizerWithUI.Settings.ShowConfirmation = true;
    App.SpeechRecognizerWithUI.Settings.ListenText =
    "What are you looking for?";
    var result = await App.SpeechRecognizerWithUI.RecognizeWithUIAsync();

    if (result.ResultStatus == SpeechRecognitionUIStatus.Succeeded)
    {
    SpeechSynthesizer synth = new SpeechSynthesizer();

    if (result.RecognitionResult.TextConfidence ==
    SpeechRecognitionConfidence.High ||
    result.RecognitionResult.TextConfidence ==
    SpeechRecognitionConfidence.Medium)
    {
    var recipe = App.Recipes.FindRecipeByText(
    result.RecognitionResult.Text);

    if (null != recipe)
    NavigationService.Navigate(new Uri("/RecipeDetailPage.xaml?ID=" +
    recipe.UniqueId, UriKind.Relative));
    else
    await synth.SpeakTextAsync(string.Format(
    "Cannot find any {0}. I am sorry!",
    result.RecognitionResult.Text));
    }
    else
    {
    await synth.SpeakTextAsync("I am not sure what you just said. Please try again!");
    }
    }
    }

  6. 前方代碼將之前任務實體化的 SpeechRecognitionUI 物件做設定,靠著呼叫 RecognizeWithUIAsync 方式以編程方式啟動語音辨識功能。這個方式預先下載先前創建的語句清單及回報辨識結果。

  7. 如果語音指令服務成功辨識一個字 (信心度為中或高),我們使用這個練習開始時加入的 FindRecipeByText 方法,並利用食譜 id 試圖瀏覽到 RecipeDetailPage.xaml 頁面。

任務 4 – 測試動態語音辨識

  1. 編譯、設定並執行 App

  2. 按住螢幕右下角的 "…"按鈕顯示 "Search" 按鍵


    圖 11
    語音搜尋按鍵

  3. 按下 "Search" 按鍵並說出 "rice"


    圖 12
    說出 Rice

  4. 瀏覽回 App 首頁

  5. 按住 "…" 按鍵然後再按下 "Search" 按鍵, 說出 "fish"


    圖 13
    說出 Fish

總結

在這個實驗中,你學會了如何使用 Windows Phone 8 靜態的外部語音指令辨識器及 App 內的語音指令。只需要幾個步驟就能使一個 App 可以識別語音指令,你可以使用編譯方式啟用 App 的語音識別功能,透過使用上下文代碼及編譯過程達到更多語音功能的控制設定。

多國語言的語音辨識功能讓您的 App 更容易操作及使用,對任何 App 都是一項利多功能。

繼續下一個實驗