Windows Phone

音声コマンドを使用して Windows Phone 8 アプリを音声対応にする

F Avery Avery

コード サンプルのダウンロード

先日の夕方、仕事の後に旧友と会う約束がありましたが仕事が長引いてしまいました。友人は既に待ち合わせ場所に車で向かっていると思ったため、電話をかけても出ることができないと考えました。それでも、会社を急いで出て車に向かいながら、Windows Phone をつかんでスタート ボタンを押しました。"イヤコン" からのメッセージを聞いて「Robert Brown にメール」と言い、メール アプリが起動したところで「悪いが少し遅れる。今会社を出た」と言い、「送信」と言ってテキスト メッセージを送信しました。

組み込みのメール アプリで音声機能を使用できなければ、走るのを止めて、イライラしながら Windows Phone をいじってテキストを送信しなければなりませんでした。私としては、太い指でキーパッドを操作するのは厄介で、走りながら画面を見るのは難しいのです。メールの送信に音声を使用することで、時間を節約でき、ストレスやかなりの不安を軽減できました。

Windows Phone 8 には、開発者が音声認識や音声合成を通じてユーザーとやり取りできるように、これと同様の音声機能が用意されています。その機能は先ほどの例で示した 2 つのシナリオをサポートします。ユーザーは携帯電話のどの画面からでもコマンドを言ってアプリを起動し、たったひと言で操作を実行できます。また、アプリが起動すると、携帯電話ではユーザーが話した言葉からコマンドやテキストを取得し、通知やフィードバックとしてユーザーにテキストを音声再生して、ユーザーとの対話を継続します。

1 つ目のシナリオは、音声コマンドという機能でサポートされます。この機能を有効にするには、アプリで VCD (Voice Command Definition: 音声コマンド定義) ファイルを使用して、アプリが処理できる一連のコマンドを指定します。アプリを音声コマンドで起動すると、コマンド名、パラメーター名、認識されたテキストなどのパラメーターをクエリ文字列として受け取り、これらを使用してユーザーが指定したコマンドを実行できます。この 2 部構成のコラムの第 1 部では、Windows Phone 8 のアプリで音声コマンドを有効にする方法について説明します。

第 2 部では、アプリ内での音声による対話について説明します。この音声対話をサポートするため、Windows Phone 8 には音声認識と音声合成の API が用意されています。この API には、確認とあいまいさ解消のための既定の UI と、音声認識文章校正やタイムアウトなどのプロパティの既定値が含まれており、数行のコードでアプリに音声認識を追加できるようになっています。また、音声合成 API (別名、TTS) を使用すると、単純なシナリオのコードを簡単に作成できます。この API では、World Wide Web コンソーシアムの音声合成マークアップ言語 (SSML) を使用した微調整や、携帯電話に既に保存されているエンド ユーザーの音声やマーケットプレースからダウンロードした音声の切り替えなど、高度な機能を利用することも可能です。第 2 部のコラムではこの機能について詳しく説明します。引き続きご注目ください。

以上の機能について説明するため、Magic Memo という簡単なアプリを開発しました。スタート ボタンを押し、音声を求められたらコマンドを言うことで、Magic Memo を起動してコマンドを実行できます。アプリでは、簡単な内容を口述してメモを入力したり、音声を使用してアプリ内を移動しコマンドを実行したりできます。ここでは、これらの機能を実装するソース コードについて説明します。

アプリで音声機能を使用するための要件

Magic Memo アプリは、お使いの開発環境が Windows Phone 8 アプリの開発および携帯電話エミュレーターのテストのためのハードウェアおよびソフトウェアの要件を満たしていれば、既定で機能します。このコラムの公開時の要件は次のとおりです。

  • 64 ビット バージョンの Windows 8 Pro 以上
  • 4 GB 以上の RAM
  • BIOS でサポートされる第 2 レベルのアドレス変換
  • Hyper-V のインストールおよび実行
  • Visual Studio 2012 Express for Windows Phone 以上

いつものように、アプリの開発および実行を試す前に、MSDN ドキュメントで最新の要件を確認することをお勧めします。

アプリを最初から独自に開発する場合は、次の 3 点にご注意ください。

  1. デバイスのマイクおよびスピーカーが適切に機能していることを確認します。
  2. プロパティ エディターの該当するボックスを確認するか、次のコードを XML ファイルに手作業で含めることで、音声認識とマイクの機能を WpAppManifest.xml ファイルに追加します。
