首页 > 技术文章 > rk音频驱动分析之tinyplay播放

wen123456 2020-11-26 15:54 原文

一.tinyplay播放
操作命令:tinyplay /sdcard/test.wav
Tinyplay.c (external\tinyalsa) 
    file = fopen(filename, "rb");  //对应的音频文件
    fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); //读取音频文件头部
     /* parse command line arguments */
    处理一些参数的问题
    //参数分别是播放的文件,声卡,通道,采样率,位数,DMA一次传输的数据量,传输次数
    play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
                chunk_fmt.bits_per_sample, period_size, period_count);
        struct pcm_config config;  //用来记录音频信息
        config.channels = channels;
        config.rate = rate;
        config.period_size = period_size;
        config.period_count = period_count;
        if (bits == 32) //格式
            config.format = PCM_FORMAT_S32_LE;
        else if (bits == 16)
            config.format = PCM_FORMAT_S16_LE;
        if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count))
            struct pcm_params *params;
            params = pcm_params_get(card, device, PCM_OUT);
            //我们这里是/dev/snd/pcmC0D0p
            snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
            fd = open(fn, O_RDWR); //打开播放dev,驱动分析一
            //分配参数结构体
            params = calloc(1, sizeof(struct snd_pcm_hw_params));
            param_init(params); //初始化
            ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params) //会调用到驱动,获取参数,驱动分析二
    pcm = pcm_open(card, device, PCM_OUT, &config);
        pcm->config = *config;
        snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
        pcm->flags = flags;
        pcm->fd = open(fn, O_RDWR); //打开/dev/snd/pcmC0D0p节点,已经分析了
        ////获取pcm的信息,包括card,流,dma各种信息
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info) //获取PCM信息,驱动分析三
        param_init(&params); 初始化参数
        //包括format,channels等
        param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,pcm_format_to_alsa(config->format));
        。。。。。。很多设置。。。。。
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params) //驱动分析四
         /* get our refined hw_params */
        pcm->buffer_size = config->period_count * config->period_size; //缓冲区buf大小
        if (flags & PCM_MMAP) //如果支持mmap
            //mmap将一个文件或者其它对象映射进内存,调用底层的mmap函数,驱动分析五
                                                          起始地址                    长度
            pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                 表示页可以读写,与其它进程共享映射空间                        有效的文件描述词
                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
        params.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
        sparams.period_step = 1;
        //这种开始和定制阈值
        pcm->config.start_threshold = sparams.start_threshold = config->period_count * config->period_size / 2;
        pcm->config.stop_threshold = sparams.stop_threshold = config->period_count * config->period_size;
        if (pcm->flags & PCM_MMAP)
             pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
        sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
        ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams) ////主要是把上层的参数赋值给snd_pcm_runtime,驱动分析六
       rc = pcm_hw_mmap_status(pcm); //单独分析
        #ifdef SNDRV_PCM_IOCTL_TTSTAMP
        int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
        rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); //设置为绝对时间
        #endif
        size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));  
            //frames * pcm->config.channels * byte
            return frames * pcm->config.channels *(pcm_format_to_bits(pcm->config.format) >> 3);
        buffer = malloc(size); //分配内存
         do {
            num_read = fread(buffer, 1, size, file); //把文件的内容读到buffer里
            if (num_read > 0)
                if (pcm_write(pcm, buffer, num_read)) //然后写入pcm,单独分析
        } while (!close && num_read > 0);

1.static int pcm_hw_mmap_status(struct pcm *pcm)
    pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); //见驱动分析五,就是SNDRV_PCM_MMAP_OFFSET_STATUS这个不一样
     if (pcm->flags & PCM_MMAP) //最少的可以用的大小
        pcm->mmap_control->avail_min = pcm->config.avail_min;
    
2.pcm_write(pcm, buffer, num_read)
    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); //得到多少帧
    for (;;)
        if (!pcm->running)
            int prepare_error = pcm_prepare(pcm);
                ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) //调用驱动,驱动分析七
                pcm->prepared = 1;
            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) //调用驱动,驱动分析八


二.驱动分析一
Pcm_native.c (kernel\sound\core) 
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,
 }
};

