《ESP32音頻開發實戰:I2S協議解析與WAV音頻錄制/播放全指南》

前言

在智能硬件和物聯網應用中,音頻處理能力正成為越來越重要的功能——無論是語音交互、環境音采集,還是音樂播放,都離不開高效的音頻數據傳輸與處理。而I2S(Inter-IC Sound)作為專為音頻設計的通信協議,正是實現這些功能的核心技術。

本文將以ESP32為例,深入剖析I2S協議的工作原理,詳解TDM與PDM兩種通信模式的差異,并通過實戰代碼演示如何用MicroPython實現音頻錄制(PCM原始數據)、WAV文件解析與播放。無論你是想打造一個語音識別設備、自定義音頻播放器,還是探索實時音效處理,這篇指南都將為你提供從理論到實踐的完整路徑。

I2S簡介

I2S(Inter-IC Sound,集成電路內置音頻總線)是一種同步串行通信協議,通常用于在兩個數字音頻設備之間傳輸音頻數據。

ESP32-S3 包含 2 個 I2S 外設。通過配置這些外設,可以借助 I2S 驅動來輸入和輸出采樣數據。

TDM 通信模式(標準)

I2S 總線包含以下幾條線路:

  • MCLK:主時鐘線。該信號線可選,具體取決于從機,主要用于向 I2S 從機提供參考時鐘。
  • BCLK:位時鐘線。用于數據線的位時鐘。
  • WS:字(聲道)選擇線。通常用于識別聲道。
  • DIN/DOUT:串行數據輸入/輸出線。如果 DIN 和 DOUT 被配置到相同的 GPIO,數據將在內部回環。

PDM 通信模式

I2S 總線包含以下幾條線路:

  • CLK:PDM 時鐘線。
  • DIN/DOUT:串行數據輸入/輸出線。

每個 I2S 控制器都具備以下功能,可由 I2S 驅動進行配置:

  • 可用作系統主機或從機
  • 可用作發射器或接收器
  • DMA 控制器支持流數據采樣,CPU 無需單獨復制每個采樣數據

每個控制器都有獨立的 RX 和 TX 通道,連接到不同 GPIO 管腳,能夠在不同的時鐘和聲道配置下工作。注意,盡管在一個控制器上 TX 通道和 RX 通道的內部 MCLK 相互獨立,但輸出的 MCLK 信號只能連接到一個通道。如果需要兩個互相獨立的 MCLK 輸出,必須將其分配到不同的 I2S 控制器上。

. 對比總結

特性

TDM

PDM

核心目標

多路信號時分復用

高精度模數信號轉換

適用場景

周期性數據(語音、固定速率流)

高動態模擬信號(音頻、傳感器)

抗噪能力

依賴信道質量

強(數字脈沖抗干擾)

硬件復雜度

中等(需同步電路)

低(單比特量化)

延遲

低(固定時隙)

較高(過采樣+濾波)

參考鏈接: I2S - ESP32-S3 - — ESP-IDF 編程指南 v5.4.1 文檔

為什么要學習I2S

  • 高質量音頻傳輸:I2S是專為音頻設計的通信協議,能夠傳輸高質量的音頻數據,適合音頻播放、錄音等應用。
  • 低延遲:I2S支持實時音頻處理,適合對延遲要求高的場景,如語音識別或實時音頻效果處理。
  • ESP32內置I2S外設:ESP32集成了I2S接口,可直接連接麥克風、DAC、ADC等音頻設備,簡化硬件設計。
  • 靈活性:I2S支持多種數據格式和采樣率,適應不同的音頻需求。
  • 音頻播放與錄音:可用于音樂播放器、錄音設備等。
  • 語音識別與控制:適合智能音箱、語音助手等需要音頻輸入輸出的設備。
  • 音效處理:支持實時音效處理,如均衡器、混音器等。
  • 低功耗:ESP32的I2S外設在低功耗模式下仍能高效工作,適合電池供電設備。
  • 高性能:ESP32的高性能處理器結合I2S,能夠處理復雜的音頻任務。

總之I2S有助于開發高質量的音頻應用,擴展項目功能,尤其在物聯網和智能設備領域具有廣泛應用。豐富的資源和強大的硬件支持使得學習和開發更加便捷。

PCM原始數據

I2S錄制聲音

