前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧。

现在直接分析最普通的声卡wm8904的驱动,由点到面学习alsa的整个驱动流程。

驱动现在编写的流程即解析dts->匹配驱动->调用驱动入口,那么我们直接分析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,这是一个典型的machine平台驱动设备,因此直接查找到所在的驱动文件:

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驱动匹配直接调用probe函数。


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,还要做一些其他硬件的初始化,这不难理解,比如,如果硬件播放需要是能一个功放,那么功放的控制既不属于codec,也不属于soc上的音频控制引脚,这个功放控制使能也许只在当前板子上才存在,这种不属于soc音频控制所需共性的资源,即在machine中初始化。

即machine核心函数:

snd_soc_register_card(card);

上文我们一直谈到了一个观念,machine驱动的核心作用是匹配在链表中匹配platform和dai,codec。 因此可以在card查看关联信息:

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,};

我们发现card->dai_link就保存这我们当前所有完整链路的所需资源,一个声卡可能有多个dailink,前文几经讨论过了,我们这里只分析一条即可。

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,};

整个流程执行到这里,其实还算十分简单,简言概之,dts配置了一个sound节点,匹配调用machine的驱动probe,在probe事先把描述platform、codec关系的card->dai-link填充好,然后调用snd_soc_register_card(card),即完成了machine驱动的编写。

这里不难猜出snd_soc_register_card中会按照card->dai-link中所描述的codec、dai,去从codec_list、dai_list上遍历匹配,找到对应的codec、dai驱动。

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中所有list,特别注意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,其实就是设置一个flag,当snd_card初始化完成就把设置为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]这里我们所有猜想的核心执行都在snd_soc_instantiate_card加以实现。这里只节选部分代码,完整请查阅代码数。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定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息,这里为什么要有一个rtd的数组,其实很简单,machine驱动只会在加载的时候执行一次,假设我们存在多个dai-link,即多条有效的数据链路,那么每一条的都存在codec、dai的驱动信息,因此多条dai-link的信息就要用数组来表示,每一个rtd数组项表示一个dai-link的资源,为什么叫

runtime,个人猜想可能是因为这个数组项中的内容是在运行扫描后生成的。至于是不是就不知道了。

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);}

然后调用标准的alsa函数创建声卡实例:

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

然后,依次调用各个子结构的probe函数,这里我们知道card->rtd[]中存了一个dailink的Codec,DAI和Platform驱动的信息,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备


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

