Nhân bản giọng nói

Nhân bản bất kỳ giọng nói nào từ một mẫu âm thanh tham chiếu ngắn. Cả Qwen3-TTS và CosyVoice3 đều hỗ trợ nhân bản giọng nói với các speaker encoder khác nhau — lần lượt là ECAPA-TDNN (1024 chiều) và CAM++ (192 chiều).

Cơ chế hoạt động

  1. Ghi âm hoặc cung cấp một mẫu âm thanh tham chiếu của giọng nói mục tiêu
  2. Trích xuất embedding người nói — một speaker encoder xử lý âm thanh tham chiếu thành một vector embedding có chiều cố định
  3. Tiêm embedding — embedding người nói điều kiện hóa mô hình TTS trong quá trình tổng hợp
  4. Tổng hợp giọng nói — mô hình TTS sinh ra giọng nói khớp với các đặc tính giọng của mẫu tham chiếu

Engine

Nhân bản giọng nói khả dụng với cả hai engine TTS. Mỗi engine dùng một speaker encoder khác nhau:

EngineSpeaker EncoderEmbeddingBackend
Qwen3-TTSECAPA-TDNNx-vector 1024 chiềuMLX (GPU)
CosyVoice3CAM++192 chiềuCoreML (Neural Engine)

CosyVoice3 + CAM++

CosyVoice3 sử dụng speaker encoder CAM++ (Context-Aware Masking++) từ dự án 3D-Speaker của Alibaba. Embedding 192 chiều điều kiện hóa mô hình DiT flow thông qua một lớp affine projection (192 → 80) được huấn luyện đồng thời với CosyVoice3.

Kiến trúc CAM++

Giai đoạnMô tả
FCMModule convolution đầu cuối (Conv2d + 2 ResBlock, 32 kênh)
TDNNTime Delay Neural Network (320 đến 128 kênh, kernel size 5)
D-TDNN blocks3 block kết nối dày đặc (12/24/16 lớp) với context-aware masking
Stats PoolPooling trung bình + độ lệch chuẩn (thống kê toàn cục)
DenseLinear projection xuống embedding 192 chiều

Mô hình CoreML (~14 MB, FP16) chạy trên Neural Engine. Nó được tải tự động từ aufklarer/CamPlusPlus-Speaker-CoreML ở lần dùng đầu tiên.

Nhân bản giọng nói với Qwen3-TTS

Qwen3-TTS hỗ trợ hai chế độ nhân bản giọng nói:

Chế độ ICL (khuyến nghị)

Chế độ In-Context Learning mã hóa âm thanh tham chiếu thành các token codec qua bộ encoder của Mimi speech tokenizer và đặt chúng trước bản chép lại của tham chiếu. Điều này cung cấp cho mô hình ngữ cảnh âm học đầy đủ — chất lượng cao hơn và EOS đáng tin cậy (sửa các vấn đề với văn bản ngắn và các ngôn ngữ phi tiếng Anh).

let (model, encoder) = try await Qwen3TTSModel.fromPretrainedWithEncoder()
let audio = model.synthesizeWithVoiceCloneICL(
    text: "Target text to synthesize.",
    referenceAudio: refSamples,
    referenceSampleRate: 24000,
    referenceText: "Exact transcript of reference audio.",
    language: "english",
    codecEncoder: encoder
)

Chế độ X-Vector

Dùng một encoder ECAPA-TDNN sinh ra x-vector 1024 chiều. Không cần bản chép lại nhưng chất lượng thấp hơn. Có thể không phát ra EOS trên văn bản ngắn hoặc một số ngôn ngữ nhất định.

Kiến trúc ECAPA-TDNN

Giai đoạnMô tả
TDNNTime Delay Neural Network (128 đến 512 kênh, kernel size 5)
SE-Res2Net blocks3 block với Squeeze-and-Excitation (512 kênh, dilation 2/3/4)
MFAMulti-layer Feature Aggregation (1536 kênh + ReLU)
ASPAttentive Statistics Pooling (1536 kênh, softmax theo thời gian)
FCLớp fully connected (3072 đến 1024 chiều)

Các trọng số (76 tham số) được bao gồm trong safetensors của Qwen3-TTS — không cần tải xuống riêng.

Sử dụng CLI

# Nhân bản giọng CosyVoice3 (CAM++, CoreML Neural Engine)
.build/release/speech speak "Text in the cloned voice" \
    --engine cosyvoice --voice-sample reference.wav -o output.wav

# Nhân bản giọng Qwen3-TTS (ECAPA-TDNN, MLX GPU)
.build/release/speech speak "Text in the cloned voice" \
    --voice-sample reference.wav -o output.wav

Ví dụ

# CosyVoice3: nhân bản giọng đa ngôn ngữ (9 ngôn ngữ)
.build/release/speech speak "Hello, this is my cloned voice." \
    --engine cosyvoice --voice-sample my_voice.wav -o cloned_hello.wav

