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, ¶ms);
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(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
pcm->config.period_count =param_get_int(¶ms, 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, ¶ms);
snd_pcm_sw_params_user(substream, arg);
struct snd_pcm_sw_params *params;
copy_from_user(¶ms, _params, sizeof(params));
err = snd_pcm_sw_params(substream, ¶ms);
//设置runtime的软件参数
runtime->tstamp_mode = params->tstamp_mode;
...
copy_to_user(_params, ¶ms, 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指针。