ALSA音效卡驅動的DAPM(二)-建立過程

来源:https://www.cnblogs.com/linhaostudy/archive/2018/03/06/8513276.html
-Advertisement-
Play Games

在上一篇文章中,我們重點介紹了widget、path、route之間的關係及其widget的註冊; http://www.cnblogs.com/linhaostudy/p/8509899.html 在最後一章中,我們已經簡單介紹了snd_soc_dapm_new_controls函數用來創建wid ...


在上一篇文章中,我們重點介紹了widget、path、route之間的關係及其widget的註冊;

http://www.cnblogs.com/linhaostudy/p/8509899.html

 

在最後一章中,我們已經簡單介紹了snd_soc_dapm_new_controls函數用來創建widget。

實際上,這個函數只是創建widget的第一步,它為每一個widget分配記憶體,初始化;

要使widget之間具備連接能力,我們還需要第二個函數snd_soc_dapm_new_widgets:這個函數會根據widget的信息,創建widget所需要的dapm kcontrol,這些dapm kcontol的狀態變化,代表著音頻路徑的變化,從而影響著各個widget的電源狀態。看到函數的名稱可能會迷惑一下,實際上,snd_soc_dapm_new_controls的作用更多地是創建widget,而snd_soc_dapm_new_widget的作用則更多地是創建widget所包含的kcontrol,所以在我看來,這兩個函數名稱應該換過來叫更好!下麵我們分別介紹一下這兩個函數是如何工作的。

 

一、創建widget:snd_soc_dapm_new_controls:

snd_soc_dapm_new_controls函數完成widget的創建工作,並把這些創建好的widget註冊在音效卡的widgets鏈表中,我們看看他的定義:  
 1 /**
 2  * snd_soc_dapm_new_controls - create new dapm controls
 3  * @dapm: DAPM context
 4  * @widget: widget array
 5  * @num: number of widgets
 6  *
 7  * Creates new DAPM controls based upon the templates.
 8  *
 9  * Returns 0 for success else error.
10  */
11 int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
12     const struct snd_soc_dapm_widget *widget,
13     int num)
14 {
15     struct snd_soc_dapm_widget *w;
16     int i;
17     int ret = 0;
18 
19     mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
20     for (i = 0; i < num; i++) {
21         w = snd_soc_dapm_new_control(dapm, widget);
22         if (!w) {
23             dev_err(dapm->dev,
24                 "ASoC: Failed to create DAPM control %s\n",
25                 widget->name);
26             ret = -ENOMEM;
27             break;
28         }
29         widget++;
30     }
31     mutex_unlock(&dapm->card->dapm_mutex);
32     return ret;
33 }

該函數只是簡單的一個迴圈,為傳入的widget模板數組依次調用snd_soc_dapm_new_control函數,實際的工作由snd_soc_dapm_new_control完成,繼續進入該函數,看看它做了那些工作。

我們之前已經說過,驅動中定義的snd_soc_dapm_widget數組,只是作為一個模板,所以,snd_soc_dapm_new_control所做的第一件事,就是為該widget重新分配記憶體,並把模板的內容拷貝過來:
1 static struct snd_soc_dapm_widget *
2 snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
3                          const struct snd_soc_dapm_widget *widget)
4 {
5         struct snd_soc_dapm_widget *w;
6         int ret;
7 
8         if ((w = dapm_cnew_widget(widget)) == NULL)
9                 return NULL;
由dapm_cnew_widget完成記憶體申請和拷貝模板的動作。接下來,根據widget的類型做不同的處理:
 1         switch (w->id) {
 2         case snd_soc_dapm_regulator_supply:
 3                 w->regulator = devm_regulator_get(dapm->dev, w->name);
 4                 ......
 5 
 6                 if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
 7                         ret = regulator_allow_bypass(w->regulator, true);
 8                         ......
 9                 }
10                 break;
11         case snd_soc_dapm_clock_supply:
12 #ifdef CONFIG_CLKDEV_LOOKUP
13                 w->clk = devm_clk_get(dapm->dev, w->name);
14                 ......
15 #else
16                 return NULL;
17 #endif
18                 break;
19         default:
20                 break;
21         }

 對於snd_soc_dapm_regulator_supply類型的widget,根據widget的名稱獲取對應的regulator結構,對於snd_soc_dapm_clock_supply類型的widget,根據widget的名稱,獲取對應的clock結構。接下來,根據需要,在widget的名稱前加入必要的首碼:

if (dapm->codec && dapm->codec->name_prefix)  
        w->name = kasprintf(GFP_KERNEL, "%s %s",  
                dapm->codec->name_prefix, widget->name);  
else  
        w->name = kasprintf(GFP_KERNEL, "%s", widget->name); 

 然後,為不同類型的widget設置合適的power_check電源狀態回調函數,widget類型和對應的power_check回調函數設置如下表所示:

 

widget的power_check回調函數
 

 

widget類型power_check回調函數
mixer類:
snd_soc_dapm_switch
snd_soc_dapm_mixer
snd_soc_dapm_mixer_named_ctl
dapm_generic_check_power
mux類:
snd_soc_dapm_mux
snd_soc_dapm_mux
snd_soc_dapm_mux
dapm_generic_check_power
snd_soc_dapm_dai_out dapm_adc_check_power
snd_soc_dapm_dai_in dapm_dac_check_power
端點類:
snd_soc_dapm_adc
snd_soc_dapm_aif_out
snd_soc_dapm_dac
snd_soc_dapm_aif_in
snd_soc_dapm_pga
snd_soc_dapm_out_drv
snd_soc_dapm_input
snd_soc_dapm_output
snd_soc_dapm_micbias
snd_soc_dapm_spk
snd_soc_dapm_hp
snd_soc_dapm_mic
snd_soc_dapm_line
snd_soc_dapm_dai_link
dapm_generic_check_power
電源/時鐘/影子widget:
snd_soc_dapm_supply
snd_soc_dapm_regulator_supply
snd_soc_dapm_clock_supply
snd_soc_dapm_kcontrol
dapm_supply_check_power
其它類型 dapm_always_on_check_power
      當音頻路徑發生變化時,power_check回調會被調用,用於檢查該widget的電源狀態是否需要更新。power_check設置完成後,需要設置widget所屬的codec、platform和context,幾個用於音頻路徑的鏈表也需要初始化,然後,把該widget加入到音效卡的widgets鏈表中:
1         w->dapm = dapm;
2         w->codec = dapm->codec;
3         w->platform = dapm->platform;
4         INIT_LIST_HEAD(&w->sources);
5         INIT_LIST_HEAD(&w->sinks);
6         INIT_LIST_HEAD(&w->list);
7         INIT_LIST_HEAD(&w->dirty);
8         list_add(&w->list, &dapm->card->widgets);

 

幾個鏈表的作用如下:
  • sources    用於鏈接所有連接到該widget輸入端的snd_soc_path結構
  • sinks    用於鏈接所有連接到該widget輸出端的snd_soc_path結構
  • list    用於鏈接到音效卡的widgets鏈表
  • dirty    用於鏈接到音效卡的dapm_dirty鏈表
最後,把widget設置為connect狀態:
1         /* machine layer set ups unconnected pins and insertions */
2         w->connected = 1;
3         return w;
4 }

 connected欄位代表著引腳的連接狀態,目前,只有以下這些widget使用connected欄位:

  • snd_soc_dapm_output
  • snd_soc_dapm_input
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
  • snd_soc_dapm_vmid
  • snd_soc_dapm_mic
  • snd_soc_dapm_siggen
  驅動程式可以使用以下這些api來設置引腳的連接狀態:
  • snd_soc_dapm_enable_pin
  • snd_soc_dapm_force_enable_pin
  • snd_soc_dapm_disable_pin
  • snd_soc_dapm_nc_pin
到此,widget已經被正確地創建並初始化,而且被掛在音效卡的widgets鏈表中,以後我們就可以通過音效卡的widgets鏈表來遍歷所有的widget,再次強調一下snd_soc_dapm_new_controls函數所完成的主要功能:
  • 為widget分配記憶體,並拷貝參數中傳入的在驅動中定義好的模板
  • 設置power_check回調函數
  • 把widget掛在音效卡的widgets鏈表中
   

