蓝牙语音功能的实现

蓝牙语音功能的实现

要实现蓝牙能够打skype电话,或蓝牙录音等功能,从下到上,需要做如下的修改:

audio部分:

驱动层,需要实现audio pcm的驱动。

hal层,需要添加蓝牙sco音频的通路支持。

蓝牙部分:

使用的蓝牙芯片的pcm接口连接到ap的pcm接口(用于传送音频数据),不是走的uart口传送音频数据。

使用的handfree/handset的profile,而不是a2dp profile

pcm部分,ap的pcm controller做主,蓝牙芯片做从。参数是:采样率8Khz,16bit,长/短同步,单声道


系统约束:

audioflinger目前是将系统所有声源的采样率都统一到48Khz,而我们蓝牙语音只需要8khz,所以需要有个resample的过程。

audioflinger下来的声音都是双通道的,而蓝牙芯片却只需要单声道,所以需要去掉一个通道。

先resample然后再去掉一个通道的数据,顺序不能搞反。

pcm驱动部分:

pcm驱动是属于音频驱动,遵循Linux alsa驱动架构

linux alsa的驱动架构,粗略的讲,是有如下几个部分构成:

codec------------>snd_soc_codec----->snd_soc_codec_driver

platform--------->snd_soc_platform-->snd_soc_platform_driver-->snd_pcm_ops

cpu dai驱动---->snd_soc_dai--------->snd_soc_dai_driver---->snd_soc_dai_ops

codec dai驱动->snd_soc_dai--------->snd_soc_dai_drive----->snd_soc_dai_ops

而以上部分的驱动,则是通过如下Audio machine driver来关联起来的。

machine--------->struct snd_soc_card------------>snd_soc_dai_link

即是通过snd_soc_dai_link结构来关联codec,platform,dai等之间的关系的。详情见snd_soc_dai_link结构内容:

  1. struct snd_soc_dai_link {  
  2.     /* config - must be set by machine driver */  
  3.     const char *name;           /* Codec name */  
  4.     const char *stream_name;        /* Stream name */  
  5.     const char *codec_name;     /* for multi-codec */  
  6.     const struct device_node *codec_of_node;  
  7.     const char *platform_name;  /* for multi-platform */  
  8.     const struct device_node *platform_of_node;  
  9.     const char *cpu_dai_name;  
  10.     const struct device_node *cpu_dai_of_node;  
  11.     const char *codec_dai_name;  
  12.   
  13.     unsigned int dai_fmt;           /* format to set on init */  
  14.   
  15.     /* Keep DAI active over suspend */  
  16.     unsigned int ignore_suspend:1;  
  17.   
  18.     /* Symmetry requirements */  
  19.     unsigned int symmetric_rates:1;  
  20.   
  21.     /* pmdown_time is ignored at stop */  
  22.     unsigned int ignore_pmdown_time:1;  
  23.   
  24.     /* codec/machine specific init - e.g. add machine controls */  
  25.     int (*init)(struct snd_soc_pcm_runtime *rtd);  
  26.   
  27.     /* machine stream operations */  
  28.     struct snd_soc_ops *ops;  
  29. };  
struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	const char *codec_name;		/* for multi-codec */
	const struct device_node *codec_of_node;
	const char *platform_name;	/* for multi-platform */
	const struct device_node *platform_of_node;
	const char *cpu_dai_name;
	const struct device_node *cpu_dai_of_node;
	const char *codec_dai_name;

	unsigned int dai_fmt;           /* format to set on init */

	/* Keep DAI active over suspend */
	unsigned int ignore_suspend:1;

	/* Symmetry requirements */
	unsigned int symmetric_rates:1;

	/* pmdown_time is ignored at stop */
	unsigned int ignore_pmdown_time:1;

	/* codec/machine specific init - e.g. add machine controls */
	int (*init)(struct snd_soc_pcm_runtime *rtd);

	/* machine stream operations */
	struct snd_soc_ops *ops;
};
通过如下的结构体定义,可以看出snd_soc_dai_link通过名字来指定了当前声卡使用的codec,platform,cpu dai,codec dai等结构。

