当前位置: 首页 > news >正文

基于RDA5807M的FM收音机模块开发指南:从I2C驱动到RDS解析

1. 项目概述与核心价值

如果你和我一样,对嵌入式开发充满热情,同时又对模拟信号处理、无线电这类“老派”技术抱有好奇,那么ScoutMakes FM Radio Board绝对是一个能让你玩上好几个周末的宝藏模块。它本质上是一个将RDA5807这颗经典的FM收音机芯片,通过I2C总线“驯服”成一个标准数字外设的桥梁。这意味着,你不再需要和复杂的模拟电路、中周变压器打交道,只需几根杜邦线,就能在你的Arduino、Raspberry Pi Pico或者任何支持I2C的微控制器上,轻松实现一个功能完整的数字调频收音机。

这个模块的核心价值在于它的“桥梁”属性。RDA5807本身是一颗性能不错的单芯片FM立体声收音机解决方案,但它传统的控制接口相对复杂。ScoutMakes团队通过一个精心设计的电平转换和接口电路,将其所有功能——从搜台、调谐到音量控制、RDS数据解析——都映射到了标准的I2C寄存器上。这使得开发者可以完全摆脱底层硬件驱动的困扰,专注于应用逻辑的开发。无论是想做一个带RDS显示的桌面时钟收音机,还是一个能自动录制特定节目的智能设备,这个模块都提供了绝佳的起点。它特别适合那些希望将无线广播功能集成到物联网项目、交互式艺术装置或复古科技改造中的开发者。

2. 硬件深度解析与设计思路

2.1 核心芯片RDA5807M功能拆解

RDA5807M是上海锐迪科微电子推出的一款低功耗FM立体声收音机芯片。它的强大之处在于高度集成:内部集成了从RF输入到音频输出的完整信号链,包括低噪声放大器、混频器、中频滤波器、立体声解码器以及RDS/RBDS解调器。这意味着,外部只需要极少的无源元件(主要是晶振和几个电容电阻)就能工作。

从开发者的视角看,这颗芯片有几个关键特性值得关注:

  1. 数字调谐:采用锁相环频率合成技术,调谐精度高,无机械磨损,且支持全球FM频段(通常为76-108MHz,可通过配置选择)。
  2. 软件控制:所有功能,包括频率设置、音量、静音、高低音控制、搜台模式等,都通过读写内部寄存器来实现。这正是它能被I2C控制的基础。
  3. RDS支持:RDS(Radio Data System)是FM广播中嵌入的数字信息流,可以传递电台名称、节目类型、实时交通信息甚至简单的文本消息。RDA5807M能硬件解码RDS数据,并通过中断或轮询方式告知主控制器,大大减轻了MCU的负担。
  4. 低功耗设计:支持软关机模式,在电池供电的便携设备中非常有用。

ScoutMakes板的设计思路,就是为这颗功能强大的芯片提供一个“友好”的访问接口。他们并没有重新发明轮子,而是做了一层优秀的“翻译”和“适配”。

2.2 ScoutMakes板电路设计亮点

拿到模块后,我习惯性地用放大镜观察了一下PCB。它的设计非常工整,体现了模块化设计的思路。

电源管理部分:板载了一个低压差线性稳压器。这是非常关键的一步,它允许模块接受3.3V至5V的宽电压输入,并稳定输出RDA5807M所需的工作电压(通常是1.8V或3.3V,取决于芯片版本)。这意味着你可以安全地将其连接到3.3V逻辑的现代微控制器(如ESP32、RP2040)或传统的5V Arduino上,而无需担心电平不匹配或损坏芯片。

I2C电平转换与上拉:模块上的SCL和SDA线路集成了双向电平转换电路。我实测用3.3V的QT Py和5V的Arduino Uno都能稳定通信。板上还预留了10kΩ的上拉电阻,对于大多数应用场景,你甚至不需要在总线上额外添加上拉电阻,这简化了连线。不过,如果你的I2C总线很长或者挂载设备很多,可能需要根据情况调整上拉电阻的阻值。

天线接口设计:模块提供了一个标准的焊盘(标记为ANT)用于连接天线。FM广播的波长约为3米,因此最佳天线长度是1/4波长,约75厘米。在实际使用中,我通常焊接一根60-80厘米的普通导线,或者使用带鳄鱼夹的导线临时连接。天线的好坏直接决定了接收灵敏度和RDS数据的稳定性。板子没有集成磁性天线,所以对于便携设备,你需要妥善布置这根导线。

