【TinyALSA全解析(四)】扩展篇-从TinyALSA到底层音频驱动的全流程分析

扩展篇-从TinyALSA到底层音频驱动的全流程分析

  • 第一节 本文说明
  • 第二节 声卡驱动统一入口进行ops替换过程
    • 2.1 tinyalsa到Linux kernel
    • 2.2 Linux Kernel中,由主设备号ops分流到次设备号ops
  • 第三节 次设备中file_operations的open函数
    • 3.1 本节主要内容
    • 3.2 为何次设备的file_operations是snd_pcm_f_ops?
    • 3.3 snd_pcm_playback_open函数访问底层

/*****************************************************************************************************************/

声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!

创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢

/*****************************************************************************************************************/

第一节 本文说明

本文讲一下从HAL层的TinyALSA调到ASOC的platform、codec、machine的完整流程,也就是如何从Tinyalsa调到驱动程序中。

学习本文需要提前掌握ASOC架构、声卡注册过程、ALSA结构体的关系等知识,基础内容不逐个介绍(不然讲不完,后续再出相关教程),就以Tinyalsa中的pcm_open为例讲解。参考的安卓版本为Android 12_r8官方源码,Linux版本为Linux Kernel 5.10官方源码

本文主要内容也可见下面的简图:
【TinyALSA全解析(四)】扩展篇-从TinyALSA到底层音频驱动的全流程分析_第1张图片

第二节 声卡驱动统一入口进行ops替换过程

2.1 tinyalsa到Linux kernel

当我们open的设备类似为非ALSA plug的时候,实际上调用的open函数应该是pcm_hw_open函数。

// file path :  external/tinyalsa/pcm.c
struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, struct pcm_config *config)
{
    //...
    pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
    //...
}

//open非ALSA plug时,pcm->ops->open函数就是pcm_hw_open函数
//file path : external/tinyalsa/pcm_hw.c
static int pcm_hw_open(unsigned int card, unsigned int device,
                unsigned int flags, void **data,
                __attribute__((unused)) void *node)
{
//...
    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
             flags & PCM_IN ? 'c' : 'p');
    fd = open(fn, O_RDWR|O_NONBLOCK);
//...
}

在Linux设备中,音频节点的主设备号都是116,可以用CMD查看这个音频节点"/dev/snd/pcmC%uD%u%c",例如:

# ls -l /dev/snd/pcmC0D0p
crw-rw---- 1 system audio 116,   2 20xx-xx-xx xx:xx /dev/snd/pcmC0D0p

可以见到主设备号是116,次设备号是2
由此需要找Kernel中主设备号为116的设备,看看这个设备的ops是怎么样操作的

2.2 Linux Kernel中,由主设备号ops分流到次设备号ops

在Linux Kernel 5.10中,主设备号为116的设备是如下注册的:

//file path : sound\core\sound.c
static int __init alsa_sound_init(void)
{
    snd_major = major;
    snd_ecards_limit = cards_limit;
    if (register_chrdev(major, "alsa", &snd_fops)) {
/*
这个主设备号major的定义是:
static int major = CONFIG_SND_MAJOR;
这个宏定义在下面:
#define CONFIG_SND_MAJOR    116    // standard configuration
*/
        pr_err("ALSA core: unable to register native major device number %d\n", major);
        return -EIO;
    }
    if (snd_info_init() < 0) {
        unregister_chrdev(major, "alsa");
        return -ENOMEM;
    }
#ifndef MODULE
    pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
    return 0;
}

那么对应的open就是:

//file path :sound\core\sound.c
static const struct file_operations snd_fops =
{
    .owner =    THIS_MODULE,
    .open =     snd_open,
    .llseek =   noop_llseek,
};

注意这个file_operations有open函数,但没有close、write、read函数的实现,这个是为什么?
因为后续会在snd_open函数中根据次设备号来替换这个原先的file_operations。

替换file_operations的过程如下:

//file path :sound\core\sound.c

