ACTION_AUDIO_BECOMING_NOISYについて調べた

# 概要

Androidで音楽プレイヤーを開発するに当たり、AudioManager. ACTION_AUDIO_BECOMING_NOISY イベントの挙動について調べました。

# ACTION_AUDIO_BECOMING_NOISYとは

[AudioManager  \|  Android Developers](https://developer.android.com/reference/android/media/AudioManager.html#ACTION_AUDIO_BECOMING_NOISY)

**ACTION_AUDIO_BECOMING_NOISY**とは、Androidデバイスでイヤホン等から音声が出力された状態から、デバイスのスピーカー出力に切り替わる直前に、ブロードキャストされてくるアクションです。アクションの名前は"android.media.AUDIO_BECOMING_NOISY"です。
これを使う事で、イヤホンが抜けた場合にスピーカーから音声を出力させないように再生を停止する、といった処理を行うことができます。
このアクションを受信する場合、次のようなコードになるかと思います。

```java
IntentFilter filter = new IntentFilter();
filter .addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
context.registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {
            // 再生停止処理
            ...
        }
    }
}, filter);
```

なお、AndroidManifest.xmlにintent-filterを定義しても、アプリが起動していない状態で受信することはできません。

# デバイス切断アクションとの違い

オーディオデバイスが切断された際、それぞれのデバイスに対応するアクションがこのイベントとは別に発生します。

- オーディオジャックからプラグが抜けた場合:  
  **AudioManager.ACTION_HEADSET_PLUG**  
  (Lollipopより前 は"Intent.ACTION_HEADSET_PLUG")  
  [= android.intent.action.HEADSET_PLUG]
- BluetoothのA2DPデバイスが切断された場合:  
  **BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED**  
  [= android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED]

発生する順番としては、ACTION_AUDIO_BECOMING_NOISY -> デバイス切断アクション となります。

一見するとデバイス切断アクションでACTION_AUDIO_BECOMING_NOISYと同様の処理を行っても良さそうに見えますが、デバイス切断アクションが発生した時点で、デバイスのスピーカーから音が出力された状態となってしまう問題があります。つまり、デバイス切断アクションで音を止めると一瞬だけ音が漏れてしまうため、実装としては不適切です。
（これについては、デバイスやOSに依存する可能性も考えられますが、手持ちの複数の端末で音が漏れることを確認しています。）

# 複数デバイス接続時の挙動

さらに、デバイス切断アクションとはもっと大きな違いがあります。それは、複数のデバイスが接続された場合の挙動です。
イヤホンとBluetoothが両方接続された場合、通常はBluetooth側から音が出力されます。この時、Bluetoothを切断すると、イヤホンからの出力に切り替わります。この時ブロードキャストされるのは、Bluetoothが切断されたアクションのみで、ACTION_AUDIO_BECOMING_NOISYは発生しません。逆にイヤホンを抜いた場合も同様です。両方のデバイスを切断して、スピーカーから出力されるタイミングで発生します。
あくまでも、最終的にスピーカーから音が発生する段階で発生するようです。

# 対応するデバイス

現状、Androidでは一般的に次のような接続デバイスから音を出力することができます。（探せばもっとあるかもしれません。）

- ステレオミニプラグ
- Bluetooth
- USB Audio (ステレオプラグアダプタ含む)
- HDMI (MHL等のUSB拡張アクセサリ含む)
- Miracast
- Chromecast

これらについて、ACTION_AUDIO_BECOMING_NOISYが発生するかどうかについて調べてみました。手持ちのデバイスで確認した範囲なので、歯抜け等あります。また、USBで直接接続できないものは、変換アダプタを噛ませています。

<table class="tg">
  <tr>
    <th class="tg-dvpl">デバイス</th>
    <th class="tg-c3ow">SHARP 702SH<br />(Android 8.0)</th>
    <th class="tg-c3ow">NVIDIA SHIELD<br />(Android 7.0)</th>
    <th class="tg-c3ow">SONY SO-01G<br />(Android 6.0)</th>
  </tr>
  <tr>
    <td class="tg-dvpl">イヤホン</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
  </tr>
  <tr>
    <td class="tg-dvpl">USB *1 *2</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
  </tr>
  <tr>
    <td class="tg-dvpl">Bluetooth</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○</td>
  </tr>
  <tr>
    <td class="tg-dvpl">HDMI</td>
    <td class="tg-c3ow">-</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">○ *3</td>
  </tr>
  <tr>
    <td class="tg-dvpl">Miracast *4</td>
    <td class="tg-c3ow">×</td>
    <td class="tg-c3ow">-</td>
    <td class="tg-c3ow">-</td>
  </tr>
  <tr>
    <td class="tg-dvpl">Chromecast</td>
    <td class="tg-c3ow">○</td>
    <td class="tg-c3ow">×</td>
    <td class="tg-c3ow">×</td>
  </tr>
</table>

 ○: ACTION_AUDIO_BECOMING_NOISYあり / ×: なし / -: 未確認
 *1: Apple USB-C - 3.5 mmヘッドフォンジャックアダプタ利用
 *2: Covia ZDC-205A-SG利用
 *3: ELECOM MPA-MHL005BK利用
 *4: Microsoft P3Q-00009

調べて見たところ、一部対応に違いがありました。これがAndroidのバージョンによるものなのか、デバイスの実装によるものなのかは分かりませんが、ChromecastやMiracastなどの画面接続デバイスはあまりACTION_AUDIO_BECOMING_NOISYの発生を期待しない方が良さそうです。

# まとめ

ACTION_AUDIO_BECOMING_NOISYを使った実装には、以下の点について気を付けた方が良さそうです。

- ACTION_AUDIO_BECOMING_NOISYは、イヤホンの音を外に漏らさないような場面で利用する。
- デバイス切断アクションは、スピーカー出力に切り替わってから発生するので音が漏れる。
- 複数デバイスが接続された状態では、最後のデバイスが切断されたタイミングで発生する。
- イヤホン、Bluetooth、USBオーディオ系デバイスは発生が期待できるが、画面出力系のデバイスは期待できない。ただ、HDMIは発生するし、Chromecastで発生する端末もある。