相关概念
-
什么是pcm? pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。
-
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];
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
};
.card:此pcm设备所属card
.list:用与连接所有pcm设备,最终所有pcm设备会被放到snd_pcm_devices链表中
.device:该pcm的索引号
.id:该pcm的标识
.streams:指向pcm的capture和playback stream,通常0代表playback,1代表capture
通常一个pcm下会有两个stream,为playback和capture,在每个stram下又会存在多个substream。linux中用snd_pcm_str定义stream,使用snd_pcm_substream定义substream
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;
};
.stream:当前stream的方向,playback或capture
.pcm:所属的pcm
.substream_count:该stream下substream的个数
.substream_opened:该stream下open的substream的个数
.substream:该stream下的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;
/* misc flags */
unsigned int hw_opened: 1;
};
.pcm:所属pcm
.pstr:所属stream
.id:序号,代表该stream下第几个substream
.name:名字
.ops:硬件操作函数集合
.runtime:运行时的pcm的一些信息
.next:用于连接下一个substream
这几个结构体的关系如下:
创建、注册流程
声卡注册的过程中,会调用soc_new_pcm()来创建PCM设备
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_rtdcom_list *rtdcom;
struct snd_pcm *pcm;
struct snd_pcm_str *stream;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
int i;
...
}
在该函数里执行了如下操作:
-
确认PCM通道数
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else { /* Adapt stream for codec2codec links */ struct snd_soc_pcm_stream *cpu_capture = rtd->dai_link->params ? &cpu_dai->driver->playback : &cpu_dai->driver->capture; struct snd_soc_pcm_stream *cpu_playback = rtd->dai_link->params ? &cpu_dai->driver->capture : &cpu_dai->driver->playback; for_each_rtd_codec_dai(rtd, i, codec_dai) { if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) && snd_soc_dai_stream_valid(cpu_dai, SNDRV_PCM_STREAM_PLAYBACK)) playback = 1; if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_CAPTURE) && snd_soc_dai_stream_valid(cpu_dai, SNDRV_PCM_STREAM_CAPTURE)) capture = 1; } capture = capture && cpu_capture->channels_min; playback = playback && cpu_playback->channels_min; } if (rtd->dai_link->playback_only) { playback = 1; capture = 0; } if (rtd->dai_link->capture_only) { playback = 0; capture = 1; }
-
创建PCM设备
/* 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", rtd->dai_link->stream_name, (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, num); ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm); }
其中 snd_pcm_new_internal() 和 snd_pcm_new() 都是调用了 _snd_pcm_new(),只是有个传参不一样 来看看这个函数:
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,
};
static 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);
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)
strlcpy(pcm->id, id, sizeof(pcm->id));
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;
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;
}
可以看到这里主要是做了以下工作:
-
初始化pcm里面的open_wait队列
-
设置pcm->id
-
创建两个新的pcm-stream --- playback和capture
-
创建新的设备,并根据internal来传入不同的ops
来看看函数: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; ... }
这个函数主要就是创建并初始化snd_pcm_str和snd_pcm_substream这两个成员
-
最后就是将pcm设备加入到card
-
电源管理的相关设置
-
处理pcm没有host IO的情况
-
设置dai的操作函数
-
调用pcm-component的pcm_new()函数
补充
ASoC-codec层已经注册了一个component,它的driver就是和pcm相关的操作函数,所以在注册声卡的时候,显示创建pcm,然后对他进行初始化的时候会调用前面提到的pcm的操作函数,最后完成pcm的注册