asla-lib庫函數snd_pcm_open打開流程

本文轉自:http://blog.chinaunix.net/uid-20564848-id-74356.html

《alsa聲卡/dev/snd /pcmC0D0p的open打開流程》
雜記asla-lib庫函數snd_pcm_open打開流程
淺析ac97聲卡intel8x0的DMA內存substream->dma_buffer什麼時候被賦值
淺析ac97聲卡intel8x0的runtime->dma_area是怎麼獲取的
淺析ac97聲卡intel8x0的pci總線DMA物理地址填充和音頻數據發送流程
aplay.c
==> main
==> snd_pcm_open(&handle, pcm_name, stream, open_mode); // 打開一路pcm,刷新config配置
如果是"default",同時type等於SND_CONFIG_TYPE_COMPOUND那麼這裏對應"empty"
static const char *const build_in_pcms[] = {
    "adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
    "linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
    "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
    NULL
};
_snd_pcm_empty_open和snd_pcm_open_named_slave
==> snd_pcm_open_conf(pcmp, name, root, conf, stream, mode);
==> open_func = snd_dlobj_cache_lookup(open_name);將獲得lib庫中_snd_pcm_empty_open函數
    所以open_func將等於_snd_pcm_empty_open
   
    _snd_pcm_empty_open
    _snd_pcm_asym_open
    _snd_pcm_plug_open
    _snd_pcm_softvol_open
    _snd_pcm_dmix_open
    _snd_pcm_hw_open
    ==> snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream,
                  mode | (nonblock ? SND_PCM_NONBLOCK : 0),
                  0, sync_ptr_ioctl);

==> snd_ctl_hw_open
filename等於"/dev/snd/controlC0"
==> snd_open_device(filename, fmode);
    ctl->ops = &snd_ctl_hw_ops;
    ctl->private_data = hw;
    ctl->poll_fd = fd;
    *handle = ctl;
filename等於"/dev/snd/pcmC0D0p"
==> fd = snd_open_device(filename, fmode);
==> return snd_pcm_hw_open_fd(pcmp, name, fd, 0, sync_ptr_ioctl);
==> snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);
    pcm->ops = &snd_pcm_hw_ops;
    pcm->fast_ops = &snd_pcm_hw_fast_ops;

static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm)
{
    snd_pcm_hw_t *hw = pcm->private_data;
    void *ptr;
    int err;
    if (hw->sync_ptr == NULL) { // 如果還沒有mmap,那麼執行mmap映射內核空間驅動使用的聲音緩衝區
        ptr = mmap(NULL, page_align(sizeof(struct sndrv_pcm_mmap_control)),
               PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED,
               hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
        if (ptr == MAP_FAILED || ptr == NULL) {
            err = -errno;
            SYSMSG("control mmap failed");
            return err;
        }
        hw->mmap_control = ptr; // 聲卡驅動頭部填充了一個結構體sndrv_pcm_mmap_control,類似qvfb顯示原理.
// struct sndrv_pcm_mmap_control {
//   sndrv_pcm_uframes_t appl_ptr;    /* RW: appl ptr (0...boundary-1) */
//   sndrv_pcm_uframes_t avail_min;    /* RW: min available frames for wakeup */
// };
    } else {
        hw->mmap_control->avail_min = 1;
    }
    snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
    return 0;
}

snd_pcm_mmap

        switch (i->type) {
        case SND_PCM_AREA_MMAP: // 表示爲數據區分配驅動內存,在snd_pcm_hw_channel_info中設置了type
            ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset);
/*
mmap
==> snd_pcm_mmap_data
==> snd_pcm_default_mmap
// mmap the DMA buffer on RAM
static int snd_pcm_default_mmap(struct snd_pcm_substream *substream,
                struct vm_area_struct *area)
{
    area->vm_ops = &snd_pcm_vm_ops_data; // vma操作函數,當應用程序向該area讀寫不存在的內存數據時,
    area->vm_private_data = substream;   // 將執行snd_pcm_vm_ops_data中的fault
    // 函數snd_pcm_mmap_data_fault進一步以頁爲單位申請內存空間,所以如果用戶程序需要64k,那麼將執行16次,每次申請4k空間[luther.gliethttp].
    area->vm_flags |= VM_RESERVED;
    atomic_inc(&substream->mmap_count);
    return 0;
}
*/
            if (ptr == MAP_FAILED) {
                SYSERR("mmap failed");
                return -errno;
            }
            i->addr = ptr;


==> snd_pcm_mmap_control
static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file,
                struct vm_area_struct *area)
{
    struct snd_pcm_runtime *runtime;
    long size;
    if (!(area->vm_flags & VM_READ))
        return -EINVAL;
    runtime = substream->runtime;
    size = area->vm_end - area->vm_start;
    if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)))
        return -EINVAL;
    area->vm_ops = &snd_pcm_vm_ops_control; // 當對( area->vm_start,area->vm_end)之間空間操作,發生
    area->vm_private_data = substream;      // 缺頁時,內核將調用該vm_ops方法來處理fault異常,
    area->vm_flags |= VM_RESERVED;          // 進而執行snd_pcm_mmap_control_fault申請1個page空間
    return 0;
}



