InfiniteTalk 源码解析 #5:Wav2Vec2 音频编码:如何把语音变成逐帧 audio embedding
2026/7/1 20:58:07 网站建设 项目流程

上一篇我们分析了 InfiniteTalk 的音频预处理流程。

在进入模型之前,音频会先经历几步处理:

视频抽音频 ↓ librosa 读取 ↓ 统一到 16k 采样率 ↓ 响度归一化 ↓ 单人或双人音频整理

这些步骤的目标是把各种来源的音频统一成稳定的 speech array。

但 speech array 还不能直接驱动视频生成模型。

它只是一个一维波形数组,本质上是连续的振幅数值。视频生成模型无法直接从这些原始采样点里理解“当前音节是什么”“嘴巴应该张多大”“人物什么时候该停顿”。

所以 InfiniteTalk 还需要一个关键步骤:

把原始语音波形转换成模型可以使用的音频特征。

在源码中,这一步主要由 Wav2Vec2 完成。

本文就重点分析:

  • InfiniteTalk 为什么使用 Wav2Vec2;

  • custom_init()做了什么;

  • get_embedding()如何把音频变成 embedding;

  • 为什么要计算video_length = audio_duration * 25

  • hidden_states[1:]代表什么;

  • rearrange(audio_emb, "b s d -> s b d")为什么重要;

  • .pt文件在整个推理链路里扮演什么角色;

  • 单人和双人音频 embedding 有什么差异。


一、为什么音频不能直接喂给视频模型?

首先要明确一个问题:

音频文件里的波形数据,并不是视频生成模型想要的输入形式。

比如一段 16k 采样率的音频,1 秒钟就有 16000 个采样点。

如果音频是 10 秒,就有 160000 个采样点。

这些采样点表示的是空气振动的幅度变化,但它们并不直接等于语义、发音、节奏和口型。

对于 InfiniteTalk 来说,真正有用的信息包括:

当前是否有人在说话 发音开始和结束在哪里 音节节奏如何变化 元音和辅音的变化趋势 停顿位置在哪里 语气强弱如何变化 多人音频中谁在说话

这些信息隐藏在原始音频波形里,需要通过语音模型提取出来。

Wav2Vec2 的作用,就是把原始音频转换成更高层的语音表征。

简单理解:

原始音频波形:一串采样点 Wav2Vec2 输出:一串语音特征向量

这些语音特征向量后面会作为条件输入,参与视频生成过程。

所以,Wav2Vec2 在 InfiniteTalk 里相当于一个“语音理解前端”。


二、InfiniteTalk 中音频编码的整体链路

先把音频编码流程放在整体链路里看:

audio_prepare_single / audio_prepare_multi ↓ 得到 16k speech_array ↓ custom_init 加载 Wav2Vec2FeatureExtractor 和 Wav2Vec2Model ↓ get_embedding 提取 audio embedding ↓ torch.save 保存为 1.pt / 2.pt ↓ 写入 input_clip['cond_audio'] ↓ 传给 InfiniteTalkPipeline ↓ 作为音频条件控制视频生成

在源码里,和 Wav2Vec2 直接相关的主要是两个函数:

def custom_init(device, wav2vec): ... def get_embedding(speech_array, wav2vec_feature_extractor, audio_encoder, sr=16000, device='cpu'): ...

其中:

custom_init()负责加载模型。

get_embedding()负责真正提取音频特征。

这两个函数虽然代码不长,但它们连接了音频世界和视频生成世界,是 InfiniteTalk 的关键桥梁。


三、custom_init:初始化 Wav2Vec2

先看custom_init()的核心逻辑:

def custom_init(device, wav2vec): audio_encoder = Wav2Vec2Model.from_pretrained( wav2vec, local_files_only=True ).to(device) audio_encoder.feature_extractor._freeze_parameters() wav2vec_feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained( wav2vec, local_files_only=True ) return wav2vec_feature_extractor, audio_encoder

这个函数做了三件事:

1. 从本地目录加载 Wav2Vec2Model 2. 冻结 Wav2Vec2 的 feature_extractor 参数 3. 从本地目录加载 Wav2Vec2FeatureExtractor

最后返回两个对象:

wav2vec_feature_extractor audio_encoder

这两个对象名字很像,但职责不一样。


四、Wav2Vec2FeatureExtractor 负责什么?

Wav2Vec2FeatureExtractor可以理解成输入适配器。

它负责把原始 speech array 整理成 Wav2Vec2 模型需要的输入格式。

get_embedding()中,对它的调用是:

audio_feature = np.squeeze( wav2vec_feature_extractor( speech_array, sampling_rate=sr ).input_values )

这里输入的是:

speech_array:librosa 读取并预处理后的音频波形 sampling_rate:采样率,默认 16000

输出的是:

input_values

也就是 Wav2Vec2 可以接受的输入张量数据。

为什么不能直接把 speech array 转成 torch tensor 就喂给模型?

因为特征提取器通常还会负责:

检查采样率 整理 batch 维度 做输入归一化或格式适配 生成模型期望的 input_values

所以,Wav2Vec2FeatureExtractor更像是“把原始音频整理成模型输入”的一层封装。


五、Wav2Vec2Model 负责什么?

audio_encoder是真正的 Wav2Vec2 模型。

在源码中,它来自:

from src.audio_analysis.wav2vec2 import Wav2Vec2Model

然后通过:

Wav2Vec2Model.from_pretrained(wav2vec, local_files_only=True)

从本地目录加载。

注意这里的local_files_only=True

这意味着模型不会运行时联网下载,而是要求你提前准备好 Wav2Vec2 权重目录。

这也是为什么命令行参数里有:

--wav2vec_dir

如果这个路径配置不对,音频编码阶段就会失败。

从工程角度看,audio_encoder的作用是:

输入:音频 input_values 输出:多层 hidden states

这些 hidden states 后面会被整理成 audio embedding。


六、为什么要 freeze feature_extractor?

custom_init()里有一行:

audio_encoder.feature_extractor._freeze_parameters()

这表示冻结 Wav2Vec2 的 feature extractor 参数。

在推理阶段,模型只是做特征提取,不需要训练。

冻结参数有几个意义:

避免误更新参数 减少训练相关开销 明确当前流程只是 inference 保证音频编码器行为稳定

虽然在纯推理场景下本来也不会反向传播,但显式冻结 feature extractor 可以表达一个设计意图:

Wav2Vec2 在这里不是被训练的模块,而是作为固定音频特征提取器使用。

InfiniteTalk 的视频生成模型会使用它提取出的语音特征,但不会在这个脚本里更新 Wav2Vec2。


七、get_embedding:音频编码的核心函数

接下来进入最关键的函数:

def get_embedding( speech_array, wav2vec_feature_extractor, audio_encoder, sr=16000, device='cpu' ): ...

这个函数输入的是:

speech_array:预处理后的音频数组 wav2vec_feature_extractor:Wav2Vec2 输入特征处理器 audio_encoder:Wav2Vec2 模型 sr:采样率,默认 16000 device:运行设备,默认 cpu

输出的是:

audio_emb

也就是后续视频生成模型要使用的音频条件。

源码逻辑可以拆成六步:

1. 计算音频时长 2. 根据 25fps 估算视频长度 3. 用 feature_extractor 处理音频 4. 转成 torch tensor 并增加 batch 维度 5. 调用 Wav2Vec2Model 得到 hidden states 6. 整理 hidden states 维度,得到 audio embedding

下面逐步看。


八、第一步:计算 audio_duration

源码中先计算:

audio_duration = len(speech_array) / sr

这里的逻辑很简单。

speech_array是音频采样点数组。

sr是采样率,默认 16000。

所以:

音频时长 = 采样点数量 / 每秒采样点数量

比如一段音频有 160000 个采样点,采样率是 16000,那么:

160000 / 16000 = 10 秒

这个时长后面会用于估算视频帧长度。


九、第二步:把音频时长映射到视频帧数

