转载请标注原文地址:http://blog.csdn.net/uranus_wm/article/details/11595647
平台配置:samsung exynos4412 + wm8994(wolfson audio codec) + lsu6300v(龙尚wcdma modem)
半年前调试wm8994语音通话和录音功能时,参考了droidphone和spenic两位博友的帖子
他们对linux alsa架构有深入的了解,帖子也写得比较详细,可惜对于新手上手调试还是碰到很多问题不理解,特别是调试手段方面
在此我将自己的调试过程做个备忘录,也希望对广大苦逼的程序猿有所帮助,难免有错,还望不吝指出:
这篇文章主要介绍linux alsa的数据结构和初始化过程
后面一篇文章重点介绍soundrecord的控制和数据流程
1.说明下demo板的音频系统框图:
3G音频接口PCM直接与codec相连,通话时音频数据走向和AP没有关闭,通话录音除外
2. 介绍下注册一个alsa类型声卡涉及到的几个paltform_device和关键数据结构的系统框图:
注册声卡,文件位置:/sound/soc/samsung/smdk_wm8994.c
static struct snd_soc_dai_link smdk_dai[] = { { /* Primary DAI i/f */ .name = "WM8994 AIF1", .stream_name = "Pri_Dai", .cpu_dai_name = "samsung-i2s.0", .codec_dai_name = "wm8994-aif1", .platform_name = "samsung-audio", .codec_name = "wm8994-codec", .init = smdk_wm8994_init_paiftx, .ops = &smdk_ops, }, { /* Sec_Fifo DAI i/f */ .name = "Sec_FIFO TX", .stream_name = "Sec_Dai", .cpu_dai_name = "samsung-i2s.4", .codec_dai_name = "wm8994-aif1", #if defined(CONFIG_SND_SAMSUNG_NORMAL) || defined(CONFIG_SND_SAMSUNG_RP) .platform_name = "samsung-audio", #else .platform_name = "samsung-audio-idma", #endif .codec_name = "wm8994-codec", .init = smdk_wm8994_init_paiftx, .ops = &smdk_ops, }, { /* Second DAI i/f */ .name = "WM8994 AIF2", .stream_name = "Voice_Dai", .cpu_dai_name = "smdk-voice-dai", .codec_dai_name = "wm8994-aif2", .platform_name = "snd-soc-dummy", .codec_name = "wm8994-codec", .init = smdk_wm8994_init_paiftx, .ops = &smdk_voice_ops, } }; static struct snd_soc_card smdk = { .name = "SMDK-WM8994", .dai_link = smdk_dai, .num_links = ARRAY_SIZE(smdk_dai), };
BSP初始化的时候,注册了一个名为"soc-audio"的平台设备,该设备有一个私有数据结构体snd_soc_dai_link
本例中smdk_dai总共有三个dai_link,具体说明一下:
前两个dai_link分别是Primary DAI和Sec_Fifo DAI在物理层上其实都是exynos4412的I2S0与wm8994的aif1相连接
只不过软件上用了多路复用,表示层两个dai_link。作用在4412这边Sec_Fifo DAI即"samsung-i2s.4"这个虚拟端口支持更高的采样率
但它只能用于playback,不能用于capture;因此soundrecord只能用Primary DAI。另外Sec_Fifo DAI在使用dma时用的是idma,
即snd_dma_buffer来自于iram。
第三个dai_link是用于电话语音,物理层上由codec wm8994的aif2与lsu6300v的pcm接口相连
再说明下snd_soc_dai_link的结构体成员:
.name = "WM8994 AIF2"指定dai_link的名字,可以随便取
.stream_name = "Voice_Dai"指定cpu侧stream名称,可以随便取
.cpu_dai_name = "smdk-voice-dai" 指定与codec_dai对应连接的cpu_dai的名字,本例中我们必须为lsu6300v声明注册一个cpu_dai,如:
{ .name = "smdk-voice-dai", .id = 3, .playback = { .channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, //.rates = SNDRV_PCM_RATE_8000, //.formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { .channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, //.rates = SNDRV_PCM_RATE_8000, //.formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &smdk_voice_dai_ops, },
由于lsu630v和4412本身没有关联,注册cpu_dai完全是为了满足alsa的需要,所以注册的位置随便放在哪里都可以,如"snd-soc-dummy"设备里,甚至可以放在wm8994注册的dai之后。
.codec_dai_name = "wm8994-aif2" 指定用哪个codec_dai,名字必须与codec里面注册的dai name相同
.platform_name = "snd-soc-dummy"指定cpu lsu6300v的platform,可以不写默认用的就是"snd-soc-dummy"这个虚拟设备,但最好另写一个,因为后面注册snd_pcm_hardware时需要用到这个设备
.codec_name = "wm8994-codec":指定codec的名称,需与wm8994注册的名字一样
.init = smdk_wm8994_init_paiftx:指定初始化函数,open此dai_link时会调用此函数,主要用来开启关闭一些dapm pin
.ops = &smdk_voice_ops:指定此dai_link的操作函数open此dai_link时会调用这类函数
再说明一下框图中的snd_soc_pcm_runtime和snd_pcm_hardware:
整个soc_core都是以snd_soc_pcm_runtime为桥梁来操作,可以这么理解:每一个物理连接对应一个或多个dai_link
每个dai_link对应一个snd_pcm_hardware设备,而snd_soc_pcm_runtime就是这个设备的驱动私有数据,snd_soc_pcm_runtime包含一个snd_pcm成员
snd_pcm才是真正保存音频数据的结构体,其成员stream内部有多个substream分别用于playback和capture
因此我们在增加dai_link时一定要注意注册snd_pcm_hardware。
static const struct snd_pcm_hardware dummy_dma_hardware = { .formats = 0xffffffff, .channels_min = 1, .channels_max = UINT_MAX, /* Random values to keep userspace happy when checking constraints */ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, .buffer_bytes_max = 128*1024, .period_bytes_min = PAGE_SIZE, .period_bytes_max = PAGE_SIZE*2, .periods_min = 2, .periods_max = 128, }; static int dummy_dma_open(struct snd_pcm_substream *substream) { snd_soc_set_runtime_hwparams(substream, &dummy_dma_hardware); return 0; }
说到snd_pcm_hardware这个结构体,后面讲alsa init的时候还要讲到这个结构体的用途
3.linux alsa初始化流程,先看图:
从1到5是设备的probe过程:
1:dma_probe,由于soundrecorder用到dma,所以其platform其实就是dma,常用的是arm的pl330
2:snd_soc_dummy_probe,由于语音通话的platform是3G模块,它和4412没有连接,所以用了一个dummy设备作为platform
8:snd_soc_dapm_add_route,关于dapm有必要专门一章介绍,这里不做详述
10:soc_new_pcm之后才是alsa初始化的关键:
12:probe_dai_link,这里会创建设备节点/dev/snd/pcmCxDxp,Cx表示系统第几个codec,Dx表示第几个dai_link,最有一个字母p表示playback,c表示cpature
android HAL层通过tinyalsa访问这些设备节点,以实现和codec的数据通信。
至此alsa init过程基本完成,录音的实现请看下面一篇文章!
http://blog.csdn.net/uranus_wm/article/details/12748559