音频输出:标准的3.5mm耳机接口可以直接驱动32欧姆的耳机。需要注意的是,输出功率有限,驱动大阻抗的扬声器会音量不足或音质变差。如果需要接音箱,建议增加一级音频放大电路,或者使用自带功放的有源音箱。

LED电源指示灯与跳线:板子背面有一个红色的电源LED和一个LED使能跳线。对于追求极致低功耗的项目(比如太阳能供电的户外气象站收音机),你可以用美工刀小心地割断这个跳线上的铜箔,彻底关闭指示灯,能节省几个毫安的电流。这个细节考虑得很周到。

3. 开发环境搭建与基础驱动

3.1 CircuitPython环境实战

我首先在Adafruit QT Py RP2040上测试了CircuitPython方案。它的优势是“即插即用”,交互性强,非常适合快速原型验证。

第一步:固件与库准备

  1. 访问CircuitPython官网,为你的开发板下载最新的UF2固件文件。
  2. 将板子置于Bootloader模式(通常通过双击复位键),会出现一个名为RPI-RP2的U盘,将下载的UF2文件拖入其中。板子会自动重启,并出现一个名为CIRCUITPY的U盘。
  3. 获取驱动库。你需要Adafruit的社区捆绑包(Community Bundle)。下载后,在解压的lib文件夹中找到tinkeringtech_rda5807m.mpy文件(这是一个预编译的库文件)。
  4. tinkeringtech_rda5807m.mpy文件复制到CIRCUITPY磁盘的lib文件夹内。如果lib文件夹不存在,就新建一个。

注意:务必使用社区捆绑包中的库,或者从TinkeringTech的GitHub仓库获取最新版。直接pip install可能找不到这个专用库。

第二步:硬件连接使用一根STEMMA QT/Qwiic连接线(4芯I2C线)将收音机模块和QT Py连接起来。这种连接线防反插,非常方便。同时,将天线(一根导线)焊接到ANT焊盘上,并插入耳机或音箱到3.5mm接口。

第三步:运行示例代码将社区捆绑包examples文件夹中的rda5807m_simpletest.py代码复制到CIRCUITPY磁盘的根目录,并重命名为code.py。这样板子一上电就会自动运行该程序。

打开Mu编辑器或任何支持串行终端的软件(如PuTTY、VS Code的串行监视器),连接到QT Py的串口,波特率通常为115200。你会立刻看到一个交互式菜单。

# 代码关键点解析(基于原示例): import tinkeringtech_rda5807m # 预设电台列表,单位是10kHz,8930代表89.30 MHz presets = [8930, 9510, 9710, 9950, 10100, 10110, 10650] # 初始化I2C,board.STEMMA_I2C()是QT Py等板子的专用方法,自动配置I2C引脚 i2c = board.STEMMA_I2C() # 初始化收音机对象,地址0x11,音量初始为3(范围0-15) radio = tinkeringtech_rda5807m.Radio(i2c, presets[3], 3) radio.set_band(“FM”) # 明确设置为FM波段

在串口输入?查看所有命令。例如,输入>切换到下一个预设频率,输入+增加音量。如果当前频率有RDS信号,你会看到电台名称、节目信息等文字在终端滚动显示。

实操心得

  • 首次上电无声音:检查天线是否连接。没有天线或天线太短,芯片可能无法锁定任何电台,表现为只有噪音或完全无声。我建议至少连接50厘米的导线。
  • RDS数据乱码:这是正常现象,尤其在信号边缘区域。RDS协议需要连续接收并校验多组数据包才能还原出正确字符串。保持电台信号稳定几十秒,乱码会逐渐变成正确的文字。
  • 修改预设电台:直接编辑代码中的presets列表。你需要知道你当地电台的频率,例如102.7 MHz,就写成10270。你可以用手机上的收音机APP或网络搜索来查。

3.2 Arduino IDE环境配置

对于资源受限或需要更高执行效率的项目,Arduino是更传统和直接的选择。