<Capability Name="ID_CAP_SPEECH_RECOGNITION"/>
       <Capability Name="ID_CAP_MICROPHONE"/>
  1. 音声認識を試すときは、ユーザーが音声のプライバシー ポリシーに同意していない場合にスローされる例外をキャッチします。付属のコード サンプルのダウンロードにある MainPage.xaml.cs の GetNewMemoByVoice ヘルパー関数に、この処理方法の例を用意しています。

シナリオ

スマートフォンでは、アプリを起動してコマンドを 1 つ実行し、その後必要に応じて追加のコマンドを実行するのが一般的なシナリオです。この操作を手動で行う場合、アプリの検索、適切な場所への移動、ボタンまたはメニュー項目の検索、見つけた項目のタップなど、複数の手順が必要です。この手順が習慣的に行われるようになった今でも多くのユーザーが不満を感じています。

たとえば、Magic Memo サンプル アプリに保存されたメモの中から 12 番目のメモを表示するには、ユーザーはアプリを見つけて起動し、[View saved memos] (保存されたメモの表示) をタップして、目的のメモが表示されるまで下方向にスクロールする必要があります。この操作を、Windows Phone 8 の音声コマンドの機能を使用した場合と比較します。ユーザーはスタート ボタンを押して「Magic Memo のメモ 12 を表示」と言うだけです。これで、Magic Memo アプリが起動し、メッセージ ボックスに目的のメモが表示されます。このような簡単なコマンドでも、ユーザーの操作を確実に短縮します。

アプリに音声コマンドを実装するための 3 つの手順と、動的コンテンツを処理するためのオプションの 4 つ目の手順があります。ここからはこれらの手順について説明します。

認識するユーザー コマンドを指定する

音声コマンドを実装するには、まず、認識するコマンドを VCD ファイルに指定します。VCD ファイルは簡単な XML 形式で作成し、CommandSet 要素のコレクションで構成し、それぞれの Command 子要素に認識するフレーズを含めます。Magic Memo アプリの例を図 1 に示します。

図 1 Magic Memo アプリの音声コマンド定義ファイル

<?xml version="1.0" encoding="utf-8"?>
<VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.0">
  <CommandSet xml:lang="en-us" Name="MagicMemoEnu">
    <!-- Command set for all US English commands-->
    <CommandPrefix>Magic Memo</CommandPrefix>
    <Example>enter a new memo</Example>

    <Command Name="newMemo">
      <Example>enter a new memo</Example>
      <ListenFor>Enter [a] [new] memo</ListenFor>
      <ListenFor>Make [a] [new] memo</ListenFor>
      <ListenFor>Start [a] [new] memo</ListenFor>
      <Feedback>Entering a new memo</Feedback>
      <Navigate />    <!-- Navigation defaults to Main page -->
    </Command>

    <Command Name="showOne">
      <Example>show memo number two</Example>
      <ListenFor>show [me] memo [number] {num} </ListenFor>
      <ListenFor>display memo [number] {num}</ListenFor>
      <Feedback>Showing memo number {num}</Feedback>
      <Navigate Target="/ViewMemos.xaml"/>
    </Command>

    <PhraseList Label="num">
      <Item> 1 </Item>
      <Item> 2 </Item>
      <Item> 3 </Item>
    </PhraseList>
  </CommandSet>

  <CommandSet xml:lang="ja-JP" Name="MagicMemoJa">
    <!-- Command set for all Japanese commands -->
    <CommandPrefix>マジック・メモ</CommandPrefix>
    <Example>新規メモ</Example>

    <Command Name="newMemo">
      <Example>新規メモ</Example>
      <ListenFor>新規メモ[を]</ListenFor>
      <ListenFor>新しいメモ</ListenFor>
      <Feedback>メモを言ってください</Feedback>
      <Navigate/>
    </Command>

    <Command Name="showOne">
      <Example>メモ1を表示</Example>
      <ListenFor>メモ{num}を表示[してください] </ListenFor>
      <Feedback>メモ{num}を表示します。 </Feedback>
      <Navigate Target="/ViewMemos.xaml"/>
    </Command>

    <PhraseList Label="num">
      <Item> 1 </Item>
      <Item> 2 </Item>
      <Item> 3 </Item>
    </PhraseList>
</CommandSet>
</VoiceCommands>

