日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ALSA音频框架理解:machine

發(fā)布時(shí)間:2023/12/20 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ALSA音频框架理解:machine 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前面一節(jié)的內(nèi)容我們提到,ASoC被分為Machine、Platform和Codec三大部分,其中的Machine驅(qū)動(dòng)負(fù)責(zé)Platform和Codec之間的耦合以及部分和設(shè)備或板子特定的代碼,再次引用上一節(jié)的內(nèi)容:Machine驅(qū)動(dòng)負(fù)責(zé)處理機(jī)器特有的一些控件和音頻事件(例如,當(dāng)播放音頻時(shí),需要先行打開一個(gè)放大器);單獨(dú)的Platform和Codec驅(qū)動(dòng)是不能工作的,它必須由Machine驅(qū)動(dòng)把它們結(jié)合在一起才能完成整個(gè)設(shè)備的音頻處理工作。

ASoC的一切都從Machine驅(qū)動(dòng)開始,包括聲卡的注冊,綁定Platform和Codec驅(qū)動(dòng)等等,下面就讓我們從Machine驅(qū)動(dòng)開始討論吧。

現(xiàn)在直接分析最普通的聲卡wm8904的驅(qū)動(dòng),由點(diǎn)到面學(xué)習(xí)alsa的整個(gè)驅(qū)動(dòng)流程。

驅(qū)動(dòng)現(xiàn)在編寫的流程即解析dts->匹配驅(qū)動(dòng)->調(diào)用驅(qū)動(dòng)入口,那么我們直接分析dts開始。

查看dts:

sound {compatible = "atmel,asoc-wm8904";pinctrl-names = "default";pinctrl-0 = <&pinctrl_pck0_as_mck>;atmel,model = "wm8904 @ AT91SAM9N12EK";atmel,audio-routing ="Headphone Jack", "HPOUTL","Headphone Jack", "HPOUTR","IN2L", "Line In Jack","IN2R", "Line In Jack","Mic", "MICBIAS","IN1L", "Mic";atmel,ssc-controller = <&ssc0>;atmel,audio-codec = <&wm8904>;};

由dts文件可知,配置的聲卡為 atmel,asoc-wm8904,這是一個(gè)典型的machine平臺(tái)驅(qū)動(dòng)設(shè)備,因此直接查找到所在的驅(qū)動(dòng)文件:
?

static struct platform_driver atmel_asoc_wm8904_driver = {.driver = {.name = "atmel-wm8904-audio",.of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids),.pm = &snd_soc_pm_ops,},.probe = atmel_asoc_wm8904_probe,.remove = atmel_asoc_wm8904_remove,};

?

Platform驅(qū)動(dòng)匹配直接調(diào)用probe函數(shù)。

static int atmel_asoc_wm8904_probe(struct platform_device *pdev){struct snd_soc_card *card = &atmel_asoc_wm8904_card;struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;int id, ret;card->dev = &pdev->dev;ret = atmel_asoc_wm8904_dt_init(pdev);id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");ret = atmel_ssc_set_audio(id);ret = snd_soc_register_card(card); //核心入口return 0;err_set_audio:atmel_ssc_put_audio(id);return ret;}

從上面我們可知,machine除了鏈接platform和code,還要做一些其他硬件的初始化,這不難理解,比如,如果硬件播放需要是能一個(gè)功放,那么功放的控制既不屬于codec,也不屬于soc上的音頻控制引腳,這個(gè)功放控制使能也許只在當(dāng)前板子上才存在,這種不屬于soc音頻控制所需共性的資源,即在machine中初始化。

即machine核心函數(shù):

? snd_soc_register_card(card);

上文我們一直談到了一個(gè)觀念,machine驅(qū)動(dòng)的核心作用是匹配在鏈表中匹配platform和dai,codec。 因此可以在card查看關(guān)聯(lián)信息:

static struct snd_soc_card atmel_asoc_wm8904_card = {.name = "atmel_asoc_wm8904",.owner = THIS_MODULE,.dai_link = &atmel_asoc_wm8904_dailink,.num_links = 1,.dapm_widgets = atmel_asoc_wm8904_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets),.fully_routed = true,};

