Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)
1、基本概念及邏輯關(guān)系
??????? 如上圖,通過上一節(jié)聲卡的學(xué)習(xí)我們已經(jīng)知道PCM是聲卡的一個(gè)子設(shè)備,或者表示一個(gè)PCM實(shí)例。
??????? 每個(gè)聲卡最多可以包含4個(gè)pcm的實(shí)例,每個(gè)pcm實(shí)例對(duì)應(yīng)一個(gè)pcm設(shè)備文件。pcm實(shí)例數(shù)量的這種限制源于linux設(shè)備號(hào)所占用的位大小,如果以后使用64位的設(shè)備號(hào),我們將可以創(chuàng)建更多的pcm實(shí)例。不過大多數(shù)情況下,在嵌入式設(shè)備中,一個(gè)pcm實(shí)例已經(jīng)足夠了。
????????一個(gè)pcm實(shí)例由一個(gè)playback stream和一個(gè)capture stream組成,這兩個(gè)stream又分別有一個(gè)或多個(gè)substreams組成??梢杂萌缦聢D來表示他們直接的邏輯關(guān)系:
????????當(dāng)一個(gè)子流已經(jīng)存在,并且已經(jīng)被打開,當(dāng)再次被打開的時(shí)候,會(huì)被阻塞。????????
????????在實(shí)際的應(yīng)用中,通常不會(huì)如上圖這么復(fù)雜,大多數(shù)情況下是一個(gè)聲卡有一個(gè)PCM實(shí)例,PCM下面有一個(gè)playback和capture,而playback和capture各自有一個(gè)substream。
??????? PCM層有幾個(gè)很重要的結(jié)構(gòu)體,我們通過如下的UML圖來梳理他們直接的關(guān)系。
?????????圖片地址:http://hi.csdn.net/attachment/201104/2/0_1301728746sAUd.gif
??????? 1、snd_pcm:掛在snd_card下面的一個(gè)snd_device。
??????? 2、snd_pcm中的字段:streams[2]:該數(shù)組中的兩個(gè)元素指向兩個(gè)snd_pcm_str結(jié)構(gòu),分別代表playback stream和capture stream。
??????? 3、snd_pcm_str中的substream字段:指向snd_pcm_substream結(jié)構(gòu)。
??????? 4、snd_pcm_substream是pcm中間層的核心,絕大部分任務(wù)都是在substream中處理,尤其是他的ops(snd_pcm_ops)字段,許多user空間的應(yīng)用程序通過alsa-lib對(duì)驅(qū)動(dòng)程序的請(qǐng)求都是由該結(jié)構(gòu)中的函數(shù)處理。它的runtime字段則指向snd_pcm_runtime結(jié)構(gòu),snd_pcm_runtime記錄這substream的一些重要的軟件和硬件運(yùn)行環(huán)境和參數(shù)。
2、PCM創(chuàng)建流程
??????? PCM的整個(gè)創(chuàng)建流程請(qǐng)參考如下時(shí)序圖進(jìn)行理解:
???????? alsa-driver的中間層已經(jīng)提供新建PCM的API:
2.1、創(chuàng)建PCM實(shí)例
int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, struct snd_pcm **rpcm)????????card:表示所屬的聲卡。
??????? ID:PCM實(shí)例的ID(名字)。
??????? device:表示目前創(chuàng)建的是該聲卡下的第幾個(gè)PCM,第一個(gè)PCM設(shè)備從0開始計(jì)數(shù)。
????????playback_count:表示該P(yáng)CM播放流中將會(huì)有幾個(gè)substream。
????????capture_count :表示該P(yáng)CM錄音流中將會(huì)有幾個(gè)substream。
??????? rpcm:返回的PCM實(shí)例。
??????? 該函數(shù)的主要作用是創(chuàng)建PCM邏輯設(shè)備,創(chuàng)建回放子流和錄制子流實(shí)例,并初始化回放子流和錄制子流的PCM操作函數(shù)(數(shù)據(jù)搬運(yùn)時(shí),需要調(diào)用這些函數(shù)來驅(qū)動(dòng) codec、codec_dai、cpu_dai、dma 設(shè)備工作)。
2.2、設(shè)置PCM設(shè)備的操作函數(shù)
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,const struct snd_pcm_ops *ops)??????? pcm:上述snd_pcm_new 創(chuàng)建的PCM實(shí)例。
??????? direction:是指SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE,即設(shè)置為播放或者錄音功能。
??????? snd_pcm_ops:結(jié)構(gòu)中的函數(shù)通常就是我們驅(qū)動(dòng)要實(shí)現(xiàn)的函數(shù)。
2.3、定義PCM的操作函數(shù)
??????? 以AC97驅(qū)動(dòng)(linux/sound/arm/pxa2xx-ac97.c)為例,在驅(qū)動(dòng)中對(duì)于PCM進(jìn)行了如下設(shè)置:
static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = {.open = pxa2xx_ac97_pcm_open,.close = pxa2xx_ac97_pcm_close,.hw_params = pxa2xx_pcm_hw_params,.prepare = pxa2xx_ac97_pcm_prepare,.trigger = pxa2xx_pcm_trigger,.pointer = pxa2xx_pcm_pointer, };snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);2.4、定義硬件參數(shù)
static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {.info = SNDRV_PCM_INFO_MMAP |SNDRV_PCM_INFO_MMAP_VALID |SNDRV_PCM_INFO_INTERLEAVED |SNDRV_PCM_INFO_PAUSE |SNDRV_PCM_INFO_RESUME,.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_S24_LE |SNDRV_PCM_FMTBIT_S32_LE,.period_bytes_min = 32,.period_bytes_max = 8192 - 32,.periods_min = 1,.periods_max = 256,.buffer_bytes_max = 128 * 1024,.fifo_size = 32, };int pxa2xx_pcm_open(struct snd_pcm_substream *substream) {struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_pcm_runtime *runtime = substream->runtime;struct snd_dmaengine_dai_dma_data *dma_params;int ret;runtime->hw = pxa2xx_pcm_hardware;dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);if (!dma_params)return 0;/** For mysterious reasons (and despite what the manual says)* playback samples are lost if the DMA count is not a multiple* of the DMA burst size. Let's add a rule to enforce that.*/ret = snd_pcm_hw_constraint_step(runtime, 0,SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);if (ret)return ret;ret = snd_pcm_hw_constraint_step(runtime, 0,SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);if (ret)return ret;ret = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS);if (ret < 0)return ret;return snd_dmaengine_pcm_open(substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev,dma_params->chan_name)); }3、PCM相關(guān)源碼分析
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) {/* 直接調(diào)用函數(shù)_snd_pcm_new,參數(shù)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. 邏輯設(shè)備的操作函數(shù)結(jié)構(gòu)體, 主要用于注冊(cè)子設(shè)備 */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結(jié)構(gòu)體分配空間,根據(jù)傳入?yún)?shù)賦值 */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. 根據(jù)傳入的playback和capture的個(gè)數(shù)創(chuàng)建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. 創(chuàng)建一個(gè)PCM邏輯設(shè)備,創(chuàng)建邏輯設(shè)備,并添加到邏輯設(shè)備鏈表 */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 };???????? 這里重要的變量有兩個(gè)streams與private_data。streams有兩個(gè),是因?yàn)橐粋€(gè)指向播放設(shè)備,一個(gè)指向錄音設(shè)備。private_data在很多結(jié)構(gòu)里都可以看到,和面象對(duì)象里的繼承有點(diǎn)類似,如果將snd_pcm理解為基類的話,private_data指向的就是它的繼承類,也就是真正的實(shí)現(xiàn)者。
????????list,在pcm.c中有一個(gè)全局變量snd_pcm_devices,將所有的snd_pcm對(duì)象鏈接起來,目的是外部提供一些可供枚舉所有設(shè)備的接口,看起來并不怎么被用到。
????????另外還有info_flags、dev_class等變量看起來是為一些特殊設(shè)備預(yù)留的,對(duì)待一些特殊操作。
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_PROCFSstruct snd_info_entry *proc_root; #ifdef CONFIG_SND_PCM_XRUN_DEBUGunsigned int xrun_debug; /* 0 = disabled, 1 = verbose, 2 = stacktrace */ #endif #endifstruct snd_kcontrol *chmap_kctl; /* channel-mapping controls */struct device dev; };???????? snd_pcm_str的主要作用是指向snd_pcm_substream,而snd_pcm_substream可以有多個(gè),這也是snd_pcm_str存在的原因,否則snd_pcm直接指向snd_pcm_substream就可以了。
????????這里的dev是將pcm加入到文件系統(tǒng)時(shí)要用到。包含的信息,在下面介紹的snd_pcm_new_stream中會(huì)看到。
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 根據(jù)傳入的參數(shù),為PCM流(snd_pcm_str)賦值:方向,所屬的PCM,PCM子流的個(gè)數(shù) */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); #endifpstr->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) /* 第一個(gè)子流 */pstr->substream = substream;elseprev->next = substream; /* 非第一個(gè)子流,添加到前一個(gè)子流后部 *//* 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;elseprev->next = NULL;kfree(substream);return err;}}/* 結(jié)構(gòu)體初始化 */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; }?????????函數(shù)參數(shù)中的int stream,是一個(gè)枚舉類型:?
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最終會(huì)被傳入device_add函數(shù)中,用來構(gòu)建文件系統(tǒng)。
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; }????????這段函數(shù)中可以看到dev->class被設(shè)置成sound_class,這個(gè)是我們之前提到的文件放到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_PROCFSstruct 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的內(nèi)容有些多,此處只需要重要的進(jìn)行介紹。
????????private_data:從snd_pcm中的private_data拷貝過來的,指向?qū)崿F(xiàn)者的結(jié)構(gòu)。
????????const struct snd_pcm_ops *ops:這部分是框架的內(nèi)容,具體的操作需要實(shí)現(xiàn)者的參與,留給實(shí)現(xiàn)者的函數(shù)指針集。這個(gè)和文件操作的設(shè)計(jì)策略是一致的。
????????struct snd_pcm_runtime *runtime:讀寫數(shù)據(jù)的時(shí)候由它來控制。到分析讀寫代碼的時(shí)候,會(huì)重點(diǎn)關(guān)注它。
????????struct snd_pcm_substream *next:將多個(gè)snd_pcm_substream對(duì)象鏈接起來,它就是snd_pcm_str指向的鏈接。
????????group:在用戶空間可以通過SNDRV_PCM_IOCTL_LINK將多個(gè)substream鏈接起來。然后就可以對(duì)這些對(duì)象進(jìn)行統(tǒng)一的操作。我沒遇到過具體的應(yīng)用場(chǎng)景。
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);??????? 此函數(shù)是提供給調(diào)用側(cè)使用的。設(shè)置的內(nèi)容可以參考pcm文件結(jié)構(gòu)簡(jiǎn)圖。?
3.6、snd_pcm_dev_register
????????在繼續(xù)分析snd_pcm_dev_register函數(shù)之前需要先介紹一個(gè)結(jié)構(gòu)體。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: 設(shè)備類型,比如是pcm, control, timer等設(shè)備。
????????card_number: 所屬的card。
????????device: 當(dāng)前設(shè)備類型下的設(shè)備編號(hào)。
????????f_ops: 具體設(shè)備的文件操作集合。
????????private_data: open函數(shù)的私有數(shù)據(jù)。
????????card_ptr: 所屬的card。
????????此結(jié)構(gòu)體是用來保存當(dāng)前設(shè)備的上下文信息,該card下所有邏輯設(shè)備都存在此結(jié)構(gòu)。
static int snd_pcm_dev_register(struct snd_device *device) {/* 1、添加pcm結(jié)構(gòu)體到全局鏈表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對(duì)象 */pcm = device->device_data;mutex_lock(®ister_mutex);/* snd_pcm對(duì)象將被保存到全局變量snd_pcm_devices中,用于枚舉設(shè)備等操作 */err = snd_pcm_add(pcm);if (err)goto unlock;for (cidx = 0; cidx < 2; cidx++) {/* 2、確定PCM設(shè)備節(jié)點(diǎn)名字 */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 *//* 將設(shè)備添加到文件系統(tǒng),將snd_pcm_f_ops傳入,將被設(shè)置給snd_minor對(duì)象 */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)/* 設(shè)定CONFIG_SND_PCM_TIMER宏的時(shí)候,會(huì)去設(shè)置substream的時(shí)間 */snd_pcm_timer_init(substream);}pcm_call_notify(pcm, n_register);unlock:mutex_unlock(®ister_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;/* 創(chuàng)建一個(gè)snd_minor,并添加到全局結(jié)構(gòu)體 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、注冊(cè)一個(gè)設(shè)備節(jié)點(diǎn) */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; }??????? 當(dāng)聲卡被注冊(cè)時(shí),會(huì)注冊(cè)所有的邏輯設(shè)備。主要的工作是創(chuàng)建PCM設(shè)備節(jié)點(diǎn)
 具體的流程:
??????????????? 1、添加pcm結(jié)構(gòu)體到全局鏈表snd_pcm_devices。
??????????????? 2、確定PCM設(shè)備節(jié)點(diǎn)名字。
??????????????? 3、創(chuàng)建一個(gè)snd_minor,并添加到全局結(jié)構(gòu)體 snd_minors。
??????????????? 4、注冊(cè)一個(gè)設(shè)備節(jié)點(diǎn)
????????可以看到添加到文件系統(tǒng)的是播放設(shè)備和錄音設(shè)備,根據(jù)snd_pcm_str指向的內(nèi)容來設(shè)定的。代碼中看到snd_pcm也被定義為SNDRV_DEV_PCM設(shè)備,但是文件系統(tǒng)中并不會(huì)保存這個(gè)類型的設(shè)備。
????????snd_pcm_timer_init是在CONFIG_SND_PCM_TIMER宏被定義的時(shí)候,會(huì)起作用。
????????通過下圖可以幫助你更好的理解各結(jié)構(gòu)直接的亂講關(guān)系。
總結(jié)
以上是生活随笔為你收集整理的Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: OpenCV——计算轮廓长度/周长和面积
 - 下一篇: python温度转换的详细说明_pyth