android mic切换_Android系统中从发生耳机插拔事件到音频Route切换过程分析

本文详细分析了Android系统中耳机插入/拔出事件如何触发中断,中断处理函数如何执行,以及如何改变音频Route。通过查看硬件电路、中断处理函数和Codec芯片配置,揭示了从耳机检测到音频Route切换的完整过程。
摘要由CSDN通过智能技术生成

【概要】

我们知道,耳机插入/拔出事件肯定是通过中断通知系统进行处理的。有了这个认识之后,我们就可以对这个过程进行逐个击破的分析了:

1、  谁为耳机事件产生中断?

2、  中断处理函数是哪个?

3、  中断处理函数中执行了什么操作来改变音频Route?

【备注】

本文基于我所使用的硬件环境进行分析,虽然可能与你现在使用的芯片不同,但思路是一样的。重在方法,不在结果。

【谁产生中断】

要回答这个问题,需要查看耳机部分的电路原理图:

图1  耳机检测电路

可以看到,耳机插口(headset-jack)是和ts3a227e这款耳机检测芯片相连接的。芯片上的/INT和/MIC_PRESENT应该就是中断输出引脚。查看芯片datasheet中的说明:

图2  ts3a227e芯片引脚说明

至此我们就清楚了:当耳机插入/拔出时,ts3a227e能够检测到这一动作,同时向SoC发送中断。SoC接收到中断后,相应的中断处理函数会对硬件配置进行修改。

【找到中断处理函数】

回忆一下《Linux设备驱动程序》这本书“中断处理”章节的内容:设备需要为自己要使用的中断线向系统申请中断号,并在系统中注册相应的中断处理函数。

查看ts3a227e芯片的驱动源码文件ts3a227e.c可以发现该芯片是作为i2c设备进行注册的。在probe函数中不仅申请了设备中断号、绑定了中断处理函数ts3a227e_interrupt(),还向工作队列中添加了3个延迟任务,分别是hs_detect_func()、enable_key_detect_func()和long_press_func(),它们在后面很快就会被用到。如下所示:

static int ts3a227e_i2c_probe(struct i2c_client *i2c,

const struct i2c_device_id *id)

{

struct ts3a227e *ts3a227e;

struct device *dev = &i2c->dev;

unsigned int i;

int ret;

…...

INIT_DELAYED_WORK(&ts3a227e->hs_det_work, hs_detect_func);

INIT_DELAYED_WORK(&ts3a227e->enable_key_detect_work, enable_key_detect_func);

INIT_DELAYED_WORK(&ts3a227e->long_press_work, long_press_func);

if (i2c->irq != -1) {

ret = devm_request_threaded_irq(dev, i2c->irq, NULL, ts3a227e_interrupt,

IRQF_TRIGGER_LOW | IRQF_ONESHOT,

"TS3A227E", ts3a227e);

if (ret) {

dev_err(dev, "Cannot request irq %d (%d)\n", i2c->irq, ret);

return ret;

}

}

…...

return 0;

}

【中断处理函数执行了什么操作】

经过上面分析,我们可以判断当ts3a227e芯片发出的中断被SoC接收到时,函数ts3a227e_interrupt()会被立即调用。这个函数的功能有2个:读取寄存器以更新相关变量的值,以及调用之前添加到工作队列中的延迟任务hs_detect_func()。关键代码如下:

static irqreturn_t ts3a227e_interrupt(int irq, void *data)

{

…...

/* Check for plug/unplug. */

regmap_read(regmap, TS3A227E_REG_INTERRUPT, &(ts3a227e->int_reg));

pr_err("%s ent, int_reg(0x01): %0x\n", __func__, ts3a227e->int_reg);

if (ts3a227e->int_reg & (DETECTION_COMPLETE_EVENT | INS_REM_EVENT)) {

regmap_read(regmap, TS3A227E_REG_ACCESSORY_STATUS, &(ts3a227e->acc_reg));

pr_err("%s acc_reg(0x0b): %0x\n", __func__, ts3a227e->acc_reg);

}

…...

cancel_delayed_work_sync(&ts3a227e->hs_det_work);

schedule_delayed_work(&ts3a227e->hs_det_work,

msecs_to_jiffies(5)); /* 调度延迟任务hs_detect_func() */

return IRQ_HANDLED;

}

被调用的hs_detect_func()函数代码如下:

static void hs_detect_func(struct work_struct *work)

{

struct ts3a227e *ts3a227e = container_of(work,

struct ts3a227e, hs_det_work.work);

pr_err("%s, id:%x\n", __func__, ts3a227e->id);

check_jack_status(ts3a227e);

}

被hs_detect_func()调用的check_jack_status()函数主要功能是:检测耳机是否完全插入/拔出,并完成耳机按键状态检测,最终调用ts3a227e_jack_report()函数根据检测结果上报用于执行音频Route切换的操作。关键代码如下:

