1. 前言
本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec。
在Linux 音频驱动(一) ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图:
- 对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
- DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。
- 通过I2S总线,将音频数据传送到Codec。
- Codec内部经过DAC转换,将模拟信号传到扬声器SPK(头戴式耳机HP、耳塞式耳机Earp)。
下面基于源码讲解PCM Data Flow。
2. PCM Data Flow
内核版本:Kernel 版本:3.10
内核源码文件:
./kernel-3.10/sound/core/device.c
./kernel-3.10/sound/core/init.c
./kernel-3.10/sound/core/pcm.c
./kernel-3.10/sound/core/pcm_lib.c
./kernel-3.10/sound/core/pcm_native.c
./kernel-3.10/sound/soc/soc-pcm.c
Tinyalsa源码文件:
./external/tinyalsa/pcm.c
./external/kernel-headers/original/uapi/sound/asound.h
User Space
在我的源码包里,用户空间应用程序使用的是 tinyalsa提供的接口write PCM Data,即播放音频文件。
Write PCM逻辑设备是通过 ioctl() 函数完成的,即应用程序将需要播放的音频数据通过pcm_write() --> ioctl() 传递到内核。
// ./external/kernel-headers/original/uapi/sound/asound.h, line 448
struct snd_xferi {
snd_pcm_sframes_t result;
void __user *buf;
snd_pcm_uframes_t frames;
};
// ./external/tinyalsa/pcm.c, line 483
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
struct snd_xferi x;
......
x.buf = (void*)data;
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
......
ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x);
......
}
音频数据中的几个重要概念:
Format:样本长度(采样精度 or 采样深度),音频数据最基本的单位,常见的有 8 位和 16 位;
Channel:声道数,分为单声道 mono 和立体声stereo;
Frame:帧,构成一个完整的声音单元,Frame = Format * Channel;
Rate:又称 sample rate:采样率,即每秒的采样次数,针对帧而言;
Period size:周期,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,以此为单位;
Buffer size:数据缓冲区大小,这里指runtime 的 buffer size,而不是结构体 snd_pcm_hardware 中定义的buffer_bytes_max;一般来说 buffer_size = period_size * period_count,period_count 相当于处理完一个 buffer 数据所需的硬件中断次数。
为了通过系统调用ioctl()
传递音频数据,定义了struct snd_xferi x
,x.buf
指向本次要播放的音频数据,x.frames
表示本次音频数据总共有多少帧(frame)。
Kernel Space
通过系统调用ioctl()
传递数据到内核,在内核空间是PCM逻辑设备对应的snd_pcm_f_ops[0].unlocked_ioctl()
。
// ./kernel-3.10/sound/core/pcm_native.c, line 3481
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
......
.unlocked_ioctl = snd_pcm_playback_ioctl,
......
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
......
.unlocked_ioctl = snd_pcm_capture_ioctl,
......
}
};
snd_pcm_playback_ioctl()
直接调用了snd_pcm_playback_ioctl1()
。
// ./kernel-3.10/sound/core/pcm_native.c, line 2784
static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_pcm_file *pcm_file;
pcm_file = file->private_data;
......
return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, (void __user *)arg);
}
snd_pcm_playback_ioctl1()函数:
a. 定义struct snd_xferi xferi
,为了获取用户空间传来的arg;
b. 调用put_user()
清除snd_xferi.result状态;
c. 调用copy_from_user()
将取用户空间的arg拷贝到内核空间,即拷贝音频数据存储空间的指针buf和音频数据帧数frames;
d. 调用snd_pcm_lib_write()
;
e. 调用put_user()
回填write结果_xferi->result。
// ./kernel-3.10/sound/core/pcm_native.c, line 2624
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
{
......
switch (cmd) {
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
{
struct snd_xferi xferi;
struct snd_xferi __user *_xferi = arg;
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t result;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
if (put_user(0, &_xferi->result))
return -EFAULT;
if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
return -EFAULT;