Rockchip RK3399 - ALSA 声卡之PCM设备
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux :6.3
----------------------------------------------------------------------------------------------------------------------------
我们在Rockchip RK3399 - ALC5651 & I2S基础中实际上已经介绍过PCM,它是一种音频编码格式,更确切的说是一种将声音从模拟信号转换成数字信号的技术。
音频驱动的两大核心任务就是:
- playback :如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频;
- capture :把mic拾取到得模拟信号,经过采样、量化,转换为PCM数据送回给用户空间的应用程序;
ALSA CORE已经实现了PCM中间层,我们编写的驱动都是调用PCM相关的API,基本上只需要实现底层的需要访问硬件的函数即可;
- include/sound/pcm.h:提供访问 PCM 中间层代码的API;
- include/sound/pcm_params.h:提供访问一些与hw_param 相关的函数;
一、核心数据结构
1.1 ALSA中的数据结构
1.1.1 struct pcm_new
pcm使用struct snd_pcm数据结构来描述,一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
在嵌入式系统中,通常不会像图中这么复杂,大多数情况下是一个声卡,一个pcm实例,pcm下面有一个playback stream和capture stream,playback和capture下面各自有一个substream。
snd_pcm数据结构定义在include/sound/pcm.h;
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 };
这里重要的变量如下:
- card:声卡设备;
- device:pcm设备编号;
- streams:数组长度为2,元素0指向playback stream设备,元素1指向capture stream设备;
- private_data:在很多数据结构里面都可以看到,一般用于指向私有数据;
1.1.2 struct snd_pcm_str
struct snd_pcm_str用于表示pcm stream,定义在include/sound/pcm.h;
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就可以了。
其中:
- stream:类类型,比如SNDRV_PCM_STREAM_PLAYBACK表示播放,SNDRV_PCM_STREAM_CAPTURE表示捕获;
- pcm:指向pcm设备;
- substream_count:substream的个数;
- substream_opened:substream打开标志;
- substream:用于保存所有的substream,struct snd_pcm_substream实是一个链表节点类型的数据结构;
- dev:设备驱动模型中的device,可以将snd_pcm_str看做其子类;
1.1.3 struct snd_pcm_substream
struct snd_pcm_substream用于表示pcm substream,定义在include/sound/pcm.h;
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; };
snd_pcm_substream的内容有些多,此处只需要重要的进行介绍;
- ops:pcm操作集,这部分具体的操作需要实现者的参与,留给实现者的函数指针集。这个和文件操作的设计策略是一致的;
- runtime:pcm运行时实例,读写数据的时候由它来控制;
- next:指向下一个pcm substream,用于将多个snd_pcm_substream对象链接起来;
- pstrt:指向所属的pcm stream;
- group:在用户空间可以通过SNDRV_PCM_IOCTL_LINK将多个substream链接起来。然后就可以对这些对象进行统一的操作。
1.1.4 snd_pcm_hw_params
struct snd_pcm_hw_params定义在include/uapi/sound/asound.h,是用于配置音频硬件参数的结构体,比如通道数、采样率、数据格式等;
struct snd_pcm_hw_params { unsigned int flags; struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; struct snd_mask mres[5]; /* reserved masks */ struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - // 宏的值为19 SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]; // 宏的值为8 struct snd_interval ires[9]; /* reserved intervals */ unsigned int rmask; /* W: requested masks */ unsigned int cmask; /* R: changed masks */ unsigned int info; /* R: Info flags for returned setup */ unsigned int msbits; /* R: used most significant bits */ unsigned int rate_num; /* R: rate numerator */ unsigned int rate_den; /* R: rate denominator */ snd_pcm_uframes_t fifo_size; /* R: chip FIFO size in frames */ unsigned char reserved[64]; /* reserved for future */ };
该结构体定义了一组成员变量,用于存储和管理音频硬件参数的信息。下面是各成员变量的含义:
- flags:标志位,用于指示参数的特性和状态。
- masks:用于描述硬件参数的位掩码。在数组中的每个元素都代表一个具体的参数;
- mres:保留的位掩码;
- intervals:这是一个数组,数组长度为12,用于描述硬件参数的取值范围;每一个元素依次表示的意义为:
- SNDRV_PCM_HW_PARAM_SAMPLE_BITS:Bits per sample ,每个采样点的位数,即位宽度;
- SNDRV_PCM_HW_PARAM_FRAME_BITS:Bits per frame,每帧所占用的位数,假设有一个音频流,采样率为 44100 Hz,声道数为 2(立体声),每个采样点的比特数为 16bit。在这种情况下,每个采样点占用 16 个比特,每帧就包含了双声道的采样点数据。因此,每帧的比特数可以计算为:16 bits/sample × 2 channels = 32 bits/frame;
- SNDRV_PCM_HW_PARAM_CHANNELS:Channels,通道数;
- SNDRV_PCM_HW_PARAM_RATE:Approx rate,近似采样率;
- SNDRV_PCM_HW_PARAM_PERIOD_TIME:Approx distance between interrupts in us;
- SNDRV_PCM_HW_PARAM_PERIOD_SIZE:Approx frames between interrupts;
- SNDRV_PCM_HW_PARAM_PERIOD_BYTES:Approx bytes between interrupts;
- SNDRV_PCM_HW_PARAM_PERIODS:Approx interrupts per buffer;
- SNDRV_PCM_HW_PARAM_BUFFER_TIME:Approx duration of buffer in us;
- SNDRV_PCM_HW_PARAM_BUFFER_SIZE:Size of buffer in frames;
- SNDRV_PCM_HW_PARAM_BUFFER_BYTES:Size of buffer in bytes
- SNDRV_PCM_HW_PARAM_TICK_TIME:Approx tick duration in us;
- ires:保留的取值范围;
- rmask:请求的位掩码;
- cmask:变化的位掩码;
- info:返回的设置信息标志,用于指示配置的相关信息;
- msbits:使用的最高有效位数;
- rate_num:采样率的分子部分(分子/分母构成实际的采样率);
- rate_den:采样率的分母部分;
- fifo_size:芯片 FIFO(First In First Out)缓冲区的大小;
- reserved:保留字段,用于未来的扩展;
其中struct snd_interval数据结构定义如下:
struct snd_interval { unsigned int min, max; unsigned int openmin:1, openmax:1, integer:1, empty:1; };
1.2 ASoC中的数据结构
1.2.1 struct snd_soc_pcm_stream
struct snd_soc_pcm_stream是ASoC中定义的pcm stream,定义在include/sound/soc.h,用于描述SoC pcm stream的信息;
/* SoC PCM stream information */ struct snd_soc_pcm_stream { const char *stream_name; u64 formats; /* SNDRV_PCM_FMTBIT_* */ unsigned int rates; /* SNDRV_PCM_RATE_* */ unsigned int rate_min; /* min rate */ unsigned int rate_max; /* max rate */ unsigned int channels_min; /* min channels */ unsigned int channels_max; /* max channels */ unsigned int sig_bits; /* number of bits of content */ };
其中:
- stream_name:stream名称;
- formats:位深度,也就是数据传输的位宽;
- rate:音频采样率;
- rate_min:最低采样率;
- rate_max:最高采样率;
- channels_min:最少通道数;
- channels_max:最大通道数;
- sig_bits:采样位宽;
位深度种类很多,比如:
#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) // 8位整数采样格式 #define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) #define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) // 16为整数采样格式(小端) #define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) #define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) #define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) #define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) #define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) #define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) #define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) #define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) #define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) #define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) #define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) #define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) #define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE)
音频采样率种类很多,比如:
/* If you change this don't forget to change rates[] table in pcm_native.c */ #define SNDRV_PCM_RATE_5512 (1<<0) /* 5512Hz */ #define SNDRV_PCM_RATE_8000 (1<<1) /* 8000Hz */ #define SNDRV_PCM_RATE_11025 (1<<2) /* 11025Hz */ #define SNDRV_PCM_RATE_16000 (1<<3) /* 16000Hz */ #define SNDRV_PCM_RATE_22050 (1<<4) /* 22050Hz */ #define SNDRV_PCM_RATE_32000 (1<<5) /* 32000Hz */ #define SNDRV_PCM_RATE_44100 (1<<6) /* 44100Hz */ #define SNDRV_PCM_RATE_48000 (1<<7) /* 48000Hz */ #define SNDRV_PCM_RATE_64000 (1<<8) /* 64000Hz */ #define SNDRV_PCM_RATE_88200 (1<<9) /* 88200Hz */ #define SNDRV_PCM_RATE_96000 (1<<10) /* 96000Hz */ #define SNDRV_PCM_RATE_176400 (1<<11) /* 176400Hz */ #define SNDRV_PCM_RATE_192000 (1<<12) /* 192000Hz */ #define SNDRV_PCM_RATE_CONTINUOUS (1<<30) /* continuous range */ #define SNDRV_PCM_RATE_KNOT (1<<31) /* supports more non-continuos rates */ #define SNDRV_PCM_RATE_8000_44100 (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_11025|\ SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_22050|\ SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100) #define SNDRV_PCM_RATE_8000_48000 (SNDRV_PCM_RATE_8000_44100|SNDRV_PCM_RATE_48000) #define SNDRV_PCM_RATE_8000_96000 (SNDRV_PCM_RATE_8000_48000|SNDRV_PCM_RATE_64000|\ SNDRV_PCM_RATE_88200|SNDRV_PCM_RATE_96000) #define SNDRV_PCM_RATE_8000_192000 (SNDRV_PCM_RATE_8000_96000|SNDRV_PCM_RATE_176400|\ SNDRV_PCM_RATE_192000)
1.2.2 struct snd_soc_pcm_runtime
struct snd_soc_pcm_runtime是ASoC中定义的pcm runtime,定义在include/sound/soc.h,这个数据结构非常用重要;在注册ASoC声卡时会调用soc_new_pcm_runtime为每一个音频数据链路(struct snd_soc_dai_link)分配一个snd_soc_pcm_runtime ,snd_soc_pcm_runtime 是ASoC的桥梁,保存音频数据链路的dai、以及dai所属的component等信息;
/* SoC machine DAI configuration, glues a codec and cpu DAI together */ struct snd_soc_pcm_runtime { struct device *dev; struct snd_soc_card *card; struct snd_soc_dai_link *dai_link; struct snd_pcm_ops ops; unsigned int params_select; /* currently selected param for dai link */ /* Dynamic PCM BE runtime data */ struct snd_soc_dpcm_runtime dpcm[SNDRV_PCM_STREAM_LAST + 1]; struct snd_soc_dapm_widget *c2c_widget[SNDRV_PCM_STREAM_LAST + 1]; long pmdown_time; /* runtime devices */ struct snd_pcm *pcm; struct snd_compr *compr; /* * dais = cpu_dai + codec_dai * see * soc_new_pcm_runtime() * asoc_rtd_to_cpu() * asoc_rtd_to_codec() */ struct snd_soc_dai **dais; struct delayed_work delayed_work; void (*close_delayed_work_func)(struct snd_soc_pcm_runtime *rtd); #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_dpcm_root; #endif unsigned int num; /* 0-based and monotonic increasing */ struct list_head list; /* rtd list of the soc card */ /* function mark */ struct snd_pcm_substream *mark_startup; struct snd_pcm_substream *mark_hw_params; struct snd_pcm_substream *mark_trigger; struct snd_compr_stream *mark_compr_startup; /* bit field */ unsigned int pop_wait:1; unsigned int fe_compr:1; /* for Dynamic PCM */ int num_components; struct snd_soc_component *components[]; /* CPU/Codec/Platform */ };
其中:
- dev:设备驱动模型中的device,可以将snd_soc_pcm_runtime看做其子类;
- dev->parent一般会被设置为ASoC声卡card->dev,以后面我们将要介绍的machine驱动(simple-card)为例,card->dev为rt5651-sound设备;
- dev->release一般会被设置为soc_release_rtd_dev;
- dev设备名称会为设置为snd_soc_dai_link的名称,比如ALC5651音频驱动,这个名称为"rockchip-i2s-rt5651";
- dev设备驱动数据会被设置为当前snd_soc_pcm_runtime ;
- card:指向ASoC声卡的指针;
- dai_link:指向音频数据链路的指针;
- dpcm:Dynamic PCM BE runtime data;
- dais:指向一个指针数组,指针数组中每个元素指向一个dai,用于保存当前音频数据链路上的dai,包括cpu dai、codec dai;
- delayed_word:延迟工作;
- num:为ASoC声卡设备的每一个pcm runtime会分配一个编号,从0开始;
- pmdown_time:下电超时时间;
- list:链表节点,用于构建双向链表,将当前pcm runtime添加到ASoC声卡的rtd_list链表中;
- components:指向一个数组,用于存储当前音频数据链路上的代表platform以及codec设备的component;
- num_components:components数组的长度;
如果你去比较snd_soc_dai_link和soc_new_pcm_runtime数据结构,你就可以发现这个数据结构实际上都是用来描述音频数据链路的,只不过一个描述的是静态数据,另一个描述是动态数据。
1.3 关系图
下面一张图列出了PCM中间层几个重要的结构,可以让我们从uml的角度看一看这列结构的关系,理清他们之间的关系,对我们理解PCM中间层的实现方式。
其中我们比较关注的点是:
- pcm是挂在snd_card成员devices链表下面的一个snd_device;
- snd_pcm中的字段:streams[2],该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream;
- snd_pcm_str中的substream字段,指向snd_pcm_substream结构;
- snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是它的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数;
二、核心API
2.1 ALSA创建PCM设备(snd_pcm_new)
在ALSA中,PCM设备的创建可以通过snd_pcm_new函数来完成,函数接收6个参数:
- card:ALSA声卡设备;
- id:pcm设备唯一标识;
- device:设备号,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始;
- playback_count: 表示该pcm将会有几个playback substream;
- capture_count:表示该pcm将会有几个capture substream;
- rpcm:保存创建的pcm设备;
函数定义在sound/core/pcm.c,
/** * 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); }
这段代码实际上就是调用_snd_pcm_new创建一个pcm_new实例,并根据playback_count、capture_count等参数初始化playback stream和capture stream,card、device等成员;
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 const struct snd_device_ops ops = { // 声卡pcm设备操作集 .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; pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); // 动态申请内存,申请一个snd_pcm数据结构 if (!pcm) return -ENOMEM; pcm->card = card; // 设置声卡设备 pcm->device = device; // 设置pcm设备编号 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)); // 设置id err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count); // 创建playback stream if (err < 0) goto free_pcm; err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count); // 创建capture stream if (err < 0) goto free_pcm; err = snd_device_new(card, SNDRV_DEV_PCM, pcm, // device_data设置为pcm internal ? &internal_ops : &ops); // 创建一个新的snd_device实例,并将添加到声卡设备的devices链表中 if (err < 0) goto free_pcm; if (rpcm) *rpcm = pcm; // 写回 return 0; free_pcm: snd_pcm_free(pcm); return err; }
2.1.1 snd_pcm_new_stream
这里我们需要关注一下pcm stream的创建函数snd_pcm_new_stream,第一个参数为pcm实例,第二个参数为pcm数据流方向,即playback还是capture,第三个参数为substream数量;
/** * 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) // 假设pcm->internal=0 { 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; // 设置pcm数据流方向,即playback还是capture pstr->pcm = pcm; // 设置pcm实例 pstr->substream_count = substream_count; // substream数量 if (!substream_count) return 0; snd_device_initialize(&pstr->dev, pcm->card); // 初始化pstr->dev成员 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'); // 设置设备名称为pcmC0Dxp或pcmC0Dxc if (!pcm->internal) { err = snd_pcm_stream_proc_init(pstr); // proc文件系统相关 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++) { // 创建若干个snd_pcm_substream实例,填充 substream 字段,
// 如 streams,number,stream等,并将所有的 substream 填充到 pcm->snd_pcm_substream->substream 中。 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; 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; }
2.1.2 snd_pcm_stream_proc_init
snd_pcm_stream_proc_init函数实现如下:
static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { struct snd_pcm *pcm = pstr->pcm; struct snd_info_entry *entry; char name[16]; sprintf(name, "pcm%i%c", pcm->device, pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); entry = snd_info_create_card_entry(pcm->card, name, pcm->card->proc_root); // 创建pcmxp/c目录 if (!entry) return -ENOMEM; entry->mode = S_IFDIR | 0555; pstr->proc_root = entry; entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root); // 在 pcmxp|c下创建info文件 if (entry) snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read); #ifdef CONFIG_SND_PCM_XRUN_DEBUG entry = snd_info_create_card_entry(pcm->card, "xrun_debug", pstr->proc_root); if (entry) { snd_info_set_text_ops(entry, pstr, snd_pcm_xrun_debug_read); entry->c.text.write = snd_pcm_xrun_debug_write; entry->mode |= 0200; } #endif return 0; }
当我们查看/proc/asound/card0/pcm0p/info文件的内容:
root@rk3399:/# cat /proc/asound/card0/pcm0p/info card: 0 device: 0 subdevice: 0 stream: PLAYBACK id: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 name: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 subname: subdevice #0 class: 0 subclass: 0 subdevices_count: 1 subdevices_avail: 1
实际上就是调用的snd_pcm_stream_proc_info_read函数;
static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream, struct snd_info_buffer *buffer) { struct snd_pcm_info *info; int err; if (! substream) return; info = kmalloc(sizeof(*info), GFP_KERNEL); if (!info) return; err = snd_pcm_info(substream, info); if (err < 0) { snd_iprintf(buffer, "error %d\n", err); kfree(info); return; } snd_iprintf(buffer, "card: %d\n", info->card); snd_iprintf(buffer, "device: %d\n", info->device); snd_iprintf(buffer, "subdevice: %d\n", info->subdevice); snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream)); snd_iprintf(buffer, "id: %s\n", info->id); snd_iprintf(buffer, "name: %s\n", info->name); snd_iprintf(buffer, "subname: %s\n", info->subname); snd_iprintf(buffer, "class: %d\n", info->dev_class); snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass); snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count); snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail); kfree(info); } static void snd_pcm_stream_proc_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream, buffer); }
2.1.3 snd_pcm_dev_register
在注册声卡设备card时会遍历声卡设备的逻辑设备链表devices,并调用声卡逻辑设备操作集中的dev_register函数,对于pcm设备也就是snd_pcm_dev_register函数,该函数调用snd_register_device依次注册pcm播放设备、pcm录音设备;
static int snd_pcm_dev_register(struct snd_device *device) { int cidx, err; struct snd_pcm_substream *substream; struct snd_pcm *pcm; if (snd_BUG_ON(!device || !device->device_data)) return -ENXIO; pcm = device->device_data; // 获取snd_pcm实例 mutex_lock(®ister_mutex); err = snd_pcm_add(pcm); // 将pcm添加到全局链表snd_pcm_devices if (err) goto unlock; for (cidx = 0; cidx < 2; cidx++) { int devtype = -1; // 设备类型 if (pcm->streams[cidx].substream == NULL) continue; switch (cidx) { case SNDRV_PCM_STREAM_PLAYBACK: //0 devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; break; case SNDRV_PCM_STREAM_CAPTURE: //1 devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; break; } /* register pcm */ 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) snd_pcm_timer_init(substream); } pcm_call_notify(pcm, n_register); unlock: mutex_unlock(®ister_mutex); return err; }
2.1.4 时序图表示
通过分析,我们将声卡设备创建、以及pcm设备创建的完整流程通过时序图表示如下:
(1) snd_card_create :pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡;
(2) snd_pcm_new:调用该API创建一个pcm,在该API中会做以下事情;
- 建立playback stream,相应的substream也同时建立;
- 建立capture stream,相应的substream也同时建立;
- 调用snd_device_new把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用;
(3) snd_pcm_set_ops:设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数;
(4) snd_card_register:注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(比如alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc;
2.2 ASoC创建PCM设备
在ASoC中,PCM设备的创建可以通过soc_create_pcm函数来完成,函数接收5个参数:
- pcm:存放通过snd_pcm_new创建的pcm设备;
- rtd:pcm runtime;
- num:设备号,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始;
- playback: 表示该pcm将会有几个playback substream;
- capture:表示该pcm将会有几个capture substream;
函数定义在sound/soc/soc-pcm.c,实际上该函数就是调用snd_pcm_new来完成pcm设备的创建;
static int soc_create_pcm(struct snd_pcm **pcm, struct snd_soc_pcm_runtime *rtd, int playback, int capture, int num) { char new_name[64]; int ret; /* create the PCM */ if (rtd->dai_link->params) { snprintf(new_name, sizeof(new_name), "codec2codec(%s)", rtd->dai_link->stream_name); ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, playback, capture, pcm); } else if (rtd->dai_link->no_pcm) { snprintf(new_name, sizeof(new_name), "(%s)", rtd->dai_link->stream_name); ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, playback, capture, pcm); } else { // 进入这个分支 if (rtd->dai_link->dynamic) snprintf(new_name, sizeof(new_name), "%s (*)", rtd->dai_link->stream_name); else snprintf(new_name, sizeof(new_name), "%s %s-%d", // 比如ff880000.i2s-rt5651-aif1 rt5651-aif1-0 rtd->dai_link->stream_name, // ff880000.i2s-rt5651-aif1 soc_codec_dai_name(rtd), num); // rt5651-aif1 0 ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, // 创建pcm设备 比new_name=“ff880000.i2s-rt5651-aif1 rt5651-aif1-0”,num=0 capture, pcm); } if (ret < 0) { dev_err(rtd->card->dev, "ASoC: can't create pcm %s for dailink %s: %d\n", new_name, rtd->dai_link->name, ret); return ret; } dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name); return 0; }
三、PCM设备文件
当我们移植外音频驱动之后,我们是可以在/dev/snd目录下看到pcm设备文件的,比如在NanoPC-T4开发板移植了ALC5651声卡驱动之后;
root@rk3399:~# ll /dev/snd drwxr-xr-x 2 root root 60 Aug 24 21:31 by-path/ crw-rw---- 1 root audio 116, 4 Aug 24 21:31 controlC0 crw-rw---- 1 root audio 116, 3 Aug 24 21:31 pcmC0D0c crw-rw---- 1 root audio 116, 2 Aug 24 21:31 pcmC0D0p crw-rw---- 1 root audio 116, 1 Aug 24 21:31 seq crw-rw---- 1 root audio 116, 33 Aug 24 21:31 timer
可以看到这些字符设备的主设备号都是116,其中:
-
controlC0:用于声卡的控制,例如通道选择,混音,麦克控制,音量加减,开关等;
-
pcmC0D0c:用于录音的pcm设备;
-
pcmC0D0p:用于播放的pcm设备;
-
-
timer:定时器;
C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback。
3.1 应用层打开PCM设备
当我们在应用成打开一个PCM设备,比如dev/snd/pcmC0D0p。对于字符设备,当我们进行open操作时,实际上会执行字符设备的文件操作集的open函数,也就是snd_pcm_f_ops[0]中的open函数,即snd_pcm_playback_open;
/* * Register section */ const struct file_operations snd_pcm_f_ops[2] = { { .owner = THIS_MODULE, .write = snd_pcm_write, .write_iter = snd_pcm_writev, .open = snd_pcm_playback_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_poll, .unlocked_ioctl = snd_pcm_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, }, { .owner = THIS_MODULE, .read = snd_pcm_read, .read_iter = snd_pcm_readv, .open = snd_pcm_capture_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_poll, .unlocked_ioctl = snd_pcm_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, } };
下面的时序图展示了应用程序如何最终调用到snd_pcm_f_ops中的回调函数:
下面我绘制了一张流程图更加形象的展示打开PCM设备的过程:
3.1.1 snd_pcm_playback_open
当打开音频文件时,会调用snd_pcm_playback_open函数,定义在sound/core/pcm_native.c;
static int snd_pcm_playback_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; int err = nonseekable_open(inode, file); if (err < 0) return err; pcm = snd_lookup_minor_data(iminor(inode), // 获取pcm实例 SNDRV_DEVICE_TYPE_PCM_PLAYBACK); err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); // 重点 if (pcm) snd_card_unref(pcm->card); return err; }
3.1.2 snd_pcm_open
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) { int err; wait_queue_entry_t wait; if (pcm == NULL) { err = -ENODEV; goto __error1; } err = snd_card_file_add(pcm->card, file); if (err < 0) goto __error1; if (!try_module_get(pcm->card->module)) { err = -EFAULT; goto __error2; } init_waitqueue_entry(&wait, current); // 初始化等待队列元素,并设置private为当前进程 add_wait_queue(&pcm->open_wait, &wait); // 向等待队列open_wait添加元素wait mutex_lock(&pcm->open_mutex); while (1) { err = snd_pcm_open_file(file, pcm, stream); if (err >= 0) break; if (err == -EAGAIN) { if (file->f_flags & O_NONBLOCK) { // 非堵塞 err = -EBUSY; break; } } else break; set_current_state(TASK_INTERRUPTIBLE); // 设置设置当前进程状态为TASK_INTERRUPTIBLE,在该状态下,进程如果休眠的话可以被信号和wake_up唤醒 mutex_unlock(&pcm->open_mutex); schedule(); // // 让出CPU,进程进入睡眠,那什么时候会唤醒该进程呢,一般会在xxxxx中调用wake_up_interruptible(&pcm->open_wait)唤醒该进程 mutex_lock(&pcm->open_mutex); if (pcm->card->shutdown) { err = -ENODEV; break; } if (signal_pending(current)) { // 如果有信号处理,跳出处理信号,然后便会进入对应的signal处理函数 err = -ERESTARTSYS; break; } } remove_wait_queue(&pcm->open_wait, &wait); // 从等待队列移除元素wait mutex_unlock(&pcm->open_mutex); if (err < 0) goto __error; return err; __error: module_put(pcm->card->module); __error2: snd_card_file_remove(pcm->card, file); __error1: return err; }
3.1.3 snd_pcm_open_file
snd_pcm_open_file函数实现如下:
static int snd_pcm_open_file(struct file *file, struct snd_pcm *pcm, int stream) { struct snd_pcm_file *pcm_file; struct snd_pcm_substream *substream; int err; err = snd_pcm_open_substream(pcm, stream, file, &substream); // 重点 if (err < 0) return err; pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); if (pcm_file == NULL) { snd_pcm_release_substream(substream); return -ENOMEM; } pcm_file->substream = substream; if (substream->ref_count == 1) substream->pcm_release = pcm_release_private; file->private_data = pcm_file; return 0; }
3.1.4 snd_pcm_open_substream
snd_pcm_open_substream函数实现如下:
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, struct file *file, struct snd_pcm_substream **rsubstream) { struct snd_pcm_substream *substream; int err; err = snd_pcm_attach_substream(pcm, stream, file, &substream); // 获取一个空闲的pcm substream(substream = pcm->streams[stream]->substream[n]) if (err < 0) return err; if (substream->ref_count > 1) { *rsubstream = substream; return 0; } err = snd_pcm_hw_constraints_init(substream); if (err < 0) { pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n"); goto error; } err = substream->ops->open(substream); // 重点 if (err < 0) goto error; substream->hw_opened = 1; err = snd_pcm_hw_constraints_complete(substream); if (err < 0) { pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n"); goto error; } /* automatically set EXPLICIT_SYNC flag in the managed mode whenever * the DMA buffer requires it */ if (substream->managed_buffer_alloc && substream->dma_buffer.dev.need_sync) substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC; *rsubstream = substream; return 0; error: snd_pcm_release_substream(substream); return err; }
pcm subustream的ops(即PCM操作集)在ASoC声卡注册的时候被初始为rtd->ops,其中rtd为pcm runtime;
rtd->ops.open = soc_pcm_open; rtd->ops.hw_params = soc_pcm_hw_params; rtd->ops.prepare = soc_pcm_prepare; rtd->ops.trigger = soc_pcm_trigger; rtd->ops.hw_free = soc_pcm_hw_free; rtd->ops.close = soc_pcm_close; rtd->ops.pointer = soc_pcm_pointer;
这些函数都定义在sound/soc/soc-pcm.c。
3.1.5 soc_pcm_open
/* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls * startup for the cpu DAI, component, machine and codec DAI. */ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream) { struct snd_soc_component *component; struct snd_soc_dai *dai; int i, ret = 0; snd_soc_dpcm_mutex_assert_held(rtd); for_each_rtd_components(rtd, i, component) pinctrl_pm_select_default_state(component->dev); ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream); if (ret < 0) goto err; ret = soc_pcm_components_open(substream); if (ret < 0) goto err; ret = snd_soc_link_startup(substream); // 执行rtd->dai_link->ops->startup(substream) if (ret < 0) goto err; /* startup the audio subsystem */ for_each_rtd_dais(rtd, i, dai) { // dai = (rtd)->dais[i] ret = snd_soc_dai_startup(dai, substream); // dai->driver->ops->startup(substream, dai) if (ret < 0) goto err; } /* Dynamic PCM DAI links compat checks use dynamic capabilities */ if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) goto dynamic; /* Check that the codec and cpu DAIs are compatible */ soc_pcm_init_runtime_hw(substream); soc_pcm_update_symmetry(substream); ret = soc_hw_sanity_check(substream); if (ret < 0) goto err; soc_pcm_apply_msb(substream); /* Symmetry only applies if we've already got an active stream. */ for_each_rtd_dais(rtd, i, dai) { ret = soc_pcm_apply_symmetry(substream, dai); if (ret != 0) goto err; } dynamic: snd_soc_runtime_activate(rtd, substream->stream); ret = 0; err: if (ret < 0) soc_pcm_clean(rtd, substream, 1); return soc_pcm_ret(rtd, ret); } /* PCM open ops for non-DPCM streams */ static int soc_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); int ret; snd_soc_dpcm_mutex_lock(rtd); ret = __soc_pcm_open(rtd, substream); snd_soc_dpcm_mutex_unlock(rtd); return ret; }
可以看到这里会执行音频数据链路dai_link操作集ops中的startup函数,对应上面代码snd_soc_link_startup(substream);
同时会执行音频数据链路dai driver操作集中的startup函数,对应上面代码snd_soc_dai_startup(dai, substream);也就是在这个阶段会回调cpu dai、codec dai驱动操作集ops中的startup函数。
3.2 应用层调用ioctl函数
当我们在应用程序中调用ioctl函数来进行一些音频参数配置,比如:
ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,...);
其中:
-
第一个参数为文件描述符;
-
第二个参数为命令码,应用程序通过下发命令码来控制驱动程序完成对应操作;
-
当应用程序调用ioctl函数时,会执行对应驱动程序文件操作集中的unlocked_ioctl、或者compat_ioctl;
long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args); long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);
-
unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数;
-
compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter;
3.2.1 snd_pcm_ioctl
这里我们以unlocked_ioctl为例,该函数被设置为了snd_pcm_ioctl,函数定义在sound/core/pcm_native.c;
static long snd_pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_pcm_file *pcm_file; pcm_file = file->private_data; if (((cmd >> 8) & 0xff) != 'A') return -ENOTTY; return snd_pcm_common_ioctl(file, pcm_file->substream, cmd, (void __user *)arg); }
3.2.2 snd_pcm_common_ioctl
snd_pcm_common_ioctl函数定义在sound/core/pcm_native.c,该函数实现了各种命令;
static int snd_pcm_common_ioctl(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg) { struct snd_pcm_file *pcm_file = file->private_data; int res; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; res = snd_power_wait(substream->pcm->card); if (res < 0) return res; switch (cmd) { case SNDRV_PCM_IOCTL_PVERSION: return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0; case SNDRV_PCM_IOCTL_INFO: return snd_pcm_info_user(substream, arg); case SNDRV_PCM_IOCTL_TSTAMP: /* just for compatibility */ return 0; case SNDRV_PCM_IOCTL_TTSTAMP: return snd_pcm_tstamp(substream, arg); case SNDRV_PCM_IOCTL_USER_PVERSION: if (get_user(pcm_file->user_pversion, (unsigned int __user *)arg)) return -EFAULT; return 0; case SNDRV_PCM_IOCTL_HW_REFINE: return snd_pcm_hw_refine_user(substream, arg); case SNDRV_PCM_IOCTL_HW_PARAMS: // 硬件参数配置 对ASoC的各个驱动模块做hw_params()动作 return snd_pcm_hw_params_user(substream, arg); // // 触发执行dai->driver->ops->hw_params()、rtd->dai_link->ops->hw_params() case SNDRV_PCM_IOCTL_HW_FREE: return snd_pcm_hw_free(substream); case SNDRV_PCM_IOCTL_SW_PARAMS: // 软件参数配置 return snd_pcm_sw_params_user(substream, arg); case SNDRV_PCM_IOCTL_STATUS32: return snd_pcm_status_user32(substream, arg, false); case SNDRV_PCM_IOCTL_STATUS_EXT32: return snd_pcm_status_user32(substream, arg, true); case SNDRV_PCM_IOCTL_STATUS64: return snd_pcm_status_user64(substream, arg, false); case SNDRV_PCM_IOCTL_STATUS_EXT64: return snd_pcm_status_user64(substream, arg, true); case SNDRV_PCM_IOCTL_CHANNEL_INFO: return snd_pcm_channel_info_user(substream, arg); case SNDRV_PCM_IOCTL_PREPARE: // 对ASoC的各个驱动模块做prepare()动作 return snd_pcm_prepare(substream, file); // 触发执行dai->driver->ops->prepare()、rtd->dai_link->ops->prepare() case SNDRV_PCM_IOCTL_RESET: return snd_pcm_reset(substream); case SNDRV_PCM_IOCTL_START: // 对ASoC的各个驱动模块做trigger()动作 return snd_pcm_start_lock_irq(substream); // 触发执行dai->driver->ops->trigger()、rtd->dai_link->ops->trigger() case SNDRV_PCM_IOCTL_LINK: return snd_pcm_link(substream, (int)(unsigned long) arg); case SNDRV_PCM_IOCTL_UNLINK: return snd_pcm_unlink(substream); case SNDRV_PCM_IOCTL_RESUME: return snd_pcm_resume(substream); case SNDRV_PCM_IOCTL_XRUN: return snd_pcm_xrun(substream); case SNDRV_PCM_IOCTL_HWSYNC: return snd_pcm_hwsync(substream); case SNDRV_PCM_IOCTL_DELAY: { snd_pcm_sframes_t delay = 0; snd_pcm_sframes_t __user *res = arg; int err; err = snd_pcm_delay(substream, &delay); if (err) return err; if (put_user(delay, res)) return -EFAULT; return 0; } case __SNDRV_PCM_IOCTL_SYNC_PTR32: return snd_pcm_ioctl_sync_ptr_compat(substream, arg); case __SNDRV_PCM_IOCTL_SYNC_PTR64: return snd_pcm_sync_ptr(substream, arg); #ifdef CONFIG_SND_SUPPORT_OLD_API case SNDRV_PCM_IOCTL_HW_REFINE_OLD: return snd_pcm_hw_refine_old_user(substream, arg); case SNDRV_PCM_IOCTL_HW_PARAMS_OLD: return snd_pcm_hw_params_old_user(substream, arg); #endif case SNDRV_PCM_IOCTL_DRAIN: return snd_pcm_drain(substream, file); case SNDRV_PCM_IOCTL_DROP: return snd_pcm_drop(substream); case SNDRV_PCM_IOCTL_PAUSE: return snd_pcm_pause_lock_irq(substream, (unsigned long)arg); case SNDRV_PCM_IOCTL_WRITEI_FRAMES: // 写 case SNDRV_PCM_IOCTL_READI_FRAMES: // 读 return snd_pcm_xferi_frames_ioctl(substream, arg); case SNDRV_PCM_IOCTL_WRITEN_FRAMES: case SNDRV_PCM_IOCTL_READN_FRAMES: return snd_pcm_xfern_frames_ioctl(substream, arg); case SNDRV_PCM_IOCTL_REWIND: return snd_pcm_rewind_ioctl(substream, arg); case SNDRV_PCM_IOCTL_FORWARD: return snd_pcm_forward_ioctl(substream, arg); } pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd); return -ENOTTY; }
这里我们仅仅关注SNDRV_PCM_IOCTL_HW_PARAMS命令,该命令的实现函数为snd_pcm_hw_params_user。
此外还有一些很重要的命令比如SNDRV_PCM_IOCTL_SW_PARAMS、SNDRV_PCM_IOCTL_PREPARE、SNDRV_PCM_IOCTL_START我们就不再分析了,感兴趣可以自行研究。
3.2.3 snd_pcm_hw_params_user
snd_pcm_hw_params_user函数定义在sound/core/pcm_native.c;
static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_hw_params __user * _params) { struct snd_pcm_hw_params *params; int err; params = memdup_user(_params, sizeof(*params)); if (IS_ERR(params)) return PTR_ERR(params); err = snd_pcm_hw_params(substream, params); // 重点 if (err < 0) goto end; if (copy_to_user(_params, params, sizeof(*params))) err = -EFAULT; end: kfree(params); return err; }
snd_pcm_hw_params函数定义在在sound/core/pcm_native.c;
static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime; int err, usecs; unsigned int bits; snd_pcm_uframes_t frames; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; snd_pcm_stream_lock_irq(substream); switch (runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: case SNDRV_PCM_STATE_PREPARED: if (!is_oss_stream(substream) && atomic_read(&substream->mmap_count)) err = -EBADFD; break; default: err = -EBADFD; break; } snd_pcm_stream_unlock_irq(substream); if (err) goto unlock; snd_pcm_sync_stop(substream, true); params->rmask = ~0U; err = snd_pcm_hw_refine(substream, params); if (err < 0) goto _error; err = snd_pcm_hw_params_choose(substream, params); if (err < 0) goto _error; err = fixup_unreferenced_params(substream, params); if (err < 0) goto _error; if (substream->managed_buffer_alloc) { err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); if (err < 0) goto _error; runtime->buffer_changed = err > 0; } if (substream->ops->hw_params != NULL) { err = substream->ops->hw_params(substream, params); // 重点,调用了soc_pcm_hw_params if (err < 0) goto _error; } runtime->access = params_access(params); runtime->format = params_format(params); runtime->subformat = params_subformat(params); runtime->channels = params_channels(params); runtime->rate = params_rate(params); runtime->period_size = params_period_size(params); runtime->periods = params_periods(params); runtime->buffer_size = params_buffer_size(params); runtime->info = params->info; runtime->rate_num = params->rate_num; runtime->rate_den = params->rate_den; runtime->no_period_wakeup = (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); bits = snd_pcm_format_physical_width(runtime->format); runtime->sample_bits = bits; bits *= runtime->channels; runtime->frame_bits = bits; frames = 1; while (bits % 8 != 0) { bits *= 2; frames *= 2; } runtime->byte_align = bits / 8; runtime->min_align = frames; /* Default sw params */ runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; runtime->period_step = 1; runtime->control->avail_min = runtime->period_size; runtime->start_threshold = 1; runtime->stop_threshold = runtime->buffer_size; runtime->silence_threshold = 0; runtime->silence_size = 0; runtime->boundary = runtime->buffer_size; while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) runtime->boundary *= 2; /* clear the buffer for avoiding possible kernel info leaks */ if (runtime->dma_area && !substream->ops->copy_user) { size_t size = runtime->dma_bytes; if (runtime->info & SNDRV_PCM_INFO_MMAP) size = PAGE_ALIGN(size); memset(runtime->dma_area, 0, size); } snd_pcm_timer_resolution_change(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); usecs = period_to_usecs(runtime); if (usecs >= 0) cpu_latency_qos_add_request(&substream->latency_pm_qos_req, usecs); err = 0; _error: if (err) { /* hardware might be unusable from this time, * so we force application to retry to set * the correct hardware parameter settings */ snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); if (substream->ops->hw_free != NULL) substream->ops->hw_free(substream); if (substream->managed_buffer_alloc) snd_pcm_lib_free_pages(substream); } unlock: snd_pcm_buffer_access_unlock(runtime); return err; }
pcm subustream的ops(即PCM操作集)在ASoC声卡注册的时候被初始为rtd->ops,其中rtd为pcm runtime;
rtd->ops.open = soc_pcm_open; rtd->ops.hw_params = soc_pcm_hw_params; rtd->ops.prepare = soc_pcm_prepare; rtd->ops.trigger = soc_pcm_trigger; rtd->ops.hw_free = soc_pcm_hw_free; rtd->ops.close = soc_pcm_close; rtd->ops.pointer = soc_pcm_pointer;
这些函数都定义在sound/soc/soc-pcm.c。
3.2.5 soc_pcm_hw_params
/* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers * (using snd_pcm_lib_* ). It's non-atomic. */ static int __soc_pcm_hw_params(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; int i, ret = 0; snd_soc_dpcm_mutex_assert_held(rtd); ret = soc_pcm_params_symmetry(substream, params); if (ret) goto out; ret = snd_soc_link_hw_params(substream, params); // 执行rtd->dai_link->ops->hw_params(substream,params) if (ret < 0) goto out; for_each_rtd_codec_dais(rtd, i, codec_dai) { // 遍历codec dai struct snd_pcm_hw_params codec_params; unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); /* * Skip CODECs which don't support the current stream type, * the idea being that if a CODEC is not used for the currently * set up transfer direction, it should not need to be * configured, especially since the configuration used might * not even be supported by that CODEC. There may be cases * however where a CODEC needs to be set up although it is * actually not being used for the transfer, e.g. if a * capture-only CODEC is acting as an LRCLK and/or BCLK master * for the DAI link including a playback-only CODEC. * If this becomes necessary, we will have to augment the * machine driver setup with information on how to act, so * we can do the right thing here. */ if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) continue; /* copy params for each codec */ codec_params = *params; /* fixup params based on TDM slot masks */ if (tdm_mask) soc_pcm_codec_params_fixup(&codec_params, tdm_mask); ret = snd_soc_dai_hw_params(codec_dai, substream, &codec_params); // codec_dai->driver->ops->hw_params(substream, params, codec_dai) if(ret < 0) goto out; soc_pcm_set_dai_params(codec_dai, &codec_params); snd_soc_dapm_update_dai(substream, &codec_params, codec_dai); } for_each_rtd_cpu_dais(rtd, i, cpu_dai) { // cpu dai /* * Skip CPUs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details */ if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream)) continue; ret = snd_soc_dai_hw_params(cpu_dai, substream, params); // cpu_dai->driver->ops->hw_params(substream, params, cpu_dai) if (ret < 0) goto out; /* store the parameters for each DAI */ soc_pcm_set_dai_params(cpu_dai, params); snd_soc_dapm_update_dai(substream, params, cpu_dai); } ret = snd_soc_pcm_component_hw_params(substream, params); out: if (ret < 0) soc_pcm_hw_clean(rtd, substream, 1); return soc_pcm_ret(rtd, ret); } /* hw_params PCM ops for non-DPCM streams */ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); int ret; snd_soc_dpcm_mutex_lock(rtd); ret = __soc_pcm_hw_params(rtd, substream, params); snd_soc_dpcm_mutex_unlock(rtd); return ret; }
可以看到这里会执行音频数据链路dai_link操作集ops中的hw_params函数,对应上面代码snd_soc_link_hw_params(substream, params);
同时会执行音频数据链路dai driver操作集中的hw_params函数,对应上面代码snd_soc_dai_hw_params(dai, substream,params);也就是在这个阶段会回调cpu dai、codec dai驱动操作集ops中的hw_params函数。
3.2.6 总结
经过一遍分析,我们大概可以得到ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params)函数调用栈;
ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params); snd_pcm_ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params); snd_pcm_hw_params_user(substream, params) snd_pcm_hw_params(substream, params) substream->ops->hw_params(substream, params) soc_pcm_hw_params(substream, params) rtd->dai_link->ops->hw_params(substream, params) codec_dai->driver->ops->hw_params(dai, substream,params) cpu_dai->driver->ops->hw_params(dai, substream,params)
参考文章
[3] Linux音频子系统
[4] http://www.alsa-project.org/。
[6] Linux内核中的链表——struct list_head
[8] Sound Subsystem Documentation
[9] alsa pcm函数调用流程
[10] Linux ALSA声卡驱动之三:PCM设备的创建
[11]