第24章 Voicebox 语音档案解析:克隆与预设的架构原理
开发者 Voice Profiles Voicebox 中语音档案管理的工作原理 概览
语音档案(Voice Profile)是 Voicebox 中“已保存的声音”的基本单位。从 0.4 版本开始,它支持两种类型,背后共用同一张 profiles 表:
克隆档案(Cloned)—— 存储一个或多个参考音频样本;克隆引擎在使用时生成 voice embedding 预设档案(Preset)—— 不存储音频,只是一个指向引擎内置预置语音的引用(例如 Kokoro 的 am_adam、Qwen CustomVoice 的 Ryan)
Schema 中还保留了第三种类型 designed,用于未来基于文字描述创建的声音,目前尚无已发布的引擎使用。 架构
语音档案系统由三个主要部分组成:
数据库层:SQLite 表存储档案元数据、样本引用(克隆档案)以及引擎和语音 ID(预设档案)。 文件存储:音频样本以结构化目录格式保存在磁盘上。预设档案没有磁盘音频文件。 档案模块:backend/services/profiles.py 提供 CRUD 业务逻辑,并根据 voice_type 分发到对应的引擎。 数据模型
VoiceProfile 表
class VoiceProfile(Base): __tablename__ = "profiles"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) name = Column(String, unique=True, nullable=False) description = Column(Text) language = Column(String, default="en") avatar_path = Column(String, nullable=True) effects_chain = Column(Text, nullable=True)
# Voice type system — added v0.3.x voice_type = Column(String, default="cloned") # "cloned" | "preset" | "designed" preset_engine = Column(String, nullable=True) # e.g. "kokoro" — only for preset preset_voice_id = Column(String, nullable=True) # e.g. "am_adam" — only for preset design_prompt = Column(Text, nullable=True) # text description — only for designed (reserved) default_engine = Column(String, nullable=True) # auto-selected engine, locked for preset
created_at = Column(DateTime, default=lambda: datetime.now(UTC)) updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)) voice_type 字段用于区分三种类型:
voice_typepreset_enginepreset_voice_idprofile_samples 中的样本clonedNULLNULL必需(至少 1 行)preset引擎名称语音 ID 字符串无designedNULLNULL无(使用 design_prompt)
default_engine 字段在档案创建时自动设置。对于预设档案,它会锁定为来源引擎——生成时切换引擎会跳过该档案(用户点击置灰的卡片时 UI 会自动切回,参见浮动生成框和档案网格)。 ProfileSample 表
class ProfileSample(Base): __tablename__ = "profile_samples"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) profile_id = Column(String, ForeignKey("profiles.id")) audio_path = Column(String, nullable=False) reference_text = Column(Text, nullable=False) 仅克隆档案会使用这张表。预设和 designed 档案在表中没有任何记录。 文件结构
档案存储在 data 目录中: 核心功能
创建档案
async def create_profile(data: VoiceProfileCreate, db: Session) -> VoiceProfileResponse: # 1. Create database record db_profile = DBVoiceProfile( id=str(uuid.uuid4()), name=data.name, description=data.description, language=data.language, ) db.add(db_profile) db.commit() # 2. Create profile directory profile_dir = profiles_dir / db_profile.id profile_dir.mkdir(parents=True, exist_ok=True) return VoiceProfileResponse.model_validate(db_profile) 添加样本
添加样本时,会先校验音频,然后复制到档案目录:
async def add_profile_sample( profile_id: str, audio_path: str, reference_text: str, db: Session, ) -> ProfileSampleResponse: # 1. Validate audio (duration, format, quality) is_valid, error_msg = validate_reference_audio(audio_path) if not is_valid: raise ValueError(f"Invalid reference audio: {error_msg}") # 2. Copy to profile directory sample_id = str(uuid.uuid4()) dest_path = profile_dir / f"{sample_id}.wav" audio, sr = load_audio(audio_path) save_audio(audio, str(dest_path), sr) # 3. Create database record db_sample = DBProfileSample( id=sample_id, profile_id=profile_id, audio_path=str(dest_path), reference_text=reference_text, ) db.add(db_sample) db.commit() 创建语音 Prompt
生成语音时,样本会被组合成一个 voice prompt:
async def create_voice_prompt_for_profile( profile_id: str, db: Session, ) -> dict: samples = db.query(DBProfileSample).filter_by(profile_id=profile_id).all() if len(samples) == 1: # Single sample - use directly voice_prompt, _ = await tts_model.create_voice_prompt( sample.audio_path, sample.reference_text, ) else: # Multiple samples - combine them combined_audio, combined_text = await tts_model.combine_voice_prompts( [s.audio_path for s in samples], [s.reference_text for s in samples], ) voice_prompt, _ = await tts_model.create_voice_prompt( combined_audio_path, combined_text, ) return voice_prompt 音频校验
参考音频在录入前会经过校验:
时长:建议 3–30 秒 格式:支持 WAV、MP3、FLAC、OGG、M4A 采样率:取决于具体引擎——音频工具会实时重采样为当前引擎所需的采样率(Whisper 使用 16 kHz,大多数 TTS 引擎使用 24 kHz,LuxTTS 输出 48 kHz)。存储的样本保留原始采样率。 声道:立体声会转换为单声道 导出/导入
档案可以导出为 ZIP 压缩包以便分享:
profile.json API 端点
方法端点说明GET/profiles列出所有档案POST/profiles创建档案GET/profiles/{id}按 ID 获取档案PUT/profiles/{id}更新档案DELETE/profiles/{id}删除档案GET/profiles/{id}/samples获取档案样本POST/profiles/{id}/samples向档案添加样本PUT/profiles/samples/{id}更新样本文本DELETE/profiles/samples/{id}删除样本GET/profiles/{id}/export导出为 ZIPPOST/profiles/import从 ZIP 导入 最佳实践
样本质量
使用干净、背景噪音少的音频 确保参考文本与实际朗读内容完全一致 多个样本(3–5 个)可以提升声音克隆质量
语言匹配
将档案语言设置为与参考音频一致 支持的语言:en、zh、ja、ko、de、fr、ru、pt、es、it
命名规范
使用能识别该声音的描述性名称 避免可能引发文件系统问题的特殊字符 在 GitHub 上编辑 自动更新 Voicebox 自动更新的工作原理 TTS 生成 Voicebox 多引擎后端中文本转语音生成的工作原理