VCD ファイルを設計するためのガイドラインを次に示します。

  1. コマンドの接頭辞の発音が Windows Phone のキーワードと異なるようにします。これにより、アプリと携帯電話の組み込みの機能が混同されないようにします。英語 (米国) の場合、キーワードには call、dial、start、open、find、search、text、note、help などがあります。
  2. コマンドの接頭辞がアプリ名とまったく異なるものではなく、アプリ名の一部または自然な発音のものになるようにします。これにより、ユーザーの混乱を避け、アプリを他のアプリや機能と間違えられないようにします。
  3. コマンドを認識するにはコマンドの接尾辞が完全に一致する必要があります。そのため、コマンドの接頭辞は簡単で覚えやすいものにすることをお勧めします。
  4. 各コマンド セットに Name 属性を指定して、コードからアクセスできるようにします。
  5. Command 要素の ListenFor 要素の発音はそれぞれ異なるものにして、誤認識の可能性を減らすようにします。
  6. 同じコマンドの ListenFor 要素は異なる方法で同じコマンドを指定するようにします。コマンドの ListenFor 要素が複数の操作に対応する場合は個別のコマンドに分けます。これにより、アプリのコマンドの処理が簡単になります。
  7. さまざまな制限があることを覚えておいてください。コマンド セットには Command 要素を 100 個までしか含めることができません。コマンドには ListenFor 要素のエントリを 10 個までしか含めることができません。PhraseList 要素は合計 50 個までしか使用できません。すべての PhraseList 要素で PhraseList の項目は合計 2,000 個までしか含めることができません。
  8. PhraseList 要素を認識するには部分一致ではなく完全一致が必要です。そのため、"Star Wars" と "Star Wars Episode One" の両方を認識するには、両方とも PhraseList 要素に含めます。

ここで紹介する例では 2 つの CommandSet 要素を含め、それぞれ異なる xml:lang 属性と Name 属性を指定しています。CommandSet 要素は xml:lang 値ごとに 1 つのみです。Name 属性も一意値にする必要がありますが、Name 属性は Name 属性の値の仕様によってのみ制限されます。オプションですが、アプリのコードから CommandSet 要素にアクセスして 4 つ目の手順を実装するには Name 属性が必要になるため、Name 属性を含めることを強くお勧めします。また、アプリで一度に有効にできる CommandSet 要素は 1 つのみです。つまり、ユーザーが SETTINGS/speech で設定しているように、xml:lang 属性が現在のグローバルな音声認識エンジンの値に完全に一致する CommandSet 要素のみ有効になります。ユーザーが市場で必要としていると考えられるすべての言語の CommandSet 要素を含めます。

次に、CommandPrefix 要素に注目してください。これはユーザーがアプリを呼び出すときに使用できるエイリアスだと考えてください。これはアプリ名が Mag1c や gr00ve など、一般的でないつづりや発音しにくい文字列の場合に役立ちます。この単語やフレーズは音声認識エンジンで認識できる必要があり、また、Windows Phone の組み込みのキーワードと発音が異なるようにしなければならないことを覚えておいてください。

CommandSet 要素と Command 要素の両方の子要素に Example 要素があることに注目してください。CommandSet 要素の Example 要素は、図 2 に示すシステム ヘルプの [WHAT CAN I SAY?] (音声コマンドを使用できるアプリ) 画面に表示されるアプリの一般的な例です。これに対して、Command 要素の Example 要素はその Command 要素固有の要素です。この Example 要素は、ユーザーが図 2 に示すヘルプ ページのアプリ名をタップするときに表示されるシステム ヘルプ ページ (図 3 参照) に表示されます。

Help Page Showing Voice Command Examples for Installed Apps
図 2 インストールされているアプリの音声コマンドの例を示すヘルプ ページ

Example Page for Magic Memo Voice Commands
図 3 Magic Memo の音声コマンドの例を示すページ

また、CommandSet 要素内の Command 子要素はそれぞれアプリの起動後に実行する操作に対応します。Command 要素には複数の ListenFor 要素を含めることができますが、これらはすべて異なる方法で子要素として指定している操作 (コマンド) を実行するようにアプリに指示する必要があります。

ListenFor 要素のテキストには 2 つの特別な構造があることにも注目してください。テキストを囲む角かっこは、テキストが省略可能であることを示します。つまり、ユーザーが話した内容に、角かっこで囲まれているテキストが含まれても含まれなくても音声を認識します。中かっこには、PhraseList 要素を参照するラベルを含めます。図 1 の英語 (米国) の例では、"showOne" コマンドにある 1 つ目の ListenFor 要素に、以下のフレーズの一覧を参照するラベル {num} を含めます。このラベルは、参照先の一覧のフレーズ (この場合は数) を含めるスロットと考えることができます。

