Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下


3、PCM相关源码分析

3.1、snd_pcm_new

/**
 * 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)
{
    /* 直接调用函数_snd_pcm_new,参数internal传入false */
  return _snd_pcm_new(card, id, device, playback_count, capture_count,
      false, rpcm);
}
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;
    /* 1. 逻辑设备的操作函数结构体, 主要用于注册子设备 */
  static const struct snd_device_ops ops = {
    .dev_free = snd_pcm_dev_free,
    .dev_register = snd_pcm_dev_register,
    .dev_disconnect = snd_pcm_dev_disconnect,
  };
  static const struct snd_device_ops internal_ops = {
    .dev_free = snd_pcm_dev_free,
  };
  if (snd_BUG_ON(!card))
    return -ENXIO;
  if (rpcm)
    *rpcm = NULL;
    /* 2. 为snd_pcm结构体分配空间,根据传入参数赋值 */
  pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
  if (!pcm)
    return -ENOMEM;
  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);
  if (id)
    strscpy(pcm->id, id, sizeof(pcm->id));
    /* 3. 根据传入的playback和capture的个数创建PCM流 snd_pcm_str */
  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;
    /* 4. 创建一个PCM逻辑设备,创建逻辑设备,并添加到逻辑设备链表 */
  err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
           internal ? &internal_ops : &ops);
  if (err < 0)
    goto free_pcm;
  if (rpcm)
    *rpcm = pcm;
  return 0;
free_pcm:
  snd_pcm_free(pcm);
  return err;
}

3.2、snd_pcm

struct snd_pcm {
  struct snd_card *card;
  struct list_head list;
  int device; /* device number */
  unsigned int info_flags;
  unsigned short dev_class;
  unsigned short dev_subclass;
  char id[64];
  char name[80];
  struct snd_pcm_str streams[2];
  struct mutex open_mutex;
  wait_queue_head_t open_wait;
  void *private_data;
  void (*private_free) (struct snd_pcm *pcm);
  bool internal; /* pcm is for internal use only */
  bool nonatomic; /* whole PCM operations are in non-atomic context */
  bool no_device_suspend; /* don't invoke device PM suspend */
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
  struct snd_pcm_oss oss;
#endif
};

这里重要的变量有两个streams与private_data。streams有两个,是因为一个指向播放设备,一个指向录音设备。private_data在很多结构里都可以看到,和面象对象里的继承有点类似,如果将snd_pcm理解为基类的话,private_data指向的就是它的继承类,也就是真正的实现者。

list,在pcm.c中有一个全局变量snd_pcm_devices,将所有的snd_pcm对象链接起来,目的是外部提供一些可供枚举所有设备的接口,看起来并不怎么被用到。

另外还有info_flags、dev_class等变量看起来是为一些特殊设备预留的,对待一些特殊操作。

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;
};

snd_pcm_str的主要作用是指向snd_pcm_substream,而snd_pcm_substream可以有多个,这也是snd_pcm_str存在的原因,否则snd_pcm直接指向snd_pcm_substream就可以了。

这里的dev是将pcm加入到文件系统时要用到。包含的信息,在下面介绍的snd_pcm_new_stream中会看到。

3.3、snd_pcm_new_stream

/**
 * snd_pcm_new_stream - create a new PCM stream
 * @pcm: the pcm instance
 * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
 * @substream_count: the number of substreams
 *
 * Creates a new stream for the pcm.
 * The corresponding stream on the pcm must have been empty before
 * calling this, i.e. zero must be given to the argument of
 * snd_pcm_new().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
  int idx, err;
    /* 3.1 根据传入的参数,为PCM流(snd_pcm_str)赋值:方向,所属的PCM,PCM子流的个数 */
  struct snd_pcm_str *pstr = &pcm->streams[stream];
  struct snd_pcm_substream *substream, *prev;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
  mutex_init(&pstr->oss.setup_mutex);
#endif
  pstr->stream = stream;
  pstr->pcm = pcm;
  pstr->substream_count = substream_count;
  if (!substream_count)
    return 0;
  snd_device_initialize(&pstr->dev, pcm->card);
  pstr->dev.groups = pcm_dev_attr_groups;
  pstr->dev.type = &pcm_dev_type;
  dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
         stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
    /* proc */
  if (!pcm->internal) {
    err = snd_pcm_stream_proc_init(pstr);
    if (err < 0) {
      pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
      return err;
    }
  }
  prev = NULL;
  for (idx = 0, prev = NULL; idx < substream_count; idx++) {
        /* 为子流分配空间,赋值(pcm,pcm流,ID, 方向.....) */
    substream = kzalloc(sizeof(*substream), GFP_KERNEL);
    if (!substream)
      return -ENOMEM;
    substream->pcm = pcm;
    substream->pstr = pstr;
    substream->number = idx;
    substream->stream = stream;
    sprintf(substream->name, "subdevice #%i", idx);
    substream->buffer_bytes_max = UINT_MAX;
        /* 添加子流到子流的链表 */
    if (prev == NULL)    /* 第一个子流 */
      pstr->substream = substream;
    else
      prev->next = substream;    /* 非第一个子流,添加到前一个子流后部 */
        /* proc */
    if (!pcm->internal) {
      err = snd_pcm_substream_proc_init(substream);
      if (err < 0) {
        pcm_err(pcm,
          "Error in snd_pcm_stream_proc_init\n");
        if (prev == NULL)
          pstr->substream = NULL;
        else
          prev->next = NULL;
        kfree(substream);
        return err;
      }
    }
        /* 结构体初始化 */
    substream->group = &substream->self_group;
    snd_pcm_group_init(&substream->self_group);
    list_add_tail(&substream->link_list, &substream->self_group.substreams);
    atomic_set(&substream->mmap_count, 0);
    prev = substream;
  }
  return 0;
}

