雙AI上臉實戰:雙音訊線、語音預生成與情緒通道

這篇解決的問題:兩個AI角色同時上臉直播,共用一條虛擬音訊線會讓兩張臉一起對嘴,回合之間的接話等待也會拖到數秒。適合誰讀:已經有單一AI角色能開口說話,正要擴充成雙角色上臉直播的人,需要Windows、一張入門顯示卡,並且會裝虛擬音訊驅動與VTube Studio。讀完能做到什麼:兩張臉分別對嘴、回合間的接話等待壓到0秒、語調隨情緒起伏但不會爆音。

接續 雙AI雜談台 架構與執行,記錄2026-06-12到06-13打通的部分。延遲的理論背景見 直播延遲優化方案。

雙臉對嘴與接話歸零已驗收,動作與推流長測隨後補齊

實跑 duo_talk.py,雙AI話題討論成立,選題與人設方向留待再調。雙VTS加雙音訊線讓兩角色各自動嘴,實測通過。預生成升級之後接話等待壓到0.0秒。情緒通道第一版做出來,LLM在句首標情緒標籤,餵給edge-tts調語調。雙VTS同時跑的負載測試也做了,數字在下文的硬體段落。待辦的是VTS內建動作(眨眼、呼吸、物理、idle)與OBS同框推流長測,這兩項在隨後完成,見文末。

一條虛擬音訊線只能同步一張嘴,雙角色要開兩條線

VTS的唇形同步原理是「聽麥克風音量開合嘴」。兩個角色若共用同一條虛擬線,兩張嘴會一起動,雙人感直接毀掉。解法是一角色一條線、一線驅動一張嘴:

角色Python送音到(播放端)VTS實例的麥克風(錄音端)
星野CABLE Input(VB-CABLE)CABLE Output
楓Hi-Fi Cable Input(新裝)Hi-Fi Cable Output
graph LR
  A[星野語音合成] --> B[CABLE Input]
  B --> C[CABLE Output]
  C --> D[VTS實例一:星野模型]
  E[楓語音合成] --> F[Hi-Fi Cable Input]
  F --> G[Hi-Fi Cable Output]
  G --> H[VTS實例二:楓模型]

官方推薦的VB-CABLE A+B是捐贈制,最低要付15美元才拿得到安裝包。同公司的Hi-Fi Cable免費直載,差別是它不做自動重採樣,兩端取樣率要手動對齊。整條管線統一在48kHz,剛好相容。若長時間直播發現Hi-Fi Cable不穩,備案是花錢買A+B。

角色設定檔加一個 audio_device 欄位,main.py 的 play_routed() 接受每次呼叫指定裝置,缺省走全域 VTS_AUDIO_DEVICE。多角色路由變成一角色一行設定。

接話等待歸零,關鍵是把語音合成也放進預生成

只把LLM文字預生成藏進播音時間,理論上接話該無縫,實聽卻還有明顯的空隙。真兇是TTS:原本的流程是播完音才開始合成下一句,edge-tts的網路請求加上ffmpeg轉檔全部卡在這個空檔裡。文字被藏掉了,語音沒被藏掉。

解法是把 speak() 拆成兩段:synth_to_wav48() 只負責合成,play_routed() 只負責播放。duo_talk.py 的背景執行緒趁對方講話的時候,把LLM和TTS一次做完,輪到自己講話時直接播現成的wav。升級後接話等待壓到0.0秒。

藏延遲要對整條鏈算帳,LLM、TTS、轉檔、播放啟動,漏掉任何一段,省下來的時間就被那一段吃回去。每一段都要單獨量測才抓得到真兇。每題的第一輪沒有預生成可用,仍有完整延遲,但換題本來就有緩衝,這段可以接受。

情緒先接語調,同一個標籤之後也能接表情動作

edge-tts不支援情緒風格,SSML的express-as會被忽略,只吃rate、pitch、volume這三個參數。設計重點是先在文字產生的階段掛一個情緒標籤,讓這個標籤同時餵給語調調整,之後也能餵給表情動作。

system prompt要求每句句首輸出一個情緒標籤,從平靜、認真、開心、興奮、吐槽、驚訝、無奈裡選一個。程式拆下標籤後對映到rate/pitch的微調,疊加在人設本身的基準音高上。標籤本身不會被唸出來,句子中間如果又冒出一次標籤(模型偶爾會在句中換情緒重標一次),也要清乾淨。Gemini標得相當準,開場自動標興奮、吐槽的回嘴標對地方、收尾標平靜。

這個標籤是一個可以重複利用的訊號源。第二版規劃是用pyvts接VTS的WebSocket API,雙實例各開一個埠,把同一個標籤對映到表情或動作的hotkey,講到興奮就睜眼揮手,聲音和臉自動同步,不用另外寫判斷邏輯。語調本身的天花板尚未驗證,備案是Azure Speech官方API的同款聲線支援真正的express-as風格,或雲端GPU跑 GPT-SoVITS。

疊加的情緒參數太猛,會把聲音直接弄爆

情緒通道上線後,長句開始變成兩倍速的尖銳聲,男聲則直接電子破音。根因是上一節那組增量:星野的基準音高再疊上驚訝的加成,一路加到正40Hz,超出這條聲線本身撐得住的範圍。修法是把情緒增量調降,並加上疊加夾限正負15%、正負20Hz。男聲破音還多一個原因是播放緩衝來不及,改用 sd.OutputStream(latency="high") 補掉。

情緒標籤是疊加在人設基準上的。設計增量時要問的是疊在最極端的那個基準上會不會超出去。

VTS內建設定就能讓角色除了嘴巴都會動

上臉的動作分兩層。第一層不用寫程式:VTS內建的自動眨眼、自動呼吸、物理效果(頭髮衣服搖晃)、模型idle動作循環,每個實例各自設定一次,效果立竿見影。第二層是程式驅動,用pyvts觸發表情或動作的hotkey,訊號源就是前面那個情緒標籤。

兩個VTS實例同時跑,顯卡負載不到一半

雙VTS實跑,硬體是GTX 1660 6GB:

指標平均峰值上限
GPU使用率29%41%—
VRAM2632 MiB2799 MiB6144 MiB

硬體徹底不是瓶頸。OBS推流疊上去之後要再測一次,這時候該看的風險指標是OBS統計裡的渲染延遲錯過影格。編碼不必看,NVENC是獨立硬體,不佔用這裡量到的GPU使用率。

雙臉雙線打通了,中央音訊調度器還沒開始做

VTS的內建動作設定完成,OBS雙視窗同框加推流的長測也通過,雙臉雙線這條路整段打通。硬體從頭到尾都不是瓶頸。

還沒做的是中央音訊調度器,讓語音逐句串流、播放可以隨時取消,這會讓插話從「回合之間」細到「句子之間」。這件事尚未動手。