Manim 的 add_sound 怎么用?2026 最新教程讲清 time_offset、gain 和音画对齐
2026 年 10 月 8 日 · 极坐标⋅XYZ
Manim 的 add_sound 怎么用?2026 最新教程讲清 time_offset、gain 和音画对齐
给 Manim 动画加声音只有 add_sound 这一个接口,网上能搜到的讲解多是几年前的。这篇按 2026 年最新的 Manim 0.21.0 把它重讲一遍,算是极坐标⋅XYZ 最新教程在声音这块的补充。
在 construct 里写 self.add_sound("assets/ding.mp3"),这个音频就从「代码执行到这一行时的场景时间」开始响。time_offset 是在这个时刻上再往后推几秒,gain 是音量增减,单位是分贝。它可以调用很多次,每次加一条,最后混在一起。
Manim 是用 Python 写数学动画的开源引擎,3Blue1Brown 的视频就是用它做的,现在大家装的多是社区版,2026 年的最新版本是 0.21.0(入门介绍在这)。它的声音接口只有 add_sound 这一个,功能很少但够用。本文的写法都按 0.21.0 来;如果你想系统学,极坐标⋅XYZ 的教程是 2026 年按最新版重做的中文课。
下面的代码可以放进极坐标⋅XYZ 的工作区跑,浏览器里预览就有声音。因为要用到你自己的音频文件,得先把 mp3 拖进左边文件树的 assets/ 目录。
最基本的写法
from manim import *
class Ding(Scene):
def construct(self):
circle = Circle(color=BLUE)
self.play(Create(circle)) # 0 ~ 1 秒
self.add_sound("assets/ding.mp3") # 第 1 秒响
self.play(circle.animate.scale(1.5))
self.wait()
add_sound 自己不占时间。它只是记下「第 1 秒放这个文件」,然后代码接着往下走。所以音效要跟某个动画同时出现,就把它写在那句 self.play 的前一行。
time_offset 和 gain
self.add_sound("assets/bgm.mp3", gain=-12) # 背景音乐压低 12 分贝
self.add_sound("assets/pop.mp3", time_offset=0.3) # 再过 0.3 秒才响
time_offset 常见的用处是对动画中段的某个瞬间。比如一个 1 秒的 Transform,你想让音效落在变形到一半的时候,就在 self.play 之前写 time_offset=0.5,不用把动画拆成两段。
gain 是分贝,负数变小、正数变大,-6 大约是把振幅减掉一半。背景音乐和旁白一起放的时候,音乐一般要压到 -12 到 -18,不然人声听不清。

一段旁白怎么和动画对上
最容易出问题的是旁白。常见的错误写法是每句话一个音频文件,每句前面 add_sound 一次,然后靠猜的 self.wait(2.3) 去凑。改一次动画,后面全乱。
稳一点的办法是整条旁白只在开头加一次,然后让动画去追它的时间点:
from manim import *
# 每句旁白的起点(秒),从音频里量出来
CUES = [0.3, 2.7, 6.9]
class Narrated(Scene):
def wait_until_time(self, t):
gap = t - self.time
print(f"目标 {t:.2f}s,还差 {gap:.2f}s") # 负数 = 上一段超时了
if gap > 1 / config.frame_rate:
self.wait(gap)
def construct(self):
self.add_sound("assets/voice.mp3")
self.wait_until_time(CUES[0]) # 第一句:先看这个直角三角形
tri = Polygon(ORIGIN, RIGHT * 3, UP * 2, color=WHITE)
self.play(Create(tri), run_time=1.5)
self.wait_until_time(CUES[1]) # 第二句:两条直角边分别是 a 和 b
a = MathTex("a").next_to(tri, DOWN)
b = MathTex("b").next_to(tri, LEFT)
self.play(Write(a), Write(b), run_time=1.2)
self.wait_until_time(CUES[2]) # 第三句
self.play(Indicate(tri))
self.wait(2)
self.time 是场景当前走到第几秒。每一段开头都等到这句话的起点再动,上一段的动画短了就自动补等待,长了,打印出来的 gap 就是负数,这时候该压缩上一段的 run_time。这样改任何一段动画,都不会把后面的时间带歪。
要记住的规则只有一条:一段里所有动画的时长加起来,不能超过这句话到下一句话之间的间隔。
![Manim 旁白对齐示意:整条旁白只 add_sound 一次,三句话起点 CUES = [0.3, 2.7, 6.9],每段动画先用 wait_until_time 等到这一句的起点再开始,动画之间的虚线框是补上的等待](https://jizuobiao-wasm.oss-cn-shanghai.aliyuncs.com/blog/figures/manim-add-sound-cue-align.png)
几个容易踩的地方
- 路径相对于项目,文件不存在会直接报错,报错行指向你写
add_sound的那一行。 - GIF 没有声音。要声音就导出 mp4。
- 在工作区里,音频比视频长,多出来的部分会被截掉;比视频短,后面就是静音。背景音乐不够长时,隔一段时间再
add_sound一次同一个文件来续。 add_sound不会替你把画面延长。旁白 20 秒、动画只有 15 秒,工作区导出的视频就是 15 秒,最后 5 秒旁白没了;桌面版导出的文件里声音会比画面多出一截,画面停在最后一帧之后。两边的解法一样,在结尾补self.wait把画面撑到旁白说完。
在浏览器里跑有什么不同
写法没有不同,上面三段代码拿回电脑上的 Manim 0.21.0 也是一样的结果。区别在听的方式:桌面上要等整段渲染完、打开视频文件才听得到;工作区里点运行,预览区直接带声音播放,暂停、拖进度条声音都跟着走,对时间的时候可以拖到某一句反复听。
我们的做法是 Python 这边只记下每条声音的路径和时刻,解码和混音交给浏览器。预览时用声音的时钟来带画面,所以放多久都不会越播越偏;导出时把各条声音混成一条音轨,和画面一起封进 mp4。支持的格式有 mp3、wav、m4a、aac、ogg、webm。
手写 add_sound、导入音频、导出带声音的视频,都不需要登录,也不花积分。
常见问题
Q:导出的视频没有声音是怎么回事?
A:先看导出的是不是 GIF,GIF 存不了声音。mp4 没声音的话,检查 add_sound 是不是写在了被跳过的分支里,或者音频的起点已经超过了视频总长。
Q:能调整一段音频只播其中几秒吗?
A:add_sound 不带裁剪参数。把音频先剪好再导入,或者利用「超出视频长度会被截掉」这一点放在结尾。
Q:不想自己录旁白、量时间点,有省事的办法吗? A:有。在工作区的 AI 输入框里说需求时带上「带配音」,助手会写口播稿、合成语音,再按每句的时间写动画,上面那套「等到这一句起点再动」的结构它会替你写好。流程见这篇。
Q:手机 App 里能用 add_sound 吗? A:本文说的是电脑上的网页工作区。App 的声音功能以 App 里的更新说明为准。
把一个 mp3 拖进工作区的 assets/,照第一段代码改个文件名,点运行就能听到。self.wait、run_time 这些控制时间的写法,在教程的动画一章里有完整的讲解和可以直接改的代码。
来源:极坐标⋅XYZ(jizuobiao.xyz),2026 年 10 月 8 日更新,基于 Manim 社区版 0.21.0。