第一步:库安装

  1. 打开Arduino IDE,点击“项目” -> “加载库” -> “管理库...”。
  2. 在库管理器中搜索“RDA5807”。我推荐使用由matthias-bs维护的版本。安装它。
  3. 这个库包含了多个示例,我们将使用SerialRadio这个最全面的示例。

第二步:硬件连接如果你使用的开发板(如Arduino Uno)没有STEMMA QT接口,就需要使用杜邦线进行连接:

ScoutMakes模块引脚Arduino Uno引脚
VCC (红色)5V
GND (黑色)GND
SDA (蓝色)A4 (或SDA)
SCL (绿色)A5 (或SCL)

同样,务必接上天线和音频输出设备。

第三步:上传与测试

  1. 在Arduino IDE中,选择正确的开发板和端口。
  2. 打开文件->示例->RDA5807->SerialRadio
  3. 上传代码到开发板。
  4. 打开串口监视器,将波特率设置为115200。你将看到一个更丰富的文本菜单。

Arduino库的示例代码功能更强大,菜单导航更直观。它通常将频率显示、信号强度(RSSI)、立体声状态和RDS信息整合在一个刷新显示的界面中。

避坑指南

  • 编译错误“找不到Wire.h:这通常不会发生,因为Wire是Arduino核心库。如果出现,检查你是否选择了正确的开发板型号(例如Arduino Uno)。
  • I2C地址错误:ScoutMakes模块的固定地址是0x11。如果你同时连接了其他I2C设备,确保地址不冲突。库中初始化时通常使用RDA5807M radio;,然后在setup()里调用radio.init();,它会自动使用默认地址0x11进行探测。
  • 音量过大或失真:Arduino示例的初始音量可能设置得较高。你可以在串口菜单中先调低音量,或者修改代码中radio.setVolume(4);这样的初始化语句(音量范围一般是0-15)。

4. 核心功能实现与代码精讲

4.1 频率调谐与搜台算法

无论是CircuitPython还是Arduino,调谐的核心都是向RDA5807的特定寄存器写入目标频率值。频率值需要转换成一个16位的字。

频率计算公式: 对于FM波段,通常每个频道间隔100kHz(0.1MHz)。RDA5807期望的寄存器值CHAN可以通过以下公式计算:CHAN = (目标频率(KHz) - 基准频率(KHz)) / 频道间隔(KHz)

例如,全球大部分地区FM波段从87.0 MHz开始,频道间隔100kHz。要调谐到101.1 MHz:CHAN = (101100 - 87000) / 100 = 141

将这个十进制数141转换为十六进制0x008D,并写入对应的寄存器。库函数已经封装了这个过程,我们只需调用radio.setFrequency(10110)(注意单位是10kHz)。

自动搜台实现: 搜台功能是收音机的灵魂。RDA5807支持硬件搜台,只需设置相应寄存器位即可启动。

  1. 向上/向下搜索:设置SEEKUPSEEKDOWN位为1,芯片会自动从当前频率开始向高频或低频搜索,直到找到一个信号强度(RSSI)超过设定阈值的电台,然后自动停止并锁定。
  2. 搜索阈值:可以通过寄存器设置搜台灵敏度。阈值设得太高,可能会跳过一些弱台;设得太低,可能会锁在噪音上。库的seek()函数内部通常有一个合理的默认值。
  3. 软件实现逻辑:在代码中,搜台通常是一个循环过程。以向上搜台为例:
    // 伪代码逻辑 void seekUp() { radio.startSeek(UP); // 启动硬件搜台 delay(100); // 等待一段时间,让硬件完成搜索 while (!radio.isSeekComplete()) { // 检查搜索是否完成 delay(10); } radio.stopSeek(); // 停止搜索 uint16_t currentFreq = radio.getFrequency(); // 获取当前锁定频率 // 可以将找到的频率存入预设列表 }
    在实际使用中,我建议在搜台时加入超时判断,避免在无信号区域陷入死循环。

4.2 RDS数据解析与应用

RDS功能是这个模块的亮点。它允许广播电台发送数字信息,如PS(节目服务名称,通常是电台呼号,如“BBC R1”)和RT(广播文本,如歌曲名和歌手)。

