----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板eMMC :16GBLPDDR3 :4GB显示屏 :15.6英寸HDMI接口显示屏u-boot :2023.04linux :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 */
};
该结构体定义了一组成员变量,用于存储和管理音频硬件参数的信息。下面是各成员