在接下来的几个小节中,会讲解几个音频的项目,现在想讲解第一个项目:对 耳麦拔插事件的处理,一起分为四个小节左右: 5.1 驱动程序上报耳麦拔插事件 5.2 在状态栏显示耳麦图标 5.3 耳麦拔插事件调用流程分析 5.4 切换声音通道流程
在讲解驱动程序之前,先讲解几个概念: 如上,对于耳机,有很多类型,带麦克的耳机术语叫做headset,不带麦克的术语叫做headphone:
上面是一个android系统,单需要播放声音的时候,cpu把数据信号发送给声卡,在声卡内部进行数模转换(DAC),然后通过运放进行播放,其中line out为原始数据,即还没有经过运放的数据,通过耳机运放称为headphone(如果有麦的耳机叫做headset),也可以听过音响进行运放,其约等于line out数据。。
在驱动程序中,只要支持headphone,headset以及line out这3中设备的上报就可以了。那么我们怎么上报这3中设备的拔插呢?(以下两种只能选择其中一种): 1.输入子系统:可上报输入事件,也可上报开关事件。 2.Switch State:使用UEvent系统,通过网络向应用程序上报事件。
下面是编写输入系统的流程: 1.分配input_dev结构体 2.设置a.能产生哪些事件(EV_SW)。b.能产生EV_SW中的哪些事件(如:headset,headset,Line out等等)。 3.注册input_dev 4.硬件相关:中断程序,使用input_event上报插入或者拔出事件。
编写驱动程序如下(其使用两种方法实现):
#include
#include
#include
#include
#include
#include
#include
#include
static struct input_dev *g_virtual_input;
static ssize_t input_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long code;
long val;
char *endp;
/* 如果字符串前面含有非数字, simple_strtol不能处理 */
while ((*buf == ' ') || (*buf == '\t'))
buf++;
code = simple_strtol(buf, &endp, 0);
/* 如果字符串前面含有非数字, simple_strtol不能处理 */
while ((*endp == ' ') || (*endp == '\t'))
endp++;
val = simple_strtol(endp, NULL, 0);
printk("emulate to report EV_SW: 0x%lx 0x%lx\n", code, val);
input_event(g_virtual_input, EV_SW, code, val);
input_sync(g_virtual_input);
return count;
}
static DEVICE_ATTR(test_input, S_IRUGO | S_IWUSR, NULL, input_test_store);
static int register_input_device_for_jack(void)
{
int err;
/* 分配input_dev */
g_virtual_input = input_allocate_device();
/* 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_SYN, g_virtual_input->evbit);
set_bit(EV_SW, g_virtual_input->evbit);
/* 2.2 能产生这类事件中的哪些 */
/* headset = 听筒 + MIC = SW_HEADPHONE_INSERT + SW_MICROPHONE_INSERT
* 同时上报 SW_HEADPHONE_INSERT 和 SW_MICROPHONE_INSERT, 就表示headset
* 为了简化, 对于android系统只上报SW_MICROPHONE_INSERT也表示headset
*/
set_bit(SW_HEADPHONE_INSERT, g_virtual_input->swbit);
set_bit(SW_MICROPHONE_INSERT, g_virtual_input->swbit);
set_bit(SW_LINEOUT_INSERT, g_virtual_input->swbit);
/* 2.3 这些事件的范围 */
g_virtual_input->name = "alsa_switch"; /* 不重要 */
/* 注册 */
err = input_register_device(g_virtual_input);
if (err) {
input_free_device(g_virtual_input);
printk("input_register_device for virtual jack err!\n");
return err;
}
/* 创建/sys/class/input/inputX/test_input文件
* 可以执行类似下面的命令来模拟耳麦的动作:
* 触发上报headset插入: echo 4 1 > /sys/class/input/inputX/test_input
* 触发上报headset取下: echo 4 0 > /sys/class/input/inputX/test_input
*/
err = device_create_file(&g_virtual_input->dev, &dev_attr_test_input);
if (err) {
printk("device_create_file for test_input err!\n");
input_unregister_device(g_virtual_input);
input_free_device(g_virtual_input);
return err;
}
return 0;
}
static void unregister_input_device_for_jack(void)
{
device_remove_file(&g_virtual_input->dev, &dev_attr_test_input);
input_unregister_device(g_virtual_input);
input_free_device(g_virtual_input);
}
/**************************************************************************************************************/
static struct switch_dev g_virtual_switch;
static ssize_t state_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
val = simple_strtol(buf, NULL, 0);
printk("emulate to report swtich state: 0x%lx\n", val);
switch_set_state(&g_virtual_switch, val);
return count;
}
static DEVICE_ATTR(test_state, S_IRUGO | S_IWUSR, NULL, state_test_store);
static int register_switch_device_for_jack(void)
{
int err;
g_virtual_switch.name = "h2w";
err = switch_dev_register(&g_virtual_switch);
if (err) {
printk("switch_dev_register h2w err!\n");
return err;
}
/* 创建/sys/class/switch/h2w/test_state文件
* 可以执行类似下面的命令来模拟耳麦的动作:
* 触发上报headset插入: echo 1 > /sys/class/switch/h2w/test_state
* 触发上报headset取下: echo 0 > /sys/class/switch/h2w/test_state
*/
err = device_create_file(g_virtual_switch.dev, &dev_attr_test_state);
if (err) {
printk("device_create_file test err!\n");
switch_dev_unregister(&g_virtual_switch);
return err;
}
return 0;
}
static void unregister_switch_device_for_jack(void)
{
device_remove_file(g_virtual_switch.dev, &dev_attr_test_state);
switch_dev_unregister(&g_virtual_switch);
}
/**************************************************************************************************************/
static int __init virtual_jack_init(void)
{
int err;
err = register_input_device_for_jack();
err = register_switch_device_for_jack();
return 0;
}
static void __exit virtual_jack_exit(void)
{
unregister_input_device_for_jack();
unregister_switch_device_for_jack();
}
module_init(virtual_jack_init);
module_exit(virtual_jack_exit);
MODULE_AUTHOR("weidongshan@qq.com");
MODULE_DESCRIPTION("Virutal jack driver for sound card");
MODULE_LICENSE("GPL");
/* 创建/sys/class/switch/h2w/test_state文件 * 可以执行类似下面的命令来模拟耳麦的动作: * 触发上报headset插入: echo 1 > /sys/class/switch/h2w/test_state * 触发上报headset取下: echo 0 > /sys/class/switch/h2w/test_state */
/* 创建/sys/class/input/inputX/test_input文件 * 可以执行类似下面的命令来模拟耳麦的动作: * 触发上报headset插入: echo 4 1 > /sys/class/input/inputX/test_input * 触发上报headset取下: echo 4 0 > /sys/class/input/inputX/test_input */
下面说一下编写switch dev驱动的流程: 1.分配switch dev 2.设置switch dev 3.注册 4.硬件触发 代码简化如下:
static struct switch_dev g_virtual_switch;
/*戴米安耳机的拔插*/
g_virtual_switch.name = "h2w";
err = switch_dev_register(&g_virtual_switch);
/*硬件触发,状态设置,中断上报*/
switch_set_state(&g_virtual_switch, val);
下面是switch_set_state与输入子系统能够上报事件的一些对比: 前面提到Switch State通过网络上报事件,那么我们来分析一下:
void switch_set_state(struct switch_dev *sdev, int state)
kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE);
return kobject_uevent_env(kobj, action, NULL);
/*通过网络发送数据*/
retval = netlink_broadcast_filtered(uevent_sock, skb,0, 1, GFP_KERNEL,kobj_bcast_filter,kobj);
/*sbin/hotplug APP*/
retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
如何把程序编写到内就不讲解了,下小节我们开始做实验。