GStreamer 簡明教程(十一):插件開發,以一個音頻生成(Audio Source)插件為例

系列文章目錄

  • GStreamer 簡明教程(一):環境搭建,運行 Basic Tutorial 1 Hello world!
  • GStreamer 簡明教程(二):基本概念介紹,Element 和 Pipeline
  • GStreamer 簡明教程(三):動態調整 Pipeline
  • GStreamer 簡明教程(四):Seek 以及獲取文件時長
  • GStreamer 簡明教程(五):Pad 相關概念介紹,Pad Capabilities/Templates
  • GStreamer 簡明教程(六):利用 Tee 復制流數據,巧用 Queue 實現多線程
  • GStreamer 簡明教程(七):實現管道的動態數據流
  • GStreamer 簡明教程(八):常用工具介紹
  • GStreamer 簡明教程(九):Seek 與跳幀
  • GStreamer 簡明教程(十):插件開發,以一個音頻特效插件為例

文章目錄

  • 系列文章目錄
  • 前言
  • 一、準備工作
  • 二、Show me the code
    • 3.1 線程模型分析
        • 消費端模型
        • 生產端實現方案
    • 3.2 深入 audiotestsrc 的實現邏輯
      • 3.2.1 生產者線程的啟動時機
      • 3.2.2 GStreamer Pad Task 機制詳解
        • 核心接口與功能
        • 設計優勢
      • 3.2.3 loop 中做了哪些事情
      • 3.2.4 格式協商
    • 3.2 寫一個 AudioSource 插件
      • 3.2.1 初始化函數
      • 3.2.2 激活函數
      • 3.3.3 loop 循環
  • 總結
  • 參考


前言

GStreamer 中插件分為三種:Source、Filter 和 Sink,在上一章中我們學習了如何寫一個 Filter 插件,可以說 Filter 插件是最簡單的,因為它只需要關系數據的處理邏輯,而 Source 和 Sink 就更加復雜一些。本章我們來討論如何寫一個 Source 插件。

本章所提及的代碼你可以在 my_plugin 找到。

一、準備工作

準備工作與 GStreamer 簡明教程(十):插件開發,以一個音頻特效插件為例 中提到的類似,不再贅述。

二、Show me the code

接下來詳細說明代碼中各個細節,其中很多邏輯都是參考 audiotestsrc 來實現的,大家如果想自己對代碼進行詳細的分線,建議寫一個 audiotestsrc 的 demo,進行代碼調試。

3.1 線程模型分析

要理解GStreamer pipeline中的數據流動機制,需要首先明確其線程模型。我們以基礎音頻流水線為例:

audiotestsrc -> autoaudiosink
  1. 生產者audiotestsrc 元素,負責生成音頻測試信號
  2. 消費者autoaudiosink 元素,負責將音頻輸出到系統聲卡
消費端模型

autoaudiosink 的實現通常會依賴系統音頻服務(如ALSA/PulseAudio)的回調機制:

  • 系統音頻線程定期通過回調請求數據
  • 形成天然的"消費線程"驅動模型
生產端實現方案

生產者有兩種典型的實現范式:

方案A:Push模式(主動生產)

  • audiotestsrc 創建獨立的生產者線程
  • 持續生成數據并推送(push)至下游
  • 下游可能需維護數據緩沖區
  • 優勢:實現直接,適合連續數據流
  • 缺點:可能需要維護緩沖區

方案B:Pull模式(按需生產)

  • audiotestsrc 保持被動狀態
  • autoaudiosink需要數據時,通過鏈式調用向上游拉取(pull)
  • 優勢:流量控制精準
  • 難點:需要實現復雜的同步機制

由于Push模式更符合"生產者-消費者"的直觀理解,且實現復雜度較低,本文選擇方案A作為實現基礎。Pull模式涉及GStreamer更底層的調度機制,將在后續深入研究后另文探討。

3.2 深入 audiotestsrc 的實現邏輯

我們先分析官方 audiotestsrc 的關鍵實現,學習 GStreamer 標準 source 元素的調度機制。

3.2.1 生產者線程的啟動時機

當 pipeline 進入播放流程時,audiotestsrc 的生產者線程在 PAUSED 狀態下就已經啟動了。通過調試分析,其線程啟動流程如下:

典型觸發路徑

  1. 狀態切換觸發
    gst_element_set_state(pipeline, GST_STATE_PAUSED) 被調用時,會觸發對所有元素的 pad 激活操作:

    gst_pad_set_active(pad, TRUE)  // 激活所有 pad
    
  2. Pad 激活回調
    audiotestsrc 的 src pad 重寫了 activatemode_function,此時會調用繼承鏈:

    → gst_base_src_activate_mode()  // GstBaseSrc 的標準實現
    
  3. 任務線程創建
    gst_base_src_activate_mode() 中,最終通過:

    gst_pad_start_task(pad, gst_base_src_loop, ...)
    

    啟動獨立線程執行主循環邏輯

  4. 主循環工作
    gst_base_src_loop() 包含完整處理邏輯:

    • 格式協商(caps negotiation)
    • 發送 STREAM_START 事件
    • 生成音頻數據
    • 數據推送(gst_pad_push()

關鍵結論

  • 線程啟動的實際觸發點是 PAUSED 狀態下的 pad 激活,而非 PLAYING 狀態
  • 通過重寫 activatemode_function 可以自定義啟動邏輯
  • GstBaseSrc 已封裝標準線程調度框架,子類只需實現數據生成

3.2.2 GStreamer Pad Task 機制詳解

在 GStreamer 框架中,GstPad 不僅負責數據流的連接與協商,還提供了一套完整的異步任務(Task)接口,允許開發者將線程邏輯直接封裝在 Pad 層面,而非傳統的 Element 中。這一設計顯著提升了模塊化程度和靈活性。

核心接口與功能
  1. 任務啟動:gst_pad_start_task()

    gboolean gst_pad_start_task(GstPad *pad,GstTaskFunction func,gpointer user_data,GDestroyNotify notify
    );
    
    • 作用:啟動一個專用線程,循環執行指定的 GstTaskFunction
    • 關鍵特性
      • 線程會自動進入循環,持續調用目標函數,無需開發者手動實現循環邏輯。
      • 典型應用場景:在 GstBaseSrc 的子類中,gst_base_src_loop() 僅需實現單次數據生成邏輯,任務線程會負責循環調度。例如音頻源(audiosource)可通過此機制持續生成音頻幀。
  2. 任務暫停:gst_pad_pause_task()

    • 行為:臨時掛起任務線程的執行,但保留任務狀態(如內部變量)。
    • 用途:實現動態流控,如響應管道的暫停狀態或資源限制。
  3. 任務終止:gst_pad_stop_task()

    • 行為:完全停止任務線程并釋放相關資源。
    • 注意:與暫停不同,停止后需重新調用 start_task 才能恢復執行。
設計優勢
  • 邏輯解耦:將線程管理與業務邏輯分離,Element 只需關注數據處理,Pad Task 處理線程調度。
  • 性能優化:避免在 Element 層頻繁創建/銷毀線程,任務線程可復用。
  • 標準化的流控:通過統一的任務接口實現暫停/恢復,簡化狀態管理。

3.2.3 loop 中做了哪些事情

在 GStreamer 中,audiotestsrc 這類源元素(source element)通過 gst_pad_start_task 啟動一個任務循環(gst_base_src_loop),其中有兩件事情非常重要

  1. 格式協商(negotiate)
    和下游商量用什么格式傳遞數據(比如采樣率、位深等)。這一步確保數據能被正確處理。

  2. 生成數據并推送(push)
    按協商好的格式生成音頻數據,然后推給下游。

接下來我們重點講 格式協商,當格式確定后如何生成數據和推給下游就會變得簡單很多

3.2.4 格式協商

格式協商的前提是兩個元素已經 link 成功。兩個元素能夠相互連接的前提是它們 pad 的 Capability 是有交集的,比如 src pad 支持的音頻采樣率是 [1, 96000],那么如果下游支持的采樣率在這個范圍內,他們就能 link 成功,否則在元素 link 階段就會失敗

auto ok = gst_element_link_many(ele0, ele1, NULL);
if(!ok){printf("link failed");
}

在 link 階段僅僅是確認了元素之間支持的數據格式是包含一個子集的,那么接下來在運行階段,我們要從這個子集中,確認唯一的格式,這樣才能確定數據是以什么形式進行流動,例如確定音頻采樣率是 44100,聲道數 2,32位浮點數。也就是說,協商的過程就是找到一個這樣的固定格式,audiotestsrc 根據這個固定格式來生成音頻數據。為了說明這一點,我這邊舉一個簡單的例子。

有三個元素 Source、Filter 和 Sink,它們順序相互連接,支持的采樣率分別是:

  • Source : {16000, 32000}
  • Filter: [1, Max]
  • Sink: {32000, 44100, 48000}
+--------+       +--------+       +--------+
| Source |------>| Filter |------>|  Sink  |
+--------+       +--------+       +--------+
{16000, 32000}   [1, Max]       {16000, 32000, 44100, 48000}

首先,它們的采樣率有一個公共的子集,即 {16000, 32000},因此它們在 link 階段是成功的;接著,格式協商由 Source 發起,它用自己的格式作為格式過濾器(filter),獲取 peer 端的所支持的格式,流程大致是:

  1. Sink 支持采樣率 {16000, 32000, 44100, 48000}{16000, 32000} 取交集,得到 {16000, 32000} 記作 A
  2. Filter 支持采樣率 [1, Mac] 與 A 取交集,得到 {16000, 32000} 記作 B
  3. Source 支持采樣率 {16000, 32000} 與 B 取交集,得到 {16000, 32000} 記作 peercaps

這時候 peercaps 仍然是一個范圍,不是一個固定的值,最終由 source 來決定使用哪個固定值,固定下來后,再將它作為 source 的 caps,并通過事件通知給其他元素,其他元素會收到 GST_QUERY_ACCEPT_CAPS 事件,在這個事件中獲取 caps 數據做相應的處理。

// 獲取 source 的 caps
thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (src), NULL);
// 以 thiscaps 作為 filter,獲取 peercaps
peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps);
// basesrc 來決定最終使用哪些固定值
caps = gst_base_src_fixate (basesrc, caps);
// 固定 caps,并發送 GST_QUERY_ACCEPT_CAPS 事件
result = gst_base_src_set_caps (basesrc, caps);