函数参数中的int stream,是一个枚举类型:

enum {
  SNDRV_PCM_STREAM_PLAYBACK = 0,
  SNDRV_PCM_STREAM_CAPTURE,
  SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE,
};

从snd_device_initialize(&pstr->dev, pcm->card); 开始。dev最终会被传入device_add函数中,用来构建文件系统。

void snd_device_initialize(struct device *dev, struct snd_card *card)
{
  device_initialize(dev);
  if (card)
    dev->parent = &card->card_dev;
  dev->class = sound_class;
  dev->release = default_release;
}

这段函数中可以看到dev->class被设置成sound_class,这个是我们之前提到的文件放到snd目录的原因。

3.4、snd_pcm_substream

struct snd_pcm_substream {
  struct snd_pcm *pcm;
  struct snd_pcm_str *pstr;
  void *private_data;   /* copied from pcm->private_data */
  int number;
  char name[32];      /* substream name */
  int stream;     /* stream (direction) */
  struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
  size_t buffer_bytes_max;  /* limit ring buffer size */
  struct snd_dma_buffer dma_buffer;
  size_t dma_max;
  /* -- hardware operations -- */
  const struct snd_pcm_ops *ops;
  /* -- runtime information -- */
  struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
  struct snd_timer *timer;    /* timer */
  unsigned timer_running: 1;  /* time is running */
  long wait_time; /* time in ms for R/W to wait for avail */
  /* -- next substream -- */
  struct snd_pcm_substream *next;
  /* -- linked substreams -- */
  struct list_head link_list; /* linked list member */
  struct snd_pcm_group self_group;  /* fake group for non linked substream (with substream lock inside) */
  struct snd_pcm_group *group;    /* pointer to current group */
  /* -- assigned files -- */
  int ref_count;
  atomic_t mmap_count;
  unsigned int f_flags;
  void (*pcm_release)(struct snd_pcm_substream *);
  struct pid *pid;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
  /* -- OSS things -- */
  struct snd_pcm_oss_substream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
  struct snd_info_entry *proc_root;
#endif /* CONFIG_SND_VERBOSE_PROCFS */
  /* misc flags */
  unsigned int hw_opened: 1;
  unsigned int managed_buffer_alloc:1;
};

snd_pcm_substream的内容有些多,此处只需要重要的进行介绍。

       private_data:从snd_pcm中的private_data拷贝过来的,指向实现者的结构。

       const struct snd_pcm_ops *ops:这部分是框架的内容,具体的操作需要实现者的参与,留给实现者的函数指针集。这个和文件操作的设计策略是一致的。

       struct snd_pcm_runtime *runtime:读写数据的时候由它来控制。到分析读写代码的时候,会重点关注它。

       struct snd_pcm_substream *next:将多个snd_pcm_substream对象链接起来,它就是snd_pcm_str指向的链接。

       group:在用户空间可以通过SNDRV_PCM_IOCTL_LINK将多个substream链接起来。然后就可以对这些对象进行统一的操作。我没遇到过具体的应用场景。

3.5、snd_pcm_set_ops

/**
 * snd_pcm_set_ops - set the PCM operators
 * @pcm: the pcm instance
 * @direction: stream direction, SNDRV_PCM_STREAM_XXX
 * @ops: the operator table
 *
 * Sets the given PCM operators to the pcm instance.
 */
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
         const struct snd_pcm_ops *ops)
{
  struct snd_pcm_str *stream = &pcm->streams[direction];
  struct snd_pcm_substream *substream;
  for (substream = stream->substream; substream != NULL; substream = substream->next)
    substream->ops = ops;
}
EXPORT_SYMBOL(snd_pcm_set_ops);

此函数是提供给调用侧使用的。设置的内容可以参考pcm文件结构简图。  

3.6、snd_pcm_dev_register

在继续分析snd_pcm_dev_register函数之前需要先介绍一个结构体。struct snd_minor。

struct snd_minor {
  int type;     /* SNDRV_DEVICE_TYPE_XXX */
  int card;     /* card number */
  int device;     /* device number */
  const struct file_operations *f_ops;  /* file operations */
  void *private_data;   /* private data for f_ops->open */
  struct device *dev;   /* device for sysfs */
  struct snd_card *card_ptr;  /* assigned card instance */
};

