pcm设备的注册流程

相关概念

  1. 什么是pcm? pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

  2. 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;
        
        ...
}

在该函数里执行了如下操作:

  1. 确认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;
    }

  2. 创建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;
}

可以看到这里主要是做了以下工作:

  1. 初始化pcm里面的open_wait队列

  2. 设置pcm->id

  3. 创建两个新的pcm-stream --- playback和capture

  4. 创建新的设备,并根据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这两个成员

  5. 最后就是将pcm设备加入到card

  6. 电源管理的相关设置

  7. 处理pcm没有host IO的情况

  8. 设置dai的操作函数

  9. 调用pcm-component的pcm_new()函数

补充

ASoC-codec层已经注册了一个component,它的driver就是和pcm相关的操作函数,所以在注册声卡的时候,显示创建pcm,然后对他进行初始化的时候会调用前面提到的pcm的操作函数,最后完成pcm的注册

猜你喜欢

转载自blog.csdn.net/hhx123456798/article/details/123646157