Android音频输出设备判断 & Headset(耳机)在位状态查询

23 篇文章 0 订阅

Audio Jack(耳机插孔)

针对Audio Jack的设备物理组件的存在性判断,及耳机是否在位的判断。

官方文档中提及的词:

  • headset 指带有麦克风的头戴式耳机;
  • headphone 指头戴式耳机,无麦克风;
  • earphone 也指耳机,非头戴式耳机;
  • earpiece 指耳机,猜测比earhphone多个配置;

Android 6 (API 23)及以上设备测试:

  1. 判断是否支持音频输出。

    final PackageManager pm = context.getPackageManager();
    boolean hasAudioOutput = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
    
  2. 再判定输出设备类型。

    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扬声器。

  1. 判断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设备是否插入耳机孔。

  2. 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的音频输出设备。大多数情况下是可以满足使用的。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VoidHope

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值