【问题现象】

根据测试组同事反馈:在我们的设备上使用 JBL 品牌某款带有 3 个按键的有线耳机时,按下“音量+”键时设备会减小音量而不是增加音量,按下“音量-”键时设备无响应;在设备上使用 Samsung 品牌某款带有 3 个按键的有线耳机时,按下“音量-”键时设备会启动语音助手而不是减小音量。但按下 2 款耳机的中间键(播放/暂停键)均可以得到正确的响应。

【分析问题】

首先查看这 2 款耳机的音频接口,均为 3.5mm 耳机插头,其中三星耳机是白色环的 CTIA 标准插头,JBL耳机是黑色环的 OMTP 标准插头。这 2 种标准插头的区别在于第 3、4个金属环的接法是相反的。CTIA标准的第 3 个金属环为 GND,第 4 个金属环为 MIC;OMTP标准的第 3 个金属环则为 MIC,而第 4 个金属环为 GND。更具体的信息可以参考我之前写的《3.5mm 音频接口类型说明》这篇文章。

然后 adb 登录到设备上,使用 getevent 命令查看耳机按键按下时,设备的实时上报键值和响应。类似下图中这样,参数 /dev/input/event9 :

经过实时查看发现:JBL耳机的“音量+”键按下时,实际上报的键值为 KEY_VOLUMEDOWN,所以设备上相应地做出了减小音量的响应,而“音量-”键按下时,没有键值被上报。这很异常。三星耳机的“音量-”键按下时,实际上报的键值为 KEY_VOICECOMMAND,所以设备上相应地启动了语音助手程序。至此可以看到,不是系统上层做出了错误的响应,而是系统底层上报按键事件时就发生了错误,所以应该在底层驱动来解决这个问题。

可是为什么按下 JBL 耳机的“音量-”时没有键值被上报呢?让我们从按键事件的本质来分析:按键本身是个硬件,当它被按下时实际上是接通了耳机检测芯片到地线(GND)的一条通路。这条通路可以是直接连接到 GND,也可以是通过电阻连接到 GND。建立这样的连接通路会在耳机检测芯片上造成电平变化(上升沿或下降沿),而这实际上就是一个中断信号。耳机检测芯片接收到这个中断后,会对连接到按键的芯片引脚上的电平进行 ADC 采样,再将得到的数值与芯片内存储的各按键值(KEY_VOLUMEUP、KEY_VOLUMEDOWN、KEY_MEDIA、KEY_VOICECOMMAND)进行对比,如果有符合的键值,则向 SoC 芯片发送一个中断,同时发送该键值,如果没有找到匹配的键值,自然也就不会上报了。

我们可以通过 /proc/interrupts 节点来查看 SoC 芯片是否收到了中断。比如可以像下方这样查看 ts3a227e 发送的中断数,如果按下“音量-”按键后中断计数没有增加,那么就说明耳机检测芯片确实没有向 SoC 芯片发出中断:

耳机按键分压电路模型类似于下图:

之所以 2 款耳机的中间键被按下时都可以得到正确的响应,是因为大部分耳机的中间键都是直接接地的,按键被按下时对应的电平都是 0V。具体电路类似下图:

综上所述,各键值与各电压值是对应的,这个问题的本质实际上是底层驱动检测到了错误的电压继而上报了错误的键值或没有上报键值。我们无法改变耳机本身各按键所对应的电阻,但可以通过修改芯片的寄存器值来改变按键分压电路上总的偏置电压(也就是上图的MICBIAS),从而改变每个按键上的电压值。

【解决问题】

在我参与开发的设备上,耳机按键检测使用的是 ts3a227e 这款芯片,偏置电压则由 Codec 芯片 RT5677 提供。所以要修改耳机按键分压电路的偏置电压需要修改 RT5677 芯片的寄存器,要修改电平与键值的对应关系需要修改 ts3a227e 芯片的寄存器。查阅 2 款芯片的 datasheet,对电压配置作出如下修改:

diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c

index 5668ac0..206b356 100644

--- a/sound/soc/codecs/rt5677.c

+++ b/sound/soc/codecs/rt5677.c