1.打开播放dev,驱动分析1
在_snd_pcm_new函数中注册snd_pcm_dev_register函数,后面snd_device_register_all中统一调用dev->ops->dev_register
snd_pcm_dev_register函数中会注册/dev/snd/pcmC0D0p和/dev/snd/pcmC0D0c,并提供操作函数,我们这里分析open

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
    //通过次设备号和SNDRV_DEVICE_TYPE_PCM_PLAYBACK获取snd_pcm指针
    pcm = snd_lookup_minor_data(iminor(inode),  SNDRV_DEVICE_TYPE_PCM_PLAYBACK); 
    err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
        //This function adds the file to the file linked-list of the card. This linked-list is used to keep tracking the connection state,
        // and to avoid the release of busy resources by hotplug.
        //这个函数把file 加入到cardlinked-list ,这个linked-list 是用来持续追踪链接状态的,可以避免很忙的资源被hotplug释放
        err = snd_card_file_add(pcm->card, file);
            struct snd_monitor_file *mfile;
            mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
            mfile->file = file;
            list_add(&mfile->list, &card->files_list);  //加入card->files_list链表
        try_module_get(pcm->card->module)
        init_waitqueue_entry(&wait, current); //初始化等待队列,当前的进程
        add_wait_queue(&pcm->open_wait, &wait); //把等待队列加入到pcm->open_wait里面
        while (1)
            err = snd_pcm_open_file(file, pcm, stream);
                err = snd_pcm_open_substream(pcm, stream, file, &substream); //主要是初始化snd_pcm_runtime相关的东西,单独分析2
                pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); //分配snd_pcm_file
                pcm_file->substream = substream; 
                if (substream->ref_count == 1) //因为snd_pcm_open_substream已经初始化,这里是1
                    substream->file = pcm_file;
  substream->pcm_release = pcm_release_private;
file->private_data = pcm_file;
set_current_state(TASK_INTERRUPTIBLE);  //设置当前进程可被中断唤醒
schedule(); //放弃cpu
if (signal_pending(current))   //检查当前进程是否有信号处理,返回不为0表示有信号需要处理。
        remove_wait_queue(&pcm->open_wait, &wait);
    

                 
2.//单独分析2
 err = snd_pcm_open_substream(pcm, stream, file, &substream);
    struct snd_pcm_substream *substream;
    err = snd_pcm_attach_substream(pcm, stream, file, &substream);
        struct snd_pcm_str * pstr;
        pstr = &pcm->streams[stream]; //这里是SNDRV_PCM_STREAM_PLAYBACK,找到对应的snd_pcm_str 
        card = pcm->card;
        list_for_each_entry(kctl, &card->ctl_files, list) //遍历snd_ctl_file
            if (kctl->pid == task_pid(current)) //找到与当前进程pid一样的snd_ctl_file
                prefer_subdevice = kctl->prefer_pcm_subdevice;
        switch (stream)
        case SNDRV_PCM_STREAM_PLAYBACK:
            if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) //如果是半双工
                for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
                    if (SUBSTREAM_BUSY(substream)) //如果正常忙,就是正在工作,就返回
        case SNDRV_PCM_STREAM_CAPTURE: //录音和播放一样,有在工作的就返回
        。。。。。中间有很多判断设备是不是在忙的代码。。。。。。。
        runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); //分配snd_pcm_runtime结构体
        size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); //环形缓冲区结构体大小,需要字节对齐
        runtime->status = snd_malloc_pages(size, GFP_KERNEL); //分配页
        size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); //映射控制
        runtime->control = snd_malloc_pages(size, GFP_KERNEL);
        init_waitqueue_head(&runtime->sleep); //初始化睡眠等待队列
        init_waitqueue_head(&runtime->tsleep);
        runtime->status->state = SNDRV_PCM_STATE_OPEN; //状态为开启
        substream->runtime = runtime; //赋值给substream
        substream->private_data = pcm->private_data;
        substream->ref_count = 1;
       substream->pid = get_pid(task_pid(current));
       pstr->substream_opened++;
       *rsubstream = substream;
    //给pcm加一些限制,// 为该runtime追加hw硬件rules规则,来最终约束substream所属声卡支持的参数特性
    err = snd_pcm_hw_constraints_init(substream); //设置一些限制,比较繁琐,暂不分析
    err = substream->ops->open(substream) //调用到pcm的open,主要是初始化code,platform,dai等,单独分析3
    substream->hw_opened = 1; //说明已经打开
    err = snd_pcm_hw_constraints_complete(substream); //主要设置一些限制,包括权限,格式,channels等
        err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
        err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
        hw->channels_min, hw->channels_max);       