二、為widget建立dapm kcontrol

定義一個widget,我們需要指定兩個很重要的內容:一個是用於控制widget的電源狀態的reg/shift等寄存器信息,另一個是用於控制音頻路徑切換的dapm kcontrol信息,這些dapm kcontrol有它們自己的reg/shift寄存器信息用於切換widget的路徑連接方式。

 1 static int snd_soc_instantiate_card(struct snd_soc_card *card)
 2 {
 3         ......
 4         /* card bind complete so register a sound card */
 5         ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
 6                         card->owner, 0, &card->snd_card);
 7         ......
 8  
 9         card->dapm.bias_level = SND_SOC_BIAS_OFF;
10         card->dapm.dev = card->dev;
11         card->dapm.card = card;
12         list_add(&card->dapm.list, &card->dapm_list);
13 
14 #ifdef CONFIG_DEBUG_FS
15         snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
16 #endif
17         ......
18         if (card->dapm_widgets)    /* 創建machine級別的widget  */
19                 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
20                                           card->num_dapm_widgets);
21         ......
22         snd_soc_dapm_link_dai_widgets(card);  /*  連接dai widget  */
23 
24         if (card->controls)    /*  建立machine級別的普通kcontrol控制項  */
25                 snd_soc_add_card_controls(card, card->controls, card->num_controls);
26 
27         if (card->dapm_routes)    /*  註冊machine級別的路徑連接信息  */
28                 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
29                                         card->num_dapm_routes);
30         ......
31 
32         if (card->fully_routed)    /*  如果該標誌被置位,自動把codec中沒有路徑連接信息的引腳設置為無用widget  */
33                 list_for_each_entry(codec, &card->codec_dev_list, card_list)
34                         snd_soc_dapm_auto_nc_codec_pins(codec);
35 
36         snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、電源狀態和連接狀態*/
37 
38         ret = snd_card_register(card->snd_card);
39         ......
40         card->instantiated = 1;
41         snd_soc_dapm_sync(&card->dapm);
42         ......
43         return 0;
44 } 

 

正如我添加的註釋中所示,在完成machine級別的widget和route處理之後,調用的snd_soc_dapm_new_widgets函數,來為所有已經註冊的widget初始化他們所包含的dapm kcontrol,並初始化widget的電源狀態和路徑連接狀態。下麵我們看看snd_soc_dapm_new_widgets函數的工作過程。

2.1 snd_soc_dapm_new_widgets函數:

 1 int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
 2 {
 3         ......
 4         list_for_each_entry(w, &card->widgets, list)
 5         {               
 6                 if (w->new)     
 7                         continue;
 8                                 
 9                 if (w->num_kcontrols) {
10                         w->kcontrols = kzalloc(w->num_kcontrols *
11                                                 sizeof(struct snd_kcontrol *),
12                                                 GFP_KERNEL);
13                         ......
14                 }

接著,對幾種能影響音頻路徑的widget,創建並初始化它們所包含的dapm kcontrol:

 1                 switch(w->id) {
 2                 case snd_soc_dapm_switch:
 3                 case snd_soc_dapm_mixer:
 4                 case snd_soc_dapm_mixer_named_ctl:
 5                         dapm_new_mixer(w);
 6                         break;
 7                 case snd_soc_dapm_mux:
 8                 case snd_soc_dapm_virt_mux:
 9                 case snd_soc_dapm_value_mux:
10                         dapm_new_mux(w);
11                         break;
12                 case snd_soc_dapm_pga:
13                 case snd_soc_dapm_out_drv:
14                         dapm_new_pga(w);
15                         break;
16                 default:
17                         break;
18                 }

 需要用到的創建函數分別是:

  • dapm_new_mixer()    對於mixer類型,用該函數創建dapm kcontrol;
  • dapm_new_mux()   對於mux類型,用該函數創建dapm kcontrol;
  • dapm_new_pga()   對於pga類型,用該函數創建dapm kcontrol;

 

 接著,設置new欄位,表明該widget已經初始化完成,我們還要把該widget加入到音效卡的dapm_dirty鏈表中,表明該widget的狀態發生了變化,稍後在合適的時刻,dapm框架會掃描dapm_dirty鏈表,統一處理所有已經變化的widget。為什麼要統一處理?因為dapm要控制各種widget的上下電順序,同時也是為了減少寄存器的讀寫次數(多個widget可能使用同一個寄存器):
1                 w->new = 1;
2 
3                 dapm_mark_dirty(w, "new widget");
4                 dapm_debugfs_add_widget(w);
5         }

 最後,通過dapm_power_widgets函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變:

1 dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);  
2 ......  
3 return 0;  

 

 

