AI Workflow · part 21
[Dev Workflow] 你的聲音,24 秒就能複製:MacBook 上的 Qwen3-TTS 教學
❯ cat --toc
TL;DR
預設 TTS 的聲音好歸好,就是不像你。要拿到自己的聲音只有一條路:錄 24 秒乾淨錄音,用 Qwen3-TTS-12Hz-1.7B-Base 的 8bit 版做 zero-shot clone,不用訓練也不用 GPU 機。MacBook 本機合成 83 秒旁白花 51 秒,比即時還快。逐段轉寫比對,字元錯誤率 7.6%,我自己聽過,過關。要注意的是它沒有固定 seed,同一句每次語調都不一樣,產線要用逐句重跑而不是拼寫字典。

前言
電話那頭是不是本人,你通常在對方講完第一句話之前就知道了。
我要幫自己的文章配旁白,第一個念頭當然是找現成的 TTS。中文語音的品質其實都不錯,問題不在品質——它們聽起來都不像我。旁白是要掛在我自己寫的東西上面的,那個聲音卻是別人的。
試了幾種現成的做法,都卡在同一個地方。macOS 內建的 say -v Meijia 慢,一秒只有 4.7 個字;預設語音庫我逐個聽過,結論是「還是 AI 感很重」。這條路沒有調得好的空間,因為缺的東西不在參數裡。
剩下唯一一條路是 clone 我自己的聲音。錄一段,讓模型照著這段的音色去唸別的字。
好消息是這件事比想像中簡單:錄 24 秒、十行 Python、MacBook 本機跑,不用訓練也不用 GPU 機。
zero-shot clone 是什麼:你只要準備兩樣東西
先講清楚 zero-shot voice clone 是什麼,免得你以為要自己訓練一顆模型。
你不用準備一小時的錄音,不用跑微調,不用等好幾個鐘頭。你給模型兩樣東西:一段你的錄音,和那段錄音的逐字稿。模型從這兩樣東西抓出你的音色跟腔調,然後拿去唸你給的新句子。整個過程完全不用更新權重,所以叫 zero-shot。
這也是為什麼參考錄音跟逐字稿必須完全對得上。模型是靠「這段聲音對應這些字」去學你怎麼發音的,對不上就學歪。
錄那 24 秒:一次錄完,吃螺絲就重錄
先錄一段自己的聲音。錄一次,之後可以一直用。
環境:安靜房間,冷氣出風口別對著你,關掉背景音樂。麥克風用 MacBook 內建的就夠了,離嘴 15 到 20 公分。
唸法:照你平常講話的速度跟音量唸,像在跟一個人解釋事情,不要用廣播腔。
長度:20 到 30 秒。我實際用的那份是 23.9 秒。
內容:自己寫一段,但要涵蓋你之後真的會用到的詞。我這份塞了英文技術詞進去,因為旁白一定會唸到:
大家好,我是 ai-muninn 的作者。這個頻道談的是怎麼用便宜的硬體、開源的模型,把 AI 真正跑起來。我不太喜歡講理論,比較喜歡把東西實際跑一遍,量出數字,再告訴你哪裡會踩坑。今天這一堂會提到 GPU、token 跟 llama.cpp 這幾個詞,聽不懂沒關係,我會一個一個解釋。好,我們開始。
最重要的一條:一次錄完整段,中間唸錯就整段重錄。 不要剪。因為逐字稿要跟音檔一字不差,剪過的音檔跟你手上那份文字就對不起來了。
錄法最簡單的是 QuickTime Player 的「新增音訊錄製」,或手機語音備忘錄錄完 AirDrop 過來。要用命令列的話:
# 先看麥克風是哪一號
ffmpeg -f avfoundation -list_devices true -i ""
# → [1] MacBook Pro的麥克風
# 錄 30 秒(冒號開頭 = 只收音訊)
ffmpeg -f avfoundation -i ":1" -t 30 -ac 1 -ar 24000 ref_raw.wav
錄完轉成模型要的格式,順便修掉頭尾的空白:
ffmpeg -i ref_raw.wav -ac 1 -ar 24000 \
-af "silenceremove=start_periods=1:start_threshold=-45dB:stop_periods=-1:stop_threshold=-45dB" \
ref.wav
檢查一下錄得能不能用:
ffmpeg -i ref.wav -af volumedetect -f null /dev/null 2>&1 | grep volume
# mean_volume: -29.6 dB
# max_volume: -10.7 dB
峰值落在 -6 到 -12 dB 之間就很好,代表音量夠大又沒有爆音。我這份是 -10.7 dB。
最後把逐字稿存成 ref_text.txt,內容跟你唸的一模一樣。
裝環境:兩個套件
python3 -m venv .venv && source .venv/bin/activate
pip install mlx-audio soundfile
mlx-audio 會把 MLX 一起帶進來,那是 Apple 自己的機器學習框架,直接吃 Apple Silicon 的 GPU。我這台上面是 mlx 0.31.1 + mlx-audio 0.5.1,Python 3.14 arm64。
模型第一次跑會自己從 Hugging Face 下載。

