Android音频输出设备判断 & Headset(耳机)在位状态查询
文章目录Headset(耳机)在AudioJack(音频插孔)状态在Android平台版本迭代过程中,新API不断出现,旧API会被标记Deprecated。虽然deprecated的API依然可以使用,但在不断迭代过程中,其起到的作用慢慢不太符合对应的需求,或者其原有实现被分解更加详细地实现。这里我遇到的耳机的状态判断就是其中一种情况。## isWiredHeadsetOn() 在API 15(
文章目录
Audio Jack(耳机插孔)
针对Audio Jack的设备物理组件的存在性判断,及耳机是否在位的判断。
官方文档中提及的词:
- headset 指带有麦克风的头戴式耳机;
- headphone 指头戴式耳机,无麦克风;
- earphone 也指耳机,非头戴式耳机;
- earpiece 指耳机,猜测比earhphone多个配置;
Android 6 (API 23)及以上设备测试:
-
判断是否支持音频输出。
final PackageManager pm = context.getPackageManager(); boolean hasAudioOutput = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-
再判定输出设备类型。
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
测试设备可以获取到两个值:
- AudioDeviceInfo.TYPE_BUILTIN_EARPIECE(1)
- AudioDeviceInfo.TYPE_BUILTIN_SPEAKER(2)
两台设备,一台有耳机孔无USB口,另一台设备有USB插口但无耳机插孔。
即 耳机插入 符合 AudioDeviceInfo.TYPE_BUILTIN_EARPIECE 类型设备, 扬声器 符合 AudioDeviceInfo.TYPE_BUILTIN_SPEAKER 类型设备。
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
这个设备类型很容易理解,即内置扬声器,基本设备出厂时均会集成在设备内。
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
earpiece 指耳机类型,耳机孔设备属于此范围,而 USB类型的耳机 是否属于此范围,由于无设备测试,暂且无法明确。
但从程序运行角度看,无耳机插孔的设备,也同样返回了 TYPE_BUILTIN_EARPIECE 的输出类型。以此推测,USB类型耳机也属于这个范畴。
判断耳机输出
根据判断,需要检查当前音频输出是Audio Jack还是speaker扬声器。
-
判断headphone是否在位(插入耳机孔判断)
final Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); final boolean headsetExists = intent.getIntExtra("state", 0) == 1;
在匹配的Intent.ACTION_HEADSET_PLUG广播Intent中包含有参数state,表明headphone设备是否插入耳机孔。
-
MediaRouter 判断
final MediaRouter mr = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); final MediaRouter.RouteInfo ri = mr.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); final CharSequence routeName = ri.getName(); // Headphones
判断媒体路由,获取当前音频路由输出。getName() 方法返回用户可见的字符串方式,返回系统字符串资源的定义。
<resources> <!-- more string definitions --> <!-- Name of the default audio route for tablets when nothing is connected to a headphone or other wired audio output jack. [CHAR LIMIT=50] --> <string name="default_audio_route_name" product="tablet">Tablet</string> <!-- Name of the default audio route for tablets when nothing is connected to a headphone or other wired audio output jack. [CHAR LIMIT=50] --> <string name="default_audio_route_name" product="tv">TV</string> <!-- Name of the default audio route when nothing is connected to a headphone or other wired audio output jack. [CHAR LIMIT=50] --> <string name="default_audio_route_name" product="default">Phone</string> <!-- Name of the default audio route when an audio dock is connected. [CHAR LIMIT=50] --> <string name="default_audio_route_name_dock_speakers">Dock speakers</string> <!-- Name of the default audio route when HDMI is connected. [CHAR LIMIT=50] --> <string name="default_audio_route_name_hdmi">HDMI</string> <!-- Name of the default audio route when wired headphones are connected. [CHAR LIMIT=50] --> <string name="default_audio_route_name_headphones">Headphones</string> <!-- Name of the default audio route when USB is connected. [CHAR LIMIT=50] --> <string name="default_audio_route_name_usb">USB</string> <!-- more string definitions --> </resources>
若不插入耳机的情况,需要看OS是何种产品类型:
-
平板(tablet) => Tablet
-
手机(default) => Phone
-
TV(tv) => TV
其他的音频路由输出也有对应的系统值定义。
-
Headset(耳机)在位状态
在Android平台版本迭代过程中,新API不断出现,旧API会被标记Deprecated。虽然deprecated的API依然可以使用,但在不断迭代过程中,其起到的作用慢慢不太符合对应的需求,或者其原有实现被分解更加详细地实现。这里我遇到的耳机的状态判断就是其中一种情况。
isWiredHeadsetOn()
在API 15(含)后被标记为deprecated的isWiredHeadsetOn()这个方法,一般就被使用来判断是否有耳机设备连接到当前Android设备。
/**
* Checks whether a wired headset is connected or not.
* <p>This is not a valid indication that audio playback is
* actually over the wired headset as audio routing depends on other conditions.
*
* @return true if a wired headset is connected.
* false if otherwise
* @deprecated Use {@link AudioManager#getDevices(int)} instead to list available audio devices.
*/
public boolean isWiredHeadsetOn() {
if (AudioSystem.getDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET,"")
== AudioSystem.DEVICE_STATE_UNAVAILABLE &&
AudioSystem.getDeviceConnectionState(DEVICE_OUT_WIRED_HEADPHONE,"")
== AudioSystem.DEVICE_STATE_UNAVAILABLE &&
AudioSystem.getDeviceConnectionState(DEVICE_OUT_USB_HEADSET, "")
== AudioSystem.DEVICE_STATE_UNAVAILABLE) {
return false;
} else {
return true;
}
}
其源码实现中判断了WIRED_HEADSET,WIRED_HEADPHONE,USB_HEADSET三种有线设备的状态。一般情况下均可以使用此方法进行是否连接了外部的音频设备,因为Android设备外设接口类型可用作支持的音频的只有耳机孔及USB。但在一种场景下,此方法无法具体判断是何种外接设备在位——是耳机在耳机孔内亦或USB设备连接上。
AudioManager.ACTION_HEADSET_PLUG
此值定义是广播动作(action),用以接收有线的插拔状态广播。在文档说明中,此action起作用只能使用Context#registerReceiver(BroadcastReceiver, IntentFilter)方式,在manifest进行注册的方式将无法接收到此广播消息。
另外在Intent中包含有其他消息数据:
- state —— 0 表示耳机不在位,1表示在位;
- name —— 耳机类型;
- microphone —— 1 表示耳机有麦克风,0 表示没有;
这些值均比较容易理解,由于测试设备并非手机,并无phone模块,因此不知是否会影响到name,microphone的值。在我的测试场景中name值null,microphone也未准确获取到(由于OS是定制的,可能是下边修改的,如果知道确切原因的请在评论中告知我)。
获取状态
一般Broadcast的action动作接收均是通过定义BroadcastReceiver后接收ACTION_HEADSET_PLUG的广播,后在回调方法内进行相关处理。若仔细看Context#registerReceiver(BroadcastReceiver, IntentFilter)的签名,可以看到起返回是一个Intent对象。AudioManager.ACTION_HEADSET_PLUG广播的定义有对应的数据说明。
/**
* Broadcast Action: Wired Headset plugged in or unplugged.
*
* You <em>cannot</em> receive this through components declared
* in manifests, only by explicitly registering for it with
* {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
*
* <p>The intent will have the following extra values:
* <ul>
* <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
* <li><em>name</em> - Headset type, human readable string </li>
* <li><em>microphone</em> - 1 if headset has a microphone, 0 otherwise </li>
* </ul>
* </ul>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_HEADSET_PLUG =
"android.intent.action.HEADSET_PLUG";
可以看到源码文档注释中的intent中携带的数据key,及类型。因此可以使用如下的代码获取当前的audio的headset状态。
final Intent intent = context
.registerReceiver(null, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
boolean headsetExists = false;
try {
Objects.requireNonNull(intent, "headset device does not exist!");
headsetExists = intent.getIntExtra("state", 0) == 1;
final String audioType = intent.getStringExtra("name");
final boolean hasMicrophone = intent.getIntExtra("", 0) == 1;
Log.d(TAG, String.format("headset exists? %b, audio type: %s, microphone exists? %b",
headsetExists, audioType, hasMicrophone));
} catch (NullPointerException e) { // headset does not exist
Log.i(TAG, "headset unplugged.");
headsetExists = false;
}
在调用registerReceiver(BroadcastReceiver, IntentFilter)时第一个参数出入null,这样告诉Android不接收ACTION_HEADSET_PLUG的动作,但需要匹配动作的sticky Intent返回。这里有个requireNonNull()方法的调用对返回的Intent进行判null操作,原因在于首次系统启动,且headset不在位的情况下,获取到的intent为null。因此我们多了一种获取headset是否在位的方式,且可以不使用deprecated的API,看着代码可读性会好很多。
PS: 这里的headset不仅是耳机,还包含了支持USB的音频输出设备。大多数情况下是可以满足使用的。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)