type: 设备类型,比如是pcm, control, timer等设备。

       card_number: 所属的card。

       device: 当前设备类型下的设备编号。

       f_ops: 具体设备的文件操作集合。

       private_data: open函数的私有数据。

       card_ptr: 所属的card。

此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。

static int snd_pcm_dev_register(struct snd_device *device)
{
    /* 1、添加pcm结构体到全局链表snd_pcm_devices */
  int cidx, err;
  struct snd_pcm_substream *substream;
  struct snd_pcm *pcm;
  if (snd_BUG_ON(!device || !device->device_data))
    return -ENXIO;
    /* snd_devcie保存的是snd_pcm对象 */
  pcm = device->device_data;
  mutex_lock(&register_mutex);
    /* snd_pcm对象将被保存到全局变量snd_pcm_devices中,用于枚举设备等操作 */
  err = snd_pcm_add(pcm);
  if (err)
    goto unlock;
  for (cidx = 0; cidx < 2; cidx++) {
        /* 2、确定PCM设备节点名字 */
    int devtype = -1;
    if (pcm->streams[cidx].substream == NULL)
      continue;
    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 */
        /* 将设备添加到文件系统,将snd_pcm_f_ops传入,将被设置给snd_minor对象 */
    err = snd_register_device(devtype, pcm->card, pcm->device,
            &snd_pcm_f_ops[cidx], pcm,
            &pcm->streams[cidx].dev);
    if (err < 0) {
      list_del_init(&pcm->list);
      goto unlock;
    }
    for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
            /* 设定CONFIG_SND_PCM_TIMER宏的时候,会去设置substream的时间 */
      snd_pcm_timer_init(substream);
  }
  pcm_call_notify(pcm, n_register);
 unlock:
  mutex_unlock(&register_mutex);
  return err;
}
/**
 * snd_register_device - Register the ALSA device file for the card
 * @type: the device type, SNDRV_DEVICE_TYPE_XXX
 * @card: the card instance
 * @dev: the device index
 * @f_ops: the file operations
 * @private_data: user pointer for f_ops->open()
 * @device: the device to register
 *
 * Registers an ALSA device file for the given card.
 * The operators have to be set in reg parameter.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_register_device(int type, struct snd_card *card, int dev,
      const struct file_operations *f_ops,
      void *private_data, struct device *device)
{
  int minor;
  int err = 0;
  struct snd_minor *preg;
  if (snd_BUG_ON(!device))
    return -EINVAL;
  preg = kmalloc(sizeof *preg, GFP_KERNEL);
  if (preg == NULL)
    return -ENOMEM;
    /* 创建一个snd_minor,并添加到全局结构体 snd_minors */
  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;
  mutex_lock(&sound_mutex);
    /* 4、注册一个设备节点 */
  minor = snd_find_free_minor(type, card, dev);
  if (minor < 0) {
    err = minor;
    goto error;
  }
  preg->dev = device;
  device->devt = MKDEV(major, minor);
  err = device_add(device);
  if (err < 0)
    goto error;
  snd_minors[minor] = preg;
 error:
  mutex_unlock(&sound_mutex);
  if (err < 0)
    kfree(preg);
  return err;
}

当声卡被注册时,会注册所有的逻辑设备。主要的工作是创建PCM设备节点

具体的流程:

               1、添加pcm结构体到全局链表snd_pcm_devices。

               2、确定PCM设备节点名字。

               3、创建一个snd_minor,并添加到全局结构体 snd_minors。

               4、注册一个设备节点

可以看到添加到文件系统的是播放设备和录音设备,根据snd_pcm_str指向的内容来设定的。代码中看到snd_pcm也被定义为SNDRV_DEV_PCM设备,但是文件系统中并不会保存这个类型的设备。

snd_pcm_timer_init是在CONFIG_SND_PCM_TIMER宏被定义的时候,会起作用。

通过下图可以帮助你更好的理解各结构直接的乱讲关系。

相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
11天前
|
存储 编解码 Ubuntu
【QT】linux下alsa库的移植和QT中音视频的处理&笔记
【QT】linux下alsa库的移植和QT中音视频的处理&笔记
|
11天前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
11天前
|
Linux 程序员 芯片
【Linux驱动】普通字符设备驱动程序框架
【Linux驱动】普通字符设备驱动程序框架
|
23天前
|
Linux 数据安全/隐私保护 Windows
linux 搭建cloudreve win映射网络驱动器WebDav
linux 搭建cloudreve win映射网络驱动器WebDav
26 1
|
10天前
|
Linux
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
|
1天前
|
安全 Linux Shell
Linux中SSH命令介绍
Linux中SSH命令介绍
10 2
|
1天前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
6 1
|
1天前
|
存储 Linux 程序员
tar命令详解:linux文件打包神器
tar命令详解:linux文件打包神器
|
2天前
|
Linux 应用服务中间件 数据安全/隐私保护
|
16小时前
|
Java Linux 网络安全
Linux常用50个命令分享:功能、示例与实用技巧
Linux常用50个命令分享:功能、示例与实用技巧
5 0