# 本地媒体主机响应保护编码方案 V002 创建人员:`dev.developer.project` 文件职责:在 V001 已通过部分不回退的前提下,闭环限定方案审核的 F1—F3;作为后续实现、离线测试、实现审核和一次受控短片验收的唯一方案基线。 管理规范/模板:`../../../dev-doc/编码规范.md`;`../../../dev-doc/开发审计规范.md`;`../../../../common/dev-doc/编码规范.md`。 引用文件:`CODE-DESIGN-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-V001.md`;`../../../dev-doc/开发审计报告.md`;`../会议录屏PPT页面提取工具.md`;`../本地视频语音转写工具.md`。 记录方式:事项 `DEV-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-20260804-001` 的 V002 限定修订;V002 复审 PASS 前继续冻结产品代码与真实媒体。 ## 1. 修订依据与不回退合同 - 前序方案:`CODE-DESIGN-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-V001.md`,`13606 bytes / 7F0F5505EF1D92C73FBE6EDFF382F6E25D95A0ABE6D28A7A366353B88CA80273`。 - 前序审核:`DEV-AUDIT-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-V001-DESIGN-20260804-001=HOLD/3`,审计报告当时为 `171737 bytes / C9857D954230147A316DD72E2EAC0798C13865859125C566FCC14196AABB6D28`。 - 本版只修订 F1“回退归因边界”、F2“H.264/H.265 业务等价合成门禁”和 F3“资源采样非空与时间口径”。V001 第 2、3、4.1、4.2、5、7.1 和 8 节中与三项修订不冲突的内容继续有效。 - 产品文件仍仅允许修改 `dev/project-dev/transcribe_media.py`、`dev/project-dev/extract_ppt_slides.py` 及其既有测试;另允许一个测试侧只读 PowerShell 采样器、两份既有用法说明、目录导读和开发账本。不新增产品模块、依赖、参数、服务、数据库、API、GUI、批处理或调度器。 - 两 CLI 继续共用非阻塞 Windows named mutex;根 Python 和产品默认 FFprobe/FFmpeg 全生命周期使用 BelowNormal;固定解码线程 `4`、滤镜线程 `2`。公开 CLI、页面/转写算法、PNG/PDF、FLAC/TXT/SRT/JSON、源保护、原子提交和失败清理合同不变。 - 实现审核 PASS 前仍不得读取或运行真实源、不得生成真实片段。实现审核 PASS 后最多只读生成一个 `<=600 s` 独立片段并运行一次 PPT 提取;不得重跑、调参、并发转写或改跑长媒体。 ## 2. F1:硬件可归因失败与单次回退边界 ### 2.1 类型边界 扫描内部只新增一个私有、不可跨 CLI 暴露的类型 `HardwareDecodePathError(SlideExtractionError)`。它只表示“本次硬件 decoder/硬件帧下载管道没有形成可供现有页面算法完整消费的 rawvideo 帧流”,不得用来包装任何页面业务错误。 硬件 `_scan_once` 分为两个严格区域: 1. **外部解码/传输区域**:组装和启动 FFmpeg、读取固定大小 rawvideo 帧、等待 FFmpeg、解释返回码和 stderr。只有本区域下表列出的失败可转换为 `HardwareDecodePathError`。 2. **页面业务区域**:把已经读满的单帧交给既有稳定段 detector、页面 classifier、动画/去重和主课件选择。这里的异常保留原类型、原消息和原 traceback;不得被硬件错误捕获器包围,也不得触发 CPU。 | 事件 | 对外类型/语义 | CPU 回退次数 | |---|---|---:| | 硬件 FFmpeg `Popen` 启动失败,且命令确为 NVDEC | `HardwareDecodePathError`,保留 decoder、命令与原异常链 | 恰好 1 次 | | 读取到 `0 < len(frame) < frame_size` 的半帧 | `HardwareDecodePathError`,记录期望/实际字节数 | 恰好 1 次 | | FFmpeg 退出后完整帧数为 0 | `HardwareDecodePathError`,保留返回码和 stderr 尾部 | 恰好 1 次 | | 硬件 FFmpeg 非零退出 | `HardwareDecodePathError`,保留返回码和 stderr 尾部 | 恰好 1 次 | | detector/classifier/特征计算/动画/去重/主序列选择异常 | 原异常原样传播 | 0 次 | | FFmpeg 完整成功、页面算法也结束,但合法结果是“无稳定页面” | 既有无稳定页面错误原样传播 | 0 次 | | 输入/输出文件、正式提交、暂存或清理异常 | 既有错误原样传播 | 0 次 | | `KeyboardInterrupt`、`SystemExit` | 原对象原样传播 | 0 次 | | 受限 CPU 第二次扫描失败 | CPU 路径的原失败作为最终失败;告警中仍保留首个硬件失败摘要 | 不再尝试 | 硬件帧读取循环不得以“其他普通扫描失败”作为兜底转换条件。关闭 pipe、`wait`、收集 stderr 或回收子进程时出现的清理异常,不得冒充 decoder 不可用;若已有业务异常正在传播,清理异常只作为诊断附注,不覆盖业务根因。`BaseException` 只用于 `finally` 清理,不用于转换或回退判断。 ### 2.2 唯一回退控制流 外层扫描协调器只捕获 `HardwareDecodePathError`:打印一次带 `NVDEC`、实际 decoder 和根因的 stderr 告警,然后创建全新的 detector/classifier,执行一次固定 `4/2` 线程的 CPU `_scan_once`。CPU `_scan_once` 不会抛 `HardwareDecodePathError`,其任何失败直接结束任务;代码中不存在第三个 scan 调用点或循环重试。 不支持的 codec 不是运行时 NVDEC 失败:它在探测后直接打印“不支持硬件映射,使用受限 CPU”的明确告警并只运行一次 CPU。H.264/H.265 才进入上述硬件尝试。 ### 2.3 F1 离线桩门禁 必须记录每次 scan 的路径和调用序号,并覆盖: 1. 硬件启动失败、非零退出、零帧、半帧各自只触发一次 CPU;CPU 成功时总尝试数严格为 2。 2. detector 异常、classifier 异常、无稳定页面、文件/清理异常的 CPU 调用数严格为 0,异常类型、消息和因果链不被改写为 NVDEC 错误。 3. 硬件阶段 `KeyboardInterrupt`、`SystemExit` 使用对象身份或退出码断言原样传播,CPU 调用数为 0。 4. CPU 第二次失败时总尝试数严格为 2,没有第三次;最终错误是 CPU 根因,stderr 告警仍含首个硬件根因,二者不互相覆盖。 ## 3. F2:H.264/H.265 双路径业务等价合成门禁 ### 3.1 固定合成序列 测试只使用测试临时目录中的程序生成 RGB PNG,不读取业务视频。生成 `320x180`、`1 fps` 的确定性短序列,颜色、几何块和帧号完全固定: - `A-stable`:3 帧,稳定页 A; - `A-animation`:3 帧,在 A 的固定区域加入动画块; - `B-stable`:3 帧,切换到稳定页 B; - `A-animation-repeat`:3 帧,重复已经出现的 A 动画终态,用于验证合理去重; - `C-stable`:3 帧,切换到稳定页 C。 每段首尾画面保持足够稳定,使既有稳定段、动画合并、重复去重和主题选择链都被真实调用。测试生成器不得修改产品阈值,也不得通过跳过 detector/classifier 来制造等价。 同一 PNG 序列分别用当前 FFmpeg 的 `libx264` 和 `libx265` 编成短小 H.264/H.265 媒体。编码命令自身固定线程上限,仅用于合成测试。缺少任一 encoder、`h264_cuvid` 或 `hevc_cuvid` 时,对应集成测试必须明确 `SKIP` 并保存能力缺口;不得伪造 PASS。当前目标验收主机具备这些入口,提交实现审核时两种 codec 都必须实际 PASS。 ### 3.2 四组实际扫描 对 H.264、H.265 各运行两条真实扫描路径: - NVDEC:分别固定 `h264_cuvid`、`hevc_cuvid`,CUDA 硬件帧经 `hwdownload` 转为产品现有 RGB rawvideo; - forced CPU:同一媒体、同一采样/缩放/业务函数,但禁用 `-hwaccel` 和 cuvid,使用固定 `4/2` 线程。 每次运行保存实际 codec、decoder、完整 argv、FFmpeg 返回码、stderr 尾部和产品扫描/选择摘要。硬件证据以 argv 中的 `-hwaccel cuda`、实际 cuvid decoder 和成功返回为主;显存变化不能代替该证据。 ### 3.3 等价断言 对每种 codec 的 NVDEC 与 forced CPU 结果逐项断言: 1. 输入采样数相同;稳定候选数和动画更新数相同。 2. 去重后的逻辑页数相同;主序列选中页的逻辑标识及顺序相同。 3. 每个选中页的相对时间戳相同;允许的唯一浮点误差为 `<=0.001 s`,不得用整页误差掩盖漂移。 4. 完整产品提取生成的 PNG 数、连续编号、顺序、RGB 模式和尺寸相同;PDF 页数、顺序、每页 RGB 图像尺寸相同。 5. 不要求 NVDEC/CPU PNG 或 PDF 压缩字节逐字节相同;但是任一业务页、时间、数量、顺序、模式或尺寸漂移即阻断。 命令结构测试必须对 H.264/H.265 各自断言 `-hwaccel`、`-hwaccel_output_format`、`-c:v ` 和输入 `-threads 4` 全部位于目标输入 `-i` 之前;`-filter_threads 2`、`-filter_complex_threads 2` 明确存在。CPU 命令不得含硬件参数,但同样含 `4/2` 上限。 ## 4. F3:非空、连续且按真实时间判定的资源采样 ### 4.1 采样阶段与有效行 测试侧 `monitor_media_host_responsiveness.ps1` 目标周期仍为 `500 ms`。采样器以首次发现本次任务树中的扫描 FFmpeg 为 `scan_start`,持续到该 FFmpeg 退出后的同一采样周期为 `scan_end`;该区间必须连续覆盖,不能用根 Python 的非扫描阶段替代。 性能计数器第一次读数标为 `counter_warmup=true`、`cpu_valid=false`,只保留原始值,不进入任何窗口或高 CPU 段。其后每一行保存: - `timestamp_utc`、`previous_valid_timestamp_utc`、`interval_seconds`、`cpu_valid`; - 整机 CPU、任务 PID 集、任务树 CPU/RAM、根/FFmpeg PriorityClass; - GPU utilization、decoder utilization、全局显存、PID 专用显存及原始查询状态; - `scan_ffmpeg_present`、实际命令和 decoder 字段。 任一有效行代表区间 `(previous_valid_timestamp, timestamp]`。实际相邻有效间隔必须 `0 < interval_seconds <=1.5`;超过 `1.5 s`、时间戳非递增、计数器读取失败、扫描起止无法归属或必填字段缺失,均令 `sampling_valid=false`。 ### 4.2 五秒尾窗公式与非空性 只评估完整落在 `[scan_start, scan_end]` 的 5 秒尾窗。每个尾窗以有效样本区间与窗口的时间重叠作为权重: `window_cpu = sum(cpu_i * overlap_seconds_i) / sum(overlap_seconds_i)`。 每个窗口必须同时满足: - 时间覆盖 `sum(overlap_seconds_i) >= 5.000 s`(仅允许浮点计算容差 `1 ms`); - 至少 `10` 个不同有效样本成员; - 窗口内最大样本间隔 `<=1.5 s`; - `window_cpu <85.0`。 汇总的 `window_count` 必须 `>0`。若扫描阶段不足以形成一个完整 5 秒窗口、任一窗口覆盖或密度不足,均为 `sampling_valid=false`,不得用空集合宣布 CPU PASS。`resource_windows.csv` 固定逐窗保存窗口起止、成员行号列表、成员数、覆盖秒数、最大间隔、加权 CPU 和判定;`resource_summary.json` 保存 `window_count`、最大窗口 CPU、最差窗口及成员。 ### 4.3 连续高 CPU 的真实时间公式 高 CPU 段不用固定 20 行近似。对连续有效且整机 CPU `>=85.0` 的样本区间,按真实 `interval_seconds` 累计;遇到 `<85.0`、无效行、缺口或扫描阶段边界即结束一段。逐段保存 `start_timestamp_utc`、`end_timestamp_utc`、`duration_seconds`、成员行号和终止原因。 任一高 CPU 段 `duration_seconds >=10.000` 即 CPU FAIL;等于 10 秒必须失败。密度/覆盖无效和阈值超标是不同字段:前者使 `sampling_valid=false`,后者使 `cpu_gate_pass=false`,不得混为成功。 ### 4.4 F3 合成 CSV 门禁 不启动媒体,使用固定时间戳的合成 CSV 直接测试 parser/汇总器: 1. 密集 `84.9%`:窗口非空、全部 `<85%`、无 10 秒高段,PASS。 2. 密集 `85.0%`:所有相关 5 秒窗口以严格 `<85%` 门禁 FAIL。 3. 连续高 CPU 恰好 `10.000 s`:高段门禁 FAIL;`9.999 s` 反例不因该项失败。 4. 稀疏 `0.8—1.5 s`:即使单行看似正常,只要任一完整尾窗成员少于 10 或覆盖不足,`sampling_valid=false`。 5. 扫描不足 5 秒或没有合格窗口:`window_count=0` 且 `sampling_valid=false`,证明不能空通过。 6. warm-up 行不进入窗口;首/末覆盖、时间戳倒退和 `>1.5 s` 缺口分别触发无效。 真实短片验收的 summary 还必须固定扫描开始/结束、有效样本数、最大间隔、窗口数/最差成员、高 CPU 最长段起止/时长、整机 CPU 两项门禁,并同时保留 V001 规定的任务树 CPU/RAM、GPU/decoder/显存和命令/日志证据。 ## 5. 实现与审核门禁 1. V002 限定方案复审只检查本文件第 2—4 节及第 1 节不回退合同;PASS 前产品代码、产品测试和真实媒体继续冻结。 2. PASS 后仅按 V001+V002 实现;先运行 F1 桩、F2 H.264/H.265 合成媒体、F3 合成 CSV,再运行两个目标测试和 project 全回归。合成媒体只在测试临时目录生成和清理。 3. 实现审核材料必须包含代码/测试/用法/采样器 bytes 与 SHA-256、完整测试结果、两种 codec 的四组 decoder argv 与等价摘要、F1 调用序列、F3 反例汇总、产品边界未回退证明,以及真实业务媒体动作仍为 0。 4. 只有独立实现审核 PASS 后,才执行 V001 第 7.1 节的一次 `<=600 s` 片段和一次 PPT 运行。采样无效、CPU 门禁失败、NVDEC 主证据缺失或业务回归失败时只报告单一具体阻断,不重试、不调参、不改跑长视频。 5. 最终向 `case_analysis.media_processor` 回传代码变更、测试、互斥/优先级/回退行为、CPU/GPU/decoder 采样、独立审核结论和源保护;不扩展产品范围。 ## 6. V001 阻断逐项映射 | 前序 finding | V002 闭环 | 实现后必备证据 | |---|---|---| | F1 flow-failure-contract | 第 2 节:只允许 `HardwareDecodePathError` 回退;算法、空结果、文件/清理和 `BaseException` 均零回退;CPU 失败无第三次 | 启动/非零/零帧/半帧、算法、空结果、中断、CPU 二次失败桩的调用序列和根因断言 | | F2 business-contract-test | 第 3 节:固定稳定页+动画+重复+切换序列;H.264/H.265 分别实际比较 NVDEC 与 forced CPU | 两种 codec 的 decoder argv;采样、页数、顺序、时间戳、PNG/PDF/RGB/尺寸等价摘要 | | F3 evidence-sampling | 第 4 节:warm-up 丢弃;扫描 FFmpeg 连续覆盖;窗口非空且每窗至少 10 样本;按实际时间累计高 CPU 段 | 窗口成员、最大间隔、最差窗口、高段起止/时长;`<85`、`==85`、10 秒、稀疏、零窗口合成反例 |