创建pcm,这里是alsa和asoc开始结合的部分,alsa中的pcm的fops实质是执行的rtd->fops,这里非常非常的重要。该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。

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;/****************判断当前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;}......     ......./****************实例化playback或capture两个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 设备为 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;/*******************调用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;}设置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,则执行。

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

最后执行ret = snd_card_register(card->snd_card);

到这里,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,完成了snd_card和pcm设备的添加。以后的部分进入了alsa框架部分。

总结一下本篇的内容,即配置dts触发machine驱动执行probe函数,在probe中初始化了和当前板子相关的一些硬件,如打开功放等等,然后调用snd_soc_register_card下的snd_soc_instantiate_card函数进行platform、codec、dai驱动的匹配,匹配完成后将这些信息保存在rtd[]中备用,依次执行platform、codec、dai驱动的probe函数,这里知道platform驱动只要初始化soc时钟内存和音频接口。Codec主要初始化外置codec芯片,dai主要初始化传输配置相关,执行到这里,意味着我们一条链路所需要的硬件初始化即完成了,最后注册snd_card声卡,并实例化当中的pcm设备,进入alsa框架了。当这里,我们可以看到/dev/snd存在声卡设备节点了。

ALSA音频框架理解:machine相关推荐

  1. Linux ALSA音频框架分析五:HDA Driver分析

    Linux ALSA音频框架分析五:HDA Driver分析 一 概述 HDA(High Definition Audio)是intel设计的用来取代AC97的音频标准,硬件架构上由hda dodec ...

  2. Linux ALSA音频系统:platform,machine,codec

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_41965270/arti ...

  3. Linux ALSA音频子系统二

    From 本文以高通平台为例,介绍一下android下的音频结构.android使用的是tinyALSA作为音频系统,使用方法和基本框架与linux中常用的ALSA音频子系统是一致的. ALSA音频框 ...

  4. ALSA驱动asoc框架之machine(二)

    一.注册platform Driver 前面章节ALSA驱动asoc框架之machine(一)说过machine驱动的probe函数会分配一个名为"soc-audio"的平台设备, ...

  5. ALSA驱动asoc框架之machine(一)

    从前面内容我们知道ALSA 驱动 asoc 框架主要包括 codec driver. platform driver. machine driver,其中machine是连接codec driver及 ...

  6. Linux ALSA 音频系统:物理链路篇

    原址 1. Overview 硬件平台及软件版本: Kernel - 3.4.5 SoC - Samsung exynos CODEC - WM8994 Machine - goni_wm8994 U ...

  7. (一)Linux ALSA 音频系统:物理链路篇

    物理链路篇 转自:https://me.csdn.net/zyuanyun Linux ALSA 音频系统:物理链路篇 Linux ALSA 音频系统:物理链路篇 原创 zyuanyun 最后发布于2 ...

  8. ALSA驱动框架分析

    一.ALSA驱动框架介绍 1.ALSA介绍 ALSA(Advanced Linux Sound Architecture-高级linux声音架构),目前已经成为了linux的主流音频体系结构,ALSA ...

  9. Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建

    (1)ALSA简介 (1) Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音.录音.控制 ALSA ...

  10. Android音频框架笔记 - 上篇

    原址 一.音频数字化基础知识 见书,列出知识点如下: 声音 声波,声音频率.响度, 音调.音色. 音响设备中的声道 得翻翻初高中的课本了. 声音数字化过程 声源 -> mic -> ADC ...

最新文章

  1. __clone class php_PHP 对象克隆 clone 关键字与 __clone() 方法
  2. Java学习笔记(43)——Java泛型
  3. 从Thread.start()方法看Thread源码,多次start一个线程会怎么样
  4. 数据中心管理中的人工智能:其对人员配置和流程意味着什么?
  5. 在SQL2005/SQL2008中CTE用法差异
  6. SQL 2005 数据库镜像
  7. 【渝粤教育】电大中专药物分析技术基础作业 题库
  8. 分享一下10个常用jquery片段
  9. 【卷积神经网络】ResNet翻译详解
  10. Spring架构这么牛逼,这两大特性不得不说说!
  11. 分布式一致性算法-paxos详解与分析
  12. 【数学建模】论文模板和latex模板
  13. 【CGAL_空间搜索与排序】3D快速求交和距离计算
  14. 短信验证码校验的实现
  15. Python Day 67 Dango框架图解(总结)、Wsgiref和uwsgi、前后端传输数据的编码格式、From表单和Ajax方式在前端往后端发送文件、补充一下页面清缓存...
  16. 医院管理信息系统与临床信息系统
  17. 初级会计实务--第三章第四节、应交税费
  18. Xcode7 网络请求报错:The resource could not be loaded because the App Transport Security policy requir
  19. 列表如何做,看这一篇就够啦——触底加载、虚拟滚动与计算展现值
  20. 注意力机制的分类 | Soft Attention和Hard Attention

热门文章

  1. Linux-firewalld-squid正向代理
  2. python输出姓名
  3. 层次结构类毕业论文文献都有哪些?
  4. 使用APUE源码找不到apue.h
  5. 如何在 Mac 上使用“活动监视器”
  6. 基于51单片机的GPS公交自动报站系统
  7. 我的Android之路
  8. C语言编程齿轮轮廓线坐标,C语言程序实现齿轮基本参数几何尺寸计算
  9. mongodb中的3t客户端的时间延长做法
  10. 游戏设计的100个原理(1-5)