Rust webrtc 实战:用 play-from-disk-vpx 将磁盘上的 VP8/VP9 视频与 Opus 音频推送到浏览器
音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载本文以 examples/play-from-disk-vpx/README.md 为主体讲解 how to 用 Rust 的 webrtc 库Async-friendly WebRTC implementation in Rust把保存在磁盘上的 IVF 视频文件VP8/VP9 编码和 OGG 音频文件Opus 编码作为媒体源通过标准 Offer/Answer 非 Trickle ICE 信令流程实时推送到浏览器播放。读完本文你将掌握完整的 ffmpeg 造数据命令、示例程序的构建与运行方式含 Linux/macOS 与 Windows 两套操作、全部 CLI 参数含义以及示例源码中媒体引擎配置、SDP 交换、媒体 pacing 与退出时序的源码级实现细节。play-from-disk-vpx 是什么play-from-disk-vpx演示了如何把从磁盘读取的 VP8/VP9 视频和/或音频发送给你的浏览器。从源码 examples/play-from-disk-vpx/play-from-disk-vpx.rs 的整体结构看它的媒体管线是用IVFReader/OggReader逐帧/逐页解析磁盘文件来自rtc::media::io模块把解析出的原始帧写入TrackLocalStaticSample轨道由PeerConnection完成 Offer/Answer 协商、ICE 连接后轨道内容被封装成 RTP 包经 UDP 发出浏览器端通过一个带两个 text-area 和 Start Session 按钮的 Web 信令页接收并播放。与play-from-disk-h26xH.264/H.265 的 MP4 源不同本示例的输入是IVF 容器output_vp8.ivf/output_vp9.ivf和OGG 容器output.ogg对应 WebRTC 生态中浏览器端最通用、兼容性最好的 VP8/VP9 Opus 组合。第一步用 ffmpeg 准备媒体文件原文档给出的造数据命令如下$INPUT_FILE为你任意一个输入媒体文件# VP8 视频IVF 容器GOP 间隔 30 帧 ffmpeg -i $INPUT_FILE -g 30 output_vp8.ivf # VP9 视频IVF 容器显式指定 libvpx-vp9 编码器 ffmpeg -i $INPUT_FILE -g 30 -c libvpx-vp9 output_vp9.ivf # Opus 音频只取音轨-map 0:a双声道-ac 2libopus 编码 # page_duration 20000微秒即 20ms 一个 Ogg page-vn 丢弃视频 ffmpeg -i $INPUT_FILE -map 0:a -c:a dca -ac 2 -c:a libopus -page_duration 20000 -vn output.ogg三个参数的取值与源码实现是严格对应的-g 30GOP 间隔视频是 I 帧周期为 30 帧的序列保证丢包场景下浏览器有足够多的同步点IVF 容器stream_video使用IVFReader::new(reader)?解析它会先读 IVF header 拿到timebase_numerator/timebase_denominator后面 pacing 就依赖这两个值见下文-page_duration 20000源码中定义了常量const OGG_PAGE_DURATION: Duration Duration::from_millis(20)play-from-disk-vpx.rs#L46stream_audio用一个 20ms 周期的 ticker 来按真实时间轴逐页发送。造文件时若 page 时长与发送节奏差异过大会造成播放抖动。原文档要求生成的output_vp8.ivf/output_vp9.ivf以及output.ogg应放在运行命令指定的路径下例如rtc/examples/test-data/目录当前仓库中rtc/是Cargo.toml声明的 path 依赖rtc { version 0.21.1, path rtc }若该目录内没有现成的 test-data 文件直接用上面的 ffmpeg 命令自行生成即可然后把路径传给-v/-a参数。第二步构建示例cargo build --example play-from-disk-vpx该 example 已在 Cargo.toml 中注册[[example]] name play-from-disk-vpx path examples/play-from-disk-vpx/play-from-disk-vpx.rs bench false注意示例代码通过#[path ../common/mod.rs] mod common;引入共享的运行时辅助模块examples/common/mod.rs。该模块的核心设计是示例与具体 async runtime 解耦——同一份源码可分别跑在runtime-tokio和runtime-smol两个 Cargo feature 之下common::runtime()通过OnceLock保证进程内只有一个共享的Arcdyn Runtime实例既驱动示例的sleep/interval定时器也通过PeerConnectionBuilder::with_runtime()注入给连接。默认 feature 为default [runtime-tokio, crypto-ring]因此默认构建即为 tokio ring。第三步打开 Web 信令页面按原文档说明应打开一个带有两个 text-area 和一个 Start Session 按钮的信令页面README 中提供了一个 jsfiddle 在线页面链接打开后你会看到两个 text-area 和 Start Session 按钮。这个页面只做一件事在两个文本框之间转发 SessionDescriptionbase64 编码充当手动信令通道——上框是浏览器侧、下框是play-from-disk-vpx侧点击 Start Session 后浏览器会执行setRemoteDescription并把 offer 填回上框。第四步运行 play-from-disk-vpx把浏览器的 SDP 作为 stdin 输入程序启动后会打印Paste offer from browser and press Enter:并从stdin读取一行 SessionDescription。源码中该逻辑是play-from-disk-vpx.rs#L292-L302// Wait for the offer to be pasted print!(Paste offer from browser and press Enter: ); let line if cli.input_sdp_file.is_empty() { signal::must_read_stdin()? // 从 stdin 读取一行 } else { fs::read_to_string(cli.input_sdp_file)? // 或从 -i 指定的文件读取 }; let desc_data signal::decode(line.as_str())?; // base64 解码也兼容裸 SDP let offer serde_json::from_str::RTCSessionDescription(desc_data)?;由于 stdin 是一次性通道原文档给出了三种喂入方式Linux/macOS用管道把信令页面上框浏览器侧的 SessionDescription 复制为环境变量$BROWSER_SDP然后运行echo $BROWSER_SDP | ./target/debug/examples/play-from-disk-vpx -v rtc/examples/test-data/output_vp8.ivf -a rtc/examples/test-data/output.oggVP9 版本加--vp9开关echo $BROWSER_SDP | ./target/debug/examples/play-from-disk-vpx -v rtc/examples/test-data/output_vp9.ivf -a rtc/examples/test-data/output.ogg --vp9Windows重定向文件把 SessionDescription 粘贴保存到一个文件如my_file运行./target/debug/examples/play-from-disk-vpx -v rtc/examples/test-data/output_vp8.ivf -a rtc/examples/test-data/output.ogg my_fileVP9 版本./target/debug/examples/play-from-disk-vpx -v rtc/examples/test-data/output_vp9.ivf -a rtc/examples/test-data/output.ogg --vp9 my_file补充源码还支持-i/--input-sdp-file参数直接指定 offer 文件路径input_sdp_file非空时走fs::read_to_string分支这是 README 未提及但从 Cli 结构体#L50-L73 可以确认的第三种输入方式对每次都要重定向 stdin的场景更方便。第五步把 answer 贴回浏览器开始播放play-from-disk-vpx处理完 offer 后会向 stdout 打印一行 base64 编码的 answer把它复制进信令页下框第二 text-area然后点击Start Session。此时浏览器上方应开始播放视频文件播完后play-from-disk-vpx会自行退出。完整 CLI 参数一览结合源码中的 clap 定义play-from-disk-vpx.rs#L50-L73该示例的全部参数如下参数默认值说明-d, --debugfalse开启 env_logger 详细日志-l, --log-levelINFO日志级别ERROR/WARN/INFO/DEBUG/TRACE-i, --input-sdp-file空从文件读取 offer为空则从 stdin 读取-o, --output-log-file空配合-d把日志写入文件而非 stdout-v, --video PATH无要推流的视频文件含 VP8 或 VP9 的.ivf-a, --audio PATH无要推流的音频文件.ogg/ Opus--vp9false视频使用 VP9 而非默认的 VP8-v与-a均为Option可以只推视频、只推音频或两者都推video and/or audio。程序在启动时即校验文件存在性路径错误会直接报错video file: {} not exist/audio file: {} not exist退出。源码解析SDP 交换与非 Trickle ICE拿到 offer 后程序执行标准答案方流程play-from-disk-vpx.rs#L304-L317peer_connection.set_remote_description(offer).await?; let answer peer_connection.create_answer(None).await?; peer_connection.set_local_description(answer).await?; // Block until ICE Gathering is complete (non-trickle ICE) let _ gather_complete_rx.recv().await;这里刻意采用非 Trickle ICE注册的事件处理器Handler实现PeerConnectionEventHandler在on_ice_gathering_state_change收到RTCIceGatheringState::Complete时通过 channel 通知主流程。主流程阻塞等待该信号确保local_description()返回的 answer 中已包含全部 ICE candidate再 base64 编码打印if let Some(local_desc) peer_connection.local_description().await { let json_str serde_json::to_string(local_desc)?; let b64 signal::encode(json_str); println!({b64}); }这样浏览器只需setRemoteDescription一次即可拿到完整候选简化了手工信令。连接真正建立RTCPeerConnectionState::Connected后connected_tx触发主流程才开始推流——媒体发送严格发生在 ICE 连接成功之后。此外PeerConnectionBuilder还做了两件事值得注意play-from-disk-vpx.rs#L224-L232with_udp_addrs(vec![format!({}:0, signal::get_local_ip())])把 ICE 收发包的 UDP socket 绑定到本机 IP避免多网卡环境下 host candidate 混乱with_runtime(runtime.clone())显式注入示例共享的运行时本库无 ambient runtime所有连接级 I/O 都走注入的dyn Runtime。ICE 服务器配置为公共 STUNlet config RTCConfigurationBuilder::new() .with_ice_servers(vec![RTCIceServer { urls: vec![stun:stun.l.google.com:19302.to_string()], ..Default::default() }]) .build();源码解析编解码器注册与媒体引擎MediaEngine决定本端能协商出哪些编解码器。示例按 CLI 开关注册 VP8/VP9 与 Opusplay-from-disk-vpx.rs#L160-L198let audio_codec RTCRtpCodecParameters { rtp_codec: RTCRtpCodec { mime_type: MIME_TYPE_OPUS.to_owned(), clock_rate: 48000, // Opus 固定 48kHz channels: 2, .. }, payload_type: 120, .. }; let video_codec RTCRtpCodecParameters { rtp_codec: RTCRtpCodec { mime_type: if is_vp9 { MIME_TYPE_VP9.to_owned() } else { MIME_TYPE_VP8.to_owned() }, clock_rate: 90000, // 视频固定 90kHz channels: 0, .. }, payload_type: if is_vp9 { 98 } else { 96 }, .. }; if audio_file.is_some() { media_engine.register_codec(audio_codec.clone(), RtpCodecKind::Audio)?; } if video_file.is_some() { media_engine.register_codec(video_codec.clone(), RtpCodecKind::Video)?; } let registry register_default_interceptors(Registry::new(), mut media_engine)?;要点只注册本次要推的编解码器--vp9时 mime 变为 VP9、payload type 从 96 变为 98不传-a则根本不注册 Opusanswer 里也就不会有音频 m-line浏览器自然不会等待音轨payload type 只是初始提议真正发包用的 PT 必须取协商结果。negotiated_payload_type()通过sender.get_parameters()读取协商后的rtp_parameters.codecs.first().payload_typeplay-from-disk-vpx.rs#L373-L384注释明确说明write_sample会把它打到每个包上而底层write_rtp要求 PT 与协商出的 sender codec 匹配——这是自定义静态轨道示例中最容易踩的坑。轨道侧视频/音频分别创建TrackLocalStaticSample并add_track注册进 PeerConnection轨道元数据stream id / track id / label与随机ssrc都写死为webrtc-rs-stream-id-video一类命名。源码解析媒体 pacing——为什么用 ticker 而不是 sleep推流函数stream_videoplay-from-disk-vpx.rs#L386-L434与stream_audioplay-from-disk-vpx.rs#L436-L483的发送节奏设计是本示例最有复用价值的部分视频——发送间隔由 IVF 头部的 timebase 决定即每帧应占据的时长let sleep_time Duration::from_millis( ((1000 * header.timebase_numerator) / header.timebase_denominator) as u64, ); let mut ticker interval(sleep_time); loop { let frame match ivf.parse_next_frame() { Ok((frame, _)) frame, Err(err) { println!(All video frames parsed and sent: {err}); break; } }; video_track .sample_writer(ssrc, payload_type) .write_sample(Sample { data: frame.freeze(), duration: Duration::from_secs(1), ..Sample::new(Instant::now()) }) .await?; let _ ticker.tick().await; }音频——每读出一个 Ogg page用相邻两个 page 的granule_position差值算出该页包含的采样数48kHz 采样率据此得到精确的sample_duration再以 20ms 的intervalticker 逐页发送let sample_count page_header.granule_position - last_granule; last_granule page_header.granule_position; let sample_duration Duration::from_millis(sample_count * 1000 / 48000);源码注释解释了必须使用ticker 而非 sleep的两个原因避免累积偏差——time.Sleep不会补偿解析数据花费的时间逐帧累加下来会越来越慢ticker 以固定周期唤醒偏差不会累积绕过 Sleep 本身的延迟抖动问题。注释同时点出这不是强制要求因为帧自带时间戳但如果一次性把所有帧灌出去丢包率会高得多——这正是把本地文件当作实时信源播放时必须按真实时间轴 pacing 的原因。pacing 依赖的interval来自 examples/common/mod.rs其内部是runtime().interval(period)——即由注入的 Runtime 提供的可等待定时器这也是示例能做到 runtime 无关的关键。退出时序什么时候程序会结束原文档说文件播到结尾时play-from-disk-vpx会退出。对应实现是三路futures::select!play-from-disk-vpx.rs#L350-L366futures::select! { _ done_rx.recv().fuse() { println!(received done signal!); // PeerConnection 进入 Failed } _ ctrlc_rx.recv().fuse() { println!(received ctrl-c signal!); // 用户 Ctrl-C } _ async { let _ video_done_rx.recv().await; let _ audio_done_rx.recv().await; }.fuse() { println!(All media streaming completed.); // 视频、音频都播完 } } peer_connection.close().await?;三个分支连接失败Failed状态由事件处理器转发done_tx、用户 Ctrl-Cctrlc::set_handler转发ctrlc_tx、媒体流全部播完video/audio 各自的 streaming task 结束后发送 done 信号注意未启用的那一路会drop(tx)使对应 recv 立即完成。命中任一分支即close()退出。因此文件播完自动退出是常态路径Ctrl-C 随时停是交互路径。与其他示例的配合反向录制examples/save-to-disk-vpx/save-to-disk-vpx.rs 与本示例结构几乎镜像——浏览器侧发起 offer 后它以Recvonlytransceiver 接收 VP8/VP9 Opus 并写回 IVF/OGG 文件适合先save再play的完整往返验证H.264/H.265 变体examples/play-from-disk-h26x/play-from-disk-h26x.rs 演示 MP4 源的同一套 Offer/Answer 非 Trickle ICE 流程同系列进阶play-from-disk-fecFlexFEC 前向纠错、play-from-disk-playlist-control播放列表控制、play-from-disk-renegotiation重协商、bandwidth-estimation-from-disk带宽估计等示例都是在本文这个文件 → 轨道 → RTP → 浏览器骨架上做增量扩展建议以本文为起点逐个阅读。小结play-from-disk-vpx用一个约 500 行的示例覆盖了 Rust webrtc 库静态轨道推流的完整闭环MediaEngine注册编解码器VP8/VP9 OpusPT 96/98/120→add_track创建静态轨道 → 从 stdin 或文件读取 base64 offer →set_remote_description/create_answer/set_local_description→ 等待 ICE Gathering 完成非 Trickle→ 打印 base64 answer → 连接建立后按 IVF timebase / Ogg granule 用 ticker pacing 逐帧推流 → 播完或 Ctrl-C 后close退出。掌握这套流程后把示例中的IVFReader/OggReader换成任意实时采集源即可得到自己的 WebRTC 推流服务端。赞分享音视频即时通讯通信【免费下载链接】webrtcAsync-friendly WebRTC implementation in Rust项目地址https://gitcode.com/gh_mirrors/webr/webrtc点击查看免费下载相关推荐Pion WebRTC play-from-disk 实战用 Go 将本地 IVF/OGG 媒体文件推流到浏览器Pion WebRTC play from disk 实战用 Go 将本地 IVF/OGG 媒体文件推流到浏览器 play from disk 是 PionWebRTC音视频即时通讯通信webrtc-rs 重协商实战用 play-from-disk-renegotiation 在已建立的连接上动态增删视频轨webrtc rs 重协商实战用 play from disk renegotiation 在已建立的连接上动态增删视频轨 本文围绕 examples/pla音视频即时通讯通信使用 Pion WebRTC 的 save-to-disk-av1 示例将浏览器 AV1 视频流实时落盘为 IVF 文件使用 Pion WebRTC 的 save to disk av1 示例将浏览器 AV1 视频流实时落盘为 IVF 文件 本指南以仓库中 save to diWebRTC音视频即时通讯通信上一篇Cockpit URL 路径体系详解应用路由、资源缓存、Channel 与直连机器机制下一篇aiohttp 安全威胁模型深度解析基于 STRIDE 剖析 HTTP/1、WebSocket 与 Multipart 的安全边界创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Java线程安全八大问题解析与解决方案