# CosyVoice3: nhân bản giọng ở một ngôn ngữ khác
.build/release/speech speak "Guten Tag, das ist meine geklonte Stimme." \
    --engine cosyvoice --voice-sample my_voice.wav --language german -o german.wav

# Qwen3-TTS: nhân bản giọng tiếng Anh
.build/release/speech speak "The quick brown fox jumps over the lazy dog." \
    --voice-sample recording_15s.wav -o cloned_fox.wav

Hội thoại nhiều người nói

CosyVoice3 hỗ trợ hội thoại nhiều người nói với nhân bản giọng nói theo từng người. Dùng cờ --speakers để ánh xạ các thẻ người nói tới các tệp âm thanh tham chiếu:

# Hội thoại hai người với nhân bản giọng
.build/release/speech speak "[S1] Hello there! [S2] Hey, how are you?" \
    --engine cosyvoice --speakers s1=alice.wav,s2=bob.wav -o dialogue.wav

# Hội thoại với thẻ cảm xúc + nhân bản giọng
.build/release/speech speak "[S1] (happy) Great news! [S2] (surprised) Really? Tell me more." \
    --engine cosyvoice --speakers s1=alice.wav,s2=bob.wav -o emotional_dialogue.wav

# Điều chỉnh khoảng lặng giữa các lượt nói
.build/release/speech speak "[S1] First line. [S2] Second line." \
    --engine cosyvoice --speakers s1=a.wav,s2=b.wav --turn-gap 0.5 -o gapped.wav

Âm thanh tham chiếu của mỗi người nói được xử lý qua encoder CAM++ để trích xuất một embedding 192 chiều. Mô hình được load một lần và tái sử dụng cho tất cả người nói. Xem hướng dẫn CosyVoice3 để biết chi tiết đầy đủ về cú pháp hội thoại và thẻ cảm xúc.

Mẹo về âm thanh tham chiếu

Quan trọng

Với Qwen3-TTS, nhân bản giọng nói chỉ hoạt động với mô hình base — không phải customVoice. Nhân bản giọng nói CosyVoice3 hoạt động với mô hình mặc định.

API Swift

import CosyVoiceTTS

// Nhân bản giọng CosyVoice3
let model = try await CosyVoiceTTSModel.fromPretrained()
let speaker = try await CamPlusPlusSpeaker.fromPretrained()

// Trích xuất embedding người nói 192 chiều từ âm thanh tham chiếu
let embedding = try speaker.embed(audio: refSamples, sampleRate: 16000)

// Tổng hợp với giọng đã nhân bản
let audio = model.synthesize(
    text: "Hello in a cloned voice!",
    speakerEmbedding: embedding
)

// Với chỉ dẫn tùy chỉnh + embedding người nói
let styledAudio = model.synthesize(
    text: "Hello!",
    instruction: "Speak happily and with excitement.",
    speakerEmbedding: embedding
)

// Hội thoại nhiều người nói
let segments = DialogueParser.parse("[S1] (happy) Hi! [S2] Hey there.")
let embeddings = ["S1": aliceEmbedding, "S2": bobEmbedding]
let dialogueAudio = DialogueSynthesizer.synthesize(
    segments: segments,
    speakerEmbeddings: embeddings,
    model: model,
    language: "english"
)
import Qwen3TTS

// Nhân bản giọng Qwen3-TTS
let model = try await Qwen3TTSModel.fromPretrained()
let audio = model.synthesizeWithVoiceClone(
    text: "Hello in a cloned voice!",
    referenceAudio: refSamples,
    referenceSampleRate: 24000
)

Cache âm thanh tham chiếu

Cả synthesizeWithVoiceClone (x-vector) và synthesizeWithVoiceCloneICL (ICL) đều cache phần tiền xử lý theo tham chiếu giữa các lời gọi trên cùng một instance mô hình. Đường x-vector cache embedding người nói ECAPA-TDNN; đường ICL còn cache thêm đầu ra của bộ encoder codec Mimi. Cache được định địa chỉ theo nội dung (hash của các mẫu thô + sample rate) và bị giới hạn bởi một LRU nhỏ (mặc định 4 mục), vì vậy các lần tạo lặp lại với cùng dạng sóng tham chiếu sẽ bỏ qua các bước mel + encoder mà không tăng bộ nhớ vô hạn.

let tts = try await Qwen3TTSModel.fromPretrained()

// Lần gọi đầu tiên: chạy ECAPA-TDNN, cache embedding
_ = tts.synthesizeWithVoiceClone(text: "Hello", referenceAudio: ref, ...)

// Các lần gọi sau với cùng tham chiếu: cache hit
_ = tts.synthesizeWithVoiceClone(text: "How are you?", referenceAudio: ref, ...)

// Giải phóng tường minh (hiếm khi cần — LRU xử lý dung lượng)
tts.clearReferenceAudioCache()