1. 程式人生 > >Linux音訊驅動-PCM裝置

Linux音訊驅動-PCM裝置

概述

1.  什麼是pcm? pcm(Pulse-code modulation)脈衝編碼調製,是將模擬訊號轉化為數字訊號的一種方法。聲音的轉化的過程為,先對連續的模擬訊號按照固定頻率週期性取樣,將取樣到的資料按照一定的精度進行量化,量化後的訊號和取樣後的訊號差值叫做量化誤差,將量化後的資料進行最後的編碼儲存,最終模擬訊號變化為數字訊號。
2. pcm的兩個重要屬性     a.  取樣率:        單位時間內取樣的次數,取樣頻率越高越高,     b.  取樣位數:    一個取樣訊號的位數,也是對取樣精度的變現。
對於人類而言,能接受聲音的頻率範圍是20Hz-20KHz, 所以取樣的頻率44.1KHz 以及16bit的取樣位數就可以有很好的保真能力(CD格式的取樣率和取樣位數)。


                                                              圖1-1  聲音的錄音和播放過程

資料結構

在ALSA架構下,pcm也被稱為裝置,所謂的邏輯裝置。在linux系統中使用snd_pcm結構表示一個pcm裝置。 [cpp]
  view plain  copy
  1. struct snd_pcm {  
  2.     struct snd_card *card;  
  3.     struct list_head list;  
  4.     int
     device; /* device number */  
  5.     unsigned int info_flags;  
  6.     unsigned short dev_class;  
  7.     unsigned short dev_subclass;  
  8.     char id[64];  
  9.     char name[80];  
  10.     struct snd_pcm_str streams[2];  
  11.     struct mutex open_mutex;  
  12.     wait_queue_head_t open_wait;  
  13.     void *private_data;  
  14.     void (*private_free) (struct snd_pcm *pcm);  
  15.     struct device *dev; /* actual hw device this belongs to */  
  16.     bool internal; /* pcm is for internal use only */  
  17.     bool nonatomic; /* whole PCM operations are in non-atomic context */  
  18. #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)  
  19.     struct snd_pcm_oss oss;  
  20. #endif  
  21. };  
.card:         此pcm裝置所屬的card。 .list:           用於將pcm裝置連結起來,最終所有的pcm裝置會放入snd_pcm_devices連結串列中。 .device:      該pcm的索引號。 .id:             該pcm的標識。 .streams:   指向pcm的capture和playback stream,通常0代表playback,1代表capture。
通常一個pcm下會有兩個stream, 分別為capture stream和playback stream,在每個stream下又會存在多個substream。 linux系統中使用snd_pcm_str定義stream, 使用snd_pcm_substream定義substream。 [cpp]  view plain  copy
  1. struct snd_pcm_str {  
  2.     int stream;             /* stream (direction) */  
  3.     struct snd_pcm *pcm;  
  4.     /* -- substreams -- */  
  5.     unsigned int substream_count;  
  6.     unsigned int substream_opened;  
  7.     struct snd_pcm_substream *substream;  
  8. };  
.stream:  當前stream的方向,capture or playback。 .pcm:      所屬的pcm。 .substream_count:  該stream下substream的個數。 .substream_opened:  該stream下open的substream個數。 .substream:  該stream下的substream.
[cpp]  view plain  copy
  1. struct snd_pcm_substream {  
  2.     struct snd_pcm *pcm;  
  3.     struct snd_pcm_str *pstr;  
  4.     void *private_data;     /* copied from pcm->private_data */  
  5.     int number;  
  6.     char name[32];          /* substream name */  
  7.     int stream;         /* stream (direction) */  
  8.     struct pm_qos_request latency_pm_qos_req; /* pm_qos request */  
  9.     size_t buffer_bytes_max;    /* limit ring buffer size */  
  10.     struct snd_dma_buffer dma_buffer;  
  11.     size_t dma_max;  
  12.     /* -- hardware operations -- */  
  13.     const struct snd_pcm_ops *ops;  
  14.     /* -- runtime information -- */  
  15.     struct snd_pcm_runtime *runtime;  
  16.         /* -- timer section -- */  
  17.     struct snd_timer *timer;        /* timer */  
  18.     unsigned timer_running: 1;  /* time is running */  
  19.     /* -- next substream -- */  
  20.     struct snd_pcm_substream *next;  
  21.     /* -- linked substreams -- */  
  22.     struct list_head link_list; /* linked list member */  
  23.     struct snd_pcm_group self_group;    /* fake group for non linked substream (with substream lock inside) */  
  24.     struct snd_pcm_group *group;        /* pointer to current group */  
  25.     /* -- assigned files -- */  
  26.     void *file;  
  27.     int ref_count;  
  28.     atomic_t mmap_count;  
  29.     unsigned int f_flags;  
  30.     void (*pcm_release)(struct snd_pcm_substream *);  
  31.     struct pid *pid;  
  32.     /* misc flags */  
  33.     unsigned int hw_opened: 1;  
  34. };  
