這篇解決什麼問題:解決兩個 AI 虛擬主播共用一條虛擬音訊線導致兩張臉同時對嘴,以及 AI 之間接話有數秒尷尬空白的問題。 適合誰讀:已經有單個 AI 角色能說話,想擴展為雙角色同框直播的開發者。需要 Windows 系統、一張入門獨立顯示卡,且懂得安裝虛擬音訊驅動與 VTube Studio 的基礎操作。 讀完能做到什麼:實作兩個 AI 角色各自獨立對嘴、接話等待時間壓到 0.0 秒,且語調能隨情緒起伏而不會電子破音。
接續先前在 [[雙AI雜談台 架構與執行]] 的實驗,我記錄了 2026-06-12 到 06-13 期間打通雙 AI 上臉的技術實踐。關於延遲優化的理論背景,可以參考 [[直播延遲優化方案]]。
0. 進度快照
- 完成 階段 A 驗收:我實地運行
duo_talk.py,雙 AI 話題討論架構成立,後續僅需微調選題與人設。 - 完成 雙 VTS 與雙音訊線:兩個角色各自獨立對嘴,測試通過。
- 完成 接話空隙歸零:預生成機制升級為文字與整段語音同時預備,實測接話等待時間降至 0.0 秒。
- 完成 情緒通道 v1:LLM 句首情緒標籤成功對應到 edge-tts 的語調微調。
- 完成 負載實測:雙 VTS 運行 10 分鐘,GPU 平均使用率 29%、峰值 41%,VRAM 峰值 2799/6144 MiB。
- 待辦 後續計畫:設定 VTS 內建動作(自動眨眼、呼吸、物理效果、空閒動作)與 OBS 同框推流長期測試。
1. 雙臉雙線:解決共用虛擬音訊線的對嘴衝突
VTube Studio(以下簡稱 VTS,一款 Live2D 角色控制軟體)的唇形同步機制是偵測輸入麥克風的音量大小來讓模型開合嘴巴。如果兩個 AI 角色共用同一個虛擬音訊裝置,兩張嘴就會同時開合,徹底失去雙人對話的真實感。 為了解決這個問題,我採用「一角色配置一條音訊線」的物理路由。
我將兩個角色的音訊路由進行並排對比:
| 角色 | Python 播放端送音位置 | VTS 實例的麥克風錄音端 |
|---|---|---|
| 星野 | CABLE Input(VB-CABLE,原有虛擬音訊裝置) | CABLE Output |
| 楓 | Hi-Fi Cable Input(新裝虛擬音訊裝置) | Hi-Fi Cable Output |
在選擇虛擬音訊線時,官方推薦的 VB-CABLE A+B 需要捐款至少 15 美元才能取得安裝包。因此我改用同公司免費提供的 Hi-Fi Cable 驅動。它的限制是不提供自動重採樣功能,也就是播放端與錄音端的音訊採樣率必須手動對齊。
技術細節:Hi-Fi Cable 與雙開 VTube Studio 設定
- 開啟 Windows 傳統聲音控制台。
- 將 Hi-Fi Cable Input(播放)與 Output(錄音)的預設格式皆手動設為 2 聲道、16 位元、48000 Hz。
- 系統不需要啟動隨附的 ASIO Bridge 應用程式,虛擬線裝完重新開機後即在驅動層常駐。
- 若要雙開 VTube Studio,Steam 版的第二個實例請勿從 Steam 開啟,請直接執行安裝目錄下的
VTube Studio.exe。每個實例載入各自的模型,並在麥克風設定中選取對應的虛擬線裝置。
在程式碼實作上,我在角色的 JSON 設定檔中新增了 audio_device 欄位。主程式 main.py 的 play_routed() 函數改為可接受指定播放裝置,若未指定則使用全域設定 VTS_AUDIO_DEVICE。這讓多角色音訊路由簡化為一角色一行設定。
2. 接話延遲歸零:將 TTS 語音生成藏進對手說話時間
在先前的階段 A 中,我雖然已經把 LLM 文字預生成藏在對方播放音訊的時間內,但實測發現接話時仍有 1 至 3 秒的尷尬空白。 經過追查,發現問題出在文字轉語音(TTS)的延遲。原流程是聽完對方說話後,才開始將下一句的文字合成為語音。雖然 LLM 生成文字的時間被藏掉了,但 TTS 的網路請求與音訊轉檔時間卻沒被避開。
這就像是在舞台劇中,演員不能等對手講完最後一個字,才去後台拿大喇叭;而是要在對手還在說話時,就已經把大喇叭拿在手上,輪到自己時才能直接開口。這個提前準備並把語音檔案暫存在硬碟中的機制,就是「預生成」。
我將 main.py 的 speak() 拆解成負責語音合成的 synth_to_wav48() 與負責播放的 play_routed()。在雙人對話控制腳本 duo_talk.py 中,當前一位角色正在發言時,背景執行緒會同時啟動 LLM 與 TTS 合成,將下一句的音訊直接轉成 WAV 檔備用。輪到該角色發言時,系統可以直接調用播放,等待時間直接壓到 0.0 秒。
技術細節:預生成檔案管理
- 預生成的 WAV 檔案使用 Python 的
tempfile.mkstemp寫入暫存目錄,播放完畢後立即刪除。- 若因使用者插話或留言導致預生成內容作廢,作廢的檔案會交給背景執行緒,確保其完成資源釋放後再行刪除,避免阻塞主迴圈。
- 每一輪對話的第一個句子因為沒有前置對話可供預先生成,仍需承受完整的 LLM 與 TTS 延遲。不過,換題空檔本來就有「正在讀取下一題」的系統緩衝,因此在體驗上是可以接受的。
3. 情緒通道:透過標籤同步驅動語調與動作
免費版的 edge-tts 並不支援微軟 Azure 的 SSML 情緒風格標籤,它只接受基本的語速(rate)與音高(pitch)調整。為了讓角色說話有起伏,我建立了一個「情緒通道」架構。
首先在系統提示詞中新增規則,要求 LLM 在生成每句話的句首輸出情緒標籤。程式在取得文本後會先拆下這些標籤,並將其轉換成對應的語速與音高增量。 這些增量是疊加在角色基礎音色上的,例如興奮會使語速增加 12%、音高增加 25Hz。 這些標籤在合成語音前會被過濾掉,不會被角色唸出來。
情緒通道的架構設計如下:
graph TD LLM[LLM 輸出句首帶標籤文本] --> Split[程式拆分標籤與純文本] Split --> Emotion[提取情緒標籤] Split --> Text[過濾後的純文本] Emotion --> Calc[計算語速與音高增量] Text --> TTS[TTS 語音合成] Calc --> TTS Emotion --> VTS[VTS WebSocket 動作觸發] TTS --> Play[虛擬音訊線播放對嘴]
這種設計的優點在於,情緒標籤是一個獨立的訊號源。目前 v1 版本它用來微調語音參數;未來 v2 版本則可以直接透過 pyvts 庫連接 VTS WebSocket API,將同一個標籤對應到 Live2D 模型的表情或動作快捷鍵上。當角色說到興奮時,聲音會高亢,模型也會同時做出揮手動作,完成音畫同步。
語調的極限備案,則是改用 Azure Speech 官方 API,或是在雲端 GPU 跑 [[GPT-SoVITS 與 Live2D 接線安裝指南|GPT-SoVITS]]。
4. 技術踩坑:情緒參數疊加導致的破音與限流問題
情緒通道實際上線後,立刻遇到嚴重的語音失真與技術限制。 星野的基礎音色原本就偏高,當疊加了【驚訝】的情緒增量後,合成出來的音訊超出了該聲線的物理極限,導致長句子變成尖銳且兩倍速的怪異聲音;而男聲角色在疊加極端音高時,也因為播放緩衝不足而出現電子破音。 為此,我進行了參數修正與穩定性調整。
技術細節:四個 TTS 穩定性坑的根因與修法
症狀 根因 修法 長句變 2 倍速尖銳聲 情緒疊加過頭(星野基準音調加上驚訝增量疊加到 +40Hz) 調降情緒增量,並在程式中加入疊加夾限:語速限制在正負 15% 內、音高限制在正負 20Hz 內。 男聲電子破音 音高硬拉失真,且 PyAudio 播放緩衝區發生 underrun 實施上述夾限,並將播放輸出參數改為 sd.OutputStream(latency="high")。edge-tts 間歇性出現 NoAudioReceived 報錯 微軟 edge-tts 伺服器端限流(此為官方 issue #443 描述之現象,非本地套件版本問題) 在 synth_edge函數中實作最多 4 次的指數退避重試機制,每次重試間隔 0.8 至 3.2 秒。語音夾雜中國腔調 edge-tts 預設中文聲線為 zh-CN 將星野的聲線改為 zh-TW-HsiaoChen,楓的聲線維持台灣男聲zh-TW-YunJhe。我使用測試腳本
prototype/tools/test_tts_fix.py進行驗證。測試中包含 2 個角色搭配 4 種情緒、長度達 80 字的長句子,共 8 組測試樣品,最終合成全部順利通過。 目前 edge-tts 提供的台灣中文聲線僅剩HsiaoChen(女)、HsiaoYu(女)與YunJhe(男)三種。如果未來需要更精確的在地化腔調,我需要考慮轉換至雲端聲線複製方案。
5. 免寫程式讓 Live2D 模型動起來的兩層做法
要讓虛擬主播顯得自然,除了對嘴之外,身體的自然擺動與眨眼也是關鍵。這部分可以分為免寫程式的內建設定,以及後續透過程式驅動兩種途徑。
在免寫程式的階段,我完全利用 VTube Studio 內建的功能來達成基本動態。
技術細節:VTube Studio 內建動態設定指南
以下設定必須在多開的兩個 VTS 實例中分別完成:
- 自動眨眼:進入模型設定 → 參數設定清單 → 展開
ParamEyeLOpen與ParamEyeROpen兩個通道,並勾選「Auto-Blink」。此設定不需要任何網路攝影機輸入。- 自動呼吸:找到輸出為
ParamBreath的通道並勾選「Auto-Breath」。若模型中無此通道,可手動新增一槽,將 INPUT 留空,OUT 設為ParamBreath。- 物理效果:確認官方範例模型的
physics3.json物理設定檔已啟用,並可將頭髮與衣服的物理擺動強度調整至 110% 到 120% 之間。- 空閒動作:在 Basic Setup 基礎設定中,透過檔案選擇器挑選
.motion3.json格式的動作檔案,VTS 將會自動循環播放。- 防止呼吸影響對嘴:確認
ParamMouthOpenY(嘴巴張開度)的 INPUT 維持為 VoiceVolume,且不可勾選自動眨眼或自動呼吸。- 若使用「Auto-Setup」自動設定,跑完後必須回頭確認嘴巴的 INPUT 設定未被改動。
6. 硬體負載與未來展望
在單主機上多開兩個 VTube Studio 實例並運行雙 Python 控制腳本,對於系統硬體是一大考驗。 以下是在 GeForce GTX 1660 6GB 顯示卡上實測 10 分鐘的硬體消耗數據:
| 效能指標 | 平均值 | 峰值 | 硬體上限 |
|---|---|---|---|
| GPU 使用率 | 29% | 41% | 100% |
| VRAM 顯示記憶體 | 2632 MiB | 2799 MiB | 6144 MiB |
測試結果顯示,顯示記憶體與繪圖晶片使用率連上限的一半都不到,硬體運算效能並非目前的瓶頸。
目前,雙臉雙音訊線的架構已在 2026-06-13 測試完畢。VTS 的內建動作配置完成,且 OBS 雙視窗同框推流長期測試表現穩定。
尚未驗證且仍待開發的部分,是中央音訊調度器。這套調度器預期要能支援逐句串流與播放中斷機制。完成後,AI 之間的插話行為將能從目前的「回合結束後插話」細緻化到「句子與句子之間中斷插話」。這項功能我先記錄在待辦事項中,後續再進行實作。