Linux ALSA音频系统:soundcard

8.1声卡和PCM设备的建立过程

 前面分析了codec,platform,machine驱动的组成部分及其注册过程,这三者都是物理设备相关的。

     pcm逻辑设备,我们习惯称之为PCM中间层或pcm native,起着承上启下的作用:往上是与用户态接口的交互,实现音频数据在用户和内核态之间的拷贝;往下是触发codec,platform,machine的操作函数,实现音频数据在dma_buffer<->cpu_dai<->codec之间的传输。

  声卡驱动中,一般挂载着多个逻辑设备,看看我们计算机的声卡驱动有几个逻辑设备:

$ cat /proc/asound/devices 
  2: [ 0]   : control
  3: [ 0- 0]: digital audio playback
  4: [ 0- 0]: digital audio capture
  5: [ 0- 3]: digital audio playback
  6: [ 0- 7]: digital audio playback
  7: [ 0- 8]: digital audio playback
  8: [ 0- 0]: hardware dependent
  9: [ 0- 3]: hardware dependent
 33:        : timer

 

Device Description
digital audio playback 用于回放的 PCM 设备
digital audio capture 用于录制的 PCM 设备
control 用于声卡控制的 CTL 设备,如通路控制、音量调整等
timer 定时器设备
sequencer 音序器设备

嵌入式系统中,我们更关心PCM和CTL这两种设备。

$ ll /dev/snd/
total 0
drwxr-xr-x  2 root root       60 7月  14 22:19 by-path
crw-rw----+ 1 root audio 116,  2 7月  14 22:19 controlC0
crw-rw----+ 1 root audio 116,  8 7月  14 22:19 hwC0D0
crw-rw----+ 1 root audio 116,  9 7月  14 22:19 hwC0D3
crw-rw----+ 1 root audio 116,  4 7月  15 13:43 pcmC0D0c
crw-rw----+ 1 root audio 116,  3 7月  15 15:35 pcmC0D0p
crw-rw----+ 1 root audio 116,  5 7月  14 22:20 pcmC0D3p
crw-rw----+ 1 root audio 116,  6 7月  14 22:20 pcmC0D7p
crw-rw----+ 1 root audio 116,  7 7月  14 22:20 pcmC0D8p
crw-rw----+ 1 root audio 116,  1 7月  14 22:19 seq
crw-rw----+ 1 root audio 116, 33 7月  14 22:19 timer
 

可以看到这些设备节点的Major=116,Minor则与/proc/asound/devices所列的对应起来,都是字符设备。上层可以通过open/close/read/write/ioctl等系统调用来操作声卡设备,这和其他字符设备类似,但一般情况下我们会使用已封装好的用户接口库如alsa-lib。

8.2声卡结构概述

  回顾下ASoC是如何注册声卡的,这里仅简单陈述下:

  • Machine驱动初始化时,.name = "soc-audio"的platform_device与platform_driver匹配成功,触发soc_probe()调用;
  • 继而调用snd_soc_register_card():  
  1. 为每个音频物理链路找到对应的codec,codec_dai,cpu_dai,platform设备实例,完成dai_link的绑定;
  2. 调用snd_card_create()创建声卡;
  3. 依次回调cpu_dai,codec,platform的probe()函数,完成物理设备的初始化;
  • 随后调用soc_new_pcm():
  1. 设置pcm native中要使用的pcm操作函数,这些函数用于驱动音频物理设备,包括machine,codec_dai,cpu_dai,platform;
  2. 调用snd_pcm_new()创建pcm逻辑设备,回放子流和录制子流都在这里创建;
  3. 回调platform驱动的pcm_new(),完成音频dma设备初始化和dma buffer内存分配;
  • 最后调用snd_card_register()注册声卡。

下面详细分析声卡和PCM逻辑设备的注册过程。

上面提到声卡驱动上挂着多个逻辑子设备,有pcm音频数据流,control混音器,midi,timer定时器,sequencer音序器等。

 

                  +-----------+
                  | snd_card  |
                  +-----------+
                    |   |   |
        +-----------+   |   +------------+
        |               |                |