3.pcm的open,单独分析3
static int soc_pcm_open(struct snd_pcm_substream *substream)
    pm_runtime_get_sync(cpu_dai->dev);
    pm_runtime_get_sync(codec_dai->dev);
    pm_runtime_get_sync(platform->dev);
    /* startup the audio subsystem */
    if (cpu_dai->driver->ops->startup) //这里没有,建立cpu dai链接
        ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
    if (platform->driver->ops && platform->driver->ops->open)
        ret = platform->driver->ops->open(substream);  //打开platform,也就是设置DMA相关参数,单独分析4
        //Soc-generic-dmaengine-pcm.c (kernel\sound\soc),调用dmaengine_pcm_open函数
    if (codec_dai->driver->ops->startup) //调用codec_dai的startup函数,只是设置了速度限制,单独分析5
        ret = codec_dai->driver->ops->startup(substream, codec_dai); 
    if (rtd->dai_link->ops && rtd->dai_link->ops->startup) //dai_link的startup函数,暂时没有
        ret = rtd->dai_link->ops->startup(substream);
    /* Check that the codec and cpu DAIs are compatible */
    //检查codec和cpu DAIs会不会兼容,检查的有rate,channels,formats,rates,
    if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)  //这里播放
        //取读取速率的最大值,也就是codec和cpu DAIs都支持的值
        runtime->hw.rate_min = max(codec_dai_drv->playback.rate_min, cpu_dai_drv->playback.rate_min);
        runtime->hw.rate_max = min(codec_dai_drv->playback.rate_max, cpu_dai_drv->playback.rate_max);
        。。。。。。。。
    else //录音,与播放大致一样的检查步骤
    snd_pcm_limit_hw_rates(runtime); //determine rate_min/rate_max fields,设置最大最小速度
    soc_pcm_apply_msb(substream, codec_dai); //设置多少位的限制,codec_dai和cpu_dai一样
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
            bits = dai->driver->playback.sig_bits; //内容的比特数
            for (i = 0; i < ARRAY_SIZE(sample_sizes); i++)  //这里判断是24位的还是32位
                if (bits >= sample_sizes[i])
                    continue
                //设置限制
                ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, sample_sizes[i], bits);
    soc_pcm_apply_msb(substream, cpu_dai);
    /* Symmetry only applies if we've already got an active stream. */
    //只有当我们已经有了一个活动流时才会出现对称性
    if (cpu_dai->active)  //设置对称性
        ret = soc_pcm_apply_symmetry(substream, cpu_dai);
    if (codec_dai->active) {
        ret = soc_pcm_apply_symmetry(substream, codec_dai);

            

4.打开platform,也就是DMA相关的东西,单独分析4
platform相关的参数
static const struct snd_pcm_hardware rockchip_pcm_hardware = {
 .info = SNDRV_PCM_INFO_INTERLEAVED |
        SNDRV_PCM_INFO_BLOCK_TRANSFER |
        SNDRV_PCM_INFO_MMAP |
        SNDRV_PCM_INFO_MMAP_VALID |
        SNDRV_PCM_INFO_PAUSE |
        SNDRV_PCM_INFO_RESUME,
 .formats = SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S20_3LE |
        SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_S32_LE ,
 .channels_min = 2,
 .channels_max = 8,
 .buffer_bytes_max = 2*1024*1024,/*128*1024,*/
 .period_bytes_min = 64,
 .period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
 .periods_min = 3,
 .periods_max = 128,
 .fifo_size = 16,
};
static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
    //首先注册上叙的参数,这个在注册的platform的会赋值rockchip_pcm_hardware 结构体
    ret = snd_soc_set_runtime_hwparams(substream, pcm->config->pcm_hardware);
        struct snd_pcm_runtime *runtime = substream->runtime;
        runtime->hw.info = hw->info;
        runtime->hw.formats = hw->formats;
        runtime->hw.period_bytes_min = hw->period_bytes_min;
        runtime->hw.period_bytes_max = hw->period_bytes_max;
        runtime->hw.periods_min = hw->periods_min;
        runtime->hw.periods_max = hw->periods_max;
        runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
        runtime->hw.fifo_size = hw->fifo_size;
    return snd_dmaengine_pcm_open(substream, chan);
        struct dmaengine_pcm_runtime_data *prtd;
        //设置限制参数,暂不分析
        ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
        prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); //分配dmaengine_pcm_runtime_data,用于记录DMA数据
        prtd->dma_chan = chan;
        substream->runtime->private_data = prtd; //把数据赋值给runtime的private_data指针