接着源码计算:

video_length = audio_duration * 25

注释里写得很明确:

# Assume the video fps is 25

也就是说,InfiniteTalk 在这里默认按 25fps 来对齐音频和视频。

如果音频是 4 秒,那么对应视频长度大约是:

4 × 25 = 100 帧

如果音频是 10 秒,对应视频长度大约是:

10 × 25 = 250 帧

这个video_length后面会传给 Wav2Vec2Model:

embeddings = audio_encoder( audio_feature, seq_len=int(video_length), output_hidden_states=True )

这里非常关键。

它说明 InfiniteTalk 提取音频 embedding 时,不只是想得到一段音频的全局特征,而是希望得到和视频帧长度匹配的时间序列特征。

换句话说,audio embedding 需要服务于视频帧生成。

视频生成模型后续需要知道:

第 1 帧附近的语音状态 第 20 帧附近的语音状态 第 50 帧附近的语音状态 第 100 帧附近的语音状态

所以这里要把音频时长和视频帧数关联起来。

这就是标题里“逐帧 audio embedding”的来源。

严格说,它不是每个原始音频采样点对应一帧,而是通过 Wav2Vec2 和seq_len让音频特征序列对齐到视频生成所需的时间尺度。


十、第三步:FeatureExtractor 处理 speech_array

接着代码调用:

audio_feature = np.squeeze( wav2vec_feature_extractor( speech_array, sampling_rate=sr ).input_values )

这里做了两件事。

第一,把speech_array交给wav2vec_feature_extractor

第二,用np.squeeze()去掉多余维度。

处理后的audio_feature仍然是 numpy 数据,还不能直接进入 PyTorch 模型。

所以后面会继续转 tensor。


十一、第四步:转成 torch tensor 并增加 batch 维度

源码接着写:

audio_feature = torch.from_numpy(audio_feature).float().to(device=device) audio_feature = audio_feature.unsqueeze(0)

第一行把 numpy array 转成 float tensor,并移动到指定设备。

第二行增加 batch 维度。

如果原始audio_feature形状类似:

[T]

unsqueeze(0)后就变成:

[1, T]

这里的1表示 batch size。

模型通常要求输入有 batch 维度,即使一次只处理一段音频,也要把它包装成 batch。

所以这一步是非常常见的模型输入格式整理。


十二、第五步:调用 audio_encoder 得到 hidden states

真正的音频编码发生在这里:

with torch.no_grad(): embeddings = audio_encoder( audio_feature, seq_len=int(video_length), output_hidden_states=True )

这里有三个关键信息。

第一,使用了torch.no_grad()

这说明当前是推理过程,不需要计算梯度。

这样可以减少显存或内存开销,也能提高推理效率。

第二,传入了seq_len=int(video_length)

这说明音频编码器输出的特征序列会受到视频长度约束。

第三,设置了:

output_hidden_states=True

这意味着模型不只是返回最后一层输出,还会返回中间多层 hidden states。

后面源码正是使用这些 hidden states 来构造 audio embedding。


十三、为什么要使用 hidden_states?

在 Wav2Vec2 这类深度语音模型里,不同层的 hidden states 可能包含不同层次的信息。

较浅层可能更接近声学特征,比如音高、能量、局部频谱变化。

较深层可能更接近语音内容、发音结构和上下文信息。

对于音频驱动视频生成来说,只用最后一层未必最合适。

因为嘴型和表情不只依赖“语义内容”,也依赖声音的节奏、发音边界、音素变化和局部声学细节。

所以源码使用:

embeddings.hidden_states[1:]

也就是去掉第 0 层后,保留后续多层 hidden states。

这样相当于把不同层级的语音特征都交给后续模型使用。

可以理解为:

浅层特征:更偏声学和局部变化 中层特征:更偏发音和音素结构 深层特征:更偏上下文和语音表示

把这些层堆叠起来,可以为视频生成模型提供更丰富的音频条件。


十四、为什么跳过 hidden_states[0]?

源码中使用的是:

embeddings.hidden_states[1:]

