音频数据通路解析

现在来介绍wav文件的pcm数据从用户层到内核层最后到硬件codec的整个通路

用户层

从tinyplay和tinycap入手,最后会到pcm_read()和pcm_write(),这两个函数在结构上十分相似,我们找到里面对设备节点的操作部分:

pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_READI_FRAMES, &x)  /* tinyplay */
pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x) /* tinycap */
/* data      :所要操作的声卡的相关信息 */
/* SNDRV_xxx :cmd */
/* x         :关于音频数据的结构体 */

然后会到这里

static int pcm_hw_ioctl(void *data, unsigned int cmd, ...)
{
    struct pcm_hw_data *hw_data = data;
    va_list ap;
    void *arg;

    va_start(ap, cmd);
    arg = va_arg(ap, void *);
    va_end(ap);

    return ioctl(hw_data->fd, cmd, arg);
}

内核层

这里的pcm设备其实是在注册声卡的时候创建并被注册的,我们找到注册pcm设备的函数:

static int snd_pcm_dev_register(struct snd_device *device)

在该函数里可以找到这么一句

err = snd_register_device(devtype, pcm->card, pcm->device,
                          &snd_pcm_f_ops[cidx], pcm,
                          &pcm->streams[cidx].dev);

可以发现用户层对应的函数在这里有一些线索,我们再找到snd_pcm_f_ops:

const struct file_operations snd_pcm_f_ops[2] = {
        {
                .owner =                THIS_MODULE,
                .write =                snd_pcm_write,
                .write_iter =           snd_pcm_writev,
                .open =                 snd_pcm_playback_open,
                .release =              snd_pcm_release,
                .llseek =               no_llseek,
                .poll =                 snd_pcm_poll,
                .unlocked_ioctl =       snd_pcm_ioctl,
                .compat_ioctl =         snd_pcm_ioctl_compat,
                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        },
        {
                .owner =                THIS_MODULE,
                .read =                 snd_pcm_read,
                .read_iter =            snd_pcm_readv,
                .open =                 snd_pcm_capture_open,
                .release =              snd_pcm_release,
                .llseek =               no_llseek,
                .poll =                 snd_pcm_poll,
                .unlocked_ioctl =       snd_pcm_ioctl,
                .compat_ioctl =         snd_pcm_ioctl_compat,
                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        }
};

在这里可以找到与tinyplay和tinycap在用户层最终调用到的ioctl相对应的函数snd_pcm_ioctl()

static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg)
{
        struct snd_pcm_file *pcm_file;

        pcm_file = file->private_data;

        if (((cmd >> 8) & 0xff) != 'A')
                return -ENOTTY;

        return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
                                     (void __user *)arg);
}

这个函数在简单处理传参之后,最终return到了snd_pcm_common_ioctl(),来看看这个函数:

static int snd_pcm_common_ioctl(struct file *file,
                                 struct snd_pcm_substream *substream,
                                 unsigned int cmd, void __user *arg)
{
        struct snd_pcm_file *pcm_file = file->private_data;
        int res;

        if (PCM_RUNTIME_CHECK(substream))
                return -ENXIO;

        res = snd_power_wait(substream->pcm->card, SNDRV_CTL_POWER_D0);
        if (res < 0)
                return res;

        switch (cmd) {
        case SNDRV_PCM_IOCTL_PVERSION:
                return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
        case SNDRV_PCM_IOCTL_INFO:
                return snd_pcm_info_user(substream, arg);
        case SNDRV_PCM_IOCTL_TSTAMP:    /* just for compatibility */
                return 0;
        case SNDRV_PCM_IOCTL_TTSTAMP:
                return snd_pcm_tstamp(substream, arg);
        case SNDRV_PCM_IOCTL_USER_PVERSION:
                if (get_user(pcm_file->user_pversion,
                             (unsigned int __user *)arg))
                        return -EFAULT;
                return 0;
		...
}

这里只展示一部分,因为剩余部分都是类似这些直接用case进行cmd判断并跳转到相应函数 我们可以通过用户层传下来的cmd找到pcm_read和read_write对应的部分:

case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
case SNDRV_PCM_IOCTL_READI_FRAMES:
        return snd_pcm_xferi_frames_ioctl(substream, arg);

可以发现无论是读写最后都会跳到同一个函数进行处理,来看看这个函数

static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream,
                                      struct snd_xferi __user *_xferi)
{
        struct snd_xferi xferi;
        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;
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
        else
                result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
        __put_user(result, &_xferi->result);
        return result < 0 ? result : 0;
}

