在文件相关的章节中,已经介绍过打开文件的过程。但还没介绍完,这一章将继续这个话题,具体看看还有哪些操作。
打开一个设备,主线上的函数大概有这么多。这些函数基本都在pcm_native.c中,azx_pcm_open是例外,其实它被设置在了snd_pcm_substream对象中。这种实现方法其实就是设计模式中所说的模板方法,基本框架已经建立好了,具体实现留给子类去处理,不过c 语言中是通过函数指针去实现的。
这一部分,我们要追踪的内容将以这些函数为主线,依次来看它们的具体功能。
5.1 snd_pcm_playback_open
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
int err = nonseekable_open(inode, file);
if (err < 0)
return err;
pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
if (pcm)
snd_card_unref(pcm->card);
return err;
}
这个函数之前已经介绍过,它将通过snd_lookup_minor_data从snd_minors中获取之前保存的snd_pcm对象。下一步就是调用snd_pcm_open了。另外如果是打开录制设备,过程是类似的:
static int snd_pcm_capture_open(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
int err = nonseekable_open(inode, file);
if (err < 0)
return err;
pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_CAPTURE);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
if (pcm)
snd_card_unref(pcm->card);
return err;
}
差别只是传入的参数是SNDRV_PCM_STREAM_CAPTURE。
5.2 snd_pcm_open
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
{
int err;
wait_queue_entry_t wait;
if (pcm == NULL) {
err = -ENODEV;
goto __error1;
}
// file 将被保存到snd_card对象
err = snd_card_file_add(pcm->card, file);
if (err < 0)
goto __error1;
if (!try_module_get(pcm->card->module)) {
err = -EFAULT;
goto __error2;
}
init_waitqueue_entry(&wait, current);
add_wait_queue(&pcm->open_wait, &wait);
mutex_lock(&pcm->open_mutex);
while (1) {
err = snd_pcm_open_file(file, pcm, stream);
if (err >= 0)
break;
if (err == -EAGAIN) {
if (file->f_flags & O_NONBLOCK) {
err = -EBUSY;
break;
}
} else
break;
set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&pcm->open_mutex);
schedule();
mutex_lock(&pcm->open_mutex);
if (pcm->card->shutdown) {
err = -ENODEV;
break;
}
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
}
remove_wait_queue(&pcm->open_wait, &wait);
mutex_unlock(&pcm->open_mutex);
if (err < 0)
goto __error;
return err;
__error:
module_put(pcm->card->module);
__error2:
snd_card_file_remove(pcm->card, file);
__error1:
return err;
}
这段代码的主要作用是应对错误的处理。当err == -EAGAIN,非block打开的模式下,将多次尝试打开文件。
5.3 snd_pcm_open_file
static int snd_pcm_open_file(struct file *file,
struct snd_pcm *pcm,
int stream)
{
struct snd_pcm_file *pcm_file;
struct snd_pcm_substream *substream;
int err;
err = snd_pcm_open_substream(pcm, stream, file, &substream);
if (err < 0)
return err;
pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);
if (pcm_file == NULL) {
snd_pcm_release_substream(substream);
return -ENOMEM;
}
pcm_file->substream = substream;
if (substream->ref_count == 1)
substream->pcm_release = pcm_release_private;
file->private_data = pcm_file;
return 0;
}
这段函数里,我们关注一下snd_pcm_flie。
struct snd_pcm_file {
struct snd_pcm_substream *substream;
int no_compat_mmap;
unsigned int user_pversion; /* supported protocol version */
};
从snd_pcm_open_substream获取的substream,将被保存到这个结构里。然后它将被保存到file的private_data中:file->private_data = pcm_file。这样后续的读写操作就不需要再次查找substream对象了。
5.4 snd_pcm_open_substream
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_substream *substream;
int err;
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
if (err < 0)
return err;
if (substream->ref_count > 1) {
*rsubstream = substream;
return 0;
}
err = snd_pcm_hw_constraints_init(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
goto error;
}
err = substream->ops->open(substream);
if (err < 0)
goto error;
substream->hw_opened = 1;
err = snd_pcm_hw_constraints_complete(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
goto error;
}
/* automatically set EXPLICIT_SYNC flag in the managed mode whenever
* the DMA buffer requires it
*/
if (substream->managed_buffer_alloc &&
substream->dma_buffer.dev.need_sync)
substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC;
*rsubstream = substream;
return 0;
error:
snd_pcm_release_substream(substream);
return err;
}
这里涉及到的几个函数,我们都会介绍到。就这个函数而言,目的是获取substream对象,并对它进行必要的赋值,它是后续读写等操作的对象。
5.5 snd_pcm_attach_substream
int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_str * pstr;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
struct snd_card *card;
int prefer_subdevice;
size_t size;
if (snd_BUG_ON(!pcm || !rsubstream))
return -ENXIO;
if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK &&
stream != SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
*rsubstream = NULL;
// 根据stream来获取snd_pcm_str,stream只能是SNDRV_PCM_STREAM_PLAYBACK或者 SNDRV_PCM_STREAM_CAPTURE
pstr = &pcm->streams[stream];
if (pstr->substream == NULL || pstr->substream_count == 0)
return -ENODEV;
card = pcm->card;
// 从打开的control文件中获取当前的substream是否已经打开
prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM);
// 半双工的设备中,不能同时录制和播放。只能选择其中一种。
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
int opposite = !stream;
for (substream = pcm->streams[opposite].substream; substream;
substream = substream->next) {
if (SUBSTREAM_BUSY(substream))
return -EAGAIN;
}
}
// 针对打开文件选项中追加模式的处理,这时候文件已经打开过了。其实直接返回就可以了,但是要处理多个substream的情况
if (file->f_flags & O_APPEND) {
if (prefer_subdevice < 0) {
if (pstr->substream_count > 1)
return -EINVAL; /* must be unique */
substream = pstr->substream;
} else {
for (substream = pstr->substream; substream;
substream = substream->next)
if (substream->number == prefer_subdevice)
break;
}
if (! substream)
return -ENODEV;
if (! SUBSTREAM_BUSY(substream))
return -EBADFD;
substream->ref_count++;
*rsubstream = substream;
return 0;
}
//选择对应的substream
for (substream = pstr->substream; substream; substream = substream->next) {
if (!SUBSTREAM_BUSY(substream) &&
(prefer_subdevice == -1 ||
substream->number == prefer_subdevice))
break;
}
if (substream == NULL)
return -EAGAIN;
// 生成snd_pcm_runtime对象
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
if (runtime == NULL)
return -ENOMEM;
// 分配status
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = alloc_pages_exact(size, GFP_KERNEL);
if (runtime->status == NULL) {
kfree(runtime);
return -ENOMEM;
}
memset(runtime->status, 0, size);
// 分配control
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = alloc_pages_exact(size, GFP_KERNEL);
if (runtime->control == NULL) {
free_pages_exact(runtime->status,
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
kfree(runtime);
return -ENOMEM;
}
memset(runtime->control, 0, size);
init_waitqueue_head(&runtime->sleep);
init_waitqueue_head(&runtime->tsleep);
runtime->status->state = SNDRV_PCM_STATE_OPEN;
mutex_init(&runtime->buffer_mutex);
// 将snd_pcm_runtime对象赋予substream
substream->runtime = runtime;
// 指向具体驱动中生成的对象,hda设备中指向的是azx_pcm
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
substream->pid = get_pid(task_pid(current));
pstr->substream_opened++;
*rsubstream = substream;
return 0;
}
熟悉这段代码,先再看看snd_pcm_str。
struct snd_pcm_str {
int stream; /* stream (direction) */
struct snd_pcm *pcm;
/* -- substreams -- */
unsigned int substream_count;
unsigned int substream_opened;
struct snd_pcm_substream *substream;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
/* -- OSS things -- */
struct snd_pcm_oss_stream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
struct snd_info_entry *proc_root;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
unsigned int xrun_debug; /* 0 = disabled, 1 = verbose, 2 = stacktrace */
#endif
#endif
struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
struct device dev;
};
这里有一个substream_count,一般这个值都是1。表示一个设备中播放或者录制使用过的stream是一条,但在一些情景下,它可以是多个。比如HDA中支持多SDI模式,录音的时候可以有多路音频数据返回。用处我不是太明白,在回声消除的处理中,不同方位录制的声音,时间上有差值,可以用来消除回声。理解可能和这个有关系。
当substream_cout的数量超过1的时候,就不能单单通过对pcm文件的操作来完成了。需要control文件的配合,这就是要使用snd_ctl_get_preferred_subdevice的原因。
另外可以注意下status与control的分配:
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = alloc_pages_exact(size, GFP_KERNEL);
status 和 control可以通过mmap的方式分享给用户空间,mmap的最小单位是页,所以这里分配空间有点特殊。它们的用法,将在介绍播放音频的时候,详细介绍。