数据获取流程

  1. 使能RDS:首先需要通过寄存器配置使能RDS接收。
  2. 轮询或中断:RDA5807提供了两种方式通知主机RDS数据就绪:一种是轮询RDSR寄存器的RDSR标志位;另一种是利用INT引脚产生中断。ScoutMakes模块没有引出INT引脚,因此我们只能使用轮询方式。
  3. 读取数据块:RDS数据以4个16位字(共64位)为一个组(Group)发送。需要连续读取4个寄存器来获取一个完整的数据组。
  4. 解析与解码:获取的原始数据需要根据RDS标准(EN 50067)进行解析。这包括校验、判断组类型(0A, 2A等)、提取PI(节目标识)、PTY(节目类型)、PSRT文本等信息。

库函数封装: 幸运的是,我们使用的库已经完成了最复杂的解析工作。以CircuitPython库为例,它内部维护了一个RDSParser对象。

def textHandle(rdsText): print(“RDS Text:”, rdsText) rds.attach_text_callback(textHandle)

你只需要像上面这样附加一个回调函数,当有新的RDS文本(通常是RT)被完整解码后,这个函数就会被自动调用。PS信息则通常可以通过radio.get_rds_ps()这样的方法直接获取。

应用创意

  • 电台信息显示器:将PS信息显示在OLED屏幕上,做成一个始终显示电台名称的迷你收音机。
  • 歌曲识别器:捕获RT信息中的歌曲名,通过串口发送到电脑,甚至联动Spotify或音乐识别API。
  • 交通信息过滤器:解析PTY代码,当收到交通公告(PTY=29)时自动提高音量或记录。

4.3 音频控制与音效设置

除了基本的音量控制,RDA5807还提供了一些改善听感的音效设置,这些都可以通过库函数轻松调用。

音量控制radio.setVolume(level)level范围通常是0-15。0为静音,但注意和Mute功能不同。静音radio.setMute(true/false)。硬件静音,直接关闭音频输出,响应速度快。高低音控制

  • 低音增强radio.setBassBoost(true/false)。这个功能会提升低频响应,在听音乐或语音时效果明显,但在小扬声器上可能引起失真。
  • 强制单声道radio.setMono(true/false)。在信号很弱、立体声分离度差导致噪音大时,强制单声道可以显著改善信噪比,使声音更清晰。
  • 软静音radio.setSoftMute(true/false)。这是一个很有用的功能。当信号变弱时,芯片不是突然切断声音(硬静音),而是逐渐降低音量,提供更平滑的收听体验。我建议在车载或便携应用中将此功能开启。

实操心得:音质调试: 音质很大程度上取决于信号强度。在串口菜单中,使用r命令可以读取RSSI(接收信号强度指示器)值。这个值越高(最大约75),信号越好。

  1. 找到一个信号稳定的电台(RSSI > 65)。
  2. 尝试开关低音增强立体声/单声道,在耳机中仔细聆听区别。你会发现,对于弱信号电台,切换到单声道后背景嘶嘶声会减少。
  3. 调整天线方向和长度,观察RSSI值的变化,找到最佳接收位置。这对于内置天线的最终产品设计至关重要。

5. 项目进阶与系统集成

5.1 构建一个完整的桌面网络收音机

单独一个收音机模块玩法有限,但将其作为核心,与其他模块结合,就能创造出有趣的项目。这里以构建一个带有时钟、天气显示和网络日志功能的桌面收音机为例。

系统架构

  • 主控:ESP32-S3开发板(兼具Wi-Fi功能和足够的GPIO)。
  • 显示:I2C接口的OLED显示屏(SSD1306,128x64)。
  • 输入:旋转编码器(用于调节音量、切换电台)。
  • 音频输出:仍使用模块的3.5mm接口接有源音箱。
  • 网络功能:通过Wi-Fi获取NTP时间、天气API数据,并将收听记录(电台、时间)上传到私有服务器或物联网平台。

硬件连接: 由于所有设备都使用I2C,你需要一个I2C多路复用器(如TCA9548A),或者确保地址不冲突。ScoutMakes模块地址是0x11,OLED通常是0x3C,复用器本身地址可配置。旋转编码器则连接到ESP32的普通GPIO引脚。

软件逻辑核心

