# 本地媒体主机响应保护编码方案 V001 创建人员:`dev.developer.project` 文件职责:冻结 PPT 页面提取与本地视频转写两个既有 CLI 的最小主机响应保护改动、离线测试和受控真实资源验收。 管理规范/模板:`../../../编码规范.md`;`../../../开发审计规范.md`;`../../../../common/dev-doc/编码规范.md`。 引用文件:`../../../../ai-media-processor/draft/会议录屏PPT页面提取方案_v0.1.md`;`../会议录屏PPT页面提取工具.md`;`../本地视频语音转写工具.md`;`../../../开发执行日志.md`。 记录方式:事项 `DEV-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-20260804-001` 的 V001 完整方案;方案独立审核 PASS 前不得修改产品代码或运行真实媒体。 ## 1. 上游依据与问题归因 - 授权:`HANDOFF-MEDIA-INFODEV-MEDIA-HOST-RESPONSIVENESS-20260804-001 / AUTHORIZED_MINIMAL_IMPLEMENTATION`。 - 事故窗口:`2026-08-03 20:58:07+08:00..21:16:36+08:00`;对应 PPT 完整提取根 Python PID=`45268`。 - 已有证据表明 `extract_ppt_slides.py` 的稳定帧扫描命令没有 `-hwaccel`、解码/滤镜线程上限或 Windows 优先级;本机为 `22C/44T` Xeon,长时间软件解码可挤占桌面响应。 - 本机只读能力核对:FFmpeg 提供 `cuda`、`h264_cuvid`、`hevc_cuvid`、`hwdownload`、`scale_cuda`;GPU 为 RTX 3090 24GB。2 秒合成 H.264 以本方案拟定的 `h264_cuvid + hwdownload` 命令执行 `exit=0`,证明命令链在当前主机可用。 - 问题类型:性能与进程调度问题,不是页面算法、转写模型、源媒体或既有产物问题。 - 修改基线:`extract_ppt_slides.py=32133/9C4F57ED56B01C9EE2BD68CDC532BC865F73EB3662E01B6600410C5660F1F605`;`transcribe_media.py=20996/309166EEFE11A74472EA355CA4AF87F9432A15393B9ABE6D69F7F1054055DE35`。两组目标测试基线为 `41/41 PASS`。 ## 2. 冻结范围 ### 2.1 允许修改 1. `dev/project-dev/transcribe_media.py`。 2. `dev/project-dev/extract_ppt_slides.py`。 3. 对应现有测试;允许新增一个仅供验收的只读 PowerShell 采样测试脚本。 4. 两份既有用法说明、目标目录导读和根级开发账本。 不新增产品 package、常驻进程、服务、数据库、API、GUI、调度器或用户参数。为避免引入第三个产品模块,跨脚本公共保护的 stdlib-only 内部 owner 固定放在现有 `transcribe_media.py`,`extract_ppt_slides.py` 只导入该内部入口;两个公开 CLI 和业务函数签名保持不变。 ### 2.2 不得回退 - PPT:稳定段、动画合并、去重、主课件序列过滤、逐页原分辨率 PNG、合并 PDF、第一视频流、进度、源保护、拒绝覆盖、原子目录提交及中断/失败清理均不变。 - 转写:第一音轨、16kHz 单声道 FLAC、large-v3、CUDA/float16/VAD、20 分钟长视频分块、全局时间轴、FLAC/TXT/SRT/JSON、拒绝 CPU/模型降级、四文件提交回滚及源保护均不变。 - 不通过降低质量、跳过页面、降低扫描频率、减少输出或改变识别参数换取资源指标。 - 不增加 OCR、人像/人脸识别、裁剪、摘要、翻译、Skill 或其他媒体能力。 ## 3. 公共主机保护 ### 3.1 跨进程互斥 `transcribe_media.py` 增加一个 stdlib-only 内部上下文入口,由两个 CLI 的 `main` 在解析参数后、任何 FFprobe/FFmpeg、模型加载、暂存或正式输出动作前进入: 1. Windows 使用固定命名互斥体 `Local\\MBXMediaHeavyTaskV1`;`CreateMutexW` 后以 `WaitForSingleObject(..., 0)` 非阻塞获取。 2. `WAIT_OBJECT_0` 或 `WAIT_ABANDONED` 表示本任务取得唯一占用;退出时 `ReleaseMutex` 并 `CloseHandle`。进程异常退出时由 Windows 释放 owner,不使用易残留的“文件存在”判断。 3. `WAIT_TIMEOUT` 明确返回“已有重型媒体任务运行,拒绝并行启动”,不得等待、排队或启动任何外部进程;其他 Win32 错误带错误码失败。 4. 非 Windows 保持既有可运行性,不设置 Windows 优先级;本事项只对已登记 Windows 主机声明互斥与资源验收合同。 5. `extract_ppt_slides.py` 与 `transcribe_media.py` 必须调用同一个导入对象和同一个 mutex name,不复制第二套锁实现。 互斥只覆盖一次 CLI 重型任务生命周期,不建立常驻服务,不改变输出目录锁和 CreateNew/拒绝覆盖语义。 ### 3.2 Windows 低优先级 取得互斥后,Windows CLI 先用 `GetPriorityClass` 保存进入前等级,再以 `SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS)` 把根 Python 设为“低于正常”。读取或设置失败必须在外部进程启动前显式终止并报告 Win32 错误,不静默继续。 所有产品代码启动的 FFprobe/FFmpeg 都显式使用 Python `subprocess.BELOW_NORMAL_PRIORITY_CLASS` creation flag;`extract_ppt_slides.py` 的两个 `Popen` 入口和 `transcribe_media.py` 的默认 `subprocess.run` runner 共用同一 creation-flags helper。测试桩 runner 不启动系统进程,不伪造优先级证据。 上下文退出时先尽力恢复进入前优先级,再释放 mutex;恢复失败写明确 stderr 告警但不得覆盖正在传播的业务异常或把已原子提交的正式结果伪装成未完成。`KeyboardInterrupt`、`SystemExit` 和普通异常均不得遗留互斥占用;CLI 的整个重型工作区间仍始终为 BelowNormal。 ## 4. FFmpeg 线程上限与 PPT NVDEC ### 4.1 固定资源常量 - `MEDIA_DECODE_THREADS = 4`:所有 FFmpeg 输入解码显式使用 `-threads 4`,不使用默认 `auto`。 - `MEDIA_FILTER_THREADS = 2`:PPT 扫描显式使用 `-filter_threads 2 -filter_complex_threads 2`。 这些值固定在内部代码,不新增 CLI 参数。本事项主机为 44 逻辑处理器,4 个解码线程和 2 个滤镜线程属于保守上限。逐页 PNG 只解码一个定位帧,也使用 `-threads 4`;转写的完整音轨和音频块 FFmpeg 命令同样使用 `-threads 4`。FFprobe 不包含可配置的 FFmpeg 解码/滤镜线程,但子进程优先级仍为 BelowNormal。 ### 4.2 视频流 codec 探测 现有 FFprobe JSON 的第一视频流增加读取 `codec_name`;`VideoInfo` 增加带默认值的 `codec_name`,不改变既有三参数测试构造。映射固定为: | `codec_name` | NVDEC decoder | |---|---| | `h264` | `h264_cuvid` | | `hevc` | `hevc_cuvid` | 其他 codec 没有猜测性 decoder 映射,直接进入有线程上限的 CPU 路径并明确告警。 ### 4.3 扫描命令与回退 支持 codec 的首次扫描命令固定包含: ```text -hwaccel cuda -hwaccel_output_format cuda -c:v -threads 4 -filter_threads 2 -filter_complex_threads 2 -vf hwdownload,format=nv12,setpts=PTS-STARTPTS,fps=fps=1:start_time=0,scale=::flags=bilinear ``` NVDEC 只替代输入视频解码;硬件帧下载后继续沿用原来的 `setpts -> fps=1 -> scale` 和 Python Pillow/NumPy 特征算法,不用 CUDA 重写页面算法。rawvideo 仍是 `rgb24`,采样时间映射和页面结果合同不变。 单次扫描逻辑拆成内部 `_scan_once`:每次创建全新的稳定段 detector 与页面 classifier。硬件路径非零退出、无完整帧或其他普通扫描失败时,丢弃该次内存候选,向 stderr 打印包含 decoder 与原错误的明确“NVDEC 不可用,改用受限 CPU”告警,然后只重启一次 CPU 扫描;不得在部分硬件候选上继续,也不得多次重试。`KeyboardInterrupt`/`SystemExit` 原样传播,不触发 CPU 回退。 CPU 回退命令不带任何 `-hwaccel`/`cuvid`,但必须含 `-threads 4`、`-filter_threads 2` 和 `-filter_complex_threads 2`。成功路径 stdout 固定打印实际解码路径、decoder 和线程上限;日志可作为 NVDEC 路径证据,显存变化不能替代该证据。 ## 5. 失败与兼容合同 1. 互斥忙、主进程优先级设置失败属于外部动作前错误;不得创建暂存、正式输出或加载模型。 2. NVDEC 普通失败只允许一次明确告警后的受限 CPU 回退;CPU 路径失败继续使用既有 `SlideExtractionError`、stderr 尾部、子进程回收和暂存清理。 3. 所有新保护都在现有最外层失败包络内;中断类型、CLI 退出码以及提交回滚合同不变。 4. 外部调用向两个业务函数注入测试 runner 时继续沿用原签名;只有默认 runner 负责真实 Windows creation flag。 5. 原视频和既有证据只读;设计、离线测试和审核阶段不得读取 3.57 小时源视频或 4 小时媒体。 ## 6. 离线与合成测试门禁 ### 6.1 线程和命令 - H.264/H.265 分别唯一映射到 `h264_cuvid/hevc_cuvid`;硬件命令包含 CUDA、hwdownload、固定滤镜顺序、4/2 线程上限。 - CPU 命令不含硬件参数,仍含相同 4/2 上限;逐页 PNG、完整音轨、音频块命令包含 `-threads 4`。 - 默认 `run/Popen` 在 Windows 都收到 `BELOW_NORMAL_PRIORITY_CLASS`;优先级设置失败时没有外部进程调用。 ### 6.2 路径与互斥 - 用两个真实 Python 进程做无媒体互斥:进程 A 取得命名 mutex 并等待测试事件;进程 B 分别调用两个 CLI,均在路径/FFmpeg/模型动作前得到明确 busy;A 退出后锁可重新取得。 - `KeyboardInterrupt`、普通异常及 Win32 获取错误均验证 mutex handle 关闭;不创建队列、守护进程或锁文件。 ### 6.3 硬件回退与业务回归 - 桩硬件失败后只出现一次显式告警和一次 CPU 命令;硬件已产生的候选不进入 CPU classifier;CPU 再失败时不做第三次尝试。 - 2 秒合成 H.264 实际 NVDEC 扫描成功并记录路径;合成/桩测试不得使用业务源。 - 既有 PPT 颜色主题、稳定/动画/去重、PDF、安全测试与转写分块/时间轴/提交回滚测试全部通过;py_compile、两个 CLI help、project 测试、UTF-8 和治理校验无回退。 基线为 `41/41 PASS`。实现阶段测试允许 FFmpeg 合成媒体,不允许读取完整业务源。 ## 7. 实现审核后的唯一真实资源验收 ### 7.1 执行边界 只有独立实现审核 PASS 后才允许: 1. 只读核对原源大小、创建时间、修改时间与 SHA-256。 2. 在 `dev/project-dev/tmp/DEV-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-20260804-001/` 用 FFmpeg 从既有 H.264 源生成一个从 `00:30:00` 开始、目标 `590 s` 的独立短片;FFprobe 实测必须 `<=600 s`。生成命令本身使用线程上限和 BelowNormal 启动,不覆盖任何文件。 3. 只运行一次 `extract_ppt_slides.py`,输出写新目录;不得并行启动转写,不得重试或调参,不得运行 3.57 小时/4 小时媒体。 4. 运行后再次核对原源四项指纹;检查 PNG/PDF、页面顺序、源保护、暂存清理和冻结代码哈希。 转写侧只做离线命令/优先级/互斥回归;本轮真实资源验收由 PPT NVDEC 路径代表重型 FFmpeg 场景。 ### 7.2 只读采样器 新增测试辅助 `dev/project-dev/test/monitor_media_host_responsiveness.ps1`,不进入产品 CLI。它以 `500 ms` 目标周期启动唯一被测 Python 并记录: - 整机:`Processor(_Total)\\% Processor Time`。 - 任务树:根 Python 与递归后代 PID、进程名、命令行、PriorityClass、CPU 累计时间、同采样点 CPU 百分比和 WorkingSet 合计。 - GPU:`nvidia-smi` 原始行、GPU utilization、decoder utilization、全局显存;同任务 PID 的 Windows `GPU Process Memory\\Dedicated Usage` 专用显存及原始实例名。 - 媒体:stdout/stderr、首次观察到的完整 FFmpeg 命令、采样实际间隔、退出码和墙钟。 逐样本写 `resource_samples.csv`,汇总写 `resource_summary.json`,原始命令和日志分别保存。stdout 中的 `NVDEC/CUDA (h264_cuvid)` 与 FFmpeg 命令参数是硬件路径主证据;decoder utilization 仍逐样本记录,不能只用显存声明硬件解码。 ### 7.3 CPU 门禁与采样有效性 - 对时间戳位于每个样本尾端前 `5 s` 的至少 10 个连续有效样本取整机 CPU 算术平均;所有完整窗口必须 `<85%`。 - 不得出现连续 20 个 `500 ms` 有效样本的整机 CPU 均 `>=85%`,即不得连续约 10 秒达到或超过 85%。 - 任务树 CPU 按同间隔内 CPU 累计秒增量除以 `间隔秒 × 逻辑处理器数` 归一到整机 `0..100%`;RAM 为同采样点根与全部后代 WorkingSet 之和。 - 采样周期、实际间隔、逻辑处理器数、滚动窗口成员和最长高 CPU 连续段必须落盘;任一实际间隔 `>1500 ms`、根 PID 丢失、未观察到扫描 FFmpeg、优先级不是 BelowNormal、GPU/decoder 查询失败或必填字段缺失时 `sampling_valid=false`,不得宣告资源 PASS。 真实验收通过条件:被测 exit=0;整机两项 CPU 门禁通过;根 Python 和全部观察到的 FFmpeg 均 BelowNormal;NVDEC 命令/日志证据存在;CPU/RAM/GPU/decoder/显存字段完整;业务输出和源保护回归通过。未给 RAM/显存数值上限,只记录峰值,不自行扩展门禁。 ## 8. 审核与交付顺序 1. V001 独立方案审核 PASS。 2. 仅按本方案实现并运行离线/合成测试。 3. 提交 `dev.reviewer.project` 限定实现审核;PASS 前禁止真实片段生成和运行。 4. PASS 后执行第 7 节唯一一次短片验收;失败只报告单一具体证据,不放宽 85% 门限、不改跑长视频。 5. 更新两份用法说明和开发账本,向 `case_analysis.media_processor` 回传代码、测试、互斥、回退、CPU/GPU 采样、审核和已知限制。