+-----------+    +-----------+    +-----------+
 |  snd_pcm  |    |snd_control|    | snd_timer |    ...
 +-----------+    +-----------+    +-----------+

这些与声音相关的逻辑设备都在结构体snd_card管理之下,可以说snd_card是alsa中最顶层的结构。我们在看看alsa声卡驱动的大致结构图。

  Linux ALSA音频系统:soundcard_第1张图片

Linux ALSA音频系统:soundcard_第2张图片     

snd_cards:记录着所注册的声卡实例,每个声卡实例有着各自的逻辑设备,如PCM设备,CTL设备,MIDI设备等,并一一记录到snd_card的device链表上

snd_minors:记录着所有逻辑设备的上下文信息,它是声卡逻辑设备与系统调用api之间的桥梁;每个snd_minor在逻辑设备注册时被填充,在逻辑设备使用时就可以从该结构中的到相应的信息(主要是系统调用函数集file_operations)

8.3声卡的创建

声卡实例通过函数snd_card_new()来创建,其函数原型:

/**
 *  snd_card_new - create and initialize a soundcard structure
 *  @parent: the parent device object
 *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
 *  @xid: card identification (ASCII string)
 *  @module: top level module for locking
 *  @extra_size: allocate this extra size after the main soundcard structure
 *  @card_ret: the pointer to store the created card instance
 *
 *  Creates and initializes a soundcard structure.
 *
 *  The function allocates snd_card instance via kzalloc with the given
 *  space for the driver to use freely.  The allocated struct is stored
 *  in the given card_ret pointer.
 *
 *  Return: Zero if successful or a negative error code.
 */
 int snd_card_new(struct device *parent, int idx, const char *xid,                                                                                                                
             struct module *module, int extra_size,
             struct snd_card **card_ret)

主要参数说明:

  • parent: 父设备对象
  • idx:声卡的编号,如为-1,则由系统自动分配
  • xid:声卡标识符,如为NULL,则以snd_card的shortname或longname代替
  • card_ret:返回所创建的声卡实例的指针

如下是我ubuntu16.04的声卡信息:

$ cat /proc/asound/cards
 0 [PCH            ]: HDA-Intel - HDA Intel PCH
                      HDA Intel PCH at 0xf2530000 irq 31
  • number:  0
  • id: PCH
  • shortname: HDA Intel PCH
  • longname:HDA Intel PCH at 0xf7c30000 irq 47

shortname,longname常用于打印信息,上面的声卡信息是通过如下函数打印出来的:

static void snd_card_info_read(struct snd_info_entry *entry,
                   struct snd_info_buffer *buffer)
 {
     int idx, count;
     struct snd_card *card;
 
     for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
         mutex_lock(&snd_card_mutex);
         if ((card = snd_cards[idx]) != NULL) {
             count++;
             snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
                     idx,
                     card->id,
                     card->driver,
                     card->shortname);
             snd_iprintf(buffer, "                      %s\n",
                     card->longname);
         }
         mutex_unlock(&snd_card_mutex);
     }
     if (!count)
         snd_iprintf(buffer, "--- no soundcards ---\n");
 }

8.4逻辑设备的创建

当声卡实例建立后,接着可以创建声卡下面的各个逻辑设备看。每个逻辑设备创建式,都会调用snd_device_new()生成一个snd_device实例,并把该实例挂到声卡snd_card的devices链表上。alsa驱动为各种逻辑设备提供了创建接口,如下:

Device Interface
PCM snd_pcm_new()
CONTROL snd_ctl_create()
MIDI snd_rawmidi_new()
TIMER snd_timer_new()
SEQUENCER snd_seq_device_new()
JACK snd_jack_new()

这些接口的一般过程如下:

int snd_xxx_new()
{
      //这些接口提供逻辑设备注册时回调
    static struct snd_device_ops ops = {
          .dev_free = snd_xxx_dev_free,
          .dev_register = snd_xxx_dev_register,
          .dev_disconnect = snd_xxx_dev_disconnect,
      };
      //逻辑设备实例初始化
   //新建一个设备实例snd_device,挂到snd_card的devices链表上,把该逻辑设备纳入声卡的管理当中,SNDRV_DEV_XXX是逻辑设备的类型
      snd_device_new(card, SNDRV_DEV_XXX, xxx, &ops);
}

