转载请标注原文地址:http://blog.csdn.net/uranus_wm/article/details/12851111
前两篇文章分别介绍了linux alsa结构和android soundrecorder的应用层实现
除了板级的初始过程,其他都是关于类层次关系的一些静态说明
这章主要介绍下录音的动态过程,以及数据是如何一步步获取和保存的
还是先上图:
这个图大致介绍了soundrecord的流程,alsa到kernel部分还有个control模块这里没有画出来
下面一张图里面会提到,实际上control模块还是影响我们代码量主要的模块,主要就是mixer和muxer的配置
HAL层以上前面已经说过了,这里我们关注HAL以下部分:
- 应用层点击录音按钮
- tinyalsa打开设备文件节点/dev/snd/pcmC0D0c,给出相应的采样通道,采样率,一帧数据大小,总buffer大小等
- tinyalsa打开设备文件节点/dev/snd/controlC0,设置codec内部相关寄存器,选择输入输出device,配置内部route,即所谓dapm
- tinyalsa通过pcmC0D0c触发录音开始,dai_link上时钟开启,dapm建立后,从main_mic接收的pcm信号就会通过codec内部route发送到AIFADCDAT线上cpu exynos4412这边AIFDACDAT接收pcm信号数据后,通过移位器将数据保存到I2S的RX_FIFO
- RX_FIFO收到数据,触发DMA控制器开始拷贝数据,DMA(pl330)将RX_FIFO中的数据拷贝到DMA专有的内存缓冲,本例中snd_dma_buffer起始物理地址addr=0x6036_0000
- tinyalsa通过ioctl向设备节点/dev/snd/pcmC0D0c发起读数据请求,snd_pcm会将dma缓冲buffer中的数据拷贝到tinyalsa传入的用户buffer中
下面这张顺序图详细记录了soundrecorder开启录音之后alsa的交互流程:
这里我要提到的是在snd_pcm_open之后,有很大一段代码是做constraint检查
最开始我在调试的时候这个constraint检查一直不过,也就是上面的1.1节点,然后后面的过程没法进行下去
snd_pcm_open()时增加了一大推rule;这些rule的作用其实就是检查tinyalsa指定的config对于当前dai_link是否支持
int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
int k, err;
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
snd_mask_any(constrs_mask(constrs, k));
}
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
snd_interval_any(constrs_interval(constrs, k));
}
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
snd_pcm_hw_rule_format, NULL,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
snd_pcm_hw_rule_sample_bits, NULL,
SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
snd_pcm_hw_rule_div, NULL,
SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS,
snd_pcm_hw_rule_mul, NULL,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
.......
}
其过程是这样的:
首先snd_pcm_hardware会声明自己支持的通道数,格式等,buffer_bytes_max是buffer总大小,其他值我也没有详细研究
基本按照linux原生拷贝,没有修改
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
codec_dai这边声明如下:
static struct snd_soc_dai_driver wm8994_dai[] = {
{
.name = "wm8994-aif1",
.id = 1,
.playback = {
.stream_name = "AIF1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.capture = {
.stream_name = "AIF1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
},
.ops = &wm8994_aif1_dai_ops,
},
};
cpu_dai这边声明如下: