藍牙語音功能的實作
要實作藍牙能夠打skype電話,或藍牙錄音等功能,從下到上,需要做如下的修改:
audio部分:驅動層,需要實作audio pcm的驅動。hal層,需要添加藍牙sco音頻的通路支援。
藍牙部分:使用的藍牙晶片的pcm接口連接配接到ap的pcm接口(用于傳送音頻資料),不是走的uart口傳送音頻資料。使用的handfree/handset的profile,而不是a2dp profilepcm部分,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來關聯起來的。即是通過snd_soc_dai_link結構來關聯codec,platform,dai等之間的關系的。詳情見snd_soc_dai_link結構内容:machine--------->struct snd_soc_card------------>snd_soc_dai_linkstruct 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 widgetf: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參數:
提供的sink,source,control等名字資訊,在(struct snd_soc_card *)card->widgets的控件清單中,分别找到源控件,目的控件,和path所對應的kcontrol控件。配置設定并初始化struct snd_soc_dapm_path *path結構所需的記憶體空間,并根據控件id的不同,調用如下函數之一:/* * 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); };
source dapm_connect_mux
dapm_connect_mixer
來将dapm控件和path進行關聯起來,并設定path的connect狀态。
dapm mux控件和dapm mixer控件為什麼要分開來處理,因為dapm mixer控件會有多個Kcontrol,而dapm mux控件隻會有一個控件。
下面展開dapm_connect_mixer函數:
下面展開 dapm_connect_mux函數:/* 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; }
h:snd_soc_dapm_new_widgets/* 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; }
周遊(struct snd_soc_card *)card->widgets清單中所有dapm控件,為每個控件做如下的事情:
1:struct snd_soc_dapm_widget *w;為widget中的所有kcntrols配置設定記憶體空間(w->kcontrols),并做如下事情:
初始化path->long_name
2:更新dapm的電源狀态:w->power,并标記已經建立了該dapm widgetpath->kcontrol = snd_soc_cnew(...)//建立kcontrol,并傳回該kcontrol
snd_ctl_add(card, path->kcontrol);//添加該控件到系統中
w->kcontrols[i] = path->kcontrol;//用新建立的kcontrol設定widget和path對應的kcontrol
3:将該dapm widget标記為dirty,将他添加到dirty list中,然後通過dapm_power_widgets函數作随後的順序上下電操作/* 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
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);
for 控制裝置:snd_minors[minor] = preg;
device_create //在/dev/snd目錄下,建立裝置節點
以上snd_pcm_f_ops結構體展開如下:
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,
}
};
::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[]中。
j: snd_soc_dapm_sync(&card->dapm);snd_cards[card->number] = card;
以上函數,後續章節再詳細講解至此linux alsa音頻驅動的初始化基本完成。