Android/Linux音频架构开发ALSA-篇5

接上一篇我们讲到snd_device_register_all,这里最后调用了dev->ops->dev_register(dev);那么dev_register是怎么来的,下面我们根据pcm设备逻辑的创建来找一找

1、snd_pcm_new

创建pcm逻辑设备的函数就是snd_pcm_new,这也是alsa为我们提供的接口。它定义在linux\sound\core\pcm.c文件中


/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
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);

2、_snd_pcm_new

_snd_pcm_new首先就创建了一个snd_device_ops,这里我们主要关注dev_register函数指针,指向了snd_pcm_dev_register,上面我们说到snd_device_register_all方法最终调用了dev_register其实就是调用的这里。我们继续追,看如何关联起来的。


static struct snd_device_ops ops = {
    .dev_free = snd_pcm_dev_free,
    .dev_register =  snd_pcm_dev_register,
    .dev_disconnect = snd_pcm_dev_disconnect,
  };

这段代码主要初始化一些pcm的参数,还有锁、链表之类的。

pcm->card = card;
pcm->device = device;
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
INIT_LIST_HEAD(&pcm->list);

创建了两个流,playback合capture,一个播放一个录音。    


  err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
         playback_count);
  if (err < 0)
    goto free_pcm;

  err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
  if (err < 0)
    goto free_pcm;

可以看到这里用到了ops,那就是在这里将ops合上文中的注册函数关联了起来。


  err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
           internal ? &internal_ops : &ops);

3、snd_device_new

首先创建了一个snd_device指针,并且初始化了其中的参数,这里可以看到有device_data,也有ops。

struct snd_device *dev;
...
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
...
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;

这里有一个循环,便利card->devices链表内的设备,找到一个pdev->type<=type的设备,最后将其添加到&dev->list链表中。

 /* insert the entry in an incrementally sorted list */
  list_for_each_prev(p, &card->devices) {
    struct snd_device *pdev = list_entry(p, struct snd_device, list);
    if ((unsigned int)pdev->type <= (unsigned int)type)
      break;
  }

  list_add(&dev->list, p);

因为这里我们将dev->card = card;所以在snd_device_register_all函数中可以获取到这个dev。

4、snd_device_register_all

这里遍历了&card->devices,获取到snd_device,调用__snd_device_register(dev)。有兴趣的同学可以去学习下内核知识链表这部分,有助于理解这个架构。


int snd_device_register_all(struct snd_card *card)
{
  struct snd_device *dev;
  int err;
  
  if (snd_BUG_ON(!card))
    return -ENXIO;
  list_for_each_entry(dev, &card->devices, list) {
    err = __snd_device_register(dev);
    if (err < 0)
      return err;
  }
  return 0;
}

看到这里我们就很清晰了,我们调用的dev_register就是上面指向的的snd_pcm_dev_register。下面我们看下snd_pcm_dev_register到底做了什么。


static int __snd_device_register(struct snd_device *dev)
{
  if (dev->state == SNDRV_DEV_BUILD) {
    if (dev->ops->dev_register) {
      int err = dev->ops->dev_register(dev);
      if (err < 0)
        return err;
    }
    dev->state = SNDRV_DEV_REGISTERED;
  }
  return 0;
}

5、snd_pcm_dev_register

首先获取了device->device_data复制给pcm指针。这里的device_data就是我们在snd_device_new函数中配置的。


struct snd_pcm *pcm;
pcm = device->device_data;

这里将这个新的pcm逻辑设备插入snd_pcm_devices链表中。


err = snd_pcm_add(pcm);

下面其实很明显就是循环两次调用snd_register_device创建两个设备,

一个devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;

另一个devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;


for (cidx = 0; cidx < 2; cidx++) {
    int devtype = -1;
....
    switch (cidx) {
    case SNDRV_PCM_STREAM_PLAYBACK:
      devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
      break;
    case SNDRV_PCM_STREAM_CAPTURE:
      devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
      break;
    }
    /* register pcm */
    err = snd_register_device(devtype, pcm->card, pcm->device,
            &snd_pcm_f_ops[cidx], pcm,
            &pcm->streams[cidx].dev);
...

    for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
      snd_pcm_timer_init(substream);
  }

6、snd_register_device