我們發(fā)現(xiàn)card->dai_link就保存這我們當(dāng)前所有完整鏈路的所需資源,一個(gè)聲卡可能有多個(gè)dailink,前文幾經(jīng)討論過了,我們這里只分析一條即可。

static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {.name = "WM8904",.stream_name = "WM8904 PCM",.codec_dai_name = "wm8904-hifi",.dai_fmt = SND_SOC_DAIFMT_I2S| SND_SOC_DAIFMT_NB_NF| SND_SOC_DAIFMT_CBM_CFM,.ops = &atmel_asoc_wm8904_ops,};

整個(gè)流程執(zhí)行到這里,其實(shí)還算十分簡單,簡言概之,dts配置了一個(gè)sound節(jié)點(diǎn),匹配調(diào)用machine的驅(qū)動(dòng)probe,在probe事先把描述platform、codec關(guān)系的card->dai-link填充好,然后調(diào)用snd_soc_register_card(card),即完成了machine驅(qū)動(dòng)的編寫。

這里不難猜出snd_soc_register_card中會(huì)按照card->dai-link中所描述的codec、dai,去從codec_list、dai_list上遍歷匹配,找到對應(yīng)的codec、dai驅(qū)動(dòng)。

int snd_soc_register_card(struct snd_soc_card *card){if (!card->name || !card->dev)return -EINVAL;dev_set_drvdata(card->dev, card); snd_soc_initialize_card_lists(card);INIT_LIST_HEAD(&card->dai_link_list);INIT_LIST_HEAD(&card->rtd_list);card->num_rtd = 0;INIT_LIST_HEAD(&card->dapm_dirty);INIT_LIST_HEAD(&card->dobj_list);card->instantiated = 0;/* ===============以上初始化card中所有l(wèi)ist,特別注意rtd_list=====================*/mutex_init(&card->mutex);mutex_init(&card->dapm_mutex);spin_lock_init(&card->dpcm_lock);return snd_soc_bind_card(card); //核心入口}這里將card->instantiated = 0,其實(shí)就是設(shè)置一個(gè)flag,當(dāng)snd_card初始化完成就把設(shè)置為1。以防止多次初始化,static int snd_soc_bind_card(struct snd_soc_card *card){struct snd_soc_pcm_runtime *rtd;int ret;ret = snd_soc_instantiate_card(card); //核心入口/* deactivate pins to sleep state */for_each_card_rtds(card, rtd) {struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_dai *codec_dai;int j;for_each_rtd_codec_dai(rtd, j, codec_dai) {if (!codec_dai->active)pinctrl_pm_select_sleep_state(codec_dai->dev);}if (!cpu_dai->active)pinctrl_pm_select_sleep_state(cpu_dai->dev);}return ret;}[Click and drag to move]這里我們所有猜想的核心執(zhí)行都在snd_soc_instantiate_card加以實(shí)現(xiàn)。這里只節(jié)選部分代碼,完整請查閱代碼數(shù)。static int snd_soc_instantiate_card(struct snd_soc_card *card){struct snd_soc_pcm_runtime *rtd;struct snd_soc_dai_link *dai_link;int ret, i, order;mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);...... ....../*dapm的作用以后分析*/card->dapm.bias_level = SND_SOC_BIAS_OFF;card->dapm.dev = card->dev;card->dapm.card = card;list_add(&card->dapm.list, &card->dapm_list);...... ....../* bind DAIs */for_each_card_prelinks(card, i, dai_link) {ret = soc_bind_dai_link(card, dai_link);if (ret != 0)goto probe_end;}/* add predefined DAI links to the list */for_each_card_prelinks(card, i, dai_link)snd_soc_add_dai_link(card, dai_link);/* card bind complete so register a sound card */ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);soc_init_card_debugfs(card);#ifdef CONFIG_DEBUG_FSsnd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);#endif#ifdef CONFIG_PM_SLEEP/* deferred resume work */INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);#endifif (card->dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,card->num_dapm_widgets);if (card->of_dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,card->num_of_dapm_widgets);/* initialise the sound card only once */if (card->probe) {ret = card->probe(card);if (ret < 0)goto probe_end;}/* probe all components used by DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_components(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}/* probe auxiliary components */ret = soc_probe_aux_devices(card);if (ret < 0)goto probe_end;/** Find new DAI links added during probing components and bind them.* Components with topology may bring new DAIs and DAI links.*/for_each_card_links(card, dai_link) {if (soc_is_dai_link_bound(card, dai_link))continue;ret = soc_init_dai_link(card, dai_link);if (ret)goto probe_end;ret = soc_bind_dai_link(card, dai_link);if (ret)goto probe_end;}/* probe all DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_dais(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}snd_soc_dapm_link_dai_widgets(card);snd_soc_dapm_connect_dai_link_widgets(card);if (card->controls)snd_soc_add_card_controls(card, card->controls,card->num_controls);if (card->dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,card->num_dapm_routes);if (card->of_dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,card->num_of_dapm_routes);/* try to set some sane longname if DMI is available */snd_soc_set_dmi_name(card, NULL);snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),"%s", card->name);snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),"%s", card->long_name ? card->long_name : card->name);snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),"%s", card->driver_name ? card->driver_name : card->name);}if (card->late_probe) {ret = card->late_probe(card);}snd_soc_dapm_new_widgets(card);ret = snd_card_register(card->snd_card);card->instantiated = 1;...... ......mutex_unlock(&card->mutex);mutex_unlock(&client_mutex);return ret;}

ASoC定義了三個(gè)全局的鏈表頭變量:codec_list、dai_list、platform_list,系統(tǒng)中所有的Codec、DAI、Platform都在注冊時(shí)連接到這三個(gè)全局鏈表上。soc_bind_dai_link函數(shù)逐個(gè)掃描這三個(gè)鏈表,根據(jù)card->dai_link[]中的名稱進(jìn)行匹配,匹配后把相應(yīng)的codec,dai和platform實(shí)例賦值到card->rtd[]中(snd_soc_pcm_runtime)。經(jīng)過這個(gè)過程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驅(qū)動(dòng)的信息,這里為什么要有一個(gè)rtd的數(shù)組,其實(shí)很簡單,machine驅(qū)動(dòng)只會(huì)在加載的時(shí)候執(zhí)行一次,假設(shè)我們存在多個(gè)dai-link,即多條有效的數(shù)據(jù)鏈路,那么每一條的都存在codec、dai的驅(qū)動(dòng)信息,因此多條dai-link的信息就要用數(shù)組來表示,每一個(gè)rtd數(shù)組項(xiàng)表示一個(gè)dai-link的資源,為什么叫

runtime,個(gè)人猜想可能是因?yàn)檫@個(gè)數(shù)組項(xiàng)中的內(nèi)容是在運(yùn)行掃描后生成的。至于是不是就不知道了。