提供该结构体的一个实例:

  1. /* Digital audio interface glue - connects codec <--> CPU */  
  2. static struct snd_soc_dai_link wmt_dai[] = {  
  3.     {  
  4.         .name = "HiFi",  
  5.         .stream_name = "HiFi",  
  6.         .platform_name = "wmt-audio-pcm.0",  
  7.         .init = wmt_soc_dai_init,  
  8.         .ops = &wmt_soc_primary_ops,  
  9.     },  
  10.     {  
  11.         .name = "Voice",  
  12.         .stream_name = "Voice",  
  13.         .platform_name = "wmt-pcm-dma.0",  
  14.         .cpu_dai_name = "wmt-pcm-controller.0",  
  15.         .codec_dai_name = "HWDAC",  
  16.         .codec_name = "wmt-i2s-hwdac.0",  
  17.         .ops = &wmt_soc_second_ops,  
  18.     },  
  19. };  
/* Digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link wmt_dai[] = {
	{
		.name = "HiFi",
		.stream_name = "HiFi",
		.platform_name = "wmt-audio-pcm.0",
		.init = wmt_soc_dai_init,
		.ops = &wmt_soc_primary_ops,
	},
	{
		.name = "Voice",
		.stream_name = "Voice",
		.platform_name = "wmt-pcm-dma.0",
		.cpu_dai_name = "wmt-pcm-controller.0",
		.codec_dai_name = "HWDAC",
		.codec_name = "wmt-i2s-hwdac.0",
		.ops = &wmt_soc_second_ops,
	},
};
我们蓝牙pcm语音,是使用的第二组snd_soc_dai_link描述。该描述指定了我们是使用名为:wmt-pcm-dma.0的平台驱动;还有名为wmt-pcm-controller.0的cpu dai驱动;还有名为HWDAC的codec dai驱动;还有名为wmt-i2s-hwdac.0的codec驱动。

而以上驱动分别通过如下函数来注册到系统中去的:

snd_soc_codec_driver------------->snd_soc_register_codec----------->:codec_list

snd_soc_platform_driver---------->snd_soc_register_platform-------->:platform_list

snd_soc_dai_driver---------------->snd_soc_register_dais-------------->:dai_list

而以上驱动又是在什么时候注册到系统中去的呢?

概略的将,他们都是在系统初始化阶段,通过平台设备跟平台驱动匹配时,在平台驱动的probe函数中注册进去的。由于这些不是我们要讲的重点,所以略过去。


在这里再稍微描述下,这里指的platform一般就是指的dma,而dai字面意思就是数字音频接口,目前主要是i2s和pcm接口,而对应的cpu dai drvier一般就是指的ap端的i2s和pcm控制器驱动。在当前实例中,就是pcm接口;由于蓝牙的pcm和cpu的pcm是直接相连的,中间没经过codec,所以更本就不需要codec的参与,所以这里做了一个功能是dummy的虚拟codec driver及codec dai。


音频系统的初始化过程

音频模块的初始化始于如下函数:snd_soc_register_card(struct snd_soc_card *card)

而该函数的主要初始化工作都是在 snd_soc_instantiate_cards();该函数主要完成如下的工作:

a:soc_bind_dai_link

通过card->dai_link[num];即struct snd_soc_dai_link *dai_link 中指定的platform,codec,cpu dai,codec dai的名字,来找到对应的平台驱动,codec驱动,cpu dai驱动和codec dai驱动。并将他们分别赋值给(snd_soc_pcm_runtime*)rtd->cpu_dai,rtd->codec,rtd->codec_dai,rtd->platform


b: snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,

card->owner, 0, &card->snd_card);


注意struct snd_soc_card与struct snd_card之间的关系,snd_soc_card->snd_card 即为struct snd_card结构。

并且用于存放snd_card_create函数创建和初始化的声卡结构。snd_soc_card用于描述Audio machine driver,而snd_card用于描述一个声卡对象,一个声卡对象包含多个声卡设备(struct snd_device),其中必须包含了一个控制设备,在本例中还包含一个pcm设备。


声卡的控制设备创建api为:snd_ctl_create;pcm设备创建api为:snd_pcm_new,其实他们最终都是调用的snd_device_new函数,该函数的作用就是分配snd_device所需的内存,并初始化他,最后将该snd_device添加到声卡对象snd_card所包含的设备列表中(->devices).


而以上创建的声卡设备,在系统的声卡对象注册时:snd_card_register会遍历该声卡对象(snd_card)所属的所有声卡设备(snd_device),对每个声卡设备,调用snd_register_device_for_dev函数实现声卡设备的注册。

c:snd_soc_dapm_new_controls

创建机器struct snd_soc_card所包含的所有dapm控件:structsnd_soc_card*card->dapm_widgets,该函数主要作用是:为控件dapm_widget分配所需内存并初始化它,设置控件的callback函数,一般为power_check,设置控件为连接状态(->connected = 1),并将该dapm_widget添加到snd_soc_card的控件列表中.控件是一个很重要的概念,在这里做下展开描述,先上结构体:

  1. struct snd_soc_dapm_widget {  
  2.     enum snd_soc_dapm_type id;  
  3.     const char *name;       /* widget name */  
  4.     const char *sname;  /* stream name */  
  5.     struct snd_soc_codec *codec;  
  6.     struct snd_soc_platform *platform;  
  7.     struct list_head list;  
  8.     struct snd_soc_dapm_context *dapm;  
  9.   
  10.     void *priv;             /* widget specific data */  
  11.   
  12.     /* dapm control */  
  13.     short reg;                      /* negative reg = no direct dapm */  
  14.     unsigned char shift;            /* bits to shift */  
  15.     unsigned int saved_value;       /* widget saved value */  
  16.     unsigned int value;             /* widget current value */  
  17.     unsigned int mask;          /* non-shifted mask */  
  18.     unsigned int on_val;            /* on state value */  
  19.     unsigned int off_val;           /* off state value */  
  20.     unsigned char power:1;          /* block power status */  
  21.     unsigned char invert:1;         /* invert the power bit */  
  22.     unsigned char active:1;         /* active stream on DAC, ADC's */  
  23.     unsigned char connected:1;      /* connected codec pin */  
  24.     unsigned char new:1;            /* cnew complete */  
  25.     unsigned char ext:1;            /* has external widgets */  
  26.     unsigned char force:1;          /* force state */  
  27.     unsigned char ignore_suspend:1;         /* kept enabled over suspend */  
  28.     unsigned char new_power:1;      /* power from this run */  
  29.     unsigned char power_checked:1;      /* power checked this run */  
  30.     int subseq;             /* sort within widget type */  
  31.   
  32.     int (*power_check)(struct snd_soc_dapm_widget *w);  
  33.   
  34.     /* external events */  
  35.     unsigned short event_flags;     /* flags to specify event types */  
  36.     int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);  
  37.   
  38.     /* kcontrols that relate to this widget */  
  39.     int num_kcontrols;  
  40.     const struct snd_kcontrol_new *kcontrol_news;  
  41.     struct snd_kcontrol **kcontrols;  
  42.   
  43.     /* widget input and outputs */  
  44.     struct list_head sources;  
  45.     struct list_head sinks;  
  46.   
  47.     /* used during DAPM updates */  
  48.     struct list_head power_list;  
  49.     struct list_head dirty;  
  50.     int inputs;  
  51.     int outputs;  
  52. };  
