feat(unified-render): Phase 3 - 音频统一混音 + 灰度观测埋点 #232

Merged
xiaoxia merged 6 commits from feat/unified-render-phase3 into develop 2026-07-12 23:00:52 +08:00
Owner

变更内容

音频后处理混音架构

  • 音视频分离渲染:视频先渲染无声版,音频单独处理后 -c:v copy 合并,对齐链路B
  • 主图层音频 concat:main/broll 图层音频按顺序拼接
  • 独立音频轨 amix 混音:audio role 的独立音轨用 amix 混入,支持音量调节
  • 主音频源优先级:main > broll,background 不参与主音频(P1 修复)
  • 单clip音频截断:统一截断到 video_duration(P2 修复)

灰度观测埋点

  • 4 阶段结构化日志:start / video_done / audio_done / render_done
  • 关键字段:plan_id、耗时、分辨率、文件大小、has_audio、pass_through
  • 适配层日志:render success / failed,含 engine=unified 标识

其他

  • _resolve_layer_role 新增 config.role == "audio" 支持
  • 新增 11 + 4 = 15 个音频单测,合计 83 个全绿

单测

83 passed in 2.82s
## 变更内容 ### 音频后处理混音架构 - **音视频分离渲染**:视频先渲染无声版,音频单独处理后 `-c:v copy` 合并,对齐链路B - **主图层音频 concat**:main/broll 图层音频按顺序拼接 - **独立音频轨 amix 混音**:audio role 的独立音轨用 amix 混入,支持音量调节 - **主音频源优先级**:main > broll,background 不参与主音频(P1 修复) - **单clip音频截断**:统一截断到 video_duration(P2 修复) ### 灰度观测埋点 - **4 阶段结构化日志**:start / video_done / audio_done / render_done - **关键字段**:plan_id、耗时、分辨率、文件大小、has_audio、pass_through - **适配层日志**:render success / failed,含 engine=unified 标识 ### 其他 - `_resolve_layer_role` 新增 `config.role == "audio"` 支持 - 新增 11 + 4 = 15 个音频单测,合计 83 个全绿 ## 单测 ``` 83 passed in 2.82s ```
Author
Owner

复审结论 通过

变更概览

  • 范围:Phase 3 音频统一混音 + 灰度埋点(P1+P2 修复后)
  • 文件:3 个(unified_render_service.py / render_adapter.py / test_unified_render_service.py)
  • 新增/删除:+699 / -7
  • 单测:83 个全绿(67 渲染 + 16 adapter)

P1 修复验证

问题_mix_audio 主音频源按 layer 顺序遍历,background 排在 main/broll 前导致主音频丢失

修复确认

  1. 改为按优先级 main > broll 精确查找(layer_map + role 匹配)
  2. background 完全不参与主音频选择(符合图片素材无音轨的实际场景)
  3. 兜底逻辑也改为按 role 精确匹配:overlay > corner_voice
  4. 独立音频轨(audio role)同样改为按 role 精确查找

P2 修复验证

# 问题 修复状态
P2-1 单 clip 音频路径未截断到 video_duration 已修复:final_duration = min(effective_duration, video_duration),带防御性判断
P2-2 background 图片素材音频可用性判断 随 P1 修复自然解决:background 不再参与主音频

新增单测验证

4 个新增单测全部覆盖到位:

  • test_mix_audio_background_not_used_as_main — background 排除验证
  • test_mix_audio_main_priority_over_broll — main > broll 优先级验证
  • test_mix_audio_broll_used_when_no_main — broll 兜底验证
  • test_mix_audio_single_clip_truncated_to_video_duration — 单 clip 截断验证

灰度埋点完整性

UnifiedRenderService 层(4 个阶段):

  1. start render — clip_count / layers / clip_counts
  2. video render done — duration_ms / pass_through
  3. audio mix done — duration_ms
  4. render done — total_ms / video_ms / audio_ms / output_duration / output_size / resolution / has_audio

RenderAdapter 层(2 个结果):

  • render success — engine=unified + duration + file_size + resolution + clip_count
  • render failed — engine=unified + error

埋点维度完整,可支撑灰度期间新旧引擎对比分析。