三、為widget建立連接關係

 如果widget之間沒有連接關係,dapm就無法實現動態的電源管理工作,正是widget之間有了連結關係,這些連接關係形成了一條所謂的完成的音頻路徑,dapm可以順著這條路徑,統一控制路徑上所有widget的電源狀態,前面我們已經知道,widget之間是使用snd_soc_path結構進行連接的,驅動要做的是定義一個snd_soc_route結構數組,該數組的每個條目描述了目的widget的和源widget的名稱,以及控制這個連接的kcontrol的名稱,最終,驅動程式使用api函數snd_soc_dapm_add_routes來註冊這些連接信息,接下來我們就是要分析該函數的具體實現方式:

 1 int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,  
 2                             const struct snd_soc_dapm_route *route, int num)  
 3 {  
 4         int i, r, ret = 0;  
 5   
 6         mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);  
 7         for (i = 0; i < num; i++) {  
 8                 r = snd_soc_dapm_add_route(dapm, route);  
 9                 ......  
10                 route++;  
11         }  
12         mutex_unlock(&dapm->card->dapm_mutex);  
13   
14         return ret;  
15 }  

 該函數只是一個迴圈,依次對參數傳入的數組調用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我們進入snd_soc_dapm_add_route函數看看:

 1 static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
 2                                   const struct snd_soc_dapm_route *route)
 3 {
 4         struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
 5         struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
 6         const char *sink;
 7         const char *source;
 8         ......
 9         list_for_each_entry(w, &dapm->card->widgets, list) {
10                 if (!wsink && !(strcmp(w->name, sink))) {
11                         wtsink = w;
12                         if (w->dapm == dapm)
13                                 wsink = w;
14                         continue;
15                 }
16                 if (!wsource && !(strcmp(w->name, source))) {
17                         wtsource = w;
18                         if (w->dapm == dapm)
19                                 wsource = w;
20                 }
21         }

上面的代碼我再次省略了關於名稱首碼的處理部分。我們可以看到,用widget的名字來比較,遍歷音效卡的widgets鏈表,找出源widget和目的widget的指針,這段代碼雖然正確,但我總感覺少了一個判斷退出迴圈的條件,如果鏈表的開頭就找到了兩個widget,還是要遍歷整個鏈表才結束迴圈,好浪費時間。

下麵,如果在本dapm context中沒有找到,則使用別的dapm context中找到的widget:

1         if (!wsink)
2                 wsink = wtsink;
3         if (!wsource)
4                 wsource = wtsource;

 最後,使用來增加一條連接信息:

1         ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
2                 route->connected);
3         ......
4 
5         return 0;
6 }

 

snd_soc_dapm_add_path函數是整個調用鏈條中的關鍵,我們來分析一下:
 1 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
 2         struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
 3         const char *control,
 4         int (*connected)(struct snd_soc_dapm_widget *source,
 5                          struct snd_soc_dapm_widget *sink))
 6 {
 7         struct snd_soc_dapm_path *path;
 8         int ret;
 9 
10         path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
11         if (!path)
12                 return -ENOMEM;
13 
14         path->source = wsource;
15         path->sink = wsink;
16         path->connected = connected;
17         INIT_LIST_HEAD(&path->list);
18         INIT_LIST_HEAD(&path->list_kcontrol);
19         INIT_LIST_HEAD(&path->list_source);
20         INIT_LIST_HEAD(&path->list_sink);

 最後,使用來增加一條連接信息:

1 ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,  
2                 route->connected);  
3         ......  
4   
5         return 0;  
6 }  

