Linux ALSA架构:从tinyalsa到音频驱动(八)

Linux ALSA架构:从tinyalsa到音频驱动(八)

一、本文简介

本文讲一下从HAL层的TinyALSA调到ASOC的platform、codec、machine的完整流程,也就是如何从Tinyalsa调到驱动程序中。

二、tinyalsa到Linux kernel

在TinyALSA中,用户对pcm进行操作,该结构体如下:

struct pcm {
    int fd;   
    unsigned int flags;
    int xrun;
    unsigned int buffer_size;
    unsigned int boundary;
    char error[PCM_ERROR_MAX];
    struct pcm_config config;
    struct snd_pcm_mmap_status *mmap_status;
    struct snd_pcm_mmap_control *mmap_control;
    struct snd_pcm_sync_ptr *sync_ptr;
    void *mmap_buffer;
    unsigned int noirq_frames_per_msec;
    int pcm_delay;
    unsigned int subdevice;
    struct pcm_ops *ops;
    void *data;
    void *snd_node;
};

在tinyalsa中播放过程中的主要调用函数为pcm_open、pcm_start和pcm_write,以下逐一分析各个函数。

2.1 pcm_open
pcm_open()
    struct pcm *pcm;
    struct snd_pcm_info info;
    pcm = calloc(1, sizeof(struct pcm));
    pcm->ops = &hw_ops;
    pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL);
        struct pcm_hw_data *hw_data;
        hw_data = calloc(1, sizeof(struct pcm_hw_data));
        snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c",card, 
                        device, flags & PCM_IN ? 'c' : 'p');
        fd = open(fn, O_RDWR | O_NONBLOCK);
        hw_data->card = card;
        hw_data->device = device;
        hw_data->fd = fd;
        hw_data->node = node;
        *data = hw_data;
    pcm->flags = flags;
    pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info);
        snd_pcm_info(substream, info);
            strcut snd_pcm *pcm = substream->pcm;
            //把pcm信息拷贝到info
            info->subdevice = substream->number;
            ...
    pcm->subdevice = info.subdevice;
    pcm_set_config(pcm, config);

    // Only on coherent architectures, On others, we have to use HWSYNC ioctl
    pcm_hw_mmap_status(pcm);

hw_ops指的就是操作硬件设备的一组函数。plug_ops是处理plug插件的一组函数,plug 是 ALSA 中的一种插件,它可以自动进行音频格式转换。

    struct pcm_ops hw_ops = {
        .open = pcm_hw_open,
        .close = pcm_hw_close,
        .ioctl = pcm_hw_ioctl,
        .mmap = pcm_hw_mmap,
    .    munmap = pcm_hw_munmap,
        .poll = pcm_hw_poll,
    };

open就是主设备号file_operations有open函数,根据inode找到次设备号,snd_open替换完file_operations后,紧跟着 file->f_op->open(inode, file),这个就是调用次设备的open。在soc_pcm_components_open中会调用到platform的open函数。

int snd_open(struct inode *inode, struct file *file)
    unsigned int minor = iminor(inode);
    mptr = snd_minors[minor];
    new_fops = fops_get(mptr->f_ops);
    replace_fops(file, new_fops);
    err = file->f_op->open(inode, file);    // =snd_pcm_playback_open
        err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
            err = snd_pcm_open_file(file, pcm, stream);
                struct snd_pcm_file *pcm_file;
                struct snd_pcm_substream *substream;
                err = snd_pcm_open_substream(pcm, stream, file, &substream);
                    err = snd_pcm_hw_constraints_init(substream);
                    err = substream->ops->open(substream) // = soc_pcm_open
                        ret = soc_pcm_components_open(substream);
                    err = snd_pcm_hw_constraints_complete(substream);
                pcm_file->substream = substream;
                file->private_data = pcm_file;

pcm_set_config函数会runtime的软件和硬件参数进行赋值。如果flag为PCM_MMAP,则将进行mmap映射(在AAudio中详细介绍)。

pcm_set_config(pcm, config);
    pcm->config = *config;
    strcut snd_pcm_hw_params params;
    pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params);
        snd_pcm_hw_params_user(substream, arg);
            struct snd_pcm_hw_params *params;
            params = memdup_user(_params, sizeof(*params));
            err = snd_pcm_hw_params(substream, params);
                runtime = substream->runtime;
                err = substream->ops->hw_params(substream, params);
                    ret = snd_soc_pcm_component_hw_params(substream, params);
                        //runtime->dma_buffer_p = bufp;
                        ret = component->driver->hw_params(substream, params);
                //设置runtime的硬件参数
                runtime->access = params_access(params);
                ...
                snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);
            copy_to_user(_params, params, sizeof(*params));

    pcm->config.period_size =param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
    pcm->config.period_count =param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
    pcm->buffer_size = config->period_count * config->period_size;
    //
    if (pcm->flags & PCM_MMAP) 
        pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                PROT_READ | PROT_WRITE, MAP_SHARED, 0);

    strcut snd_pcm_sw_params params;
    pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &params);
        snd_pcm_sw_params_user(substream, arg);
            struct snd_pcm_sw_params *params;
            copy_from_user(&params, _params, sizeof(params));
            err = snd_pcm_sw_params(substream, &params);
                //设置runtime的软件参数
                runtime->tstamp_mode = params->tstamp_mode;
                ...
            copy_to_user(_params, &params, sizeof(params));

