🎬 Meta电影音频生成器揭秘:第五部分,你绝对想不到的幕后技术!🎧


4 个月前

引言

Movie Gen Audio 是一款生成式 AI 模型,它能够根据输入的视频生成音频,或者在给定视频和部分音频的情况下扩展音频。此外,用户还可以通过提供或添加文本提示来控制生成或扩展的音频。

对于一段视频,其音频内容可能是多样的。音频可能来自场景内,也可能是为了强调某些内容而添加的场景外音,或者仅仅是角色对话时的背景音乐等。

为了更清晰地说明,他们通过表格对视频中可能出现的音频进行了分类。

None

与视频相关的不同类型的声音

在收集的数据集中,他们重点关注了以下几类音频,并忽略了其他类型:

  1. 场景内声音(Diegetic General Sounds)

    • 屏幕内的声音,如海浪拍打声
    • 屏幕外的声音,如狗吠声,这些声音自然发生在场景环境中
    • 与可见动作或环境匹配的音效(如脚步声或自然环境声)
  2. 非场景内音效(Non-diegetic Sound Effects)

    • 罐头笑声(后期制作中添加的)
    • 紧张感增强音效(用于营造紧张气氛的音效)
    • 后期制作中添加的音效,这些音效并非自然发生在场景中
  3. 器乐音乐(Instrumental Music)

    • 后期制作中添加的背景音乐
    • 非人声的音乐配乐和主题曲
    • 支持情绪并与场景动作对齐的音乐

非场景内音效与器乐音乐的关键区别在于,非场景内音效是为特定戏剧性时刻添加的独立音效元素,而器乐音乐则是为整个场景提供连续的音乐伴奏,设定整体的情感基调和氛围。

你可以观看《爱乐之城》来体会音频如何让一个简单的故事变得美丽,并理解我们所说的不同类型音效。

现在你已经知道 Movie Gen Audio 模型能够生成/扩展音频,以及它可以生成/扩展的音频类型,接下来让我们关注它是如何做到这一点的。和往常一样,我们将通过 FTI 架构来剖析技术细节。请阅读这篇博客,了解我所说的通过 FTI 架构理解的含义。

所有代码示例均使用 Code Sonnet 3.5 进行了增强

特征管道

思考一下我们需要训练这个模型的数据。它应该是大量的样本,每个样本必须包含视频、音频以及相应的音频描述。

但音频的类型是多样的。你如何识别或过滤出包含我们所需音频(场景内声音、非场景内音效或器乐音乐)的视频?如果你被分配了为这个模型收集数据的任务,你可能会大规模收集带有音频的视频(同时获取音频描述的情况很少见)。以下是 Meta AI 团队为获取所需数据所采取的步骤。

  1. 初始过滤

    原始视频/音频数据 ↓ 时长过滤(4–120秒) ↓ 分辨率过滤(≥480像素) ↓ 静态内容移除 ↓ 文字覆盖移除(OCR,即移除包含文字的视频) ↓ 去重

他们使用了复制检测嵌入来移除重复视频(去重)。使用机器学习模型来训练新的机器学习模型所需的数据!!

2. 音频分类

过滤后的数据
↓
音频事件检测(AED)
- 标记 527 种可能的音频事件
- 映射到基本类别(语音/音乐/声音)
↓
音频-视觉对齐评分(CAVTP)
- 测量音频-视频嵌入之间的余弦相似度
- 根据阈值分类为场景内/非场景内

None

AED 模型(一种机器学习算法)可以根据输入的音频将其分类为 527 个类别(Audioset 本体)。

CAVTP(对比音频-视频-文本预训练)评分用于帮助分类视频中的音频是场景内还是非场景内。CAVTP 评分是通过 CAVTP 模型中的音频和视频嵌入之间的余弦相似度来衡量的。

我们得到了所需的数据,即我们有了一个包含视频和所需音频类型(场景内、非场景内和器乐)的数据集,供模型训练。剩下的就是生成音频描述。然后我们的特征库就完全准备好了。

3. 元数据生成

质量评分:

复制处理后的片段
↓
音频质量模型(1–10 分)

在此过程中使用了一个深度学习模型。

音频质量模型:用于评估音频的质量

B. 字幕生成:

复制合格的片段
↓
音频事件描述
↓
音乐风格描述(如果适用)
↓
语音/音乐存在标签

在此过程中使用了两个深度学习模型。

音频字幕模型:根据音频生成字幕

音乐字幕模型:与 AED 结合(以区分语音和音乐的存在),包含音乐的音频使用此模型生成字幕。

以下是他们通过执行上述两个过程(质量评分和字幕生成)创建的描述示例。

None

None

Movie Gen Audio 的特征管道概述

以下是所有训练数据(包括测试和验证数据)最终在特征库中的组织方式。因此,我们的特征库中有多个样本,每个样本包含视频、音频和相应的音频描述。

训练数据组织:

A. 时长结构:

复制最终数据集
↓
10 秒音频块(主要)
↓
30 秒音频块(补充)
(训练比例为 5:1)

B. 音量分布:

复制主要类别:
- 纯声音:约 1 亿个样本
- 音乐组合:每个约 1000 万个样本
- 语音组合:每个约 1000 万个样本
总计:约 1 亿个样本 / 约 100 万小时

训练管道

类似于我们通过一个数据样本学习 Movie Gen Video 模型的训练管道,让我们也通过同样的方式来理解这个模型。我将提供一个粗略的 Python 脚本,然后我们将理解其工作原理。

None

Movie Audio Gen 模型的架构

def process_single_sample(self, video, audio, text_caption, flow_time_step):
    """
    通过 Movie Gen Audio 模型处理单个训练样本
    
    参数:
        video: 输入视频张量,形状为 [10s * 30fps, 3, H, W] = [300, 3, 720, 1280]
        audio: 输入音频 48kHz,形状为 [480000, 2](10s * 48000Hz,立体声)
        text_caption: 文本描述
        flow_time_step: 当前流时间步长 t(标量,介于 0 和 1 之间)
    """
    batch_size = 1  # 对于单个样本
    
    # 1. 生成噪声潜在表示
    # DAC-VAE 将 48kHz 降低到 25Hz:10s * 25Hz = 250 个时间帧
    clean_audio_latent = self.dac_vae_encoder(audio)  # 形状:[1, 250, 128]
    
    # 从标准正态分布中采样噪声
    noise = torch.randn_like(clean_audio_latent)  # 形状:[1, 250, 128]
    
    # 在时间步长 t 处创建噪声潜在表示
    noised_latent = (flow_time_step * clean_audio_latent + 
                     (1 - (1 - 1e-5) * flow_time_step) * noise)  # 形状:[1, 250, 128]
    
    # 2. 音频上下文
    audio_context = self.create_audio_context(clean_audio_latent)  # 形状:[1, 250, 128]
    
    # 3. 合并噪声潜在表示和音频上下文
    # 沿通道维度连接
    combined_audio = torch.cat([noised_latent, audio_context], dim=-1)  # 形状:[1, 250, 256]
    
    # 4. 处理视频分支
    # 空间调整:224x224 是许多视觉模型的标准尺寸
    resized_video = self.spatial_resize(video)  # 形状:[300, 3, 224, 224]
    
    # MetaCLIP 特征 
    video_features = self.metaclip(resized_video)  # 形状:[300, 1024]
    
    # 从 30fps 时间重采样到 25Hz 以匹配音频
    video_features = self.temporal_resample(video_features)  # 形状:[250, 1024]
    
    # 5. 主处理分支
    # 通道投影合并的音频
    latent_proj = self.channel_proj(combined_audio)  # 形状:[1, 250, 256]
    
    # 线性投影到 DiT 维度(假设 DiT-d = 4608,如论文中所述)
    features = self.linear_proj_dit(latent_proj)  # 形状:[1, 250, 4608]
    
    # 将视频特征投影到相同维度
    video_proj = self.video_linear_proj(video_features)  # 形状:[250, 4608]
    
    # 门控线性投影
    gated_features = self.gated_linear_proj(
        features,
        video_proj
    )  # 形状:[1, 250, 4608]
    
    # 自注意力
    attention_output = self.self_attention(gated_features)  # 形状:[1, 250, 4608]
    
    # 5. 条件分支
    # 文本:T5-Base 生成 756 维嵌入,最多 512 个标记
    text_features = self.t5_encoder(text_caption)  # 形状:[1, 512, 756]
    text_proj = self.text_linear_proj(text_features)  # 形状:[1, 512, 4608]
    
    # 时间嵌入
    time_embed = self.time_embedding(flow_time_step)  # 形状:[1, 256]
    time_mlp = self.time_mlp(time_embed)  # 形状:[1, 4608]
    
    # 6. DiT 块(假设如论文中所述有 36 层)
    dit_output = self.process_dit_blocks(
        attention_output,  # 形状:[1, 250, 4608]
        text_proj,        # 形状:[1, 512, 4608]
        time_mlp         # 形状:[1, 4608]
    )  # 输出形状:[1, 250, 4608]
    
    # 7. 流输出
    # 投影回音频潜在维度
    predicted_velocity = self.flow_proj(dit_output)  # 形状:[1, 250, 128]
    
    # 8. 损失计算
    true_velocity = clean_audio_latent - (1 - 1e-5) * noise  # 形状:[1, 250, 128]
    loss = F.mse_loss(predicted_velocity, true_velocity)
    
    return loss

def create_audio_context(self, audio_latent):
    """
    创建掩码音频上下文
    参数:
        audio_latent: 形状 [1, 250, 128]
    """
    if random.random() < 0.5:
        return torch.zeros_like(audio_latent)  # 完全掩码
    else:
        # 部分掩码(75-100% 掩码)
        mask = torch.rand(audio_latent.shape) > random.uniform(0.75, 1.0)
        return audio_latent * mask.float()