5.调用codec_dai的startup函数,单独分析5
    Es8323.c (kernel\sound\soc\codecs)
static int es8323_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
    //设置约束,主要是大约的速度,暂不具体分析
    snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,  es8323->sysclk_constraints);
        return snd_pcm_hw_rule_add(runtime, cond, var, snd_pcm_hw_rule_list, (void *)l, var, -1);


三.驱动分析二
snd_pcm_common_ioctl1
    case SNDRV_PCM_IOCTL_HW_REFINE:
        return snd_pcm_hw_refine_user(substream, arg);
            params = memdup_user(_params, sizeof(*params)); //用户态到内核态的拷贝
            err = snd_pcm_hw_refine(substream, params); //根据底层的限制,重新给params 赋值
                struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
                m = hw_param_mask(params, k);
            copy_to_user(_params, params, sizeof(*params)) //拷贝给用户空间
            


四.驱动分析三
static int snd_pcm_common_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    case SNDRV_PCM_IOCTL_INFO:
        return snd_pcm_info_user(substream, arg); //获取pcm的信息,包括card,流,dma各种信息
            info = kmalloc(sizeof(*info), GFP_KERNEL); //分配snd_pcm_info
            info->card = pcm->card->number;
   info->device = pcm->device;
            info->stream = substream->stream;
            info->subdevice = substream->number;
            strlcpy(info->id, pcm->id, sizeof(info->id));
            strlcpy(info->name, pcm->name, sizeof(info->name));
            info->dev_class = pcm->dev_class;
            info->dev_subclass = pcm->dev_subclass;
            info->subdevices_count = pstr->substream_count;
            info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
            strlcpy(info->subname, substream->name, sizeof(info->subname));
            runtime = substream->runtime;
        if (runtime) { //这里没有什么用
            info->sync = runtime->sync;
            substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
        copy_to_user(_info, info, sizeof(*info)) //赋值给用户空间
            

五.驱动分析四
case SNDRV_PCM_IOCTL_HW_PARAMS:
return snd_pcm_hw_params_user(substream, arg);
    params = memdup_user(_params, sizeof(*params)); //从用户空间获取
    err = snd_pcm_hw_params(substream, params);
        runtime = substream->runtime;
        err = snd_pcm_hw_refine(substream, params); //已经分析了,根据底层的限制,重新给params 赋值
        //choose a configuration defined by @params
        err = snd_pcm_hw_params_choose(substream, params);
        //对于所有的参数,包括FORMAT,CHANNELS,根据底层都返回最小值
        for (v = vars; *v != -1; v++) //    refine config space and return minimum value
            err = snd_pcm_hw_param_first(pcm, params, *v, NULL);
    if (substream->ops->hw_params != NULL) 
        //这里只有是设置cpu dai和codec dai的类型和时钟等
        err = substream->ops->hw_params(substream, params); //调用到rk音频驱动分析之machine的soc_pcm_hw_params函数
    copy_to_user(_params, params, sizeof(*params)) //拷贝给用户空间



六.驱动分析五
DMA内存分配
static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
 .open = snd_pcm_mmap_data_open,
 .close = snd_pcm_mmap_data_close,
 .fault = snd_pcm_mmap_data_fault,
};
static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
    pcm_file = file->private_data;
    substream = pcm_file->substream;
    offset = area->vm_pgoff << PAGE_SHIFT;
    switch (offset) //我们这里应该是snd_pcm_mmap_data
    case SNDRV_PCM_MMAP_OFFSET_STATUS:   
        if (pcm_file->no_compat_mmap)
            return snd_pcm_mmap_status(substream, file, area); //只是加了一个操作函数
                size = area->vm_end - area->vm_start;
                area->vm_ops = &snd_pcm_vm_ops_status;
                area->vm_private_data = substream;
                area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
    case SNDRV_PCM_MMAP_OFFSET_CONTROL:
        return snd_pcm_mmap_control(substream, file, area);
    default: 
        return snd_pcm_mmap_data(substream, file, area);
            //mmap DMA buffer,分配DMA内存
            size = area->vm_end - area->vm_start;
            offset = area->vm_pgoff << PAGE_SHIFT;
            dma_bytes = PAGE_ALIGN(runtime->dma_bytes); //需要字节对齐
            area->vm_ops = &snd_pcm_vm_ops_data; //操作函数
            area->vm_private_data = substream; //是substream指针
            if (substream->ops->mmap) //调用platform->driver->ops->mmap;,这个有在soc_new_pcm赋值
                err = substream->ops->mmap(substream, area); //这里没有
            else
                err = snd_pcm_lib_default_mmap(substream, area); //mmap the DMA buffer on RAM
                    area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
                    //分配DMA缓冲区
                    return dma_mmap_coherent(substream->dma_buffer.dev.dev, area, substream->runtime->dma_area,
                    substream->runtime->dma_addr, area->vm_end - area->vm_start);
                    /* mmap with fault handler */
                    area->vm_ops = &snd_pcm_vm_ops_data_fault; //操作函数


