Android 4.x耳機插拔檢測實現方法

jopen 10年前發布 | 25K 次閱讀 Android Android開發 移動開發

        本文基于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的核心函數了。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!