==> writei_func = snd_pcm_writei;
==> playback(argv[optind++]);
==> playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name);
==> pcm_write(audiobuf, l);
==> writei_func(handle, data, count);就是調用上面的snd_pcm_writei
==> snd_pcm_writei
==> _snd_pcm_writei
==> pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size);
==> snd_pcm_plugin_writei
==> snd_pcm_write_areas(pcm, areas, 0, size,
                        snd_pcm_plugin_write_areas);
==> avail = snd_pcm_avail_update(pcm); // 獲取可用緩衝區位置偏移索引值
==> func()就是snd_pcm_plugin_write_areas函數發送1024幀音頻數據,一幀對應一次完整採樣,比如stereo立體聲
,24bits量化,那麼這裏一幀對應3*2字節數據,即一次完整採樣所需空間[luther.gliethttp].
==> plugin->write(pcm, areas, offset, frames,
                       slave_areas, slave_offset, &slave_frames);
即調用snd_pcm_linear_write_areas函數將areas中的frames頻數據拷貝到slave_areas內存區

==> pcm->fast_ops->mmap_commit(pcm->fast_op_arg, offset, frames);
==> snd_pcm_dmix_mmap_commit
==> snd_pcm_dmix_sync_area
/*
 *  synchronize shm ring buffer with hardware
 */
static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm)
==> /* add sample areas here */
    src_areas = snd_pcm_mmap_areas(pcm);
    dst_areas = snd_pcm_mmap_areas(dmix->spcm); // 添加
==> mix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
    if (dmix->interleaved) { // 可以將緩衝中的音頻數據填充到硬件中[luther.gliethttp]
        /*
         * process all areas in one loop
         * it optimizes the memory accesses for this case
         */
        do_mix_areas(size * channels,
                 (unsigned char *)dst_areas[0].addr + sample_size * dst_ofs * channels,
                 (unsigned char *)src_areas[0].addr + sample_size * src_ofs * channels,
                 dmix->u.dmix.sum_buffer + dst_ofs * channels,
                 sample_size,
                 sample_size,
                 sizeof(signed int));
        return;
    }
==> do_mix_areas(size * channels,
                 (unsigned char *)dst_areas[0].addr + sample_size * dst_ofs * channels,
                 (unsigned char *)src_areas[0].addr + sample_size * src_ofs * channels,
                 dmix->u.dmix.sum_buffer + dst_ofs * channels,
                 sample_size,
                 sample_size,
                 sizeof(signed int));
這裏的do_mix_areas在i386中,使用下面完全用匯編實現的拷貝函數MIX_AREAS_32完成數據從src到dst的快速拷貝,
每拷貝一次,聲卡就會發出一點聲音[luther.gliethttp]
/*
 *  for plain i386, 32-bit version (24-bit resolution)
 */
static void MIX_AREAS_32(unsigned int size,
             volatile signed int *dst, signed int *src,
             volatile signed int *sum, size_t dst_step,
             size_t src_step, size_t sum_step)

_snd_pcm_asym_open
_snd_pcm_dmix_open


snd_pcm_plugin_avail_update
==> snd_pcm_avail_update(slave);
==> pcm->fast_ops->avail_update(pcm->fast_op_arg);
==> snd_pcm_dmix_avail_update
==> snd_pcm_mmap_playback_avail(pcm);


