Linux音訊驅動-PCM裝置
阿新 • • 發佈:2018-12-14
概述
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]- struct snd_pcm {
- struct snd_card *card;
- struct list_head list;
- int
- 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);
- struct device *dev; /* actual hw device this belongs to */
- bool internal; /* pcm is for internal use only */
- bool nonatomic; /* whole PCM operations are in non-atomic context */
- #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
- struct snd_pcm_oss oss;
- #endif
- };
通常一個pcm下會有兩個stream, 分別為capture stream和playback stream,在每個stream下又會存在多個substream。 linux系統中使用snd_pcm_str定義stream, 使用snd_pcm_substream定義substream。 [cpp] view plain copy
- 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;
- };
[cpp] view plain copy
- 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 */
- /* -- 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 -- */
- void *file;
- int ref_count;
- atomic_t mmap_count;
- unsigned int f_flags;
- void (*pcm_release)(struct snd_pcm_substream *);
- struct pid *pid;
- /* misc flags */
- unsigned int hw_opened: 1;
- };
下圖是對這幾個結構體之間的簡單表述。
pcm裝置的建立
建立一個pcm裝置的例項,使用snd_pcm_new函式。 [cpp] view plain copy- /**
- * 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);
- }
- 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) {
- dev_err(card->dev, "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_new_stream建立一個stream [cpp] view plain copy
- int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
- {
- int idx, err;
- 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 > 0 && !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++) {
- substream = kzalloc(sizeof(*substream), GFP_KERNEL);
- if (substream == NULL) {
- pcm_err(pcm, "Cannot allocate PCM substream\n");
- 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;
- 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;
- spin_lock_init(&substream->self_group.lock);
- mutex_init(&substream->self_group.mutex);
- INIT_LIST_HEAD(&substream->self_group.substreams);
- list_add_tail(&substream->link_list, &substream->self_group.substreams);
- atomic_set(&substream->mmap_count, 0);
- prev = substream;
- }
- return 0;
- }
- [email protected]:/proc/asound/card0/pcm0c$ cat info
- card: 0
- device: 0
- subdevice: 0
- stream: CAPTURE
- id: ALC662 rev1 Analog
- name: ALC662 rev1 Analog
- subname: subdevice #0
- class: 0
- subclass: 0
- subdevices_count: 1
- subdevices_avail: 1
至此,pcm裝置就全部建立完成,建立完成後會形成如下的邏輯試圖。
大體上就是一棵樹,根節點是card0, 然後子節點是pcm裝置,pcm裝置分為capture & playback stream, 然後在stream下又分為substrem。
PCM硬體操作函式集設定
例項化一個pcm裝置之後,還需要通過snd_pcm_set_ops函式設定該硬體的操作集合。 [cpp] view plain copy- 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;
- }
整個流程梳理
PCM裝置節點建立
當呼叫snd_card_register的時候,就會依次呼叫card列表下每個裝置的dev_register回撥函式,對pcm裝置來說就是在_snd_pcm_new函式中的 [cpp] view plain copy- static struct snd_device_ops ops = {
- .dev_free = snd_pcm_dev_free,
- .dev_register = snd_pcm_dev_register,
- .dev_disconnect = snd_pcm_dev_disconnect,
- };
- 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;
- }
- dev = snd_get_device(devtype, pcm->card, pcm->device);
- if (dev) {
- err = sysfs_create_groups(&dev->kobj,
- pcm_dev_attr_groups);
- if (err < 0)
- dev_warn(dev,
- "pcm %d:%d: cannot create sysfs groups\n",
- pcm->card->number, pcm->device);
- put_device(dev);
- }
- 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;
- }
在繼續分析snd_register_device_for_dev函式之前需要先介紹一個結構體。struct snd_minor。 [cpp] view plain copy
- 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 */
- };
此結構體是用來儲存當前裝置的上下文資訊,該card下所有邏輯裝置都存在此結構。
[cpp] view plain copy
- int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
- const struct file_operations *f_ops,
- void *private_data,
- const char *name, struct device *device)
- {
- int minor;
- struct snd_minor *preg;
- if (snd_BUG_ON(!name))
- return -EINVAL;
- preg = kmalloc(sizeof *preg, GFP_KERNEL);
- if (preg == NULL)
- return -ENOMEM;
- preg->type = type;
- preg->card = card ? card->nu