关闭

杂记asla-lib库函数snd_pcm_open打开流程

标签: playbackstructactiondststreambuffer
1466人阅读 评论(0) 收藏 举报
分类:
杂记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]
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:99492次
    • 积分:1434
    • 等级:
    • 排名:千里之外
    • 原创:8篇
    • 转载:109篇
    • 译文:0篇
    • 评论:47条
    最新评论