alsa_sound_init
#define CONFIG_SND_MAJOR    116    /* standard configuration */
static int major = CONFIG_SND_MAJOR;
module_init(alsa_sound_init)
alsa_sound_init
==> register_chrdev(major, "alsa", &snd_fops)               // 主設備號爲116的所有設備都爲alsa設備,節點方法集爲snd_fops
static const struct file_operations snd_fops =              // alsa的設備名爲pcmC0D1c或pcmC0D1p等[luther.gliethttp].
{
    .owner =    THIS_MODULE,
    .open =        snd_open
};
snd_open
==> __snd_open(inode, file);
==> __snd_open
    unsigned int minor = iminor(inode);
    mptr = snd_minors[minor];
    file->f_op = fops_get(mptr->f_ops);
    file->f_op->open(inode, file);
const struct file_operations snd_pcm_f_ops[2] = {
    {                                                       // alsa使用到的SNDRV_PCM_STREAM_PLAYBACK放音方法集[luther.gliethttp]
        .owner =        THIS_MODULE,
        .write =        snd_pcm_write,
        .aio_write =        snd_pcm_aio_write,
        .open =            snd_pcm_playback_open,
        .release =        snd_pcm_release,
        .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 =    dummy_get_unmapped_area,
    },
    {                                                       // alsa使用到的SNDRV_PCM_STREAM_CAPTURE錄音方法集[luther.gliethttp]
        .owner =        THIS_MODULE,
        .read =            snd_pcm_read,
        .aio_read =        snd_pcm_aio_read,
        .open =            snd_pcm_capture_open,
        .release =        snd_pcm_release,
        .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 =    dummy_get_unmapped_area,
    }
};
=========================================================================
snd_intel8x0_probe
==> snd_intel8x0_create
==> request_irq(pci->irq, snd_intel8x0_interrupt, IRQF_SHARED,
card->shortname, chip)
snd_intel8x0_interrupt
snd_intel8x0_update


snd_open
==> snd_pcm_playback_open
==> snd_pcm_open
==> snd_pcm_open_file
==> snd_pcm_open_substream
==> substream->ops->open(substream)即snd_intel8x0_playback_ops.open
==> snd_intel8x0_playback_open
==> snd_intel8x0_pcm_open
static int snd_intel8x0_pcm_open(struct snd_pcm_substream *substream, struct ichdev *ichdev)
{
    struct intel8x0 *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    int err;

    ichdev->substream = substream;
    runtime->hw = snd_intel8x0_stream; // 聲卡配置硬件信息[luther.gliethttp]
    runtime->hw.rates = ichdev->pcm->rates;
    snd_pcm_limit_hw_rates(runtime);
    if (chip->device_type == DEVICE_SIS) {
        runtime->hw.buffer_bytes_max = 64*1024;
        runtime->hw.period_bytes_max = 64*1024;
    }
    if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
        return err;
    runtime->private_data = ichdev;
    return 0;
}

ioctl(SNDRV_PCM_IOCTL_HW_PARAMS)
==> snd_pcm_f_ops.unlocked_ioctl即:snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
    case SNDRV_PCM_IOCTL_HW_PARAMS:
        return snd_pcm_hw_params_user(substream, arg);
==> snd_pcm_hw_params_user
==> snd_pcm_hw_params
==> substream->ops->hw_params即snd_intel8x0_playback_ops.hw_params
==> snd_intel8x0_hw_params
==> snd_ac97_pcm_open(ichdev->pcm, params_rate(hw_params),
                params_channels(hw_params),
                ichdev->pcm->r[dbl].slots);


ioctl(SNDRV_PCM_IOCTL_PREPARE)
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
==> snd_pcm_prepare // prepare the PCM substream to be triggerable
==> snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
                           substream, f_flags);
==> snd_pcm_action_single(ops, substream, state);
    ops->pre_action(substream, state);
    ops->do_action(substream, state);
    ops->post_action(substream, state);
    上面ops就是之前提到的snd_pcm_action_prepare
==> snd_pcm_do_prepare調用snd_pcm_do_reset(substream, 0);復位
    substream->ops->prepare(substream);即snd_intel8x0_playback_ops.prepare
