webrtc-rs RTCP 处理实战:用 Attribute::DeliverToApplication 拦截器把关键帧请求送达应用
音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载本文以 webrtc-rswebrtccrate仓库中的rtcp-processing示例为主线讲清楚一个关键问题入站 RTCP 默认是拦截器链的内部控制流量应用默认什么都看不到。要看到 RTCP必须写一个自定义拦截器把想要的那部分包打上Attribute::DeliverToApplication标记。读完后你将掌握如何注册这样的拦截器、为什么“打标”比“复制转发”更合理以及如何通过TrackRemoteEvent::OnRtcpPacket在异步侧稳定接收关键帧请求PLI/FIR并能用仓库自带的集成测试验证双向行为。默认行为入站 RTCP 为什么到不了应用rtcp-processing示例README、主程序演示的核心就是在没有特殊处理时浏览器发来的 RTCP 包会被拦截器链在到达应用之前全部消费掉。从 TrackRemoteEvent::OnRtcpPacket 的文档 可以看到官方对这个默认行为的明确定义当远程 track 上收到新的 RTCP 包且有拦截器把它们标记给应用时触发。入站 RTCP 是拦截器要处理的控制流量——接收报告喂给发送端统计、NACK 要被应答、传输层反馈驱动带宽估计——所以它默认在链尾结束、事件不触发。要收到它需要加一个把Attribute::DeliverToApplication挂到你想要的包上的拦截器rtcp-processing示例正是这么做的。这里“没有事件”应理解为“没有任何包被担保放行”而不是“对端一个都没发”。换句话说“应用收不到 RTCP”是设计意图而非 bugReceiver Report、NACK、Transport-Wide CC 这些包链路上的默认拦截器已经各自消费完毕。真正需要应用亲自出面的是关键帧请求——因为只有应用自己知道这条流是谁生产的、怎么重发关键帧。拦截器实现RtcpForwarderInterceptor 全解析示例的核心是一个自定义拦截器RtcpForwarderInterceptor它把入站 RTCP 收窄为关键帧请求PLI 与 FIR并给保留下来的包打上Attribute::DeliverToApplication。完整实现见 rtcp-processing.rs关键部分如下。判定“什么是关键帧请求”/// Whether an RTCP packet is a request for a keyframe. fn is_keyframe_request(packet: Boxdyn rtc::rtcp::Packet) - bool { let payload packet.as_any(); payload.is::PictureLossIndication() || payload.is::FullIntraRequest() }判定逻辑非常直接对包做 downcast 检查PictureLossIndicationPLI或FullIntraRequestFIR二者之一即为关键帧请求。这两个类型来自rtc::rtcp::payload_feedbacks是 RTCP 反馈负载的标准定义。读路径收窄 打标拦截器实现的是Protocoltrait入站用handle_read/poll_read出站用handle_write/poll_write读路径是这个示例的精髓fn handle_read(mut self, mut msg: TaggedPacket) - Result(), Self::Error { if let Packet::Rtcp(packets) msg.message.packet { let requests: VecBoxdyn rtc::rtcp::Packet packets .iter() .filter(|packet| is_keyframe_request(packet)) .cloned() .collect(); if requests.is_empty() { // Not the applications business, and the interceptors have already acted on it. return Ok(()); } msg.message.packet Packet::Rtcp(requests); // Inbound RTCP stops at the end of the chain unless something vouches for it. msg.message.add(Attribute::DeliverToApplication); } self.read_queue.push_back(msg); Ok(()) }这段代码做了三件事每一步都有源码注释佐证其意图过滤从 RTCP 复合包中筛出 PLI/FIR如果为空“不是应用该操心的事拦截器们也已经处理过了”直接return让原始包继续走读队列但不打标最终在链尾被消费收窄把msg.message.packet原地替换为只含关键帧请求的Packet::Rtcp(requests)后面环节看到的已经是精简后的内容打标msg.message.add(Attribute::DeliverToApplication)这就是“担保放行”的动作。为什么是“打标”而不是“复制转发”README 里专门用一节解释了这个设计决策值得完整理解标记之所以是必要机制是因为一份副本永远跑不过链尾链是一个扁平列表某个拦截器发出的包只会插回列表里它自己身后的位置而结束入站 RTCP 路径的那一级仍然在它前面。被打标的包则正常走完整条链那一级读到标记后放行。标记也让判定保持逐包进行——这正是要点这个示例为关键帧请求担保让接收报告继续停在它们一直停留的地方。也就是说拦截器链是“传送带”模型后发的包只会插回自己身后不存在一个位置能让包“越过链尾”。Attribute::DeliverToApplication让包正常走完行程由链尾的终止级读标记做决定同时判定保持逐包粒度——同一批包里PLI 被放行Receiver Report 依旧停在链尾。这正是“链级开关”无法做到的事。它必须放在链上什么位置示例代码中拦截器的文档注释写明Last这样在它收窄之前每一个读取 RTCP 的拦截器都已经完整看到了全部入站 RTCP。组装 registry 时对应这一句见 rtcp-processing.rs// Register default interceptors (NACK, reports, etc.) let registry register_default_interceptors(Registry::new(), mut media_engine)?; // Inbound RTCP is for the interceptors unless one of them marks a packet for the application. // Application-most, so every interceptor has already seen the whole of the inbound RTCP before // this one narrows it to keyframe requests and marks those. let registry registry.with(Slot::from(14_000), RtcpForwarderBuilder::new().build());先用register_default_interceptors注册默认拦截器NACK 应答、报告、拥塞控制等再用registry.with(Slot::from(14_000), ...)把自定义拦截器追加进 registry。Slot是链上的槽位序号14_000这个高序号把它排在默认拦截器之后——“靠后”保证了收窄动作不会截胡别人要读的 RTCP。完整构建流程从 MediaEngine 到 PeerConnection示例的run()函数展示了与仓库其他异步示例一致的PeerConnectionBuilder模式按顺序做以下几步注册编解码器MediaEngine 注册 VP8videopayload type 9690 kHz和 Opusaudiopayload type 11148 kHz双声道类型分别为RtpCodecKind::Video/RtpCodecKind::Audio注册拦截器即上面展示的register_default_interceptorsRtcpForwarderInterceptor配置 ICERTCConfigurationBuilder里放了一个stun:stun.l.google.com:19302服务器构建连接let peer_connection PeerConnectionBuilder::new() .with_configuration(config) .with_media_engine(media_engine) .with_interceptor_registry(registry) .with_handler(Arc::clone(handler) as Arcdyn PeerConnectionEventHandler) .with_runtime(runtime) .with_udp_addrs(vec![format!({}:0, signal::get_local_ip())]) .build() .await?;注意with_runtime本库没有环境默认运行时每个PeerConnection通过 builder 显式注入Arcdyn Runtime。示例里用的runtime()/block_on()等辅助函数来自 examples/common/mod.rs它用OnceLock缓存全进程一个default_runtime()并按 Cargo featureruntime-tokio或runtime-smol选择内置运行时——这也是为什么示例代码本身对运行时保持中立。添加 recvonly 收发器音频和视频各一个方向均为RTCRtpTransceiverDirection::Recvonlypeer_connection .add_transceiver_from_kind( RtpCodecKind::Video, Some(RTCRtpTransceiverInit { direction: RTCRtpTransceiverDirection::Recvonly, ..Default::default() }), ) .await?;这个 recvonly 身份对理解输出行为很关键本端只收不发所以下游浏览器方向不会向它要关键帧它自己打印什么完全取决于谁向它发了 PLI/FIR。构建、运行与命令行参数rtcp-processing在 Cargo.toml 中注册为[[example]]构建与运行命令来自 READMEcargo build --example rtcp-processing cargo run --example rtcp-processing # 带调试日志 cargo run --example rtcp-processing -- --debug # 从文件读取 SDP cargo run --example rtcp-processing -- --input-sdp-file offer.txt适用前提Cargo.toml的默认 feature 是[runtime-tokio, crypto-ring]按默认 feature 构建即可直接运行。CLI 定义用clapderive完整参数如下rtcp-processing.rs参数短选项默认值作用--debug-dfalse初始化env_logger按文件:行号 [级别] 时间戳 - 内容格式输出日志--log-level-lINFO日志级别过滤log::LevelFilter::from_str解析--input-sdp-file-i空非空时从该文件读取 offer SDP替代从 stdin 读--output-log-file-o空非空且--debug时日志写入该文件覆盖写否则输出到 stdout一个值得注意的细节--log-level只在--debug分支里生效——只有开了--debug才会初始化 loggerlog_level才是那套env_logger::Builder的过滤级别不传--debug时程序不产生日志。信令流程与浏览器配合README 给出的信令流程是从浏览器粘贴 base64 编码的 SDP offer把程序打印的 base64 answer 贴回浏览器浏览器开始发送音视频观察按远程 track 打印的入站 RTCP 包。浏览器那一半由仓库提供的 jsfiddle 页面承担jsfiddle 演示README 中给出的在线地址为 https://jsfiddle.net/zurq6j7x/ 。页面上有文本域、Start Session按钮和Copy browser SessionDescription to clipboard。其 demo.js 的逻辑是用getUserMedia拿到音视频流并addTrackcreateOffersetLocalDescription后等 ICE gathering 结束onicecandidate收到null候选时把 base64 的localDescription填入输入框startSession()则把粘贴进来的 base64 解码后setRemoteDescription。程序侧的对应代码rtcp-processing.rsprint!(Paste offer from browser and press Enter: ); let line if input_sdp_file.is_empty() { signal::must_read_stdin()? } else { std::fs::read_to_string(input_sdp_file)? }; let desc_data signal::decode(line.as_str())?; // base64 解码 let offer serde_json::from_str::RTCSessionDescription(desc_data)?; peer_connection.set_remote_description(offer).await?; let answer peer_connection.create_answer(None).await?; peer_connection.set_local_description(answer).await?;等待 gathering 完成后answer 会被 base64 编码打印到终端let json_str serde_json::to_string(local_desc)?; let b64 signal::encode(json_str); println!(\nPaste this answer in your browser:\n{}\n, b64);异步侧接收TrackRemoteEvent::OnRtcpPacketREADME 给出的最小接收循环是while let Some(evt) track.poll().await { if let TrackRemoteEvent::OnRtcpPacket(rtcp_packets) evt { for packet in rtcp_packets { let header packet.header(); println! (Type: {:?}, header.packet_type); println! ({packet}); } } }示例的完整处理在on_track回调里拿到track_id、stream_id、kind、codec等元信息打印后向 runtime spawn 一个轮询任务。每到一批包rtcp-processing.rsTrackRemoteEvent::OnRtcpPacket(rtcp_packets) { let batch rtcp_count.fetch_add(1, Ordering::Relaxed) 1; println!( Keyframe request #{} (Track: {}) , batch, track_id); for (i, packet) in rtcp_packets.iter().enumerate() { let header packet.header(); println!( [{}] Type: {:?}, Length: {} words, i 1, header.packet_type, header.length ); for line in format!({packet}).lines() { println!( {}, line); } } } TrackRemoteEvent::OnEnded { /* 打印后 break */ } TrackRemoteEvent::OnError { /* 打印后 break */ }rtcp_count是共享的ArcAtomicU64退出时打印Total RTCP packets received。这里按 README 的说法打印的是“header 可读正文”header.packet_type给出类型如 PLI、FIRformat!({packet})是rtc::rtcp包实现的 Display 输出逐行打印。两个行为边界需要注意均来自 README没有拦截器打标时OnRtcpPacket根本不会触发——链在到达应用前就终止了入站 RTCP 路径本例只放行 PLI 和 FIR而且本端是 recvonly所以下游不会向它要关键帧在没有下游发起请求前它什么都不会打印。想看全部入站 RTCP 怎么办README 的答案是加宽拦截器的担保范围而不是删掉它。即在handle_read里对每个Packet::Rtcp无条件add(Attribute::DeliverToApplication)。仓库里的集成测试恰好就写了这样一个对照组DeliverAllRtcp——对matches!(msg.message.packet, Packet::Rtcp(_))的包一律打标rtcp_processing_webrtc2webrtc.rs。反例同样重要如果把拦截器整条删掉“就没有任何东西去给包打标”应用将彻底看不到 RTCP。集成测试双向 RTCP 投递的验证test_rtcp_processing_webrtc2webrtc 是这个示例的双端集成测试值得作为理解该机制的“标准答案”读一遍。它的拓扑是offerer 发送一条 VP8 流answerer 接收。两个端点各自装不同的拦截器offerer发送方用RtcpForwarderInterceptor收窄臂因为它会被请求关键帧answerer接收方用DeliverAllRtcp放行臂它只收不发不会被要关键帧读的是关于所收流的报告。测试里 answerer 每 200 ms 通过track.write_rtcp显式发送一对包PLI Receiver Report。这一设计让断言变得严格Receiver Report 是“一定会到达 offerer 链路、但必须被 forwarder 丢掉”的对照流量。最终断言rtcp_count 2answerer 通过TrackRemoteEvent::OnRtcpPacket看到了关于所收流的 RTCP接收侧local_rtcp_count 1offerer 通过TrackLocal::poll→TrackLocalEvent::OnRtcpPacket看到了关于自己所发流的 RTCP 反馈发送侧leaked 0收窄臂没有让任何一个非 PLI/FIR 的包漏到应用——“drop 半边”是对已确认到达的流量断言的而不是对着“没有事件”断言的。发送侧事件TrackLocalEvent::OnRtcpPacket的文档track_local/mod.rs与接收侧完全对称例如 Receiver Report或 PLI/FIR 关键帧请求。SFU 会把这类反馈向上传递给发布者。仅对拦截器打了Attribute::DeliverToApplication的反馈触发默认链上永远不会触发——这里的沉默意味着没有包被担保而非对端没发。小结入站 RTCP 默认属于拦截器Receiver Report 喂统计、NACK 被应答、TWCC 驱动带宽估计应用默认看不到任何 RTCP让应用看到 RTCP 的唯一手段是拦截器逐包打标Attribute::DeliverToApplication而不是复制转发链是扁平列表副本只会插回自己身后或删掉拦截器删了就没人打标了RtcpForwarderInterceptor的完整姿势过滤 PLI/FIR → 原地收窄复合包 → 打标 →registry.with(Slot::from(14_000), ...)放在默认拦截器之后应用侧通过TrackRemoteEvent::OnRtcpPacket接收流与TrackLocalEvent::OnRtcpPacket发送流两条事件通道接收想全量放行时把拦截器判定加宽为无条件打标即可tests/rtcp_processing_webrtc2webrtc.rs 里的DeliverAllRtcp就是现成参照。延伸阅读examples/README.md 对rtcp-processing的一句话索引、track_remote 与 track_local 的 API 文档、以及 examples/common/mod.rs 中示例共享的运行时辅助。赞分享音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载相关推荐Docmost拦截器HTTP请求响应处理Docmost拦截器HTTP请求响应处理 在现代化的Web应用开发中拦截器Interceptor扮演着至关重要的角色。Docmost作为一款开源的协作文后端前端内容协同知识库Electron.NET 请求拦截实战用 WebRequest 拦截与改写 HTTP 请求Electron.NET 请求拦截实战用 WebRequest 拦截与改写 HTTP 请求 导读 WebRequest 是 Electron 会话 Ses桌面应用跨平台Changes邮件通知系统配置实时构建状态推送方案终极指南Changes邮件通知系统配置实时构建状态推送方案终极指南 在持续集成和持续交付的现代软件开发流程中 实时构建状态通知 是确保团队高效协作的关键。Chang后端运维上一篇Flink 托管状态自定义序列化TypeSerializer 与 TypeSerializerSnapshot 完整实践指南下一篇pandoc 测试用例 6709 解析--file-scope 模式下制表符仍须按 --tab-stop 展开创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

