当前位置:首页 > 芯闻号 > 充电吧
[导读]杂记asla-lib库函数snd_pcm_open打开流程浅析ac97声卡intel8x0的DMA内存substream->dma_buffer什么时候被赋值浅析ac97声卡intel8x0的r

杂记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]

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

这篇文章想分享笔者在嵌入式开发过程中常用的库函数,他们的使用方法,使用场景,使用好处以及头文件位置。1#include一般我在写C代码的时候都会包含此头文件,因为一旦包含此文件后,你就可以畅快的使用bool数据类型,而不...

关键字: 库函数

关注、星标公众号,直达精彩内容来源:知乎作者:Clarence这篇文章想分享笔者在嵌入式开发过程中常用的库函数,他们的使用方法,使用场景,使用好处以及头文件位置。1#include一般我在写C代码的时候都会包含此头文件,...

关键字: 库函数

基本概念阐述memcpy和memmove都是C语言的库函数,相比于 strcpy和 strncpy只能针对于字符类型的数组(),这两个函数可以拷贝其他类型的数组,对于 memcpy和 memmove的区别是什么呢?这里,...

关键字: 库函数 ov

在嵌入式Linux的C语言开发中,C语言的基本编程依然是最重要的内容。

关键字: 嵌入式 C语言 库函数

单片机编程软件的使用频率极高,采用单片机编程软件,可制造诸多系统。对于单片机编程软件,小编做过诸多介绍。本文对于单片机编程软件的介绍基于Keil,主要在于介绍该单片机编程软件是如何处理库函数以及寄存器的关系的。

关键字: 单片机编程软件 库函数 指数

1 整体架构   Application  ---------------   Alsa-lib                   User Space ---------------------

关键字: callback playback

stm32有两个看门狗,独立看门狗和窗口看门狗,其实两者的功能是类似的,只是喂狗的限制时间不同。独立看门狗是限制喂狗时间在0-x内,x由你的相关寄存器决定。喂狗的时间不能过晚。窗口看门狗,所以称之为窗口就是因为

关键字: STM32 库函数 操作寄存器 窗口看门狗

首先,I2C总线由两条线——串行数据(SDA)和串行时钟(SCL),这是同步通信,也是半双工通信,不能同时读写。每个器件都有一个唯一的地址识别,当总线空闲时I2C两条线都是高电平,只有当连接到总线的器件的输出级是

关键字: i2c stm32f429 库函数 读取eeprom

stm32f103最少有2个AD模数转换器,每个ADC都有18个通道,可以测量16个外部和2个内部模拟量。最大转换频率为1Mhz,也就是转换时间为1us(在 ADCCLK = 14Mhz,采样周期为1.5个时钟周期时)。...

关键字: STM32 库函数 操作寄存器 ad模数转换

问题及现象(STM32F103系列:http://www.y-ec.com/cpcp/class/?32.html)使用USART_SendData()函数非连续发送单个字符是没有问题的;当连续发送字符时(两个字符间没有...

关键字: STM32 库函数
关闭
关闭