Android音频驱动-ASOC之PCM Device创建

来源:互联网 发布:参考文献 网络 编辑:程序博客网 时间:2024/05/16 06:22

前面已经创建了control设备,现在soc_probe_link_dais调用soc_new_pcm创建pcm设备。
1)设置pcm native中要使用的pcm操作函数,这些函数用于操作音频物理设备,包括machine、codec_dai、cpu_dai、platform;
2)调用snd_pcm_new()创建pcm设备,回放子流实例和录制子流实例都在这里创建;
3)回调platform驱动的pcm_new(),完成音频dma设备初始化和dma buffer内存分配;

soc-core.c    soc_probe    platform_get_drvdata(pdev)//获取声卡    snd_soc_register_card    snd_soc_instantiate_card    soc_probe_link_daissoc-pcm.c    soc_new_pcmint soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)//num为设备编号,递增排序{    struct snd_soc_platform *platform = rtd->platform;    struct snd_soc_dai *codec_dai;    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;    struct snd_pcm *pcm;    char new_name[64];    int ret = 0, playback = 0, capture = 0;    int i;    /* create the PCM */    if (rtd->dai_link->no_pcm) {        snprintf(new_name, sizeof(new_name), "(%s)", rtd->dai_link->stream_name);        ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, playback, capture, &pcm);    } else {        if (rtd->dai_link->dynamic)            snprintf(new_name, sizeof(new_name), "%s (*)", rtd->dai_link->stream_name);        else            //生成stream的/proc/asound/pcm,分为playback和capture,new_name为stream_name codec_dai_name-num            snprintf(new_name, sizeof(new_name), "%s %s-%d", rtd->dai_link->stream_name,                (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, num);        ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm);    }    rtd->pcm = pcm;    pcm->private_data = rtd;//保存runtime对象    /* ASoC PCM operations */    if (rtd->dai_link->dynamic) {        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;        rtd->ops.ioctl      = soc_pcm_ioctl;    } else {        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;        rtd->ops.ioctl      = soc_pcm_ioctl;    }    if (platform->driver->ops) {        rtd->ops.ack        = platform->driver->ops->ack;        rtd->ops.copy       = platform->driver->ops->copy;        rtd->ops.silence    = platform->driver->ops->silence;        rtd->ops.page       = platform->driver->ops->page;        rtd->ops.mmap       = platform->driver->ops->mmap;    }    if (playback)        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);//设置playback的substream->ops    if (capture)        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);//设置capture的substream->ops    if (platform->driver->pcm_new) {            ret = platform->driver->pcm_new(rtd);//platform没事具体实现        if (ret < 0) {            dev_err(platform->dev,                "ASoC: pcm constructor failed: %d\n",                ret);            return ret;        }    }    return ret;}/*设置substream的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;}*//*platform driver的回调函数pcm_newstatic int mtk_asoc_pcm_I2S0dl1_new(struct snd_soc_pcm_runtime *rtd){    int ret = 0;    pr_warn("%s\n", __func__);    return ret;}*/int snd_pcm_new(struct snd_card *card, const char *id, int device,        int playback_count, int capture_count, struct snd_pcm **rpcm){    return _snd_pcm_new(card, id, device, playback_count, capture_count,            false, rpcm);}static int _snd_pcm_new(struct snd_card *card, const char *id, int device,        int playback_count, int capture_count, bool internal,        struct snd_pcm **rpcm){    struct snd_pcm *pcm;    int err;    //参数ops中的dev_register字段指向snd_pcm_dev_register,    //这个回调函数会在声卡的注册阶段创建pcm设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc    static struct snd_device_ops ops = {        .dev_free = snd_pcm_dev_free,        .dev_register = snd_pcm_dev_register,        .dev_disconnect = snd_pcm_dev_disconnect,    };    pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);//创建pcm对象    pcm->card = card;    pcm->device = device;//pcm的编号,递增    pcm->internal = internal;    if (id)        strlcpy(pcm->id, id, sizeof(pcm->id));//保存pcm的proc信息    //创建playback_count个放音stream,每个pcm可以有多个放音stream声音流通道,一般就1个    if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {        snd_pcm_free(pcm);        return err;    }    // 创建capture_count个录音stream,每个pcm可以有多个录音stream声音流通道,一般就1个    if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {        snd_pcm_free(pcm);        return err;    }    // 创建pcm对应的device并加入到card管理的devices链表    if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {        snd_pcm_free(pcm);        return err;    }    if (rpcm)        *rpcm = pcm;    return 0;}

创建回放子流实例和录制子流实例