Java线程安全八大问题解析与解决方案

1. 线程安全基础概念解析在多线程编程中,线程安全问题是每个Java开发者必须面对的挑战。当多个线程同时访问共享资源时,如果没有正确的同步机制,就会导致数据不一致、程序行为异常等严重后果。理解这些问题的本质,是构建可靠并发系…

2026/10/13 9:21:48 阅读更多 →
Android字体适配全解析:不跟随系统与动态设置字体大小实战

Android字体适配全解析:不跟随系统与动态设置字体大小实战

1. 字体适配为什么成了Android开发里的“隐形地雷”做过Android应用的人大概都有过这种经历:在测试机上跑得好好的界面,到了用户手里就变成了“车祸现场”——要么文字大得溢出屏幕,要么小得看不清,要么中英文混排时行高突然塌陷。…

2026/10/13 9:21:48 阅读更多 →
SQL字符串精准定位与条件填充实战:POSITION+SUBSTRING+CASE WHEN

SQL字符串精准定位与条件填充实战:POSITION+SUBSTRING+CASE WHEN

1. 这不是“查数据”,而是“精准定位智能填充”的组合拳SQL里最常被低估的,其实是字段级的字符串操作能力。很多人一看到“从指定字段查找指定字符”,第一反应是LIKE加%模糊匹配——这就像用消防水枪给盆栽浇水,方向对了&#xff…