struct snd_soc_dapm_widget {
	enum snd_soc_dapm_type id;
	const char *name;		/* widget name */
	const char *sname;	/* stream name */
	struct snd_soc_codec *codec;
	struct snd_soc_platform *platform;
	struct list_head list;
	struct snd_soc_dapm_context *dapm;

	void *priv;				/* widget specific data */

	/* dapm control */
	short reg;						/* negative reg = no direct dapm */
	unsigned char shift;			/* bits to shift */
	unsigned int saved_value;		/* widget saved value */
	unsigned int value;				/* widget current value */
	unsigned int mask;			/* non-shifted mask */
	unsigned int on_val;			/* on state value */
	unsigned int off_val;			/* off state value */
	unsigned char power:1;			/* block power status */
	unsigned char invert:1;			/* invert the power bit */
	unsigned char active:1;			/* active stream on DAC, ADC's */
	unsigned char connected:1;		/* connected codec pin */
	unsigned char new:1;			/* cnew complete */
	unsigned char ext:1;			/* has external widgets */
	unsigned char force:1;			/* force state */
	unsigned char ignore_suspend:1;         /* kept enabled over suspend */
	unsigned char new_power:1;		/* power from this run */
	unsigned char power_checked:1;		/* power checked this run */
	int subseq;				/* sort within widget type */