.pcm:       所屬的pcm。 .pstr:       所屬的stream。 .id:           代表的該stream下第幾個substream,也就是序號。 .stream:  該substream的方向流,是palyback or capture。 .name:     該substrem的名字。 .ops:        硬體操作函式集合。 .runtime:   執行時的pcm的一些資訊。 .next:        用於連結下一個sub stream。

下圖是對這幾個結構體之間的簡單表述。


pcm裝置的建立

建立一個pcm裝置的例項,使用snd_pcm_new函式。 [cpp]  view plain  copy
  1. /** 
  2.  * snd_pcm_new - create a new PCM instance 
  3.  * @card: the card instance 
  4.  * @id: the id string 
  5.  * @device: the device index (zero based) 
  6.  * @playback_count: the number of substreams for playback 
  7.  * @capture_count: the number of substreams for capture 
  8.  * @rpcm: the pointer to store the new pcm instance 
  9.  * 
  10.  * Creates a new PCM instance. 
  11.  * 
  12.  * The pcm operators have to be set afterwards to the new instance 
  13.  * via snd_pcm_set_ops(). 
  14.  * 
  15.  * Return: Zero if successful, or a negative error code on failure. 
  16.  */  
  17. int snd_pcm_new(struct snd_card *card, const char *id, int device,  
  18.         int playback_count, int capture_count, struct snd_pcm **rpcm)  
  19. {  
  20.     return _snd_pcm_new(card, id, device, playback_count, capture_count,  
  21.             false, rpcm);  
  22. }  
此函式會傳入六個引數,其中該函式的註釋寫的很清楚,不做過多解釋。函式最終會返回rpcm引數。 [cpp]  view plain  copy
  1. static int _snd_pcm_new(struct snd_card *card, const char *id, int device,  
  2.         int playback_count, int capture_count, bool internal,  
  3.         struct snd_pcm **rpcm)  
  4. {  
  5.     struct snd_pcm *pcm;  
  6.     int err;  
  7.     static struct snd_device_ops ops = {  
  8.         .dev_free = snd_pcm_dev_free,  
  9.         .dev_register = snd_pcm_dev_register,  
  10.         .dev_disconnect = snd_pcm_dev_disconnect,  
  11.     };  
  12.   
  13.     if (snd_BUG_ON(!card))  
  14.         return -ENXIO;  
  15.     if (rpcm)  
  16.         *rpcm = NULL;  
  17.     pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);  
  18.     if (pcm == NULL) {  
  19.         dev_err(card->dev, "Cannot allocate PCM\n");  
  20.         return -ENOMEM;  
  21.     }  
  22.     pcm->card = card;  
  23.     pcm->device = device;  
  24.     pcm->internal = internal;  
  25.     if (id)  
  26.         strlcpy(pcm->id, id, sizeof(pcm->id));  
  27.     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {  
  28.         snd_pcm_free(pcm);  
  29.         return err;  
  30.     }  
  31.     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {  
  32.         snd_pcm_free(pcm);  
  33.         return err;  
  34.     }  
  35.     mutex_init(&pcm->open_mutex);  
  36.     init_waitqueue_head(&pcm->open_wait);  
  37.     if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {  
  38.         snd_pcm_free(pcm);  
  39.         return err;  
  40.     }  
  41.     if (rpcm)  
  42.         *rpcm = pcm;  
  43.     return 0;  
  44. }  