"""
使用I2S讀取數據
數據寬度16bit
采樣率16000Hz
緩沖區大小1024
"""from machine import I2S
from machine import Pin
import timesck_pin = Pin(14)
ws_pin = Pin(13)
sd_in_pin = Pin(12)
sd_out_pin = Pin(45)audio_in = I2S(0, sck=sck_pin, ws=ws_pin, sd=sd_in_pin, mode=I2S.RX,     # only RX mode availablebits=16,         # 數據寬度16bit,2字節format=I2S.MONO, # 單通道MONO, 雙通道STEREOrate=16000,      # 采樣率16000Hzibuf=2048        # 緩沖區大小1024字節
)
print("I2S init complete!")# 等待I2S初始化完成
# time.sleep_ms(500)# 所有數據的列表
frames = []print("開始錄制...")
# 錄制5s
start = time.time()
# 讀取數據
while True:if time.time() - start > 5:break# 創建一個字節數組buf = bytearray(1024)num = audio_in.readinto(buf)frames.append(buf)# 將音頻數據寫到文件
with open("audio.pcm", "wb") as f:for frame in frames:f.write(frame)audio_in.deinit();print("錄音結束:", len(frames), "幀")
# 合并所有數據
data = b''.join(frames)
print("數據長度:", len(data))

I2S播放聲音

"""
使用I2S播放數據
數據寬度16bit
采樣率16000Hz
緩沖區大小1024
"""from machine import I2S
from machine import Pin
import timesck_pin = Pin(14)
ws_pin = Pin(13)
sd_in_pin = Pin(12)
sd_out_pin = Pin(45)# sd引腳要設置為sd_out_pin
# 這里要注意用I2S.TXaudio_i2s = I2S(0, sck=sck_pin, ws=ws_pin, sd=sd_out_pin, mode=I2S.TX,     # only TX mode availablebits=16,         # 數據寬度16bit,2字節format=I2S.MONO, # 單通道MONO, 雙通道STEREOrate=16000,      # 采樣率16000Hzibuf=2048        # 緩沖區大小1024字節
)
print("I2S init complete!")# 等待I2S初始化完成
#time.sleep_ms(500)
# 讀取音頻文件
print("playing...")
counter = 0
with open("./audio.pcm", "rb") as f:while True:buffer = f.read(1024)if buffer:print("counter: ", counter)counter+=1audio_i2s.write(buffer)else:breakaudio_i2s.deinit()
print("play complete...")

WAV音頻

WAV 文件的前 44 個字節是文件頭部分,包含了音頻文件的元數據(如采樣率、位寬、聲道數等)。WAV 文件頭遵循 RIFF 格式規范,以下是其詳細結構:


WAV 文件頭結構(44 字節)

偏移量

字段名稱

大小(字節)

描述

0

Chunk ID

4

固定為 "RIFF"

,表示文件是一個 RIFF 格式的文件。

4

Chunk Size

4

文件總大小減去 8 字節(即文件大小 - 8)。

8

Format

4

固定為 "WAVE"

,表示這是一個 WAV 文件。

12

Subchunk1 ID

4

固定為 "fmt "

,表示接下來的部分是格式信息。

16

Subchunk1 Size

4

格式信息的大小(通常是 16 字節)。

20

Audio Format

2

音頻格式(PCM 為 1,表示未壓縮)。

22

Num Channels

2

聲道數(1 表示單聲道,2 表示立體聲)。

24

Sample Rate

4

采樣率(如 44100 Hz)。

28

Byte Rate

4

每秒的字節數(Sample Rate * Num Channels * BitsPerSample / 8

)。

32

Block Align

2

每個采樣點的字節數(Num Channels * BitsPerSample / 8

)。

34

Bits Per Sample

2

每個采樣點的位數(如 16 位)。

36

Subchunk2 ID

4

固定為 "data"

,表示接下來的部分是音頻數據。

40

Subchunk2 Size

4

音頻數據的大小(字節數)。

44

Data

N

音頻數據(從第 44 字節開始)。

解析wav格式數據

struct.unpack 是 Python 中用于將二進制數據解析為 Python 數據類型的函數。它通常用于處理二進制文件、網絡協議數據或硬件設備的原始數據。struct.unpackstruct.pack 的逆操作,后者用于將 Python 數據類型打包為二進制數據。


struct.unpack 的基本用法
struct.unpack(fmt, buffer)
  • fmt:格式化字符串,指定如何解析二進制數據。
  • buffer:包含二進制數據的字節對象(如 bytesbytearray)。
  • 返回值: 返回一個元組,包含解析后的數據。

格式化字符串 (fmt)

格式化字符串由以下部分組成:

  1. 字節順序(可選):
    • @:本地字節順序(默認)。
    • =:本地字節順序,忽略對齊。
    • <:小端序(低位字節在前)。
    • >:大端序(高位字節在前)。
    • !:網絡字節順序(大端序)。
  1. 數據類型
    • c:字符(1 字節)。
    • b:有符號字節(1 字節)。
    • B:無符號字節(1 字節)。
    • ?:布爾值(1 字節)。
    • h:有符號短整型(2 字節)。
    • H:無符號短整型(2 字節)。
    • i:有符號整型(4 字節)。
    • I:無符號整型(4 字節)。
    • l:有符號長整型(4 字節)。
    • L:無符號長整型(4 字節)。
    • q:有符號長長整型(8 字節)。
    • Q:無符號長長整型(8 字節)。
    • f:浮點型(4 字節)。
    • d:雙精度浮點型(8 字節)。
    • s:字符串(需要指定長度,如 10s 表示 10 字節的字符串)。
    • p:Pascal 字符串(1 字節長度 + 字符串)。
    • x:填充字節(跳過 1 字節)。

示例 1:解析單個值
import struct# 二進制數據(4 字節的無符號整型)
buffer = b'\x01\x00\x00\x00'# 解析為無符號整型
value = struct.unpack('<I', buffer)
print(value)  # 輸出: (1,)
示例 2:解析多個值
import struct# 二進制數據(2 個有符號短整型)
buffer = b'\x01\x00\x02\x00'# 解析為 2 個有符號短整型
values = struct.unpack('<2h', buffer)
print(values)  # 輸出: (1, 2)
示例 3:解析混合類型
import struct# 二進制數據(1 個無符號短整型 + 1 個浮點型)
buffer = b'\x01\x00\x00\x00\x00\x00\x80\x3f'# 解析為無符號短整型和浮點型
values = struct.unpack('<Hf', buffer)
print(values)  # 輸出: (1, 1.0)
示例 4:解析字符串
import struct# 二進制數據(10 字節的字符串)
buffer = b'hello\x00\x00\x00\x00\x00'# 解析為 10 字節的字符串
value = struct.unpack('<10s', buffer)
print(value)  # 輸出: (b'hello\x00\x00\x00\x00\x00',)
示例 5:解析 WAV 文件頭
import struct# 假設這是 WAV 文件的前 44 字節
wav_header = b'RIFF\x24\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x44\xAC\x00\x00\x10\xB1\x02\x00\x04\x00\x10\x00data\x00\x00\x00\x00'# 解析 WAV 文件頭
chunk_id = struct.unpack('<4s', wav_header[0:4])[0]
chunk_size = struct.unpack('<I', wav_header[4:8])[0]
format = struct.unpack('<4s', wav_header[8:12])[0]
subchunk1_id = struct.unpack('<4s', wav_header[12:16])[0]
subchunk1_size = struct.unpack('<I', wav_header[16:20])[0]
audio_format = struct.unpack('<H', wav_header[20:22])[0]
num_channels = struct.unpack('<H', wav_header[22:24])[0]
sample_rate = struct.unpack('<I', wav_header[24:28])[0]
bits_per_sample = struct.unpack('<H', wav_header[34:36])[0]print("Chunk ID:", chunk_id)
print("Chunk Size:", chunk_size)
print("Format:", format)
print("Subchunk1 ID:", subchunk1_id)
print("Subchunk1 Size:", subchunk1_size)
print("Audio Format:", audio_format)
print("Num Channels:", num_channels)
print("Sample Rate:", sample_rate)
print("Bits Per Sample:", bits_per_sample)

注意事項
  1. 字節順序
    • 確保格式化字符串中的字節順序與數據的實際存儲順序一致。
    • 小端序(<)和大端序(>)是最常用的兩種字節順序。
  1. 數據對齊
    • 某些平臺可能要求數據對齊,可以使用 @= 來指定本地字節順序。
  1. 緩沖區大小
    • 確保緩沖區的大小與格式化字符串的要求一致,否則會拋出 struct.error
  1. 返回值
    • struct.unpack 始終返回一個元組,即使只解析一個值。

總結
  • struct.unpack 是 Python 中處理二進制數據的強大工具。
  • 通過格式化字符串,可以靈活地解析各種數據類型。
  • 在處理文件、網絡協議或硬件數據時,struct.unpack 非常有用。

實操演練
from machine import I2S, Pin
import struct# 配置I2S
i2s = I2S(0,  # I2S編號sck=Pin(14),  # 時鐘引腳ws=Pin(13),   # 字選擇引腳sd=Pin(45),   # 數據引腳mode=I2S.TX,  # 發送模式bits=16,      # 數據位寬format=I2S.MONO,  # 單聲道rate=16000,   # 采樣率ibuf=40000    # 輸入緩沖區大小
)# 解析WAV文件頭
def parse_wav_header(file):header = file.read(44)  # WAV文件頭長度為44字節if header[0:4] != b'RIFF' or header[8:12] != b'WAVE':raise ValueError("不是有效的WAV文件")ret = struct.unpack("4s",header[0:4])print("ret=",ret,header[0:4].decode())# 提取采樣率、位寬、聲道數等信息sample_rate = struct.unpack('<I', header[24:28])[0]bits_per_sample = struct.unpack('<H', header[34:36])[0]num_channels = struct.unpack('<H', header[22:24])[0]data_size = struct.unpack('<I', header[40:44])[0]return sample_rate, bits_per_sample, num_channels, data_size# 打開WAV文件
with open('audio.wav', 'rb') as f:sample_rate, bits_per_sample, num_channels, data_size = parse_wav_header(f)# 播放音頻數據buffer_size = 1024  # 每次讀取的緩沖區大小while True:buffer = f.read(buffer_size)if not buffer:break  # 文件讀取完畢i2s.write(buffer)  # 通過I2S發送音頻數據# 關閉I2S
i2s.deinit()print("播放完成")

保存wav格式數據

