Remotion LabRemotion Lab
媒體整合Audio Ducking:配音出現時自動降配樂——做出廣告級的旁白混音
audioduckingmixingvoiceoverintermediate

Audio Ducking:配音出現時自動降配樂——做出廣告級的旁白混音

用 Remotion 的 dynamic volume function 做 ducking。配音出現時 BGM 自動降下來、結束後彈回去,廣告/Podcast/YouTube 影片的標配技巧,幾行 interpolate 就搞定。

成品預覽

DAW 風格的混音介面,左到右掃描的播放頭碰到 voiceover clip 的瞬間,BGM 推桿明顯掉下來、波形變暗、彈出「DUCKING −12 dB」提示——這就是一個 volume 函式做出的廣告級混音技巧。


這篇會做出什麼

一個會自動 ducking 的雙軌音訊

  • BGM 軌:背景音樂全程播放
  • VOICE 軌:配音 / 旁白出現在影片中段
  • 自動 ducking:配音播放期間 BGM 音量降到 0.15,結束後再升回 0.5

整篇只教一個技術<Audio volume={(frame) => …}> 的 dynamic volume function。


前置知識


Step 1:把 BGM 跟 VOICE 兩軌都掛上去

Claude Code:

新增 Composition "DuckingDemo":
- 1920x1080 fps 30 durationInFrames 300(10 秒)
- 新建 src/compositions/DuckingDemo.tsx 並在 Root.tsx 註冊

元件內掛兩個 <Audio>:
1. BGM:<Audio src={staticFile("audio/bgm.mp3")} volume={0.5} />
2. VOICE:用 <Sequence from={105} durationInFrames={110}>
         包 <Audio src={staticFile("audio/voiceover.mp3")} volume={1.0} />

播一次:聽起來糟糕——配音出來時 BGM 還是滿格音量,整個被蓋過去。這就是「沒做 ducking」的影片。


Step 2:把 BGM 的 volume 換成 function

這就是整篇的核心——把 volume 從一個固定數字改成一個 (frame) => number 的函式。

改寫 BGM 的 <Audio>:

const VOICE_START = 105;
const VOICE_END = 215;
const FADE = 12;

<Audio
  src={staticFile("audio/bgm.mp3")}
  volume={(frame) => {
    if (frame > VOICE_START - FADE && frame < VOICE_END + FADE) {
      return interpolate(
        frame,
        [
          VOICE_START - FADE,
          VOICE_START,
          VOICE_END,
          VOICE_END + FADE,
        ],
        [0.5, 0.15, 0.15, 0.5],
      );
    }
    return 0.5;
  }}
/>

讀一下這個 function:

  1. 沒在 voice 期間 → 回傳 0.5(正常音量)
  2. 進入 voice 前 12 幀 → 從 0.5 線性下降0.15(fade out)
  3. voice 期間 → 維持 0.15(被 ducked)
  4. voice 結束後 12 幀 → 從 0.15 線性回升0.5(fade in)

這個四點 interpolate pattern 是 Remotion ducking 的「武林絕學」——記住它,廣告影片、Podcast、YouTube 全部都用這個。

播一次:旁白出現時 BGM 自動讓出空間,結束後音量自然回來。聽起來瞬間升級。

💡 動態音量、volume 函式的完整簽名與更多 pattern 在 /docs/audio-volume


Step 3:為什麼一定要 fade 邊界?

想偷懶寫成:

volume={(frame) => {
  if (frame >= VOICE_START && frame <= VOICE_END) return 0.15;
  return 0.5;
}}

也會 work,但是聽起來會「卡」一下——0.5 → 0.15 是音量瞬間跳變,會聽到 click / pop 聲。

加上 12 幀(0.4 秒)的 fade 之後,聽起來像專業混音師在現場推 fader——這就是廣告質感的差別。


Step 4:抽成一個 helper(選擇性)

整支影片可能有十幾個 voice clip,每個都複製一遍 ducking 邏輯太累。抽成 helper:

// src/audio/ducking.ts
import { interpolate } from "remotion";
 
export type VoiceWindow = { start: number; end: number };
 
export const buildDuckingVolume = (
  windows: VoiceWindow[],
  baseVolume = 0.5,
  duckedVolume = 0.15,
  fade = 12
) => {
  return (frame: number) => {
    for (const w of windows) {
      if (frame > w.start - fade && frame < w.end + fade) {
        return interpolate(
          frame,
          [w.start - fade, w.start, w.end, w.end + fade],
          [baseVolume, duckedVolume, duckedVolume, baseVolume]
        );
      }
    }
    return baseVolume;
  };
};

用法:

const VOICE_WINDOWS = [
  { start: 105, end: 215 },
  { start: 360, end: 510 },
  { start: 720, end: 900 },
];
 
<Audio
  src={staticFile("audio/bgm.mp3")}
  volume={buildDuckingVolume(VOICE_WINDOWS)}
/>

整支 podcast 三段旁白只要列窗口,BGM 自己會在對的時間點 duck。


Step 5:渲染

渲染 DuckingDemo:
- 1920x1080 H.264
- 音訊 codec aac
- 音訊 bitrate 192k
- 輸出 out/audio-ducking.mp4

戴耳機聽——配音那一段你會清楚聽到 BGM 「往後退一步」讓出空間,這就是 ducking 在做的事。


完成!回顧學到的概念

概念重點
<Audio volume> 接函式volume 可以是 number(frame: number) => number
四點 interpolate ducking pattern[VS-FADE, VS, VE, VE+FADE][base, ducked, ducked, base]
為什麼要 fade 邊界避免音量瞬跳的 click pop
抽 helper 管理多窗口整支影片所有 voice clip 共用一條 ducking 規則

本篇涵蓋的官方文件


常見問題

Q:可以對 voice 軌也做 ducking 嗎(讓 voice 在某個 SFX 出現時降下來)? A:可以——volume 函式對任何 <Audio> 都成立。原則是「誰需要讓位給誰,誰就掛 ducking」。

Q:ducked volume 該設多少? A:行業常用是把 BGM 降 1216 dB(線性大約 0.150.25)。太低會像「靜音」、太高蓋不住人聲。0.15 是廣告片常見值。

Q:fade 應該幾幀? A:815 幀(30fps 下大約 0.250.5 秒)最自然。少於 5 幀會聽到 click,多於 20 幀會「拖泥帶水」。

Q:為什麼不用 ffmpeg 預先 ducking? A:可以——但 Remotion 的好處是 voice 窗口跟 React props 連動,你可以在同一份 code 裡同時控制畫面跟音訊(例如「字幕出來時降配樂」)。一個資料來源,一份真相。

Q:能不能做到「自動偵測 voice 在哪」自動 ducking? A:用 getAudioDurationInSeconds + 一個外部 silence-detection 工具(例如 ffmpeg 的 silencedetect filter)取出 voice 起訖點,再丟進 VOICE_WINDOWS。完全自動化的 pipeline 是進階題。


下一步

有問題歡迎到 FB 社群 討論!