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

文章目录

  • Audio Jack(耳机插孔)
      • AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
      • AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
    • 判断耳机输出
  • Headset(耳机)在位状态
    • isWiredHeadsetOn()
    • AudioManager.ACTION_HEADSET_PLUG
    • 获取状态

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>
        
        
    	
        <string name="default_audio_route_name" product="tablet">Tabletstring>
        
        <string name="default_audio_route_name" product="tv">TVstring>
        
        <string name="default_audio_route_name" product="default">Phonestring>
        
        <string name="default_audio_route_name_dock_speakers">Dock speakersstring>
        
        <string name="default_audio_route_name_hdmi">HDMIstring>
        
        <string name="default_audio_route_name_headphones">Headphonesstring>
        
        <string name="default_audio_route_name_usb">USBstring>
        
        
    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.
     * 

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 cannot receive this through components declared
     * in manifests, only by explicitly registering for it with
     * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
     * Context.registerReceiver()}.
     *
     * 

The intent will have the following extra values: *

    *
  • state - 0 for unplugged, 1 for plugged.
  • *
  • name - Headset type, human readable string
  • *
  • microphone - 1 if headset has a microphone, 0 otherwise
  • *
* */ @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的音频输出设备。大多数情况下是可以满足使用的。

你可能感兴趣的:(Android移动开发,android,audio)