?

for_each_card_prelinks(card, i, dai_link)snd_soc_add_dai_link(card, dai_link);--------------------------------------------------------------------static int soc_bind_dai_link(struct snd_soc_card *card,struct snd_soc_dai_link *dai_link){struct snd_soc_pcm_runtime *rtd;struct snd_soc_dai_link_component *codecs;struct snd_soc_component *component;int i;rtd = soc_new_pcm_runtime(card, dai_link);...... ......rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);rtd->num_codecs = dai_link->num_codecs;rtd->codec_dai = rtd->codec_dais[0];soc_add_pcm_runtime(card, rtd);}

然后調(diào)用標(biāo)準(zhǔn)的alsa函數(shù)創(chuàng)建聲卡實(shí)例:

ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);

?

然后,依次調(diào)用各個(gè)子結(jié)構(gòu)的probe函數(shù),這里我們知道card->rtd[]中存了一個(gè)dailink的Codec,DAI和Platform驅(qū)動(dòng)的信息,在最后還調(diào)用了soc_new_pcm()函數(shù)用于創(chuàng)建標(biāo)準(zhǔn)alsa驅(qū)動(dòng)的pcm邏輯設(shè)備

for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_components(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}--------------------------------------------------------------------------------------------------------------static int soc_probe_dai(struct snd_soc_dai *dai, int order){if (dai->probed ||dai->driver->probe_order != order)return 0;if (dai->driver->probe) {int ret = dai->driver->probe(dai);if (ret < 0) {dev_err(dai->dev, "ASoC: failed to probe DAI %s: %d\n",dai->name, ret);return ret;}}dai->probed = 1;return 0;}static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,struct snd_soc_pcm_runtime *rtd){int i, ret = 0;for (i = 0; i < num_dais; ++i) {struct snd_soc_dai_driver *drv = dais[i]->driver;if (drv->pcm_new)ret = drv->pcm_new(rtd, dais[i]);if (ret < 0) {dev_err(dais[i]->dev,"ASoC: Failed to bind %s with pcm device\n",dais[i]->name);return ret;}}return 0;}static int soc_probe_link_dais(struct snd_soc_card *card,struct snd_soc_pcm_runtime *rtd, int order){struct snd_soc_dai_link *dai_link = rtd->dai_link;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_rtdcom_list *rtdcom;struct snd_soc_component *component;struct snd_soc_dai *codec_dai;int i, ret, num;/* probe the CPU DAI */ret = soc_probe_dai(cpu_dai, order);/* probe the CODEC DAI */for_each_rtd_codec_dai(rtd, i, codec_dai) {ret = soc_probe_dai(codec_dai, order);}...... .......ret = soc_new_pcm(rtd, num);return 0;}

