所有 stats 行(80+ 条)中:
output_bps=0 frame_bytes_max=0 sws_p95=0.0ms sws_avg=0.0ms
而 total_p95 永远等于 encode_p95(因为 total = sws + encode = 0 + encode)。
total_p95
encode_p95
total = sws + encode = 0 + encode
src/state_portal.rs:651-655 编码线程上报 timing 时硬编码了三个字段中的两个:
src/state_portal.rs:651-655
let _ = timing_tx.try_send(EncodeThreadTiming { sws_us: 0, // ← 永远 0 encode_us: elapsed, // ← 仅此字段真实 output_bytes: 0, // ← 永远 0 });
src/stats.rs:142-145 直接把这些值推进统计向量:
src/stats.rs:142-145
self.sws_us.push(sws_us); // 全是 0 self.encode_us.push(encode_us); self.total_us.push(sws_us.saturating_add(encode_us)); // = encode_us self.output_bytes.push(output_bytes); // 全是 0
最终 output_bytes_per_sec、output_frame_bytes_p95、output_frame_bytes_max、sws_* 全部失真。
output_bytes_per_sec
output_frame_bytes_p95
output_frame_bytes_max
sws_*
write_h264: N bytes
encode_cpu_frame()
(encode_us, sws_us, output_bytes)
EncodeThreadTiming
sws_us
sws_scale
Instant::now()
output_bytes
pkt.size
pkt.data.len()
state_portal.rs:651
import_us
Blocks:
output_bps
frame_bytes_max
建议优先修复本 issue。修好后 #15 / #18 / #20 都可以用量化数据诊断和验证。
0aba0e6
按 Oracle 审核修正后的方案实现:54 insertions, 9 deletions, 2 文件。
encode_thread_loop (state_portal.rs:651-655) 硬编码 sws_us: 0, output_bytes: 0,encode_us 用 Instant::now() 包裹整个 encode_cpu_frame 调用——把 sws 转换 + 编码混在一起,且 stats 面板永远显示 0。
encode_thread_loop
sws_us: 0, output_bytes: 0
encode_us
encode_cpu_frame
新增 SwEncodeTiming 结构体 (avhw.rs:40-48),含 Default derive:
SwEncodeTiming
Default
pub struct SwEncodeTiming { pub sws_us: u64, // NV12→YUV420P 转换 pub encode_us: u64, // avcodec_send_frame + drain pub output_bytes: usize, // libavcodec 产出的字节(即使下游 try_send drop 也计入) }
drain_encoder 改返回 Result<usize> (avhw.rs:1297):
drain_encoder
Result<usize>
total_bytes
Muxer/Channel
pkt_size
(*pkt.as_mut_ptr()).size
> 0
encode_cpu_frame 三处改动 (avhw.rs:1115-1250):
self.last_timing = SwEncodeTiming::default()
av_frame_make_writable + sws_scale
avcodec_send_frame + drain_encoder
self.last_timing
take_timing() 方法 (avhw.rs:1112):
take_timing()
pub fn take_timing(&mut self) -> SwEncodeTiming { mem::take(&mut self.last_timing) }
用 mem::take 返回并清零,防止重复读取 stale 值。
mem::take
flush() 适配新签名:let _ = self.drain_encoder(start_ts)?;(忽略 flush 路径的字节数)
flush()
let _ = self.drain_encoder(start_ts)?;
state_portal encode_thread_loop (state_portal.rs:646-656):
state_portal encode_thread_loop
let t = encode.take_timing(); let _ = timing_tx.try_send(EncodeThreadTiming { sws_us: t.sws_us, encode_us: t.encode_us, output_bytes: t.output_bytes, });
移除了外层 Instant::now() 和硬编码的 0。
last_timing = Default
take_timing
cargo build
cargo test
cargo build --release
#19 (encoder spin 6s before WebRTC connect) — 依赖当前 timing 基础设施。
No dependencies set.
The note is not visible to the blocked user.
现象
所有 stats 行(80+ 条)中:
而
total_p95永远等于encode_p95(因为total = sws + encode = 0 + encode)。根因
src/state_portal.rs:651-655编码线程上报 timing 时硬编码了三个字段中的两个:src/stats.rs:142-145直接把这些值推进统计向量:最终
output_bytes_per_sec、output_frame_bytes_p95、output_frame_bytes_max、sws_*全部失真。影响
write_h264: N bytes反推total_p95误等同encode_p95,扭曲对"编码总成本"的认知修复方向
encode_cpu_frame()返回(encode_us, sws_us, output_bytes)或写入EncodeThreadTiming共享结构sws_us:在sws_scale调用前后取Instant::now()差值output_bytes:累加 packet 大小(pkt.size或pkt.data.len())state_portal.rs:651改为透传真实值import_us(DMA-BUF → CPU 拷贝)的统计链路,目前 import 在另一处记录但未并入 encode thread timing关联
Dependencies
Blocks:
output_bps/frame_bytes_max等真实 stats 数据建议优先修复本 issue。修好后 #15 / #18 / #20 都可以用量化数据诊断和验证。
已实现并提交 (
0aba0e6)按 Oracle 审核修正后的方案实现:54 insertions, 9 deletions, 2 文件。
根因
encode_thread_loop(state_portal.rs:651-655) 硬编码sws_us: 0, output_bytes: 0,encode_us用Instant::now()包裹整个encode_cpu_frame调用——把 sws 转换 + 编码混在一起,且 stats 面板永远显示 0。修正方案
新增
SwEncodeTiming结构体 (avhw.rs:40-48),含Defaultderive:drain_encoder改返回Result<usize>(avhw.rs:1297):total_bytes累加器Muxer/Channelmatch 之前统一累加pkt_size(Oracle 强调:避免分支重复 + 正确处理多 packet drain)(*pkt.as_mut_ptr()).size读取,guard> 0encode_cpu_frame三处改动 (avhw.rs:1115-1250):self.last_timing = SwEncodeTiming::default()——Oracle 指出的最大正确性陷阱:early return 路径(disconnect/pause/dedup skip)不会报告上一帧的 stale 值Instant::now()包裹av_frame_make_writable + sws_scaleInstant::now()包裹avcodec_send_frame + drain_encoder,本地构建完整 timing snapshot 后一次性赋值self.last_timingtake_timing()方法 (avhw.rs:1112):用
mem::take返回并清零,防止重复读取 stale 值。flush()适配新签名:let _ = self.drain_encoder(start_ts)?;(忽略 flush 路径的字节数)state_portal encode_thread_loop(state_portal.rs:646-656):移除了外层
Instant::now()和硬编码的 0。Oracle 指出的 3 个陷阱均已规避
last_timing = Defaultflush()通过drain_encoder污染 timing 状态drain_encoder返回 bytes,不在内部 mutate timing 字段take_timing不清零导致重复读取mem::take返回并清零验证
cargo build✅ 0 errorscargo test✅ 91/91 unit + 3/3 integration passedcargo build --release✅下一步
#19 (encoder spin 6s before WebRTC connect) — 依赖当前 timing 基础设施。