	int (*power_check)(struct snd_soc_dapm_widget *w);

	/* external events */
	unsigned short event_flags;		/* flags to specify event types */
	int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);

	/* kcontrols that relate to this widget */
	int num_kcontrols;
	const struct snd_kcontrol_new *kcontrol_news;
	struct snd_kcontrol **kcontrols;

	/* widget input and outputs */
	struct list_head sources;
	struct list_head sinks;

	/* used during DAPM updates */
	struct list_head power_list;
	struct list_head dirty;
	int inputs;
	int outputs;
};
我们看到,该结构有几个很重要的成员:一个是控件对应的kcontrols,目前在这个函数中,还未被创建,并且w->power字段也未设置,不急,后面会讲到。需要强调的是:定义一个 widget ,我们需要指定两个很重要的内容:

1: 一个是用于控制widget本身的电源状态的reg/shift等寄存器信息,

2: 另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式

上面的item 1提到的reg/shift等寄存器信息就是struct snd_soc_dapm_widget控件中的reg/shift等成员;而item 2提到的reg/shift则是kcontrols中的(struct soc_mixer_control*)private_data中的reg/shift值。


值得说明的是,除了机器驱动snd_soc_card可以有自己的dapm widget,普通kcontrol和dapms routes外,codec driver(snd_soc_codec_driver),platform driver(snd_soc_platform_driver)都会有自己的dapm widget,普通kcontrol和dapms routes,并且他们的控件和路由信息的注册分别在soc_probe_dai_link中的soc_probe_codec,soc_probe_platform函数中进行。


d:soc_probe_dai_link

由于之前的soc_bind_dai_link函数中,已经找到了对应的平台驱动,codec驱动,cpu dai驱动和codec dai驱动,在这里,是分别调用这些驱动的probe函数。顺序是: probe the cpu_dai ,probe the CODEC(soc_probe_codec),probe the platform(soc_probe_platform),probe the CODEC DAI ,最后调用soc_new_pcm创建pcm声卡设备(即/dev/snd目录下的pcm设备节点),该实例中的pcm驱动对应的设备节点为:/dev/snd/pcmC0D1c,/dev/snd/pcmC0D1p,前者为录音(capture),后者为放音(play)


e:snd_soc_dapm_link_dai_widgets(card);

在soc_probe_codec函数中调用snd_soc_dapm_new_dai_widgets函数创建特殊的dai类型的widgets控件。而在这里通过snd_soc_dapm_link_dai_widgets则是连接这些dai widget

f:snd_soc_add_card_controls

创建(struct snd_soc_card *)card->controls所包含的普通控件,并添加到struct snd_card *card->controls控件列表中

 

g:snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,const struct snd_soc_dapm_route *route)