創(chuàng)建pcm,這里是alsa和asoc開始結(jié)合的部分,alsa中的pcm的fops實(shí)質(zhì)是執(zhí)行的rtd->fops,這里非常非常的重要。該函數(shù)首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成員,例如open,close,hw_params等,緊接著調(diào)用標(biāo)準(zhǔn)alsa驅(qū)動(dòng)中的創(chuàng)建pcm的函數(shù)snd_pcm_new()創(chuàng)建聲卡的pcm實(shí)例,pcm的private_data字段設(shè)置為該runtime變量rtd,然后用platform驅(qū)動(dòng)中的snd_pcm_ops替換部分pcm中的snd_pcm_ops字段,最后,調(diào)用platform驅(qū)動(dòng)的pcm_new回調(diào),該回調(diào)實(shí)現(xiàn)該platform下的dma內(nèi)存申請和dma初始化等相關(guān)工作。
?

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_component *component;struct snd_soc_rtdcom_list *rtdcom;struct snd_pcm *pcm;char new_name[64];int ret = 0, playback = 0, capture = 0;int i;/****************判斷當(dāng)前dai-link是不是playback或capture*****************************/if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {playback = rtd->dai_link->dpcm_playback;capture = rtd->dai_link->dpcm_capture;} else {for_each_rtd_codec_dai(rtd, i, codec_dai) {if (codec_dai->driver->playback.channels_min)playback = 1;if (codec_dai->driver->capture.channels_min)capture = 1;}capture = capture && cpu_dai->driver->capture.channels_min;playback = playback && cpu_dai->driver->playback.channels_min;}...... ......./****************實(shí)例化playback或capture兩個(gè)substream*****************************/if (rtd->dai_link->dynamic)snprintf(new_name, sizeof(new_name), "%s (*)",rtd->dai_link->stream_name);elsesnprintf(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);if (ret < 0) {dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",rtd->dai_link->name);return ret;}dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);/* DAPM dai link stream work */INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);pcm->nonatomic = rtd->dai_link->nonatomic;rtd->pcm = pcm;pcm->private_data = rtd;if (rtd->dai_link->no_pcm) {if (playback)pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;if (capture)pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;goto out;}/****************注冊rtd->ops *****************************/if (rtd->dai_link->dynamic) {rtd->ops.open = dpcm_fe_dai_open;rtd->ops.hw_params = dpcm_fe_dai_hw_params;rtd->ops.prepare = dpcm_fe_dai_prepare;rtd->ops.trigger = dpcm_fe_dai_trigger;rtd->ops.hw_free = dpcm_fe_dai_hw_free;rtd->ops.close = dpcm_fe_dai_close;rtd->ops.pointer = soc_pcm_pointer;rtd->ops.ioctl = soc_pcm_ioctl;} else {rtd->ops.open = soc_pcm_open;rtd->ops.hw_params = soc_pcm_hw_params;rtd->ops.prepare = soc_pcm_prepare;rtd->ops.trigger = soc_pcm_trigger;rtd->ops.hw_free = soc_pcm_hw_free;rtd->ops.close = soc_pcm_close;rtd->ops.pointer = soc_pcm_pointer;rtd->ops.ioctl = soc_pcm_ioctl;}for_each_rtdcom(rtd, rtdcom) {const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;if (!ops)continue;if (ops->ack)rtd->ops.ack = soc_rtdcom_ack;if (ops->copy_user)rtd->ops.copy_user = soc_rtdcom_copy_user;if (ops->copy_kernel)rtd->ops.copy_kernel = soc_rtdcom_copy_kernel;if (ops->fill_silence)rtd->ops.fill_silence = soc_rtdcom_fill_silence;if (ops->page)rtd->ops.page = soc_rtdcom_page;if (ops->mmap)rtd->ops.mmap = soc_rtdcom_mmap;}/****************將pcm->ops 設(shè)備為 rtd->ops *****************************/if (playback)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);if (capture)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);for_each_rtdcom(rtd, rtdcom) {component = rtdcom->component;if (!component->driver->pcm_new)continue;/*******************調(diào)用pcm_new*****************************************************/ret = component->driver->pcm_new(rtd);if (ret < 0) {dev_err(component->dev,"ASoC: pcm constructor failed: %d\n",ret);return ret;}}pcm->private_free = soc_pcm_private_free;pcm->no_device_suspend = true;out:dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,cpu_dai->name);return ret;}設(shè)置card->snd_card的名字:snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),"%s", card->name);snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),"%s", card->long_name ? card->long_name : card->name);snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),"%s", card->driver_name ? card->driver_name : card->name);for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {switch (card->snd_card->driver[i]) {case '_':case '-':case '\0':break;default:if (!isalnum(card->snd_card->driver[i]))card->snd_card->driver[i] = '_';break;}}

如果存在card->late_probe,則執(zhí)行。
?

if (card->late_probe) {ret = card->late_probe(card);}

最后執(zhí)行ret = snd_card_register(card->snd_card);

到這里,整個(gè)Machine驅(qū)動(dòng)的初始化已經(jīng)完成,通過各個(gè)子結(jié)構(gòu)的probe調(diào)用,實(shí)際上,也完成了部分Platfrom驅(qū)動(dòng)和Codec驅(qū)動(dòng)的初始化工作,完成了snd_card和pcm設(shè)備的添加。以后的部分進(jìn)入了alsa框架部分。

總結(jié)一下本篇的內(nèi)容,即配置dts觸發(fā)machine驅(qū)動(dòng)執(zhí)行probe函數(shù),在probe中初始化了和當(dāng)前板子相關(guān)的一些硬件,如打開功放等等,然后調(diào)用snd_soc_register_card下的snd_soc_instantiate_card函數(shù)進(jìn)行platform、codec、dai驅(qū)動(dòng)的匹配,匹配完成后將這些信息保存在rtd[]中備用,依次執(zhí)行platform、codec、dai驅(qū)動(dòng)的probe函數(shù),這里知道platform驅(qū)動(dòng)只要初始化soc時(shí)鐘內(nèi)存和音頻接口。Codec主要初始化外置codec芯片,dai主要初始化傳輸配置相關(guān),執(zhí)行到這里,意味著我們一條鏈路所需要的硬件初始化即完成了,最后注冊snd_card聲卡,并實(shí)例化當(dāng)中的pcm設(shè)備,進(jìn)入alsa框架了。當(dāng)這里,我們可以看到/dev/snd存在聲卡設(shè)備節(jié)點(diǎn)了。

總結(jié)

以上是生活随笔為你收集整理的ALSA音频框架理解:machine的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。