InfluxDB 官方 Docker 镜像完整使用指南:InfluxDB 3 Core 部署、v1/v2 兼容与版本迁移

InfluxDB 官方 Docker 镜像完整使用指南:InfluxDB 3 Core 部署、v1/v2 兼容与版本迁移

文档 【免费下载链接】docs Documentation for Docker Official Images in docker-library 项目地址: https://gitcode.com/gh_mirrors/docs/docs 点击查看 免费下载 InfluxDB 是一款专为采集、存储和处理大规模事件数据与时序数据而设计的时序数据库平台&#xff…

2026/10/13 6:48:51 阅读更多 →
binance-trading-bot 生产运营手册:部署、应急停机、安全与可观测性实战指南

binance-trading-bot 生产运营手册:部署、应急停机、安全与可观测性实战指南

金融科技后端前端 【免费下载链接】binance-trading-bot Automated Binance trading bot with pluggable strategies, historical backtesting, and a live dashboard 项目地址: https://gitcode.com/gh_mirrors/bi/binance-trading-bot 点击查看 免费下载 本文基于…

2026/10/13 6:48:51 阅读更多 →
【AI智能体】Codex 浏览器操作自动化任务实战操作详解

【AI智能体】Codex 浏览器操作自动化任务实战操作详解

目录 一、前言 二、Codex 介绍 2.1 Codex 是什么 2.2 Codex能做什么? 2.3 Codex 浏览器插件介绍 2.3.1 Codex 浏览器插件是什么 2.3.2 有什么特点 2.3.3 Codex 浏览器插件优势 2.3.4 Codex 浏览器插件使用场景 三、Codex 内置浏览器使用 3.1 Codex 内置浏…