def process_dit_blocks(self, features, text_cond, time_cond):
    """
    通过 36 个 DiT 块处理
    参数:
        features: 形状 [1, 250, 4608]
        text_cond: 形状 [1, 512, 4608]
        time_cond: 形状 [1, 4608]
    """
    x = features
    
    for block in self.dit_blocks:
        # 自注意力
        x = block.self_attn(
            q=x,  # [1, 250, 4608]
            k=x,  # [1, 250, 4608]
            v=x   # [1, 250, 4608]
        )  # 输出:[1, 250, 4608]
        
        # 与文本的交叉注意力
        x = block.cross_attn(
            q=x,           # [1, 250, 4608]
            k=text_cond,   # [1, 512, 4608]
            v=text_cond    # [1, 512, 4608]
        )  # 输出:[1, 250, 4608]
        
        # 时间调制(缩放和偏移)
        scale, shift = block.time_mlp(time_cond)  # 每个:[1, 4608]
        x = x * scale.unsqueeze(1) + shift.unsqueeze(1)
        
        # MLP(根据论文扩展 4 倍)
        x = block.mlp(x)  # 输入/输出:[1, 250, 4608]
        
    return x

解释

假设我们有一个包含音频、视频和音频描述的数据样本来训练模型。我们在 Movie Video Gen 博客中学习了如何将视频表示为向量形式。那么如何将音频表示为向量形式呢?

模拟音频是空气中的连续压力波。它通过采样转换为数字音频。44KHz 的采样率意味着每秒钟的音频被采样 44,000 次。以下是一个大致概述。

模拟到数字转换:
连续波 → 采样 → 离散值
示例(44.1kHz 采样):
1 秒音频 = 44,100 次测量
每次测量 = 振幅值(-1 到 1)

音频可以是单声道或立体声。单声道音频包含一个通道,立体声包含两个通道,即左声道和右声道。

现在你应该能理解以下内容了。假设你有一个 10 秒长的 48Hz 立体声音频。以下是如何将其表示为向量形式。

# 在 48kHz 采样率下:
samples_per_second = 48000
duration_seconds = 10
total_samples = samples_per_second * duration_seconds # 480,000
# 立体声音频形状:
audio_tensor = torch.zeros(2, 480000) # 形状:[2, 480000]

现在你知道了音频是如何表示为向量形式的。让我们继续逐步理解训练过程。

步骤 1:生成噪声潜在表示

他们使用了一个单独训练的 DAC_VAE(描述音频编解码器与变分自编码器)模型(另一个深度学习模型)来扩展原始音频。为什么?为了在不失去重建能力的情况下减少内存并提高计算效率。

None

之后,使用以下公式创建噪声音频潜在表示

# DAC-VAE 将 48kHz 降低到 25Hz:10s * 25Hz = 250 个时间帧
clean_audio_latent = self.dac_vae_encoder(audio)  # 形状:[1, 250, 128]

# 从标准正态分布中采样噪声
noise = torch.randn_like(clean_audio_latent)  # 形状:[1, 250, 128]

# 在时间步长 t 处创建噪声潜在表示
noised_latent = (flow_time_step * clean_audio_latent + 
                 (1 - (1 - 1e-5) * flow_time_step) * noise)  # 形状:[1, 250, 128]

基本上,这是将噪声与实际音频潜在表示混合,以便模型在训练过程中学习重建音频。

步骤 2:音频上下文

类似于上述步骤,他们通过将原始视频传递给 DAC_VAE 编码器来创建音频潜在表示。

这个音频潜在表示(音频上下文)是特殊的。它被掩码并且没有与噪声混合。这意味着随机选择向量并用零向量替换。为什么这样做,因为我们已经在上一步中创建了噪声音频潜在表示?

我们希望我们的模型不仅能够生成音频,还能够根据掩码的位置填充或扩展音频。当你希望模型生成音频时,音频上下文只是一个零向量序列。当我们希望模型扩展音频时,视频中的部分音频就是音频上下文。这太棒了!一个模型可以处理所有事情。

最后,音频上下文与噪声音频潜在表示逐帧合并。

步骤 3:处理视频分支

使用从 MetaCLIP 微调的长提示 MetaCLIP 来提取视频中每一帧的 1024 维嵌入。无论视频的分辨率或帧率如何,它都通过 spatial_resize 操作转换为固定大小的向量。

这个 1024 维的视频嵌入与结果输入(音频上下文 + 噪声音频潜在表示)逐帧混合,而不是按时间混合。想知道为什么吗?

# 原始视频:30 FPS
video_frames = [frame1, frame2, ..., frame300]  # 10 秒 * 30 FPS = 300 帧

# 使用长提示 MetaCLIP 提取特征
frame_features

FluxAI 中文

© 2025. All Rights Reserved