1.  分配一個snd_pcm結構體。 2.  根據傳遞進來的引數設定card, device, internal, id。 3.  分別建立palyback & capture stream。 4.  呼叫snd_device_new介面建立pcm裝置。
呼叫snd_pcm_new_stream建立一個stream [cpp]  view plain  copy
  1. int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)  
  2. {  
  3.     int idx, err;  
  4.     struct snd_pcm_str *pstr = &pcm->streams[stream];  
  5.     struct snd_pcm_substream *substream, *prev;  
  6.   
  7. #if IS_ENABLED(CONFIG_SND_PCM_OSS)  
  8.     mutex_init(&pstr->oss.setup_mutex);  
  9. #endif  
  10.     pstr->stream = stream;  
  11.     pstr->pcm = pcm;  
  12.     pstr->substream_count = substream_count;  
  13.     if (substream_count > 0 && !pcm->internal) {  
  14.         err = snd_pcm_stream_proc_init(pstr);  
  15.         if (err < 0) {  
  16.             pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");  
  17.             return err;  
  18.         }  
  19.     }  
  20.     prev = NULL;  
  21.     for (idx = 0, prev = NULL; idx < substream_count; idx++) {  
  22.         substream = kzalloc(sizeof(*substream), GFP_KERNEL);  
  23.         if (substream == NULL) {  
  24.             pcm_err(pcm, "Cannot allocate PCM substream\n");  
  25.             return -ENOMEM;  
  26.         }  
  27.         substream->pcm = pcm;  
  28.         substream->pstr = pstr;  
  29.         substream->number = idx;  
  30.         substream->stream = stream;  
  31.         sprintf(substream->name, "subdevice #%i", idx);  
  32.         substream->buffer_bytes_max = UINT_MAX;  
  33.         if (prev == NULL)  
  34.             pstr->substream = substream;  
  35.         else  
  36.             prev->next = substream;  
  37.   
  38.         if (!pcm->internal) {  
  39.             err = snd_pcm_substream_proc_init(substream);  
  40.             if (err < 0) {  
  41.                 pcm_err(pcm,  
  42.                     "Error in snd_pcm_stream_proc_init\n");  
  43.                 if (prev == NULL)  
  44.                     pstr->substream = NULL;  
  45.                 else  
  46.                     prev->next = NULL;  
  47.                 kfree(substream);  
  48.                 return err;  
  49.             }  
  50.         }  
  51.         substream->group = &substream->self_group;  
  52.         spin_lock_init(&substream->self_group.lock);  
  53.         mutex_init(&substream->self_group.mutex);  
  54.         INIT_LIST_HEAD(&substream->self_group.substreams);  
  55.         list_add_tail(&substream->link_list, &substream->self_group.substreams);  
  56.         atomic_set(&substream->mmap_count, 0);  
  57.         prev = substream;  
  58.     }  
  59.     return 0;  
  60. }             
1.   根據傳遞進來的引數,設定pcm的stream, pcm, substream_count的值。 2.   在proc下建立pcm相關目錄資訊。會呼叫snd_pcm_stream_proc_init函式,根據stream的型別建立pcm0p/pcm0c資料夾,然後會在此資料夾下建立info檔案。info檔案的型別會通過snd_pcm_stream_proc_info_read函式獲得。代表就不貼出來了。:( [cpp]  view plain  copy
  1. [email protected]:/proc/asound/card0/pcm0c$ cat info   
  2. card: 0  
  3. device: 0  
  4. subdevice: 0  
  5. stream: CAPTURE  
  6. id: ALC662 rev1 Analog  
  7. name: ALC662 rev1 Analog  
  8. subname: subdevice #0  
  9. class: 0  
  10. subclass: 0  
  11. subdevices_count: 1  
  12. subdevices_avail: 1  
3.   會根據substrem_count的個數,進行for迴圈操作。 4.   分配一個substream結構,設定必要的引數,如:  pcm,  pstr,  number,  stream,  name等。 5.   呼叫snd_pcm_substream_proc_init函式,建立sub0目錄,然後在此目錄下建立info, hw_params, sw_params,status等檔案。 6.   將所有的substream會通過linklist連結串列儲存,同時如果有多個substream會通過next指標相連。
至此,pcm裝置就全部建立完成,建立完成後會形成如下的邏輯試圖。
大體上就是一棵樹,根節點是card0, 然後子節點是pcm裝置,pcm裝置分為capture & playback stream, 然後在stream下又分為substrem。

PCM硬體操作函式集設定

例項化一個pcm裝置之後,還需要通過snd_pcm_set_ops函式設定該硬體的操作集合。 [cpp]  view plain  copy
  1. void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,  
  2.              const struct snd_pcm_ops *ops)  
  3. {  
  4.     struct snd_pcm_str *stream = &pcm->streams[direction];  
  5.     struct snd_pcm_substream *substream;  
  6.       
  7.     for (substream = stream->substream; substream != NULL; substream = substream->next)  
  8.         substream->ops = ops;  
  9. }  
該函式會根據當前stream的方向/型別,設定該硬體對應的snd_pcm_ops操作集合。

整個流程梳理


PCM裝置節點建立

當呼叫snd_card_register的時候,就會依次呼叫card列表下每個裝置的dev_register回撥函式,對pcm裝置來說就是在_snd_pcm_new函式中的 [cpp]  view plain  copy
  1. static struct snd_device_ops ops = {  
  2.     .dev_free = snd_pcm_dev_free,  
  3.     .dev_register = snd_pcm_dev_register,  
  4.     .dev_disconnect = snd_pcm_dev_disconnect,  
  5. };  
