1.PCM
PCM(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。
与音频采样相关的名词:
PCM两个重要属性:
人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。
2.PCM中间层
ALSA已经实现了功能强劲的PCM中间层,自己的驱动中只要实现一些底层的需要访问硬件的函数即可。
要访问PCM的中间层代码,首先要包含头文件
每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了。
一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
在嵌入式系统中,大多数情况下是一个声卡,一个pcm实例,pcm下面有一个playback和capture stream,playback和capture下面各自有一个substream。
一个pcm实例(例如pcm0)是card下的一个逻辑设备, 这个逻辑设备会在用户空间创建两个设备节点.
一个pcm实例包含两个stream : playback & capture. 每个stream对应一个设备节点.
每个stream下可包含多个substream.
在内核层, 每个substream都有一块自己的Buffer来与用户空间交换音频数据. 从这个角度来看, substream存在的意义貌似是为了分时复用底层的音频硬件.
在用户空间, 每个设备节点可以被open多次, 每次open内核层都会找到一个空闲的substream与之对应, 如果内核层的substream被用完了, 则此次open操作会失败. 这样看来, 用户空间的读、写、控制操作都是针对substream进行的, 这也进一步说明substream可以用来分时复用底层音频硬件.
3.1.struct snd_pcm (代表一个pcm实例, 也是代表一个pcm逻辑设备)
在ALSA架构下,pcm也被称为设备,所谓的逻辑设备。在linux系统中使用snd_pcm结构表示一个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]; //指向它下属的playback stream和capture stream.
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
}
3.2.struct snd_pcm_str (代表一个pcm stream)
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;
struct snd_info_entry *proc_info_entry;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
unsigned int xrun_debug; /* 0 = disabled, 1 = verbose, 2 = stacktrace */
struct snd_info_entry *proc_xrun_debug_entry;
#endif
#endif
struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
struct device dev;
};
3.3.struct snd_pcm_file
struct snd_pcm_file {
struct snd_pcm_substream *substream;
int no_compat_mmap;
unsigned int user_pversion; /* supported protocol version */
};
3.4.struct 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 */
/* -- 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;
#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;
struct snd_info_entry *proc_info_entry;
struct snd_info_entry *proc_hw_params_entry;
struct snd_info_entry *proc_sw_params_entry;
struct snd_info_entry *proc_status_entry;
struct snd_info_entry *proc_prealloc_entry;
struct snd_info_entry *proc_prealloc_max_entry;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
struct snd_info_entry *proc_xrun_injection_entry;
#endif
#endif /* CONFIG_SND_VERBOSE_PROCFS */
/* misc flags */
unsigned int hw_opened: 1;
};
3.5.struct snd_dma_buffer
struct snd_dma_buffer {
struct snd_dma_device dev; /* device type */
unsigned char *area; /* virtual pointer */
dma_addr_t addr; /* physical address */
size_t bytes; /* buffer size in bytes */
void *private_data; /* private for allocator; don't touch */
};
3.6.struct snd_pcm_ops
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream,
unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*get_time_info)(struct snd_pcm_substream *substream,
struct timespec *system_ts, struct timespec *audio_ts,
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
int (*fill_silence)(struct snd_pcm_substream *substream, int channel,
unsigned long pos, unsigned long bytes);
int (*copy_user)(struct snd_pcm_substream *substream, int channel,
unsigned long pos, void __user *buf,
unsigned long bytes);
int (*copy_kernel)(struct snd_pcm_substream *substream, int channel,
unsigned long pos, void *buf, unsigned long bytes);
struct page *(*page)(struct snd_pcm_substream *substream,
unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
3.7.snd_pcm_new
int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm rpcm);
- 参数device 表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始.
- 参数playback_count 表示该pcm将会有几个playback substream.
- 参数capture_count 表示该pcm将会有几个capture substream.
函数实现:
请注意, 在card被注册之前, 我们需要调用snd_pcm_set_ops为此PCM实例设置回调函数, 因为当用户空间通过设备节点与PCM中间层交互时, PCM中间层需要回调底层驱动实现的ops函数.
3.8.设置pcm操作函数接口
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);
3.9.PCM字符设备的创建
当card被注册时, 会扫描下属的每个逻辑设备并注册它们, 这里创建的PCM逻辑设备也会在那时进行注册. 当PCM逻辑设备被注册时, ALSA系统层会回调逻辑设备的snd_device_ops. dev_register函数, 也就是snd_pcm_dev_register. 在该回调函数中, 会针对每一个stream调用snd_register_device, 进而在用户空间创建对应的设备节点.
PCM中间层的snd_pcm_f_ops会负责与用户空间交互, 其主要功能包括:
4.pcm设备创建完成逻辑图
大体上就是一棵树,根节点是card0, 然后子节点是pcm设备,pcm设备分为capture & playback stream, 然后在stream下又分为substrem。