其中snd_device_ops是声卡逻辑设备的注册函数集,dev_register()回调尤其重要,它在声卡注册时被调用,用于建立系统的设备节点,/dev/snd/目录的设备节点都是在这里创建的,通过这些设备节点可系统调用open/release/read/write/ioctl..访问操作该逻辑设备。

snd_ctl_dev_register():

static const struct file_operations snd_ctl_f_ops =
 {                                                                                                                                   
     .owner =    THIS_MODULE,
     .read =     snd_ctl_read,
     .open =     snd_ctl_open,
     .release =  snd_ctl_release,
     .llseek =   no_llseek,
     .poll =     snd_ctl_poll,
     .unlocked_ioctl =   snd_ctl_ioctl,
     .compat_ioctl = snd_ctl_ioctl_compat,
     .fasync =   snd_ctl_fasync,
 };
 
 /*
  * registration of the control device
  */
 static int snd_ctl_dev_register(struct snd_device *device)
 {
    struct snd_card *card = device->device_data;
 
     return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
                    &snd_ctl_f_ops, card, &card->ctl_dev);
 }
/**
 * 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;
    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);
    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;
 }

从snd_ctl_dev_register函数中可以看到:

  • 分配并初始化一个snd_minor实例,
  • 分配设备节点
  • 保存snd_minor实例到snd_minors数组中
1016 /**
  1017  * device_add - add device to device hierarchy.                                                                                     
  1018  * @dev: device.
  1019  *
  1020  * This is part 2 of device_register(), though may be called
  1021  * separately _iff_ device_initialize() has been called separately.
  1022  *
  1023  * This adds @dev to the kobject hierarchy via kobject_add(), adds it
  1024  * to the global and sibling lists for the device, then
  1025  * adds it to the other relevant subsystems of the driver model.
  1026  *
  1027  * Do not call this routine or device_register() more than once for
  1028  * any device structure.  The driver model core is not designed to work
  1029  * with devices that get unregistered and then spring back to life.
  1030  * (Among other things, it's very hard to guarantee that all references
  1031  * to the previous incarnation of @dev have been dropped.)  Allocate
  1032  * and register a fresh new struct device instead.
  1033  *
  1034  * NOTE: _Never_ directly free @dev after calling this function, even
  1035  * if it returned an error! Always use put_device() to give up your
  1036  * reference instead.
  1037  */
  1038 int device_add(struct device *dev)
  1039 {
  1040     struct device *parent = NULL;
  1041     struct kobject *kobj;
  1042     struct class_interface *class_intf;
  1043     int error = -EINVAL;
  1044     struct kobject *glue_dir = NULL;
  1045 
  1046     dev = get_device(dev);
  1047     if (!dev)
  1048         goto done;
1050     if (!dev->p) {
  1051         error = device_private_init(dev);
  1052         if (error)
  1053             goto done;
  1054     }
  1055 
  1056     /*
  1057      * for statically allocated devices, which should all be converted
  1058      * some day, we need to initialize the name. We prevent reading back
  1059      * the name, and force the use of dev_name()
  1060      */
  1061     if (dev->init_name) {
  1062         dev_set_name(dev, "%s", dev->init_name);
  1063         dev->init_name = NULL;
  1064     }
  1065 
  1066     /* subsystems can specify simple device enumeration */
  1067     if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
  1068         dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
  1069 
  1070     if (!dev_name(dev)) {
  1071         error = -EINVAL;
  1072         goto name_error;
  1073     }
  1074 
  1075     pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
  1076 
  1077     parent = get_device(dev->parent);
  1078     kobj = get_device_parent(dev, parent);
  1079     if (kobj)
  1080         dev->kobj.parent = kobj;                                                                                                    
  1081 
  1082     /* use parent numa_node */
  1083     if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
 1084         set_dev_node(dev, dev_to_node(parent));
  1085 
  1086     /* first, register with generic layer. */
  1087     /* we require the name to be set before, and pass NULL */
  1088     error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
  1089     if (error) {
  1090         glue_dir = get_glue_dir(dev);
  1091         goto Error;
  1092     }
  1093 
  1094     /* notify platform of device entry */
  1095     if (platform_notify)
  1096         platform_notify(dev);
  1097 
  1098     error = device_create_file(dev, &dev_attr_uevent);
  1099     if (error)
  1100         goto attrError;
  1101 
  1102     error = device_add_class_symlinks(dev);
  1103     if (error)
  1104         goto SymlinkError;
  1105     error = device_add_attrs(dev);
  1106     if (error)
  1107         goto AttrsError;
  1108     error = bus_add_device(dev);
 1109     if (error)
  1110         goto BusError;
  1111     error = dpm_sysfs_add(dev);
  1112     if (error)
          goto DPMError;
     device_pm_add(dev); 
     if (MAJOR(dev->devt)) {
         error = device_create_file(dev, &dev_attr_dev);
         if (error)
             goto DevAttrError;
 
         error = device_create_sys_dev_entry(dev);
         if (error)
             goto SysEntryError;
 
         devtmpfs_create_node(dev);
      }
      /* Notify clients of device addition.  This call must come
        * after dpm_sysfs_add() and before kobject_uevent().
      */
     if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                          BUS_NOTIFY_ADD_DEVICE, dev); 
     kobject_uevent(&dev->kobj, KOBJ_ADD);
     bus_probe_device(dev);                                                                                                          
    if (parent)
       klist_add_tail(&dev->p->knode_parent,
                    &parent->p->klist_children);
      if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
         klist_add_tail(&dev->knode_class,
                    &dev->class->p->klist_devices);
        /* notify any interfaces that the device is here */
         list_for_each_entry(class_intf,
                   &dev->class->p->interfaces, node)
         if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
         mutex_unlock(&dev->class->p->mutex);
    }
 done:
     put_device(dev);
     return error;
 SysEntryError:
    if (MAJOR(dev->devt))
         device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
    device_pm_remove(dev);
    dpm_sysfs_remove(dev);
 DPMError:
    bus_remove_device(dev);
 BusError:
    device_remove_attrs(dev);
 AttrsError:
    device_remove_class_symlinks(dev);
 SymlinkError:
    device_remove_file(dev, &dev_attr_uevent);
 attrError:
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);
    glue_dir = get_glue_dir(dev);
    kobject_del(&dev->kobj);
 Error:
    cleanup_glue_dir(dev, glue_dir);
    put_device(parent);
 name_error:
    kfree(dev->p);
    dev->p = NULL;
    goto done;
  }