而不是:

embeddings.hidden_states

这意味着第 0 层被跳过了。

通常可以理解为,第 0 层更接近模型最初的输入表示,还没有经过足够深的语音上下文建模。

后续层经过 Transformer 或类似结构处理后,特征表达能力更强。

所以跳过第 0 层,是为了保留更有语音语义和时序表达能力的 hidden states。

从二次开发角度看,这也是一个可以实验的点:

只用最后一层会怎样? 使用全部 hidden states 会怎样? 只用中间几层会怎样? 不同层对嘴型同步和动作自然度有什么影响?

不过在原始源码中,它选择了hidden_states[1:]

我们先按照源码理解即可。


十五、第六步:stack 多层 hidden states

接下来是:

audio_emb = torch.stack(embeddings.hidden_states[1:], dim=1).squeeze(0)

这行代码很重要。

假设hidden_states[1:]里有多层特征,每一层形状大致类似:

[batch, seq, dim]

torch.stack(..., dim=1)会把这些层堆叠到新的维度上。

堆叠后大致变成:

[batch, layers, seq, dim]

然后.squeeze(0)去掉 batch 维度,得到:

[layers, seq, dim]

也就是说,audio_emb此时包含三个核心维度:

layers:来自 Wav2Vec2 的不同层 seq:时间序列长度 dim:每个时间点的特征维度

这一步的意义是:

把 Wav2Vec2 多层 hidden states 合并成一个多层音频条件张量。

它不是单层特征,也不是一个全局向量,而是一个包含层级信息和时间信息的音频表示。


十六、第七步:rearrange 调整维度

紧接着源码执行:

audio_emb = rearrange(audio_emb, "b s d -> s b d")

这里变量名虽然写的是b s d,但结合前面的 stack,实际可以理解为:

原始维度:层数 × 时间 × 特征维度 调整后:时间 × 层数 × 特征维度

也就是说,它把时间维度放到了第一位。

调整前:

[layers, seq, dim]

调整后:

[seq, layers, dim]

为什么要这么做?

因为后续视频生成模型更关心“每个时间点对应的音频条件”。

视频是按时间帧生成的,音频条件也应该以时间为主轴。

seq放在第一维,可以更方便地按时间步对齐视频帧。

简单理解:

audio_emb[0]:第 0 个时间位置的多层语音特征 audio_emb[1]:第 1 个时间位置的多层语音特征 audio_emb[2]:第 2 个时间位置的多层语音特征 ...

这样后续模型在处理视频时间维度时,就可以更自然地取到对应音频特征。


十七、第八步:detach 并放回 CPU

最后源码执行:

audio_emb = audio_emb.cpu().detach() return audio_emb

这里做了两件事。

第一,.detach()让张量从计算图中分离出来。

因为推理阶段不需要梯度。

第二,.cpu()把张量放回 CPU。

这也符合前面整体工程策略:尽量减少 GPU 显存占用。

InfiniteTalk 后续会把 audio embedding 保存为.pt文件:

torch.save(audio_embedding, emb_path)

既然要保存到磁盘,就没有必要继续放在 GPU 上。

所以get_embedding()最终返回的是一个 CPU tensor。


十八、audio embedding 的形状可以怎么理解?

虽然具体维度取决于 Wav2Vec2 配置和源码实现,但从逻辑上可以把audio_emb理解成:

[时间位置, Wav2Vec2层级, 特征维度]

它表达的是:

在某一个时间点,语音模型不同层抽取到的声音特征是什么。

这和普通语音分类任务不一样。

语音分类可能只需要一个全局向量,表示这段音频整体属于哪个类别。

但 InfiniteTalk 需要的是时间连续的特征,因为视频中人物每一帧的嘴型和动作都要跟着语音变化。

所以这里的 audio embedding 必须保留时间维度。

如果时间维度丢了,就无法做精细的嘴型同步。


十九、单人场景:生成 1.pt

在单人音频场景中,源码逻辑大致是:

human_speech = audio_prepare_single(items[1]) audio_embedding = get_embedding( human_speech, wav2vec_feature_extractor, audio_encoder ) emb_path = os.path.join(args.audio_save_dir, '1.pt') torch.save(audio_embedding, emb_path) cond_audio['person1'] = emb_path

这里的流程非常清楚:

单人原始音频 ↓ audio_prepare_single ↓ get_embedding ↓ 保存为 1.pt ↓ cond_audio['person1'] 指向 1.pt

后续传给 pipeline 的不是 wav,而是:

1.pt

也就是说,1.pt才是模型真正使用的音频条件文件。

原始 wav 只是音频来源。


二十、双人场景:生成 1.pt 和 2.pt

在双人场景中,源码逻辑是:

new_human_speech1, new_human_speech2, sum_human_speechs = audio_prepare_multi(...) audio_embedding_1 = get_embedding( new_human_speech1, wav2vec_feature_extractor, audio_encoder ) audio_embedding_2 = get_embedding( new_human_speech2, wav2vec_feature_extractor, audio_encoder ) torch.save(audio_embedding_1, emb1_path) torch.save(audio_embedding_2, emb2_path) cond_audio['person1'] = emb1_path cond_audio['person2'] = emb2_path

这说明双人场景不是把两个人声音混成一个 embedding。

它会分别生成:

person1 → 1.pt person2 → 2.pt

这样模型后续才能区分:

哪个音频条件对应第一个人物 哪个音频条件对应第二个人物 当前时间段是谁在说话 另一个人是否应该保持静音

这对于多人对话非常关键。

如果只生成一个混合 embedding,模型就很难知道应该让谁动嘴。


二十一、audio embedding 和最终音轨的区别

这里必须再强调一次:

cond_audiovideo_audio是两条不同路线。

模型条件路线:

speech_array ↓ Wav2Vec2 ↓ audio_embedding ↓ 1.pt / 2.pt ↓ cond_audio ↓ 驱动视频生成

最终音轨路线:

speech_array ↓ sum.wav / sum_all.wav ↓ video_audio ↓ FFmpeg ↓ 合成到最终 MP4

所以:

1.pt / 2.pt:给模型“看”的声音特征 sum.wav / sum_all.wav:给观众“听”的最终声音

如果生成视频嘴型对了,但最终视频没有声音,问题可能在video_audio或 FFmpeg 合成。

如果最终视频有声音,但嘴型不对,问题可能在cond_audio或 Wav2Vec2 embedding。

排查问题时要分清这两条链路。


二十二、为什么 audio embedding 要保存成 .pt 文件?

源码没有直接把audio_embeddingtensor 放进input_clip,而是保存为.pt文件,再把路径放进去:

torch.save(audio_embedding, emb_path) cond_audio['person1'] = emb_path

这种设计有几个好处。

1. 降低内存占用

视频生成模型本身非常吃显存和内存。

把 audio embedding 保存到磁盘,可以让 pipeline 在需要时再读取,而不是在入口脚本里长期持有大量中间 tensor。

2. 方便缓存

如果同一段音频要反复生成不同视频,可以复用.pt文件,避免重复跑 Wav2Vec2。

比如:

同一段口播音频 + 不同人物形象 同一段角色台词 + 不同视频背景 同一段课程讲解 + 不同画面模板

这些场景都可以复用 audio embedding。

3. 方便调试

如果生成效果异常,可以检查:

音频 wav 是否正常 1.pt / 2.pt 是否生成 pt 文件维度是否符合预期 双人场景是否两个 embedding 都存在

如果.pt文件没生成,说明问题在音频编码之前。

如果.pt文件生成正常,但视频不对,就继续查 pipeline 和 attention。

4. 方便分布式或任务队列

在服务化架构里,音频编码和视频生成可以拆成两个阶段。

例如:

音频 worker:负责生成 .pt 视频 worker:读取 .pt 生成视频

这样可以更灵活地调度资源。


二十三、v_length:音频 embedding 长度如何影响生成?

源码中还有一行:

v_length = audio_embedding.shape[0]

或者双人时:

v_length = audio_embedding_1.shape[0]

虽然在当前片段里这个变量没有展开使用太多,但它表达了一个关键信息:

audio_embedding.shape[0]是时间维度长度。

也就是说,audio embedding 的第一维代表音频条件的时间长度。

这和前面的rearrange(audio_emb, "b s d -> s b d")对应。

如果 audio embedding 的时间长度和视频帧长度不匹配,就可能出现音画不同步。

所以在调试时,可以重点看:

音频时长是多少? video_length 计算出来是多少? audio_embedding.shape[0] 是多少? 生成视频帧数是多少? 最终音频长度是多少?

这些信息能帮助判断同步问题是不是来自音频编码阶段。


二十四、Wav2Vec2 embedding 为什么能控制嘴型?

严格来说,Wav2Vec2 本身并不会直接输出“嘴巴张开多少”。

它输出的是语音特征。

真正把语音特征转成嘴型、表情、头部动作的是后面的 InfiniteTalk 视频生成模型。

可以这样理解:

Wav2Vec2:把声音变成语音特征 InfiniteTalk:根据语音特征生成视频动作

Wav2Vec2 提供的信息包括发音节奏、声音变化、语音结构等。

视频模型在训练或设计中学习到:

某些语音特征对应嘴巴张开 某些语音特征对应闭嘴 某些节奏对应头部微动 某些停顿对应动作减弱 某些说话强度对应表情变化

所以 Wav2Vec2 不是直接控制嘴巴,而是提供音频条件。

后面的 audio cross attention 或相关条件注入机制,才是真正把语音条件作用到视频生成里的关键。

这也是后续第 8 篇会重点分析的内容。


二十五、为什么不是直接做语音识别文本?

有人可能会问:

既然要理解语音,为什么不先把语音转成文本,再让模型根据文本生成嘴型?

原因是,文本丢失了太多声音细节。

比如同一句话:

“我知道了。”

可以有很多种说法:

平静地说 激动地说 犹豫地说 低声说 快速说 拖长音说 带停顿地说

如果只看文本,这些差异都没了。

但音频里包含:

语速 停顿 重音 音高变化 情绪强度 发音持续时间 音节边界

这些信息对人物视频生成非常重要。

所以 InfiniteTalk 不是把语音转文本后再控制视频,而是直接用语音表征作为条件。

这能保留更多和嘴型、动作、节奏相关的信息。


二十六、为什么不只用音量曲线?

也有人会想到一种简单方法:

用音频音量大小控制嘴巴开合。

音量大,嘴巴张大;音量小,嘴巴闭上。

这种方法可以做非常粗糙的说话动画,但远远不够。

因为嘴型不只由音量决定。

比如:

“a” “o” “m” “f” “shi”

这些发音对应的嘴型完全不同。

有些音量不大,但嘴型变化明显。

有些音量很大,但嘴型不一定张得特别大。

所以音量曲线只能提供说话强弱,不能提供足够的发音结构。

Wav2Vec2 这类模型能提取更丰富的语音特征,比简单音量曲线更适合驱动视频生成。


二十七、从二次开发角度看,可以怎么优化?

如果你想基于 InfiniteTalk 做自己的产品,Wav2Vec2 音频编码这里有几个可优化方向。


1. 缓存 embedding

同一段音频只需要编码一次。

可以根据音频文件 hash、采样率、预处理参数生成缓存 key。

例如:

cache_key = sha256(audio_file + sr + normalize_params)

如果缓存命中,就直接读取.pt,不用重新跑 Wav2Vec2。


2. 增加 embedding 维度检查

生成.pt后可以检查:

是否为 torch.Tensor 是否存在 NaN shape 是否为空 时间维度是否大于 0 双人 embedding 时间长度是否一致

这些检查能提前发现音频异常。


3. 把音频编码拆成独立服务

视频生成非常耗 GPU,而 Wav2Vec2 音频编码相对轻一些。

可以把音频编码拆成单独 worker:

任务提交 ↓ 音频预处理 worker ↓ Wav2Vec2 embedding worker ↓ 视频生成 worker