这里主要创建了一个snd_minor结构体指针preg,然后进行一些参数的初始化。还要关注一下在这里我们给preg->f_ops赋值f_ops,后面会调用到。


struct snd_minor *preg;
...
preg = kmalloc(sizeof *preg, GFP_KERNEL);
...
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data;
preg->card_ptr = card;

获取声卡的次设备号


minor = snd_find_free_minor(type, card, dev);

这里最主要的我们要关注的就是他将snd_minor结构体指针保存在了全局数组snd_minors中,并且index是声卡的次设备号。这里还有一个device_add创建了一个设备节点,这个设备节点的名字在哪里配置的,我们可以回顾_snd_pcm_new函数中曾经调用过snd_pcm_new_stream函数。


preg->dev = device;
device->devt = MKDEV(major, minor);
err = device_add(device);
...
snd_minors[minor] = preg;

7、snd_pcm_new_stream

看这里调用了snd_pcm_new_stream函数,并且传入了两种stream类型,SNDRV_PCM_STREAM_PLAYBACK和SNDRV_PCM_STREAM_CAPTURE。

err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
         playback_count);
err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);

函数内部,就通过判断传入的stream类型来给pstr->dev命名了。所以我们在、/dev/snd/目录下可以看到这些节点。

 dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
         stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');

8、snd_pcm_f_ops

回到我们刚刚调用snd_register_device的地方我们可以看到函数还传入了一个file_operations结构体snd_pcm_f_ops。当上层应用调用open,read,write的时候就会调用到这里。但是我们看这个snd_pcm_f_ops并没有注册给任何字符设备又是如何被调用的呢?

const struct file_operations snd_pcm_f_ops[2] = {
  {
    .owner =    THIS_MODULE,
    .write =    snd_pcm_write,
    .write_iter =    snd_pcm_writev,
    .open =      snd_pcm_playback_open,
    .release =    snd_pcm_release,
    .llseek =    no_llseek,
    .poll =      snd_pcm_poll,
    .unlocked_ioctl =  snd_pcm_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,
    .read_iter =    snd_pcm_readv,
    .open =      snd_pcm_capture_open,
    .release =    snd_pcm_release,
    .llseek =    no_llseek,
    .poll =      snd_pcm_poll,
    .unlocked_ioctl =  snd_pcm_ioctl,
    .compat_ioctl =   snd_pcm_ioctl_compat,
    .mmap =      snd_pcm_mmap,
    .fasync =    snd_pcm_fasync,
    .get_unmapped_area =  snd_pcm_get_unmapped_area,
  }
};

现在我们去到linux\sound\core\sound.c代码中来看一下。这里注册了一个字符设备,并且传入了snd_fops。


static int __init alsa_sound_init(void)
{
...
  if (register_chrdev(major, "alsa", &snd_fops)) {
    pr_err("ALSA core: unable to register native major device number %d\n", major);
    return -EIO;
  }
...
  return 0;
}
subsys_initcall(alsa_sound_init);

这个snd_fops中只有一个open函数指针被实现了,是snd_open。


static const struct file_operations snd_fops =
{
  .owner =  THIS_MODULE,
  .open =    snd_open,
  .llseek =  noop_llseek,
};

可以看到snd_open函数这里调用iminor获取了次设备号,然后以次设备号为索引,取出了snd_minors数组中的pcm设备,这里的此设备号正好是我们在snd_register_device函数中配置好的,所以我们就可以获取到mptr中的f_ops,然后调用replace_fops将f_ops进行了替换,换成了pcm设备的f_ops。之后应用层测read,write等操作就都会调用到snd_pcm_f_ops中的回调函数了。


static int snd_open(struct inode *inode, struct file *file)
{
  unsigned int minor = iminor(inode);
  struct snd_minor *mptr = NULL;
  const struct file_operations *new_fops;
  int err = 0;
...
  mptr = snd_minors[minor];
...
  new_fops = fops_get(mptr->f_ops);
  mutex_unlock(&sound_mutex);
...
  replace_fops(file, new_fops);

  if (file->f_op->open)
    err = file->f_op->open(inode, file);
  return err;
}

至此pcm逻辑设备就创建完成了。

需要了解更多文章的朋友,可以关注我的微信公众号【快乐程序猿】里面会更新的比这里要快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值