上面过程是声卡注册时才被回调的。

8.5声卡的注册

当声卡下的所有逻辑设备都已经准备就绪后,就可以调用snd_card_register()注册声卡了:

  • 创建声卡的sysfs设备;
  • 调用snd_device_register_all()注册所有挂在该声卡下的逻辑设备;
  • 建立proc信息文件和sysfs属性文件。
   724 /**
   725  *  snd_card_register - register the soundcard
   726  *  @card: soundcard structure
   727  *
   728  *  This function registers all the devices assigned to the soundcard.
   729  *  Until calling this, the ALSA control interface is blocked from the
   730  *  external accesses.  Thus, you should call this function at the end
   731  *  of the initialization of the card.
   732  *
   733  *  Return: Zero otherwise a negative error code if the registration failed.
   734  */
   735 int snd_card_register(struct snd_card *card)
   736 {
   737     int err;
   738 
   739     if (snd_BUG_ON(!card))
   740         return -EINVAL;
   741    //创建sysfs设备,声卡的class将会出现在/sys/class/sound下面
   742     if (!card->registered) {
   743         err = device_add(&card->card_dev);
   744         if (err < 0)
   745             return err;
   746         card->registered = true;
   747     }
   748 //遍历挂在该声卡的所有逻辑设备,回调各snd_device的ops->dev_register()完成各逻辑设备的注册
   749     if ((err = snd_device_register_all(card)) < 0)
   750         return err;
   751     mutex_lock(&snd_card_mutex);
   752     if (snd_cards[card->number]) {
   753         /* already registered */
   754         mutex_unlock(&snd_card_mutex);
   755         return snd_info_card_register(card); /* register pending info */
   756     }
   757     if (*card->id) {
   758         /* make a unique id name from the given string */
   759         char tmpid[sizeof(card->id)];
   760         memcpy(tmpid, card->id, sizeof(card->id));
   761         snd_card_set_id_no_lock(card, tmpid, tmpid);
   762     } else {
   763         /* create an id from either shortname or longname */
   764         const char *src;
   765         src = *card->shortname ? card->shortname : card->longname;
   766         snd_card_set_id_no_lock(card, src,
   767                     retrieve_id_from_card_name(src));                                                                                                                            
   768     }
   769     snd_cards[card->number] = card;//把该声卡实例保存到snd_cards数组中
   770     mutex_unlock(&snd_card_mutex);
          //声卡相关信息,见:/proc/asound/card0
   771     init_info_for_card(card);
   772 #if IS_ENABLED(CONFIG_SND_MIXER_OSS)
   773     if (snd_mixer_oss_notify_callback)
   774         snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
   775 #endif
   776     return 0;
   777 }