static int snd_open(struct inode *inode, struct file *file)
{
    // 获取设备的次设备号
    unsigned int minor = iminor(inode);
    struct snd_minor *mptr = NULL;
    const struct file_operations *new_fops;
    int err = 0;
    // 检查次设备号是否在有效范围内
    if (minor >= ARRAY_SIZE(snd_minors))
        return -ENODEV;
    // 加锁,防止并发操作
    mutex_lock(&sound_mutex);
    // 获取对应次设备号的设备信息
    mptr = snd_minors[minor];
    // 如果设备信息为空,则尝试自动加载设备
    if (mptr == NULL) {
        mptr = autoload_device(minor);
        if (!mptr) {
            // 如果自动加载失败,则解锁并返回错误
            mutex_unlock(&sound_mutex);
            return -ENODEV;
        }
    }
    // 获取设备的文件操作函数集
    new_fops = fops_get(mptr->f_ops);
    // 解锁
    mutex_unlock(&sound_mutex);
    // 如果获取文件操作函数集失败,则返回错误
    if (!new_fops)
        return -ENODEV;
    // 替换file结构体中的文件操作函数集
    replace_fops(file, new_fops);
    // 如果设备的打开函数存在,则调用该函数打开设备
    if (file->f_op->open)
        err = file->f_op->open(inode, file);
    // 返回结果
    return err;
}

总结:声卡设备的open函数有统一的入口–snd_open函数,任何HW音频函数均是通过这个函数作为跳板访问硬件

但是问题来了,snd_open替换完file_operations后,紧跟着
file->f_op->open(inode, file);这个就是调用次设备的open,那么次设备的open函数在哪里呢?

第三节 次设备中file_operations的open函数

3.1 本节主要内容

先说结论:在写声卡驱动的时候,会调用snd_soc_register_card函数去注册一个声卡,snd_soc_register_card函数会填充snd_minors[minor]数组,最终主设备号在这个数组中取出相应次设备号的file_operations就完成了主设备号ops向次设备的ops转变。次设备号的ops是如下的内容,类似于主设备也是统一入口(这节的主要内容就是这个结论,需要熟悉声卡注册的流程,不懂或者不理解可以记住结论):

// file path: /sound/core/pcm_native.c
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,
    }
};

本节内容会先分析次设备的file_operations为何是snd_pcm_f_ops。

然后举例当执行open设备的时候,分析从snd_pcm_f_ops中的snd_pcm_playback_open函数如何进一步调度到底层的驱动函数。

3.2 为何次设备的file_operations是snd_pcm_f_ops?

这个要说一下声卡注册的流程了(后续会出一个系列讲声卡是如何加载上去的)。

首先声卡注册的函数是snd_soc_register_card函数,这个的函数的内容比较丰富,但对于本文需要掌握如下的调用流程:

snd_soc_register_card
    --snd_soc_bind_card
        --soc_init_pcm_runtime
            --soc_new_pcm
                --snd_pcm_new
                    --_snd_pcm_new
                        --.dev_register =snd_pcm_dev_register,// 定义设备操作函数集
        --snd_card_register
            --snd_device_register_all
                --__snd_device_register
                    --dev->ops->dev_register(dev);//创建dev_ops,调用的是snd_pcm_dev_register

综上,在声卡注册的时候,次设备的ops由snd_pcm_dev_register函数去创建,分析这个函数的内容:

//file path : /sound/core/pcm.c
static int snd_pcm_dev_register(struct snd_device *device)
{
    //...
    // 遍历PCM设备的所有流
    for (cidx = 0; cidx < 2; cidx++) {
        int devtype = -1;
        if (pcm->streams[cidx].substream == NULL)
            continue;
        switch (cidx) {
        case SNDRV_PCM_STREAM_PLAYBACK:
            devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
            break;
        case SNDRV_PCM_STREAM_CAPTURE:
            devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
            break;
        }
        /* register pcm */
        err = snd_register_device(devtype, pcm->card, pcm->device,
                      &snd_pcm_f_ops[cidx], pcm,
                      &pcm->streams[cidx].dev);
        /* 注意这个参数!snd_pcm_f_ops[cidx]! */
        if (err < 0) {
            list_del_init(&pcm->list);
            goto unlock;
        }
        // 这里是初始化PCM设备的所有流的定时器
        for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
            snd_pcm_timer_init(substream);
    }
    //...
}