3.2 寫一個 AudioSource 插件

了解了上面的知識后,我們來開始寫一個自己的音頻生成插件,為了讓代碼簡單,我們做了這些簡化:

  1. 只支持單聲道、F32LE 、interleave 格式的數據
  2. 只支持 push 模式

詳細的代碼實現參考 my_plugin,使用 demo 參考 gstmyaudiotestsrc_example

3.2.1 初始化函數

在類初始化函數如下:

static void gst_my_audio_test_src_class_init(GstMyAudioTestSrcClass *klass) {
//...gobject_class->set_property = gst_my_audio_test_src_set_property;gobject_class->get_property = gst_my_audio_test_src_get_property;gobject_class->finalize = gst_my_audio_test_src_finalize;gst_my_audio_test_class_init_install_properties(gobject_class, klass);
// ...
}

我們覆寫三個函數

  1. set_property,用于設置屬性值
  2. get_property,用于獲取屬性值
  3. finalize,用于類的析構,釋放一些申請的資源

gst_my_audio_test_class_init_install_properties 函數中注冊了屬性,具體大家自行看源碼,不展開說了。

實例的初始化函數如下:

static void gst_my_audio_test_src_init(GstMyAudioTestSrc *filter) {filter->impl = new GstMyAudioTestSrcImpl();filter->srcpad = gst_pad_new_from_static_template(&gst_audio_test_src_src_template, "src");gst_pad_set_activatemode_function(filter->srcpad,gst_my_audio_test_src_activate_mode);gst_element_add_pad(GST_ELEMENT(filter), filter->srcpad);
}
  1. 申請 GstMyAudioTestSrcImpl 實例,它用 c++ 來寫,可以簡化一些代碼邏輯
  2. 創建 srcpad,并設置 srcpad 的激活函數(_activatemode_function

3.2.2 激活函數

前面提到,Pad 的 _activatemode_function 是線程啟動的入口,我們看看函數邏輯是怎么樣的


static gboolean gst_my_audio_test_src_activate_mode(GstPad *pad,GstObject *parent,GstPadMode mode,gboolean active) {auto *src = GST_MYAUDIOTESTSRC(parent);switch (mode) {case GST_PAD_MODE_PULL: {res = gst_my_audio_test_src_pull();break;}case GST_PAD_MODE_PUSH: {res = gst_my_audio_test_src_activate_push(src->srcpad, parent, active);break;}// ...
}

目前 GST_PAD_MODE_PULL 是不支持的,因此看 GST_PAD_MODE_PUSH 即可

static gboolean gst_my_audio_test_src_activate_push(GstPad *srcpad,GstObject *parent,gboolean active) {if (active) {g_print("start loop");gst_pad_start_task(srcpad, (GstTaskFunction)gst_my_audio_test_src_loop,srcpad, NULL);} else {g_print("stop loop");gst_pad_stop_task(srcpad);}return TRUE;
}

_activate_push 函數很簡單,啟動 _src_loop 或者停止 _src_loop

3.3.3 loop 循環

接下來看最重要的 _src_loop 函數,主要做的兩個事情就是格式協商和數據填充

格式協商邏輯如下:


static void gst_my_audio_test_src_loop(GstPad *pad) {GstMyAudioTestSrc *src;src = GST_MYAUDIOTESTSRC(GST_OBJECT_PARENT(pad));GstCaps *caps = NULL;gboolean result = FALSE;if (gst_pad_check_reconfigure(pad)) {g_print("need renegotiate\n");GstCaps *thiscaps = gst_pad_query_caps(src->srcpad, NULL);GST_DEBUG_OBJECT(src, "caps of src: %" GST_PTR_FORMAT, thiscaps);GstCaps *peercaps = gst_pad_peer_query_caps(src->srcpad, thiscaps);GST_DEBUG_OBJECT(src, "caps of peer: %" GST_PTR_FORMAT, peercaps);if (peercaps) {caps = peercaps;gst_caps_unref(thiscaps);} else {caps = thiscaps;}if (caps && !gst_caps_is_empty(caps)) {caps = gst_my_audio_test_src_fixate(src, caps);caps = gst_caps_fixate(caps);GST_DEBUG_OBJECT(src, "fixated to: %" GST_PTR_FORMAT, caps);if (gst_caps_is_fixed(caps)) {/* yay, fixed caps, use those then, it's possible that the subclass* does not accept this caps after all and we have to fail. */result = gst_my_audio_test_src_set_caps(src, caps);if (result) {result = gst_pad_push_event(src->srcpad, gst_event_new_caps(caps));}}}if (!result) {GST_DEBUG_OBJECT(src, "negotiation failed");gst_pad_pause_task(pad);}}// ...
}

邏輯大致是:

  1. 獲取當前 src pad 的 caps,獲取 peer pad 的 caps,兩者取交集
  2. 調用 gst_my_audio_test_src_fixate 去設置 audio src 最期望的數據格式,然后調用 gst_caps_fixate 固化數據格式(此時所有數據格式已經確定,不再是一個范圍值)
  3. gst_my_audio_test_src_set_caps 函數從 caps 中獲取 AudioInfo 信息,拿到例如采樣率、聲道數等關鍵信息
  4. gst_pad_push_event 發送事件,通知下游數據格式

數據填充數據如下:

static void gst_my_audio_test_src_loop(GstPad *pad) {//...// generate audio dataGstBuffer *buf = NULL;guint blocksize = impl->samples_per_buffer;guint bufferSize = blocksize * sizeof(float);buf = gst_buffer_new_allocate(NULL, bufferSize, NULL);if (buf == NULL) {GST_DEBUG_OBJECT(src, "alloc buffer failed");}GstMapInfo map;gst_buffer_map(buf, &map, GST_MAP_WRITE);float *data = (float *)map.data;impl->fill(data, blocksize);auto ret = gst_pad_push(src->srcpad, buf);if (ret != GST_FLOW_OK) {GST_DEBUG_OBJECT(src, "push buffer failed");gst_pad_pause_task(pad);}
}
  1. gst_buffer_new_allocate 申請 GstBuffer,用于存放音頻數據
  2. gst_buffer_map 從 GstBuffer 中拿到可寫音頻數據的地址
  3. impl->fill(data, blocksize); 用于填充音頻數據,這部分用的是一個 LFO 生成器,具體邏輯大家可以不用在意。總之就是往一塊內存中,寫入生成的音頻數據,你甚至可以寫入隨機噪聲。。
  4. gst_pad_push 將數據 push 到下游

總結

以上,我們就將 AudioSource 如何生成數據的邏輯大致講了一遍,運行 gstmyaudiotestsrc_example 之后可以聽到正弦波的聲音。后面我會去研究下如何實現 pull 模式,以及支持更多類型的音頻數據和波形。

參考

  • my_plugin
  • gstmyaudiotestsrc_example

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78459.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78459.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78459.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux kernel signal原理(下)- aarch64架構sigreturn流程

一、前言 在上篇中寫到了linux中signal的處理流程,在do_signal信號處理的流程最后,會通過sigreturn再次回到線程現場,上篇文章中介紹了在X86_64架構下的實現,本篇中介紹下在aarch64架構下的實現原理。 二、sigaction系統調用 #i…

華為OD機試真題——簡易內存池(2025A卷:200分)Java/python/JavaScript/C++/C/GO最佳實現

2025 A卷 200分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析; 并提供Java、python、JavaScript、C、C語言、GO六種語言的最佳實現方式! 本文收錄于專欄:《2025華為OD真題目錄全流程解析/備考攻略/經驗…

騰訊一面面經:總結一下

1. Java 中的 和 equals 有什么區別?比較對象時使用哪一個 1. 操作符: 用于比較對象的內存地址(引用是否相同)。 對于基本數據類型、 比較的是值。(8種基本數據類型)對于引用數據類型、 比較的是兩個引…

計算機網絡中的DHCP是什么呀? 詳情解答

目錄 DHCP 是什么? DHCP 的工作原理 主要功能 DHCP 與網絡安全的關系 1. 正面作用 2. 潛在安全風險 DHCP 的已知漏洞 1. 協議設計缺陷 2. 軟件實現漏洞 3. 配置錯誤導致的漏洞 4. 已知漏洞總結 舉例說明 DHCP 與網絡安全 如何提升 DHCP 安全性 總結 D…

2025 年導游證報考條件新政策解讀與應對策略

2025 年導游證報考政策有了不少新變化,這些變化會對報考者產生哪些影響?我們又該如何應對?下面就為大家詳細解讀新政策,并提供實用的應對策略。 最引人注目的變化當屬中職旅游類專業學生的報考政策。以往,中專學歷報考…

【物聯網】基于LORA組網的遠程環境監測系統設計(ThingsCloud云平臺版)

演示視頻: 基于LORA組網的遠程環境監測系統設計(ThingsCloud云平臺版) 前言:本設計是基于ThingsCloud云平臺版,還有另外一個版本是基于機智云平臺版本,兩個設計只是云平臺和手機APP的區別,其他功能都一樣。如下鏈接: 【物聯網】基于LORA組網的遠程環境監測系統設計(機…

SQL 函數進行左邊自動補位fnPadLeft和FORMAT

目錄 1.問題 2.解決 方式1 方式2 3.結果 1.問題 例如在SQL存儲過程中,將1 或10 或 100 長度不足的時候,自動補足長度。 例如 1 → 001 10→ 010 100→100 2.解決 方式1 SELECT FORMAT (1, 000) AS FormattedNum; SELECT FORMAT(12, 000) AS Form…

Nacos簡介—2.Nacos的原理簡介

大綱 1.Nacos集群模式的數據寫入存儲與讀取問題 2.基于Distro協議在啟動后的運行規則 3.基于Distro協議在處理服務實例注冊時的寫路由 4.由于寫路由造成的數據分片以及隨機讀問題 5.寫路由 數據分區 讀路由的CP方案分析 6.基于Distro協議的定時同步機制 7.基于Distro協…

中電金信聯合阿里云推出智能陪練Agent

在金融業加速數智化轉型的今天,提升服務效率與改善用戶體驗已成為行業升級的核心方向。面對這一趨勢,智能體與智能陪練的結合應用,正幫助金融機構突破傳統業務模式,開拓更具競爭力的創新機遇。 在近日召開的阿里云AI勢能大會期間&…

十分鐘恢復服務器攻擊——群聯AI云防護系統實戰

場景描述 服務器遭遇大規模DDoS攻擊,導致服務不可用。通過群聯AI云防護系統的分布式節點和智能調度功能,快速切換流量至安全節點,清洗惡意流量,10分鐘內恢復業務。 技術實現步驟 1. 啟用智能調度API觸發節點切換 群聯系統提供RE…

LLM量化技術全景:GPTQ、QAT、AWQ、GGUF與GGML

01 引言 本文介紹的是在 LLM 討論中經常聽到的各種量化技術。本文的目的是提供一步一步的解釋和代碼,讓大家可以自己使用這些技術來壓縮模型。 閑話少說,我們來研究一下吧! 02 Quantization 量化是指將高精度數字轉換為低精度數字。低精…

IP的基礎知識以及相關機制

IP地址 1.IP地址的概念 IP地址是分配給連接到互聯網或局域網中的每一個設備的唯一標識符 也就是說IP地址是你設備在網絡中的定位~ 2.IP版本~ IP版本分為IPv4和IPv6,目前我們最常用的還是IPv4~~但是IPv4有個缺點就是地址到現在為止,已經接近枯竭~~&…

本地使用Ollama部署DeepSeek

以下是在本地使用Ollama部署DeepSeek的詳細教程,涵蓋安裝、修改安裝目錄、安裝大模型以及刪除大模型的操作步驟。 安裝Ollama 1. 系統要求 確保你的系統滿足以下條件: 操作系統:macOS、Linux或者Windows。足夠的磁盤空間和內存。 2. 安裝…

開源項目實戰學習之YOLO11:ultralytics-cfg-datasets-Objects365、open-images-v7.yaml文件(六)

👉 點擊關注不迷路 👉 點擊關注不迷路 👉 點擊關注不迷路 medical - pills.yaml 通常用于配置與醫學藥丸檢測任務相關的參數和信息 Objects365.yaml 用于配置與 Objects365 數據集相關信息的文件。Objects365 數據集包含 365 個不同的物體類別…

23種設計模式-行為型模式之策略模式(Java版本)

Java 策略模式(Strategy Pattern)詳解 🧠 什么是策略模式? 策略模式是一種行為型設計模式,它定義了一系列算法,把它們一個個封裝起來,并且使它們可以互相替換。策略模式讓算法獨立于使用它的客…

使用 AI Agent 改善師生互動的設計文檔

使用 AI Agent 改善師生互動的設計文檔 一、引言 1.1 研究背景 當前教育領域的師生互動存在諸多挑戰,如教師負擔過重、學生個體差異大導致難以滿足所有人的需求,以及信息傳遞延遲等問題。引入AI-Agent能夠有效緩解這些問題,通過自動化手段協…

2、Ubuntu 環境下安裝RabbitMQ

?. 安裝Erlang RabbitMqRabbitMq需要Erlang語?的?持,在安裝rabbitMq之前需要安裝erlang需要Erlang語?的?持,在安裝rabitMq之前需要安裝erlang。 安裝erlang # 更新軟件包 sudo apt-get update # 安裝 erlang sudo apt-get install erlang 查看er…

Node.js 操作 ElasticSearch 完整指南:從安裝到實戰

本文將手把手教你如何搭建 ElasticSearch 環境,并通過 Node.js 實現高效數據檢索。包含 10 個可直接復用的代碼片段,助你快速掌握搜索、聚合等核心功能! 環境搭建篇 1. ElasticSearch 安裝要點 下載 es下載連接 下載下來后,進…

硬核科普丨2025年安全、高效網絡準入控制系統深度解析

陽途網絡準入控制系統(Network Access Control,簡稱NAC)是當代網絡安全領域的重要工具,有效防止未經授權的訪問和數據泄露,保障網絡資源的安全性和完整性。本文將深入探討陽途網絡準入控制系統的的重要性和作用。 一、…

搜索二叉樹-key的搜索模型

二叉搜索樹(Binary Search Tree, BST)是一種重要的數據結構,它有兩種基本模型:Key模型和Key/Value模型。 一、Key模型 1.基本概念 Key模型是二叉搜索樹中最簡單的形式,每個節點只存儲一個鍵值(key),沒有額外的數據值(value)。這…