Linux 音频驱动(六) ALSA音频驱动之PCM Write数据传递过程

本文详细介绍了Linux音频驱动中,PCM回放数据从用户空间通过内核空间传递到DMA缓冲区,最终到达Codec的过程。通过ioctl系统调用,tinyalsa接口将音频数据拷贝到内核,然后由DMA传输到I2S TX FIFO,再由Codec转换成模拟信号播放。讨论了trigger函数的执行顺序及其潜在影响。
摘要由CSDN通过智能技术生成

1. 前言

本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec。
Linux 音频驱动(一) ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图:

在这里插入图片描述

  1. 对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
  2. DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。
  3. 通过I2S总线,将音频数据传送到Codec。
  4. 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 xx.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;
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值