static void check_jack_status(struct ts3a227e *ts3a227e)

{

struct regmap *regmap = ts3a227e->regmap;

unsigned int i;

bool long_press_det = false;

/* Check for plug/unplug. */

if (ts3a227e->int_reg & (DETECTION_COMPLETE_EVENT | INS_REM_EVENT)) {

//regmap_read(regmap, TS3A227E_REG_ACCESSORY_STATUS, &acc_reg);

printk("%s acc_reg(0x0b): %0x\n", __func__, ts3a227e->acc_reg);

ts3a227e_new_jack_state(ts3a227e, ts3a227e->acc_reg);

}

…...

input_sync(ts3a227e->button_dev);

ts3a227e_jack_report(ts3a227e);

}

分析到这里可以看到,紧接着在ts3a227e_jack_report()函数中要执行的就是操作硬件实现音频Route切换了。但为了看懂这个函数中的操作,我们需要先来看一看更宏观一点的电路关系,分别是耳机功放芯片电路和Codec芯片的部分电路:

图3  耳机功放芯片电路

图4  Codec芯片局部电路

很明显,耳机功放芯片的片选引脚/SHDN连接到Codec芯片的GPIO5引脚上,由Codec芯片进行控制,低电平有效(就是说当/SHDN引脚上的电平为低时,耳机功放芯片将被关闭)。左右声道的音频信号分别从Codec芯片的LOUT1P、LOUT1N和LOUT2P、LOUT2N引脚输出到耳机功放芯片的INL+、INL-和INR+、INR-引脚上。

所以如果想从耳机里听到声音,需要先将/SHDN引脚上的电平拉高。由于这个引脚上的电平是Codec芯片进行控制的,所以需要修改Codec芯片中的寄存器配置。

我这里使用的Codec芯片为Realtek5677。

电路分析到这里就可以继续阅读ts3a227e_jack_report()函数的实现细节了。它根据之前的耳机检测结果(是否插入耳机,插入的耳机是否带有麦克风),分别调用rt5677_enable_micbias()函数对Codec芯片Realtek5677进行相应配置。代码如下:

static void ts3a227e_jack_report(struct ts3a227e *ts3a227e)

{

pr_err("%s\n", __func__);

if (ts3a227e->plugged) { /* 有耳机插入 */

pr_err("%s, HEADPHONE plug\n", __func__);

if (ts3a227e->mic_present) { /* 插入的耳机带有麦克风 */

printk("%s, MICPHONE plug\n", __func__);

extcon_set_state(&ts3a227e->edev, BIT_HEADSET);

rt5677_enable_micbias(true);

}

else { /* 插入的耳机不带麦克风 */

rt5677_enable_micbias(false);

extcon_set_state(&ts3a227e->edev, BIT_HEADSET_NO_MIC);

}

}

else { /* 没有耳机插入 */

pr_err("%s, HEADSET unplug\n", __func__);

/* disable key press detection. */

regmap_update_bits(ts3a227e->regmap, TS3A227E_REG_SETTING_2, KP_ENABLE, 0);

extcon_set_state(&ts3a227e->edev, BIT_NO_HEADSET);

rt5677_enable_micbias(false);

}

}

再查看rt5677_enable_micbias()函数的实现细节,可以发现其使用了ASoC架构中的DAPM相关的函数。如下:

void rt5677_enable_micbias(bool enable)

{

struct snd_soc_codec *codec;

pr_err("%s, %d\n", __func__, enable);

codec = cht_get_codec(&snd_soc_card_cht); /* snd_soc_card_cht是个全局变量 */

if(codec == NULL){

pr_err("%s, codec has not probed yet!\n", __func__);

return;

}

if (enable) {

snd_soc_dapm_force_enable_pin(&codec->dapm, "MICBIAS1");

}

else {

snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS1");

}

snd_soc_dapm_sync(&codec->dapm);

}

这些DAPM函数经过一系列既定的调用流程后最终会落实到DAPM widgets上。关于DAPM的相关知识本篇文章不进行介绍(因为要写的话实在太多了),如果想了解这方面内容,我推荐你阅读Linux内核官方文档《Dynamic Audio Power Management for Portable Devices》、ALSA官方文档《DAPM》,或sepnic的博客,或我自己曾经也翻译过的一篇官方文档《DAPM概述(中文翻译)/ dapm.txt》。

总之,最终要用到的DAPM widgets在ASoC Machine驱动的源文件cht_bl_dpcm_rt5677.c中进行了定义。Machine驱动中的声卡结构体定义如下:

/* SoC card */

static struct snd_soc_card snd_soc_card_cht = {

.name = "cherrytrailaud",

.dai_link = cht_dailink,

.num_links = ARRAY_SIZE(cht_dailink),

.set_bias_level = cht_set_bias_level,

.dapm_widgets = cht_dapm_widgets, /* DAPM widgets */

.num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),