snd_soc_dapm_add_path函數是整個調用鏈條中的關鍵,我們來分析一下:(註意linux3.10.28代碼沒有相應的snd_soc_dapm_add_path函數,在linux3.12才有設計snd_soc_dapm_add_path函數)

 1 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,  
 2         struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,  
 3         const char *control,  
 4         int (*connected)(struct snd_soc_dapm_widget *source,  
 5                          struct snd_soc_dapm_widget *sink))  
 6 {  
 7         struct snd_soc_dapm_path *path;  
 8         int ret;  
 9   
10         path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);  
11         if (!path)  
12                 return -ENOMEM;  
13   
14         path->source = wsource;  
15         path->sink = wsink;  
16         path->connected = connected;  
17         INIT_LIST_HEAD(&path->list);  
18         INIT_LIST_HEAD(&path->list_kcontrol);  
19         INIT_LIST_HEAD(&path->list_source);  
20         INIT_LIST_HEAD(&path->list_sink);  

 

 函數的一開始,首先為這個連接分配了一個snd_soc_path結構,path的source和sink欄位分別指向源widget和目的widget,connected欄位保存connected回調函數,初始化幾個snd_soc_path結構中的幾個鏈表。
 1 /* check for external widgets */  
 2         if (wsink->id == snd_soc_dapm_input) {  
 3                 if (wsource->id == snd_soc_dapm_micbias ||  
 4                         wsource->id == snd_soc_dapm_mic ||  
 5                         wsource->id == snd_soc_dapm_line ||  
 6                         wsource->id == snd_soc_dapm_output)  
 7                         wsink->ext = 1;  
 8         }  
 9         if (wsource->id == snd_soc_dapm_output) {  
10                 if (wsink->id == snd_soc_dapm_spk ||  
11                         wsink->id == snd_soc_dapm_hp ||  
12                         wsink->id == snd_soc_dapm_line ||  
13                         wsink->id == snd_soc_dapm_input)  
14                         wsource->ext = 1;  
15         }  

這段代碼用於判斷是否有外部連接關係,如果有,置位widget的ext欄位。判斷方法從代碼中可以方便地看出:

  • 目的widget是一個輸入腳,如果源widget是mic、line、micbias或output,則認為目的widget具有外部連接關係。
  • 源widget是一個輸出腳,如果目的widget是spk、hp、line或input,則認為源widget具有外部連接關係。
 
 1 dapm_mark_dirty(wsource, "Route added");  
 2 dapm_mark_dirty(wsink, "Route added");  
 3   
 4 /* connect static paths */  
 5 if (control == NULL) {  
 6         list_add(&path->list, &dapm->card->paths);  
 7         list_add(&path->list_sink, &wsink->sources);  
 8         list_add(&path->list_source, &wsource->sinks);  
 9         path->connect = 1;  
10         return 0;  
11 }  

 因為增加了連結關係,所以把源widget和目的widget加入到dapm_dirty鏈表中。如果沒有kcontrol來控制該連接關係,則這是一個靜態連接,直接用path把它們連接在一起。在接著往下看:

 1         /* connect dynamic paths */
 2         switch (wsink->id) {
 3         case snd_soc_dapm_adc:
 4         case snd_soc_dapm_dac:
 5         case snd_soc_dapm_pga:
 6         case snd_soc_dapm_out_drv:
 7         case snd_soc_dapm_input:
 8         case snd_soc_dapm_output:
 9         case snd_soc_dapm_siggen:
10         case snd_soc_dapm_micbias:
11         case snd_soc_dapm_vmid:
12         case snd_soc_dapm_pre:
13         case snd_soc_dapm_post:
14         case snd_soc_dapm_supply:
15         case snd_soc_dapm_regulator_supply:
16         case snd_soc_dapm_clock_supply:
17         case snd_soc_dapm_aif_in:
18         case snd_soc_dapm_aif_out:
19         case snd_soc_dapm_dai_in:
20         case snd_soc_dapm_dai_out:
21         case snd_soc_dapm_dai_link:
22         case snd_soc_dapm_kcontrol:
23                 list_add(&path->list, &dapm->card->paths);
24                 list_add(&path->list_sink, &wsink->sources);
25                 list_add(&path->list_source, &wsource->sinks);
26                 path->connect = 1;
27                 return 0;

按照目的widget來判斷,如果屬於以上這些類型,直接把它們連接在一起即可,這段感覺有點多餘,因為通常以上這些類型的widget本來也沒有kcontrol,直接用上一段代碼就可以了,也許是dapm的作者們想著以後可能會有所擴展吧。

 1 case snd_soc_dapm_mux:  
 2 case snd_soc_dapm_virt_mux:  
 3 case snd_soc_dapm_value_mux:  
 4         ret = dapm_connect_mux(dapm, wsource, wsink, path, control,  
 5                 &wsink->kcontrol_news[0]);  
 6         if (ret != 0)  
 7                 goto err;  
 8         break;  
 9 case snd_soc_dapm_switch:  
10 case snd_soc_dapm_mixer:  
11 case snd_soc_dapm_mixer_named_ctl:  
12         ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);  
13         if (ret != 0)  
14                 goto err;  
15         break;  

 當widget之間通過path進行連接之後,他們之間的關係就如下圖所示:

 

