InfiniteTalk 源码解析 #5:Wav2Vec2 音频编码:如何把语音变成逐帧 audio embedding

📅 2026/6/27 22:17:03
InfiniteTalk 源码解析 #5:Wav2Vec2 音频编码:如何把语音变成逐帧 audio embedding
上一篇我们分析了 InfiniteTalk 的音频预处理流程。在进入模型之前音频会先经历几步处理视频抽音频 ↓ librosa 读取 ↓ 统一到 16k 采样率 ↓ 响度归一化 ↓ 单人或双人音频整理这些步骤的目标是把各种来源的音频统一成稳定的 speech array。但 speech array 还不能直接驱动视频生成模型。它只是一个一维波形数组本质上是连续的振幅数值。视频生成模型无法直接从这些原始采样点里理解“当前音节是什么”“嘴巴应该张多大”“人物什么时候该停顿”。所以 InfiniteTalk 还需要一个关键步骤把原始语音波形转换成模型可以使用的音频特征。在源码中这一步主要由 Wav2Vec2 完成。本文就重点分析InfiniteTalk 为什么使用 Wav2Vec2custom_init()做了什么get_embedding()如何把音频变成 embedding为什么要计算video_length audio_duration * 25hidden_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, sr16000, devicecpu): ...其中custom_init()负责加载模型。get_embedding()负责真正提取音频特征。这两个函数虽然代码不长但它们连接了音频世界和视频生成世界是 InfiniteTalk 的关键桥梁。三、custom_init初始化 Wav2Vec2先看custom_init()的核心逻辑def custom_init(device, wav2vec): audio_encoder Wav2Vec2Model.from_pretrained( wav2vec, local_files_onlyTrue ).to(device) audio_encoder.feature_extractor._freeze_parameters() wav2vec_feature_extractor Wav2Vec2FeatureExtractor.from_pretrained( wav2vec, local_files_onlyTrue ) 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_ratesr ).input_values )这里输入的是speech_arraylibrosa 读取并预处理后的音频波形 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_onlyTrue)从本地目录加载。注意这里的local_files_onlyTrue。这意味着模型不会运行时联网下载而是要求你提前准备好 Wav2Vec2 权重目录。这也是为什么命令行参数里有--wav2vec_dir如果这个路径配置不对音频编码阶段就会失败。从工程角度看audio_encoder的作用是输入音频 input_values 输出多层 hidden states这些 hidden states 后面会被整理成 audio embedding。六、为什么要 freeze feature_extractorcustom_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, sr16000, devicecpu ): ...这个函数输入的是speech_array预处理后的音频数组 wav2vec_feature_extractorWav2Vec2 输入特征处理器 audio_encoderWav2Vec2 模型 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后面会传给 Wav2Vec2Modelembeddings audio_encoder( audio_feature, seq_lenint(video_length), output_hidden_statesTrue )这里非常关键。它说明 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_ratesr ).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(devicedevice) 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_lenint(video_length), output_hidden_statesTrue )这里有三个关键信息。第一使用了torch.no_grad()。这说明当前是推理过程不需要计算梯度。这样可以减少显存或内存开销也能提高推理效率。第二传入了seq_lenint(video_length)。这说明音频编码器输出的特征序列会受到视频长度约束。第三设置了output_hidden_statesTrue这意味着模型不只是返回最后一层输出还会返回中间多层 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:], dim1).squeeze(0)这行代码很重要。假设hidden_states[1:]里有多层特征每一层形状大致类似[batch, seq, dim]torch.stack(..., dim1)会把这些层堆叠到新的维度上。堆叠后大致变成[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_audio和video_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_onlyTrue所以本地必须已经有 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.pt和2.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 源码解析 #6InfiniteTalkPipeline 初始化T5、CLIP、VAE、WanModel 如何串起来从下一篇开始我们会看到音频 embedding、文本 prompt、参考图片或视频最终是如何在视频生成管线里汇合的。