创建并添加(struct snd_soc_card *)card->dapm_routes所包含的路由信息。该函数根据 struct snd_soc_dapm_route参数:

  1. /* 
  2.  * DAPM audio route definition. 
  3.  * 
  4.  * Defines an audio route originating at source via control and finishing 
  5.  * at sink. 
  6.  */  
  7. struct snd_soc_dapm_route {  
  8.     const char *sink;  
  9.     const char *control;  
  10.     const char *source;  
  11.   
  12.     /* Note: currently only supported for links where source is a supply */  
  13.     int (*connected)(struct snd_soc_dapm_widget *source,  
  14.              struct snd_soc_dapm_widget *sink);  
  15. };  
/*
 * DAPM audio route definition.
 *
 * Defines an audio route originating at source via control and finishing
 * at sink.
 */
struct snd_soc_dapm_route {
	const char *sink;
	const char *control;
	const char *source;

	/* Note: currently only supported for links where source is a supply */
	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink);
};
提供的sink,source,control等名字信息,在(struct snd_soc_card *)card->widgets的控件列表中,分别找到源控件,目的控件,和path所对应的kcontrol控件。分配并初始化struct snd_soc_dapm_path *path结构所需的内存空间,并根据控件id的不同,调用如下函数之一:

source dapm_connect_mux

dapm_connect_mixer

来将dapm控件和path进行关联起来,并设置path的connect状态。

dapm mux控件和dapm mixer控件为什么要分开来处理,因为dapm mixer控件会有多个Kcontrol,而dapm mux控件只会有一个控件。

下面展开dapm_connect_mixer函数:

  1. /* connect mixer widget to its interconnecting audio paths */  
  2. static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,  
  3.     struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
  4.     struct snd_soc_dapm_path *path, const char *control_name)//control_name是path<span style="font-family: Arial, Helvetica, sans-serif;">对应的kcontrol的名称</span>  
  5. {  
  6.     int i;   //不同的kcontrol对应不同的path  
  7.   
  8.     /* search for mixer kcontrol */  
  9.     for (i = 0; i < dest->num_kcontrols; i++) {//在path对应的目的控件中搜索所有的kcontrol,找到name指定的那个kcontrol  
  10.         if (!strcmp(control_name, dest->kcontrol_news[i].name)) {  
  11.             list_add(&path->list, &dapm->card->paths);//将path添加到<span style="font-size:18px;">(</span><span style="font-size:18px;">struct snd_soc_card *</span><span style="font-size:18px;">)</span><span style="font-size:18px;">card->path列表中</span>  
  12.             list_add(&path->list_sink, &dest->sources);//将path添加到目的控件的sources列表中  
  13.             list_add(&path->list_source, &src->sinks);//<span style="font-family: Arial, Helvetica, sans-serif;">将</span><span style="font-family: Arial, Helvetica, sans-serif;">patch添加到源控件sinks列表中</span>  
  14.             path->name = dest->kcontrol_news[i].name;//将找到的那个控件的名字赋值给path->name,但并未给path->kcontrol赋值,因为kcontrol 可能还未创建  
  15.             dapm_set_path_status(dest, path, i);//更新 path->connect 的链接状态  
  16.             return 0;  
  17.         }  
  18.     }  
  19.     return -ENODEV;  
  20. }  
/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
	struct snd_soc_dapm_path *path, const char *control_name)//control_name是path对应的kcontrol的名称
{
	int i;   //不同的kcontrol对应不同的path