//这个传入的参数为:
//file path :/sound/core/pcm_native.c
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,
    }
};

上面有两个ops,一个是播放使用的,一个是录音使用的。

本文就以录音为例继续分析。录音调用的open函数是snd_pcm_playback_open函数。

3.3 snd_pcm_playback_open函数访问底层

snd_pcm_playback_open的调用流程如下:

snd_pcm_playback_open
    --snd_pcm_open
        --snd_pcm_open_file
            --snd_pcm_open_substream
                --substream->ops->open(substream)

分析到这个语句:substream->ops->open(substream),substream->ops是在注册声卡的时候被指定的,声卡注册的函数是snd_soc_register_card函数,对应流程如下:

snd_soc_register_card
    --snd_soc_bind_card
        --soc_init_pcm_runtime
            --soc_new_pcm:主要内容在这里,分析这个函数

//file path:/sound/soc/soc-pcm.c
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
    //...
    /* ASoC PCM operations */
    if (rtd->dai_link->dynamic) {
    // 使用了DAPM架构,用下面的ops
        rtd->ops.open       = dpcm_fe_dai_open;
        rtd->ops.hw_params  = dpcm_fe_dai_hw_params;
        rtd->ops.prepare    = dpcm_fe_dai_prepare;
        rtd->ops.trigger    = dpcm_fe_dai_trigger;
        rtd->ops.hw_free    = dpcm_fe_dai_hw_free;
        rtd->ops.close      = dpcm_fe_dai_close;
        rtd->ops.pointer    = soc_pcm_pointer;
    } else {
    //不使用DAPM架构,用下面的ops
        rtd->ops.open       = soc_pcm_open;
        rtd->ops.hw_params  = soc_pcm_hw_params;
        rtd->ops.prepare    = soc_pcm_prepare;
        rtd->ops.trigger    = soc_pcm_trigger;
        rtd->ops.hw_free    = soc_pcm_hw_free;
        rtd->ops.close      = soc_pcm_close;
        rtd->ops.pointer    = soc_pcm_pointer;
    }
    //...
    if (playback)
        //如果设备支持播放,设置substream ops为上述的ops
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

    if (capture)
        //如果设备支持录音,设置substream ops为上述的ops
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
    //...
}

//snd_pcm_set_ops的实现
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
             const struct snd_pcm_ops *ops)
{
    struct snd_pcm_str *stream = &pcm->streams[direction];
    struct snd_pcm_substream *substream;
    
    for (substream = stream->substream; substream != NULL; substream = substream->next)
        //这里指定了substream->ops = ops
        substream->ops = ops;
}

综上可以知道:

如果使用了DAPM架构,则substream->ops->open就是dpcm_fe_dai_open,
如果不使用DAPM架构,则substream->ops->open就是soc_pcm_open。
可以关注一下,后续将会推出DAPM的详解,本文不讲述DAPM的相关知识。

以不使用DAPM架构为例:
substream->ops->open就是soc_pcm_open函数,对它进行分析:

soc_pcm_open
    --soc_pcm_components_open
        --snd_soc_component_open
            --component->driver->open  遍历component,如果有open调用open, 一般指的是CPU端的open函数
        --snd_soc_link_startup
            --rtd->dai_link->ops->startup  调用DAI_LINK的startup,位于ASOC的machine端
        --snd_soc_dai_startup
            --dai->driver->ops->startup   调用codec端的startup函数

总结,soc_pcm_open会依次调用了CPU端的open函数、machine端的startup函数、codec端的startup函数。这三个函数均是驱动定义的。

你可能感兴趣的:(TinyALSA全解析,音视频,android,驱动开发,linux,语音识别,实时音视频,物联网)