ユーザーの発言の中でコマンドが認識されると、携帯電話のグローバルな音声認識エンジンによってアプリが起動し、対応する Command 要素にある Navigate 要素の Target 属性で指定しているページが表示されます。このことについては、後の 3 つ目の手順で説明します。続いて、2 つ目の手順について見てみましょう。

音声コマンドを有効にする

VCD ファイルをインストール パッケージに含めたら、2 つ目の手順に進みます。この手順では、Windows Phone 8 でシステム文法にアプリのコマンドを追加できるようにファイルを登録します。登録するには、VoiceCommandService クラスの InstallCommandSetsFromFileAsync 静的メソッドを呼び出します (図 4 参照)。ほとんどのアプリは初回実行時にこのメソッドを呼び出しますが、もちろんいつでも呼び出すことができます。VoiceCommandService クラスの実装は、ファイルに変更がなかった場合、その後の呼び出しで何も実行しないで済むように考えられています。そのため、アプリの起動のたびに呼び出されることを懸念する必要はありません。

図 4 アプリ内からの VCD ファイルの初期化

using Windows.Phone.Speech.VoiceCommands;
// ...
// Standard boilerplate method in the App class in App.xaml.cs
private async void Application_Launching(object sender, 
  LaunchingEventArgs e)
{
  try // try block recommended to detect compilation errors in VCD file
  {
    await VoiceCommandService.InstallCommandSetsFromFileAsync(
      new Uri("ms-appx:///MagicMemoVCD.xml"));
  }
  catch (Exception ex)
  {
    // Handle exception
  }
}

InstallCommandSetsFromFileAsync というメソッド名が示すように、VCD ファイルの運用上の単位はファイル自体ではなく CommandSet 要素になります。このメソッドの呼び出しでは、ファイルに含まれるすべてのコマンド セットを調査および検証しますが、xml:lang 属性がグローバルな音声エンジンの値と完全に一致するコマンドのみインストールします。ユーザーがグローバルな認識言語を VCD ファイルの別の CommandSet 要素の xml:lang 属性に一致するように切り替えると、この CommandSet 要素が読み込まれ有効になります。

音声コマンドを処理する

続いて、3 つ目の手順について説明します。グローバルな音声認識エンジンでコマンドの接頭辞とアプリのコマンドが認識されると、アプリが起動して Navigate 要素の Target 属性に指定しているページが表示されます。Target 属性が指定されていない場合は、既定のタスク ターゲット (通常は Silverlight アプリの MainPage.xaml) が使用されます。また、Command 要素名と PhraseList 要素の値がクエリ文字列のキーと値の組み合わせに追加されます。たとえば、「Magic Memo show memo number three」というフレーズが認識されると、クエリ文字列は次のようになります (実際の文字列は実装やバージョンに応じて変わる可能性があります)。

"/ViewMemos.xaml?voiceCommandName=show&num=3&
reco=show%20memo%20number%20three"

さいわい、クエリ文字列を解析し、パラメーターを詳しく分析する必要はありません。これらは NavigationContext オブジェクトの QueryString コレクションで確認できます。アプリはこのデータを使用して、アプリが音声コマンドによって起動したかどうかを特定できます。アプリが音声コマンドによって起動した場合は、(ページの Loaded ハンドラーなどで) コマンドを適切に処理します。図 5 に Magic Memo アプリの ViewMemos.xaml ページの例を示します。

図 5 アプリの音声コマンドの処理

// Takes appropriate action if the application was launched by voice command.
private void ViewMemosPage_Loaded(object sender, RoutedEventArgs e)
{
  // Other code omitted
  // Handle the case where the page was launched by Voice Command
  if (this.NavigationContext.QueryString != null
    && this.NavigationContext.QueryString.ContainsKey("voiceCommandName"))
  {
    // Page was launched by Voice Command
    string commandName =
      NavigationContext.QueryString["voiceCommandName"];
    string spokenNumber = "";
    if (commandName == "showOne" &&
      this.NavigationContext.QueryString.TryGetValue("num", 
        out spokenNumber))
    {
      // Command was "Show memo number 'num'"
      int index = -1;
      if (int.TryParse(spokenNumber, out index) &&
        index <= memoList.Count && index > 0)
      { // Display the specified memo
        this.Dispatcher.BeginInvoke(delegate
          { MessageBox.Show(String.Format(
          "Memo {0}: \"{1}\"", index, memoList[index - 1])); });
      }
    }
    // Note: no need for an "else" block because if launched by another VoiceCommand
    // then commandName="showAll" and page is shown
  }
}