	/* search for mixer kcontrol */
	for (i = 0; i < dest->num_kcontrols; i++) {//在path对应的目的控件中搜索所有的kcontrol,找到name指定的那个kcontrol
		if (!strcmp(control_name, dest->kcontrol_news[i].name)) {
			list_add(&path->list, &dapm->card->paths);//将path添加到(struct snd_soc_card *)card->path列表中
			list_add(&path->list_sink, &dest->sources);//将path添加到目的控件的sources列表中
			list_add(&path->list_source, &src->sinks);//patch添加到源控件sinks列表中
			path->name = dest->kcontrol_news[i].name;//将找到的那个控件的名字赋值给path->name,但并未给path->kcontrol赋值,因为kcontrol 可能还未创建
			dapm_set_path_status(dest, path, i);//更新 path->connect 的链接状态
			return 0;
		}
	}
	return -ENODEV;
}
下面展开 dapm_connect_mux函数:
  1. /* connect mux widget to its interconnecting audio paths */  
  2. static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,  
  3.     struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
  4.     struct snd_soc_dapm_path *path, const char *control_name,//注意此处control_name并不是kcontrol的名字,而是mux控件中的枚举值  
  5.     const struct snd_kcontrol_new *kcontrol)//<span style="font-family:Arial, Helvetica, sans-serif;">该kcontrol即为该mux控件唯一的kcontrol</span>  
  6. {  
  7.     struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;//不同的枚举值对应不同的path  
  8.     int i;  
  9.   
  10.     for (i = 0; i < e->max; i++) {  
  11.         if (!(strcmp(control_name, e->texts[i]))) {  
  12.             list_add(&path->list, &dapm->card->paths);  
  13.             list_add(&path->list_sink, &dest->sources);  
  14.             list_add(&path->list_source, &src->sinks);  
  15.             path->name = (char*)e->texts[i];//  
  16.             dapm_set_path_status(dest, path, 0);  
  17.             return 0;  
  18.         }  
  19.     }  
  20.   
  21.     return -ENODEV;  
  22. }  
/* connect mux widget to its interconnecting audio paths */
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
	struct snd_soc_dapm_path *path, const char *control_name,//注意此处control_name并不是kcontrol的名字,而是mux控件中的枚举值
	const struct snd_kcontrol_new *kcontrol)//该kcontrol即为该mux控件唯一的kcontrol
{
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;//不同的枚举值对应不同的path
	int i;

	for (i = 0; i < e->max; i++) {
		if (!(strcmp(control_name, e->texts[i]))) {
			list_add(&path->list, &dapm->card->paths);
			list_add(&path->list_sink, &dest->sources);
			list_add(&path->list_source, &src->sinks);
			path->name = (char*)e->texts[i];//
			dapm_set_path_status(dest, path, 0);
			return 0;
		}
	}

	return -ENODEV;
}

h:snd_soc_dapm_new_widgets

遍历(struct snd_soc_card *)card->widgets列表中所有dapm控件,为每个控件做如下的事情:

1:struct snd_soc_dapm_widget *w;为widget中的所有kcntrols分配内存空间(w->kcontrols),并做如下事情:

初始化path->long_name

path->kcontrol = snd_soc_cnew(...)//创建kcontrol,并返回该kcontrol

snd_ctl_add(card, path->kcontrol);//添加该控件到系统中

w->kcontrols[i] = path->kcontrol;//用新创建的kcontrol设置widget和path对应的kcontrol


2:更新dapm的电源状态:w->power,并标记已经创建了该dapm widget

  1. /* Read the initial power state from the device */  
  2. if (w->reg >= 0) {  
  3.     val = soc_widget_read(w, w->reg);  
  4.     val &= 1 << w->shift;  
  5.     if (w->invert)  
  6.         val = !val;  
  7.   
  8.     if (val)  
  9.         w->power = 1;//<span style="font-size: 18.18181800842285px;">更新dapm的电源状态</span>  
  10. }  
  11.   
  12. w->new = 1;//<span style="font-size: 18.18181800842285px;">标记已经创建了该dapm widget</span>  
		/* Read the initial power state from the device */
		if (w->reg >= 0) {
			val = soc_widget_read(w, w->reg);
			val &= 1 << w->shift;
			if (w->invert)
				val = !val;

			if (val)
				w->power = 1;//更新dapm的电源状态
		}

		w->new = 1;//标记已经创建了该dapm widget