七.驱动分析六
//主要是把上层的参数赋值给snd_pcm_runtime
static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_sw_params __user * _params)
    copy_from_user(&params, _params, sizeof(params))
    err = snd_pcm_sw_params(substream, &params);
        struct snd_pcm_runtime *runtime;
        .......一些判断......
        runtime->tstamp_mode = params->tstamp_mode; //给runtime相关赋值
        runtime->period_step = params->period_step;
        runtime->control->avail_min = params->avail_min;
        runtime->start_threshold = params->start_threshold;
        runtime->stop_threshold = params->stop_threshold;
        runtime->silence_threshold = params->silence_threshold;
        runtime->silence_size = params->silence_size;
        params->boundary = runtime->boundary;
        if (snd_pcm_running(substream)) //如果正在运行
            //fill ring buffer with silence,用安静填满环形缓冲区
            snd_pcm_playback_silence(substream, ULONG_MAX);
            err = snd_pcm_update_state(substream, runtime); //更新状态
    copy_to_user(_params, &params, sizeof(params))


八.驱动分析七
//prepare the PCM substream to be triggerable,预备pcm可以触发
static struct action_ops snd_pcm_action_prepare = {
 .pre_action = snd_pcm_pre_prepare,
 .do_action = snd_pcm_do_prepare,
 .post_action = snd_pcm_post_prepare
};
static int snd_pcm_prepare(struct snd_pcm_substream *substream, struct file *file)
    // wait until the power-state is changed.等待电源标志变化
    if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0))
        init_waitqueue_entry(&wait, current); //这个函数的作用是用新进程来初始化队列
        add_wait_queue(&card->power_sleep, &wait); //把等待元素wait,加到 card->power_slee队列前面
        while (1)
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule_timeout(30 * HZ);
    res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, f_flags);
        if (snd_pcm_stream_linked(substream)) //查看有没有连接其他的substream
            res = snd_pcm_action_group(ops, substream, state, 0);  //暂不分析
        else
            res = snd_pcm_action_single(ops, substream, state);
                res = ops->pre_action(substream, state); //调用snd_pcm_action_prepare ,前面有赋值
                    //we use the second argument for updating f_flags,这里只是更新f_flags 
                    //这些是文件标志, 例如 O_RDONLY, O_NONBLOCK,驱动应当检查O_NONBLOCK 标志来看是否是请求非阻塞操作
                    substream->f_flags = f_flags; 
                res = ops->do_action(substream, state);
                    //主要是通知widget更新电源状态,然后设置声卡打开播放
                    err = substream->ops->prepare(substream); //初始化赋值soc_pcm_prepare,单独分析
                    return snd_pcm_do_reset(substream, 0);
                        int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
                            if (platform->driver->ops->ioctl)
                                return platform->driver->ops->ioctl(substream, cmd, arg); //这里调用rk音频驱动之platform的snd_pcm_lib_ioctl
                            return snd_pcm_lib_ioctl(substream, cmd, arg); //这里不会到这里
                        runtime->hw_ptr_base = 0;
                        .....环形缓冲区指针状态相关的reset...
                ops->post_action(substream, state);  //初始化赋值snd_pcm_post_prepare
                    runtime->control->appl_ptr = runtime->status->hw_ptr;
                    snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); //更新状态/为* stream is ready to start */