2026/10/13 9:20:47 阅读更多 →

最新新闻

开源轻量级工业物联网平台UNIHH-IOT:设备数据采集与可视化实战

开源轻量级工业物联网平台UNIHH-IOT:设备数据采集与可视化实战

年底帮一家中型制造企业做产线数据化改造,折腾了几个月,最大的感触是:市面上能用的物联网平台,要么贵得离谱,要么重得吓人。商用平台按点位收费,一个车间几十台设备一年下来就是一笔不小的开支;…

2026/10/13 10:13:36 阅读更多 →
零代码实现PLC到MQTT的10分钟工业数据接入

零代码实现PLC到MQTT的10分钟工业数据接入

1. 项目概述:为什么“零代码连接PLC到MQTT”正在成为产线工程师的刚需最近在某汽车零部件厂做设备联网方案评审时,产线主管直接把一张手写的纸条推过来:“能不能别让我写代码?我连Python和梯形图哪个是编程语言都分不清&#xff0…

2026/10/13 10:13:36 阅读更多 →
React Native在OpenHarmony上的实践:历史记录页开发与性能优化

React Native在OpenHarmony上的实践:历史记录页开发与性能优化

1. 项目背景与技术选型思考 1.1 为什么在OpenHarmony上选择React Native 这段时间一直在折腾OpenHarmony应用开发,手里正好有个叫AnimeHub的追番类App项目,首屏、详情页都跑通了,轮到历史记录页面的时候,团队里有了分歧&#xff…