到這裡為止,我們為音效卡創建並初始化好了所需的widget,各個widget也通過path連接在了一起,接下來,dapm等待用戶的指令,一旦某個dapm kcontrol被用戶空間改變,利用這些連接關係,dapm會重新創建音頻路徑,脫離音頻路徑的widget會被下電,加入音頻路徑的widget會被上電,所有的上下電動作都會自動完成,用戶空間的應用程式無需關註這些變化,它只管按需要改變某個dapm kcontrol即可。

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在網頁編程時,我們經常需要處理,當session過期時,我們要跳到登陸頁面讓用戶登陸,由於我們可能用到IFrame框架,所以我們我登陸頁面需要顯示在整個頁面,而不是一個IFrame中,大部分的網友是用下麵的代碼進行實現的。 在過濾器中寫如下代碼: 我的實現方式是:跟普通過濾器的寫法一樣,跳到某個ac ...
  • 1.# 表示許可權用戶(如:root),$ 表示普通用戶開機提示:Login:輸入用戶名password:輸入口令 用戶是系統註冊用戶成功登陸後,可以進入相應的用戶環境.退出當前shell,輸入:exit2.useradd netseek 添加一個netseek用戶passwd netseek 給ne ...
  • 一.在Oracle中創建資料庫之前先改一下虛擬機的IP地址,以便訪問 2. 3. 3.1 3.2 3.3 3.4 創建完成:輸入sqlplus sys/123456 as sysdba測試 ...
  • yum解釋:yum是一個Shell前端軟體包管理器,基於RPM包管理。能夠從指定的伺服器自動下載rpm包並且安裝,可以自動處理依賴性關係,並且一次安裝 所有依賴的軟體包,無須繁瑣地一次次下載/安裝,yum提供了查找、安裝、刪除某一個、一組甚至全部軟體包的命令。 ...
  • jdk [root@localhost] tar zxvf jdk 8u144 linux x64.tar.gz [root@localhost] vi /etc/profile 在profile文件中添加下述內容 [root@localhost] source /etc/profile [root ...
  • 本章主要內容如下: 1)多行顯示 2)居中顯示 在上章3.數位相框-通過freetype庫實現矢量顯示里,我們使用矢量坐標時,該坐標僅僅在原點位置處,所以文字有可能會超出坐標,如下圖所示: 既然超出了坐標,會不會被下一行的文字覆蓋掉? 答:對於幾行同樣大的文字而言,不會的. 以 24*24的韋字為例 ...
  • 因為有項目使用Nginx來做負載均衡,但是Nginx的Windows版本是不提供安裝成服務的,所以伺服器重啟後Nginx並不會伴隨啟動和恢復。網上查了下,這裡記錄下解決方法,防止遺忘。 第一步:下載Winsw工具,下載地址:https://github.com/kohsuke/winsw/relea ...
  • 在裸板下使用 SPI 的話,有兩種方法可選: 1. 使用 IO 口模擬 SPI 進行操作 2. 使用 SPI 控制器進行操作 這裡我們選用控制器的方式,簡單方便。 初始化 SPI ~~~~ static void SPIControllerInit(void) { / 設置頻率 / SPPRE0 = ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...