1.soc_pcm_prepare
/*
 * Called by ALSA when the PCM substream is prepared, can set format, sample
 * rate, etc. This function is non atomic and can be called multiple times,
 * it can refer to the runtime info.
 */
    if (rtd->dai_link->ops && rtd->dai_link->ops->prepare)
        ret = rtd->dai_link->ops->prepare(substream); //我们这里没有
    if (platform->driver->ops && platform->driver->ops->prepare) //这里没有
        ret = codec_dai->driver->ops->prepare(substream, codec_dai);
    if (cpu_dai->driver->ops->prepare) //这里没有
        ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
    //Sends a stream event to the dapm core. The core then makes any necessary widget power changes.
    //这里会调动到widget 
    snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START);
        soc_dapm_stream_event(rtd, stream, event);
        if (stream == SNDRV_PCM_STREAM_PLAYBACK)
            w_cpu = cpu_dai->playback_widget;
            w_codec = codec_dai->playback_widget;
        else
            ......
        if (w_cpu)
            dapm_mark_dirty(w_cpu, "stream event");  //加入dapm_dirty链表
                list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
        if (w_codec)
            dapm_mark_dirty(w_codec, "stream event");
        //Scan each dapm widget for complete audio path,这里就是搜索path
        dapm_power_widgets(&rtd->card->dapm, event);        
    snd_soc_dai_digital_mute(codec_dai, 0, substream->stream);
        //Mutes the DAI DAC.
        if (dai->driver->ops->mute_stream)
            return dai->driver->ops->mute_stream(dai, mute, direction); //我们这里没有
        else if (direction == SNDRV_PCM_STREAM_PLAYBACK && dai->driver->ops->digital_mute)
            return dai->driver->ops->digital_mute(dai, mute); //调用rk音频驱动分析之codec里面的es8323_mute,进行播放准备

                   
九.驱动分析八
snd_pcm_playback_ioctl里面的snd_pcm_playback_ioctl1的
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
    switch (cmd) 
        case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
            if (put_user(0, &_xferi->result)) //给应用发送0的返回值
            copy_from_user(&xferi, _xferi, sizeof(xferi)) //从用户拷贝数据
            result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
                /* sanity-check for read/write methods */
                err = pcm_sanity_check(substream); //检查有不有读写函数
                runtime = substream->runtime;
                nonblock = !!(substream->f_flags & O_NONBLOCK);
                //传输函数,单独分析, snd_pcm_lib_write_transfer这个也要单独分析
                return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);