@@ -99,7 +99,7 @@ static struct rt5677_init_reg init_list[] = {

{RT5677_PRIV_INDEX, 0x0014},

{RT5677_PRIV_DATA, 0x018a},

{RT5677_IN1, 0x00c0},

-{RT5677_MICBIAS, 0x4000},

+{RT5677_MICBIAS, 0x0000}, //0x4000

{RT5677_STO1_ADC_MIXER, 0x5480},

//{RT5677_STO2_ADC_MIXER, 0x9440},

{RT5677_DMIC_CTRL1, 0x2505},

diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c

index e4f30f9..796b686 100644

--- a/sound/soc/codecs/ts3a227e.c

+++ b/sound/soc/codecs/ts3a227e.c

@@ -169,7 +169,7 @@ static const struct reg_default ts3a227e_reg_defaults[] = {

{ TS3A227E_REG_INTERRUPT_DISABLE, 0x08 },

{ TS3A227E_REG_SETTING_1, 0x27 },

{ TS3A227E_REG_SETTING_2, 0x00 },

-{ TS3A227E_REG_SETTING_3, 0x3f },

+{ TS3A227E_REG_SETTING_3, 0x27 }, // 0x3f

{ TS3A227E_REG_SWITCH_CONTROL_1, 0x00 },

{ TS3A227E_REG_SWITCH_CONTROL_2, 0x00 },

{ TS3A227E_REG_SWITCH_STATUS_1, 0x0c },

按照上方所示修改好电压后,三星耳机的按键就可以正常使用了,但 JBL 耳机的“音量-”按键依然会被识别为语音助手,在尝试了其它可配置的电压组合后依然如此。既然无法通过修改电压来完全解决问题,那么我们可以从软件上修改键值-上报事件映射的逻辑,使系统底层上报 KEY_VOICECOMMAND 键值的时候,系统上层依然可以作为 KEY_VOLUMEDOWN 来处理。为了看懂我接下来要对驱动代码进行的改动,这里先看一下驱动代码里对耳机按键状态数组的定义:

struct ts3a227e_keystatus {

int key_num;

volatile int key_value;

};

struct ts3a227e_keystatus key_status_map[] = {

{KEY_MEDIA, KEY_IS_UP},

{KEY_VOLUMEUP, KEY_IS_UP},

{KEY_VOICECOMMAND, KEY_IS_UP},

{KEY_VOLUMEDOWN, KEY_IS_UP},

};

为了重新映射键值消息与上报事件的关系,我对耳机检测芯片 ts3a227e 的驱动代码作如下具体改动:

diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c

index e4f30f9..796b686 100644

--- a/sound/soc/codecs/ts3a227e.c

+++ b/sound/soc/codecs/ts3a227e.c

@@ -286,6 +286,25 @@ static void long_press_func(struct work_struct *work)

/*volume up&down long press */ // 添加 KEY_MEDIA 的长按功能代码,仅在这种情况下上报 KEY_VOICECOMMAND 事件以启动语音助手

for (i = 0; i < TS3A227E_NUM_KEYS; i++) {

+/* add key_media long press - 20170523 */

+if (key_status_map[i].key_num == KEY_MEDIA &&

+key_status_map[i].key_value == KEY_IS_DOWN) {

+key_status_map[i].key_value = KEY_IS_WAIT_UP;

+pr_err("%s, i:%x , key_voicecommand down %d ,value:%d\n", __func__, i,

+KEY_VOICECOMMAND, key_status_map[i].key_value);

+input_report_key(ts3a227e->button_dev, KEY_VOICECOMMAND, 1);

+input_sync(ts3a227e->button_dev);

+}

+/* add key_voicecommand long press - 20170523 */ // 添加 KEY_VOICECOMMAND 的长按功能代码,上报 KEY_VOLUMEDOWN 长按时的 KEY_NEXTSONG 事件

+if (key_status_map[i].key_num == KEY_VOICECOMMAND&&

+key_status_map[i + 1].key_value == KEY_IS_DOWN) {

+key_status_map[i + 1].key_value = KEY_IS_WAIT_UP;

+pr_err("%s, i:%x , key_voicecommand down %d ,value:%d\n", __func__, i,

+KEY_NEXTSONG, key_status_map[i + 1].key_value);

+input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 1);

+input_sync(ts3a227e->button_dev);

+}

+

if (key_status_map[i].key_num == KEY_VOLUMEUP &&

key_status_map[i].key_value == KEY_IS_DOWN) {

key_status_map[i].key_value = KEY_IS_WAIT_UP;

@@ -324,6 +343,7 @@ static void check_jack_status(struct ts3a227e *ts3a227e)

for (i = 0; i < TS3A227E_NUM_KEYS; i++) {

if (ts3a227e->kp_int_reg & PRESS_MASK(i)) {

pr_err("%s, kp %d down \n", __func__, i);

+/*

if (key_status_map[i].key_num != KEY_VOLUMEUP

&& key_status_map[i].key_num != KEY_VOLUMEDOWN)

input_report_key(ts3a227e->button_dev, ts3a227e_keycodes[i], 1);

@@ -331,29 +351,60 @@ static void check_jack_status(struct ts3a227e *ts3a227e)

key_status_map[i].key_value = KEY_IS_DOWN;

long_press_det = true;

}

+*/

+

+if (key_status_map[i].key_num != KEY_VOICECOMMAND) {

+key_status_map[i].key_value = KEY_IS_DOWN;

+} else {

+pr_err("%s, key_voicecommand %d is detected, remap to key %d.\n", __func__, key_status_map[i].key_num, key_status_map[i+1].key_num);

+key_status_map[i+1].key_value = KEY_IS_DOWN; // 在检测到 KEY_VOICECOMMAND 消息时,将其视为 KEY_VOLUMEDOWN 进行处理

+}

+long_press_det = true;

+pr_err("Qidi - keypress - key_status_map[%d].value = %d\n", i, key_status_map[i].key_value);

}

if (ts3a227e->kp_int_reg & RELEASE_MASK(i)) {

pr_err("%s, kp %d up\n", __func__, i);

-/* just for volume up & down long press */

-if (key_status_map[i].key_value == KEY_IS_DOWN) {

-pr_err("%s, kp %d short up \n", __func__, i);

-cancel_delayed_work_sync(&ts3a227e->long_press_work);

-input_report_key(ts3a227e->button_dev,

- ts3a227e_keycodes[i], 1);

+if (i != 2) { // 检测到非 KEY_VOICECOMMAND 消息时,按照正常流程进行处理

+/* just for volume up & down long press */

+if (key_status_map[i].key_value == KEY_IS_DOWN) {

+pr_err("%s, kp %d short up \n", __func__, i);

+cancel_delayed_work_sync(&ts3a227e->long_press_work);

+input_report_key(ts3a227e->button_dev,

+ ts3a227e_keycodes[i], 1);

+key_status_map[i].key_value = KEY_IS_UP;

+} else if (key_status_map[i].key_value == KEY_IS_WAIT_UP) {

+pr_err("%s, kp %d long up code:%d\n", __func__, i, key_status_map[i].key_num);

+if (key_status_map[i].key_num == KEY_VOLUMEUP)

+input_report_key(ts3a227e->button_dev, KEY_PREVIOUSSONG, 0);

+else if (key_status_map[i].key_num == KEY_VOLUMEDOWN)

+input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0);

+else if (key_status_map[i].key_num == KEY_MEDIA)

+input_report_key(ts3a227e->button_dev, KEY_VOICECOMMAND, 0);

+

key_status_map[i].key_value = KEY_IS_UP;

-} else if (key_status_map[i].key_value == KEY_IS_WAIT_UP) {

-pr_err("%s, kp %d long up code:%d\n", __func__, i, key_status_map[i].key_num);

-if (key_status_map[i].key_num == KEY_VOLUMEUP)

-input_report_key(ts3a227e->button_dev, KEY_PREVIOUSSONG, 0);

-else if (key_status_map[i].key_num == KEY_VOLUMEDOWN)

-input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0);

-

-key_status_map[i].key_value = KEY_IS_UP;

+}

+input_report_key(ts3a227e->button_dev,

+ ts3a227e_keycodes[i], 0);

+}else { // 在检测到 KEY_VOICECOMMAND 消息时,将其视为 KEY_VOLUMEDOWN 进行处理

+/* just for key_voicecommand long press */

+if (key_status_map[i + 1].key_value == KEY_IS_DOWN) {

+pr_err("%s, kp %d short up \n", __func__, i + 1);

+cancel_delayed_work_sync(&ts3a227e->long_press_work);

+input_report_key(ts3a227e->button_dev,

+ ts3a227e_keycodes[i + 1], 1);

+key_status_map[i + 1].key_value = KEY_IS_UP;

+} else if (key_status_map[i + 1].key_value == KEY_IS_WAIT_UP) {

+pr_err("%s, kp %d long up code:%d\n", __func__, i + 1, key_status_map[i + 1].key_num);

+if (key_status_map[i + 1].key_num == KEY_VOLUMEDOWN)

+input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0);

+

+key_status_map[i + 1].key_value = KEY_IS_UP;

+}

+input_report_key(ts3a227e->button_dev,

+ ts3a227e_keycodes[i + 1], 0);

}

-input_report_key(ts3a227e->button_dev,

- ts3a227e_keycodes[i], 0);

}

}

按照上文所述进行修改后,重新编译代码并烧写设备进行测试,发现耳机按键功能已经可以正常工作了。

其实这就是纯底层驱动的修改,不仅可以解决 Android 系统上这样的问题,纯 Linux 系统下出现了这样的问题也可以如此类比修改。

android耳机上报流程,Android系统中耳机按键键值上报不正确 解决过程相关推荐

  1. linux中键盘按键键值修改

    几年以前淘的X40本本被老婆淘汰下来了,放着浪费装了个Archlinux又可以折腾下. 但这X40是日文键盘,多出了很多键也老是按错,用着很不爽!! 想着修改里面多出来的一些按键的值,但面临着两个问题 ...

  2. 在Android系统中添加组合键快捷启动功能

    启动系统特定功能的组合键的判断应该在系统分发按键消息前处理, 这样从系统运行 的角度来说成本最低. 添加组合键处理需要先搞清楚按键消息在framework中采集 和分发子系统的工作流程, 虽然有and ...

  3. Android系统中自定义按键的短按、双击、长按事件

    在项目中碰到这样的问题: 由于系统中的按键在底层做了重新定义或者新增了按键,此时需要在APP层对按键事件(keyevent)做分解处理,模拟Android系统做法,把keyevent分解成: 1.单击 ...

  4. android获取spinner的值_在Android的Spinner中实现键值对的正确方法是什么

    这是在Android中为Spinner实现键值对的正确方法吗? package com.mypackage import android.app.Activity; import android.os ...

  5. Android系统适配蓝牙遥控器键值Hi3798MV100

    最近有个项目机顶盒要适配蓝牙遥控器,我们原来的盒子是红外的遥控器. 从某宝买回来几款通用的遥控器,最简单的一款用cat /proc/bus/input/devices 命令查看name是BESCO K ...

  6. 计算机c盘属性不显示安全选项,win7系统中文件夹属性安全选项卡空白的解决方法...

    在win7系统中,有小伙伴在使用文件夹属性的时候出现了问题,我们在win7系统中有小伙伴发现自己的文件夹属性中的"安全"选项卡不见了,安全选项卡是我们在win7系统中可以用来修改文 ...

  7. 如何删除tmp计算机桌面,教你Win10系统中tmp文件删除不了应该如何解决?

    电脑现已成为我们工作.生活和娱乐必不可少的工具了,在使用电脑的过程中,可能会遇到Win10系统中tmp文件删除不了应该如何解决的问题,如果我们遇到了Win10系统中tmp文件删除不了应该如何解决的情况 ...

  8. win10计算机管理没有蓝牙,win10系统中缺少打开或关闭蓝牙选项的解决方法

    在win10系统中,自带有蓝牙功能,但是有时候在使用蓝牙的时候,发现设置应用程序或操作中心中缺少打开蓝牙的选项,遇到这样的问题该怎么办呢,本文就给大家讲解一下win10系统中缺少打开或关闭蓝牙选项的解 ...

  9. linux系统浏览器无声音,在Deepin 20系统中外接显示器切换后浏览器没有声音的解决经历...

    如果你在Deepin 20系统遇到浏览器没有声音的问题,请看以下解决经历,或许能给你提供帮助.可先参考在Deepin系统中没有声音的解决办法. 浏览器没有声音的解决历程 在家里笔记本有时候需要外接HD ...

最新文章

  1. docker网络配置方法总结
  2. Extended Twin Composite Number
  3. hash算法_到底什么是Hash?Hash算法的原理和实际应用讲解
  4. 一文理清Cookie、Session、Token
  5. java中po代码示例_java操作oracle常用的示例代码详解
  6. python字符串的切片方式是[n、m、不包括m_python字符串的操作(去掉空格strip(),切片,查找,连接join(),分割split(),转换首字母大写, 转换字母大小写...)...
  7. Windows下误删文件解决办法
  8. poj 1743 Musical Theme【后缀自动机】
  9. Android 下拉刷新组件SwipeToLoadLayout源码解析
  10. vue2.0 axios封装
  11. openSUSE 11.2 上试动Mono
  12. mysql中rownumber用法_MySQL中row_number的实现
  13. u-boot编译构成之 MLO(1)
  14. deepin安装tftp服务器_Win10频发蓝屏,深度Deepin系统,调试华为AC和AP
  15. HTTP协议及TCP分析
  16. 用户画像 | 标签数据存储之Elasticsearch真实应用
  17. Invalid param tag: Cannot load command parameter [robot_description] 出错解决
  18. 数据库的8种优化方式
  19. 基于改进FCOS的钢带表面缺陷检测算法
  20. 纵向抽奖滚动效果代码

热门文章

  1. rust和gta5哪个吃配置_盘点4款Steam“自由度”很高的游戏,GTA5众所周知,目前最热门...
  2. Python入门--下载、安装、使用
  3. Windows位图和调色板
  4. 【表情包分享】CSDN问答区专用图
  5. 俺把所有粉丝显示在地图上啦~【详细教程+完整源码】
  6. 剑指offer(一)
  7. 虚幻引擎遇上个三国战纪
  8. Python turtle库改变海龟速度的几种方法
  9. “THEWAVEVR”打造首个VR音乐狂欢与…
  10. Linux三大剑客小结