2026/10/13 10:13:36 阅读更多 →
MySQL高级入门:执行计划、索引失效与锁机制实战解析

MySQL高级入门:执行计划、索引失效与锁机制实战解析

1. 这份笔记到底解决了什么问题?——不是复习资料,而是实战导航图 “MySQL高级入门总结笔记(下)”这个标题里,“高级入门”四个字本身就带着矛盾感——既不是纯新手向的“建库建表增删改查”,也不是DBA级的…

2026/10/13 10:13:36 阅读更多 →
ISO 22900-2 D-PDU-API 文档 DeePL 翻译实战:术语保护与工程化流程

ISO 22900-2 D-PDU-API 文档 DeePL 翻译实战:术语保护与工程化流程

简介:本资源为ISO 22900-2:2017《道路车辆—模块化车辆通信接口(MVCI)—第2部分:诊断协议数据单元(D-PDU API)》标准的中英文对照文档,采用DeePL翻译,面向汽车诊断协议开发、ECU通信…

2026/10/13 10:13:36 阅读更多 →
LFP 这个“老将“还能再榨出多少性能?

LFP 这个“老将“还能再榨出多少性能?

磷酸铁锂(LFP)凭安全、寿命和成本统治了储能与主流电动车市场,但它的物理天花板也是明确的:标称电压约 3.2 V、比容量约 170 mAh/g,能量密度提升空间见顶。嘉泉大学 Kakarla 团队系统盘点了 LFP 从材料、极片到系统级和…

2026/10/13 10:12:35 阅读更多 →

日新闻

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

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

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

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 阅读更多 →