音声入力の共有

通常、音声入力は内蔵マイク、外部マイク、または端末に接続されたオーディオインターフェースから取り込まれます。また、音声入力はスマートフォンの会話から取り込まれることもあります。

場合によっては、複数のアプリが同じ音声入力を「キャプチャ」しようとすることがあるかもしれません。それぞれ異なるタスクを実行している場合もあります。たとえば、シンプルなボイスレコーダーのように受け取った音声を録音するアプリもあれば、音声コマンドに応答する Google アシスタントやアクセシビリティ サービスのように音声を「リスニングする」アプリもあります。

いずれにしても、それらのアプリは音声入力を受け取ろうとします。アプリが音声を録音するのか、それとも単にリスニングするのかに関係なく、このページでは「キャプチャ」という用語を使用することにします。

複数のアプリが同時に音声をキャプチャしようとすると、同じ音源からの音声信号をそのすべてに配信しようとして問題が発生する場合があります。このページでは、Android システムで音声をキャプチャする複数のアプリの間で音声入力を共有する方法について説明します。

Android 10 より前の動作

Android 10 より前は、入力音声ストリームをキャプチャできるのは、一度に 1 つのアプリだけでした。アプリが既に音声を録音またはリスニングしていた場合、別のアプリが AudioRecord オブジェクトを作成することはできましたが、AudioRecord.startRecording() を呼び出した時点でエラーが返され、録音は開始されませんでした。

このルールに関する 1 つの例外は、特権アプリ(Google アシスタントやアクセシビリティ サービスなど)がパーミッション android.permission.CAPTURE_AUDIO_HOTWORD を付与されており、タイプが HOTWORD の音源を使用していた場合です。その場合、別のアプリが録音を開始することができました。その場合、特権アプリは終了し、新しいアプリが入力をキャプチャしていました。

Android 9 で加えられたもう 1 つの変更点は、音声入力をキャプチャできるのはフォアグラウンド(またはフォアグラウンド サービス)で実行されているアプリだけだったということです。フォアグラウンド サービスまたはフォアグラウンド UI コンポーネントのないアプリがキャプチャを開始した場合、アプリの実行は続行されますが、その時点で唯一の音声キャプチャ アプリであったとしても受け取る音声は無音になっていました。

新しい動作

以前の動作は「早い者勝ち」でした。あるアプリが音声キャプチャを開始すると、そのキャプチャ アプリが停止するまで、他のアプリが音声入力にアクセスする手段はありませんでした。

Android 10 (API レベル 29) 以上 では優先度スキームが定められており、それにより複数アプリの実行中にアプリ間で入力音声ストリーム切り替えが可能になっています。ほとんどの場合、新しいアプリが音声入力を獲得すると、それまでキャプチャしていたアプリは、実行を継続するものの無音を受け取ることになります。場合によっては、システムが音声を両方のアプリに配信することが可能です。以下に、さまざまな共有シナリオについて説明します。

このスキームは、音声出力の利用における競合で音声フォーカスが複数アプリを処理する方法とよく似ています。しかし、音声フォーカスではフォーカスの獲得と解放のリクエストをプログラムで管理するのに対して、ここで説明されている入力切り替えスキームは優先度ポリシーに基づくものであり、新しいアプリが音声キャプチャを開始した時点で常に自動的に適用されます。

音声キャプチャについて Android では、次の 2 種類のアプリが区別されています:

  • 「通常」アプリは、ユーザーによってインストールされます。
  • 「特権」アプリは、端末に当初からプレインストールされています。その中には Google アシスタントやすべてのアクセシビリティ サービスが含まれています。

さらに、アプリがプライバシーに注意が必要な音源(CAMCORDER または VOICE_COMMUNICATION)を使用する場合には、異なる方法で処理されます。

音声入力の使用と共有に関する優先度ルールは次のとおりです:

  • 特権アプリは、通常アプリに対して優先度が高くなっています。
  • フォアグラウンド UI が実際に表示されるアプリの優先度は、バックグラウンド アプリよりも高くなっています。
  • プライバシーに注意が必要な音源から音声をキャプチャするアプリの優先度は、その必要がないアプリよりも高くなっています。
  • 2 つの通常アプリが同時に音声をキャプチャすることはできません。
  • 場合によっては、特権アプリが音声入力を別のアプリと共有することができます。
  • 同じ優先度の 2 つのバックグラウンド アプリが音声をキャプチャする場合、後から起動したものの優先度のほうが高くなります。

共有のシナリオ

2 つのアプリが音声をキャプチャしようとすると、その両方が入力信号を受け取れる場合もあれば、一方が無音を受け取る場合もあります。

主なシナリオが 4 つあります:

  • アシスタント + 通常アプリ
  • アクセシビリティ サービス + 通常アプリ
  • 2 つの通常アプリ
  • 通話 + 通常アプリ

アシスタント + 通常アプリ

アシスタントは、プレインストールされていて RoleManager.ROLE_ASSISTANT の役割が付与されているため、特権アプリです。この役割の他のプレインストール アプリも同様に扱われます。

Android での入力音声共有のルールは以下のとおりです:

  • プライバシーに注意が必要な音源を使用する別のアプリが既にキャプチャしていない限り、アシスタントは(フォアグラウンドかバックグラウンドに関係なく)音声を受け取れます。

  • アシスタントの UI コンポーネントが最前面に表示されていなければ、アプリは音声を受け取ります。

どちらのアプリも音声を受け取るのは、アシスタントがバックグラウンドであり、かつ他方のアプリのキャプチャ対象がプライバシーに注意が必要な音源ではない場合に限られます。

アクセシビリティ サービス + 通常アプリ

AccessibilityService では、厳密な宣言が必要です。

Android での入力音声共有のルールは以下のとおりです:

  • サービスの UI が最前面に表示されている場合、サービスとアプリの両方が音声入力を受け取ります。この動作により、音声コマンドによって通話またはビデオキャプチャを制御するといった機能が可能になります。

  • サービスが最前面でない場合、この後に説明する通常アプリ 2 個の場合と同じように処理されます。

2 つの通常アプリ

2 つのアプリが並行して同時にキャプチャしている場合、その一方のアプリだけが音声を受け取り、他方は無音になります。

Android での入力音声共有のルールは以下のとおりです:

  • どちらのアプリもプライバシーへの注意が不要な場合、UI が最前面に表示されているアプリが音声を受け取ります。どちらのアプリにも UI がない場合、後からキャプチャをした方のアプリが音声を受け取ります。
  • 一方のアプリでプライバシーに注意が必要な場合、そのアプリが音声を受け取ります。もう一方のアプリは、UI が最前面に表示されているとしても、また後からキャプチャを開始したとしても無音を受け取ります。
  • どちらのアプリもプライバシーに注意が必要が必要な場合、後からキャプチャを開始した方のアプリが音声を受け取り、もう一方のアプリは無音になります。

通話 + 通常アプリ

AudioManager.getMode() から返される音声モードが MODE_IN_CALL または MODE_IN_COMMUNICATION の場合、通話がアクティブです。

Android での入力音声共有のルールは以下のとおりです:

構成の変更

複数のアプリが同時に音声をキャプチャしている場合、そのうちの 1 つまたは 2 つだけが「アクティブ」(音声を受け取る)になり、その他はミュート(無音を受け取る)になります。アクティブなアプリが変更になった場合、音声フレームワークが音声パスを再構成するルールは以下のとおりです:

  • アクティブな各アプリの音声入力端末が変更になる可能性があります(たとえば、内蔵マイクから Bluetooth 接続のヘッドセットに変更)。
  • 優先度最高のアクティブ アプリに関連する事前処理が有効になります。他のすべての事前処理は無視されます。

アクティブなアプリより優先度の高いアプリがアクティブになるとそれまでアクティブだったアプリが無音になる可能性があるので、構成が変更になった時点で通知を受けるため、AudioRecord オブジェクトまたは MediaRecorder オブジェクトの AudioManager.AudioRecordingCallback を登録することができます。可能性のある変更には、次のものがあります:

  • キャプチャが無音になった場合、または無音から回復した場合
  • 端末が変更になった場合
  • 事前処理が変更になった場合
  • ストリームのプロパティが変更になった場合(サンプリング レート、チャンネルマスク、サンプル形式)

キャプチャ開始の前に AudioRecord.registerAudioRecordingCallback() を呼び出す必要があります。callback が実行されるのは、アプリが音声を受け取っていて変更が発生した場合のみです。

onRecordingConfigChanged() メソッドは、現在の音声キャプチャの状態を内容として含む AudioRecordingConfiguration を返します。変更について調べるには、以下のメソッドを使用します:

isClientSilenced()
クライアントに返される音声が、キャプチャポリシーのために現在無音になっている場合、true を返します。
getAudioDevice()
アクティブな音声端末を返します。
getEffects()
アクティブな事前処理効果を返します。クライアントが優先度最高のアクティブ アプリではない場合、アクティブな効果は、getClientEffects() から返されるものと同じでない場合があります。
getFormat()
ストリームのプロパティを返します。クライアントが受け取る実際の音声データでは、getClientFormat() により返される必要がある形式が常に考慮されることに注意してください。必要なリサンプリング、チャンネル、およびハードウェア インターフェースで使用されている形式からクライアント指定形式への形式変換は、フレームワークにより自動的に実行されます。
AudioRecord.getActiveRecordingConfiguration()
アクティブな録音構成を返します。

AudioManager.getActiveRecordingConfigurations() を呼び出すことにより、端末上でアクティブなすべての録音の一般的なビューが得られます。