藍牙語音功能的實現

藍牙語音功能的實現

要實現藍牙能夠打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結構內容:

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等結構。

提供該結構體的一個實例:

/* 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控件:struct snd_soc_card*card->dapm_widgets,該函數主要作用是:爲控件dapm_widget分配所需內存並初始化它,設置控件的callback函數,一般爲power_check,設置控件爲連接狀態(->connected = 1),並將該dapm_widget添加到snd_soc_card的控件列表中.控件是一個很重要的概念,在這裏做下展開描述,先上結構體:

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參數:

/*
 * 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函數:

/* 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函數:
/* 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

		/* 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函數作隨後的順序上下電操作


		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設備和控制設備的註冊。

/*
 * 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音頻驅動的初始化基本完成。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章