2026/10/13 6:48:51 阅读更多 →

最新新闻

Spring Boot 3.x API密钥轮换机制:从状态机设计到平滑过渡

Spring Boot 3.x API密钥轮换机制:从状态机设计到平滑过渡

做后端开发这几年,只要接口走的是 API 密钥认证,密钥轮换就是一个绕不开的话题。Spring Boot 3.x 项目里,密钥轮换的坑尤其隐蔽:表面上看,更新一下数据库里的 key,客户端换一下配置就行,但实际一…

2026/10/13 7:38:09 阅读更多 →
深度学习优化算法与参数初始化:从梯度下降到动量实战

深度学习优化算法与参数初始化:从梯度下降到动量实战

2020年2月16日,我的深度学习笔记写到第八篇。上一篇从经验风险最小化聊到泛化误差,基本把“优化目标”讲清楚了;但目标归目标,真正让模型训练起来的是优化算法本身。这篇笔记把镜头拉到更新公式这一层,重点拆两块&…

2026/10/13 7:38:09 阅读更多 →
真三MOD专用蓝宝石渲染管线2.2b优化指南

真三MOD专用蓝宝石渲染管线2.2b优化指南

简介:本资源是魔兽争霸III玩家专用的自定义地图优化版本,面向熟悉真三国无双类RPG地图的中高级玩家,解决原版平衡性不足、技能冗余或运行卡顿等问题。压缩包为RAR格式,共2个文件:核心地图文件BT蓝宝石专署2.2b.w3x&…

