《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.mzph.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; <…