| name | bgm |
| description | 为视频添加背景音乐。支持免版权音乐来源、音量混合、淡入淡出效果。当需要为视频添加背景音乐、调整音乐与配音音量平衡时使用。 |
| argument-hint | [{"视频类型":"epic/corporate/upbeat/ambient"}] |
背景音乐技能
免版权音乐来源
推荐: Pixabay Music (无需署名,商用免费)
音乐风格选择
| 视频类型 | 推荐风格 | 关键词搜索 |
|---|
| 科技历程 | Epic, Cinematic | "epic cinematic", "technology" |
| 产品介绍 | Corporate, Upbeat | "corporate", "business" |
| 教程视频 | Ambient, Soft | "ambient", "background" |
| 发布会 | Inspiring, Motivational | "inspiring", "motivational" |
| 创意内容 | Modern, Electronic | "modern", "electronic" |
FFmpeg 音频混合
基础混合(配音优先)
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "[1:a]volume=0.05[bgm];[0:a][bgm]amix=inputs=2:duration=first[out]" \
-map "[out]" mixed_audio.mp3
带淡入淡出效果
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[1:a]volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
" \
-map "[out]" mixed_audio.mp3
完整音频处理流程
ffmpeg -stream_loop -1 -i bgm_original.mp3 \
-t 85 \
-af "volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3" \
bgm_processed.mp3
ffmpeg -i voiceover.mp3 -i bgm_processed.mp3 \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=first[out]" \
-map "[out]" final_audio.mp3
ffmpeg -i final_audio.mp3 \
-af "loudnorm=I=-16:TP=-1.5:LRA=11" \
final_audio_normalized.mp3
音量平衡建议
核心原则
配音清晰度优先:BGM 必须作为"背景"存在,绝不能盖过配音。人声频率(85-255Hz基频 + 泛音)与 BGM 重叠时尤其需要注意。
| 音频类型 | 相对音量 | 说明 |
|---|
| 配音 | 1.0 (100%) | 主音频,保持原音量 |
| BGM(有密集配音) | 0.05-0.08 | 确保配音清晰可辨 |
| BGM(有稀疏配音) | 0.08-0.12 | 配音间隙较多时 |
| BGM(无配音段落) | 0.15-0.25 | 片头/片尾/转场 |
| BGM(纯音乐段落) | 0.30-0.50 | 无配音时可提高 |
音量选择决策树
视频有配音吗?
├── 是 → 配音密度高吗(>70%时间有语音)?
│ ├── 是 → BGM volume = 0.05-0.06 (推荐 0.05)
│ └── 否 → BGM volume = 0.08-0.10
└── 否 → BGM volume = 0.25-0.40
测试方法
在正式渲染前,截取30秒包含配音的片段测试:
for vol in 0.04 0.05 0.06 0.08; do
ffmpeg -y -i bgm.mp3 -i voiceover.mp3 \
-filter_complex "[0:a]volume=$vol[bgm];[1:a][bgm]amix=inputs=2[out]" \
-map "[out]" -t 30 test_vol_$vol.mp3
done
动态音量控制(高级)
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[0:a]asplit=2[vo][vo_sidechain];
[1:a]volume=0.3[bgm];
[bgm][vo_sidechain]sidechaincompress=threshold=0.02:ratio=8:attack=50:release=500[bgm_ducked];
[vo][bgm_ducked]amix=inputs=2:duration=first[out]
" \
-map "[out]" ducked_audio.mp3
智能 BGM 长度计算
问题:无脑循环的弊端
简单的 -stream_loop -1 会导致:
- BGM 在非自然位置截断
- 音乐节奏与视频内容不匹配
- 循环接缝处听感不自然
解决方案:场景感知的 BGM 选择
策略 1: 选择合适长度的音乐
优先选择时长接近或略长于视频的 BGM:
| 视频时长 | 推荐 BGM 时长 | 策略 |
|---|
| < 60s | 60-90s | 单曲裁剪 |
| 60-120s | 90-180s | 单曲裁剪 + 淡出 |
| 120-300s | 180-360s | 单曲或精心设计的接缝 |
| > 300s | 多首拼接 | 场景转换处切换 |
策略 2: 基于场景结构计算
SCENES = {
"opening": {"start": 0, "duration": 8},
"scene1": {"start": 8, "duration": 14},
"scene2": {"start": 22, "duration": 16},
"scene3": {"start": 38, "duration": 14},
"scene4": {"start": 52, "duration": 20},
"closing": {"start": 72, "duration": 13},
}
def calculate_bgm_strategy(scenes: dict, bgm_duration: float, video_duration: float):
"""
计算 BGM 使用策略
Returns:
dict: 包含 trim_end, need_loop, loop_point 等信息
"""
if bgm_duration >= video_duration:
return {
"strategy": "single_trim",
"trim_end": video_duration,
"need_loop": False
}
scene_boundaries = sorted([s["start"] for s in scenes.values()])
best_loop_point = min(
scene_boundaries,
key=lambda x: abs(x - bgm_duration) if x <= bgm_duration else float('inf')
)
return {
"strategy": "scene_aware_loop",
"loop_point": best_loop_point,
"bgm_duration": bgm_duration,
"loops_needed": math.ceil(video_duration / best_loop_point)
}
策略 3: 动态音量适配场景
不同场景使用不同 BGM 音量:
SCENE_VOLUMES = {
"opening": 0.15,
"scene1": 0.05,
"scene2": 0.05,
"scene3": 0.05,
"scene4": 0.05,
"closing": 0.12,
}
def generate_volume_filter(scenes: dict, volumes: dict, fps: int = 30):
"""生成 FFmpeg 音量自动化滤镜"""
points = []
for name, scene in scenes.items():
start_frame = scene["start"] * fps
vol = volumes.get(name, 0.05)
points.append(f"volume=enable='between(t,{scene['start']},{scene['start']+scene['duration']})':volume={vol}")
return ",".join(points)
FFmpeg 场景感知混合
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[1:a]volume='if(lt(t,8),0.15,if(lt(t,72),0.05,0.12))':eval=frame,
afade=t=in:st=0:d=3,
afade=t=out:st=82:d=3[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
" \
-map "[out]" mixed_audio.mp3
最佳实践
- 优先选择时长合适的 BGM:避免循环,选择 >= 视频时长的音乐
- 如果必须循环:在场景转换处设置循环点,而非音乐结尾
- 场景边界淡入淡出:循环接缝处使用交叉淡化
- 动态音量:片头片尾可略高,配音密集段落降低
Remotion 动态音量控制 (推荐)
除了使用 FFmpeg 预处理,还可以在 Remotion 中直接实现动态音量控制。这种方式更灵活,便于调试。
基础用法:场景感知音量
import { Audio, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
import { SCENES } from "./config/scenes";
const SCENE_VOLUMES = {
opening: 0.15,
scene1: 0.05,
scene2: 0.05,
scene3: 0.05,
scene4: 0.05,
closing: 0.12,
};
const getDynamicVolume = (frame: number, fps: number): number => {
const currentTime = frame / fps;
for (const [sceneName, scene] of Object.entries(SCENES)) {
const sceneEnd = scene.start + scene.duration;
if (currentTime >= scene.start && currentTime < sceneEnd) {
return SCENE_VOLUMES[sceneName as keyof typeof SCENE_VOLUMES] ?? 0.05;
}
}
return 0.05;
};
export const VideoWithDynamicBGM: React.FC = () => {
const { fps } = useVideoConfig();
return (
<>
{/* 配音 - 固定音量 */}
<Audio src={staticFile("audio/voiceover.mp3")} volume={1} />
{/* BGM - 动态音量 */}
<Audio
src={staticFile("audio/bgm.mp3")}
volume={(frame) => getDynamicVolume(frame, fps)}
/>
</>
);
};
进阶用法:平滑过渡
import { interpolate } from "remotion";
const getSmoothVolume = (frame: number, fps: number): number => {
const currentTime = frame / fps;
if (currentTime < 7.5) return 0.15;
if (currentTime < 8) {
return interpolate(currentTime, [7.5, 8], [0.15, 0.05]);
}
if (currentTime < 71.5) return 0.05;
if (currentTime < 72) {
return interpolate(currentTime, [71.5, 72], [0.05, 0.12]);
}
return 0.12;
};
组件模板
import React from "react";
import { Audio, staticFile, useVideoConfig } from "remotion";
interface DynamicBGMProps {
src: string;
sceneVolumes: Record<string, number>;
scenes: Record<string, { start: number; duration: number }>;
smoothTransition?: boolean;
transitionDuration?: number;
}
export const DynamicBGM: React.FC<DynamicBGMProps> = ({
src,
sceneVolumes,
scenes,
smoothTransition = true,
transitionDuration = 0.5,
}) => {
const { fps } = useVideoConfig();
const getVolume = (frame: number): number => {
const currentTime = frame / fps;
for (const [sceneName, scene] of Object.entries(scenes)) {
const sceneEnd = scene.start + scene.duration;
if (currentTime >= scene.start && currentTime < sceneEnd) {
return sceneVolumes[sceneName] ?? 0.05;
}
}
return 0.05;
};
return (
<Audio
src={staticFile(src)}
volume={(frame) => getVolume(frame)}
/>
);
};
FFmpeg vs Remotion 对比
| 方面 | FFmpeg 预处理 | Remotion 动态控制 |
|---|
| 调试便捷性 | ⚠️ 需重新处理音频 | ✅ 实时预览 |
| 灵活性 | ⚠️ 固定一次 | ✅ 随时调整 |
| 渲染性能 | ✅ 无额外开销 | ⚠️ 微小开销 |
| 文件大小 | ⚠️ 预混合体积大 | ✅ 分离存储 |
| 推荐场景 | 最终交付 | 开发调试 |
建议工作流:
- 开发阶段使用 Remotion 动态控制快速调试
- 确定最终参数后,使用 FFmpeg 预处理生成交付版本
Python 脚本模板
"""
背景音乐处理脚本
"""
import subprocess
from pathlib import Path
def process_bgm(
bgm_path: str,
voiceover_path: str,
output_path: str,
video_duration: float,
bgm_volume: float = 0.05,
fade_duration: float = 3.0
):
"""
处理背景音乐并与配音混合
Args:
bgm_path: BGM 文件路径
voiceover_path: 配音文件路径
output_path: 输出文件路径
video_duration: 视频总时长(秒)
bgm_volume: BGM 相对音量 (0.0-1.0)
fade_duration: 淡入淡出时长(秒)
"""
fade_out_start = video_duration - fade_duration
filter_complex = f"""
[1:a]aloop=loop=-1:size=2e+09,atrim=0:{video_duration},
volume={bgm_volume},
afade=t=in:st=0:d={fade_duration},
afade=t=out:st={fade_out_start}:d={fade_duration}[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
""".replace("\n", "").replace(" ", "")
cmd = [
"ffmpeg", "-y",
"-i", voiceover_path,
"-i", bgm_path,
"-filter_complex", filter_complex,
"-map", "[out]",
output_path
]
subprocess.run(cmd, check=True)
print(f"✓ 混合音频输出: {output_path}")
def normalize_audio(input_path: str, output_path: str):
"""音量标准化到 -16 LUFS"""
cmd = [
"ffmpeg", "-y",
"-i", input_path,
"-af", "loudnorm=I=-16:TP=-1.5:LRA=11",
output_path
]
subprocess.run(cmd, check=True)
print(f"✓ 标准化输出: {output_path}")
if __name__ == "__main__":
process_bgm(
bgm_path="public/audio/bgm_epic.mp3",
voiceover_path="public/audio/synced_voiceover.mp3",
output_path="public/audio/mixed_audio.mp3",
video_duration=85,
bgm_volume=0.05,
fade_duration=3.0
)
normalize_audio(
input_path="public/audio/mixed_audio.mp3",
output_path="public/audio/final_audio.mp3"
)
Remotion 集成
在 Remotion 组件中使用混合后的音频:
import { Audio, staticFile } from "remotion";
export const FinalVideo: React.FC = () => {
return (
<>
{/* 使用混合后的音频(配音 + BGM) */}
<Audio src={staticFile("audio/final_audio.mp3")} volume={1} />
{/* 或者分开控制 */}
<Audio src={staticFile("audio/synced_voiceover.mp3")} volume={1} />
<Audio
src={staticFile("audio/bgm.mp3")}
volume={(f) => {
// 淡入淡出控制
const fps = 30;
if (f < 3 * fps) return (f / (3 * fps)) * 0.12;
if (f > 82 * fps) return ((85 * fps - f) / (3 * fps)) * 0.12;
return 0.12;
}}
/>
</>
);
};
工作流程
1. 确定视频风格 → 选择音乐关键词
2. 从免版权网站下载 BGM
3. 处理 BGM(循环/裁剪到视频时长)
4. 添加淡入淡出效果
5. 与配音混合(配音优先)
6. 音量标准化
7. 集成到视频
常见问题
| 问题 | 解决方案 |
|---|
| BGM 太响干扰配音 | 降低 volume 到 0.04-0.06(配音密集时推荐 0.05) |
| BGM 不够长 | 优先选择更长的音乐;必须循环时在场景边界处接缝 |
| 淡入淡出太突然 | 增加 fade_duration 到 4-5 秒 |
| 混合后音量不一致 | 使用 loudnorm 滤镜标准化 |
| 风格不匹配 | 重新选择更合适的 BGM |
| 循环接缝明显 | 使用交叉淡化或在场景转换处设置循环点 |