from machine import I2S, Pin
import struct# 配置I2S
i2s = I2S(0,  # I2S編號sck=Pin(14),  # 時鐘引腳ws=Pin(13),   # 字選擇引腳sd=Pin(12),   # 數據引腳mode=I2S.RX,  # 接收模式bits=16,      # 數據位寬format=I2S.MONO,  # 單聲道rate=16000,   # 采樣率ibuf=40000    # 輸入緩沖區大小
)# WAV文件參數
sample_rate = 16000  # 采樣率
bits_per_sample = 16  # 位寬
num_channels = 1  # 單聲道
duration = 5  # 錄制時長(秒)
buffer_size = 1024  # 每次讀取的緩沖區大小# 計算總數據量
total_samples = sample_rate * duration
total_data_size = total_samples * num_channels * (bits_per_sample // 8)# 創建WAV文件頭
def create_wav_header(sample_rate, bits_per_sample, num_channels, data_size):# WAV文件頭格式header = bytearray()header.extend(b'RIFF')  # Chunk IDheader.extend(struct.pack('<I', 36 + data_size))  # Chunk Sizeheader.extend(b'WAVE')  # Formatheader.extend(b'fmt ')  # Subchunk1 IDheader.extend(struct.pack('<IHHIIHH', 16, 1, num_channels,sample_rate,sample_rate * num_channels * (bits_per_sample // 8),num_channels * (bits_per_sample // 8),bits_per_sample))  # Subchunk1 Sizeheader.extend(b'data')  # Subchunk2 IDheader.extend(struct.pack('<I', data_size))  # Subchunk2 Sizereturn header# 創建WAV文件頭
wav_header = create_wav_header(sample_rate, bits_per_sample, num_channels, total_data_size)# 打開文件并寫入WAV文件頭
with open('audio.wav', 'wb') as f:f.write(wav_header)# 讀取音頻數據并寫入文件samples_read = 0while samples_read < total_samples:buffer = bytearray(buffer_size)i2s.readinto(buffer)  # 從I2S讀取數據f.write(buffer)  # 寫入文件samples_read += buffer_size // (bits_per_sample // 8)# 關閉I2S
i2s.deinit()print("錄音完成,文件已保存為 audio.wav")

結語

通過本文的學習,你已經掌握了ESP32的I2S音頻開發全流程:從硬件接口配置、PCM原始數據采集,到WAV文件頭的解析與生成,最終實現完整的音頻錄制與播放功能。這些技術可以廣泛應用于智能音箱、錄音筆、實時語音傳輸等場景。

技術的價值在于創造。不妨嘗試將這些代碼擴展為更復雜的應用——比如結合Wi-Fi實現遠程音頻流傳輸,或添加回聲消除算法提升音質。如果在實踐中遇到問題,不妨回顧I2S的時序特性或WAV文件格式的細節,往往能從中找到答案。

聲音是人與機器最自然的交互方式,而你現在已經握住了開啟這扇大門的鑰匙。愿你的項目因音頻而生動,因技術而卓越! 🎵


小提示

  • 實際開發時,注意根據硬件(如麥克風、DAC模塊)調整I2S的采樣率、位寬等參數。

  • WAV文件頭中的字段(如聲道數、數據大小)必須與音頻數據嚴格匹配,否則可能導致播放失敗。

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

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

相關文章

大數據實時數倉的數據質量監控解決方案

實時數倉不僅僅是傳統數據倉庫的升級版,它更強調數據的實時性、流動性和高可用性,通過對海量數據的即時處理和分析,為企業提供近乎實時的洞察力。這種能力在金融、零售、制造、互聯網等行業中尤為關鍵,例如,電商平臺可以通過實時數倉監控用戶行為,動態調整推薦算法;金融…

56認知干貨:智能化產業

如果在不久的未來,一座高樓大廈的建設,只需將圖紙輸入系統,無數臺機器人就能精準協作完成任務; 電影節的主角不再是人類,動漫與影視作品將不再需要人類創作; 當播種和收獲的工作無人參與,所有過程都能自動化進行; 這將預示著我們將迎來一個智能化社會,在這個社會中,…

使用synchronized關鍵字同步Java線程

問題 在Java多線程編程中&#xff0c;你需要保護某些數據&#xff0c;防止多個線程同時訪問導致數據不一致或程序錯誤。 解決方案 在需要保護的方法或代碼段上使用synchronized關鍵字。 討論 synchronized關鍵字是Java提供的同步機制&#xff0c;用于確保在同一時刻只有一…

MATLAB基于格拉姆角場與2DCNN-BiGRU的軸承故障診斷模型

本博客來源于CSDN機器魚&#xff0c;未同意任何人轉載。 更多內容&#xff0c;歡迎點擊本專欄目錄&#xff0c;查看更多內容。 目錄 0 引言 1 格拉姆角場原理 2 2DCNN-BiGRU網絡結構 3 應用實例 3.1 數據準備 3.2 格拉姆角場數據提取 3.3 網絡模型搭建-重中之重 3.4 …

電氣設備器件選型參數---斷路器

斷路器 一、基本電氣參數 額定電壓&#xff08;Ue&#xff09; 必須≥系統最高工作電壓&#xff08;如380V、660V等&#xff09;。 注意直流/交流系統的區別&#xff0c;直流斷路器需專門設計。 額定電流&#xff08;In&#xff09; 根據負載的持續工作電流選擇&#xff0c;…

Linux常用命令30——groupadd創建新的用戶組

在使用Linux或macOS日常開發中&#xff0c;熟悉一些基本的命令有助于提高工作效率&#xff0c;groupadd命令的功能是創建新的用戶組。每個用戶在創建時都有一個與其同名的基本組&#xff0c;后期可以使用groupadd命令創建出新的用戶組信息&#xff0c;讓多個用戶加入指定的擴展…

微信小程序 自定義組件 標簽管理

環境 小程序環境&#xff1a; 微信開發者工具&#xff1a;RC 1.06.2503281 win32-x64 基礎運行庫&#xff1a;3.8.1 概述 基礎功能 標簽增刪改查&#xff1a;支持添加/刪除單個標簽、批量刪除、重置默認標簽 數據展示&#xff1a;通過對話框展示結構化數據并支持復制 動…

wpf CommandParameter 傳遞MouseWheelEventArgs參數 ,用 MvvmLight 實現

在 WPF 中使用 MVVM Light 框架傳遞 MouseWheelEventArgs 參數至 CommandParameter,可通過以下步驟實現: ?1. XAML 中配置事件綁定? 在控件上通過 EventToCommand 綁定鼠標滾輪事件,并啟用 PassEventArgsToCommand 屬性以傳遞事件參數: <!-- 命名空間聲明 --> x…

vmware diffy配置ollama 本機ip無法訪問

防火墻直接關閉 本地測試&#xff0c;給它直接關了 ollama配置 vim /etc/systemd/system/ollama.service這是的配置 [Unit] DescriptionOllama Service Afternetwork-online.target[Service] Environment"OLLAMA_HOST0.0.0.0:11434" #Environment"OLLAMA_OR…

React--》掌握react構建拖拽交互的技巧

在這篇文章中將深入探討如何使用react-dnd&#xff0c;從基礎的拖拽操作到更復雜的自定義功能帶你一步步走向實現流暢、可控且用戶友好的拖拽體驗,無論你是剛接觸拖拽功能的初學者還是想要精細化拖拽交互的經驗開發者&#xff0c;都能從中找到適合自己的靈感和解決方案。 目錄 …

數據結構與算法:回溯

回溯 先給出一些leetcode算法題&#xff0c;以后遇見了相關題目再往上增加 主要參考代碼隨想錄 2.1、組合問題 關于去重&#xff1a;兩種寫法的性能分析 需要注意的是&#xff1a;使用set去重的版本相對于used數組的版本效率都要低很多&#xff0c;大家在leetcode上提交&#x…

iview 分頁改變每頁條數時請求兩次問題

問題 在iview page分頁的時候&#xff0c;修改每頁條數時&#xff0c;會發出兩次請求。 iview 版本是4.0.0 原因 iview 的分頁在調用on-page-size-change之前會調用on-Change。默認會先調用on-Change回到第一頁&#xff0c;再調用on-page-size-change改變分頁顯示數量 此時就會…

一周學會Pandas2 Python數據處理與分析-Pandas2復雜數據查詢操作

鋒哥原創的Pandas2 Python數據處理與分析 視頻教程&#xff1a; 2025版 Pandas2 Python數據處理與分析 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 前面我們學了.loc[]等幾個簡單的數據篩選操作&#xff0c;但實際業務需求往 往需要按照一定的條件甚至復雜的組合條件…

【Vue bug】:deep()失效

vue 組件中使用了 element-plus 組件 <template><el-dialog:model-value"visible":title"title":width"width px":before-close"onClose"><div class"container" :style"{height:height px}"&g…

Trae 安裝第三方插件支持本地部署的大語言模型

Trae 安裝第三方插件支持本地部署的大語言模型 0. 引言1. 安裝插件 0. 引言 字節發布的 Trae IDE 一直不支持本地部署的的大語言模型。 Qwen3 剛剛發布&#xff0c;想在 Trae 中使用本地部署的 Qwen3&#xff0c;我們可以在 Trae 中安裝其他插件。 1. 安裝插件 我們可以安裝…

JavaScript 中的 Proxy 與 Reflect 教程

目錄 get 和 set 捕獲器詳解 為什么要用 Reflect? 使用語法間接調用內部方法 使用 Reflect 直接調用內部方法 對比總結: Reflect API 及其與 Proxy 的配合 Proxy 的典型應用場景 Proxy 是 ES6 引入的一種元編程特性。它允許創建一個代理對象來包裝目標對象,并攔截對目標…

基于STM32的心電圖監測系統設計

摘要 本論文旨在設計一種基于 STM32 微控制器的心電圖監測系統&#xff0c;通過對人體心電信號的采集、處理和分析&#xff0c;實現對心電圖的實時監測與顯示。系統采用高精度的心電信號采集模塊&#xff0c;結合 STM32 強大的數據處理能力&#xff0c;能夠有效去除噪聲干擾&a…

C語言----操作符詳解(萬字詳解)

目錄 1. 操作符的分類 2. 二進制和進制轉換 3. 原碼 反碼 補碼 4. 移位操作符 4.1 左移操作符 >> 4.2 右移操作符 >> 5. 位操作符 5.1 按位與 & 5.2 按位或 | 5.3 按位異或 ^ 5.4 按位取反 ~ 練習 整數存儲在內存中二進制中1的個數 練習 二進制位…

【進階】C# 委托(Delegate)知識點總結歸納

1. 委托的基本概念 定義&#xff1a;委托是一種類型安全的函數指針&#xff0c;用于封裝方法&#xff08;靜態方法或實例方法&#xff09;。 核心作用&#xff1a;允許將方法作為參數傳遞&#xff0c;實現回調機制和事件處理。 類型安全&#xff1a;委托在編譯時會檢查方法簽…

WebRTC 服務器之Janus視頻會議插件信令交互

1.基礎知識回顧 WebRTC 服務器之Janus概述和環境搭建-CSDN博客 WebRTC 服務器之Janus架構分析-CSDN博客 2.插件使用流程 我們要使?janus的功能時&#xff0c;通常要執?以下操作&#xff1a; 1. 在你的??引入 Janus.js 庫&#xff0c;即是包含janus.js&#xff1b; <…