这样可以提高系统吞吐。


4. 尝试替换音频编码器

Wav2Vec2 是一种选择,但不是唯一选择。

如果要做更强的多语言、情绪、唱歌、方言或跨语言配音,可以尝试其他语音表征模型。

不过替换不是简单改一行代码。

因为后续视频模型可能已经适配了当前 audio embedding 的形状和语义分布。

如果换编码器,需要同时适配:

embedding 维度 时间长度 层数结构 归一化方式 后续模型输入接口 训练或微调策略

所以普通二次开发不建议一开始就换音频编码器。


5. 记录音频与 embedding 元数据

每次生成.pt时,可以额外保存一个 JSON:

{ "audio_path": "xxx.wav", "sample_rate": 16000, "duration": 12.4, "fps": 25, "video_length": 310, "embedding_shape": [310, 12, 768] }

这样排查问题会方便很多。

尤其是长视频任务,音频长度、视频帧数和 embedding 时间维度非常容易出错。


二十八、常见问题排查

如果你在 InfiniteTalk 中遇到音频编码相关问题,可以按下面思路排查。


1. wav2vec_dir 是否正确?

custom_init()使用local_files_only=True,所以本地必须已经有 Wav2Vec2 权重。

如果路径错误,模型加载会失败。


2. speech_array 是否为空?

如果音频读取失败,speech_array可能为空或长度异常。

这会直接导致 embedding 提取失败。


3. 采样率是否是 16k?

虽然前面音频预处理已经统一为 16k,但如果你修改过流程,必须确认传入get_embedding()的音频和sr一致。


4. hidden_states 是否为空?

源码里有判断:

if len(embeddings) == 0: print("Fail to extract audio embedding") return None

如果出现这个提示,说明 Wav2Vec2 没有正常返回 embedding。


5. 1.pt / 2.pt 是否生成?

如果.pt文件没有生成,说明问题出在音频编码阶段。

如果.pt文件生成了,但视频不对,再继续查 pipeline。


6. 双人模式两个 embedding 是否对齐?

双人对话中,audio_prepare_multi()会通过静音补齐来对齐两路音频。

如果你自己改了双人音频逻辑,要确认1.pt2.pt时间维度是否一致。

否则可能出现角色说话错位。


7. 最终音频和 embedding 是否来自同一段语音?

如果cond_audio用的是 A 音频生成的 embedding,但video_audio合成的是 B 音频,最终视频就会出现嘴型和声音不一致。

所以生成任务里一定要保证:

用于 embedding 的音频 和 最终合成的音频

来自同一个处理流程。


二十九、这一篇的核心结论

InfiniteTalk 中的 Wav2Vec2 音频编码,可以概括成一句话:

把 16k speech array 转换成按视频时间轴组织的多层 audio embedding。

更具体地说:

custom_init()负责加载 Wav2Vec2FeatureExtractor 和 Wav2Vec2Model,并冻结 feature extractor。

get_embedding()负责把 speech array 转成 Wav2Vec2 输入,计算音频时长,并按 25fps 估算对应视频长度。

Wav2Vec2 输出 hidden states 后,源码使用hidden_states[1:]保留多层语音特征。

然后通过torch.stack()把多层特征堆叠起来,再通过rearrange()把时间维度调整到第一维。

最终得到的 audio embedding 可以理解为:

[时间位置, Wav2Vec2层级, 特征维度]

它会被保存成:

1.pt 2.pt

然后写入:

input_clip['cond_audio']

作为 InfiniteTalkPipeline 生成视频时的音频条件。

到这里,音频已经完成了从“声音文件”到“模型条件”的转换。

下一篇我们会继续进入 Pipeline 初始化部分,分析:

InfiniteTalk 源码解析 #6:InfiniteTalkPipeline 初始化:T5、CLIP、VAE、WanModel 如何串起来

从下一篇开始,我们会看到音频 embedding、文本 prompt、参考图片或视频,最终是如何在视频生成管线里汇合的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询