此時會呼叫到snd_pcm_dev_register回撥處理函式。 [cpp]  view plain  copy
  1. static int snd_pcm_dev_register(struct snd_device *device)  
  2. {  
  3.     int cidx, err;  
  4.     struct snd_pcm_substream *substream;  
  5.     struct snd_pcm_notify *notify;  
  6.     char str[16];  
  7.     struct snd_pcm *pcm;  
  8.     struct device *dev;  
  9.   
  10.     if (snd_BUG_ON(!device || !device->device_data))  
  11.         return -ENXIO;  
  12.     pcm = device->device_data;  
  13.     mutex_lock(&register_mutex);  
  14.     err = snd_pcm_add(pcm);  
  15.     if (err) {  
  16.         mutex_unlock(&register_mutex);  
  17.         return err;  
  18.     }  
  19.     for (cidx = 0; cidx < 2; cidx++) {  
  20.         int devtype = -1;  
  21.         if (pcm->streams[cidx].substream == NULL || pcm->internal)  
  22.             continue;  
  23.         switch (cidx) {  
  24.         case SNDRV_PCM_STREAM_PLAYBACK:  
  25.             sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);  
  26.             devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;  
  27.             break;  
  28.         case SNDRV_PCM_STREAM_CAPTURE:  
  29.             sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);  
  30.             devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;  
  31.             break;  
  32.         }  
  33.         /* device pointer to use, pcm->dev takes precedence if 
  34.          * it is assigned, otherwise fall back to card's device 
  35.          * if possible */  
  36.         dev = pcm->dev;  
  37.         if (!dev)  
  38.             dev = snd_card_get_device_link(pcm->card);  
  39.         /* register pcm */  
  40.         err = snd_register_device_for_dev(devtype, pcm->card,  
  41.                           pcm->device,  
  42.                           &snd_pcm_f_ops[cidx],  
  43.                           pcm, str, dev);  
  44.         if (err < 0) {  
  45.             list_del(&pcm->list);  
  46.             mutex_unlock(&register_mutex);  
  47.             return err;  
  48.         }  
  49.   
  50.         dev = snd_get_device(devtype, pcm->card, pcm->device);  
  51.         if (dev) {  
  52.             err = sysfs_create_groups(&dev->kobj,  
  53.                           pcm_dev_attr_groups);  
  54.             if (err < 0)  
  55.                 dev_warn(dev,  
  56.                      "pcm %d:%d: cannot create sysfs groups\n",  
  57.                      pcm->card->number, pcm->device);  
  58.             put_device(dev);  
  59.         }  
  60.   
  61.         for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)  
  62.             snd_pcm_timer_init(substream);  
  63.     }  
  64.   
  65.     list_for_each_entry(notify, &snd_pcm_notify_list, list)  
  66.         notify->n_register(pcm);  
  67.   
  68.     mutex_unlock(&register_mutex);  
  69.     return 0;  
  70. }  
1.   合法性判斷,對pcm裝置來說,snd_device->device_data存放的是當前的pcm指標。 2.    會呼叫snd_pcm_add此函式,判斷此pcm裝置是存在snd_pcm_devices連結串列中存在,存在就返回錯誤,不存在就新增。 3.    設定當前pcm裝置name, 以及具體的pcm裝置型別,PCM_CAPTURE  or PCM_PLAYBACK。 4.    呼叫snd_register_device_for_dev新增pcm裝置到系統中。 5.    呼叫snd_get_device此函式返回當前註冊的pcm裝置,然後設定該pcm的屬性。 6.    呼叫snd_pcm_timer_init函式,進行pcm定時器的初始化。
在繼續分析snd_register_device_for_dev函式之前需要先介紹一個結構體。struct snd_minor。 [cpp]  view plain  copy
  1. struct snd_minor {  
  2.     int type;           /* SNDRV_DEVICE_TYPE_XXX */  
  3.     int card;           /* card number */  
  4.     int device;         /* device number */  
  5.     const struct file_operations *f_ops;    /* file operations */  
  6.     void *private_data;     /* private data for f_ops->open */  
  7.     struct device *dev;     /* device for sysfs */  
  8.     struct snd_card *card_ptr;  /* assigned card instance */  
  9. };  
.type:  裝置型別,比如是pcm, control, timer等裝置。 .card_number:  所屬的card。 .device:  當前裝置型別下的裝置編號。 .f_ops:  具體裝置的檔案操作集合。 .private_data:  open函式的私有資料。 .card_ptr:  所屬的card。
此結構體是用來儲存當前裝置的上下文資訊,該card下所有邏輯裝置都存在此結構。
[cpp]  view plain  copy
  1. int snd_register_device_for_dev(int type, struct snd_card *card, int dev,  
  2.                 const struct file_operations *f_ops,  
  3.                 void *private_data,  
  4.                 const char *name, struct device *device)  
  5. {  
  6.     int minor;  
  7.     struct snd_minor *preg;  
  8.   
  9.     if (snd_BUG_ON(!name))  
  10.         return -EINVAL;  
  11.     preg = kmalloc(sizeof *preg, GFP_KERNEL);  
  12.     if (preg == NULL)  
  13.         return -ENOMEM;  
  14.     preg->type = type;  
  15.     preg->card = card ? card->nu