2026/10/13 7:38:09 阅读更多 →
SpringBoot毕业生就业去向登记管理系统:毕设源码与二次开发指南

SpringBoot毕业生就业去向登记管理系统:毕设源码与二次开发指南

简介:这份资源是面向计算机专业毕业生与Java初学者的一套完整就业去向登记管理系统源码,可直接作为毕业设计或课程设计参考。系统基于SpringBoot框架开发,覆盖需求分析、总体设计、详细设计与功能测试全流程,包含功能结构、数据结…

2026/10/13 7:38:09 阅读更多 →
Spring Boot 3.x API密钥认证与密钥轮换实战指南

Spring Boot 3.x API密钥认证与密钥轮换实战指南

“你的服务对接了十几个下游调用方,大家约定好每次请求头里带一个 API Key,服务端校验通过才放行。密钥用了一年,安全团队突然要求立即轮换,你改配置、重启、发通知,结果重启期间服务不可用,调用方新旧密钥…

2026/10/13 7:38:09 阅读更多 →
链表分解:从内存布局到指针偏移的底层实践

链表分解:从内存布局到指针偏移的底层实践

1. 项目概述:这不是“拆链子”,而是把链表“掰开揉碎”讲清楚 “链表的分解”这五个字,乍看像一道算法题的标题,但实际在一线教学、代码评审和系统调试中,它代表一种极其关键的底层思维训练——不是单纯地写个反转或删…

2026/10/13 7:37:09 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →