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

本文基于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方式

 

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, constchar *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加入dapmwidgets中,方便dapm统一管理。这一步和InputEvent没有一定联系,可以不调用,主要是可以将耳机插座定义为widgets加入dapm进行省电管理。

 

 

 

void snd_soc_jack_report(structsnd_soc_jack *jack, intstatus, int mask)

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

 

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

 

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

 

 

 

         基于上面的函数,可以用以下做法来实现基于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机制比较简单,它基于switchdriver,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对应的switchdriver的名字。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,switchdriver通过更新state的值,从而通知Android上层耳机状态的改变。

 

 

 

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

 

        另外,在drivers/switch目录下也有同样的文件,不同之处是两者在Android下生成的结点的位置不同,我们也可以按照drivers/switch目录下的switchdriver来实现,不过需要更改WiredAccessoryManager.java文件中getDevPath和getSwitchStatePath下的路径。

 

 

 

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

 

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

 

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

 

             intswitch_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来通知userspace的核心函数了。

原文链接

http://blog.csdn.net/silvervi/article/details/23281087

你可能感兴趣的:(android系统开发,android应用开发)