int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count){    int idx, err;    //获得pcm管理substream的指针,stream为0 or 1    //0.SNDRV_PCM_STREAM_PLAYBACK放音    //1.SNDRV_PCM_STREAM_CAPTURE录音    struct snd_pcm_str *pstr = &pcm->streams[stream];    struct snd_pcm_substream *substream, *prev;    pstr->stream = stream;    pstr->pcm = pcm;    pstr->substream_count = substream_count;// 该pcm包含放音或录音流的数量    if (substream_count > 0 && !pcm->internal) {        // 创建/proc/asound/card0/pcm**目录和下面具体的文件        err = snd_pcm_stream_proc_init(pstr);    }    prev = NULL;    for (idx = 0, prev = NULL; idx < substream_count; idx++) {        substream = kzalloc(sizeof(*substream), GFP_KERNEL);        substream->pcm = pcm; // 该substream所归属的pcm        substream->pstr = pstr; // 该substream所归属的head头        substream->number = idx; // 该substream位于head链表中的id索引值number,用来锁定本substream.        substream->stream = stream;        sprintf(substream->name, "subdevice #%i", idx);        substream->buffer_bytes_max = UINT_MAX;        if (prev == NULL)            pstr->substream = substream;        else            prev->next = substream;        if (!pcm->internal) {            // 创建/proc/asound/card0/pcm**/sub0目录和info, hw_params, sw_params,status等文件            err = snd_pcm_substream_proc_init(substream);        }        substream->group = &substream->self_group;        list_add_tail(&substream->link_list, &substream->self_group.substreams);        atomic_set(&substream->mmap_count, 0);        prev = substream;    }    return 0;}

创建pcm对应的device并加入到card管理的devices链表

int snd_device_new(struct snd_card *card, enum snd_device_type type,           void *device_data, struct snd_device_ops *ops){    struct snd_device *dev;    struct list_head *p;    dev = kzalloc(sizeof(*dev), GFP_KERNEL);//创建device,之后会添加到card->devices    INIT_LIST_HEAD(&dev->list);    dev->card = card;    dev->type = type;//保存device的类型,playback or capture    dev->state = SNDRV_DEV_BUILD;    dev->device_data = device_data;//保存device对应的pcm对象    dev->ops = ops;//保存device的回调函数    /* insert the entry in an incrementally sorted list */    list_for_each_prev(p, &card->devices) {//遍历声卡挂载的device        struct snd_device *pdev = list_entry(p, struct snd_device, list);        if ((unsigned int)pdev->type <= (unsigned int)type)            break;    }    list_add(&dev->list, p);//将dev插入到card->devices    return 0;}

注册声卡的最后阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,
就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的
设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc

int snd_card_register(struct snd_card *card)int snd_device_register_all(struct snd_card *card)static int __snd_device_register(struct snd_device *dev){    if (dev->state == SNDRV_DEV_BUILD) {        if (dev->ops->dev_register) {            int err = dev->ops->dev_register(dev);        }        dev->state = SNDRV_DEV_REGISTERED;    }    return 0;}static int snd_pcm_dev_register(struct snd_device *device){    int cidx, err;    struct snd_pcm_substream *substream;    struct snd_pcm_notify *notify;    char str[16];    struct snd_pcm *pcm;    struct device *dev;    pcm = device->device_data;    //将pcm加入到全局变量snd_pcm_devices,之后会创建/proc/asound/pcm文件    err = snd_pcm_add(pcm);,    for (cidx = 0; cidx < 2; cidx++) {        int devtype = -1;        if (pcm->streams[cidx].substream == NULL || pcm->internal)            continue;        switch (cidx) {        case SNDRV_PCM_STREAM_PLAYBACK:            sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);//生成pcm playback设备节点的名字            devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;            break;        case SNDRV_PCM_STREAM_CAPTURE:            sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);//生成pcm capture设备节点的名字            devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;            break;        }        dev = pcm->dev;        if (!dev)            dev = snd_card_get_device_link(pcm->card);        /* register pcm */        err = snd_register_device_for_dev(devtype, pcm->card,                          pcm->device,                          &snd_pcm_f_ops[cidx],                          pcm, str, dev);        //返回当前注册的pcm设备,然后设置该pcm的属性        dev = snd_get_device(devtype, pcm->card, pcm->device);        if (dev) {            err = sysfs_create_groups(&dev->kobj, pcm_dev_attr_groups);            put_device(dev);        }        //进行pcm定时器的初始化        for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)            snd_pcm_timer_init(substream);    }    list_for_each_entry(notify, &snd_pcm_notify_list, list)        notify->n_register(pcm);    return 0;}static int snd_pcm_add(struct snd_pcm *newpcm){    struct snd_pcm *pcm;    list_for_each_entry(pcm, &snd_pcm_devices, list) {        if (pcm->card->number > newpcm->card->number ||                (pcm->card == newpcm->card &&                pcm->device > newpcm->device)) {            list_add(&newpcm->list, pcm->list.prev);            return 0;        }    }    list_add_tail(&newpcm->list, &snd_pcm_devices);    return 0;}

注册PCM设备
每个snd_minor结构体保存了声卡下某个逻辑设备的上下文信息,它在逻辑设备建立阶段被填充,
在逻辑设备被使用时就可以从该结构体中得到相应的信息。
pcm设备也不例外,也需要使用该结构体。该结构体在include/sound/core.h中定义。

/*struct snd_minor {      int type;    int card;    int device;    const struct file_operations *f_ops;    void *private_data;    struct device *dev;};//在sound/sound.c中定义了一个snd_minor指针的全局数组:static struct snd_minor *snd_minors[256];  //snd_pcm_f_ops是一个标准的文件系统file_operations结构数组,const struct file_operations snd_pcm_f_ops[2] = {    {        .owner =        THIS_MODULE,        .write =        snd_pcm_write,        .aio_write =        snd_pcm_aio_write,        .open =         snd_pcm_playback_open,        .release =      snd_pcm_release,        .llseek =       no_llseek,        .poll =         snd_pcm_playback_poll,        .unlocked_ioctl =   snd_pcm_playback_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,        .aio_read =     snd_pcm_aio_read,        .open =         snd_pcm_capture_open,        .release =      snd_pcm_release,        .llseek =       no_llseek,        .poll =         snd_pcm_capture_poll,        .unlocked_ioctl =   snd_pcm_capture_ioctl,        .compat_ioctl =     snd_pcm_ioctl_compat,        .mmap =         snd_pcm_mmap,        .fasync =       snd_pcm_fasync,        .get_unmapped_area =    snd_pcm_get_unmapped_area,    }};*/int snd_register_device_for_dev(int type, struct snd_card *card, int dev,                const struct file_operations *f_ops,                void *private_data,                const char *name, struct device *device){    int minor;    struct snd_minor *preg;    preg = kmalloc(sizeof *preg, GFP_KERNEL);    preg->type = type;//playback or capture    preg->card = card ? card->number : -1;    preg->device = dev;    preg->f_ops = f_ops;//文件操作函数,用来操作/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc    preg->private_data = private_data;//保存pcm对象    preg->card_ptr = card;    minor = snd_kernel_minor(type, card, dev);    snd_minors[minor] = preg;//使用全局变量保存pcm设备的上下文    //创建pcm的设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc    preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);    return 0;}
Android N:/dev/snd # lsadsp  controlC0 pcmC0D0p  pcmC0D12c pcmC0D15c pcmC0D17p pcmC0D1c  pcmC0D22c pcmC0D25p pcmC0D2c pcmC0D3p pcmC0D5c pcmC0D6p pcmC0D8p sequencer audio dsp pcmC0D10p pcmC0D13c pcmC0D16c pcmC0D18p pcmC0D20p pcmC0D24p pcmC0D26c pcmC0D2p pcmC0D4c pcmC0D5p pcmC0D7c pcmC0D9c sequencer2 comprC0D23 mixer pcmC0D11p pcmC0D14p pcmC0D17c pcmC0D19p pcmC0D21p pcmC0D25c pcmC0D26p pcmC0D3c pcmC0D4p pcmC0D6c pcmC0D7p seq  timer

创建/proc/asound/pcm文件,用来描述pcm设备功能,hal层可以通过stream name获取card id和pcm id。

static int __init alsa_pcm_init(void){    snd_ctl_register_ioctl(snd_pcm_control_ioctl);    snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl);    snd_pcm_proc_init();    return 0;}//cat时会调用该函数查询pcm信息/*static void snd_pcm_proc_read(struct snd_info_entry *entry,                  struct snd_info_buffer *buffer){    struct snd_pcm *pcm;    //读取全局变量snd_pcm_devices,获得pcm的card number、device、id和name    list_for_each_entry(pcm, &snd_pcm_devices, list) {        snd_iprintf(buffer, "%02i-%02i: %s : %s",                pcm->card->number, pcm->device, pcm->id, pcm->name);        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)            snd_iprintf(buffer, " : playback %i",                    pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count);        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)            snd_iprintf(buffer, " : capture %i",                    pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count);        snd_iprintf(buffer, "\n");    }}*/static void snd_pcm_proc_init(void){    struct snd_info_entry *entry;        //创建proc/asound/pcm文件    if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) {        snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read);        if (snd_info_register(entry) < 0) {            snd_info_free_entry(entry);            entry = NULL;        }    }    snd_pcm_proc_entry = entry;}//创建/proc/asound/pcm文件int snd_info_register(struct snd_info_entry * entry){    struct proc_dir_entry *root, *p = NULL;        //snd_proc_root等于asound    root = entry->parent == NULL ? snd_proc_root : entry->parent->p;    if (S_ISDIR(entry->mode)) {        p = proc_mkdir_mode(entry->name, entry->mode, root);    } else {        p = proc_create_data(entry->name, entry->mode, root,                    &snd_info_entry_operations, entry);        proc_set_size(p, entry->size);    }    entry->p = p;    if (entry->parent)        list_add_tail(&entry->list, &entry->parent->children);    return 0;}
Android N:/proc/asound # cat pcm00-00: MultiMedia1_PLayback mt-soc-codec-tx-dai-0 :  : playback 100-01: MultiMedia1_Capture mt-soc-codec-rx-dai-1 :  : capture 100-02: Voice_MD1_PLayback mt-soc-codec-voicemd1-dai-2 :  : playback 1 : capture 100-03: HMDI_PLayback mt-soc-hdmi-dummy-dai-codec-3 :  : playback 1 : capture 100-04: ULDL_Loopback mt-soc-codec-uldlloopback-dai-4 :  : playback 1 : capture 100-05: I2S0_PLayback mt-soc-i2s0-dummy-dai-codec-5 :  : playback 1 : capture 100-06: MRGRX_PLayback mt-soc-mrgrx-dai-codec-6 :  : playback 1 : capture 100-07: MRGRX_CAPTURE mt-soc-mrgrx-dummy-dai-codec-7 :  : playback 1 : capture 100-08: I2S0DL1_PLayback mt-soc-codec-I2s0tx-dai-8 :  : playback 100-09: DL1_AWB_Record mt-soc-codec-dl1awb-dai-9 :  : capture 100-10: Voice_MD1_BT_Playback mt-soc-codec-voicemd1-bt-dai-10 :  : playback 100-11: VOIP_Call_BT_Playback mt-soc-codec-voipcall-btout-dai-11 :  : playback 100-12: VOIP_Call_BT_Capture mt-soc-codec-voipcall-btin-dai-12 :  : capture 100-13: TDM_Debug_Record mt-soc-tdmrx-dai-codec-13 :  : capture 100-14: FM_MRGTX_Playback mt-soc-fmmrg2tx-dummy-dai-codec-14 :  : playback 100-15: MultiMediaData2_Capture mt-soc-codec-rx-dai2-15 :  : capture 100-16: I2S0AWB_Capture mt-soc-codec-i2s0awb-dai-16 :  : capture 100-17: Voice_MD2_PLayback mt-soc-codec-voicemd2-dai-17 :  : playback 1 : capture 100-18: MultiMedia_Routing mt-soc-dummy-dai-codec-18 :  : playback 100-19: Voice_MD2_BT_Playback mt-soc-codec-voicemd2-bt-dai-19 :  : playback 100-20: HP_IMPEDANCE_Playback mt-soc-codec-hp-impedance-dai-20 :  : playback 100-21: FM_I2S_Playback mt-soc-fm-i2s-dai-codec-21 :  : playback 100-22: FM_I2S_Capture mt-soc-fm-i2s-dummy-dai-codec-22 :  : capture 100-24: MultiMedia2_PLayback mt-soc-codec-tx-dai2-24 :  : playback 100-25: BTCVSD_Capture snd-soc-dummy-dai-25 :  : playback 1 : capture 100-26: BTCVSD_Playback snd-soc-dummy-dai-26 :  : playback 1 : capture 1

为什么创建出的设备节点全在/dev/snd下呢? 此问题源自sound_class创建的时候,设置的devnode参数。

static int __init init_soundcore(void){    int rc;    rc = init_oss_soundcore();    if (rc)        return rc;    sound_class = class_create(THIS_MODULE, "sound");    if (IS_ERR(sound_class)) {        cleanup_oss_soundcore();        return PTR_ERR(sound_class);    }    sound_class->devnode = sound_devnode;    return 0;}static char *sound_devnode(struct device *dev, umode_t *mode){    if (MAJOR(dev->devt) == SOUND_MAJOR)        return NULL;    //返回字符串/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc    return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));}

当调用device_create的时候,最终会调用到device_add->devtmpfs_create_node->device_get_devnode中

    /* the class may provide a specific name */      if (dev->class && dev->class->devnode)          *tmp = dev->class->devnode(dev, mode);  

最终出现的设备节点会出现在/dev/snd下。

原创粉丝点击