至此完成了声卡及声卡下的所有逻辑设备的注册,用户态可以通过系统调用来访问这些设备。

8.6PCM设备的创建

Linux ALSA音频系统:soundcard_第3张图片

 

snd_pcm_set_ops:设置PCM设备的操作接口,设置完成后,在PCM设备层即可访问操作底层音频物理设备。

snd_pcm_new:

  • 创建一个PCM设备实例snd_pcm;
  • 创建playback stream和capture stream,旗下的substream也同时建立;
  • 调用snd_device_new()把pcm设备挂到声卡的devices链表上。
/**
   800  * snd_pcm_new - create a new PCM instance
   801  * @card: the card instance
   802  * @id: the id string
   803  * @device: the device index (zero based)
   804  * @playback_count: the number of substreams for playback
   805  * @capture_count: the number of substreams for capture
   806  * @rpcm: the pointer to store the new pcm instance
   807  *
   808  * Creates a new PCM instance.
   809  *
   810  * The pcm operators have to be set afterwards to the new instance
   811  * via snd_pcm_set_ops().
   812  *
   813  * Return: Zero if successful, or a negative error code on failure.
   814  */
   815 int snd_pcm_new(struct snd_card *card, const char *id, int device,
   816         int playback_count, int capture_count, struct snd_pcm **rpcm)
   817 {
   818     return _snd_pcm_new(card, id, device, playback_count, capture_count,
   819             false, rpcm);
   820 }
755 static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
   756         int playback_count, int capture_count, bool internal,
   757         struct snd_pcm **rpcm)
   758 {
   759     struct snd_pcm *pcm;
   760     int err;
   761     static struct snd_device_ops ops = {
   762         .dev_free = snd_pcm_dev_free,
   763         .dev_register = snd_pcm_dev_register,
   764         .dev_disconnect = snd_pcm_dev_disconnect,
   765     };
   766 
   767     if (snd_BUG_ON(!card))
   768         return -ENXIO;
   769     if (rpcm)
   770         *rpcm = NULL;
   771     pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
   772     if (!pcm)
   773         return -ENOMEM;
   774     pcm->card = card;
   775     pcm->device = device;
   776     pcm->internal = internal;
   777     mutex_init(&pcm->open_mutex);
   778     init_waitqueue_head(&pcm->open_wait);
   779     INIT_LIST_HEAD(&pcm->list);
   780     if (id)
   781         strlcpy(pcm->id, id, sizeof(pcm->id));
   782     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
   783         snd_pcm_free(pcm);
   784         return err;
   785     }
   786     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
   787         snd_pcm_free(pcm);
   788         return err;
   789     }
   790     if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
   791         snd_pcm_free(pcm);
   792         return err;
   793     }
   794     if (rpcm)
   795         *rpcm = pcm;
   796     return 0;                                                                                                                                       
   797 }

在看看pcm设备的系统调用:

