在LINUX系统中,每个设备文件都是文件。音频设备也是一样,它的设备文件被放在/dev/snd目录下,我们来看下这些设备文件:
ls /dev/snd -l
crw-rw----+ 1 root audio 116, 2 5月 19 21:24 controlC0
crw-rw----+ 1 root audio 116, 4 6月 6 19:31 pcmC0D0c
crw-rw----+ 1 root audio 116, 3 6月 11 11:53 pcmC0D0p
crw-rw----+ 1 root audio 116, 1 5月 19 21:24 seq
crw-rw----+ 1 root audio 116, 33 5月 19 21:24 timer
(1)controlC0: 音频控制设备文件,例如通道选择,混音,麦克风的控制等
(2)pcmC0D0c: 声卡0设备0的录音设备,c表示capter;
(3)pcmC0D0p: 声卡0设备0的播音设备,p表示play;
(4)seq: 音序器设备;
(5)timer:定时器设备。
这5个设备为音频设备最常见的设备,它还可以是其它设备类型,这些类型被定义在include/sound/core.h:
#define SNDRV_DEV_TOPLEVEL ((__force snd_device_type_t) 0)
#define SNDRV_DEV_CONTROL ((__force snd_device_type_t) 1)
#define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 2)
#define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000)
#define SNDRV_DEV_PCM ((__force snd_device_type_t) 0x1001)
#define SNDRV_DEV_RAWMIDI ((__force snd_device_type_t) 0x1002)
#define SNDRV_DEV_TIMER ((__force snd_device_type_t) 0x1003)
#define SNDRV_DEV_SEQUENCER ((__force snd_device_type_t) 0x1004)
#define SNDRV_DEV_HWDEP ((__force snd_device_type_t) 0x1005)
#define SNDRV_DEV_INFO ((__force snd_device_type_t) 0x1006)
#define SNDRV_DEV_BUS ((__force snd_device_type_t) 0x1007)
#define SNDRV_DEV_CODEC ((__force snd_device_type_t) 0x1008)
#define SNDRV_DEV_JACK ((__force snd_device_type_t) 0x1009)
#define SNDRV_DEV_LOWLEVEL ((__force snd_device_type_t) 0x2000)
驱动代码分析:
在调用snd_soc_register_card()的过程中会创建/dev/snd/下的设备文件。
代码score-core.c:
函数snd_soc_instantiate_card():
/* early DAI link probe */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i, order); //处理每个dai_link
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}
}
跟踪soc_probe_dai_link()的代码,见
static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
int ret;
dev_dbg(card->dev, "probe %s dai link %d late %d\n",
card->name, num, order);
/* config components */
codec_dai->codec = codec;
cpu_dai->platform = platform;
codec_dai->card = card;
cpu_dai->card = card;
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;
/* probe the cpu_dai */
if (!cpu_dai->probed &&
cpu_dai->driver->probe_order == order) {
if (!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);
if (ret < 0) {
pr_err("asoc: failed to probe CPU DAI %s: %d\n",
cpu_dai->name, ret);
module_put(cpu_dai->dev->driver->owner);
return ret;
}
}
cpu_dai->probed = 1;
/* mark cpu_dai as probed and add to card dai list */
list_add(&cpu_dai->card_list, &card->dai_dev_list);
}
/* probe the CODEC */
if (!codec->probed &&
codec->driver->probe_order == order) {
ret = soc_probe_codec(card, codec);
if (ret < 0)
return ret;
}
/* probe the platform */
if (!platform->probed &&
platform->driver->probe_order == order) {
ret = soc_probe_platform(card, platform);
if (ret < 0)
return ret;
}
/* probe the CODEC DAI */
if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
if (codec_dai->driver->probe) {
ret = codec_dai->driver->probe(codec_dai);
if (ret < 0) {
pr_err("asoc: failed to probe CODEC DAI %s: %d\n",
codec_dai->name, ret);
return ret;
}
}
/* mark codec_dai as probed and add to card dai list */
codec_dai->probed = 1;
list_add(&codec_dai->card_list, &card->dai_dev_list);
}
/* complete DAI probe during last probe */
if (order != SND_SOC_COMP_ORDER_LAST)
return 0;
ret = soc_post_component_init(card, codec, num, 0);
if (ret)
return ret;
ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
if (ret < 0)
pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret);
/* create the pcm */
ret = soc_new_pcm(rtd, num);
if (ret < 0) {
pr_err("asoc: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
/* add platform data for AC97 devices */
if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
return 0;
}
Soc-pcm.c:
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
soc_pcm_ops->open = soc_pcm_open;
soc_pcm_ops->close = soc_pcm_close;
soc_pcm_ops->hw_params = soc_pcm_hw_params;
soc_pcm_ops->hw_free = soc_pcm_hw_free;
soc_pcm_ops->prepare = soc_pcm_prepare;
soc_pcm_ops->trigger = soc_pcm_trigger;
soc_pcm_ops->pointer = soc_pcm_pointer;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min)
capture = 1;
dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
ret = snd_pcm_new(rtd->card->snd_card, new_name,
num, playback, capture, &pcm);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
return ret;
}
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
rtd->pcm = pcm;
pcm->private_data = rtd;
if (platform->driver->ops) {
soc_pcm_ops->mmap = platform->driver->ops->mmap;
soc_pcm_ops->pointer = platform->driver->ops->pointer;
soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
soc_pcm_ops->copy = platform->driver->ops->copy;
soc_pcm_ops->silence = platform->driver->ops->silence;
soc_pcm_ops->ack = platform->driver->ops->ack;
soc_pcm_ops->page = platform->driver->ops->page;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
pr_err("asoc: platform pcm constructor failed\n");
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name);
return ret;
}
Sound/core/pcm.c:
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
false, rpcm);
}
_snd_pcm_new:
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (pcm == NULL) {
snd_printk(KERN_ERR "Cannot allocate PCM\n");
return -ENOMEM;
}
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
snd_pcm_dev_register()的代码如下:
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm_notify *notify;
char str[16];
struct snd_pcm *pcm;
struct device *dev;
if (snd_BUG_ON(!device || !device->device_data))
return -ENXIO;
pcm = device->device_data;
mutex_lock(®ister_mutex);
err = snd_pcm_add(pcm);
if (err) {
mutex_unlock(®ister_mutex);
return err;
}
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL || pcm->internal)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
/* device pointer to use, pcm->dev takes precedence if
* it is assigned, otherwise fall back to card's device
* if possible */
dev = pcm->dev;
if (!dev)
dev = snd_card_get_device_link(pcm->card);
/* register pcm */
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);
if (err < 0) {
list_del(&pcm->list);
mutex_unlock(®ister_mutex);
return err;
}
snd_add_device_sysfs_file(devtype, pcm->card, pcm->device,
&pcm_attrs);
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
list_for_each_entry(notify, &snd_pcm_notify_list, list)
notify->n_register(pcm);
mutex_unlock(®ister_mutex);
return 0;
}
上面粗体代码部分即是在/dev/snd下的play和capter设备的名称。
/dev/snd/pcmCxDnp/c的操作函数即对应pcm_native.c中的snd_pcm_f_ops[2], [0]是play操作,[1]是capter操作:
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, //对应应用层的open
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl, //对应应用层的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,
}
};