1.snd_pcm_lib_write1
    runtime->twake = runtime->control->avail_min ? : 1;
    if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) //如果正在进行
        snd_pcm_update_hw_ptr(substream); ///* CAUTION: call it with irq disabled */
            return snd_pcm_update_hw_ptr0(substream, 0); //暂不分析
    avail = snd_pcm_playback_avail(runtime); //获取当前ring buf的可用大小
        // 读指针+总的buf size-写指针的数值
        
        //上图白色部分avail ,灰色是已经用掉的
        snd_pcm_sframes_t avail = snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;;
        if (avail < 0) //读写指针换位的时候
            avail += runtime->boundary; // avail=runtime->boundary + avail;加负数,还是等于可以用的
        else if ((snd_pcm_uframes_t) avail >= runtime->boundary) //溢出了,无可用的,已经是负的
            avail -= runtime->boundary; //avail = runtime->boundary - avail ; //返回负的可以的大小
    while (size > 0)
        snd_pcm_uframes_t frames, appl_ptr, appl_ofs; //每块buf
        snd_pcm_uframes_t cont;
        if (!avail) //无可用的缓冲区
            if (nonblock) //非阻塞模式,返回
                err = -EAGAIN;
            //如果非0,就唤醒,然后开始传输
             runtime->twake = min_t(snd_pcm_uframes_t, size, runtime->control->avail_min ? : 1);
            //Wait until avail_min data becomes available,等待avail_min 可以用
             err = wait_for_avail(substream, &avail);
        frames = size > avail ? avail : size; //得到frames 大小
        cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
        appl_ptr = runtime->control->appl_ptr; //HW BUF写指针的位置
        appl_ofs = appl_ptr % runtime->buffer_size; //hw_ofs是读指针在当前HW buffer中的位置。
        //传输,这个是传进来的snd_pcm_lib_write_transfer函数指针,单独分析2
        err = transfer(substream, appl_ofs, data, offset, frames);  //这里只是从用户空间把数据拷贝到DMA
        appl_ptr += frames; //写指针移位
        if (appl_ptr >= runtime->boundary) //如果已经越位
            appl_ptr -= runtime->boundary; //回到环形缓冲区头部
        runtime->control->appl_ptr = appl_ptr;
        if (substream->ops->ack) //这里没有
            substream->ops->ack(substream);
        offset += frames; //offset = offset + frames //偏移增加
        size -= frames; // size = size - frames;
        xfer += frames; //xfer = xfer + frames;
        avail -= frames; //avail = avail - frames;
        if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&  //之前已经准备好传输了
          snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) //可以读的界限>可以传输的界限
            err = snd_pcm_start(substream); //开始传输,单独分析3
        

2.单独分析2:snd_pcm_lib_write_transfer
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,
          unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
    if (substream->ops->copy) //这里没有
        err = substream->ops->copy(substream, -1, hwoff, buf, frames)  //这里会调用platform->driver->ops->copy;
            platform->driver->ops->copy; //没有
    else
        char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
        copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)) //从用户空间拷贝


单独分析3:snd_pcm_start
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
};

int snd_pcm_start(struct snd_pcm_substream *substream)
    return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
        res = snd_pcm_action_single(ops, substream, state);
            res = ops->pre_action(substream, state); //调用snd_pcm_pre_start
                //check whether any data exists on the playback buffer
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&  !snd_pcm_playback_data(substream))
                    if (runtime->stop_threshold >= runtime->boundary)
                        return 1;
                    //如果可用的buf少于总的的buf,说明有数据,就是可以的
                    return snd_pcm_playback_avail(runtime) < runtime->buffer_size; 
                runtime->trigger_master = substream;
            res = ops->do_action(substream, state);   \\调用snd_pcm_do_start      
                return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);    //开始传输,单独分析4
            ops->post_action(substream, state); \\调用snd_pcm_post_start
                snd_pcm_trigger_tstamp(substream); //获取时间
                runtime->hw_ptr_jiffies = jiffies; //系统时间
                runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / runtime->rate; //传输需要的时间
                runtime->status->state = state; //SNDRV_PCM_STATE_RUNNING
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0)
                    //when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
                    snd_pcm_playback_silence(substream, ULONG_MAX); //如果有silence_size ,就把他填到缓冲区里
                if (substream->timer)
                    snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART,  &runtime->trigger_tstamp);
                
                


单独分析4:soc_pcm_trigger
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
    if (codec_dai->driver->ops->trigger) //这里没有
        ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
    if (platform->driver->ops && platform->driver->ops->trigger) //进行DMA传输到I2S控制器
        ret = platform->driver->ops->trigger(substream, cmd); //rk音频驱动之platform的snd_dmaengine_pcm_trigger
    if (cpu_dai->driver->ops->trigger) //调用rk音频驱动分析之codec的rockchip_i2s_trigger
        ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); //从i2s寄存器到codec的传输
        

    

推荐阅读