3:将该dapm widget标记为dirty,将他添加到dirty list中,然后通过dapm_power_widgets函数作随后的顺序上下电操作


  1.     dapm_mark_dirty(w, "new widget");  
  2.     dapm_debugfs_add_widget(w);  
  3. }  
  4.   
  5. dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);  
  6. mutex_unlock(&dapm->card->dapm_mutex);  
		dapm_mark_dirty(w, "new widget");
		dapm_debugfs_add_widget(w);
	}

	dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);
	mutex_unlock(&dapm->card->dapm_mutex);


i:snd_card_register(struct snd_card *card)

该函数做如下事情:

1:通过snd_device_register_all函数,注册所有属于该声卡对象struct snd_card的所有声卡设备:(struct snd_card*)card->devices,在本列中该注册过程,包含了两个设备,即pcm设备和控制设备的注册。

  1. /* 
  2.  * register all the devices on the card. 
  3.  * called from init.c 
  4.  */  
  5. int snd_device_register_all(struct snd_card *card)  
  6. {  
  7.     struct snd_device *dev;  
  8.     int err;  
  9.       
  10.     if (snd_BUG_ON(!card))  
  11.         return -ENXIO;  
  12.     list_for_each_entry(dev, &card->devices, list) {  
  13.         if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {  
  14.             if ((err = dev->ops->dev_register(dev)) < 0)//遍历该声卡对象所属的所有音频设备,并执行对应音频设备的 dev_register 函数  
  15.                 return err;  
  16.             dev->state = SNDRV_DEV_REGISTERED;  
  17.         }  
  18.     }  
  19.     return 0;  
  20. }  
/*
 * register all the devices on the card.
 * called from init.c
 */
int snd_device_register_all(struct snd_card *card)
{
	struct snd_device *dev;
	int err;
	
	if (snd_BUG_ON(!card))
		return -ENXIO;
	list_for_each_entry(dev, &card->devices, list) {
		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
			if ((err = dev->ops->dev_register(dev)) < 0)//遍历该声卡对象所属的所有音频设备,并执行对应音频设备的 dev_register 函数
				return err;
			dev->state = SNDRV_DEV_REGISTERED;
		}
	}
	return 0;
}

而以上的dev_register函数是在pcm设备和控制设备创建的时候,就已经初始化好的。他们的创建函数分别为:snd_pcm_new,snd_ctl_create函数。

在执行dev->ops->dev_register时,在本列中,分如下两种情况:

for pcm 设备:

::snd_pcm_dev_register(struct snd_device *device)//for pcm 设备

snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);

snd_minors[minor] = preg;

device_create //在/dev/snd目录下,创建设备节点

以上snd_pcm_f_ops结构体展开如下:

/*
 *  Register section
 */


const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl =
snd_pcm_playback_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,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl =
snd_pcm_capture_ioctl,
.compat_ioctl =
snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area =
snd_pcm_get_unmapped_area,
}
};

for 控制设备:

::snd_ctl_dev_register(struct snd_device *device)//for 控制设备

sprintf(name, "controlC%i", card->number);

static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,

};

snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, name)

snd_register_device_for_dev(type, card, dev, f_ops,private_data, name,snd_card_get_device_link(card)


以上snd_pcm_f_ops[2]和snd_ctl_f_ops即为设备节点文件/dev/snd/pcmC0D1c,/dev/snd/pcmC0D1p/dev/snd/controlC0访问的内核入口点。

2: 将创建的struct snd_card *card设备,存储在全局数组snd_cards[]中。

snd_cards[card->number] = card;


j: snd_soc_dapm_sync(&card->dapm);

以上函数,后续章节再详细讲解


至此linux alsa音频驱动的初始化基本完成。


文章转自:http://blog.csdn.net/xiaojsj111/article/details/24261457


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值