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