Android 4.x耳机插拔检测实现方法

jopen 11年前

        本文基于Android 4.4撰写,另外也参看了一下4.2,机制相同,也许细节方面会有所不同,这里以4.4为主。

        Android耳机插拔可以有两个机制实现:

       1.      InputEvent

       2.      UEvent

        其中UEvent是Android系统默认的耳机插拔机制,所以我这里最终代码是基于UEvent实现的,对于InputEvent机制只是大概看了看,并没有具体实现,因此不能保证一定正确,寻求解决方法的同学可以直接移步只对UEvent方式的介绍。

 

1.   耳机检测的硬件原理

        首先我们看看耳机检测的原理。一般的耳机检测包含普通的耳机检测和带mic的耳机检测两种,这两种耳机统称为Headset,而对于不带mic的耳机,一般称之为Headphone。

        对于Headset装置的插入检测,一般通过Jack即耳机插座来完成,大致的原理是使用带检测机械结构的耳机插座,将检测脚连到可GPIO中断上,当耳 机插入时,耳机插头的金属会碰到检测脚,使得检测脚的电平产生变化,从而引起中断。这样就可以在中断处理函数中读取GPIO的的值,进一步判断出耳机是插 入还是拔出。

        而对于Headset是否带mic的检测,需要通过codec附加的micbias电流的功能,具体可以参考我的下一篇文章。

2.   两种方式的切换

        前面提到Android默认提供了两种解决方法,那么一定也提供了两种方式的切换,这个提供切换的设置名为config_useDevInputEventForAudioJack,对Android源代码进行全局搜索,可以看到它在frameworks/base/core/res/res/values/config.xml中,默认为false,即不使用InputEvent方式,另外在源码包的厂商相关的文件夹中也找到了相关的设置,如下:

/android/4.4/device/asus/flo/overlay/frameworks/base/core/res/res/values/config.xml

 <boolname="config_useDevInputEventForAudioJack">false</bool>

/android/4.4/device/samsung/manta/overlay/frameworks/base/core/res/res/values/config.xml

     <boolname="config_useDevInputEventForAudioJack">true</bool>

/android/4.4/device/asus/deb/overlay/frameworks/base/core/res/res/values/config.xml

     <boolname="config_useDevInputEventForAudioJack">false</bool>

/android/4.4/device/lge/hammerhead/overlay/frameworks/base/core/res/res/values/config.xml

     <boolname="config_useDevInputEventForAudioJack">true</bool>

/android/4.4/device/lge/mako/overlay/frameworks/base/core/res/res/values/config.xml

      <boolname="config_useDevInputEventForAudioJack">true</bool>

       可以看到有些厂商的确是使用了InputEvent的方式来进行耳机检测。具体对这个变量的修改是在device下还是frameworks下我想应该都可以,device下可能更好。

 

3.   InputEvent

1) Android上层的大概机制

        InputEvent部分的大概机制可以在网上搜索文章,具体流程我也不是特别清楚,这里大概说一下。

        InputEvent的处理主要在InputManagerService.java中。在InputManagerService构造函数中,通过如下函数,

        mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

        判断当前是否通过InputEvent实现耳机插拔检测。

        当Android得到InputEvent后,会调用InputManagerService.java中notifySwitch的函数,进而转至 WiredAccessoryManager.java文件中的notifyWiredAccessoryChanged函数,之后的流程就和 UEvent相同了,在后续会讲到。

 

2) Kernel层的机制

        Kernel层对耳机插拔InputEvent处理主要是通过input_report_key/input_report_switch来实现,而在实 际使用中,ASOC已经为我们封装好了相应Jack接口函数,只要符合规范就可以拿来使用。下面列出几个常用的接口函数。

 

  • int snd_soc_jack_new(structsnd_soc_codec *codec, const char *id, int type, struct snd_soc_jack *jack)

        生成一个新的jack对象,定义其被检测的类型,即可能插入的设备类型。一般定义为SND_JACK_HEADSET,其余也可以根据接口支持种类添加SND_JACK_LINEOUT,SND_JACK_AVOUT等。

        这个函数中调用了snd_jack_new,而在snd_jack_new中可以看到调用 input_allocate_device()分配了input device,就可以在后续产生input event了。

 

  • int snd_soc_jack_add_pins(structsnd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins)

         将之前定义好的pins加入dapm widgets中,方便dapm统一管理。这一步和InputEvent没有一定联系,可以不调用,主要是可以将耳机插座定义为widgets加入dapm进行省电管理。

 

  • void snd_soc_jack_report(structsnd_soc_jack *jack, int status, int mask)

         汇报jack插拔状态,主要完成以下两个工作:

         a) 根据插入拔出状态更新前面通过snd_soc_jack_add_pins加入的dapm pin的状态,对其进行上电下电管理。

