##はじめに
オーディオ絡みのアプリを作っていると、音を鳴らす前にイヤホンが接続されているかチェックしたり、録音する前にマイクの対応サンプルレートを調べたりしたいことがあります。
特にイヤホンの確認は、有線タイプ以外にもBluetoothだったりUSBだったり様々な機種がある為、昔のように"ACTION_HEADSET_PLUG"のブロードキャストだけで調べるというわけにもいきません。
そこでAPI level 23 以上で使える**AudioManeger.getDevice()**を試してみたところ、欲しい情報が取得できたのでまとめておきます。
##サンプル
例えばこのような感じに取得できます。ただし、取得できない情報があったり、任意のフォーマットに対応している場合は空の配列が返ってきたりします。
環境は Google Nexus6 (Android7.1.1) で、有線イヤホンとELECOMのBluetoothレシーバ"LBT-AVWAR500"を接続してテストしています。
##ソース
AudioManeger.getDevice() は AudioDeviceInfo オブジェクトを返してくるので、AudioDeviceInfo のもつ各種メソッドで中身を調べ、タイプなどintで返してくるものについてはさらにAudioDeviceInfoやAudioFormatに規定された名前に変換する関数を用意して読めるようにしています。
public class MainActivity extends AppCompatActivity {
private AudioManager myAudioManager;
private TextView myTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
myTextView = (TextView)findViewById(R.id.textView0);
}
@Override
protected void onResume() {
super.onResume();
String temp = myShowAudioDeviceInfo();
myTextView.setText(temp);
}
private String myShowAudioDeviceInfo() {
AudioDeviceInfo[] audioDevices = myAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
StringBuffer myDeviceInfo = new StringBuffer("搭載デバイス一覧");
for (AudioDeviceInfo audio_device : audioDevices) {
myDeviceInfo.append("\n------------------------------------------------------");
myDeviceInfo.append("\n getChannelCounts () -> ");
myDeviceInfo.append(Arrays.toString(audio_device.getChannelCounts()));
myDeviceInfo.append("\n getChannelIndexMasks () -> ");
myDeviceInfo.append(Arrays.toString(audio_device.getChannelIndexMasks()));
myDeviceInfo.append("\n getChannelMasks () -> ");
for(int temp : audio_device.getChannelMasks()){
myDeviceInfo.append("\n " + temp +" : ");
myDeviceInfo.append(myChannelInfoToReadableString(temp));
}
myDeviceInfo.append("\n getEncodings () -> ");
for(int temp : audio_device.getEncodings()){
myDeviceInfo.append("\n " + temp +" : ");
myDeviceInfo.append(myEncodingInfoToReadableString(temp));
}
myDeviceInfo.append("\n getId () -> ");
myDeviceInfo.append(audio_device.getId());
myDeviceInfo.append("\n getProductName () -> ");
myDeviceInfo.append(audio_device.getProductName());
myDeviceInfo.append("\n getSampleRates () -> ");
myDeviceInfo.append(Arrays.toString(audio_device.getSampleRates()));
myDeviceInfo.append("\n getType () -> ");
int temp = audio_device.getType();
myDeviceInfo.append("\n " + temp +" : ");
myDeviceInfo.append(myAudioTypeToReadableString(temp));
myDeviceInfo.append("\n isSink () -> ");
myDeviceInfo.append(audio_device.isSink());
myDeviceInfo.append("\n isSource () -> ");
myDeviceInfo.append(audio_device.isSource());
}
return myDeviceInfo.toString();
}
private String myChannelInfoToReadableString(int format){
//https://developer.android.com/reference/android/media/AudioFormat.html
switch (format){
case 0:
return "CHANNEL_INVALID";
case 1:
return "CHANNEL_IN_DEFAULT / CHANNEL_OUT_DEFAULT";
//~省略~
case 12:
return"CHANNEL_IN_STEREO / CHANNEL_OUT_STEREO";
//~省略~
case 16:
return "CHANNEL_IN_FRONT / CHANNEL_IN_MONO/CHANNEL_OUT_FRONT_CENTER";
//~省略~
default:
return "規定なし(" + format +")";
}
}
private String myEncodingInfoToReadableString(int format){
//https://developer.android.com/reference/android/media/AudioFormat.html
switch (format){
case 0:
return "ENCODING_INVALID";
case 1:
return "ENCODING_DEFAULT";
case 2:
return "ENCODING_PCM_16BIT";
//~省略~
default:
return "規定なし(" + format +")";
}
}
private String myAudioTypeToReadableString(int type){
//https://developer.android.com/reference/android/media/AudioDeviceInfo.html
switch(type){
case 1:
return "TYPE_BUILTIN_EARPIECE";
case 2:
return "TYPE_BUILTIN_SPEAKER";
case 3:
return "TYPE_WIRED_HEADSET";
case 4:
return "TYPE_WIRED_HEADPHONES";
case 5:
return "TYPE_LINE_ANALOG";
case 6:
return "TYPE_LINE_DIGITAL";
case 7:
return "TYPE_BLUETOOTH_SCO";
case 8:
return "TYPE_BLUETOOTH_A2DP";
//~省略~
case 15:
return "TYPE_BUILTIN_MIC";
case 16:
return "TYPE_FM_TUNER";
case 17:
return "TYPE_TV_TUNER";
case 18:
return "TYPE_TELEPHONY";
//~省略~
default: //0
return "TYPE_UNKNOWN";
}
}
}
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
/>
</LinearLayout>
</ScrollView>
##各メソッドについて
###AudioManager.getDevices (int flag)
スマホに接続/内臓されているオーディオデバイスのうち、フラグで指定したオーディオデバイスの一覧を、AudioDeviceInfoオブジェクトの配列で返してきます。
フラグは次の3種類が用意されています。
- 出力デバイス : GET_DEVICES_OUTPUTS
- 入力デバイス : GET_DEVICES_INPUTS
- 全デバイス : GET_DEVICES_ALL
###AudioDeviceInfo.getChannelCounts ()
- 情報:オーディオデバイスで構成可能なチャンネル数
- 型:intの配列(空は任意の意味)
- 解釈例:{1,2} = モノラルまたはステレオで使用可能
なお公式ドキュメントには
Note: an empty array indicates that the device supports arbitrary channel counts.
Google翻訳>空の配列は、デバイスが任意のチャネル数をサポートしていることを示します
とありますが、例えば内蔵スピーカーへ5.1chサラウンド信号を送ったらどうなるかはわかりません。ただ取得できていないだけの可能性も考えられますが、私の考えているアプリでは使わないため詳しく調査していません。
###AudioDeviceInfo.getChannelIndexMasks ()
- 情報:対応しているチャンネルインデックスマスク
- 型:int配列(空は任意の意味)
なおChannelIndexMasksについては、AudioFormatに書かれているのですが、
They allow the selection of a particular channel from the source or sink endpoint by number, i.e. the first channel, the second channel, and so forth.
Google翻訳>それらは、ソースまたはシンクのエンドポイントからの特定のチャネル、すなわち第1のチャネル、第2のチャネルなどの番号による選択を可能にする。
なるほどわからないので飛ばします(すみません。)
###AudioDeviceInfo.getChannelMasks ()
- 情報:オーディオデバイスが対応しているチャンネルマスク(スピーカー配置)
- 型:intの配列(空は任意の意味)
- 解釈例:6396 = CHANNEL_OUT_7POINT1_SURROUND(7.1chサラウンド出力)
戻り値の意味はAudioFormatに定められています
###AudioDeviceInfo.getEncodings ()
- 情報:オーディオデバイスが対応しているエンコーディング形式
- 型:intの配列(空は任意の意味)
- 解釈例:2 = ENCODING_PCM_16BIT(16bitのPCM)
戻り値の意味はAudioFormatに定められています
###AudioDeviceInfo.getId ()
- 情報:オーディオデバイスのOS上での管理id
- 型:int
※Bluetoothや有線ヘッドホンは、一度外して再接続するとidが変わったため、このメソッドで得られるidを記憶しておいてヘッドホンを判定するといった使い方はできないと思われます。
###AudioDeviceInfo.getProductName ()
- 情報:オーディオデバイスの名前
- 型:CharSequence
内蔵機器は機種の名前が出ただけで、オーディオチップの名前などは取得できませんでした。
Bluetooth機器は、製品名が表示されました。Bluetoothの設定画面で付けてある任意の名前は取得できない点に注意が必要です。
サンプルで示したレシーバーにはOSの設定で「Bluetoothオーディオ」という名前を付けていたのですが、残念ながら名前は取得することができませんでした。例えば家に同じBluetoothスピーカーが複数ある方には、使い勝手が悪いと思われます。
###AudioDeviceInfo.getSampleRates ()
- 情報:オーディオデバイスが対応しているサンプリングレート
- 型:int配列(空は任意の意味)
録音する前にマイクのサンプリングレートを調べるのに役立ちそうです。こちらも空の配列は任意を意味します。
###getType ()
- 情報:オーディオデバイスの種類
- 型:int
- 解釈例:2 = TYPE_BUILTIN_SPEAKER(内蔵スピーカー)
戻り値の意味はAudioDeviceInfoに定められています
例えば動画アプリでうっかり電車の中で内蔵スピーカを鳴らさないようにしたい場合、動画再生の処理前にこのメソッドで接続デバイスをチェックし、有線イヤホンやBluetoothデバイスが含まれているか確認すればよさそうです。
また、サンプルではイヤピースと受話器が分かれて出てきましたが、実機にはスピーカーは耳側・口側それぞれ1つずつしか搭載されれていないため、ハードウエア的にはどちらも耳側に付いているスピーカを示していると考えられます。
###AudioDeviceInfo.isSink () / AudioDeviceInfo.isSource ()
- 情報:再生デバイス/録音デバイスであるか
- 型:Boolean
メソッド | 再生デバイス | 録音デバイス |
---|---|---|
isSink () | True | False |
isSource () | False | True |
なお入力デバイスまたは出力デバイスのどちらかのみを調べたい場合、AudioManager.getDevices (int flag)の段階でフラグを設定しておけば、一覧情報取得後にこの関数で評価するという二度手間をせずに済み便利です。
##使用したAPIの公式ドキュメント
AudioManager | Android Developers