struct snd_pcm_sw_params结构体参数如下:

struct snd_pcm_sw_params {
    int tstamp_mode;            //时间戳模式,设置为SNDRV_PCM_TSTAMP_ENABLE表示启用时间戳
    unsigned int period_step;   //周期步长,通常设置为1
    unsigned int sleep_min;            /* min ticks to sleep */
    snd_pcm_uframes_t avail_min;        /* min avail frames for wakeup */
    snd_pcm_uframes_t xfer_align;        /* obsolete: xfer size need to be a multiple */
    snd_pcm_uframes_t start_threshold;    /* min hw_avail frames for automatic start */
    snd_pcm_uframes_t stop_threshold;    /* min avail frames for automatic stop */
    snd_pcm_uframes_t silence_threshold;    /* min distance from noise for silence filling */
    snd_pcm_uframes_t silence_size;        /* silence block size */
    snd_pcm_uframes_t boundary;        /* pointers wrap point */
    unsigned int proto;            /* protocol version */
    unsigned int tstamp_type;        /* timestamp type (req. proto >= 2.0.12) */
    unsigned char reserved[56];        /* reserved for future */
};

2.1 pcm_start

pcm_start(struct pcm *pcm)
    res = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
        snd_pcm_start_lock_irq(substream);
            static const 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
            };
            snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
                res = snd_pcm_action(ops, substream, state);
                    res = snd_pcm_action_single(ops, substream, state);
                        res = ops->pre_action(substream, state);
                            runtime->trigger_tstamp_latched = false;
                            runtime->trigger_master = substream;
                        res = ops->do_action(substream, state);
                            substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
                                //dai_link和component trigger
                        ret = ops->post_action(substream, state);
                            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;
                            snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);

pcm_start最终调用会到dai_link和component trigger函数

2.3 pcm_write

pcm_write(struct pcm *pcm, void *data, unsigned int count)
    unsigned int requested_frames = pcm_bytes_to_frames(pcm, count);
    pcm_writei(pcm, data, requested_frames);
        pcm_generic_transfer(pcm, data, frame_count);
            pcm_rw_transfer(pcm, data, frames);
                struct snd_xferi transfer;
                transfer.buffer = data;
                transfer.frames = frames;
                transfer.result = 0;
                res = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &transfer);
                    snd_pcm_xferi_frames_ioctl(substream, arg);
                        struct snd_xferi xferi;
                        copy_from_user(&xferi, _xferi, sizeof(xferi));
                        result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
                            __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
                                struct snd_pcm_runtime *runtime = substream->runtime;
                                snd_pcm_uframes_t xfer = 0;
                                snd_pcm_uframes_t offset = 0;
                                snd_pcm_uframes_t avail;
                                pcm_copy_f writer;
                                pcm_transfer_f transfer;

                                writer = interleaved_copy;
                                transfer = default_write_copy;
                                //数据不足等待唤醒时间
                                runtime->twake = runtime->control->avail_min ? : 1;
                                if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
                                    snd_pcm_update_hw_ptr(substream);

                                avail = snd_pcm_avail(substream);
                                while (size > 0) {
                                    snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
                                    snd_pcm_uframes_t cont;
                                    frames = size > avail ? avail : size;
                                    appl_ptr = READ_ONCE(runtime->control->appl_ptr);
                                    appl_ofs = appl_ptr % runtime->buffer_size;
                                    cont = runtime->buffer_size - appl_ofs;

                                    err = writer(substream, appl_ofs, data, offset, frames, transfer);
                                        hwoff = frames_to_bytes(runtime, hwoff);
                                        off = frames_to_bytes(runtime, off);
                                        frames = frames_to_bytes(runtime, frames);
                                        transfer(substream, 0, hwoff, data + off, frames);
                                            copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff),
                                                      (void __user *)buf, bytes);
                                                runtime->dma_area + hwoff + channel * 
                                                            (runtime->dma_bytes / runtime->channels);
                                    appl_ptr += frames;
                                    err = pcm_lib_apply_appl_ptr(substream, appl_ptr);
                                        runtime->control->appl_ptr = appl_ptr;
                                    offset += frames;
                                    size -= frames;
                                    xfer += frames;
                                    avail -= frames;

runtime->dma_buffer_p有两个指针,runtime->status->hw_ptr和runtime->control->appl_ptr。runtime->status->hw_ptr为硬件读数据的指针,runtime->control->appl_ptr为应用写数据的指针。snd_pcm_update_hw_ptr是更新runtime->status->hw_ptr指针。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值