再到

snd_pcm_lib_write(struct snd_pcm_substream *substream,
                  const void __user *buf, snd_pcm_uframes_t frames)
{
        return __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
}
snd_pcm_lib_read(struct snd_pcm_substream *substream,
                 void __user *buf, snd_pcm_uframes_t frames)
{
        return __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
}

最后到

/* the common loop for read/write data */
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
                                     void *data, bool interleaved,
                                     snd_pcm_uframes_t size, bool in_kernel)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        snd_pcm_uframes_t xfer = 0;
        snd_pcm_uframes_t offset = 0;
        snd_pcm_uframes_t avail;
        pcm_copy_f writer;
        pcm_transfer_f transfer;
        bool nonblock;
        bool is_playback;
        int err;
        
        ...
}

后面还有很多调用,省略一部分,直接来介绍后面涉及到了substream部分,关于pcm这部分的内容在《pcm设备的注册流程》有涉及到,这里不多赘述。 继续介绍substream的操作函数:

struct snd_pcm_ops {
        int (*open)(struct snd_pcm_substream *substream);
        int (*close)(struct snd_pcm_substream *substream);
        int (*ioctl)(struct snd_pcm_substream * substream,
                     unsigned int cmd, void *arg);
        int (*hw_params)(struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *params);
        int (*hw_free)(struct snd_pcm_substream *substream);
        int (*prepare)(struct snd_pcm_substream *substream);
        int (*trigger)(struct snd_pcm_substream *substream, int cmd);
        snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
        int (*get_time_info)(struct snd_pcm_substream *substream,
                        struct timespec *system_ts, struct timespec *audio_ts,
                        struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
                        struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
        int (*fill_silence)(struct snd_pcm_substream *substream, int channel,
                            unsigned long pos, unsigned long bytes);
        int (*copy_user)(struct snd_pcm_substream *substream, int channel,
                         unsigned long pos, void __user *buf,
                         unsigned long bytes);
        int (*copy_kernel)(struct snd_pcm_substream *substream, int channel,
                           unsigned long pos, void *buf, unsigned long bytes);
        struct page *(*page)(struct snd_pcm_substream *substream,
                             unsigned long offset);
        int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
        int (*ack)(struct snd_pcm_substream *substream);
};

然后在substream的trigger函数里,又有这个函数

static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_soc_component *component;
        struct snd_soc_rtdcom_list *rtdcom;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_dai *codec_dai;
        int i, ret;

        for_each_rtd_codec_dai(rtd, i, codec_dai) {
                ret = snd_soc_dai_trigger(codec_dai, substream, cmd);
                if (ret < 0)
                        return ret;
        }

        for_each_rtdcom(rtd, rtdcom) {
                component = rtdcom->component;

                ret = snd_soc_component_trigger(component, substream, cmd);
                if (ret < 0)
                        return ret;
        }

        ret = snd_soc_dai_trigger(cpu_dai, substream, cmd);
        if (ret < 0)
                return ret;

        if (rtd->dai_link->ops->trigger) {
                ret = rtd->dai_link->ops->trigger(substream, cmd);
                if (ret < 0)
                        return ret;
        }

        return 0;
}

可以看见这里会调用dai的driver函数:

int snd_soc_dai_trigger(struct snd_soc_dai *dai,
                        struct snd_pcm_substream *substream,
                        int cmd)
{
        int ret = 0;

        if (dai->driver->ops->trigger)
                ret = dai->driver->ops->trigger(substream, cmd, dai);

        return ret;
}

先去codec-dai看看,如果dai_driver没有trigger函数则返回0,然后再去cpu-dai进行相同操作

总结

用户层通过设备节点dev/snd/control将数据传到内核层,然后内核进行一系列操作:首先是pcm驱动进行操作,会操作到它的substream成员的操作函数(trigger),substream的trigger函数会调用到dai的trigger函数,而这个函数会把DMA的开关打开,然后这个时候DMA就会开启数据搬运的工作,将内存中的音频数据搬到tx fifo,如果是内部集成的codec,那么就直接送去模拟端播放;如果是外部codec,涉及到i2s的数据传输,数据搬运到tx fifo时,i2s会按照设置好的数据格式进行数据传输,格式需要在soc和codec两端进行设置,并确保一致数据格式。录音的情况则相反

猜你喜欢

转载自blog.csdn.net/hhx123456798/article/details/123623701