本文基于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的耳机检测机制需要实现一只switch driver,它会建立一个用于耳机插拔检测的目录/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文件中getDevPath和getSwitchStatePath下的路径。

下面讲讲如何添加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的核心函数了。

更多相关文章

  1. Kotlin基本熟悉
  2. Android(安卓)SurfaceFlinger 学习之路(二)----SurfaceFlinger概
  3. 内存分析与内存泄漏检测
  4. android 浏览器插件开发 - 插件库
  5. Android(安卓)SQLite总结(一)
  6. Android(安卓)recovery 原理分析
  7. [Android] Opengl ES 机型适配 bug 汇总
  8. Android(安卓)Jni调用so库,加载库失败分析
  9. android 限制adb的访问目录

随机推荐

  1. android 面试 android 知识点 提高篇
  2. 下面就介绍下Android(安卓)NDK的入门学习
  3. Android(安卓)利用h264流合成mp4
  4. Android(安卓)前置摄像头调试纪要
  5. 在基于Android以及Jetson TK平台上如何写
  6. Android(安卓)TV 悬浮球模拟物理按键
  7. 天天记录 - 双休安装双系统并搭建环境的
  8. 框架和流程——OkHttp 源码详解(一)
  9. Android(安卓)7.0正式版发布时间曝光!等到
  10. Android布局之线性布局