学习笔记:音频之耳机
2017-09-30 15:13
405 查看
(本文仅用于本人学习记录,仅供参考)
一、耳机基本认识
引用http://www.cnblogs.com/Peter-Chen/p/3999212.html(稍作修改)
主要有两种耳机类型,从下图可以看到,左边的耳机接口结构有左声道、右声道、接地端和mic端,下面我们称该类型耳机为四环耳机;右边的耳机接口结构有左声道、右声道、接地端,不带mic接口,我们称之为三环耳机,区别就在于带不带麦克风。
与耳机对应,一般常见的耳机插口都是由5PIN or 6PIN组成,其中PIN脚分别作为HP_OUTL(左声道输出)、HP_OUTR(右声道输出)、HP_DET#(耳机检测)、GROUND(地) & MIC(麦克风)使用。
驱动中耳机检测的一般流程:
HP_DET#信号由High->Low,触发IRQ到SOC,进入中断处理函数(即耳机类型检测);先是去检测耳机的类型,确定耳机是三环还是四环耳机,将耳机状态上报给系统后,再去检测按键状态,当检测到按键状态有变化,通过输入子系统上报事件,然后进行相对应的按键功能处理。
虽然耳机插口仅由5~6PIN组成,但不同的厂商会将其设计成各种各样。耳机接口的不同构造会对应着不同现象。以下是在网上搜集的一些接口类型,仅供参考。
类型一:
该耳机插口构造是HP_DET#和HPOUTL处于最前端,并且在同一位置,这样的设计是只有当耳机全部插入的时候,才能触发HP_DET#信号。由耳机检测的流程可知,该构造可以很精确的确定耳机是三环还是四环耳机。并且不管快拔 or 慢拔耳机,都能第一时间触发HP_DET#信号。
类型二:
这样的构造可能会出现当4环耳机刚拔出1环左右长度时,在这种情况下,6个PIN的状态可能如下:HP_OUTL处于float状态;HP_DET# & HS_OUTR处于第一环;AGND处于第二环;MIC2P_HP处于第三环,恰好是耳机的“-G-”,使得MIC2P_HP引脚的电压值为0.0V。由于HP_DET#依旧处于Pull Low状态,这样系统认为4环耳机仍然插入,而此时MIC2P_HP引脚因接地,导致其引脚电压值一直为0.0V,这与插入4环耳机,长按Butten的效果是一样的。
类型三:
4000
类型三耳机插口构造是将HP_DET# & AGND引脚接到同一位置,这样当耳机插入2节左右长度时,系统就可以识别到有耳机插入。使用Type3 Jack除了会出现Type2 Jack问题外,还会出现当慢慢斜着插入3环耳机时,系统会认为是4环耳机直接上报给上层。这个问题可以通过机构的设计解决“斜插”的问题,从而消除3环误认为4环的现象。
类型四:
该构造与类型一的构造和效果差不多。
二、主要代码函数分析
1、cod3034x芯片的probe回调函数,进行芯片的初始化,用工作队列创建耳机类型和按键的状态检测函数。以下贴出的是部分代码
2、cod3034x_jack_det_work函数用于检测插入耳机的类型,确定插入的是三段耳机还是四段耳机,三段与四段的区别在于耳机是否带麦克风。
3、cod3034x的按键检测函数
4、cod3034x_adc_get_value用于读取ADC接口的数据
5、cod3034x_i2c_parse_dt该函数用于从dts获取codec的各种属性值
上述就是三星平台音频codec模块的主要代码分析。
三、总结:
1、拿到BUG,首先进行问题的分析,从测试提供的资料中获取与BUG有关的所有信息,与测试人员进行沟通,确定测试流程和测试环境,避免自测产生不必要的麻烦。
2、进行问题解析,对问题进行定位,列出可能产生此现象的问题点,先排除自身模块的问题再去考虑其他模块的问题。环环相扣,层层排除。
3、保持良好的心态,认真解BUG。
一、耳机基本认识
引用http://www.cnblogs.com/Peter-Chen/p/3999212.html(稍作修改)
主要有两种耳机类型,从下图可以看到,左边的耳机接口结构有左声道、右声道、接地端和mic端,下面我们称该类型耳机为四环耳机;右边的耳机接口结构有左声道、右声道、接地端,不带mic接口,我们称之为三环耳机,区别就在于带不带麦克风。
与耳机对应,一般常见的耳机插口都是由5PIN or 6PIN组成,其中PIN脚分别作为HP_OUTL(左声道输出)、HP_OUTR(右声道输出)、HP_DET#(耳机检测)、GROUND(地) & MIC(麦克风)使用。
驱动中耳机检测的一般流程:
HP_DET#信号由High->Low,触发IRQ到SOC,进入中断处理函数(即耳机类型检测);先是去检测耳机的类型,确定耳机是三环还是四环耳机,将耳机状态上报给系统后,再去检测按键状态,当检测到按键状态有变化,通过输入子系统上报事件,然后进行相对应的按键功能处理。
虽然耳机插口仅由5~6PIN组成,但不同的厂商会将其设计成各种各样。耳机接口的不同构造会对应着不同现象。以下是在网上搜集的一些接口类型,仅供参考。
类型一:
该耳机插口构造是HP_DET#和HPOUTL处于最前端,并且在同一位置,这样的设计是只有当耳机全部插入的时候,才能触发HP_DET#信号。由耳机检测的流程可知,该构造可以很精确的确定耳机是三环还是四环耳机。并且不管快拔 or 慢拔耳机,都能第一时间触发HP_DET#信号。
类型二:
这样的构造可能会出现当4环耳机刚拔出1环左右长度时,在这种情况下,6个PIN的状态可能如下:HP_OUTL处于float状态;HP_DET# & HS_OUTR处于第一环;AGND处于第二环;MIC2P_HP处于第三环,恰好是耳机的“-G-”,使得MIC2P_HP引脚的电压值为0.0V。由于HP_DET#依旧处于Pull Low状态,这样系统认为4环耳机仍然插入,而此时MIC2P_HP引脚因接地,导致其引脚电压值一直为0.0V,这与插入4环耳机,长按Butten的效果是一样的。
类型三:
4000
类型三耳机插口构造是将HP_DET# & AGND引脚接到同一位置,这样当耳机插入2节左右长度时,系统就可以识别到有耳机插入。使用Type3 Jack除了会出现Type2 Jack问题外,还会出现当慢慢斜着插入3环耳机时,系统会认为是4环耳机直接上报给上层。这个问题可以通过机构的设计解决“斜插”的问题,从而消除3环误认为4环的现象。
类型四:
该构造与类型一的构造和效果差不多。
二、主要代码函数分析
1、cod3034x芯片的probe回调函数,进行芯片的初始化,用工作队列创建耳机类型和按键的状态检测函数。以下贴出的是部分代码
/*Codec芯片的probe函数*/ cod3034x_codec_probe(struct snd_soc_codec *codec) |-- snd_soc_codec_get_drvdata(codec); |-- devm_regulator_get(codec->dev, "vdd_ldo27"); |-- INIT_WORK(&cod3034x->buttons_work, cod3034x_buttons_work);//添加按键检测的工作队列 |-- cod3034x_buttons_work//耳机按键检测函数 |-- create_singlethread_workqueue("buttons_wq"); |-- INIT_WORK(&cod3034x->jack_det_work, cod3034x_jack_det_work);//添加耳机检测的工作队列 |-- cod3034x_jack_det_work//耳机检测函数 |-- create_singlethread_workqueue("jack_det_wq"); |-- cod3034x_adc_start(cod3034x); |-- cod3034x_i2c_parse_dt(cod3034x);//获取dts中的设备属性值 |-- cod3034x_register_notifier(&codec_notifier, cod3034x); |-- set_codec_notifier_flag(); |-- cod3034x_codec_initialize(codec); |-- cod3034x_jack_mic_register(codec); |-- cod3034x_enable(codec->dev);
2、cod3034x_jack_det_work函数用于检测插入耳机的类型,确定插入的是三段耳机还是四段耳机,三段与四段的区别在于耳机是否带麦克风。
static void cod3034x_jack_det_work(struct work_struct *work) { dev_dbg(cod3034x->dev, "%s(%d) jackdet: %d\n", __func__, __LINE__, jackdet->jack_det); mutex_lock(&cod3034x->jackdet_lock); if (jackdet->jack_det == true) { /* set delay for read correct adc value */ msleep(cod3034x->mic_det_delay); /* read adc for mic detect */ adc = cod3034x_adc_get_value(cod3034x);//获取耳机接口的adc值 dev_dbg(cod3034x->dev, "%s mic det adc %d, mic_det_delay: %d\n", __func__, adc, cod3034x->mic_det_delay); if (adc > cod3034x->mic_adc_range)//判断是否检测到了mic jackdet->mic_det = true; else jackdet->mic_det = false; dev_dbg(cod3034x->dev, "%s Mic det: %d\n", __func__, jackdet->mic_det); jackdet->adc_val = adc; } else { dev_dbg(cod3034x->dev, "%s JACK OUT\n", __func__); /* jack/mic out */ jackdet->mic_det = false; jackdet->adc_val = -EINVAL; } /* 发送耳机接口事件给音频框架 */ if (jackdet->jack_det && jackdet->mic_det)// switch_set_state(&cod3034x->sdev, 1); /* 4段耳机 */ else if (jackdet->jack_det) switch_set_state(&cod3034x->sdev, 2); /* 3段耳机 */ else switch_set_state(&cod3034x->sdev, 0); if (cod3034x->is_suspend) regcache_cache_only(cod3034x->regmap, false); snd_soc_write(codec, COD3034X_92_JACK_CTR, 0x30); if (jackdet->jack_det && jackdet->mic_det) { /* 4 Pole Jack-in */ snd_soc_write(codec, COD3034X_92_JACK_CTR, 0x30); /* button threshold */ snd_soc_write(codec, COD3034X_88_CTR_IMP3, 0x10); dev_dbg(cod3034x->dev, "%s 4 Pole Jack-In\n", __func__); } else if (jackdet->jack_det) { /* 3 Pole Jack-in */ snd_soc_write(codec, COD3034X_92_JACK_CTR, 0x20); /* button threshold */ snd_soc_write(codec, COD3034X_88_CTR_IMP3, 0x10); dev_dbg(cod3034x->dev, "%s 3 Pole Jack-In\n", __func__); } else { /* Jack-out */ snd_soc_write(codec, COD3034X_92_JACK_CTR, 0x00); dev_dbg(cod3034x->dev, "%s JACK OUT\n", __func__); } if (cod3034x->is_suspend) regcache_cache_only(cod3034x->regmap, true); dev_dbg(cod3034x->codec->dev, "Jack %s, Mic %s\n", jackdet->jack_det ? "inserted" : "removed", jackdet->mic_det ? "inserted" : "removed"); mutex_unlock(&cod3034x->jackdet_lock); }
3、cod3034x的按键检测函数
static void cod3034x_buttons_work(struct work_struct *work) { /*按键按下但是按键突然拔出情况下的处理*/ if (!jd->jack_det) { dev_err(cod3034x->dev, "Skip button events for jack_out\n"); /* button pressed and earjack out in sudden, button should be released. */ if (jd->privious_button_state == BUTTON_PRESS) { jd->button_det = false; input_report_key(cod3034x->input, jd->button_code, 0); input_sync(cod3034x->input); cod3034x_process_button_ev(cod3034x->codec, jd->button_code, 0); dev_dbg(cod3034x->dev, ":key %d released when jack_out\n", jd->button_code); } return; } /*如果只检测到了耳机但是没有检测到麦克风,说明是三段耳机,按键检测函数直接结束*/ if (!jd->mic_det) { dev_err(cod3034x->dev, "Skip button events for 3-pole jack\n"); return; } /*确保耳机插入无误后进行按键的检测*/ /* set delay for read correct adc value */ mdelay(10); for (j = 0; j < ADC_TRACE_NUM2; j++) { /* read GPADC for button */ for (i = 0; i < ADC_TRACE_NUM; i++) { adc = cod3034x_adc_get_value(cod3034x);//获取耳机的adc值 adc_values[i] = adc; udelay(ADC_READ_DELAY_US); } /* * check avg/devi value is proper * if not read adc after 5 ms */ avg = get_adc_avg(adc_values);//求平均值 devi = get_adc_devi(avg, adc_values);//(数据值-平均值)的平方和 dev_dbg(cod3034x->dev,":button adc avg: %d, devi: %d\n", avg, devi); if (devi > ADC_DEVI_THRESHOLD) { queue_work(cod3034x->buttons_wq,&cod3034x->buttons_work); for (i = 0; i < ADC_TRACE_NUM;) { dev_err(cod3034x->dev, ":retry button_work : %d %d %d %d %d\n", adc_values[i + 0], adc_values[i + 1], adc_values[i + 2], adc_values[i + 3], adc_values[i + 4]); i += 5; } return; } adc_final_values[j] = avg; if (avg > adc_max) adc_max = avg; mdelay(ADC_READ_DELAY_MS); } adc_final = adc_max; /* 检测按键是处于按下状态还是松开状态 */ if (adc_final > cod3034x->btn_release_value) { /*btn_release_value在cod3034x_i2c_parse_dt函数中可确定*/ dev_dbg(cod3034x->dev, "Button Released! adc_fanal: %d, btn_value: %d\n", adc_final, cod3034x->btn_release_value); current_button_state = BUTTON_RELEASE;//确定当前按键状态为松开 } else { dev_dbg(cod3034x->dev, "Button Pressed! adc_final: %d, btn_value: %d\n", adc_final, cod3034x->btn_release_value); current_button_state = BUTTON_PRESS;// } /*判断当前按键状态是否与前一按键状态相同,相同的话,直接返回不会再去检测adc值*/ if (jd->privious_button_state == current_button_state) { dev_dbg(cod3034x->dev, "status are same\n"); return; } jd->privious_button_state = current_button_state;/*检测到当前按键状态与前一按键状态不同,将当前按键状态传给保存前一按键状态的标志位,用于下次判断*/ adc = adc_final; jd->adc_val = adc_final; for (i = 0; i < 4; i++)/*打印耳机各个按键相对应的code,adc范围值*/ dev_dbg(cod3034x->dev, "[DEBUG]: adc(%d), buttons: code(%d), low(%d), high(%d)\n", adc, cod3034x->jack_buttons_zones[i].code, cod3034x->jack_buttons_zones[i].adc_low, cod3034x->jack_buttons_zones[i].adc_high); /* 根据设定的范围值去确定具体是哪个按键按下 */ if (current_button_state == BUTTON_PRESS) { for (i = 0; i < num_buttons_zones; i++) if (adc >= btn_zones[i].adc_low &&adc <= btn_zones[i].adc_high) { jd->button_code = btn_zones[i].code; /*确定后通过输入子系统上报事件,产生中断实行按键功能*/ input_report_key(cod3034x->input, jd->button_code, 1); input_sync(cod3034x->input); jd->button_det = true; cod3034x_process_button_ev(cod3034x->codec, jd->button_code, 1); dev_dbg(cod3034x->dev, ":key %d is pressed, adc %d\n", btn_zones[i].code, adc); return; } dev_dbg(cod3034x->dev, ":key skipped. ADC %d\n", adc); } else { /*按键松开事件上报中断*/ jd->button_det = false; input_report_key(cod3034x->input, jd->button_code, 0); input_sync(cod3034x->input); cod3034x_process_button_ev(cod3034x-> c7bf ;codec, jd->button_code, 0); dev_dbg(cod3034x->dev, ":key %d released\n", jd->button_code); } return; }
4、cod3034x_adc_get_value用于读取ADC接口的数据
static int cod3034x_adc_get_value(struct cod3034x_priv *cod3034x) { int adc_data = -1; int adc_max = 0; int adc_min = 0xFFFF; int adc_total = 0; int adc_retry_cnt = 0; int i; struct iio_channel *jack_adc = cod3034x->jack_adc; for (i = 0; i < COD3034X_ADC_SAMPLE_SIZE; i++) { /*#define COD3034X_ADC_SAMPLE_SIZE 5*/ iio_read_channel_raw(&jack_adc[0], &adc_data); /* 设定多次读取,直到读取成功 */ while (adc_data < 0) { adc_retry_cnt++; if (adc_retry_cnt > 10) return adc_data; iio_read_channel_raw(&jack_adc[0], &adc_data); } /* Update min/max values */ if (adc_data > adc_max) adc_max = adc_data; if (adc_data < adc_min) adc_min = adc_data; adc_total += adc_data;//获取五次的值进行相加 } return (adc_total - adc_max - adc_min) / (COD3034X_ADC_SAMPLE_SIZE - 2);//减去五次数值中的最大值和最小值,求平均值。 }
5、cod3034x_i2c_parse_dt该函数用于从dts获取codec的各种属性值
static void cod3034x_i2c_parse_dt(struct cod3034x_priv *cod3034x) { /* todo .. Need to add DT parsing for 3034 */ struct device *dev = cod3034x->dev; struct device_node *np = dev->of_node; unsigned int bias_v_conf; int mic_range, mic_delay, btn_rel_val; struct of_phandle_args args; int i = 0; int ret; cod3034x->int_gpio = of_get_gpio(np, 0); if (cod3034x->int_gpio < 0) dev_err(dev, "(*)Error in getting Codec-3034 Interrupt gpio\n"); /* 默认偏差电压 */ cod3034x->mic_bias1_voltage = MIC_BIAS1_VO_3_0V; cod3034x->mic_bias2_voltage = MIC_BIAS2_VO_3_0V; cod3034x->mic_bias_ldo_voltage = MIC_BIAS_LDO_VO_3_3V; ret = of_property_read_u32(dev->of_node, "mic-bias1-voltage", &bias_v_conf); if ((!ret) && ((bias_v_conf >= MIC_BIAS1_VO_2_8V) && (bias_v_conf <= MIC_BIAS1_VO_3_0V))) cod3034x->mic_bias1_voltage = bias_v_conf; else dev_dbg(dev, "Property 'mic-bias1-voltage' %s", ret ? "not found" : "invalid value (use:1-3)"); ret = of_property_read_u32(dev->of_node, "mic-bias2-voltage", &bias_v_conf); if ((!ret) && ((bias_v_conf >= MIC_BIAS2_VO_2_8V) && (bias_v_conf <= MIC_BIAS2_VO_3_0V))) cod3034x->mic_bias2_voltage = bias_v_conf; else dev_dbg(dev, "Property 'mic-bias2-voltage' %s", ret ? "not found" : "invalid value (use:1-3)"); ret = of_property_read_u32(dev->of_node, "mic-adc-range", &mic_range);//从dts中获取mic的adc阈值,根据此值去检测判断mic是否存在 if (!ret) cod3034x->mic_adc_range = mic_range; else cod3034x->mic_adc_range = 1120; ret = of_property_read_u32(dev->of_node, "mic-det-delay", &mic_delay); if (!ret) cod3034x->mic_det_delay = mic_delay; else cod3034x->mic_det_delay = 150; ret = of_property_read_u32(dev->of_node, "btn-release-value", &btn_rel_val);/*获取按键释放的阈值,用于判断按键是否释放*/ if (!ret) cod3034x->btn_release_value = btn_rel_val; else cod3034x->btn_release_value = 1100;/*若设备树中无定义,使用该值*/ ret = of_property_read_u32(dev->of_node, "mic-bias-ldo-voltage", &bias_v_conf); if ((!ret) && ((bias_v_conf >= MIC_BIAS_LDO_VO_2_8V) && (bias_v_conf <= MIC_BIAS_LDO_VO_3_3V))) cod3034x->mic_bias_ldo_voltage = bias_v_conf; else dev_dbg(dev, "Property 'mic-bias-ldo-voltage' %s", ret ? "not found" : "invalid value (use:1-3)"); dev_dbg(dev, "Bias voltage values: bias1 = %d, bias2= %d, ldo = %d\n", cod3034x->mic_bias1_voltage, cod3034x->mic_bias2_voltage, cod3034x->mic_bias_ldo_voltage); if (of_find_property(dev->of_node, "update-firmware", NULL)) cod3034x->update_fw = true; else cod3034x->update_fw = false; if (of_find_property(dev->of_node, "use-btn-adc-mode", NULL) != NULL) cod3034x->use_btn_adc_mode = true; dev_err(dev, "Using %s for button detection\n", cod3034x->use_btn_adc_mode ? "GPADC" : "internal h/w"); if (cod3034x->use_btn_adc_mode) { /*以下获取按键的adc范围值,用于判断耳机按键类型,确定按键功能和上报节点*/ for (i = 0; i < 4; i++) { if (of_parse_phandle_with_args(dev->of_node, "but-zones-list", "#list-but-cells", i, &args)) break; cod3034x->jack_buttons_zones[i].code = args.args[0]; cod3034x->jack_buttons_zones[i].adc_low = args.args[1]; cod3034x->jack_buttons_zones[i].adc_high = args.args[2]; } /* initialize button status */ cod3034x->jack_det.privious_button_state = BUTTON_RELEASE; for (i = 0; i < 4; i++) dev_err(dev, "[DEBUG]: buttons: code(%d), low(%d), high(%d)\n", cod3034x->jack_buttons_zones[i].code, cod3034x->jack_buttons_zones[i].adc_low, cod3034x->jack_buttons_zones[i].adc_high); } }
上述就是三星平台音频codec模块的主要代码分析。
三、总结:
1、拿到BUG,首先进行问题的分析,从测试提供的资料中获取与BUG有关的所有信息,与测试人员进行沟通,确定测试流程和测试环境,避免自测产生不必要的麻烦。
2、进行问题解析,对问题进行定位,列出可能产生此现象的问题点,先排除自身模块的问题再去考虑其他模块的问题。环环相扣,层层排除。
3、保持良好的心态,认真解BUG。
相关文章推荐
- 学习笔记:音频之耳机按键事件上报流程
- Android应用开发学习笔记之播放音频
- android 多媒体部分学习笔记九----数字音频合成
- iOS音频学习笔记二:iOS SDK中与音频有关的相关框架
- HTML5学习笔记第一节(智能提示和视频音频标签)
- 视音频技术零基础学习笔记(一)
- Html5学习笔记四—播放音频和视频文件
- android 多媒体部分学习笔记九----数字音频合成
- FFmpeg基础库编程开发学习笔记——音频常见格式及字幕格式
- 流媒体技术学习笔记之(六)FFmpeg官方文档先进音频编码(AAC)
- android 学习笔记 播放音频 和视频
- 音频引擎IrrKlang学习笔记01:播放控制与音效设置
- 安卓手把手教你学习并实现 安卓耳机口音频转红外发射
- unity3D学习之音频测试-audio菜鸟笔记4
- FFmpeg学习笔记001_视音频基础知识
- FFmpeg基础库编程开发学习笔记——音频常见格式及字幕格式
- 【js学习笔记-108】----脚本化音频和视频
- asoc 音频驱动学习笔记2
- iOS音频学习笔记三:音频会话管理
- Android游戏开发学习笔记(二):音频的播放