// 伪代码框架 #include <Wire.h> #include <RDA5807M.h> #include <Adafruit_SSD1306.h> #include <WiFi.h> #include <time.h> RDA5807M radio; Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { Serial.begin(115200); Wire.begin(); // 初始化收音机、显示屏、Wi-Fi radio.init(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); connectToWiFi(); configTime(“CST-8”, “pool.ntp.org”); // 配置中国时区 // 显示初始化界面:时间、天气、当前电台 updateDisplay(); } void loop() { // 1. 检查旋转编码器,处理音量/换台 handleEncoder(); // 2. 检查RDS数据,更新OLED上的电台名和歌曲信息 checkAndUpdateRDS(); // 3. 每分钟同步一次网络时间,每半小时获取一次天气 updateTimeAndWeather(); // 4. 如果电台改变,记录日志(时间+频率)到SD卡或发送到服务器 logStationChange(); delay(50); // 主循环延迟 }

这个项目的难点在于多任务调度和显示界面的布局。你可以使用状态机模型来管理不同的显示页面(主页面-收音机,页面二-时钟天气,页面三-设置菜单)。

5.2 低功耗便携式收音机设计

如果你想用18650电池供电,让收音机持续工作数天,低功耗设计是关键。

功耗分析

  • RDA5807M芯片本身:在工作模式下,电流约为15-20mA。在待机模式下,可降至几个微安。
  • 微控制器:使用像ATmega328P(Arduino Pro Mini所用)这类芯片,在深度睡眠模式下电流可低于1mA。
  • 外围电路:ScoutMakes板上的稳压器和LED是主要耗电点。务必切断LED跳线。

系统设计要点

  1. 电源管理:使用高效率的DC-DC降压模块为整个系统供电,而不是线性稳压器。
  2. 间歇性工作:如果不是一直需要收听,可以让系统大部分时间处于睡眠状态。例如,做一个定时唤醒录音机:每半小时唤醒一次,打开收音机,检查特定频率(如交通台),如果有RDS交通标志(PTY=29)则开始录音,否则继续睡眠。
  3. 软件优化
    // Arduino低功耗示例(使用LowPower库) #include <LowPower.h> #include <RDA5807M.h> RDA5807M radio; const int wakeUpPin = 2; // 连接一个按钮,用于唤醒 void setup() { pinMode(wakeUpPin, INPUT_PULLUP); radio.init(); radio.setVolume(5); radio.setFrequency(10110); // 预设一个电台 radio.powerDown(); // 初始化后先关机 } void loop() { // 进入深度睡眠,外部中断唤醒 attachInterrupt(digitalPinToInterrupt(wakeUpPin), wakeUp, FALLING); LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); detachInterrupt(digitalPinToInterrupt(wakeUpPin)); // 唤醒后的操作 radio.powerUp(); delay(100); // 等待芯片稳定 // ... 播放收音机 ... delay(60000); // 播放一分钟 radio.powerDown(); // 再次关机,进入睡眠 } void wakeUp() { // 中断处理函数,什么都不做即可 }
  4. 天线优化:使用高灵敏度的有源天线或磁性天线可以改善接收,从而允许你降低收音机芯片的RF增益,间接节省功耗。

5.3 常见问题排查速查表

在实际开发中,你肯定会遇到各种问题。下面这个表格汇总了我踩过的一些坑和解决方法:

现象可能原因排查步骤与解决方案
上电后无任何反应,LED不亮1. 电源接反或未接通。
2. 电压不足或过高。
3. 模块损坏。
1. 用万用表检查VCC和GND之间电压是否为3.3V-5V。
2. 检查连接线是否完好。
3. 尝试更换一个电源或开发板。
I2C扫描不到设备(地址0x11)1. I2C线路接错(SDA/SCL交叉)。
2. 总线电平不匹配(5V MCU与3.3V模块未转换)。
3. 总线冲突或上拉电阻问题。
1. 确认SDA接SDA,SCL接SCL。
2. ScoutMakes板自带电平转换,一般无需担心。检查MCU是否已正确初始化I2C(Wire.begin())。
3. 尝试断开其他I2C设备,单独连接收音机模块。如果总线过长,可适当减小上拉电阻(如改为4.7kΩ)。
有电流但无声,或全是噪音1. 天线未连接或太短。
2. 频率未调谐到有效电台。
3. 音量设置为0或静音开启。
4. 耳机/音箱损坏或未插紧。
1.这是最常见原因!务必连接至少50cm导线作为天线。
2. 使用串口命令扫描(.,)或手动设置本地已知频率(f 10110)。
3. 发送+命令增加音量,发送u命令解除静音。
4. 更换音频设备测试。
RDS数据持续乱码或没有1. 信号太弱。
2. 当前电台未发送RDS信号。
3. 代码中RDS功能未正确使能。
1. 改善天线,移动到开阔地带,查看RSSI值(r命令)是否大于60。
2. 并非所有电台都支持RDS。尝试切换到本地的公共广播电台或大型音乐台。
3. 检查代码,确认radio.init()或相关配置已开启RDS功能。
搜台功能停不下来或找不到台1. 搜台信号强度阈值设置不当。
2. 所在区域FM信号极弱。
1. 查阅库的API,看是否有设置seekThreshold的函数,适当调低阈值(更敏感)。
2. 尝试手动设置一个已知的强大本地电台频率,确认模块本身工作正常。可能需要外接更好的天线。
使用特定MCU时工作不稳定1. I2C时钟速度过快。
2. 电源噪声干扰。
1. 尝试在代码中降低I2C时钟频率(如Arduino的Wire.setClock(100000)设为标准100kHz)。
2. 在模块的VCC和GND之间并联一个100uF的电解电容和一个0.1uF的陶瓷电容,进行电源去耦。

最后,分享一个调试小技巧:当你怀疑是硬件连接问题时,一个简单的I2C扫描程序能提供巨大帮助。在Arduino中,运行File->Examples->Wire->scanner,它能列出总线上所有设备的地址。如果能看到0x11,至少证明物理连接和基本通信是正常的,问题可能出在软件配置或射频部分。

http://www.cnnetsun.cn/news/2447395.html

相关文章:

  • NeoPixel省电实战:Gamma校正与动画算法优化指南
  • Linux本地包签名生产排障流程
  • 使用FastLED库与Arduino实现WS2812B动态调色板灯光秀
  • 避坑指南:S32K3xx的DTCM里藏着栈,DMA访问不了局部变量怎么办?
  • 构建跨游戏模组管理平台:XXMI启动器的架构设计与实现
  • [ 应急恢复篇 ] Kali Linux 单用户模式实战:root密码遗忘后的系统级修复
  • 基于光传感器与舵机的万圣节互动惊吓盒制作指南
  • 从嵌入式音频到口型同步:基于Teddy Ruxpin的DIY故事玩具改造全流程
  • 面向具身操作的视觉-语言-动作模型:让机器人真正理解并执行人类指令
  • Keil MDK中解决LPC1788 Trace调试同步问题
  • OpenClaw用户指南,如何正确配置Taotoken作为其大模型供应商
  • 别再只会看任务管理器了!用Perfmon监控Windows性能,这5个关键计数器才是真香
  • 从Linux 0.11的缺页处理,看现代操作系统特性(写时复制、延迟分配)的雏形
  • Claude 不是来打工的,是来当金融系统“水电工”的!
  • 降重工具怎么选?能同时降知网和维普重复率和AIGC疑似率的才是王者!
  • DeepSeek专家模式不能传文件?5分钟搭一个“能读文档的V4-Pro”
  • 软考中级嵌入式——第一章 计算机系统基础
  • 【网络安全】圈内热门逆向工具 TOP9 合集
  • Arduino电池电压监测:从ADC采样到低功耗设计的完整方案
  • SC4541SKTRT 2MHz 2.9V~22V升/降压单线LED驱动器Semtech电子元器件IC芯片
  • .NET + Surging 微服务引擎,快速搭建多协议物联网平台
  • AI时代的技术趋势:为什么软件正在回归CLI?
  • AI 挖洞新思路、深度解析两大间接提示词注入漏洞攻防思路,注入也能获得上万美金
  • Arm SVE2向量存储指令ST3Q/ST4Q详解与应用优化
  • 星露谷物语Stardew Valley-服务器命令教程
  • 多店铺场景下如何通过快手订单接口实现订单数据的统一聚合管理?
  • NotebookLM研究问题质量不稳定,如何用3层校验机制+2个黄金指标实现98.6%问题可用率
  • 一行环境变量,给 Claude Code 省下 90% 成本
  • 2026本地视频怎么去水印?3种免费方法+4款必备工具实测对比
  • AI 写代码比你强?别慌,这才是程序员真正的护城河