Android音頻驅動-ASOC之PCM Device創建

前面已經創建了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_dais
soc-pcm.c
    soc_new_pcm

int 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_new
static 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 # ls
adsp  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 pcm
00-00: MultiMedia1_PLayback mt-soc-codec-tx-dai-0 :  : playback 1
00-01: MultiMedia1_Capture mt-soc-codec-rx-dai-1 :  : capture 1
00-02: Voice_MD1_PLayback mt-soc-codec-voicemd1-dai-2 :  : playback 1 : capture 1
00-03: HMDI_PLayback mt-soc-hdmi-dummy-dai-codec-3 :  : playback 1 : capture 1
00-04: ULDL_Loopback mt-soc-codec-uldlloopback-dai-4 :  : playback 1 : capture 1
00-05: I2S0_PLayback mt-soc-i2s0-dummy-dai-codec-5 :  : playback 1 : capture 1
00-06: MRGRX_PLayback mt-soc-mrgrx-dai-codec-6 :  : playback 1 : capture 1
00-07: MRGRX_CAPTURE mt-soc-mrgrx-dummy-dai-codec-7 :  : playback 1 : capture 1
00-08: I2S0DL1_PLayback mt-soc-codec-I2s0tx-dai-8 :  : playback 1
00-09: DL1_AWB_Record mt-soc-codec-dl1awb-dai-9 :  : capture 1
00-10: Voice_MD1_BT_Playback mt-soc-codec-voicemd1-bt-dai-10 :  : playback 1
00-11: VOIP_Call_BT_Playback mt-soc-codec-voipcall-btout-dai-11 :  : playback 1
00-12: VOIP_Call_BT_Capture mt-soc-codec-voipcall-btin-dai-12 :  : capture 1
00-13: TDM_Debug_Record mt-soc-tdmrx-dai-codec-13 :  : capture 1
00-14: FM_MRGTX_Playback mt-soc-fmmrg2tx-dummy-dai-codec-14 :  : playback 1
00-15: MultiMediaData2_Capture mt-soc-codec-rx-dai2-15 :  : capture 1
00-16: I2S0AWB_Capture mt-soc-codec-i2s0awb-dai-16 :  : capture 1
00-17: Voice_MD2_PLayback mt-soc-codec-voicemd2-dai-17 :  : playback 1 : capture 1
00-18: MultiMedia_Routing mt-soc-dummy-dai-codec-18 :  : playback 1
00-19: Voice_MD2_BT_Playback mt-soc-codec-voicemd2-bt-dai-19 :  : playback 1
00-20: HP_IMPEDANCE_Playback mt-soc-codec-hp-impedance-dai-20 :  : playback 1
00-21: FM_I2S_Playback mt-soc-fm-i2s-dai-codec-21 :  : playback 1
00-22: FM_I2S_Capture mt-soc-fm-i2s-dummy-dai-codec-22 :  : capture 1
00-24: MultiMedia2_PLayback mt-soc-codec-tx-dai2-24 :  : playback 1
00-25: BTCVSD_Capture snd-soc-dummy-dai-25 :  : playback 1 : capture 1
00-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下。

發佈了40 篇原創文章 · 獲贊 16 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章