.dapm_routes = cht_audio_map,

.num_dapm_routes = ARRAY_SIZE(cht_audio_map),

.controls = cht_mc_controls,

.num_controls = ARRAY_SIZE(cht_mc_controls),

};

上述声卡所使用的DAPM

widgets定义如下,其中第一个对应的就是耳机插拔事件。其中cht_rt5677_hp_event()则是耳机插拔事件发生时要被执行的函数:

static const struct snd_soc_dapm_widget cht_dapm_widgets[] = {

SND_SOC_DAPM_HP("Headphone", cht_rt5677_hp_event), /* 耳机事件的DAPM widget */

SND_SOC_DAPM_SPK("Speaker", cht_rt5677_spk_event),

SND_SOC_DAPM_MIC("Headset Mic", NULL),

SND_SOC_DAPM_MIC("Int Mic", NULL),

SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,

platform_clock_control, SND_SOC_DAPM_PRE_PMU|

SND_SOC_DAPM_POST_PMD),

};

在cht_rt5677_hp_event()函数中就可以看到修改Codec芯片Realtek5677寄存器的相关代码了。通过拉高Codec芯片的GPIO5引脚上的电平,相应的耳机功放芯片上的/SHDN引脚电平也被拉高,耳机功放芯片开始工作。代码如下:

static int cht_rt5677_hp_event(struct snd_soc_dapm_widget *w,

struct snd_kcontrol *k, int event)

{

struct snd_soc_dapm_context *dapm = w->dapm;

struct snd_soc_card *card = dapm->card;

struct snd_soc_codec *codec;

pr_err("%s, %d\n", __func__, __LINE__);

codec = cht_get_codec(card);

if (!codec) {

pr_err("Codec not found; Unable to set platform clock\n");

return -EIO;

}

if (SND_SOC_DAPM_EVENT_ON(event)) { /* 发生耳机插入事件 */

pr_err("%s, %d\n", __func__, __LINE__);

msleep(20);

snd_soc_update_bits(codec, RT5677_GPIO_CTRL2,

RT5677_GPIO5_OUT_MASK, RT5677_GPIO5_OUT_HI); /* 将耳机功放芯片/SHDN引脚的电平拉高 */

msleep(50);

} else {

pr_err("%s, %d\n", __func__, __LINE__);

snd_soc_update_bits(codec, RT5677_GPIO_CTRL2,

RT5677_GPIO5_OUT_MASK, RT5677_GPIO5_OUT_LO);

}

return 0;

}

至此,从耳机插入/拔出到音频Route更改的过程就分析完毕了。希望这篇文章能让你有所收获。

Android 上,可以使用两种方式来改变音频输出输入设备:使用 AudioManager 类和使用 AudioPolicyManager 类。下面我将分别介绍这两种方式。 1. 使用 AudioManager 类: - 切换音频输出设备: ```java AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager.setSpeakerphoneOn(true); // 切换到扬声器 audioManager.setBluetoothScoOn(true); // 切换到蓝牙耳机 audioManager.setMode(AudioManager.MODE_IN_CALL); // 切换耳机 ``` 通过调用 `setSpeakerphoneOn()` 方法切换到扬声器,调用 `setBluetoothScoOn()` 方法切换到蓝牙耳机,调用 `setMode()` 方法切换耳机。 - 切换音频输入设备(麦克风): ```java AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager.setMicrophoneMute(true); // 静音麦克风 audioManager.setMicrophoneMute(false); // 取消静音麦克风 ``` 2. 使用 AudioPolicyManager 类(需要较高的系统权限): - 切换音频输出设备: ```java AudioPolicyManager audioPolicyManager = (AudioPolicyManager) getSystemService(Context.AUDIO_POLICY_SERVICE); audioPolicyManager.setDeviceConnectionState(deviceId, AudioSystem.DEVICE_STATE_AVAILABLE, ""); // 设备可用 audioPolicyManager.setDeviceConnectionState(deviceId, AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); // 设备不可用 ``` 通过调用 `setDeviceConnectionState()` 方法,可以将指定的音频输出设备设置为可用或不可用。 - 切换音频输入设备(麦克风): ```java AudioPolicyManager audioPolicyManager = (AudioPolicyManager) getSystemService(Context.AUDIO_POLICY_SERVICE); audioPolicyManager.setMicrophoneMute(deviceId, true); // 静音麦克风 audioPolicyManager.setMicrophoneMute(deviceId, false); // 取消静音麦克风 ``` 通过调用 `setMicrophoneMute()` 方法,可以将指定的麦克风设置为静音或取消静音。 请注意,具体的设备标识符(deviceId)和方法实现可能因设备型号和 Android 版本而有所不同。因此,在实际应用,你可能需要进行适配和测试以确保兼容性和正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值