----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板eMMC :16GBLPDDR3 :4GB显示屏 :15.6英寸HDMI接口显示屏u-boot :2023.04linux :6.3----------------------------------------------------------------------------------------------------------------------------
jack字面上就是插孔,什么是插孔,我理解就是我们音频设备上的麦克风、耳机等设备的插孔。
那jack设备是用来实现什么功能的呢?其主要目的是为了实现耳机、麦克风等设备插入和拔出的检测。
我们以耳机检测为例来说说,一般的耳机检测包括普通的耳机检测和带有麦克风的耳机检测两种,这两种耳机统称为Headset,而对于不带麦克风的耳机,一般统称为Headphone。
对于Headset装置的插入检测,一般是通过jack来完成,大致原理是使用带检测机械结构的耳机插座,将检测引脚连接到SoC的GPIO口,如下图所示,当耳机插入时。耳机插头的金属会碰到检测引脚,使得检测引脚的电平发生改变,从而触发中断。这样就可以在中断处理函数中读取GPIO口的值,进一步判断是耳机插入还是拔出。
ALSA CORE已经实现了jack中间层,在文件include/sound/jack.h中提供了访问jack中间层代码的API。
一、ALSA核心数据结构
1.1 struct snd_jack
Jack使用struct snd_jack数据结构来描述;
struct snd_jack {
struct list_head kctl_list;
struct snd_card *card;
const char *id;
#ifdef CONFIG_SND_JACK_INPUT_DEV
struct input_dev *input_dev;
struct mutex input_dev_lock;
int registered;
int type;
char name[100];
unsigned int key[6]; /* Keep in sync with definitions above */
#endif /* CONFIG_SND_JACK_INPUT_DEV */
int hw_status_cache;
void *private_data;
void (*private_free)(struct snd_jack *);
};
其中:
- kctl_list:保存jack kcontrol的链表,链表中的每一个元素都是struct snd_jack_kctl;
- card:所属声卡设备;
- id:jack的唯一标识符;
- input_dev:输入设备结构体(struct input_dev)指针,用于处理与jack相关的输入事件;
- input_dev_lock:输入设备锁,用于保护对输入设备的操作;
- registered:jack是否已经注册的标志;
- type:jack能够上报的类型,参考enum snd_jack_types;
- name:jack的名称字符串,长度为100字节;
- key:如果jack能够上报SND_JACK_BTN_0、SND_JACK_BTN_1等类型,数组元素key[x]存放SND_JACK_BTN_x对应的EV_KEY事件的事件编码;
- hw_status_cache:硬件状态的缓存值;
- private_data:私有数据指针,可以用于存储与jack相关的其他数据;
- private_free:私有数据释放函数指针,用于在释放snd_jack时释放相关的私有数据;
1.2 enum snd_jack_types
jack能够上报的类型如结构体定义描述;
/**
* enum snd_jack_types - Jack types which can be reported
* @SND_JACK_HEADPHONE: Headphone
* @SND_JACK_MICROPHONE: Microphone
* @SND_JACK_HEADSET: Headset
* @SND_JACK_LINEOUT: Line out
* @SND_JACK_MECHANICAL: Mechanical switch
* @SND_JACK_VIDEOOUT: Video out
* @SND_JACK_AVOUT: AV (Audio Video) out
* @SND_JACK_LINEIN: Line in
* @SND_JACK_BTN_0: Button 0
* @SND_JACK_BTN_1: Button 1
* @SND_JACK_BTN_2: Button 2
* @SND_JACK_BTN_3: Button 3
* @SND_JACK_BTN_4: Button 4
* @SND_JACK_BTN_5: Button 5
*
* These values are used as a bitmask.
*
* Note that this must be kept in sync with the lookup table in
* sound/core/jack.c.
*/
enum snd_jack_types {
SND_JACK_HEADPHONE = 0x0001, // 如果jack->type & 0x001=0x001,则设置input设备可以上报EV_SW事件,事件编码为0x02
SND_JACK_MICROPHONE = 0x0002, // 同上,设置input设备可以上报EV_SW事件,事件编码为0x04
SND_JACK_HEADSET = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE,
SND_JACK_LINEOUT = 0x0004, // 同上,设置input设备可以上报EV_SW事件,事件编码为0x04
SND_JACK_MECHANICAL = 0x0008, /* If detected separately */ 同上,设置input设备可以上报EV_SW事件,事件编码为0x07
SND_JACK_VIDEOOUT = 0x0010, // 同上,设置input设备可以上报EV_SW事件,事件编码为0x08
SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT,
SND_JACK_LINEIN = 0x0020, // 同上,则设置input设备可以上报EV_SW事件,事件编码为0x0d
/* Kept separate from switches to facilitate implementation */
SND_JACK_BTN_0 = 0x4000, // 如果jack->type & 0x4000=0x4000,则设置input设备可以上报EV_KEY事件,事件编码为0x100
SND_JACK_BTN_1 = 0x2000, // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x101
SND_JACK_BTN_2 = 0x1000, // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x102
SND_JACK_BTN_3 = 0x0800, // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x103
SND_JACK_BTN_4 = 0x0400, // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x104
SND_JACK_BTN_5 = 0x0200, // 同上。设置input设备可以上报EV_KEY事件,事件编码为0x105
};
1.3 struct snd_jack_kctl
ALSA中使用struct snd_jack_kctl数据结构来描述jack中的kcontrol,定义在sound/core/jack.c:
struct snd_jack_kctl {
struct snd_kcontrol *kctl;
struct list_head list; /* list of controls belong to the same jack */
unsigned int mask_bits; /* only masked status bits are reported via kctl */
struct snd_jack *jack; /* pointer to struct snd_jack */
bool sw_inject_enable; /* allow to inject plug event via debugfs */
#ifdef CONFIG_SND_JACK_INJECTION_DEBUG
struct dentry *jack_debugfs_root; /* jack_kctl debugfs root */
#endif
};
其中:
- kctl:指向struct snd_kcontrol的指针;
- list:链表节点,用于将当前节点链接到snd_jack的kctl_list链表;
- mask_bits:当前jack control能够上报的类型,参考enum snd_jack_types;比如我们设置了mask_bits值为SND_JACK_HEADPHONE,当耳机插入或者拔出的时候HP_DET引脚就会接收到中断,中断处理程序读取引脚电平,并通过input设备上报SND_JACK_HEADPHONE对应的EV_SK事件,事件编码为0x02,值为插入/拔出;
- jack:指向struct snd_jack的指针,表示与snd_jack_kctl相关联的jack;
- sw_inject_enable:允许通过debugfs注入插头事件;
- jack_debugfs_root:jack_kctl debugfs根目录;
1.4 关系图
为了更加清晰的了解struct snd_jack 、struct snd_kcontrol、struct snd_jack_kctl 等数据结构的关系,我们绘制了如下关系图:
其中我们比较关注的点是:
- jack是挂在snd_card成员devices链表下面的一个snd_device;
- snd_jack中的字段:kctl_list,该链表保存所有的jack kcontrol,每个jack kcontrol都关联一个kcontrol;
- snd_jack中的字段:input_dev是一个input设备,用于上报耳机插入/拔出、麦克风插入/拔出等事件。
二、ALSA核心API
2.1 创建Jack设备
jack设备的创建可以通过snd_jack_new函数来完成,函数接收6个参数:
- card:ALSA声卡设备;
- id:jack设备唯一标识;
- type:由枚举类型snd_jack_type的位掩码组成,表示该jack能够上报的类型;这里我们可以理解为jack能够检测的类型,比如耳机插入/拔出;
- jack:jack指针的指针,用于保存分配的snd_jack;
- initial_kctl: 如果为真,则创建一个jack kcontrol并将其添加到jack的kctl_list链表中;
- phantom_jack:对于虚拟jack,不创建输入设备;
函数定义在sound/core/jack.c;
/**
* snd_jack_new - Create a new jack
* @card: the card instance
* @id: an identifying string for this jack
* @type: a bitmask of enum snd_jack_type values that can be detected by
* this jack
* @jjack: Used to provide the allocated jack object to the caller.
* @initial_kctl: if true, create a kcontrol and add it to the jack list.
* @phantom_jack: Don't create a input device for phantom jacks.
*
* Creates a new jack object.
*
* Return: Zero if successful, or a negative error code on failure.
* On success @jjack will be initialised.
*/
int snd_jack_new(struct snd_card *card, const char *id, int type,
struct snd_jack **jjack, bool initial_kctl, bool phantom_jack)
{
struct snd_jack *jack;
struct snd_jack_kctl *jack_kctl = NULL;
int err;
static const struct snd_device_ops ops = { // jack设备操作集
.dev_free = snd_jack_dev_free,
#ifdef CONFIG_SND_JACK_INPUT_DEV // 这个宏,默认是配置的
.dev_register = snd_jack_dev_register,
.dev_disconnect = snd_jack_dev_disconnect,
#endif /* CONFIG_SND_JACK_INPUT_DEV */
};
if (initial_kctl) { // 如果为真,动态创建一个jack kcontrol ,包含一个名称为id的kcontrol
jack_kctl = snd_jack_kctl_new(card, id, type); // jack kcontrol的mask字段设置为type
if (!jack_kctl)
return -ENOMEM;
}
jack = kzalloc(sizeof(struct snd_jack),