十行 Python
from mlx_audio.tts.utils import load_model
import soundfile as sf
model = load_model("mlx-community/Qwen3-TTS-12Hz-1.7B-Base-8bit")
ref_text = open("ref_text.txt", encoding="utf-8").read().strip()
results = list(model.generate(
text="今天要講的是怎麼在自己的機器上跑大模型。",
ref_audio="ref.wav",
ref_text=ref_text,
lang_code="auto",
split_pattern="",
verbose=False,
))
r = results[-1]
sf.write("out.wav", r.audio, r.sample_rate, subtype="PCM_16")
就這樣。ref_audio 跟 ref_text 是那對必須對得上的東西,text 是你要它唸的新句子。
🔴 模型名字裡的 12Hz 不能漏。 完整是 Qwen3-TTS-12Hz-1.7B-Base。少打那三個字元會回 401,而 401 看起來像權限問題,你會跑去查登入而不是查名字。我就是這樣浪費過時間。
另外 Base 這個變體只做 clone,它沒有預設語音。 想要現成的音色要換 CustomVoice 那支,兩支的 API 也不一樣。細節在後面那節。
你會拿到什麼
速度比即時快。 我合成 83 秒的旁白花了 51 秒。這是 MacBook 本機的數字,沒有動用任何 GPU 機。
語速大約一秒 5.3 到 6.5 個字,換算下來 60 秒的旁白裝得下 320 到 390 字。寫腳本的時候拿這個抓長度很準。
品質:我把成品逐段丟給 whisper 轉寫,再跟原始腳本逐字比對,字元錯誤率 7.6%。這個數字的用途是抓「唸錯字」,不是抓「好不好聽」——好不好聽還是得自己聽。我聽過,過關。
授權:Base、1.7B-CustomVoice、0.6B-CustomVoice 三個都是 Apache-2.0 而且非 gated,商用沒問題。這點值得特別確認,因為同類的開源 TTS 有不少是不能商用的。
進階:四件開始用之後才會遇到的事
不讀這節不影響你把第一段旁白做出來。
三個變體分工不同,選錯 API 就對不上
Qwen3-TTS 有三個變體,名字很像,能做的事不一樣。我一開始照著網路上查到的說法選,結果跟實測對不上,以下是自己跑出來的:
| 變體 | 做什麼 | 呼叫方式 |
|---|---|---|
| Base | 只做聲音複製,沒有預設語音 | model.generate(text=, ref_audio=, ref_text=) |
| CustomVoice | 有 9 個預設語音,支援情緒指令 | model.generate_custom_voice(text=, speaker=, language=, instruct=) |
| VoiceDesign | 用文字描述生一個新聲音 | model.generate_voice_design(text=, language=, instruct=) |
CustomVoice 那 9 個預設語音是 serena / vivian / uncle_fu / ryan / aiden / ono_anna / sohee / eric / dylan,而且它吃 instruct 參數,可以下「平穩、清楚」這種情緒指令。Base 沒有這個參數,因為它的語氣是從你的錄音裡抄的。
支援 10 種語言,外加一個 auto 自動選:auto / chinese / english / german / italian / portuguese / spanish / japanese / korean / french / russian。
沒有固定 seed,同一句每次語調都不一樣
這件事會直接決定你的產線怎麼設計。
同一段文字跑兩次,出來的語調不一樣。字都對,但抑揚頓挫會飄,英文字母每次唸出來也不太一樣。這不是 bug,是取樣本來就有隨機性,而這支模型沒有給你固定 seed 的入口。
我原本想的做法是建一份拼寫字典,把會唸歪的詞預先寫死。那個方向是錯的,因為問題不在「這個詞它不會唸」,是「這個詞它這次唸成這樣、下次唸成那樣」。
實際可行的做法是逐句重跑:整段旁白切成句子,一句一個檔,聽到哪句不順就只重跑那一句。反正一句只要一兩秒,重跑的成本低到可以當免費。這也順便解掉另一個問題——改一句話不用重合成整段。
驗收要逐段轉寫,別整檔丟進去
上面那個 7.6% 是逐段算的。我第一次量的時候是把 83 秒的拼接檔整個丟給 whisper,拿到 0.35,差點以為這條路不行。
去翻逐字稿才發現錯的全部擠在檔案結尾——whisper 在長音檔的尾巴會開始自己編,那是已知行為。前面每一句都對得漂漂亮亮。
改成逐段轉寫再算,實際只有 0.076。差 4.6 倍,而且我差點就照那個數字把一份沒問題的成品丟掉。
這裡的教訓跟 TTS 本身無關:壞掉的是量測程序,不是被量的東西,而這兩件事在數字上長得一模一樣。
同一台機器上,另一個選擇不能用
我同時測了 BreezyVoice,那是聯發科出的,明寫支援中文與中英混講,聽起來剛好就是我要的。
在 MacBook 上它比即時慢 24 倍。合成一分鐘旁白要跑二十四分鐘。這不是慢一點,是產線做不起來。
它不是壞模型,是它需要 GPU 而我這條線的前提是本機跑。要用它得整條搬到 GPU 機上,那就變成另一個題目了。
收工
錄 24 秒、裝兩個套件、十行 Python,你就有一個會用你自己聲音講話的 TTS,跑在自己的筆電上,商用授權沒問題。
三件事別忘了:錄音跟逐字稿要一字不差、模型名字裡有 12Hz、驗收要逐段轉寫。
至於旁白要拿來做什麼,那是下一篇的事。
同系列其他文章:Skill 裡的檢查完全寫對了,但它一次都沒跑過 · AI 味不在破折號,在結構
常見問題
- MacBook 本機跑得動語音合成嗎?需要 GPU 機嗎?
- 跑得動,而且比即時還快。Qwen3-TTS-12Hz-1.7B 的 8bit 版透過 MLX 在 Apple Silicon 上跑,我實測合成 83 秒的旁白花 51 秒。同一台機器上 BreezyVoice 慢到即時的 24 倍,那個就不能用了。
- 為什麼不用內建的預設語音,一定要 clone 自己的聲音?
- 因為預設語音沒有一個是你。旁白掛在自己的文章或課程上時,聲音本身就是識別度的一部分。另外 Base 這個變體本來就沒有預設語音,它只做 clone;要現成音色得換 CustomVoice 那支。
- 參考錄音要多長、要多乾淨?
- 20 到 30 秒就夠,zero-shot 不用訓練。我用的那份是 23.9 秒、24 kHz 單聲道、峰值 -10.7 dB。安靜房間、離嘴 15 到 20 公分、用平常講話的速度念完一整段,中間吃螺絲就整段重錄,因為文字稿要跟音檔逐字對得上。
接著讀
- 2026-08-31[Dev Workflow] AI 味不在破折號,在結構:一篇論文教我怎麼檢查自己的文章
StoryScope 完全不看文風,只看敘事結構,93.2% macro-F1 就分得出人寫的與 AI 寫的。我拿它的五個問題掃自己已發布的文章,同一個教訓被抓到講了四次。
- 2026-08-21[AI Agent] 從 41 分鐘到 73 秒:一張小工單的病理解剖
派給 Codex CLI 的小工單常常一跑就是 40 分鐘起跳。這篇拆開近兩週 41 個執行紀錄,量出「牆鐘時間≈工具呼叫次數×14.3 秒」的公式,揪出 41 分鐘裡機器真正做事只有 4.4 分鐘,再用四刀把同型工單壓到 73 秒。
- 2026-07-29[Dev Workflow] Skill 裡的檢查完全寫對了,但它一次都沒跑過
一篇技術文五關全過,唯一的人類讀者一句話就命中:同一個句型在稿子裡出現了五次。查下去發現該抓的規則白紙黑字寫在 skill 裡、內容也對,但它標著「人工掃」——沒有指令、沒有 exit code、擋不住任何東西,所以從來沒運作過。門檻怎麼用五篇舊文校準、兩次真實攔截、以及一個機械檢查永遠抓不到的變體。
- 2026-07-27[Dev Workflow] agent 把自己講錯的話存進了長期記憶——連 /new 都殺不掉
一個有長期記憶的 AI agent,把自己答錯的結論自動寫進記憶,之後每個新對話都翻出來當事實。/new 清不掉,因為幻覺住進了記憶層。五回合探針加三刀解毒的完整病歷。
不想錯過新文章?
訂閱我確保不漏接!
隨時一鍵退訂。