赞
踩
本文仅仅是个人的一个学习过程总结,并没有细化下去,如果想要细化研究,可百度查找相关资料细看。
大概会按照:
概念 -> 硬件 -> 软件 -> 驱动 -> 用户空间 -> Android 这种流程介绍
下面展示了一般 Codec 芯片的框架及所可能拥有的模块图
仅简单介绍了常用功能,具体含义可百度,写太多会抓不住重点
MIC: 麦克风
ADC: 模数转换
DAC: 数模转换
供参考理解上面介绍的相关概念
取自 Mini2440 手册,数字接口为:IIS+L3
芯片为 UD1341TS
下面是个更详细的例子,介绍了 WM9883 音频芯片逻辑通路及相关 Linux Codec 通路抽象
取自 wm8993 芯片手册
Linux 部分参考资料:
韦东山一期/三期声卡相关视频
http://blog.csdn.net/droidphone Alsa 相关博客
Android 部分参考:
深入理解Android内核设计思想_林学森./第 13 章 应用不再同质化 – 音频系统
深入剖析 Android 系统_杨长刚/第 14 章 Audio
深入理解 Android 卷1_邓凡平/ 第7章 深入理解 Audio 系统
不会写太细,提纲擎领而已
怎么写 ALSA 声卡驱动:
1. snd_card_create() // 创建 snd_card 的一个实例
snd_ctl_create()
2. 初始化:创建声卡的功能部件(逻辑设备)
snd_pcm_new()
3. snd_card_register() //注册声卡
1. struct snd_card 结构体 1.1. snd_card 是什么 snd_card 可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声 音相关的逻辑设备都是在 snd_card 的管理之下,声卡驱动的第一个动作通常就是创建一个 snd_card 结构体。正因 为如此,本节中,我们也从 struct cnd_card 开始吧。 1.2. snd_card的定义 snd_card的定义位于改头文件中: include/sound/core.h /* main structure for soundcard */ struct snd_card { int number; / * number of soundcard (index to snd_cards) * / char id[16]; / * id string of this card * / char driver[16]; / * driver name * / char shortname[32]; / * short name of this soundcard * / char longname[80]; / * name of this soundcard * / char mixername[80]; / * mixer name * / char components[128]; / * card components delimited with space * / struct module *module; / * top‐level module * / void *private_data; / *【声卡的私有数据,可以在创建声卡时通过参数指定数据的大小】/ void (*private_free) (struct snd_card *card); / * callback for freeing of private data * / struct list_head devices; / *【记录该声卡下所有逻辑设备的链表】 / unsigned int last_numid; / * last used numeric ID * / struct rw_semaphore controls_rwsem; / * controls list lock * / rwlock_t ctl_files_rwlock; / * ctl_files list lock * / int controls_count; / * count of all controls * / int user_ctl_count; / * count of all user controls * / struct list_head controls; / *【记录该声卡下所有的控制单元的链表】 / struct list_head ctl_files; / * active control files * / struct snd_info_entry *proc_root; / * root for soundcard specific files * / struct snd_info_entry *proc_id; / * the card id * / struct proc_dir_entry *proc_root_link; / * number link to real id * / struct list_head files_list; / * all files associated to this card * / struct snd_shutdown_f_ops *s_f_ops; / * file operations in the shutdown state * / spinlock_t files_lock; / * lock the files for this card * / int shutdown; / * this card is going down * / int free_on_last_close; / * free in context of file_release * / wait_queue_head_t shutdown_sleep; struct device *dev; / * device assigned to this card * / #ifndef CONFIG_SYSFS_DEPRECATED struct device *card_dev; / * cardX object for sysfs * / #endif #ifdef CONFIG_PM unsigned int power_state; / * power state * / struct mutex power_lock; / * power lock * / wait_queue_head_t power_sleep; #endif #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) struct snd_mixer_oss *mixer_oss; int mixer_oss_change_count; #endif }; 2. 声卡的建立流程 2.1.1. 第一步,创建snd_card的一个实例 struct snd_card *card; int err; .... err = snd_card_create(index, id, THIS_MODULE, 0, &card); index :一个整数值,该声卡的编号 id :字符串,声卡的标识符 第四个参数 :该参数决定在创建 snd_card 实例时,需要同时额外分配的私有数据的大小,该数据的指针最 终会赋值给 snd_card 的 private_data 数据成员 card :返回所创建的 snd_card 实例的指针 2.1.2. 第二步,创建声卡的芯片专用数据 声卡的专用数据主要用于存放该声卡的一些资源信息,例如中断资源、 io资源、 dma资源等。可以有两种 创建方法: 1. 通过上一步中 snd_card_create() 中的第四个参数,让 snd_card_create() 自己创建 // struct mychip 用于保存专用数据 err = snd_card_create(index, id, THIS_MODULE, sizeof(struct mychip), &card); // 从 private_data 中取出 struct mychip *chip = card‐>private_data; 2. 自己创建: struct mychip { struct snd_card *card; .... }; struct snd_card *card; struct mychip *chip; chip = kzalloc(sizeof(*chip), GFP_KERNEL); ...... err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card); // 专用数据记录snd_card实例 chip‐>card = card; ..... 然后,把芯片的专有数据注册为声卡的一个低阶设备: static int snd_mychip_dev_free(struct snd_device *device) { return snd_mychip_free(device‐>device_data); } static struct snd_device_ops ops = { .dev_free = snd_mychip_dev_free, }; .... snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); 注册为低阶设备主要是为了当声卡被注销时,芯片专用数据所占用的内存可以被自动地释放。 2.1.3. 第三步,设置 Driver 的 ID 和名字 strcpy(card‐>driver, "My Chip"); strcpy(card‐>shortname, "My Own Chip 123"); sprintf(card‐>longname, "%s at 0x%lx irq %i", card‐>shortname, chip‐>ioport, chip‐>irq); snd_card 的 driver 字段保存着芯片的ID字符串, user 空间的 alsa-lib 会使用到该字符串,所以必须要保证该 ID 的唯 一性。 shortname 字段更多地用于打印信息, longname 字段则会出现在 /proc/asound/cards 中。 2.1.4. 第四步,创建声卡的功能部件(逻辑设备),例如PCM, Mixer, MIDI等 这时候可以创建声卡的各种功能部件了,还记得开头的 snd_card 结构体的 devices 字段吗?每一种部件的创建最终 会调用 snd_device_new() 来生成一个 snd_device 实例,并把该实例链接到 snd_card 的 devices 链表中。 通常, alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),比如: PCM -- snd_pcm_new() RAWMIDI -- snd_rawmidi_new() CONTROL -- snd_ctl_create() TIMER -- snd_timer_new() INFO -- snd_card_proc_new() JACK -- snd_jack_new() 2.1.5. 第五步,注册声卡 err = snd_card_register(card); if (err < 0) { snd_card_free(card); return err; }
/sound/arm/pxa2xx-ac97.c的部分代码贴上来:
static int __devinit pxa2xx_ac97_probe(struct platform_device *dev) { struct snd_card *card; struct snd_ac97_bus *ac97_bus; struct snd_ac97_template ac97_template; int ret; pxa2xx_audio_ops_t *pdata = dev->dev.platform_data; if (dev->id >= 0) { dev_err(&dev->dev, "PXA2xx has only one AC97 port./n"); ret = -ENXIO; goto err_dev; } (1) 第一步,创建snd_card的一个实例 ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,THIS_MODULE, 0, &card); if (ret < 0) goto err; card->dev = &dev->dev; (3) 第三步,设置Driver的ID和名字 strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver)); (4) 第四步,创建声卡的功能部件(逻辑设备)PCM ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm); if (ret) goto err; (2) 第二步,创建声卡的芯片专用数据 ret = pxa2xx_ac97_hw_probe(dev); if (ret) goto err; (4) 第四步,创建声卡的功能部件(逻辑设备)AC97_BUS ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus); if (ret) goto err_remove; memset(&ac97_template, 0, sizeof(ac97_template)); ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97); if (ret) goto err_remove; (3) 第三步,设置Driver的ID和名字 snprintf(card->shortname, sizeof(card->shortname), "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97)); snprintf(card->longname, sizeof(card->longname), "%s (%s)", dev->dev.driver->name, card->mixername); if (pdata && pdata->codec_pdata[0]) snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]); snd_card_set_dev(card, &dev->dev); (5) 第五步,注册声卡 ret = snd_card_register(card); if (ret == 0) { platform_set_drvdata(dev, card); return 0; } err_remove: pxa2xx_ac97_hw_remove(dev); err: if (card) snd_card_free(card); err_dev: return ret; } static int __devexit pxa2xx_ac97_remove(struct platform_device *dev) { struct snd_card *card = platform_get_drvdata(dev); if (card) { snd_card_free(card); platform_set_drvdata(dev, NULL); pxa2xx_ac97_hw_remove(dev); } return 0; } static struct platform_driver pxa2xx_ac97_driver = { .probe = pxa2xx_ac97_probe, .remove = __devexit_p(pxa2xx_ac97_remove), .driver = { .name = "pxa2xx-ac97", .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &pxa2xx_ac97_pm_ops, #endif }, }; static int __init pxa2xx_ac97_init(void) { return platform_driver_register(&pxa2xx_ac97_driver); } static void __exit pxa2xx_ac97_exit(void) { platform_driver_unregister(&pxa2xx_ac97_driver); } module_init(pxa2xx_ac97_init); module_exit(pxa2xx_ac97_exit); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
ASOC 是建立在 Alsa 驱动层之上的优化,模块化更好,个人感觉更方便偷懒了。。。
它在 ALSA 中又抽象出来三部分: 1. machine: 单板相关,表明 platform 是哪个,CPU DAI 是哪个,DMA 是哪个 表明 codec 是哪个,codec DAI 是哪个 代表结构体:snd_soc_card/snd_soc_dai_link 2. platform: CPU 相关,主要有两方面: 1> DAI: 控制接口,代表数据结构 snd_soc_dai_driver 2> DMA: 传数据,代表数据结构 snd_soc_platform_driver 3. codec: 声卡芯片相关,也分两方面: 1> DAI: 用于传输数据,代表结构 snd_soc_dai_driver 2> 控制接口:用于寄存器设置什么的,代表结构 snd_soc_codec_driver 相关的数据结构内容为: 1. machine: snd_soc_dai_link{各子驱动的名字} 2. codec: snd_soc_dai_driver{ 属性:声道数,采样率,格式 ops: 启动、关闭、参数设置 } snd_soc_codec_driver{函数} 3. platform: dai: snd_soc_dai_driver{ 属性:声道数,采样率,格式 ops: 启动、关闭、参数设置 } dma: snd_soc_platform_driver{ 函数 ops: 数据传输相关函数 }
具体可参考附件 【非常好】音频驱动框架总结.不带DPM.3.4.2.txt
// 【machine】 相关部分 // 主要任务: // 注册一个平台设备,名为 "soc-audio" // 设置平台设备的私有数据为 snd_soc_card 结构体 // snd_soc_card: // 设置一个 snd_soc_dai_link 结构体,匹配各驱动 // snd_soc_dai_link: // ################################################ // # Codec 驱动: snd_soc_codec_driver/snd_soc_dai_driver // .codec_name : 指明 Codec 名称,用于设置 Codec 寄存器,注册 snd_soc_codec_driver 两个结构体 // .codec_dai_name : 指明 Codec Dai 名称,用于与 CPU 通信的接口操作,如 PCM/I2S, 注册 snd_soc_dai_driver // ################################################ // # Platform 驱动:snd_soc_platform_driver/snd_soc_dai_driver // .platform_name : 指明 DMA 驱动的名称,用于如何将音频数据传到 dai 接口, 注册 snd_soc_platform_driver 这个结构体 // .cpu_dai_name : 指明 CPU Dai 名称,用于与 codec 通信的接口,如 PCM/I2S 等,注册 snd_soc_dai_driver 结构体 // .ops //S3c24xx_uda134x.c (sound\soc\samsung) | module_platform_driver(s3c24xx_uda134x_driver); |//Mach-mini2440.c (arch\arm\mach-s3c24xx) static struct platform_driver s3c24xx_uda134x_driver = { | static struct platform_device mini2440_audio = { .probe = s3c24xx_uda134x_probe, | .name = "s3c24xx_uda134x", .remove = s3c24xx_uda134x_remove, | .id = 0, .driver = { | .dev = { .name = "s3c24xx_uda134x", | .platform_data = &mini2440_audio_pins, .owner = THIS_MODULE, | }, }, | }; }; | 左边: s3c24xx_uda134x_probe()//【注册一个名为 "soc-audio" 平台设备,其私有数据保存了要注册的 snd_soc_card 】 s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1); platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x); /* 整个驱动核心 */ static struct snd_soc_card snd_soc_s3c24xx_uda134x = { .name = "S3C24XX_UDA134X", .owner = THIS_MODULE, .dai_link = &s3c24xx_uda134x_dai_link, .num_links = 1, }; /* 指明了用哪个 codec,codec dai,cpu,cpu dai */ static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { .name = "UDA134X", .stream_name = "UDA134X", .codec_name = "uda134x-codec", // 【Codec】用哪一个 codec .codec_dai_name = "uda134x-hifi", // 用 codec 芯片的哪一个 dai .cpu_dai_name = "s3c24xx-iis", // 【Platform】指定 2440 的 dai .ops = &s3c24xx_uda134x_ops, .platform_name = "samsung-audio",// 2440 DMA 操作 }; static struct snd_soc_ops s3c24xx_uda134x_ops = { .startup = s3c24xx_uda134x_startup, // 获得时钟 .shutdown = s3c24xx_uda134x_shutdown,// 释放时钟 .hw_params = s3c24xx_uda134x_hw_params,//设置 cpu dai 通信格式 }; 右边: // Soc-core.c (sound\soc) 匹配对应的驱动 static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, .pm = &snd_soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, }; soc_probe() // 注册左边声明 snd_soc_card 结构的 snd_soc_s3c24xx_uda134x snd_soc_register_card(card); card->rtd = devm_kzalloc(card->dev,... card->rtd[i].dai_link = &card->dai_link[i]; // &s3c24xx_uda134x_dai_link list_add(&card->list, &card_list); snd_soc_instantiate_cards(); // 实例化声卡 snd_soc_instantiate_cards(card); 3.1 /* bind DAIs,确定使用 CPU 侧以及 Codec 侧的哪一个 DAI */ for (i = 0; i < card->num_links; i++) soc_bind_dai_link(card, i); 3.1.1 /* find CPU DAI */ rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai 3.1.2 /* find_codec */ rtd->codec = codec; = // codec, codec->driver=&soc_codec_dev_uda134x 3.1.3 /* find CODEC DAI */ rtd->codec_dai = codec_dai; // = &uda134x_dai 3.1.4 /* find_platform */ rtd->platform = platform; // = &samsung_asoc_platform 3.2 /* initialize the register cache for each available codec */ ret = snd_soc_init_codec_cache(codec, compress_type); 3.3 snd_card_create() // 标准声卡创建函数 3.4 /* early DAI link probe */ // 这个函数会调用各个匹配的驱动的 probe 函数, 会匹配哪些呢?就是 s3c24xx_uda134x_dai_link 指定的那些名字的驱动 soc_probe_dai_link() /* probe the cpu_dai */ /* probe the CODEC */ /* probe the platform */ /* probe the CODEC DAI */ /* create the pcm */ ret = soc_new_pcm(rtd, num); // 创建 PCM 部件 struct snd_pcm_ops *soc_pcm_ops = &rtd->ops; 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; snd_pcm_new() 3.5 snd_card_register() // 标准声卡注册函数
// 【platform】的匹配 probe : 这是处理 CPU 的 DMA 的操作 // 主要作用:通过 snd_soc_register_platform() 注册平台相关的 DMA 相关操作 // 即注册 snd_soc_platform_driver 这个结构体 // // Dma.c (sound\soc\samsung) |//Devs.c (arch\arm\plat-samsung) static struct platform_driver asoc_dma_driver = { | struct platform_device samsung_asoc_dma = { .driver = { | .name = "samsung-audio", .name = "samsung-audio", | .id = -1, .owner = THIS_MODULE, | .dev = { }, | .dma_mask = &samsung_device_dma_mask, | .coherent_dma_mask = DMA_BIT_MASK(32), .probe = samsung_asoc_platform_probe, | } .remove = __devexit_p(samsung_asoc_platform_remove), | }; }; | samsung_asoc_platform_probe() return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform); // 平台相关操作驱动 static struct snd_soc_platform_driver samsung_asoc_platform = { .ops = &dma_ops, .pcm_new = dma_new, // 分配 DMA 内存 .pcm_free = dma_free_dma_buffers, // 释放 DMA 内存 }; // DMA 操作 static struct snd_pcm_ops dma_ops = { .open = dma_open, // 打开 pcm 设备时调用,用于保存平台 dma 参数,获取 DMA 中断 .close = dma_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = dma_hw_params, // 获得对应的 dai 的 dma 参数 .hw_free = dma_hw_free, .prepare = dma_prepare, // 正式开始数据传输时会调用该函数 .trigger = dma_trigger, // 数据传送的开始、暂停、恢复和停止时调用 .pointer = dma_pointer, // 返回传送数据的当前位置 .mmap = dma_mmap, }; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 【CPU DAI】 的匹配 probe: 这是处理与声卡芯片的操作的接口设置的 // 主要作用:通过 snd_soc_register_dai() 注册 CPU Dai // 即注册 snd_soc_dai_driver 结构体 // // S3c24xx-i2s.c (sound\soc\samsung) |// Devs.c (arch\arm\plat-samsung) static struct platform_driver s3c24xx_iis_driver = { | struct platform_device s3c_device_iis = { .probe = s3c24xx_iis_dev_probe, | .name = "s3c24xx-iis", .remove = __devexit_p(s3c24xx_iis_dev_remove), | .id = -1, .driver = { | .num_resources = ARRAY_SIZE(s3c_iis_resource), .name = "s3c24xx-iis", | .resource = s3c_iis_resource, .owner = THIS_MODULE, | .dev = { }, | .dma_mask = &samsung_device_dma_mask, }; | .coherent_dma_mask = DMA_BIT_MASK(32), | } s3c24xx_iis_dev_probe() | }; return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai); static struct snd_soc_dai_driver s3c24xx_i2s_dai = { .probe = s3c24xx_i2s_probe, .suspend = s3c24xx_i2s_suspend, .resume = s3c24xx_i2s_resume, .playback = { .channels_min = 2, .channels_max = 2, .rates = S3C24XX_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, .capture = { .channels_min = 2, .channels_max = 2, .rates = S3C24XX_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, .ops = &s3c24xx_i2s_dai_ops, }; // cpu dai 操作,他与 codec dai 数据结构是一样的 static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = { .trigger = s3c24xx_i2s_trigger, // 在 PCM 数据开始,传输,唤醒时被调用,用来启动/停止 IIS 传输 .hw_params = s3c24xx_i2s_hw_params, // 根据传入的参数,配置 CPU 的 IIS 相关模块设置 /// // 以下这里函数在 Machine 驱动的 hw_params() 会调用 .set_fmt = s3c24xx_i2s_set_fmt, .set_clkdiv = s3c24xx_i2s_set_clkdiv, .set_sysclk = s3c24xx_i2s_set_sysclk, };
// 【codec】 的匹配 probe // 主要作用:通过 snd_soc_register_codec() 注册 Codec Dai /Code Driver // 即 snd_soc_dai_driver 与 snd_soc_codec_driver 两个结构体 // //Uda134x.c (sound\soc\codecs) |// Mach-mini2440.c (arch\arm\mach-s3c24xx) static struct platform_driver uda134x_codec_driver = { | static struct platform_device uda1340_codec = { .driver = { | .name = "uda134x-codec", .name = "uda134x-codec", | .id = -1, .owner = THIS_MODULE, | }; }, | .probe = uda134x_codec_probe, | .remove = __devexit_p(uda134x_codec_remove), | }; | uda134x_codec_probe() return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_uda134x, &uda134x_dai, 1); // codec 的寄存器操作: snd_soc_codec_driver static struct snd_soc_codec_driver soc_codec_dev_uda134x = { .probe = uda134x_soc_probe, // 配置硬件,通过 snd_soc_add_codec_controls() 注册 snd_kcontrol_new .remove = uda134x_soc_remove, .suspend = uda134x_soc_suspend, .resume = uda134x_soc_resume, // UDA1341的寄存器不支持读操作 // 要知道某个寄存器的当前值, // 只能在写入时保存起来 .reg_cache_size = sizeof(uda134x_reg), .reg_word_size = sizeof(u8), .reg_cache_default = uda134x_reg, .reg_cache_step = 1, .read = uda134x_read_reg_cache, .write = uda134x_write, // 写寄存器,通过 snd_soc_wirte() 调用到 .set_bias_level = uda134x_set_bias_level, }; / // codec 的 DAI 接口设置 static struct snd_soc_dai_driver uda134x_dai = { .name = "uda134x-hifi", /* playback capabilities */ .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* capture capabilities */ .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* pcm operations */ .ops = &uda134x_dai_ops, }; / // 以下这里函数在 Machine 驱动的 hw_params() 会调用 static const struct snd_soc_dai_ops uda134x_dai_ops = { .startup = uda134x_startup, .shutdown = uda134x_shutdown, .hw_params = uda134x_hw_params, .digital_mute = uda134x_mute, .set_sysclk = uda134x_set_dai_sysclk, .set_fmt = uda134x_set_dai_fmt, };
################################################################################ Mixer 控件:混音器, Mixer控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由 地混合在一起,形成混合后的 ################################################################################ static const struct snd_kcontrol_new left_speaker_mixer[] = { SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), }; // 以上这个 mixer 使用寄存器 WM8993_SPEAKER_MIXER 的第 3,5,6,7 位来分别控制 4 个输入端的开启和关闭 ################################################################################ Mux 控件:多路开关选择器 Mux 中能同时只有一路被选中输出,多路开关选择器嘛 ################################################################################ 第一步,定义字符串和values数组:输入端名字 ############################################### static const char *drc_path_text[] = { "ADC", "DAC" }; ############################################### 第二步,利用 ASoc 提供的辅助宏定义 soc_enum 结构,用于描述寄存器 ############################################### static const struct soc_enum drc_path = SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text); // 从左到右依次为: 【xreg】/【xshif】/【xmax】/【xtexts】 ############################################### 第三步,利用 ASoc 提供的辅助宏,定义 soc_kcontrol_new 结构,该结构最后用于注册该 mux 控件 ############################################### static const struct snd_kcontrol_new wm8993_snd_controls[] = { SOC_DOUBLE_TLV(......), // 定义简单型的控件,只控制一位 ...... SOC_ENUM("DRC Path", drc_path), // 定义 Mux 控件 ...... }
硬件示意图:见 WM9883 SPK 输出逻辑通路与硬件通路图 ################################################################################ 第一步,利用辅助宏定义 widget 所需要的 dapm kcontrol ################################################################################ ######################################## # Mixer 控件:多选一,多个输入可同时选中 static const struct snd_kcontrol_new left_speaker_mixer[] = { // 从右到右依次为:【name】/【reg】/【shift】/【max】/【invert】 SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), }; static const struct snd_kcontrol_new right_speaker_mixer[] = { SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0), SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0), SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0), }; ######################################## # Mux 控件:多选一,多个输入同时只有一个可选中 // 1. 定义字符串和 values 数组:输入端名字 static const char *aif_text[] = { "Left", "Right" }; // 2. 利用 ASoc 提供的辅助宏定义 soc_enum 结构,用于描述寄存器,从左到右依次为: 【xreg】/【xshif】/【xmax】/【xtexts】 static const struct soc_enum aifinl_enum = SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text); static const struct soc_enum aifinr_enum = SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 14, 2, aif_text); // 3. 利用 ASoc 提供的辅助宏,定义 soc_kcontrol_new 结构,该结构最后用于注册该 mux 控件 static const struct snd_kcontrol_new aifinl_mux = SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum); static const struct snd_kcontrol_new aifinr_mux = SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum); ################################################################################ 第二步,定义真正的 widget,包含第一步定义好的 dapm 控件 ################################################################################ static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = { ...... ######################################### # 指定 AIFINL/AIFINR 为输出流: SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), ...... ######################################### # 创建 Mux, 不带电源管理: DACL Mux/DACR Mux SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux), SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux), ######################################### # 创建 Mixer, SPKL/SPKR 带电源管理,指定电源管理寄存器及操作位 SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), ...... }; ################################################################################ 第三步,定义这些 widget 的连接路径: ################################################################################ static const struct snd_soc_dapm_route routes[] = { ...... // 模块 模块引脚 另一模块引脚 { "DACL Mux", "Left", "AIFINL" }, # AIFINL 连接到 DACL Mux 的 Left 输入脚 { "DACL Mux", "Right", "AIFINR" }, # AIFINL 连接到 DACR Mux 的 Left 输入脚 { "DACR Mux", "Left", "AIFINL" }, # AIFINR 连接到 DACL Mux 的 Right 输入脚 { "DACR Mux", "Right", "AIFINR" }, # AIFINR 连接到 DACR Mux 的 Right 输入脚 ...... { "SPKL", "DAC Switch", "DACL" }, # DACL 连接到 SPKL 的 DAC Switch 输入脚 { "SPKL", NULL, "CLK_SYS" }, { "SPKR", "DAC Switch", "DACR" }, # DACR 连接到 SPKR 的 DAC Switch 输入脚 { "SPKR", NULL, "CLK_SYS" }, }; ################################################################################ 第四步,在 codec 驱动的 probe 回调中注册这些 widget 和路径 ################################################################################ static int wm8993_probe(struct snd_soc_codec *codec) { ...... snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,ARRAY_SIZE(wm8993_dapm_widgets)); ...... snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes)); ...... }
安卓与内核相关音频交互是通过 tinyalsa 相关接口交互的,即通过 tinyalsa 相关命令,控制前面内核定义的用户接口
所以这里简单介绍下 tinyalsa 工具,了解下有哪些接口
tinymix 的使用: # tinymix Mixer name: 'audiocodec' Number of controls: 12 ctl type num name value 0 INT 1 MIC1_G boost stage output mixer control 3 1 INT 1 MIC2_G boost stage output mixer control 3 2 INT 1 LINEIN_G boost stage output mixer control 3 3 INT 1 MIC1 boost AMP gain control 4 4 INT 1 MIC2 boost AMP gain control 4 5 INT 1 Lineout volume control 31 6 INT 1 ADC input gain ctrl 3 7 BOOL 1 Audio linein in On 8 BOOL 1 Audio lineout Off 9 BOOL 1 Audio adda drc Off 10 BOOL 1 Audio adda loop Off 11 ENUM 1 audio capture mode linein 一个 mixer 通常有多个 controler,像这个,里面有 12 个,然后就分别列出每一个 controller 的信息 首先看第一个:它的编号为 0,类型是 int 型,它目前的值是 3,它是用来控制 mic1 的放大倍数的 然后看第7个是一个 bool 型,也就是只有开关 使用方法: tinymix [序号] <参数> // 写配置 tinymix [序号] // 读配置 如: tinymix "RX1 MIX1 INP1" RX1 tinymix "RX2 MIX1 INP1" RX2 tinymix "RDAC2 MUX" RX2 tinymix "HPHL" Switch tinymix "HPHR" Switch
tinypcminfo 的使用:获得支持格式类型 # tinypcminfo -D 0 -d 0 Info for card 0, device 0: PCM out: Access: 0x000009 Format[0]: 0x003ffc Format[1]: 00000000 Format Name: S16_LE, S16_BE, U16_LE, U16_BE, S24_LE, S24_BE, U24_LE, U24_BE, S32_LE, S32_BE, U32_LE, U32_BE Subformat: 0x000001 Rate: min=8000Hz max=192000Hz Channels: min=1 max=2 Sample bits: min=16 max=32 Period size: min=0 max=24576 Period count: min=1 max=4 PCM in: cannot open device '/dev/snd/pcmC0D0c' Device does not exist.
tinyplay 的使用:
tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
目前 286 能用的使用方法:
插入耳机
tinymix 4 On // Headset_Speaker_Amp_Switch, 这里有个问题,就是增益为 0,奇怪,需要点一下 TP 才短暂有声
tinyplay 441.wav -D 0 -d 0 -p 2048 -n 2
由于 Android 中默认并没有使用标准 alsa,而是使用的是 tinyalsa,所以就算基于命令行的测试也要使用 libtinyalsa。 Android 系统在上层 Audio 千变万化的时候,可以能这些个工具实时查看到,比如音频通道的切换等等. 1.编译tinyalsa配套工具 $ mmm external/tinyalsa/ 编译完后会产生tinyplay/tinymix/tinycap等等工具。 tinymix: 查看配置混音器 tinyplay: 播放音频 tinycap: 录音 2.查看当前系统的声卡 root@android:/ # cat /proc/asound/cards 0 [RKRK616 ]: RK_RK616 - RK_RK616 RK_RK616 1 [ROCKCHIPSPDIF ]: ROCKCHIP-SPDIF - ROCKCHIP-SPDIF ROCKCHIP-SPDIF root@android:/ # 3.tinymix 查看混响器 tinymix 使用方法 a.不加任何参数 - 显示当前配置情况 b.tinymix [ctrl id] [var] 不加 [var] 可以查看该 [ctrl id] 可选选项 root@android:/ # tinymix Number of controls: 7 ctl type num name value 0 ENUM 1 Playback Path OFF 1 ENUM 1 Capture MIC Path MIC OFF 2 ENUM 1 Voice Call Path OFF 3 ENUM 1 Voip Path OFF 4 INT 2 Speaker Playback Volume 0 0 5 INT 2 Headphone Playback Volume 0 0 6 ENUM 1 Modem Input Enable ON root@android:/ # 对应解释: 英文 中文 备注 Playback Path 音频输出通道 Capture MIC Path 音频输入通道 Voice Call Pah 通话音频通道 设备没有通话模块,暂无法测试 Voip Pah IP 电话音频通道 场景 Gtalk;值有: SPK/HP_NO_MIC/BT Speaker Playback Volume 扬声器音量 和上层音量值无关 Headphone Playback Volume 耳机音量 同上 Modem Input Enable 暂不知何用 经测试不能控制音频输入输出 Playback Path有: 英文 中文 备注 OFF 关闭 RCV - SPK 扬声器 常用 HP 耳机带麦 HP_NO_MIC 耳机无麦 常用 BT 蓝牙 SPK_HP - RING_SPK - RING_HP - RING_HP_NO_MIC - RING_SPK_HP - 例:将输出切换到扬声器 root@Android:/ # tinymix 0 SPK
这里只是简单根据各本书上的流程进行了相关高通 8.0 代码的追溯,比较多也比较乱,大概主要就追了 Audio 服务启动初始化,
以及从 App -> Linux 放音这两线,其他的只是针对书本上的流程进行了总结
Audio 系统是 Android 平台的重要组成部分,它主要包括三方面内容: AudioRcorder 和 AudioTrack: 这两个类属于 Audio 系统对外提供的 API 类,通过 它们可以完成 Android 平台上音频数据的采集和输出任务。 AudioFlinger:它是 Audio 系统的工作引擎,管理着系统中的输入输出音频流,并承担 音频数据的混音,以及读写 Audio 硬件等工作以实现数据的输入输出功能。 AudioPolicyService:它是 Audio 系统的策略控制中心,具体掌管系统中声音设备的选择 和切换、音量控制等功能。 AudtioPolicyService 用于路由 AudioTrack 到 PlaybackThread 中,即对应的硬件 AudioPolicyService 通过 AudioPolicyManager 来与 AF/AS 交互的 AudioTrack 与 AudioFlinge 是通过 IAudioTrack 交互的,在 AudioFlinge 对应的即是 TrackHandle
相关小类介绍
------------- Java ------------------------------------ AudioSystem: Audio 的管理 AudioTrack: Audio 的 PCM 输出 AudioRecod: Audio 录制输入 AudioEffect: Audio 音效 AudioPolicy: 音频设备策略 Visualizer: Audio 可视化效果(Visualizer) ------------- C++ -------------------------------------- AudioRcorder/AudioTrack: 这两个类属于 Audio 系统对外提供的 API 类,通过 他们可以完成 Android 平台上的音频数据的采集和输出任务。 AudioSystem/AudioService: 封装的 AudioFlinger/AudioPolicyService 接口,提供给 AudioTrack AudioFlinger: 它是 Audio 系统的工作引擎,管理着系统中的输入输出音频流,并 承担音频数据的混音,以及读写 Audio 硬件等工作以实现数据的输入/ 输出功能。 AudioPolicyService: 它是 Audio 系统的策略控制中心,具体掌管系统中声音设备的 选择和切换、音量控制等功能。 AudioMixer: 混音核心类 注:此类用于将 AudioTrack 中的 32 路 track 数据,有选择的放到消费都缓冲区中? AudioMixer 内部有一个 mState 成员变量 最多达 32 路的 Track 数据就存储在其中 state_t::tracks[MAX_NUM_TRACKS] 数组中 每个 PlaybackThread::TrackBase 在 AudioMixer 中对应 tracks 数组中的一个元素 AudioMixer 用于混音操作的缓冲区对象和 AudioTrack/AudioFlinger 中的数据区是一个 AudioTrack 与 AudioSystem / AudioService 关系: AudioTrack 与底层服务间又提供了 AudioSystem 和 AudioService 通过这两个类来访问 AudioFlinger 前者同时提供了 Java 和 Native 两层的接口实现, 而 AudioService 则只有 Native 层的实现 这样就降低了使用者(AudioTrack)与底层服务(AudioPolicyService, AudioFlinger 等)间的耦合 只要 AudioSytem 和 AudioService 向上接口不变, 那么 AudioTrack 就不需要做任何修改 AudioTrack 通过 AudioSystem 来访问 AudioPolicyService
开始播放流程细分
停止播放流程细分
// // AudioTrack ==> // AudioSystem ==>: 是一个接口类,降低与 AudioPolicy 之间的耦合 // AudioPolicyService ==> // AudioPolicyManager ==> 相关音频逻辑关系实现类 // AudioFlinge ==> // MixerThread ==> 混音进程: 持有硬件接口 // AudioMixer: 混音器 // AudioStreamOut ==> HAL 硬件代表 // HAL ==> // Kernel // // # 调用经过模块原因解释: 在上面的创建 AudioTrack 流程中,经过 AS--APS--厂家实现策略 AudioSystem 想找到 AF 中的一个工作线程,会经过 AP 返回 原因是因为 Audio 系统需要: 根据流类型找到对应的路由策略 根据该策略找到合适的输出设备(指扬声器、听筒之类的) 根据设备选择 AF 中合适的工作线程 如蓝牙的 MixerThread,还是 DSP 的 MixerThread 或者是 DuplicatingThread AT 根据得到的工作线程索引号,最终将在对应的工作线程中创建 Track 之后,AT 的数据将由该线程负责处理,因为 只有 MixerThread 与硬件设备输出相关 # 从目的反推开始原因:AudioTrack:set() AT 的目的是把数据发送到对应的设备,如蓝牙、DSP 等 代表输出设备的 HAL 对象由 MixerThread 线程执有,所以要找到对应 MixerThread AP 维护流类型和输出设备和输出设备(耳机、蓝牙耳机、听筒等)之间的关系 不同的输出设备使用不同的混音线程 AT 根据自己的流类型向 AudioSystem 查询,希望得到对应的混音线程号
> PlaybackThread 和 AudioStreamOutput
PlaybackThread 类中有一个 AudioStreamOutput 类型对象
例如:MixerThread 有个 AudioStreamOutput 用于硬件输出
这个对象提供了音频数据的输出功能
PlaybackThread 接收来自 AT 的数据,对这些数据进行混音
把混音的结果写到 AudioStreamOut 中,完成音频输出
> 工作线程介绍
RecordThread: 录音线程,用于音频输入
PlaybackThread: 回放线程,用于音频输出
两个 Track 数组:
mActiveTracks: 表示当前活跃的 Track
mTracks: 表示这个线程创建的所有 Track
DirectOutputThread: 直接输出线程,选择一路音频输出
MixerThread: 混音线程,用于将多个源音频数据混音后输出
DuplicatingThread: 多路输出,也能混音
mOutputTracks: 表示多路输出的目的端
音效类继承关系: frameworks/av/media/libeffects
AudioEffect
BassBoost: 重低音
EnvironmentalReverb:环境音混响
Equalizer:均衡器
PresetReverb:预置混响
Virtualizer:可视化
Virtualizer: 虚拟器
################################################# # 3rd audio effect的实现 ################################################# # audio_effect_library_t: 定义了所有effect的一个统一接口 // Audio_effect.h (hardware\libhardware\include\hardware) // 所有的音效库必须实现一个名为 AUDIO_EFFECT_LIBRARY_INFO_SYM 的 audio_effect_library_t 的结构 typedef struct audio_effect_library_s { // tag must be initialized to AUDIO_EFFECT_LIBRARY_TAG uint32_t tag; // Version of the effect library API : 0xMMMMmmmm MMMM: Major, mmmm: minor uint32_t version; // Name of this library const char *name; // Author/owner/implementor of the library const char *implementor; create_effect(): 就是创建对应的音效引擎,得到引擎控制接口 effect_interface_t release_effect): 释放对应的音效引擎 get_descriptor(): 通过 uuid 去获取音效描述符 } # 音效处理引擎接口 effect_interface_s 包括四个函数指针: process(): 音效处理 command(): 用于向音效引擎发送命令和接收对命令的回复 // Audio_effect.h (system\media\audio\include\system) / // Effect control interface / // //--- Standardized command codes for command() function // enum effect_command_e { EFFECT_CMD_INIT, // initialize effect engine EFFECT_CMD_SET_CONFIG, // configure effect engine (see effect_config_t) EFFECT_CMD_RESET, // reset effect engine EFFECT_CMD_ENABLE, // enable effect process EFFECT_CMD_DISABLE, // disable effect process EFFECT_CMD_SET_PARAM, // set parameter immediately (see effect_param_t) EFFECT_CMD_SET_PARAM_DEFERRED, // set parameter deferred EFFECT_CMD_SET_PARAM_COMMIT, // commit previous set parameter deferred EFFECT_CMD_GET_PARAM, // get parameter EFFECT_CMD_SET_DEVICE, // set audio device (see audio.h, audio_devices_t) EFFECT_CMD_SET_VOLUME, // set volume EFFECT_CMD_SET_AUDIO_MODE, // set the audio mode (normal, ring, ...) EFFECT_CMD_SET_CONFIG_REVERSE, // configure effect engine reverse stream(see effect_config_t) EFFECT_CMD_SET_INPUT_DEVICE, // set capture device (see audio.h, audio_devices_t) EFFECT_CMD_GET_CONFIG, // read effect engine configuration EFFECT_CMD_GET_CONFIG_REVERSE, // read configure effect engine reverse stream configuration EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS,// get all supported configurations for a feature. EFFECT_CMD_GET_FEATURE_CONFIG, // get current feature configuration EFFECT_CMD_SET_FEATURE_CONFIG, // set current feature configuration EFFECT_CMD_SET_AUDIO_SOURCE, // set the audio source (see audio.h, audio_source_t) EFFECT_CMD_OFFLOAD, // set if effect thread is an offload one, // send the ioHandle of the effect thread EFFECT_CMD_FIRST_PROPRIETARY = 0x10000 // first proprietary command code }; get_desriptor(): 返回音效描述符 process_reverse(): 提供一个反向音频流,一般用于回声消除 ################### # 音效库实现例: ################### // EffectDownmix.c (frameworks\av\media\libeffects\downmix) // This is the only symbol that needs to be exported __attribute__ ((visibility ("default"))) audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { .tag = AUDIO_EFFECT_LIBRARY_TAG, .version = EFFECT_LIBRARY_API_VERSION, .name = "Downmix Library", .implementor = "The Android Open Source Project", .create_effect = DownmixLib_Create, .release_effect = DownmixLib_Release, .get_descriptor = DownmixLib_GetDescriptor, // 这个结构体在Android4.3+时有发生变化,query_num_effects()和query_effect()被删除。 // 如果希望做库兼容性,需要检测EFFECT_LIBRARY_API_VERSION,当其为 // #ifEFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION) > 2 时 query_num_effects() 和 query_effect() 不复存在 }; // effect_handle_t interface implementation for downmix effect // 音效处理引擎接口 effect_interface_s const struct effect_interface_s gDownmixInterface = { Downmix_Process, Downmix_Command, Downmix_GetDescriptor, NULL /* no process_reverse function, no reference stream needed */ }; # audio_buffer_t: 定义了音效输入输出的数据格式 # effect_param_t: 定义了音效之间、系统上下之间的通信协议(数据、格式等等) // audio_effects.conf 的结构如下: libraries { // libraries 指明了库的加载路径,默认是在/system/lib/soundfx/目录下 ... downmix { path /system/lib/soundfx/libdownmix.so } } effects {// effects 包含了该系统支持的所有音效,音效所使用的库,以及音效的 uuid ... downmix { library downmix uuid 93f04452-e4fe-41cc-91f9-e475b6d1d69f } } # 音效引擎工厂:EffectFactory 主要是为了减少 AudioFlinger 与音效引擎库的耦合 根据配置文件中的信息装载引擎库,解析出引擎库符号(audio_effect_library_t)放在链表中, 再遍历链表,调用 audio_effect_library_t 的 get_descriptor 函数,进而得到音效描述符 effect_descriptor_t, 这样就可以得到系统中所有的音效引擎。 frameworks/av/media/libeffects/factory/EffectsFactory.c gLibraryList: 管理音效链表 vendor/etc/audio_effects.conf 音效配置文件 # 音效使用: 会在 MixerThread 循环中中使用
App 音效处理流程
# AudioEffect的具体效果作用在音频数据上,MediaPlayer只管播放音频,二者通过AudioSessionId关联起来 # 可以说,音效的处理对 MediaPlayer 是透明的,具体的处理由 Android 框架进行 # 如果要应用全局音频输出的混响效果必须指定 audioSession=0,并且要求有 MODIFY_AUDIO_SETTINGS 权限 //创建 MediaPlayer,音频源为/res/raw/audio.mp3 mMediaPlayer = new MediaPlayer.create(this, R.raw.beautiful); //创建Equalizer,通过AudioSessionId绑定到MediaPlayer Equalizer mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId()); //启用、获取/设置参数 mEqualizer.setEnabled(true); short bands = mEqualizer.getNumberOfBands(); mEqualizer.setBandLevel(band, level); //MP3 播放,创建 Equalizer mMediaPlayer.start();
// 1. 根据音频数据的特性来确定所要分配的缓冲区的最小 size int bufsize = AudioTrack.getMinBufferSize(800, // 采样率:每秒 8000 个点 AudioTrack.CHANNEL_CONFIGURATION_STEREO,// 声道数:双声道 AudioTrack.ENCODING_PCM_16BIT // 采样精度:一个采样点 16 比特,相当于 2 个字节 // 2. 创建 AudioTrack // 创建 AudioTrack 对象 // native_setup(): 创建一个本地 AudioTrack 与 AudioFlinger 建立联系 // AudioTrack 与底层服务间又提供了 AudioSystem 和 AudioService // 前者同时提供了 Java 和 Native 两层的接口实现 // 而 AudioService 则只有 Native 层的实现 // 这样就降低了使用者(AudioTrack)与底层服务(AudioPolicyService, AudioFlinger 等)间的耦合 // 【只要 AudioSytem 和 AudioService 向上接口不变】 // 【那么 AudioTrack 就不需要做任何修改】 // AudioTrackThread: 用于给 AudioFlinger 发数据 // AudioTrack 在 AudioFlinger 内部以 Track 类来管理的 // AudioTrack 与 AudioFlinger 通过 IAudioTrack 通信 // AudioFlinger 服务名:media.audio_flinger // // AudioTrack::set() // // 此线程用于主动从用户那里获取数据时用 // mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); // mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/); // // // create the IAudioTrack // status_t status = createTrack_l(); // AudioSystem::getOutputForAttr() // AudioPolicyService()::getOutputForAttr() // AudioPolicyManager()::getOutputForAttr() // 1. getStrategyForAttr(): 获取 stream 音频类型对应的 Strategy // 每种 Stream 类型都有对应的路由策略(Routing Strategy) // getDeviceForStrategy(): 进一步为这一 Strategy 匹配最佳的音频设备 // 2. getOutputForDevice(): 应用策略,判断哪些 Output 符合用户传入的 stream 类型 // 用于获得所有支持 device 设备的 output, 并添加到 outputs 中 // 3. selectOutput(): 选择最适合的 Output // 1> 处理一些特殊情况 // 2> 开始择优判断,逐个处理所有 Outputs // 3> 根据优先级做出最后的选择 // Flag 与要求相似度最高的 Output // Primary Output // 如果上面两种都找不到,则默认返回第一个 Output // // // 该函数返回 IAudioTrack(实现上是 BpAudioTrack)对象,后续 AF 和 AT 的交互就是围绕 IAudioTrack 进行的 // audioFlinger->createTrack() // ----------- Binder -------------------------- // AudioFlinger::createTrack() // # 他会通过 AS 查询 APS 获得 AF 中的对应硬件的播放线程 // # 然后创建一个 Track 添加到此播放线程中去 // // # 根据索引号找到一个工作线程,PlaybackThread,例如 MixerThread 线程 // # 此工作线程与 HAL 输出对象相关,在 AudioPolicyService 中创建 // # 在新的工作线程对象中创建一个 Track 对象,用于写音频到硬件 // # 然后再创建一个 TrackHandler 的 Binder 代理对象 // # 用来通过 Binder 接收请求,让新工作线程 Track 处理 // // 1> 选择工作线程:AudioFlinger::checkPlaybackThread_l() // AF 会创建几个工作线程,AT 会找到对应的工作线程 // checkPlaybackThread_l(output); // AudioFlinger::openOutput(): 产生唯一的 audio_io_handle_t // 而 AudioTrack 调用 createTrack 时,需要传入这个全局标记值,从而找到匹配的 PlaybackThread // // 2> AudioFlinger::PlaybackThread::createTrack_l() // 创建 Track 对象,添加到内部数组 mTracks 中 // createTrack_l() // 找到匹配的 PlaybackThread 后,在其内部创建一个 PlaybackThread::Track 对象 // // 3> Track 创建共享内存和 TrackHandler // Track 创建了共享内存 // CB 对象通过 placement new 方法创建于这块共享内存中 // Track 没有基于 Binder 通信,所以不能接收远端请求 // 这里用其初始化一个代理对象 TrackHandle // TrackHandle 能基于 Binder 通信,可接收远端通信 // 并能调用 Track 相应函数进行处理,这就是代理模式 // // TrackHandle():实际就是 IAudioTrack AudioTrack trackplayer = new AudioTrack( AudioManager.STREAM_MUSIC, // 音频流类型 800, // 设置音频数据的采样率 32k,如果是44.1k就是44100 AudioFormat.CHANNEL_CONFIGURATION_STEREO, // // 音频流的类型:和 Audio 系统对音频的管理策略有关 STREAM_ALARM: 警告声 STREAM_MUSIC: 音乐声,例如 music 等 STREAM_RING: 铃声 STREAM_SYSTEM: 系统声音,例如低电提示音、锁屏音等 STREAM_VOCIE_CALL: 通话声 AudioFormat.ENCODING_PCM_16BIT,// 设置音频数据块是8位还是16位,这里设置为16位。好像现在绝大多数的音频都是16位的了 bufsize, AudioTrack.MODE_STREAM // 数据加载模式,在这里设置为流类型,另外一种MODE_STATIC // AudioTrack 数据加载模式: #MODE_STREAM: 在这种模式下,通过 write 一次次把音频数据写到 AudioTrack 中。这和平时通过 write 系统调用 往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的 Buffer 中拷贝到 AudioTrack 内部的 Buffer 中,这在一定程序上会引起延时。 #MODE_STATIC: 这种模式下,在 play 之前只需要将所有的数据通过一次 write 调用传递到 AudioTrack 的内部缓冲区 中,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它 也有一个缺点,就是一次 write 的数据不能太多,否则系统无法分配足够的内存来存储全部数据。 ); // 3. 开始播放 // AT 调用 IAudioTrack.start() 由于 TrackHandler 的代理作用 // 实际上会由具体的 Track 对象进行处理 // Track 代表一路音频流,他需要输出到 MixerThread 中的硬件设备中 // Track::start(): // 通过 AS 查询 APS 获得 AF 对应硬件的 MixerThread 线程 // 调用其 addTrack_l() 函数 // AudioFlinger::PlaybackThread::addTrack_l(): // 设置重试次数,等待数据写 write() // 将此 Track 添加到 MixerThread 的活跃队列 mActiveTracks // 广播一个事件,触发 MixerThread 线程,通知有活跃数组加入 // // ----------- MixerThread 线程 ------------------- // 1> MixerThread 接收广播事件后( track.start() 在 apk 调用那块写了) // Thread 类线程工作都是在 threadLoop() 中完成 // MixerThread::threadLoop() // 首先处理通知信息或配置请求,比如向监听者通知 AF 信息 // 或者根据配置请求进行音量控制、声音设备切换等 // // 然后调用 prepareTrack_l() 检查活跃的 Tracks 是否有数据 // 2> prepareTrack_l() 和 process() 分析 // prepareTrack_l(): // 一个混音器可支持 32 个 Track, 依次检查活跃 Track // 对当前活跃 Track, 输入数据设置 AudioMixer 要进行混音处理 // // 调用混音器对象 AudioMixer:process() 进行混音处理 // process(): // 对需要根据 prepareTrack_l() 设置混音参数 // 对活跃 Track 输入音频数据调用 hook() 函数进行处理 // 3> AudioMixer 对象分析 // 在其构造函数中初始化 32 路 Track 和其 hook() 函数 // 支持的 hook() 函数有: // process__validate: 根据 Track 格式、数量选择其他处理函数 // process__nop: 什么也不做 // process__genericNoResampling: 普通无需重采样 // process__genericResamplinge: 普通需重采样 // process__OneTrack16BitsStereoNoResampling: 一路音频流,双声道,PCM16 格式,无需重采样 // process__TwoTrack16BitsStereoNoResampling: 两路音频流,双声道,PCM16,无重采样 // 4> 在 AF prepare_l() 会为每个准备好的 Track 使能混音标志 // AudioMixer->enbale() 使能混音,设置 hook() 函数为 procss_validate() // AudioMixer::process__validate(): 会根据 Track 情况设置合适 hook() 函数 // 调用音频输出对象 AudioOutputStream.write() 输出音频到设备 trackplayer.play(); 。。。 // 4. 调用 write 写数据 // 5> 怎么消费数据 // 数据是在 AT 中通过 ObtainBuffer() 得到缓冲区 // 然后 memcpy 写入,最后 releaseBuffer() 释放缓冲区得到的 // 消费数据就是在对应 hook() 中进行的,如在 // ------------ 消费数据 ------------------ // AudioMixer::process__oneTrack16BitsStereoNoResampling(): // 首先找到被激活的 Track,即本 hook() 绑定的 Track 对象 // 然后通过 getNextBuffer() 获得可读数据缓冲 // // 再然后进行数据处理,即混音 // // 最后调用数据复制到 out 缓冲,得到混音后数据 // // 调用 Track 的 releaseBuffer() 释放缓冲区 // // 6> getNextBuffer 和 releaseBuffer 分析 // getNextBuffer(): 从缓冲区中得到一块可读空间 // 即根据 CB 记录的读写位置等计算可读缓冲区位置 // 首先通过 frameReady() 得到可读帧数 // 然后根据可读帧数等信息等到可读空间首地址 // releaseBuffer(): 通过 stepServer() 更新读位置 trackplayer.write(bytes_pkg, 0, bytes_pkg.length); // 往 track 中写数据 。。。 // 5. 停止播放和释放资源 // 1> TrackHandle 和 Track 的回收 // 来自 AT 的 stop() 请求最终会通过 TrackHandle 这个代理 // 交给具体的 Track 的 stop 进行处理 // AudioFlinger::PlaybackThread::Track::stop(): // 如果 Track 最初牌活跃数组中,则 // 设置 Track 状态为停止 STOPPED 状态 // 因为在 MixerThread::prepareTrack_l() 中,如果 AT 写 // 数据快,而 AF 消耗快,则声音还是会在调用 stop() 后听到 // 所以这里还有其他处理操作 // 这就是 AT 端 stop 后会被很快 delete, 导致 AF 端的 // TrackHandle 也被 delete // 所以会调用到 TrackHandle 的析构函数 // 这里会调用到 MixerThread->destroyTrack_l(),即 playbackThread->destroyTrack() // 将不在活跃数组的 Track 从 mActiveTracks 数组移出 // deleteTrackName_l(): 由 PlaybackThread 子类实现,回收一些资源 // TrackHandle 的 delete 会导致所代理的 Track 对象也被删除 // // 2> Client 的回收 // 前面说过,凡是使用 AT/AR 的线程,都会被 AF 当作 Client 对象 // Client 是 AudioFlinge 对客户端的封装, AF 的 Client,并且 Client 用它的进程 pid 为标识 // Track 的析构,会导致它的基类 TrackBase 析构函数被调用 // AudioFlinger::ThreadBse::TrackBase::~TrackBase() // 释放 定位 new 声明的对象 // 如果 mClient 强弱引用计数都为 0,则导致该 Client 被 delete trackplayer.stop(); // 停止播放
########################################### # 声音路由切换实例分析:邓凡平 2.2 ########################################### // 这一切主要介绍耳机插入,详细介绍下声音路由切换实例 // ######################### // #1. 耳机插拔事件的处理 // ######################### // 耳机插入后,系统会发出一个广播,Java 层的 AudioService 会接收这个广播 // 调用内部类 AudioServiceBroadcastReceiver 处理该事件 // // //1> 耳机插拔事件的接收 // AudioServiceBroadcastReceiver::onReceive() // 判断耳机状态,是插入还是拔出,调用 // AudioSystem::setDeviceConnectionState() 设置设备连接状态 // // //2> setDeviceConnectionState: 设置设备连接状态 // 直接调用 jni 的函数处理 // ------------ jni ------------------------- // android_media_AudioSystem_setDeviceConnectState() // 调用 Native 的 AudioSystem::setDeviceConnectionState() // 调用 AMB 处理,AMB 是需要厂家实现的,但一般直接用 AudioPolicyManagerBase 实现 // AudioPolicyManagerBase::setDeviceConnectionState() // 一次只能设置一个设备 // 根据设备号判断是不是输出设备,耳机属于输出设备 // getNewDevice():得到一个 MixerThread 对应的硬件设备 // // //3> getNewDevice() // 根据索引号找到对应的 AudioOutputDescriptor // AudioOutputDescriptor: 用来记录并维护与输出设备 DSP 相关的信息, // 此对象在 AudioPolicyManagerBase 构造函数中创建的 // 如使用该设备硬件上,他代表的是 DSP 设备, // 如 PMIC 中的 Audio Codec // 此对象是 AMB 用来控制和管理音频输出设备的 // 的流个数,各个流音量该设备所支持的采样率,采样精度等 // // 当前应用场景为正在听歌,会走 getDeviceForStrategy(STRATEGY_MEDIA) // AudioPolicyManagerBase::getDeviceForStrategy() // 重新计算策略所对应的输出设备 // 此函数会根据当前策略,以及通话、蓝牙状态,选择输出硬件设备 // // updateDeviceForStrategy(): 更新各种策略使用的设备 // //4> AudioPolicyManagerBase::updateDeviceForStrategy() // 重新计算每种策略使用的设备,保存到 mDeviceForStrategy[] 中,启 cache 作用 // mdeviceForStrategy[] = getDeviceForStrategy() // // setOutputDevice(): 设置新的输出设备 // //5> AudioPolicyManagerBase::setOutputDevice() // 创建请求,需要发送到 AF 对应的工作队列中进行处理 // 比如 DSP 的 MixerThread 或者蓝牙的 MixerThread // AudioParameter param = AudioParameter() // 调用 AP 的对象进行发送处理 // // mpClientInterface->setParameters() // 最终调用到 APS 的 setParameters() // AudioPolicyService::setParameters() // 把这个请求加入到 AudioCommandThread 中处理 // 此线程在 AudioPolicyService 构造时创建 // 创建 AudioCommandThread 用于处理控制命令,例如路由切换、音量调节等 // ######################### // #2. AudioCommandThread // ######################### // AudioCommandThread 有一个请求处理队列 // AP 负责往该队列提交请求,而 AudioCommandThread // 在它的线程函数 threadLoop 中处理这些命令 // //1> AudioCommandThread::threadLoop() // case STOP_TONE: TONE 处理 // case SET_VOLUME: 设备音量 // case SET_PARAMETERS: 处理路由设置请求 // 转到 AudioSystem 处理 // //2> AudioSystem::setParameters() // 交给 AF 处理 // AudioFlinger::setParameters() // 根据索引号找到对应的混音线程 MixerThread // 将请求交由混音线程处理 // //3> MixerThread::treadLoop() // MixerThread::checkForNewParameters() // 路由设置需要硬件参数,直接交给代表 // 音频输出设备 HAL 对象处理 // // //4> HAL 对象的处理例 // 以高通公司为例 // AudioHardware::AudioStreamOutMSM72xx:setParameters() // AudioHardware::doRouting() // AudioHardware::doAudioRouteOrMute() // 硬件相关的代码 // do_route_audio_dev_ctrl() // open(/dev/msm_audio_ctl) // ioctl(): 通过 ioctl 切换设备 // applyStreamVolumes(): 设置音量 // ########################################### # DuplicatingThread破解:邓凡平 2.2 ########################################### // 当一份数据同时需要发给 DSP 和蓝牙 A2DP 设备时使用 DuplicatingThread // // 1. DuplicatingThread 的来历 // 假设已经连接上了一个蓝牙耳机,耳机中断处理? // 会调用到 AudioPolicyManagerBase::setDeviceConnectionState() 设置设备连接状态 // AudioPolicyManagerBase::setDeviceConnectionState() // 专门处理 A2DP 设备的连接 // handleA2dpConnection() // 先为 mA2dpOutput 创建一个蓝牙用的 MixerThread // SONIFCATION 策略的音频流类型需要同时从蓝牙和 DSP 中传出 // 代表音频流有:来电铃声、短信通知等 // 所以要创建一个 Duplicateoutput 线程,传入参数为蓝牙 MixerThread // mpClientInterface->openDuplicateOutput() // # openDuplicateOutput() 结果示意图: // // [DuplicatingThread 线程] // .mOutputTracks // | | // | | // OutputTrack_0 OutputTrack_1 // | | // | | // [蓝牙 MT 线程] V V [DSP MT 线程] // .mTracks .mTracks // // // 蓝牙中有一个成员为 OutputTrack() // DT 的 mOutputTracks 也有一个成员指向 OutputTrack() // // 【这就好像 DT 是 MT 的客户端一样】 // 与前面的 AT 是 AF 的客户端类似 // // // # 3. DT 的客户端 AT // DT 是从 MT 中派生的,根据 AP 和 AT 的交互流程可知 // 当 AT 创建流类型对应策略为 SONIFACATION 时 // 他会从 AP 中得到代表 DT 的线程索引号 // // 由于 DT 没有重载 createTrack_l(), 因此也会类似 MT 过程 // 创建一个 Track, 用于跟踪音频流 // 然后配合两个 OutputTrack 进程内缓冲 // 把来自 AT 的音频数据原封不动的发给蓝牙 MT 和 DSP MT // // // # 有 AT 的 DT 全景图 // // [AudioTrack] // IAudioTrack // /\ // || // \/ // mTracks // [DuplicatingThread] // mOutputTracks // | | // OutputTrack_0 OutputTrack_1 // | | // mTracks mTracks // [蓝牙 MT] [DSP MT] // // AT 通过 Track 将音频流数据发给 DT // DT 通过 OutputTrack() 将数据分别发给蓝牙 MT 和 DSP MT // // // # 4. DT 的线程函数 // AF/DuplicatingThread::threadLoop() // 和 MT 处理一样,处理配置请求 // 如果 AT 的 Track 停止了,则需要停止和 MT 共享的 OutputTrack // prepareTracks_l(): DT 派生自 MT, 功能一样,检查活跃的 Tracks 是否有数据 // outputsReady(): 检查 OutputTracks 对应的 MT 状态 // 调用 AudioMixer 进行混音处理 // outputTracks[]->write(): 将混音后的数据写到 outputTrack 中 // AT 调用 start() 将导致 DT 的 Track 加入到上面的活跃数组中 // 蓝牙 MT 与 DSP MT 的则在 write() 将 OutputTrack 加入对应线程活跃数组 // AF/PT/OutputTrack::write() // start(): 如果此 Track 没有活跃,则调用 start() 激活 // 现在 AF 中的数据传递有三个线程:一个 DT, 两个 MT // MT 作为二级消费者可能来不及消费数据 // 所以 DT 提供了一个缓冲区进行缓冲来不及处理的数据 // 最多可缓冲 10 组数据 // 数据就这样通过 DT 的帮助,从 AT // 传输到蓝牙 MT 和 DSP 的 MT 中 // 缺点:数据传输比直接使用 MT 传输要缓慢 // // # 最终的处理都是在 AF 中 // AudioFlinger::openDuplicateOutput() // 获得对应蓝牙的 MixerThread // 获得对应 DSP 的 MixerThread // new DuplicatingThread(): 创建 DuplicatingThread, 传入第二个参数是 MixerThread // 2. AF/DuplicatingThread::DuplicatingThread() // DT 是 MT 的派生类,所以先要完成基类的构造,它会创建一个 AudioMixer // addOutputTrack(): 然后把代表 DSP 的 MT 加入进来 // AF/DuplicatingThread::addOutputTrack() // 构造一个 OutputTrack,第一个参数是 MT // AF/PT/OutputTrack::OutputTrack(): // OutputTrack 从 Track 派生,所以先调用基类的构造,创建一块内存 // 内存的结构如图 7-4 所示,前面为 CB 对象,后面为数据缓冲 // 然后将这个 Track 加入到 MT 的 Track 中 // 表示 DT 将往 MT 中写数据 // 把这个 OutputTrack 加入到 mOutputTracks 数组保存 // 加入代表 DSP 的 MixerThread // DuplicatingThread->addOutputTrack() // 经过上面两步,DT 分别构造了两个 OutputTrack // 一个对应蓝牙的 MT,另一个对应 DSP 的 MT // // 然后创建一个 new AudioOutputDescriptor 对象 // AudioOutputDescriptor: 用来记录并维护与输出设备 DSP 相关的信息, // 此对象在 AudioPolicyManagerBase 构造函数中创建的 // 如使用该设备硬件上,他代表的是 DSP 设备, // 如 PMIC 中的 Audio Codec // 此对象是 AMB 用来控制和管理音频输出设备的 // 的流个数,各个流音量该设备所支持的采样率,采样精度等 ########################################### # 音量控制: 林学森 4.3 ########################################### // 1. AudioManager // 当用户按下音量调节键 // public static KeyEvent extends InputEvent implements Parcelable // 1. AudioManager.java // handleKeyDown() // adjustSuggestedStreamVolume() // IAudioService service = getService() // 服务名为 = audio // service.adjustSuggestedStreamVolume() // ------- AudioService.java ---------------------- // adjustSuggestedStreamVolume() // getActiveStreamType(): 获得当前流类型 // adjustStreamVolume() // 函数重点: // 1> 计算 oldIndex(之前的音量值),index(要调整的音量值)和 flags // 2> 调用 sendVolumeUpdate 和 sendMsg 把上一步的计算结果发送出去 // 1. 为各 StreamType 寻找 Alias 归类 // 获取当前streamType 对应的 alias // 2. 为 Stream Alias 寻找匹配的 device // 根据 stream alias 来查询匹配的输出设备 // 3. 获取对应 device 的 index // 通过 VolumeStreamState() 获得音量值(index) // VolumeStreamState(): 维护了一个 mIndex 数组来记录 device 对应的 index 值 // 4. 调节 index // 为 UI 显示条做前期准备 // 5. 音量调节对于音量模式的影响 // 音量的调节还可能与手机的铃声模式(Ringer Mode)有关: // 静音模式 // 震动模式 // 正常模式 // 6. 将音量调节事件发送给下一个处理者 // sendMsg():把命令投递到消息队列中 // 再由 AudioHandler 做进一步处理 // AudioHandler 除了将音量值保存到系统设置文件中外 // 还会调用 AudioPolicyService 的相关接口来真正调整音频设备音量 // ------------- AudioPolicyService.cpp ------------------- // AudioPolicyServiceService::setStreamVolumeIndex() // AudioPolicyManagerBase::setStreamVolumeIndex() // 循环查找所有匹配设备 // 通过 checkAndSetVolume() 进行音量设置 // ----------- AudioFlinger.cpp ---------------- // AudioFlinger::setStreamVolume() // 首先根据 output 找到它对应的 PlaybackThread // 而后交由这个线程具体处理流的音量 // AudioFlinger::PlaybackThread::setStreamVolume() // mStreamTypes[stream].volume = value // ----- 会在 PlaybackThread::threadLoop() 处理 ---------- // AudioFlinger::MixerThread::prepareTracks_l() // 之前 setStreamVolume() 设置的音量值在这里会被提取出来 // 并和主音量等其他因素进行综合运算 // mAudioMixer->setParameter() // 设置音量 // 同时把新的值记录到 mStreamTypes 中 // sendVolumeUpdate():用于产生音量调节提示音并显示系统音量条 // 2. interceptKeyBeforeQueueing@PhoneWindowManager.java // 对于部分重要的物理按键(比如 HOME 和音量调节键) // 系统会先判断它们是否需要做预处理,即 interceptKeyBeforeQueueing // 这个函数会根据当前的具体情况(是不是在通话状态,是不是在播放音乐等) // 来决定需要调整的 STREAM 类型 // 而且最后它会调用 AudioService.adjustStreamVolume() // 接下来的处理流程就和上面 AudioManager 中的情况一致了
Linux 部分参考资料:
UDA1341TS 芯片手册
wm8993 音频芯片手册
mini2440原理图
AudioCODEC基本知识及应用_百度
相关内核源码 2.6/3.4.2
韦东山一期/三期声卡相关视频及相关源码
http://blog.csdn.net/droidphone Alsa 相关博客
Android 部分参考:
高通安卓 8.0 源码
深入理解Android内核设计思想_林学森./第 13 章 应用不再同质化 – 音频系统
深入剖析 Android 系统_杨长刚/第 14 章 Audio
深入理解 Android 卷1_邓凡平/ 第7章 深入理解 Audio 系统
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。