其他确认项

  • _resolve_layer_role 支持 audio role(通过 config.role=audio
  • 音频混音支持音量调节(volume 配置)
  • 错误处理完备:amix / merge 均有 try-catch + 错误日志
  • 直通路径输出无声视频(-an),音频统一在混音阶段处理,架构一致

后续建议(P3,非阻塞)

  1. 直通场景优化:单 clip 直通 + 无独立音频时,视频 copy + 音频提取 + 音视频合并的 3 次 FFmpeg 可合并为 1 次调用(流复制 + 音频滤镜)
  2. 时长计算抽离_clip_effective_duration_resolve_clips / _estimate_total_duration 中时长计算逻辑有重复,可统一工具函数

结论:复审通过,可合并。 建议按顺序合并:#230 → #231 → #232。

## 复审结论 ✅ 通过 ### 变更概览 - **范围**:Phase 3 音频统一混音 + 灰度埋点(P1+P2 修复后) - **文件**:3 个(unified_render_service.py / render_adapter.py / test_unified_render_service.py) - **新增/删除**:+699 / -7 - **单测**:83 个全绿(67 渲染 + 16 adapter) --- ### P1 修复验证 ✅ **问题**:`_mix_audio` 主音频源按 layer 顺序遍历,background 排在 main/broll 前导致主音频丢失 **修复确认**: 1. ✅ 改为按优先级 `main > broll` 精确查找(layer_map + role 匹配) 2. ✅ background 完全不参与主音频选择(符合图片素材无音轨的实际场景) 3. ✅ 兜底逻辑也改为按 role 精确匹配:`overlay > corner_voice` 4. ✅ 独立音频轨(audio role)同样改为按 role 精确查找 --- ### P2 修复验证 ✅ | # | 问题 | 修复状态 | |---|------|----------| | P2-1 | 单 clip 音频路径未截断到 video_duration | ✅ 已修复:`final_duration = min(effective_duration, video_duration)`,带防御性判断 | | P2-2 | background 图片素材音频可用性判断 | ✅ 随 P1 修复自然解决:background 不再参与主音频 | --- ### 新增单测验证 ✅ 4 个新增单测全部覆盖到位: - `test_mix_audio_background_not_used_as_main` — background 排除验证 - `test_mix_audio_main_priority_over_broll` — main > broll 优先级验证 - `test_mix_audio_broll_used_when_no_main` — broll 兜底验证 - `test_mix_audio_single_clip_truncated_to_video_duration` — 单 clip 截断验证 --- ### 灰度埋点完整性 ✅ **UnifiedRenderService 层**(4 个阶段): 1. `start render` — clip_count / layers / clip_counts 2. `video render done` — duration_ms / pass_through 3. `audio mix done` — duration_ms 4. `render done` — total_ms / video_ms / audio_ms / output_duration / output_size / resolution / has_audio **RenderAdapter 层**(2 个结果): - `render success` — engine=unified + duration + file_size + resolution + clip_count - `render failed` — engine=unified + error 埋点维度完整,可支撑灰度期间新旧引擎对比分析。 --- ### 其他确认项 - ✅ `_resolve_layer_role` 支持 `audio` role(通过 `config.role=audio`) - ✅ 音频混音支持音量调节(`volume` 配置) - ✅ 错误处理完备:amix / merge 均有 try-catch + 错误日志 - ✅ 直通路径输出无声视频(`-an`),音频统一在混音阶段处理,架构一致 --- ### 后续建议(P3,非阻塞) 1. **直通场景优化**:单 clip 直通 + 无独立音频时,视频 copy + 音频提取 + 音视频合并的 3 次 FFmpeg 可合并为 1 次调用(流复制 + 音频滤镜) 2. **时长计算抽离**:`_clip_effective_duration` 与 `_resolve_clips` / `_estimate_total_duration` 中时长计算逻辑有重复,可统一工具函数 --- **结论:复审通过,可合并。** 建议按顺序合并:#230 → #231 → #232。
gitea-actions bot added 4 commits 2026-07-12 22:16:36 +08:00
- 音频后处理:主图层concat拼接 + 独立音频轨amix混音
- 音视频分离:视频先渲染无声版,音频后处理后合并
- 支持音量调节(audio clip config.volume)
- 灰度埋点:渲染开始/视频渲染完成/音频混音完成/总完成 四个阶段日志
- 新增11个音频混音单元测试
- _resolve_layer_role 支持 audio role 映射
- P1: _mix_audio 主音频源按优先级 main > broll 精确查找,background 不参与主音频
- P2: 单clip音频路径统一截断到 video_duration
- 补充4个单测:background排除、main>broll优先级、broll兜底、单clip截断
- 合计 83 个单测全绿
- 直通场景3次FFmpeg合并为1次:视频vf + 音频aac编码同时输出,省去音频提取+音视频合并
- background直通不带音频(图片素材无音轨),main/broll直通带aac音频
- 时长计算逻辑统一抽离:4处重复代码全部改用 _clip_effective_duration
- 新增3个单测 + 修复2个原有测试,合计86个全绿
gitea-actions bot force-pushed feat/unified-render-phase3 from 225cd07d75 to ca86240c06 2026-07-12 22:16:36 +08:00 Compare
xiaoxia added 1 commit 2026-07-12 22:17:27 +08:00
fix(unified-render): 无音轨视频防御 + probe_has_audio 工具
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m4s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m3s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 4m23s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
73e853127a
- 新增 ffmpeg_utils.probe_has_audio:ffprobe 探测音频流
- _mix_audio 入口过滤无音频流的 clip,避免 FFmpeg 引用 [i:a] 失败
- 新增 _clip_has_audio 缓存方法,同 clip 只探测一次
- 6个新增单测覆盖:全无音频、部分无音频、主图层无但独立音轨有、缓存、双无音频兜底
xiaoxia added 1 commit 2026-07-12 22:27:22 +08:00
fix(lint): resolve flake8 E741 ambiguous variable 'l'
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m8s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m42s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m19s
0aa7997276
Author
Owner

无音轨视频bug修复 - 复审通过

验证项

1. probe_has_audio 工具函数

  • ffprobe -select_streams a:0 探测第一条音频流,输出 codec_type=audio 表示有音轨
  • 探测异常时保守返回 True(避免误删音频),fail-safe 设计正确
  • 10s 超时防止卡住

2. _mix_audio 入口过滤

  • main_clips 和 audio_clips 在处理前各过一遍 _clip_has_audio 过滤
  • 全部无音频时返回 None(和原逻辑一致,调用方已有处理)
  • 部分无音频时正常走剩余 clip 的 concat/amix 逻辑

3. 缓存机制

  • _audio_cache 字典按 local_path 缓存,同一 clip 多次引用只探测一次
  • 生命周期绑定 service 实例,不会跨任务污染

4. 6 个新增单测覆盖到位

  • 主图层全无语频 → 返回 None
  • 主图层部分无音频 → 过滤后正常 concat(走单clip路径)
  • 主图层全无语频但有独立音轨 → 正常 amix
  • 两边都无音频 → 返回 None
  • 缓存命中测试 → 同一 clip 只探测 1 次

5. 其他格式修复

  • l 歧义变量名改为 layer / label,flake8 E741 修复
  • import 排序 / black 格式化已统一

单测结果

52 passed in 2.37s (全绿)

结论

可以合并。

**无音轨视频bug修复 - 复审通过 ✅** ### 验证项 **1. probe_has_audio 工具函数 ✅** - ffprobe `-select_streams a:0` 探测第一条音频流,输出 `codec_type=audio` 表示有音轨 - 探测异常时保守返回 True(避免误删音频),fail-safe 设计正确 - 10s 超时防止卡住 **2. _mix_audio 入口过滤 ✅** - main_clips 和 audio_clips 在处理前各过一遍 `_clip_has_audio` 过滤 - 全部无音频时返回 None(和原逻辑一致,调用方已有处理) - 部分无音频时正常走剩余 clip 的 concat/amix 逻辑 **3. 缓存机制 ✅** - `_audio_cache` 字典按 local_path 缓存,同一 clip 多次引用只探测一次 - 生命周期绑定 service 实例,不会跨任务污染 **4. 6 个新增单测覆盖到位 ✅** - 主图层全无语频 → 返回 None - 主图层部分无音频 → 过滤后正常 concat(走单clip路径) - 主图层全无语频但有独立音轨 → 正常 amix - 两边都无音频 → 返回 None - 缓存命中测试 → 同一 clip 只探测 1 次 **5. 其他格式修复 ✅** - `l` 歧义变量名改为 `layer` / `label`,flake8 E741 修复 - import 排序 / black 格式化已统一 ### 单测结果 52 passed in 2.37s ✅(全绿) ### 结论 可以合并。
xiaoxia merged commit fdcf48103e into develop 2026-07-12 23:00:52 +08:00
xiaoxia deleted branch feat/unified-render-phase3 2026-07-12 23:00:52 +08:00
Sign in to join this conversation.