ページに移動するにはさまざまな方法があるため、図 5 のコードでは、まず、クエリ文字列の voiceCommandName キーの存在を確認して、ユーザーが音声コマンドでアプリを起動したかどうかを特定します。ユーザーが音声コマンドでアプリを起動した場合、コマンド名を確認し、PhraseList 要素の num パラメーターの値を取得します。この値はユーザーが表示したいメモの番号になります。このページには音声コマンドが 2 つしか設定されていないため処理は簡単ですが、多くの音声コマンドで起動できるページでは、commandName で switch ブロックなどを使用して、実行する操作を特定します。

この例の PhraseList 要素も単純で、保存されているそれぞれのメモの番号を表す一連の数字になります。より高度なシナリオを思い描く場合は、Web サイト上のデータなど、動的に設定されるフレーズの一覧が必要です。前述のように、4 つ目の手順は省略可能ですが、これらのシナリオに対する PhraseList 要素の実装に対応できます。次はこの手順について説明します。

アプリのフレーズ一覧を更新する

図 1 に示した VCD ファイルには問題があることにお気付きでしょう。VCD ファイルで静的に定義した PhraseList 要素の "num" パラメーターは最大 3 つの項目の認識をサポートしますが、最終的にはアプリの分離ストレージに 3 つよりもはるかに多くのメモが保存されることになる可能性があります。フレーズの一覧が時間の経過と共に変わる場合、アプリ内でフレーズの一覧を動的に更新する方法があります (図 6 参照)。これは特に、ダウンロードした映画、お気に入りのレストラン、携帯電話の現在地の近くにある気になるスポットなど、動的な一覧を認識する必要があるアプリで役に立ちます。

図 6 インストールされているフレーズの一覧の動的な更新

// Updates the "num" PhraseList to have the same number of
// entries as the number of saved memos; this supports
// "Magic Memo show memo 5" if there are five or more memos saved
private async void UpdateNumberPhraseList(string phraseList,
  int newLimit, string commandSetName)
{
  // Helper function that sets string array to {"1", "2", etc.}
  List<string> positiveIntegers =
    Utilities.GetStringListOfPositiveIntegers(Math.Max(1, newLimit));
  try
  {
    VoiceCommandSet vcs = null;
    if (VoiceCommandService.InstalledCommandSets.TryGetValue(
      commandSetName, out vcs))
    {
      // Update "num" phrase list to the new numbers
      await vcs.UpdatePhraseListAsync(phraseList, positiveIntegers);
    }
  }
  catch (Exception ex)
  {
    this.Dispatcher.BeginInvoke(delegate
      { MessageBox.Show("Exception in UpdateNumberPhraseList " 
        + ex.Message); }
    );
  }
}

このことについては Magic Memo アプリを使用して説明しませんが、ユーザー主体の更新では、アプリを起動していないときでも更新が行われることがあるため、動的に更新されるフレーズの一覧に対応することをお勧めします。

これで、アプリの音声コマンドを有効にする 4 つの手順について説明しました。Magic Memo サンプル アプリを試してみてください。通常、VCD ファイルを読み込むため、アプリを一度起動する必要があることを覚えておいてください。それ以後は、次のように言ってアプリを起動し、すぐにページを表示してコマンドを実行できます。

  • Magic Memo の新しいメモを入力
  • Magic Memo のすべてのメモを表示
  • Magic Memo のメモ 4 を表示

次回のトピック: アプリ内での対話

今回説明した音声コマンドの実装は、ユーザーがメール作成、検索、電話などの組み込みアプリと同じように Windows Phone 8 のアプリを操作できるようにするための最初の手順です。

続いての手順では、アプリ内で対話できるようにします。アプリ内での対話では、アプリの起動後に話しかけてテキストの記録やコマンドの実行を行ったり、話した内容を音声で再生したりします。このトピックについては第 2 部で詳しく説明しますのでご期待ください。

F Avery Bishop は、20 年以上ソフトウェア開発に携わってきました。そのうち 12 年間はマイクロソフトに勤務し、音声認識プラットフォームのプログラム マネージャーを務めています。彼は、複雑なスクリプト サポート、多言語アプリ、音声認識などのトピックについて、アプリの自然言語サポートに多数の記事を公開しています。

この記事のレビューに協力してくれた技術スタッフの Robert Brown、Victor Chang、Jay Waltmunson、および Travis Wilson に心より感謝いたします。