==> snd_intel8x0_pcm_prepare
static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream)
{
    struct intel8x0 *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct ichdev *ichdev = get_ichdev(substream);
《淺析ac97聲卡intel8x0的runtime->dma_area是怎麼獲取的》
    ichdev->physbuf = runtime->dma_addr;    // dma緩衝區地址
    ichdev->size = snd_pcm_lib_buffer_bytes(substream); // 將幀緩衝大小轉爲字節空間大小[luther.gliethttp]
    ichdev->fragsize = snd_pcm_lib_period_bytes(substream);
    if (ichdev->ichd == ICHD_PCMOUT) {
        snd_intel8x0_setup_pcm_out(chip, runtime); // 爲play模式設置ac97寄存器[luther.gliethttp]
        if (chip->device_type == DEVICE_INTEL_ICH4)
            ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1;
    }
    snd_intel8x0_setup_periods(chip, ichdev); // 設置PCI總線ac97的bank地址空間[luther.gliethttp]
    return 0;
}
==> snd_intel8x0_setup_pcm_out
static void snd_intel8x0_setup_pcm_out(struct intel8x0 *chip,
                       struct snd_pcm_runtime *runtime)
{
    unsigned int cnt;
    int dbl = runtime->rate > 48000;
// 一共有如下幾種設備:enum { DEVICE_INTEL, DEVICE_INTEL_ICH4, DEVICE_SIS, DEVICE_ALI, DEVICE_NFORCE };
    spin_lock_irq(&chip->reg_lock);
    switch (chip->device_type) {
    case DEVICE_ALI:
        cnt = igetdword(chip, ICHREG(ALI_SCR));
        cnt &= ~ICH_ALI_SC_PCM_246_MASK;
        if (runtime->channels == 4 || dbl)
            cnt |= ICH_ALI_SC_PCM_4;
        else if (runtime->channels == 6)
            cnt |= ICH_ALI_SC_PCM_6;
        iputdword(chip, ICHREG(ALI_SCR), cnt);
        break;
    case DEVICE_SIS:
        cnt = igetdword(chip, ICHREG(GLOB_CNT));
        cnt &= ~ICH_SIS_PCM_246_MASK;
        if (runtime->channels == 4 || dbl)
            cnt |= ICH_SIS_PCM_4;
        else if (runtime->channels == 6)
            cnt |= ICH_SIS_PCM_6;
        iputdword(chip, ICHREG(GLOB_CNT), cnt);
        break;
    default:
        cnt = igetdword(chip, ICHREG(GLOB_CNT));
        cnt &= ~(ICH_PCM_246_MASK | ICH_PCM_20BIT);
        if (runtime->channels == 4 || dbl)
            cnt |= ICH_PCM_4;
        else if (runtime->channels == 6)
            cnt |= ICH_PCM_6;
        else if (runtime->channels == 8)
            cnt |= ICH_PCM_8;
        if (chip->device_type == DEVICE_NFORCE) {
            /* reset to 2ch once to keep the 6 channel data in alignment,
             * to start from Front Left always
             */
            if (cnt & ICH_PCM_246_MASK) {
                iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_PCM_246_MASK);
                spin_unlock_irq(&chip->reg_lock);
                msleep(50); /* grrr... */
                spin_lock_irq(&chip->reg_lock);
            }
        } else if (chip->device_type == DEVICE_INTEL_ICH4) {
            if (runtime->sample_bits > 16)
                cnt |= ICH_PCM_20BIT;
        }
        iputdword(chip, ICHREG(GLOB_CNT), cnt);
        break;
    }
    spin_unlock_irq(&chip->reg_lock);
}

ioctl(SNDRV_PCM_IOCTL_START)
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
==> snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
==> snd_pcm_action_single // state等於SNDRV_PCM_STATE_RUNNING
static struct action_ops snd_pcm_action_start = {
    .pre_action = snd_pcm_pre_start,
    .do_action = snd_pcm_do_start,
    .undo_action = snd_pcm_undo_start,
    .post_action = snd_pcm_post_start
};
    ops->pre_action(substream, state);
    ops->do_action(substream, state);
    ops->post_action(substream, state);
    上面ops就是之前提到的snd_pcm_action_start
==> snd_pcm_do_start
==> substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);即snd_intel8x0_playback_ops.trigger
==> snd_intel8x0_pcm_trigger啓動ac97數據傳輸

以上都只是執行一次[luther.gliethttp]
只要發送音頻數據,就會執行該ioctl更新pointer
ioctl(SNDRV_PCM_IOCTL_HWSYNC)
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
==> snd_pcm_hwsync
    case SNDRV_PCM_STATE_RUNNING:
        if ((err = snd_pcm_update_hw_ptr(substream)) < 0)
            break;
==> snd_pcm_update_hw_ptr
==> snd_pcm_update_hw_ptr_post
==> snd_pcm_update_hw_ptr_pos
==> substream->ops->pointer(substream);即snd_intel8x0_playback_ops.pointer
==> snd_intel8x0_pcm_pointer // 更新dma緩衝區數據最後可用數據索引值[luther.gliethttp]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章