创建人员: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 前不得修改产品代码或运行真实媒体。
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,长时间软件解码可挤占桌面响应。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。dev/project-dev/transcribe_media.py。dev/project-dev/extract_ppt_slides.py。不新增产品 package、常驻进程、服务、数据库、API、GUI、调度器或用户参数。为避免引入第三个产品模块,跨脚本公共保护的 stdlib-only 内部 owner 固定放在现有 transcribe_media.py,extract_ppt_slides.py 只导入该内部入口;两个公开 CLI 和业务函数签名保持不变。
transcribe_media.py 增加一个 stdlib-only 内部上下文入口,由两个 CLI 的 main 在解析参数后、任何 FFprobe/FFmpeg、模型加载、暂存或正式输出动作前进入:
Local\\MBXMediaHeavyTaskV1;CreateMutexW 后以 WaitForSingleObject(..., 0) 非阻塞获取。WAIT_OBJECT_0 或 WAIT_ABANDONED 表示本任务取得唯一占用;退出时 ReleaseMutex 并 CloseHandle。进程异常退出时由 Windows 释放 owner,不使用易残留的“文件存在”判断。WAIT_TIMEOUT 明确返回“已有重型媒体任务运行,拒绝并行启动”,不得等待、排队或启动任何外部进程;其他 Win32 错误带错误码失败。extract_ppt_slides.py 与 transcribe_media.py 必须调用同一个导入对象和同一个 mutex name,不复制第二套锁实现。互斥只覆盖一次 CLI 重型任务生命周期,不建立常驻服务,不改变输出目录锁和 CreateNew/拒绝覆盖语义。
取得互斥后,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。
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。
现有 FFprobe JSON 的第一视频流增加读取 codec_name;VideoInfo 增加带默认值的 codec_name,不改变既有三参数测试构造。映射固定为:
codec_name |
NVDEC decoder |
|---|---|
h264 |
h264_cuvid |
hevc |
hevc_cuvid |
其他 codec 没有猜测性 decoder 映射,直接进入有线程上限的 CPU 路径并明确告警。
支持 codec 的首次扫描命令固定包含:
-hwaccel cuda -hwaccel_output_format cuda -c:v <h264_cuvid|hevc_cuvid>
-threads 4
-filter_threads 2 -filter_complex_threads 2
-vf hwdownload,format=nv12,setpts=PTS-STARTPTS,fps=fps=1:start_time=0,scale=<w>:<h>: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 路径证据,显存变化不能替代该证据。
SlideExtractionError、stderr 尾部、子进程回收和暂存清理。h264_cuvid/hevc_cuvid;硬件命令包含 CUDA、hwdownload、固定滤镜顺序、4/2 线程上限。-threads 4。run/Popen 在 Windows 都收到 BELOW_NORMAL_PRIORITY_CLASS;优先级设置失败时没有外部进程调用。KeyboardInterrupt、普通异常及 Win32 获取错误均验证 mutex handle 关闭;不创建队列、守护进程或锁文件。基线为 41/41 PASS。实现阶段测试允许 FFmpeg 合成媒体,不允许读取完整业务源。
只有独立实现审核 PASS 后才允许:
dev/project-dev/tmp/DEV-PROJECT-INFO-MEDIA-HOST-RESPONSIVENESS-20260804-001/ 用 FFmpeg 从既有 H.264 源生成一个从 00:30:00 开始、目标 590 s 的独立短片;FFprobe 实测必须 <=600 s。生成命令本身使用线程上限和 BelowNormal 启动,不覆盖任何文件。extract_ppt_slides.py,输出写新目录;不得并行启动转写,不得重试或调参,不得运行 3.57 小时/4 小时媒体。转写侧只做离线命令/优先级/互斥回归;本轮真实资源验收由 PPT NVDEC 路径代表重型 FFmpeg 场景。
新增测试辅助 dev/project-dev/test/monitor_media_host_responsiveness.ps1,不进入产品 CLI。它以 500 ms 目标周期启动唯一被测 Python 并记录:
Processor(_Total)\\% Processor Time。nvidia-smi 原始行、GPU utilization、decoder utilization、全局显存;同任务 PID 的 Windows GPU Process Memory\\Dedicated Usage 专用显存及原始实例名。逐样本写 resource_samples.csv,汇总写 resource_summary.json,原始命令和日志分别保存。stdout 中的 NVDEC/CUDA (h264_cuvid) 与 FFmpeg 命令参数是硬件路径主证据;decoder utilization 仍逐样本记录,不能只用显存声明硬件解码。
5 s 的至少 10 个连续有效样本取整机 CPU 算术平均;所有完整窗口必须 <85%。500 ms 有效样本的整机 CPU 均 >=85%,即不得连续约 10 秒达到或超过 85%。间隔秒 × 逻辑处理器数 归一到整机 0..100%;RAM 为同采样点根与全部后代 WorkingSet 之和。>1500 ms、根 PID 丢失、未观察到扫描 FFmpeg、优先级不是 BelowNormal、GPU/decoder 查询失败或必填字段缺失时 sampling_valid=false,不得宣告资源 PASS。真实验收通过条件:被测 exit=0;整机两项 CPU 门禁通过;根 Python 和全部观察到的 FFmpeg 均 BelowNormal;NVDEC 命令/日志证据存在;CPU/RAM/GPU/decoder/显存字段完整;业务输出和源保护回归通过。未给 RAM/显存数值上限,只记录峰值,不自行扩展门禁。
dev.reviewer.project 限定实现审核;PASS 前禁止真实片段生成和运行。case_analysis.media_processor 回传代码、测试、互斥、回退、CPU/GPU 采样、审核和已知限制。