注:文章都是通过阅读 Android && Linux 平台源码、各位前辈总结的资料、加上自己的思考分析总结出来的,其中难免有理不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用。图文内容、源码整理自互联网,如有侵权,请联系删除(◕‿◕) ,转载请注明出处( ©Android @Linux 版权所有),谢谢(๑乛◡乛๑) 、( ͡° ͜ʖ ͡°)、(ಡωಡ)!!!。 (==文章基于 Kernel-3.0.86 ==)&&(==文章基于 Android 5.0.2 ==)【开发板 - 友善之臂 FriendlyARM Cortex-A9 Tiny4412 ADK Exynos4412 ( Android 5.0.2)HD702高清电容屏 扩展套餐】 【开发板 Android 5.0.2 && Kernel 3.0.86 源码链接: https://pan.baidu.com/s/1jJHm74q 密码:yfih】
正是由于前人的分析和总结,帮助我节约了大量的时间和精力,特别感谢!!!
(一)、ALSA 音频系统框架 (1)、声卡节点 我们首先看看tiny4412的声卡节电:
1 2 3 4 5 6 7 root@tiny4412:/ # ls -l /dev/snd/ crw-rw---- system audio 116 , 0 2016 -01 -01 12 :00 controlC0 crw-rw---- system audio 116 , 24 2016 -01 -01 12 :00 pcmC0D0c crw-rw---- system audio 116 , 16 2016 -01 -01 12 :00 pcmC0D0p crw-rw---- system audio 116 , 25 2016 -01 -01 12 :00 pcmC0D1c crw-rw---- system audio 116 , 17 2016 -01 -01 12 :00 pcmC0D1p crw-rw---- system audio 116 , 33 2016 -01 -01 12 :00 timer
可以推断一个声卡可以有多个device,一个device有播放和录音通道。用户空间通过声卡节点访问内核空间,每个设备节点在驱动程序都有一个file_ops,上面的主设备号相同(116),对应同一个file_ops结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\sound.c static int major = CONFIG_SND_MAJOR; static const struct file_operations snd_fops ={ .owner = THIS_MODULE, .open = snd_open, .llseek = noop_llseek, }; static int __init alsa_sound_init (void ) { snd_major = major; snd_ecards_limit = cards_limit; if (register_chrdev(major, "alsa" , &snd_fops)) { snd_printk(KERN_ERR "unable to register native major device number %d\n" , major); return -EIO; } if (snd_info_init() < 0 ) { unregister_chrdev(major, "alsa" ); return -ENOMEM; } snd_info_minor_register(); #ifndef MODULE printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n" ); #endif return 0 ; } static int snd_open (struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); struct snd_minor *mptr = NULL ; const struct file_operations *old_fops ; int err = 0 ; if (minor >= ARRAY_SIZE(snd_minors)) return -ENODEV; mutex_lock(&sound_mutex); mptr = snd_minors[minor]; if (mptr == NULL ) { mptr = autoload_device(minor); if (!mptr) { mutex_unlock(&sound_mutex); return -ENODEV; } } old_fops = file->f_op; file->f_op = fops_get(mptr->f_ops); if (file->f_op == NULL ) { file->f_op = old_fops; err = -ENODEV; } mutex_unlock(&sound_mutex); if (err < 0 ) return err; if (file->f_op->open) { err = file->f_op->open(inode, file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } } fops_put(old_fops); return err; }
snd_fops 为中转作用,会根据次设备号找到具体的file_ops结构体。playback,capture都有自己的file_ops。
根据ALSA规范,事先定义好顶层结构体。APP就可以使用确定的接口访问声卡。
顶层:Sound.c snd_ops
(1)下层:controlC0 节点 snd_ctl_f_ops
1 2 3 4 5 6 7 8 9 10 11 12 13 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\control.c static const struct file_operations snd_ctl_f_ops ={ .owner = THIS_MODULE, .read = snd_ctl_read, .open = snd_ctl_open, .release = snd_ctl_release, .llseek = no_llseek, .poll = snd_ctl_poll, .unlocked_ioctl = snd_ctl_ioctl, .compat_ioctl = snd_ctl_ioctl_compat, .fasync = snd_ctl_fasync, };
(2)下层:pcmC0D0c 节点 snd_pcm_f_ops (3)下层:pcmC0D0p 节点 snd_pcm_f_ops snd_pcm_f_ops是一个数组,一个对应playback,一个对应capture。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\pcm_native.c const struct file_operations snd_pcm_f_ops [2] = { { .owner = THIS_MODULE, .write = snd_pcm_write, .aio_write = snd_pcm_aio_write, .open = snd_pcm_playback_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_playback_poll, .unlocked_ioctl = snd_pcm_playback_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, }, { .owner = THIS_MODULE, .read = snd_pcm_read, .aio_read = snd_pcm_aio_read, .open = snd_pcm_capture_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_capture_poll, .unlocked_ioctl = snd_pcm_capture_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, } };
(2)、怎么写声卡驱动? 随便找一个声卡驱动,总结怎么写声卡驱动?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \drivers\staging\tm6000\tm6000-alsa.c int tm6000_audio_init (struct tm6000_core *dev) { struct snd_card *card ; struct snd_tm6000_card *chip ; int rc; static int devnr; char component[14 ]; struct snd_pcm *pcm ; ...... rc = snd_card_create(index[devnr], "tm6000" , THIS_MODULE, 0 , &card); ...... rc = snd_pcm_new(card, "TM6000 Audio" , 0 , 0 , 1 , &pcm); ...... rc = snd_card_register(card); ...... return 0 ; }
sound/core/sound.c 实现了最顶层的file_operations,它起中转作用 sound/core/control.c 实现了控制接口的file_operations sound/core/pcm_native.c 实现了playback, capture的file_operations
这些file_operations规定了ALSA接口
实现硬件相关的代码即可: 创建、设置、注册snd_card结构体: a. 创建:snd_card_create //里面会创建控制接口 b. 设置:snd_pcm_new // 里面会创建playback, capture接口 c. 注册:snd_card_register
sound.c中确定最顶层file_ops结构体
1 2 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\sound.c register_chrdev(major, "alsa" , &snd_fops)
2.1、snd_card_create 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\init.c int snd_card_create (int idx, const char *xid, struct module *module , int extra_size, struct snd_card **card_ret) { struct snd_card *card ; int err, idx2; if (snd_BUG_ON(!card_ret)) return -EINVAL; *card_ret = NULL ; if (extra_size < 0 ) extra_size = 0 ; card = kzalloc(sizeof (*card) + extra_size, GFP_KERNEL); ...... snd_cards_lock |= 1 << idx; if (idx >= snd_ecards_limit) snd_ecards_limit = idx + 1 ; mutex_unlock(&snd_card_mutex); card->number = idx; card->module = module ; INIT_LIST_HEAD(&card->devices); init_rwsem(&card->controls_rwsem); rwlock_init(&card->ctl_files_rwlock); INIT_LIST_HEAD(&card->controls); INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); INIT_LIST_HEAD(&card->files_list); init_waitqueue_head(&card->shutdown_sleep); atomic_set(&card->refcount, 0 ); err = snd_ctl_create(card); ...... err = snd_info_card_create(card); ...... if (extra_size > 0 ) card->private_data = (char *)card + sizeof (struct snd_card); *card_ret = card; return 0 ; __error_ctl: snd_device_free_all(card, SNDRV_DEV_CMD_PRE); __error: kfree(card); return err; }
2.1.1、snd_ctl_create 创建控制接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\control.c int snd_ctl_create (struct snd_card *card) { static struct snd_device_ops ops = { .dev_free = snd_ctl_dev_free, .dev_register = snd_ctl_dev_register, .dev_disconnect = snd_ctl_dev_disconnect, }; if (snd_BUG_ON(!card)) return -ENXIO; return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); } G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\device.c int snd_device_new (struct snd_card *card, snd_device_type_t type, void *device_data, struct snd_device_ops *ops) { struct snd_device *dev ; if (snd_BUG_ON(!card || !device_data || !ops)) return -ENXIO; dev = kzalloc(sizeof (*dev), GFP_KERNEL); if (dev == NULL ) { snd_printk(KERN_ERR "Cannot allocate device\n" ); return -ENOMEM; } dev->card = card; dev->type = type; dev->state = SNDRV_DEV_BUILD; dev->device_data = device_data; dev->ops = ops; list_add(&dev->list , &card->devices); return 0 ; } G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\control.c static int snd_ctl_dev_register (struct snd_device *device) { struct snd_card *card = device ->device_data ; int err, cardnum; char name[16 ]; if (snd_BUG_ON(!card)) return -ENXIO; cardnum = card->number; if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS)) return -ENXIO; sprintf (name, "controlC%i" , cardnum); if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1 , &snd_ctl_f_ops, card, name)) < 0 ) return err; return 0 ; }
2.2、snd_pcm_new 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 int snd_pcm_new (struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm ** rpcm) { struct snd_pcm *pcm ; int err; static struct snd_device_ops ops = { .dev_free = snd_pcm_dev_free, .dev_register = snd_pcm_dev_register, .dev_disconnect = snd_pcm_dev_disconnect, }; if (snd_BUG_ON(!card)) return -ENXIO; if (rpcm) *rpcm = NULL ; pcm = kzalloc(sizeof (*pcm), GFP_KERNEL); if (pcm == NULL ) { snd_printk(KERN_ERR "Cannot allocate PCM\n" ); return -ENOMEM; } pcm->card = card; pcm->device = device; if (id) strlcpy(pcm->id, id, sizeof (pcm->id)); if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0 ) { snd_pcm_free(pcm); return err; } if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0 ) { snd_pcm_free(pcm); return err; } mutex_init(&pcm->open_mutex); init_waitqueue_head(&pcm->open_wait); if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0 ) { snd_pcm_free(pcm); return err; } if (rpcm) *rpcm = pcm; return 0 ; } static int snd_pcm_dev_register (struct snd_device *device) { int cidx, err; struct snd_pcm_substream *substream ; struct snd_pcm_notify *notify ; char str[16 ]; struct snd_pcm *pcm ; struct device *dev ; if (snd_BUG_ON(!device || !device->device_data)) return -ENXIO; pcm = device->device_data; mutex_lock(®ister_mutex); err = snd_pcm_add(pcm); if (err) { mutex_unlock(®ister_mutex); return err; } for (cidx = 0 ; cidx < 2 ; cidx++) { int devtype = -1 ; if (pcm->streams[cidx].substream == NULL ) continue ; switch (cidx) { case SNDRV_PCM_STREAM_PLAYBACK: sprintf (str, "pcmC%iD%ip" , pcm->card->number, pcm->device); devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; break ; case SNDRV_PCM_STREAM_CAPTURE: sprintf (str, "pcmC%iD%ic" , pcm->card->number, pcm->device); devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; break ; } dev = pcm->dev; if (!dev) dev = snd_card_get_device_link(pcm->card); err = snd_register_device_for_dev(devtype, pcm->card, pcm->device, &snd_pcm_f_ops[cidx], pcm, str, dev); if (err < 0 ) { list_del(&pcm->list ); mutex_unlock(®ister_mutex); return err; } snd_add_device_sysfs_file(devtype, pcm->card, pcm->device, &pcm_attrs); for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) snd_pcm_timer_init(substream); } list_for_each_entry(notify, &snd_pcm_notify_list, list ) notify->n_register(pcm); mutex_unlock(®ister_mutex); return 0 ; }
一个pcm就是一个逻辑设备device 一个pcm两个stream,playback,capture
何时注册,使用结构体?
2.3、snd_card_regsiter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\init.c int snd_card_register (struct snd_card *card) { int err; if (snd_BUG_ON(!card)) return -EINVAL; if (!card->card_dev) { card->card_dev = device_create(sound_class, card->dev, MKDEV(0 , 0 ), card, "card%i" , card->number); if (IS_ERR(card->card_dev)) card->card_dev = NULL ; } if ((err = snd_device_register_all(card)) < 0 ) return err; mutex_lock(&snd_card_mutex); if (snd_cards[card->number]) { mutex_unlock(&snd_card_mutex); return 0 ; } snd_card_set_id_no_lock(card, card->id[0 ] == '\0' ? NULL : card->id); snd_cards[card->number] = card; mutex_unlock(&snd_card_mutex); init_info_for_card(card); #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) if (snd_mixer_oss_notify_callback) snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); #endif if (card->card_dev) { err = device_create_file(card->card_dev, &card_id_attrs); if (err < 0 ) return err; err = device_create_file(card->card_dev, &card_number_attrs); if (err < 0 ) return err; } return 0 ; } int snd_device_register_all (struct snd_card *card) { struct snd_device *dev ; int err; if (snd_BUG_ON(!card)) return -ENXIO; list_for_each_entry(dev, &card->devices, list ) { if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) { if ((err = dev->ops->dev_register(dev)) < 0 ) return err; dev->state = SNDRV_DEV_REGISTERED; } } return 0 ; }
前面设置了snd_ctl_dev_register 和 snd_pcm_dev_register,dev->ops->dev_register(dev) 就会调用对应的 dev_register函数完成注册。
上面就是大概如何写一个声卡驱动程序。
(二)、ASoC音频驱动框架 2.1、ASoC音频驱动框架概括 ASoC:ALSA System on Chip,是建立在标准 ALSA 驱动之上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系,它依赖于标准 ALSA 驱动框架。内核文档 Documentation/alsa/soc/overview.txt 中详细介绍了 ASoC 的设计初衷,这里不一一引用,简单陈述如下:
• 独立的 codec 驱动,标准的 ALSA 驱动框架里面 codec 驱动往往与 SoC/CPU 耦合过于紧密,不利于在多样化的平台/机器上移植复用 • 方便 codec 与 SoC 通过 PCM/I2S 总线建立链接 • 动态音频电源管理 DAPM,使得 codec 任何时候都工作在最低功耗状态,同时负责音频路由的创建 • POPs 和 click 音抑制弱化处理,在 ASoC 中通过正确的音频部件上下电次序来实现 • Machine 驱动的特定控制,比如耳机、麦克风的插拔检测,外放功放的开关 在概述中已经介绍了 ASoC 硬件设备驱动的三大构成:Codec、Platform 和 Machine,下面列举各驱动的功能构成:
(1)SoC Codec Driver:
• Codec DAI 和 PCM 的配置信息 • Codec 的控制接口,如 I2C/SPI • Mixer 和其他音频控件 • Codec 的音频接口函数,见 snd_soc_dai_ops 结构体定义 • DAPM 描述信息 • DAPM 事件处理句柄 • DAC 数字静音控制
(2)ASoC Platform Driver: 包括 dma 和 cpu_dai 两部分:
• dma 驱动实现音频 dma 操作,具体见 snd_pcm_ops 结构体定义 • cpu_dai 驱动实现音频数字接口控制器的描述和配置
(3)ASoC Machine Driver:
作为链结 Platform 和 Codec 的载体,它必须配置 dai_link 为音频数据链路指定 Platform 和 Codec 处理机器特有的音频控件和音频事件,例如回放时打开外放功放 硬件设备驱动相关结构体:
• snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息、时钟配置、IO 控制等 • snd_soc_dai_driver:音频数据接口描述及操作函数,根据 codec 端和 soc 端,分为 codec_dai 和 cpu_dai • snd_soc_platform_driver:音频 dma 设备描述及操作函数 • snd_soc_dai_link:音频链路描述及板级操作函数
2.2、ASoC音频驱动框架图
2.3、ASoC音频驱动代码分析 2.3.1、SoC Codec Driver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { .probe = wm8960_probe, .remove = wm8960_remove, .suspend = wm8960_suspend, .resume = wm8960_resume, .set_bias_level = wm8960_set_bias_level, .reg_cache_size = ARRAY_SIZE(wm8960_reg), .reg_word_size = sizeof (u16), .reg_cache_default = wm8960_reg, }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) static __devinit int wm8960_i2c_probe (struct i2c_client *i2c, const struct i2c_device_id *id) { struct wm8960_priv *wm8960 ; int ret; wm8960 = kzalloc(sizeof (struct wm8960_priv), GFP_KERNEL); if (wm8960 == NULL ) return -ENOMEM; i2c_set_clientdata(i2c, wm8960); wm8960->control_type = SND_SOC_I2C; wm8960->control_data = i2c; ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8960, &wm8960_dai, 1 ); if (ret < 0 ) kfree(wm8960); return ret; } static const struct i2c_device_id wm8960_i2c_id [] = { { "wm8960" , 0 }, { } }; MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); static struct i2c_driver wm8960_i2c_driver = { .driver = { .name = "wm8960-codec" , .owner = THIS_MODULE, }, .probe = wm8960_i2c_probe, .remove = __devexit_p(wm8960_i2c_remove), .id_table = wm8960_i2c_id, }; #endif static int __init wm8960_modinit (void ) { int ret = 0 ; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&wm8960_i2c_driver); if (ret != 0 ) { printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n" , ret); } #endif return ret; } module_init(wm8960_modinit);
(1)dma部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\samsung\dma-wrapper.c static struct snd_pcm_ops asoc_platform_ops = { .open = asoc_platform_open, .close = asoc_platform_close, .ioctl = asoc_platform_ioctl, .hw_params = asoc_platform_hw_params, .hw_free = asoc_platform_hw_free, .prepare = asoc_platform_prepare, .trigger = asoc_platform_trigger, .pointer = asoc_platform_pointer, .mmap = asoc_platform_mmap, }; static void asoc_platform_free_dma_buffers (struct snd_pcm *pcm) { struct snd_soc_platform_driver *platform = asoc_get_platform (pcm ); if (platform->pcm_free) platform->pcm_free(pcm); } static int asoc_platform_new (struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { struct snd_soc_platform_driver *platform = asoc_get_platform (pcm ); if (platform->pcm_new) platform->pcm_new(card, dai, pcm); return 0 ; } static struct snd_soc_platform_driver asoc_dma_platform = { .ops = &asoc_platform_ops, .pcm_new = asoc_platform_new, .pcm_free = asoc_platform_free_dma_buffers, }; static int __devinit samsung_asoc_platform_probe (struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &asoc_dma_platform); } static int __devexit samsung_asoc_platform_remove (struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0 ; } static struct platform_driver asoc_platform_driver = { .driver = { .name = "samsung-audio" , .owner = THIS_MODULE, }, .probe = samsung_asoc_platform_probe, .remove = __devexit_p(samsung_asoc_platform_remove), }; static int __init samsung_asoc_init (void ) { return platform_driver_register(&asoc_platform_driver); } module_init(samsung_asoc_init);
(2)cpu_dai部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\samsung\i2s.c static __devinit int samsung_i2s_probe (struct platform_device *pdev) { u32 dma_pl_chan, dma_cp_chan, dma_pl_sec_chan; struct i2s_dai *pri_dai , *sec_dai = NULL ; struct s3c_audio_pdata *i2s_pdata ; struct samsung_i2s *i2s_cfg ; struct resource *res ; u32 regs_base, quirks; int ret = 0 ; if (pdev->id >= SAMSUNG_I2S_SECOFF) { sec_dai = dev_get_drvdata(&pdev->dev); snd_soc_register_dai(&sec_dai->pdev->dev, &sec_dai->i2s_dai_drv); return 0 ; } i2s_pdata = pdev->dev.platform_data; res = platform_get_resource(pdev, IORESOURCE_DMA, 0 ); dma_pl_chan = res->start; res = platform_get_resource(pdev, IORESOURCE_DMA, 1 ); dma_cp_chan = res->start; res = platform_get_resource(pdev, IORESOURCE_DMA, 2 ); if (res) dma_pl_sec_chan = res->start; else dma_pl_sec_chan = 0 ; res = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); ...... regs_base = res->start; i2s_cfg = &i2s_pdata->type.i2s; quirks = i2s_cfg->quirks; pri_dai = i2s_alloc_dai(pdev, false ); pri_dai->dma_playback.dma_addr = regs_base + I2STXD; pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; pri_dai->dma_playback.client = (struct s3c2410_dma_client *)&pri_dai->dma_playback; pri_dai->dma_capture.client = (struct s3c2410_dma_client *)&pri_dai->dma_capture; pri_dai->dma_playback.channel = dma_pl_chan; pri_dai->dma_capture.channel = dma_cp_chan; pri_dai->src_clk = i2s_cfg->src_clk; pri_dai->dma_playback.dma_size = 4 ; pri_dai->dma_capture.dma_size = 4 ; pri_dai->base = regs_base; pri_dai->quirks = quirks; if (pdev->id == 0 ) { pri_dai->audss_clk_enable = audss_clk_enable; pri_dai->audss_suspend = audss_suspend; pri_dai->audss_resume = audss_resume; } if (quirks & QUIRK_PRI_6CHAN) pri_dai->i2s_dai_drv.playback.channels_max = 6 ; if (quirks & QUIRK_SEC_DAI) { sec_dai = i2s_alloc_dai(pdev, true ); if (!sec_dai) { dev_err(&pdev->dev, "Unable to alloc I2S_sec\n" ); ret = -ENOMEM; goto err2; } sec_dai->dma_playback.dma_addr = regs_base + I2STXDS; sec_dai->dma_playback.client = (struct s3c2410_dma_client *)&sec_dai->dma_playback; sec_dai->dma_playback.channel = dma_pl_sec_chan ? : -1 ; sec_dai->src_clk = i2s_cfg->src_clk; sec_dai->dma_playback.dma_size = 4 ; sec_dai->base = regs_base; sec_dai->quirks = quirks; sec_dai->pri_dai = pri_dai; pri_dai->sec_dai = sec_dai; if (pdev->id == 0 ) { sec_dai->audss_clk_enable = audss_clk_enable; sec_dai->audss_suspend = audss_suspend; sec_dai->audss_resume = audss_resume; } } ...... snd_soc_register_dai(&pri_dai->pdev->dev, &pri_dai->i2s_dai_drv); return 0 ; ...... return ret; } static struct platform_driver samsung_i2s_driver = { .probe = samsung_i2s_probe, .remove = samsung_i2s_remove, .driver = { .name = "samsung-i2s" , .owner = THIS_MODULE, }, }; static int __init samsung_i2s_init (void ) { return platform_driver_register(&samsung_i2s_driver); } module_init(samsung_i2s_init);
2.3.3、ASoC Machine Driver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\samsung\tiny4412_wm8960.c static struct snd_soc_dai_link s3c2440_uda1341_dai_link = { .name = "100ask_UDA1341" , .stream_name = "100ask_UDA1341" , .codec_name = "wm8960-codec.0-001a" , .codec_dai_name = "wm8960-hifi" , .cpu_dai_name = "samsung-i2s.0" , .ops = &s3c2440_uda1341_ops, .platform_name = "samsung-audio" , .init = tiny4412_wm8960_machine_init, }; static struct snd_soc_card myalsa_card = { .name = "S3C2440_UDA1341" , .owner = THIS_MODULE, .dai_link = &s3c2440_uda1341_dai_link, .num_links = 1 , }; static void asoc_release (struct device * dev) {} static struct platform_device asoc_dev = { .name = "soc-audio" , .id = -1 , .dev = { .release = asoc_release, }, }; static int s3c2440_uda1341_init (void ) { platform_set_drvdata(&asoc_dev, &myalsa_card); platform_device_register(&asoc_dev); return 0 ; }
2.4、ASoC音频驱动代码分析 latform_device 还有个好伙伴 platform_driver 跟它配对。而 .name=”soc-audio” 的 platform_driver 定义在 soc-core.c 中: 两者匹配后,soc_probe() 会被调用,继而调用 snd_soc_register_card() 注册声卡。由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,流程图如下:
取出 platform_device 的私有数据,该私有数据就是 snd_soc_card ;
snd_soc_register_card() 为每个 dai_link 分配一个 snd_soc_pcm_runtime 实例,别忘了之前提过 snd_soc_pcm_runtime 是 ASoC 的桥梁,保存着 codec、codec_dai、cpu_dai、platform 等硬件设备实例。
随后的工作都在 snd_soc_instantiate_card() 进行:
遍历 dai_list、codec_list、platform_list 链表,为每个音频链路找到对应的 cpu_dai、codec_dai、codec、platform;找到的 cpu_dai、codec_dai、codec、platform 保存到 snd_soc_pcm_runtime ,完成音频链路的设备绑定;
调用 snd_card_create() 创建声卡;
soc_probe_dai_link() 依次回调 cpu_dai、codec、platform、codec_dai 的 probe() 函数,完成各音频设备的初始化,随后调用 soc_new_pcm() 创建 pcm 逻辑设备(因为涉及到本系列的重点内容,后面具体分析这个函数);
最后调用 snd_card_register() 注册声卡。 soc_new_pcm 源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 int soc_new_pcm (struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd ->codec ; struct snd_soc_platform *platform = rtd ->platform ; struct snd_soc_dai *codec_dai = rtd ->codec_dai ; struct snd_soc_dai *cpu_dai = rtd ->cpu_dai ; struct snd_pcm_ops *soc_pcm_ops = &rtd ->ops ; struct snd_pcm *pcm ; char new_name[64 ]; int ret = 0 , playback = 0 , capture = 0 ; soc_pcm_ops->open = soc_pcm_open; soc_pcm_ops->close = soc_pcm_close; soc_pcm_ops->hw_params = soc_pcm_hw_params; soc_pcm_ops->hw_free = soc_pcm_hw_free; soc_pcm_ops->prepare = soc_pcm_prepare; soc_pcm_ops->trigger = soc_pcm_trigger; soc_pcm_ops->pointer = soc_pcm_pointer; snprintf (new_name, sizeof (new_name), "%s %s-%d" , rtd->dai_link->stream_name, codec_dai->name, num); if (codec_dai->driver->playback.channels_min) playback = 1 ; if (codec_dai->driver->capture.channels_min) capture = 1 ; ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm); if (ret < 0 ) { printk(KERN_ERR "asoc: can't create pcm for codec %s\n" , codec->name); return ret; } INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); rtd->pcm = pcm; pcm->private_data = rtd; if (platform->driver->ops) { soc_pcm_ops->mmap = platform->driver->ops->mmap; soc_pcm_ops->pointer = platform->driver->ops->pointer; soc_pcm_ops->ioctl = platform->driver->ops->ioctl; soc_pcm_ops->copy = platform->driver->ops->copy; soc_pcm_ops->silence = platform->driver->ops->silence; soc_pcm_ops->ack = platform->driver->ops->ack; soc_pcm_ops->page = platform->driver->ops->page; } if (playback) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); if (capture) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops); if (platform->driver->pcm_new) { ret = platform->driver->pcm_new(rtd); if (ret < 0 ) { pr_err("asoc: platform pcm constructor failed\n" ); return ret; } } pcm->private_free = platform->driver->pcm_free; printk(KERN_INFO "asoc: %s <-> %s mapping ok\n" , codec_dai->name, cpu_dai->name); return ret; }
可见 soc_new_pcm() 最主要的工作是创建 pcm 逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的 pcm 操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。
(三)、 声卡控制之kcontrol (1)、snd_kcontrol结构体 首先看看录音电路原理图:
我们对着mic说话,声音会经过一系列线路进入声卡芯片的LINPUT1。看看声卡芯片wm8960
LINPUT1 经过一系列部件经过ADC,对于这些部件我们需要设置相应的寄存器来启动他们,让声音转换成数字信号。 怎么设置这些寄存器呢?有没有统一的接口来设置他们?既是Kcontrol。
学习Kcontrol之前我们先来想想:
一个芯片有多个寄存器 一个寄存器某些位表示某个功能 kcontrol有自己的读写函数
一个声卡有多个kcontrol 一个kcontrol对应一个功能,比如:音量,开关声音。 kcontrol有函数来设置功能
(2)、tinymix 设置kcontrol 可以看到有50个kcontrol,我们可以使用tinymix 来设置kcontrol tinymix “Left Input Mixer Boost Switch” 1 tinymix “Capture Switch” 0
(3)、snd_soc_add_controls 首先看看snd_soc_add_controls
1 2 3 4 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c snd_soc_add_controls(codec, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\soc-core.c int snd_soc_add_controls (struct snd_soc_codec *codec, const struct snd_kcontrol_new *controls, int num_controls) { struct snd_card *card = codec ->card ->snd_card ; int err, i; for (i = 0 ; i < num_controls; i++) { const struct snd_kcontrol_new *control = &controls [i ]; err = snd_ctl_add(card, snd_soc_cnew(control, codec, control->name, codec->name_prefix)); if (err < 0 ) { dev_err(codec->dev, "%s: Failed to add %s: %d\n" , codec->name, control->name, err); return err; } } return 0 ; }
snd_kcontrol 的值由谁提供。。?由对应芯片驱动程序提供,如WM8960芯片:
1 2 3 4 5 6 7 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c static const struct snd_kcontrol_new wm8960_lin_boost [] = {SOC_DAPM_SINGLE("LINPUT2 Switch" , WM8960_LINPATH, 6 , 1 , 0 ), SOC_DAPM_SINGLE("LINPUT3 Switch" , WM8960_LINPATH, 7 , 1 , 0 ), SOC_DAPM_SINGLE("LINPUT1 Switch" , WM8960_LINPATH, 8 , 1 , 0 ), };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\soc-core.c struct snd_kcontrol *snd_soc_cnew (const struct snd_kcontrol_new *_template, void *data, char *long_name, const char *prefix) { struct snd_kcontrol_new template ; struct snd_kcontrol *kcontrol ; char *name = NULL ; int name_len; memcpy (&template , _template, sizeof (template )); template .index = 0 ; if (!long_name) long_name = template .name; if (prefix) { name_len = strlen (long_name) + strlen (prefix) + 2 ; name = kmalloc(name_len, GFP_ATOMIC); if (!name) return NULL ; snprintf (name, name_len, "%s %s" , prefix, long_name); template .name = name; } else { template .name = long_name; } kcontrol = snd_ctl_new1(&template , data); kfree(name); return kcontrol; } G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\core\control.c struct snd_kcontrol *snd_ctl_new1 (const struct snd_kcontrol_new *ncontrol, void *private_data) { struct snd_kcontrol kctl ; unsigned int access; if (snd_BUG_ON(!ncontrol || !ncontrol->info)) return NULL ; memset (&kctl, 0 , sizeof (kctl)); kctl.id.iface = ncontrol->iface; kctl.id.device = ncontrol->device; kctl.id.subdevice = ncontrol->subdevice; if (ncontrol->name) { strlcpy(kctl.id.name, ncontrol->name, sizeof (kctl.id.name)); if (strcmp (ncontrol->name, kctl.id.name) != 0 ) snd_printk(KERN_WARNING "Control name '%s' truncated to '%s'\n" , ncontrol->name, kctl.id.name); } kctl.id.index = ncontrol->index; kctl.count = ncontrol->count ? ncontrol->count : 1 ; access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| SNDRV_CTL_ELEM_ACCESS_INACTIVE| SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE| SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND| SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)); kctl.info = ncontrol->info; kctl.get = ncontrol->get; kctl.put = ncontrol->put; kctl.tlv.p = ncontrol->tlv.p; kctl.private_value = ncontrol->private_value; kctl.private_data = private_data; return snd_ctl_new(&kctl, access); } static struct snd_kcontrol *snd_ctl_new (struct snd_kcontrol *control, unsigned int access) { struct snd_kcontrol *kctl ; unsigned int idx; if (snd_BUG_ON(!control || !control->count)) return NULL ; if (control->count > MAX_CONTROL_COUNT) return NULL ; kctl = kzalloc(sizeof (*kctl) + sizeof (struct snd_kcontrol_volatile) * control->count, GFP_KERNEL); if (kctl == NULL ) { snd_printk(KERN_ERR "Cannot allocate control instance\n" ); return NULL ; } *kctl = *control; for (idx = 0 ; idx < kctl->count; idx++) kctl->vd[idx].access = access; return kctl; }
概念:Dynamic Audio Power Management,动态音频电源管理,为移动 Linux 设备设计,使得音频系统任何时候都工作在最低功耗状态。
目的:使能最少的必要的部件,令音频系统正常工作。
原理:当音频路径发生改变(比如上层使用 tinymix 工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发 dapm 去遍历所有邻近的音频部件,检查是否存在完整的音频路径(complete path:满足条件的音频路径,该路径上任意一个部件往前遍历能到达输入端点如 DAC/Mic/Linein,往后遍历能到达输出端点如 ADC/HP/SPK),如果存在完整的音频路径,则该路径上面的所有部件都是需要上电的,其他部件则下电。
部件上下电都是 dapm 根据策略自主控制的,外部无法干预,可以说 dapm 是一个专门为音频系统设计的自成体系的电源管理模块,独立于 Linux 电源管理之外。即使 SoC 休眠了,Codec 仍可以在正常工作,试想下这个情景:语音通话,modem_dai 连接到 codec_dai,语音数据不经过 SoC,因此这种情形下 SoC 可以进入睡眠以降低功耗,只保持 Codec 正常工作就行了。
在这个例子中,codec 中的音频通路是:LINPUT1>LEFT Boost Mixer>ADC;
Mixer有多个输入源,只要其中的某个开关使能,顺便把其他三个也使能
(五)、DAPM的kcontrol注册过程
(1)、snd_soc_dapm_new_controls 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\soc-dapm.c int snd_soc_dapm_new_controls (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { int i, ret; for (i = 0 ; i < num; i++) { ret = snd_soc_dapm_new_control(dapm, widget); if (ret < 0 ) { dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s: %d\n" , widget->name, ret); return ret; } widget++; } return 0 ; } int snd_soc_dapm_new_control (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget) { struct snd_soc_dapm_widget *w ; size_t name_len; if ((w = dapm_cnew_widget(widget)) == NULL ) return -ENOMEM; name_len = strlen (widget->name) + 1 ; if (dapm->codec && dapm->codec->name_prefix) name_len += 1 + strlen (dapm->codec->name_prefix); w->name = kmalloc(name_len, GFP_KERNEL); if (w->name == NULL ) { kfree(w); return -ENOMEM; } if (dapm->codec && dapm->codec->name_prefix) snprintf (w->name, name_len, "%s %s" , dapm->codec->name_prefix, widget->name); else snprintf (w->name, name_len, "%s" , widget->name); dapm->n_widgets++; w->dapm = dapm; w->codec = dapm->codec; INIT_LIST_HEAD(&w->sources); INIT_LIST_HEAD(&w->sinks); INIT_LIST_HEAD(&w->list ); list_add(&w->list , &dapm->card->widgets); w->connected = 1 ; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 int snd_soc_dapm_new_widgets (struct snd_soc_dapm_context *dapm) { struct snd_soc_dapm_widget *w ; unsigned int val; list_for_each_entry(w, &dapm->card->widgets, list ) { if (w->new ) continue ; if (w->num_kcontrols) { w->kcontrols = kzalloc(w->num_kcontrols * sizeof (struct snd_kcontrol *), GFP_KERNEL); if (!w->kcontrols) return -ENOMEM; } switch (w->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: w->power_check = dapm_generic_check_power; dapm_new_mixer(w); break ; case snd_soc_dapm_mux: case snd_soc_dapm_virt_mux: case snd_soc_dapm_value_mux: w->power_check = dapm_generic_check_power; dapm_new_mux(w); break ; case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: w->power_check = dapm_adc_check_power; break ; case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: w->power_check = dapm_dac_check_power; break ; case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: w->power_check = dapm_generic_check_power; dapm_new_pga(w); break ; case snd_soc_dapm_input: case snd_soc_dapm_output: case snd_soc_dapm_micbias: case snd_soc_dapm_spk: case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: w->power_check = dapm_generic_check_power; break ; case snd_soc_dapm_supply: w->power_check = dapm_supply_check_power; case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: break ; } if (w->reg >= 0 ) { val = snd_soc_read(w->codec, w->reg); val &= 1 << w->shift; if (w->invert) val = !val; if (val) w->power = 1 ; } w->new = 1 ; dapm_debugfs_add_widget(w); } dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP); return 0 ; }
a. 对于普通的snd_kcontrol: snd_soc_add_controls : snd_kcontrol_new构造出snd_kcontrol, 放入card->controls链表
b. 对于DAPM的snd_kcontrol, 分2步: b.1 snd_soc_dapm_new_controls // 把widget放入card->widgets链表 b.2 在注册machine驱动时, 导致如下调用: soc_probe_dai_link > soc_post_component_init > snd_soc_dapm_new_widgets
snd_soc_dapm_new_widgets: 对于每一个widget, 设置它的power_check函数(用来判断该widget是否应该上电) 对于mixer widget, 取出其中的snd_kcontrol_new构造出snd_kcontrol, 放入card->controls链表 对于mux widget,它只有一个snd_kcontrol_new, 构造出snd_kcontrol, 放入card->controls链表
(六)、route_path添加过程分析 (1)、snd_soc_dapm_add_routes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c static const struct snd_soc_dapm_route audio_paths [] = { { "Left Boost Mixer" , "LINPUT1 Switch" , "LINPUT1" }, { "Left Boost Mixer" , "LINPUT2 Switch" , "LINPUT2" }, { "Left Boost Mixer" , "LINPUT3 Switch" , "LINPUT3" }, { "Left Input Mixer" , "Boost Switch" , "Left Boost Mixer" , }, { "Left Input Mixer" , NULL , "LINPUT1" , }, { "Left Input Mixer" , NULL , "LINPUT2" }, { "Left Input Mixer" , NULL , "LINPUT3" }, { "Right Boost Mixer" , "RINPUT1 Switch" , "RINPUT1" }, { "Right Boost Mixer" , "RINPUT2 Switch" , "RINPUT2" }, { "Right Boost Mixer" , "RINPUT3 Switch" , "RINPUT3" }, { "Right Input Mixer" , "Boost Switch" , "Right Boost Mixer" , }, { "Right Input Mixer" , NULL , "RINPUT1" , }, { "Right Input Mixer" , NULL , "RINPUT2" }, { "Right Input Mixer" , NULL , "LINPUT3" }, { "Left ADC" , NULL , "Left Input Mixer" }, { "Right ADC" , NULL , "Right Input Mixer" }, ...... } int snd_soc_dapm_add_routes (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_route *route, int num) { int i, ret; for (i = 0 ; i < num; i++) { ret = snd_soc_dapm_add_route(dapm, route); if (ret < 0 ) { dev_err(dapm->dev, "Failed to add route %s->%s\n" , route->source, route->sink); return ret; } route++; } return 0 ; } static int snd_soc_dapm_add_route (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_route *route) { struct snd_soc_dapm_path *path ; struct snd_soc_dapm_widget *wsource = NULL , *wsink = NULL , *w ; struct snd_soc_dapm_widget *wtsource = NULL , *wtsink = NULL ; const char *sink; const char *control = route->control; const char *source; char prefixed_sink[80 ]; char prefixed_source[80 ]; int ret = 0 ; if (dapm->codec && dapm->codec->name_prefix) { snprintf (prefixed_sink, sizeof (prefixed_sink), "%s %s" , dapm->codec->name_prefix, route->sink); sink = prefixed_sink; snprintf (prefixed_source, sizeof (prefixed_source), "%s %s" , dapm->codec->name_prefix, route->source); source = prefixed_source; } else { sink = route->sink; source = route->source; } list_for_each_entry(w, &dapm->card->widgets, list ) { if (!wsink && !(strcmp (w->name, sink))) { wtsink = w; if (w->dapm == dapm) wsink = w; continue ; } if (!wsource && !(strcmp (w->name, source))) { wtsource = w; if (w->dapm == dapm) wsource = w; } } if (!wsink) wsink = wtsink; if (!wsource) wsource = wtsource; if (wsource == NULL || wsink == NULL ) return -ENODEV; path = kzalloc(sizeof (struct snd_soc_dapm_path), GFP_KERNEL); if (!path) return -ENOMEM; path->source = wsource; path->sink = wsink; path->connected = route->connected; INIT_LIST_HEAD(&path->list ); INIT_LIST_HEAD(&path->list_source); INIT_LIST_HEAD(&path->list_sink); if (wsink->id == snd_soc_dapm_input) { if (wsource->id == snd_soc_dapm_micbias || wsource->id == snd_soc_dapm_mic || wsource->id == snd_soc_dapm_line || wsource->id == snd_soc_dapm_output) wsink->ext = 1 ; } if (wsource->id == snd_soc_dapm_output) { if (wsink->id == snd_soc_dapm_spk || wsink->id == snd_soc_dapm_hp || wsink->id == snd_soc_dapm_line || wsink->id == snd_soc_dapm_input) wsource->ext = 1 ; } if (control == NULL ) { list_add(&path->list , &dapm->card->paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); path->connect = 1 ; return 0 ; } switch (wsink->id) { case snd_soc_dapm_adc: case snd_soc_dapm_dac: case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: case snd_soc_dapm_input: case snd_soc_dapm_output: case snd_soc_dapm_micbias: case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: case snd_soc_dapm_supply: case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: list_add(&path->list , &dapm->card->paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); path->connect = 1 ; return 0 ; case snd_soc_dapm_mux: case snd_soc_dapm_virt_mux: case snd_soc_dapm_value_mux: ret = dapm_connect_mux(dapm, wsource, wsink, path, control, &wsink->kcontrol_news[0 ]); if (ret != 0 ) goto err; break ; case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: ret = dapm_connect_mixer(dapm, wsource, wsink, path, control); if (ret != 0 ) goto err; break ; case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: case snd_soc_dapm_spk: list_add(&path->list , &dapm->card->paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); path->connect = 0 ; return 0 ; } return 0 ; err: dev_warn(dapm->dev, "asoc: no dapm match for %s --> %s --> %s\n" , source, control, sink); kfree(path); return ret; }
(2)、route分类 2.1、常规route
{“sink”,NULL,”source”} path->connect = 1
{“Mux”,name1,”source1”} {“Mux”,name2,”source2”} (name1,name2)snd_kcontrol_new的名,可以通过操作某个kcontrol来打开某条path
{“Mux”,value1,”source1”} {“Mux”,value1,”source2”}
(七)、DAPM的情景分析_构造过程 a. kcontrol,route,path注册过程情景演示
(1)、普通的kcontrol 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\soc-core.c int snd_soc_add_controls (struct snd_soc_codec *codec, const struct snd_kcontrol_new *controls, int num_controls) { struct snd_card *card = codec ->card ->snd_card ; int err, i; for (i = 0 ; i < num_controls; i++) { const struct snd_kcontrol_new *control = &controls [i ]; err = snd_ctl_add(card, snd_soc_cnew(control, codec, control->name, codec->name_prefix)); if (err < 0 ) { dev_err(codec->dev, "%s: Failed to add %s: %d\n" , codec->name, control->name, err); return err; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c static int wm8960_add_widgets (struct snd_soc_codec *codec) { struct wm8960_data *pdata = codec ->dev ->platform_data ; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata (codec ); struct snd_soc_dapm_context *dapm = &codec ->dapm ; struct snd_soc_dapm_widget *w ; snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets)); snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); if (pdata && pdata->capless) { snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, ARRAY_SIZE(wm8960_dapm_widgets_capless)); snd_soc_dapm_add_routes(dapm, audio_paths_capless, ARRAY_SIZE(audio_paths_capless)); } else { snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, ARRAY_SIZE(wm8960_dapm_widgets_out3)); snd_soc_dapm_add_routes(dapm, audio_paths_out3, ARRAY_SIZE(audio_paths_out3)); } list_for_each_entry(w, &codec->card->widgets, list ) { if (w->dapm != &codec->dapm) continue ; if (strcmp (w->name, "LOUT1 PGA" ) == 0 ) wm8960->lout1 = w; if (strcmp (w->name, "ROUT1 PGA" ) == 0 ) wm8960->rout1 = w; if (strcmp (w->name, "OUT3 VMID" ) == 0 ) wm8960->out3 = w; } return 0 ; } G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\soc-core.c int snd_soc_dapm_new_controls (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { int i, ret; for (i = 0 ; i < num; i++) { ret = snd_soc_dapm_new_control(dapm, widget); if (ret < 0 ) { dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s: %d\n" , widget->name, ret); return ret; } widget++; } return 0 ; }
(3)、route=>path注册 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 G:\Android-5.0 .2 \linux-3.0 .86 -20170221 \linux-3.0 .86 \sound\soc\codecs\wm8960.c static const struct snd_soc_dapm_route audio_paths [] = { { "Left Boost Mixer" , "LINPUT1 Switch" , "LINPUT1" }, { "Left Boost Mixer" , "LINPUT2 Switch" , "LINPUT2" }, { "Left Boost Mixer" , "LINPUT3 Switch" , "LINPUT3" }, { "Left Input Mixer" , "Boost Switch" , "Left Boost Mixer" , }, { "Left Input Mixer" , NULL , "LINPUT1" , }, { "Left Input Mixer" , NULL , "LINPUT2" }, { "Left Input Mixer" , NULL , "LINPUT3" }, { "Right Boost Mixer" , "RINPUT1 Switch" , "RINPUT1" }, { "Right Boost Mixer" , "RINPUT2 Switch" , "RINPUT2" }, { "Right Boost Mixer" , "RINPUT3 Switch" , "RINPUT3" }, { "Right Input Mixer" , "Boost Switch" , "Right Boost Mixer" , }, { "Right Input Mixer" , NULL , "RINPUT1" , }, { "Right Input Mixer" , NULL , "RINPUT2" }, { "Right Input Mixer" , NULL , "LINPUT3" }, { "Left ADC" , NULL , "Left Input Mixer" }, { "Right ADC" , NULL , "Right Input Mixer" }, { "Left Output Mixer" , "LINPUT3 Switch" , "LINPUT3" }, { "Left Output Mixer" , "Boost Bypass Switch" , "Left Boost Mixer" } , { "Left Output Mixer" , "PCM Playback Switch" , "Left DAC" }, { "Right Output Mixer" , "RINPUT3 Switch" , "RINPUT3" }, { "Right Output Mixer" , "Boost Bypass Switch" , "Right Boost Mixer" } , { "Right Output Mixer" , "PCM Playback Switch" , "Right DAC" }, { "LOUT1 PGA" , NULL , "Left Output Mixer" }, { "ROUT1 PGA" , NULL , "Right Output Mixer" }, { "HP_L" , NULL , "LOUT1 PGA" }, { "HP_R" , NULL , "ROUT1 PGA" }, { "Left Speaker PGA" , NULL , "Left Output Mixer" }, { "Right Speaker PGA" , NULL , "Right Output Mixer" }, { "Left Speaker Output" , NULL , "Left Speaker PGA" }, { "Right Speaker Output" , NULL , "Right Speaker PGA" }, { "SPK_LN" , NULL , "Left Speaker Output" }, { "SPK_LP" , NULL , "Left Speaker Output" }, { "SPK_RN" , NULL , "Right Speaker Output" }, { "SPK_RP" , NULL , "Right Speaker Output" }, };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static const struct snd_soc_dapm_widget wm8960_dapm_widgets [] = {SND_SOC_DAPM_INPUT("LINPUT1" ), SND_SOC_DAPM_INPUT("RINPUT1" ), SND_SOC_DAPM_INPUT("LINPUT2" ), SND_SOC_DAPM_INPUT("RINPUT2" ), SND_SOC_DAPM_INPUT("LINPUT3" ), SND_SOC_DAPM_INPUT("RINPUT3" ), SND_SOC_DAPM_MICBIAS("MICB" , WM8960_POWER1, 1 , 0 ), SND_SOC_DAPM_MIXER("Left Boost Mixer" , WM8960_POWER1, 5 , 0 , wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), SND_SOC_DAPM_MIXER("Right Boost Mixer" , WM8960_POWER1, 4 , 0 , wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), SND_SOC_DAPM_MIXER("Left Input Mixer" , WM8960_POWER3, 5 , 0 , wm8960_lin, ARRAY_SIZE(wm8960_lin)), SND_SOC_DAPM_MIXER("Right Input Mixer" , WM8960_POWER3, 4 , 0 , wm8960_rin, ARRAY_SIZE(wm8960_rin)), SND_SOC_DAPM_ADC("Left ADC" , "Capture" , WM8960_POWER2, 3 , 0 ), SND_SOC_DAPM_ADC("Right ADC" , "Capture" , WM8960_POWER2, 2 , 0 ), SND_SOC_DAPM_DAC("Left DAC" , "Playback" , WM8960_POWER2, 8 , 0 ), SND_SOC_DAPM_DAC("Right DAC" , "Playback" , WM8960_POWER2, 7 , 0 ), SND_SOC_DAPM_MIXER("Left Output Mixer" , WM8960_POWER3, 3 , 0 , &wm8960_loutput_mixer[0 ], ARRAY_SIZE(wm8960_loutput_mixer)), SND_SOC_DAPM_MIXER("Right Output Mixer" , WM8960_POWER3, 2 , 0 , &wm8960_routput_mixer[0 ], ARRAY_SIZE(wm8960_routput_mixer)), SND_SOC_DAPM_PGA("LOUT1 PGA" , WM8960_POWER2, 6 , 0 , NULL , 0 ), SND_SOC_DAPM_PGA("ROUT1 PGA" , WM8960_POWER2, 5 , 0 , NULL , 0 ), SND_SOC_DAPM_PGA("Left Speaker PGA" , WM8960_POWER2, 4 , 0 , NULL , 0 ), SND_SOC_DAPM_PGA("Right Speaker PGA" , WM8960_POWER2, 3 , 0 , NULL , 0 ), SND_SOC_DAPM_PGA("Right Speaker Output" , WM8960_CLASSD1, 7 , 0 , NULL , 0 ), SND_SOC_DAPM_PGA("Left Speaker Output" , WM8960_CLASSD1, 6 , 0 , NULL , 0 ), SND_SOC_DAPM_OUTPUT("SPK_LP" ), SND_SOC_DAPM_OUTPUT("SPK_LN" ), SND_SOC_DAPM_OUTPUT("HP_L" ), SND_SOC_DAPM_OUTPUT("HP_R" ), SND_SOC_DAPM_OUTPUT("SPK_RP" ), SND_SOC_DAPM_OUTPUT("SPK_RN" ), SND_SOC_DAPM_OUTPUT("OUT3" ), }; int snd_soc_dapm_new_controls (struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { int i, ret; for (i = 0 ; i < num; i++) { ret = snd_soc_dapm_new_control(dapm, widget); if (ret < 0 ) { dev_err(dapm->dev, "ASoC: Failed to create DAPM control %s: %d\n" , widget->name, ret); return ret; } widget++; } return 0 ; }
(八)、DAPM的情景分析_使用过程 a. tinymix,tinyplay,tinycap殊途同归,都会调用dapm_power_widgets b. dapm的核心: complete path 某个widget是否要上电的判断过程
(1)、tinymix调用过程 以SOC_DAPM_SINGLE(“LINPUT1 Switch”, WM8960_LINPATH, 8, 1, 0)为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 snd_soc_dapm_put_volsw connect = xxx update.kcontrol = kcontrol; update.widget = widget; update.reg = reg; update.mask = mask; update.val = val; widget->dapm->update = &update; dapm_mixer_update_power(widget, kcontrol, connect); path->connect = connect; dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP); widget->dapm->update = NULL ;
(2)、tinyplay,tinycap调用过程 播放/录音前都会调用 soc_pcm_prepare
1 2 3 4 5 6 7 8 9 soc_pcm_prepare snd_soc_dapm_stream_event(rtd, stream' s name, SND_SOC_DAPM_STREAM_START) soc_dapm_stream_event dapm_power_widgets
大家都会调用dapm_power_widgets
对于每一个widget
power = w->power_check(w); // 确定是否要上电
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if (power) dapm_seq_insert(w, &up_list, true ); else dapm_seq_insert(w, &down_list, false ); dapm_seq_run(dapm, &down_list, event, false ); dapm_widget_update(dapm); dapm_seq_run(dapm, &up_list, event, true ); dapm_seq_run dapm_seq_run_coalesced snd_soc_update_bits
(九)、tinycap && tinyplay调用流程源码分析 (1)、tiny4412声卡驱动录音功能调试
(2)、tinycap && tinyplay调用流程源码分析
tinycap /data/charlesvincent.wav // 录音ok tinyplay /data/charlesvincent.wav // 播放ok
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 Line 1575 : <4 >[ 4.628618 ] zjj.android.audio.sys platform register snd-soc-dummy Line 1577: <4>[ 4.628627] zjj.android.audio.sys Registered platform 'snd-soc-dummy' Line 1579 : <4 >[ 4.628872 ] zjj.android.audio.sys platform register samsung-audio Line 1581: <4>[ 4.628878] zjj.android.audio.sys Registered platform 'samsung-audio' Line 1583 : <4 >[ 4.628997 ] zjj.android.audio.sys samsung_i2s_probe snd_soc_register_dai0 i2s.c Line 1585 : <4 >[ 4.629005 ] zjj.android.audio.sys i2s_alloc_dai i2s.c Line 1587 : <4 >[ 4.629160 ] zjj.android.audio.sys samsung_i2s_probe snd_soc_register_dai1 i2s.c Line 1589 : <4 >[ 4.629166 ] zjj.android.audio.sys dai register samsung-i2s.0 Line 1591: <4>[ 4.629174] zjj.android.audio.sys Registered DAI 'samsung-i2s.0' Line 1593 : <4 >[ 4.629200 ] zjj.android.audio.sys samsung_i2s_probe snd_soc_register_dai0 i2s.c Line 1595 : <4 >[ 4.629205 ] zjj.android.audio.sys dai register samsung-i2s.4 Line 1597: <4>[ 4.629210] zjj.android.audio.sys Registered DAI 'samsung-i2s.4' Line 1603 : <4 >[ 4.629489 ] zjj.android.audio.sys platform register samsung-audio-idma Line 1605: <4>[ 4.629496] zjj.android.audio.sys Registered platform 'samsung-audio-idma' Line 1973 : <4 >[ 9.090438 ] zjj.android.audio.sys codec register 0 -001 a Line 1975 : <4 >[ 9.090510 ] zjj.android.audio.sys dai register 0 -001 a #1 Line 1977: <4>[ 9.090572] zjj.android.audio.sys Registered codec 'wm8960-codec.0-001a' Line 1979 : <4 >[ 9.114080 ] zjj.android.audio.sys platform_device_register soc-audio tiny4412_wm8960.c Line 1981 : <4 >[ 9.114594 ] zjj.android.audio.sys soc_probe soc-core.c Line 1983 : <4 >[ 9.114688 ] zjj.android.audio.sys soc_bind_dai_link soc-core.c Line 1985 : <4 >[ 9.114753 ] zjj.android.audio.sys snd_card_create soc-core.c Line 1987 : <4 >[ 9.116842 ] zjj.android.audio.sys soc-core.c probe S3C2440_UDA1341 dai link 0 Line 1991 : <4 >[ 9.124088 ] zjj.android.audio.sys wm8960_probe Line 1995 : <4 >[ 9.234417 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 1997 : <4 >[ 9.234545 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 1999 : <4 >[ 9.234611 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2001 : <4 >[ 9.234679 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2003 : <4 >[ 9.235192 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2005 : <4 >[ 9.246173 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2007 : <4 >[ 9.246251 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2009 : <4 >[ 9.252225 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2011 : <4 >[ 9.258684 ] zjj.android.audio.sys soc-core.c registered pcm #0 100 ask_UDA1341 wm8960-hifi-0 Line 2013 : <4 >[ 9.266114 ] zjj.android.audio.sys snd_pcm_new pcm.c Line 2019 : <4 >[ 9.280976 ] zjj.android.audio.sys soc-core.c asoc: wm8960-hifi <-> samsung-i2s.0 mapping ok Line 2021 : <4 >[ 9.281075 ] zjj.android.audio.sys snd_card_register soc-core.c Line 2023 : <4 >[ 9.307787 ] zjj.android.audio.sys snd_soc_register_card soc-core.c Line 2025 : <4 >[ 9.380530 ] zjj.android.audio.sys snd_soc_dapm_put_volsw soc-dapm.c Line 2027 : <4 >[ 9.380608 ] zjj.android.audio.sys dapm_mixer_update_power soc-dapm.c Line 2029 : <4 >[ 9.380698 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2031 : <4 >[ 9.380979 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2033 : <4 >[ 9.382107 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2035 : <4 >[ 9.391457 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2037 : <4 >[ 9.393502 ] zjj.android.audio.sys snd_soc_dapm_put_volsw soc-dapm.c Line 2039 : <4 >[ 9.405092 ] zjj.android.audio.sys dapm_mixer_update_power soc-dapm.c Line 2041 : <4 >[ 9.406440 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2043 : <4 >[ 9.412262 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2045 : <4 >[ 9.417412 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2047 : <4 >[ 9.423600 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2251 : <4 >[ 59.642371 ] zjj.android.audio.sys snd_pcm_capture_open pcm_native.c Line 2253 : <4 >[ 59.642447 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2255 : <4 >[ 59.643772 ] zjj.android.audio.sys snd_pcm_do_prepare pcm_native.c Line 2257 : <4 >[ 59.643842 ] zjj.android.audio.sys soc_pcm_prepare soc-core.c Line 2259 : <4 >[ 59.643920 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2261 : <4 >[ 59.650158 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2263 : <4 >[ 59.654885 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2265 : <4 >[ 59.660862 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2267 : <4 >[ 70.453890 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2269 : <4 >[ 70.454754 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2271 : <4 >[ 70.455971 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2273 : <4 >[ 70.456096 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2275 : <4 >[ 88.689064 ] zjj.android.audio.sys snd_pcm_playback_open pcm_native.c Line 2277 : <4 >[ 88.689140 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2279 : <4 >[ 88.689566 ] zjj.android.audio.sys snd_pcm_playback_open pcm_native.c Line 2281 : <4 >[ 88.689638 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2283 : <4 >[ 88.691460 ] zjj.android.audio.sys snd_pcm_do_prepare pcm_native.c Line 2285 : <4 >[ 88.696896 ] zjj.android.audio.sys soc_pcm_prepare soc-core.c Line 2287 : <4 >[ 88.702418 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2289 : <4 >[ 88.708425 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2291 : <4 >[ 88.713567 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2293 : <4 >[ 88.719467 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2299 : <4 >[ 104.510149 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2301 : <4 >[ 104.510853 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2303 : <4 >[ 104.512003 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2305 : <4 >[ 104.512129 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2307 : <4 >[ 115.184433 ] zjj.android.audio.sys snd_pcm_capture_open pcm_native.c Line 2309 : <4 >[ 115.184510 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2311 : <4 >[ 115.185324 ] zjj.android.audio.sys snd_pcm_do_prepare pcm_native.c Line 2313 : <4 >[ 115.185397 ] zjj.android.audio.sys soc_pcm_prepare soc-core.c Line 2315 : <4 >[ 115.185683 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2317 : <4 >[ 115.191879 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2319 : <4 >[ 115.196990 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2321 : <4 >[ 115.202897 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2323 : <4 >[ 116.077184 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2325 : <4 >[ 116.077890 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2327 : <4 >[ 116.079099 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2329 : <4 >[ 116.079223 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2331 : <4 >[ 117.836590 ] zjj.android.audio.sys snd_pcm_playback_open pcm_native.c Line 2333 : <4 >[ 117.836666 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2335 : <4 >[ 117.837085 ] zjj.android.audio.sys snd_pcm_playback_open pcm_native.c Line 2337 : <4 >[ 117.837156 ] zjj.android.audio.sys snd_pcm_open pcm_native.c Line 2339 : <4 >[ 117.838975 ] zjj.android.audio.sys snd_pcm_do_prepare pcm_native.c Line 2341 : <4 >[ 117.844460 ] zjj.android.audio.sys soc_pcm_prepare soc-core.c Line 2343 : <4 >[ 117.850000 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2345 : <4 >[ 117.855998 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2347 : <4 >[ 117.862549 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2349 : <4 >[ 117.867224 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2355 : <4 >[ 123.720164 ] zjj.android.audio.sys dapm_power_widgets soc-dapm.c Line 2357 : <4 >[ 123.720905 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c Line 2359 : <4 >[ 123.722064 ] zjj.android.audio.sys dapm_widget_update soc-dapm.c Line 2361 : <4 >[ 123.722188 ] zjj.android.audio.sys dapm_seq_run soc-dapm.c
(十)、参考文档: 韦老师移植后的声卡驱动 Android音频模块启动流程分析 Jhuster的专栏 Android音频开发 高通audio offload学习 | Thinking DroidPhone的专栏 - CSDN博客 alsa音频架构1-CSDN博客 alsa音频架构2-ASoc - CSDN博客 alsa音频架构3-Pcm - CSDN博客 alsa音频架构4-声卡控制 - CSDN博客 Linux ALSA 音频系统:逻辑设备篇 - CSDN博客 Linux ALSA 音频系统:物理链路篇 - CSDN博客 专栏:MultiMedia框架总结(基于6.0源码) - CSDN博客 Android 音频系统:从 AudioTrack 到 AudioFlinger - CSDN博客 AZURE - CSDN博客 - ALSA-Android Audio AZURE - CSDN博客 - ANDROID音频系统 Audio驱动总结–ALSA | Winddoing’s Blog audio HAL - 牧 天 - 博客园 林学森的Android专栏 - CSDN博客 深入剖析Android音频 - CSDN博客Yangwen123 播放框架 - 标签 - Tocy - 博客园 Android-7.0-Nuplayer概述 - CSDN博客 Android-7.0-Nuplayer-启动流程 - CSDN博客 Android Media Player 框架分析-Nuplayer - CSDN博客 Android Media Player 框架分析-AHandler AMessage ALooper - CSDN博客 Android N Audio播放 start真面目- (六篇) CSDN博客 深入理解Android音视频同步机制(五篇)NuPlayer的avsync逻辑 - CSDN博客 wangyf的专栏 - CSDN博客-MT6737 Android N 平台 Audio系统学习 Android 7.0 Audio: Mediaplayer - CSDN博客 Android 7.0 Audio-相关类浅析- CSDN博客 Android N Audio播放六:如何读取buffer - CSDN博客 Fuchsia OS中的RPC机制-FIDL - CSDN博客 高通Audio中ASOC的codec驱动 - yooooooo - 博客园