const struct file_operations snd_pcm_f_ops[2] = {
  3681     {
  3682         .owner =        THIS_MODULE,
  3683         .write =        snd_pcm_write,
  3684         .write_iter =       snd_pcm_writev,
  3685         .open =         snd_pcm_playback_open,
  3686         .release =      snd_pcm_release,
  3687         .llseek =       no_llseek,
  3688         .poll =         snd_pcm_playback_poll,
  3689         .unlocked_ioctl =   snd_pcm_playback_ioctl,
  3690         .compat_ioctl =     snd_pcm_ioctl_compat,
  3691         .mmap =         snd_pcm_mmap,
  3692         .fasync =       snd_pcm_fasync,
  3693         .get_unmapped_area =    snd_pcm_get_unmapped_area,
  3694     },
  3695     {
  3696         .owner =        THIS_MODULE,
  3697         .read =         snd_pcm_read,
  3698         .read_iter =        snd_pcm_readv,
  3699         .open =         snd_pcm_capture_open,
  3700         .release =      snd_pcm_release,
  3701         .llseek =       no_llseek,
  3702         .poll =         snd_pcm_capture_poll,
  3703         .unlocked_ioctl =   snd_pcm_capture_ioctl,
  3704         .compat_ioctl =     snd_pcm_ioctl_compat,
  3705         .mmap =         snd_pcm_mmap,
  3706         .fasync =       snd_pcm_fasync,
  3707         .get_unmapped_area =    snd_pcm_get_unmapped_area,
  3708     }
  3709 };        

snd_pcm_f_ops作为snd_register_device_for_dev()的参数传入,并被记录在snd_minors[minor]中的字段f_ops中。snd_pcm_f_ops[0]是回放使用的系统调用接口,snd_pcm_f_ops[1]是录制使用的系统调用接口。

9.Frame && Period

音频数据中的几个重要概念:

  • Sample:样本长度,音频数据最基本的单位,常见的有8bit和16bit;
  • channel:声道数,分为单声道mono和立体声stereo;
  • Frame:帧,构成一个完整的声音单元,所谓的声音单元是☞一个采样样本, Frame = Sample * channel;
  • Rate:又称sample rate,采样率,即毎秒的采样次数,针对帧而言;
  • Period Size:周期,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,以此为单位;
  • Buffer Size:数据缓冲区大小,这里这指runtime的buffer size,而不是结构图snd_pcm_hardware中定义的buffer_bytes_max;一般来说buffer_size = period_size * period_count,period_count相当于处理完一个buffer数据所需的硬件中断次数。

下面是一章直观的表示buffer/period/frame/sample之间的关系:

Linux ALSA音频系统:soundcard_第4张图片

这个buffer中有4个period,每当DMA搬运完一个period的数据就会出生一次中断,因此搬运这个buffer中的数据将产生4次中断。

ALSA为什么这样做?因为数据缓冲区可能很大,一次传输可能会导致不可接收的延迟;为了解决这个问题,alsa把缓冲区拆分成多个周期,以周期为单元传输数据。

9.1frames&periods

alsa官网对periods的解释:https://www.alsa-project.org/main/index.php/FramesPeriods

  • Frame:帧,构成一个完整的声音单元,它的大小等于sample_bits * channels;
  • Period:周期大小,即每次dma运输处理音频数据的帧数,如果周期大小设定的较大,则单次处理的数据较多,这意味着单位时间内硬件中断的次数较少,CPU也就有更多时间处理其他任务,功耗也更低,但这样带来一个显著的弊端-数据处理的时延会增大。
  • period bytes,对于dma处理来说,它直接关心的是数据大小,而非period_size(一个周期的帧数),有个转换关系:period_bytes =period_size * sample_bits * channels / 8

由于i2s总线采样率是稳定的,我们可以计算i2s传输一个周期的数据所需的时间:transfer_time = 1 * period_size /sample_rate,in second.

例如period_size = 1024, sample_rate = 48khz,那么一个周期数据的传输时间是:1*1024/48000 = 21.3(ms)

 

 

你可能感兴趣的:(Linux,Alsa,driver)