耳机插拔流程

2024-05-07 23:58
文章标签 流程 耳机 插拔

本文主要是介绍耳机插拔流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原址

1.1      耳机

在Android系统中,有线耳机分两种,一种带mic,一种不带mic,带mic的耳机被称为Headset,不带mic的耳机被称为HeadPhone。在audio.h中,有以下几个设备来表示耳机:

       AUDIO_DEVICE_OUT_WIRED_HEADSET             = 0x4,

AUDIO_DEVICE_OUT_WIRED_HEADPHONE           = 0x8,

AUDIO_DEVICE_IN_WIRED_HEADSET         = AUDIO_DEVICE_BIT_IN | 0x10,

         对于Headset的插入检测,一般通过jack耳机插座来完成,大概原理是使用带检测机构的耳机插座,将检测脚连到GPIO中断上,耳机插入时,是的检测脚的电平变化,引起中断。通过GPIO的值判断耳机是插入还是拔出。

1.2      Uevent

目前使用的耳机插拔事件使用的是UEvent。但是也可以使用InputEvent,这种设置在frameworks/base/core/res/res/values/config.xml中,设置项为

<boolname="config_useDevInputEventForAudioJack">false</bool>

值为false,所以目前kernel是用Uevent来通知应用层耳机插拔的。

什么是Uevent:

1.    内核通知应用的一种方式

2.    目前使用socket进行内核和应用的通信

3.    uevent就是一个特殊格式的字符串,如下,这应该是插入鼠标的Uevent

"add@/class/input/input9/mouse2\0    // message

ACTION=add\0                         // action type

DEVPATH=/class/input/input9/mouse2\0 // path in /sys

SUBSYSTEM=input\0                    // subsystem (class)

SEQNUM=1064\0                        // sequence number

PHYSDEVPATH=/devices/pci0000:00/0000:00:1d.1/usb2/2­2/2­2:1.0\0  // device path in /sys

PHYSDEVBUS=usb\0      // bus

PHYSDEVDRIVER=usbhid\0 // driver

MAJOR=13\0            // major number

MINOR=34\0",          // minor number

内核发送uevent使用kobject_uevent_env,它还有一个wrapper是kobject_uevent,这个函数使用的比较多

1.3      UEvent是怎么工作的?

1.3.1       Switch驱动

Switch是在android中实现的一个module,可动态加载,是一个驱动。 Switch相关的代码在在drivers\switch\switch_class.c和drivers\switch\switch_gpio.c中,其中switch_gpio基于platform_device框架,在switch_class.c中,Export了以下两个函数供switch_gpio使用

    EXPORT_SYMBOL_GPL(switch_dev_register);

EXPORT_SYMBOL_GPL(switch_dev_unregister);

而Switch_gpio.c是驱动模块栈的更靠近设备的一层,这个设备就是gpio,初始化函数是

gpio_switch_probe{

         switch_dev_register(&switch_data->sdev);

         gpio_request(switch_data->gpio,pdev->name);

         gpio_direction_input(switch_data->gpio);

         gpio_to_irq(switch_data->gpio);

         request_irq//申请GPIO中断

         gpio_switch_work

}

这货是个platform驱动,它会调用

static int __init gpio_switch_init(void)

{

         returnplatform_driver_register(&gpio_switch_driver);

}

把自己注册成一个platform驱动。Platform是一种虚拟总线,和spi,emmc等都是类似的。

基于platform device/driver框架,在probe函数中完成初始化,包括获取gpio的使用权限,设置gpio方向为输入,注册switch_dev设备,为gpio分配中断,指定中断服务程序,初始化一个gpio_switch_work工作,最后读取gpio初始状态。  

在switch_class.c中,调用switch_set_state->switch_gpio_print_state输出GPIO状态到 sysfs。

Sysfs是一个虚拟的文件系统,由Linux内核提供,通过使用虚拟文件,sysfs向用户空间公布了关于内核子系统的信息,硬件设备和相关的设备驱动。但是这个文件系统并不是被上层直接使用的,它仅仅是作为比较直观的方式提供给human being检查状态用的。

而真正发送uevent是在switch_class.c的这个函数里:

void switch_set_state(struct switch_dev *sdev,int state)

最终通过kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp)发送。

kobject_uevent_env的流程如下:

1.    调用kobject_uevent_env之前,系统已经调用uevent_net_init(struct net *net)完成了初始化工作,也就是创建了socket,并创建了netlink,如下:

         netlink_kernel_create(net,NETLINK_KOBJECT_UEVENT,1, NULL, NULL, THIS_MODULE);

                   sock_create_lite(PF_NETLINK,SOCK_DGRAM, unit, &sock)

                   __netlink_create(&init_net,sock, cb_mutex, unit)

2.    netlink_broadcast_filtered,netlink广播uevent信息。

         sk_for_each_bound

                   do_one_broadcast(sk,&info);

上面提到了Netlink socket,下面看看这是啥:

Netlink socket是一种Linux特有的socket,用于实现用户进程与内核进程之间通信的一种特殊的进程间通信方式(IPC) ,也是网络应用程序与内核通信的最常用的接口。
Netlink 是一种在内核和用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就能使用 Netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 Netlink。

1.3.2       Netlink Socket通信

用户空间程序只需要创建一个NETLINK socket描述符,就可以侦听Uevent了,如下:

sockfd=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);

AF_NETLINK 是一种“domain”,定义了通信使用的协议,比如我们通常使用的IPV4协议。

<sys/socket.h>定义的所有的协议如下: 

AF_UNIX,

AF_LOCAL

AF_INET

IPv4

AF_INET6

IPv6

AF_IPX

IPX

AF_NETLINK

Kernel

AF_X25

ITU-T

AF_AX25

Amateur

AF_ATMPVC

Access

AF_APPLETALK

AppleTal

AF_PACKET

Low

AF_ALG

Interface

事实上,Framework就是写了一个很简单的socket网络程序:

在uevent.c (hardware\libhardware_legacy\uevent)中,看函数uevent_init,

s =socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

setsockopt(s,SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

bind(s,(struct sockaddr *) &addr, sizeof(addr)

再看uevent_next_event(char* buffer, intbuffer_length)

int count =recv(fd, buffer, buffer_length, 0);

其实就是读取socket的内容到buffer中。

1.3.3       观察者模式

拿到socket的内容后,需要通知对它感兴趣的模块。这里就使用了观察者模式。Android在frameworks/base/services/Java/com/android/server/WiredAccessoryManager. java中实现对UEvent的处理。在这个文件中,从UEventObserver中继承了类WiredAccessoryObserver,在makeObservedUEventList中将要观察的事件加入到UEvent系统中:

 

UeventObserver持有一个静态的UEventThread对象,所有子类Observer都共享一个Thread。

private staticUEventThread sThread;

UeventThread是一个静态类,有一个Array:

private finalArrayList<Object> mKeysAndObservers = new ArrayList<Object>();

各种子类Observer都添加key和observer到mKeysAndObservers。比如,WiredAccessoryObserver添加(“DEVPATH= /devices/virtual/switch/h2w”,this),(“DEVPATH= /devices/virtual/switch/usb_audio”,this),(“DEVPATH= /devices/virtual/switch/hdmi”,this)到mKeysAndObservers。这个Thread不断的去读取AF_NETLINK中的数据,并对注册进去的key字符串进行匹配,匹配上的调用改key对应的observer的onUevent函数。

如何匹配的?

private voidsendEvent(String message){

for(;;){

if(message.contains(key))

mTempObserversToSignal.add(observer);

}

for (;;) {

final UEventObserverobserver = mTempObserversToSignal.get(i);

observer.onUEvent(event);

}

}

这个onUEvent经过一系列无聊的调用,最终调到

AudioManager:: setWiredDeviceConnectionState(intdevice, int state, String name),自此进入Audio子系统

1.4      Audio子系统

最终会到Audio Policy中的setDeviceConnectionState来,它有两个参数,一个表示设备id,另一个表示该设备是插入还是拔出。比如设备id 4代表的是HEADSET,而当state是AUDIO_POLICY_DEVICE_STATE_AVAILABLE时表示插入,当AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE时表示拔出。当设备有hw module与之相对应时(getModuleForDevice),会增加或减少mAvailableOutputDevices和mAvailableInputDevices。

这篇关于耳机插拔流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/968757

相关文章

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

MySQL 临时表与复制表操作全流程案例

《MySQL临时表与复制表操作全流程案例》本文介绍MySQL临时表与复制表的区别与使用,涵盖生命周期、存储机制、操作限制、创建方法及常见问题,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小... 目录一、mysql 临时表(一)核心特性拓展(二)操作全流程案例1. 复杂查询中的临时表应用2. 临时

MySQL 升级到8.4版本的完整流程及操作方法

《MySQL升级到8.4版本的完整流程及操作方法》本文详细说明了MySQL升级至8.4的完整流程,涵盖升级前准备(备份、兼容性检查)、支持路径(原地、逻辑导出、复制)、关键变更(空间索引、保留关键字... 目录一、升级前准备 (3.1 Before You Begin)二、升级路径 (3.2 Upgrade

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

Spring Boot从main方法到内嵌Tomcat的全过程(自动化流程)

《SpringBoot从main方法到内嵌Tomcat的全过程(自动化流程)》SpringBoot启动始于main方法,创建SpringApplication实例,初始化上下文,准备环境,刷新容器并... 目录1. 入口:main方法2. SpringApplication初始化2.1 构造阶段3. 运行阶

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

Ubuntu 24.04启用root图形登录的操作流程

《Ubuntu24.04启用root图形登录的操作流程》Ubuntu默认禁用root账户的图形与SSH登录,这是为了安全,但在某些场景你可能需要直接用root登录GNOME桌面,本文以Ubuntu2... 目录一、前言二、准备工作三、设置 root 密码四、启用图形界面 root 登录1. 修改 GDM 配

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级