android soundrecorder之一 linux alsa 音频架构

转载请标注原文地址: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板的音频系统框图:

android soundrecorder之一 linux alsa 音频架构_第1张图片

3G音频接口PCM直接与codec相连,通话时音频数据走向和AP没有关闭,通话录音除外

 

2. 介绍下注册一个alsa类型声卡涉及到的几个paltform_device和关键数据结构的系统框图:

android soundrecorder之一 linux alsa 音频架构_第2张图片

 注册声卡,文件位置:/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初始化流程,先看图:

android soundrecorder之一 linux alsa 音频架构_第3张图片

从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初始化的关键:

  • 创建pcm device
  • 为capture和playback创建substream
  • 设置操作函数snd_pcm_ops
  • dma_new时分配snd_dma_buffer,录音时dma控制器会从i2s的rx_fifo中拷贝数据到这个buffer,然后hal层通过iotcl请求数据时,回调用copy_to_user将音频数据拷贝到用户空间。buffer中area是mmap之后的虚地址,addr是对应物理地址,dma需要访问物理地址,而应用访问的是虚地址。

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

 

你可能感兴趣的:(linux,架构,音频,alsa)