‚        b) 调用snd_jack_report,在其中通过input_report_key/input_report_switch来向上层汇报input event。

 

         基于上面的函数,可以用以下做法来实现基于InputEvent机制的耳机插拔检测:

         a)  snd_soc_jack_new 创建jack对象

         b)  snd_soc_jack_add_pins将其加入到dapm wigets中

         c)  通过request irq申请耳机插拔中断,在中断处理函数中通过检测线高低电平判断耳机是插入还是拔出,通过读取codec寄存器来判断是headset还是headphone

         d)  根据判断结果调用snd_soc_jack_report发送InputEvent

 

       此外,ASOC还提供了一个封装好的函数来实现上述c)和d)步骤的功能:

  • int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, struct snd_soc_jack_gpio *gpios)

       该函数通过标准GPIO驱动申请GPIO及GPIO对应中断,并提供了统一的中断处理函数来汇报事件。此函数只适用于耳机中断接至GPIO且GPIO驱动为Linux标准驱动的情况下,并且不支持mic检测,因此不建议使用。

 

4.   UEvent

        UEvent机制比较简单,它基于switch driver,switch driver会在Android建立耳机插拔的目录/sys/devices/virtual/switch/h2w,在此目录下有个设备结点名为 state,driver通过更新state的值,从而通知Android上层耳机状态的改变。   

 

1) Android上层机制

       针对UEvent机制,Android上层在WiredAccessoryManager.java中实现。

       在这个文件中,从UEventObserver中继承了类WiredAccessoryObserver,在makeObservedUEventList中将要观察的事件加入到UEvent系统中:

       if(!mUseDevInputEventForAudioJack) {

            uei = new UEventInfo(NAME_H2W,BIT_HEADSET, BIT_HEADSET_NO_MIC);

                ……

                ……

        }

       可以看到,只有当不使用InputEvent时才添加UEvent事件,NAME_H2W就是headphone对应的switch driver的名字。BIT_HEADSET和BIT_HEADSET_NO_MIC是state结点的两个值,分别表示有mic和无mic的耳机。

       当UEvent事件到来时,类WiredAccessoryObserver中重载的onUEvent函数会被回调,从而调用 updateStateLocked(devPath,name, state) ,其中state的值就是通过/sys/devices/virtual/switch/h2w/state结点来获得。

       最后,程序会进入setDeviceStateLocked函数中处理,在setDeviceStateLocked中根据 state的值设置device,然后调用mAudioManager.setWiredDeviceConnectionState,最后进入 AudioPolicyManagerBase::setDeviceConnectionState。

      

2) Kernel层的机制

       前面说过,基于UEvent的耳机检测机制需要实现一只switchdriver,它会建立一个用于耳机插拔检测的目录/sys /devices/virtual/switch/h2w,在此目录下有个设备结点名为state,switch driver通过更新state的值,从而通知Android上层耳机状态的改变。

 

       switch driver的目录在Linux kernel的drivers/staging/android/switch目录下,可以从目录名称中看到这只driver是为了Android专门产 生的。在switch目录下有两个已有文件,switch_class.c是switch driver的内部实现,它提供了switch driver所需的一些API;switch_gpio.c是一个例子,它实现了一个基于GPIO中断的switch driver。

       另外,在drivers/switch目录下也有同样的文件,不同之处是两者在Android下生成的结点的位置不同,如果要按照 drivers/switch目录下的switch driver来实现,需要更改WiredAccessoryManager.java文件。

 

       下面讲讲如何添加switch driver。添加switch driver很简单,可以仿照switch_gpio.c,大致步骤如下:

        a) 在drivers/staging/android/switch目录下新建一个platform driver,其中包含一个全局变量struct switch_dev sdev,即要注册的switch device。

        b) 在platformdriver的probe函数中调用switch_dev_register将前面的sdev注册到系统中。

             int switch_dev_register(struct switch_dev *sdev)

        c) 申请用于耳机检测的中断处理函数。对于耳机插拔来说,由于用户的插拔快慢等可能产生多次中断,所以一般是在中断处理函数中实现一个延时工作队列,即INIT_DELAYED_WORK,在队列的回调函数中来进行实际判断。

        d) 当中断发生后,通过switch_set_state设置state节点的值,这个值要和WiredAccessoryManager.java文件中定义的一致,可参看BIT_HEADSET和BIT_HEADSET_NO_MIC的定义。目前是0表示无耳机插入,1表示带Mic的耳机,2表示不带Mic的耳机。

              void switch_set_state(struct switch_dev *sdev, int state)

            我们进一步看看switch_set_state这个函数,在这个函数中调用了kobject_uevent_env/kobject_uevent,这两个函数就是kernel通过uevent来通知user space的核心函数了。