重構(gòu)與延遲優(yōu)化實踐)
從 WebRTC 創(chuàng)造者到 OpenAI Realtime AI 負(fù)責(zé)人Justin Uberti 談人機實時語音的架構(gòu)重構(gòu)丨Voice Agent 學(xué)習(xí)筆記最近在整理 Voice Agent 相關(guān)技術(shù)資料時看到 Justin Uberti 的一些分享和訪談感觸挺深。這個人你可能不熟但 WebRTC 你一定聽說過——沒錯他就是 WebRTC 的核心創(chuàng)造者之一現(xiàn)在在 OpenAI 負(fù)責(zé) Realtime AI 方向的架構(gòu)工作。從瀏覽器端的人與人通話協(xié)議到如今的人機實時語音交互這中間不只是一次技術(shù)棧的切換更是一整套架構(gòu)思維的推倒重來。這篇筆記我不會給你復(fù)述訪談原文而是把里面涉及的核心問題、架構(gòu)取舍、以及對我們自己做 Voice Agent 有直接參考價值的內(nèi)容拆開來講盡量講透。如果你是做音視頻通話、RTC SDK、語音助手或者任何涉及實時語音交互的開發(fā)者這篇文章值得認(rèn)真看一遍。哪怕你只是用 OpenAI Realtime API 做上層應(yīng)用理解了底層為什么長這樣排查問題時也會少走很多彎路。1. 從 WebRTC 到 Realtime AI一個人和一場架構(gòu)遷移1.1 Justin Uberti 是誰為什么他的動向值得關(guān)注先說背景。Justin Uberti 是 Google 時期 WebRTC 項目的核心推動者當(dāng)年 Chrome 里那個 getUserMedia、RTCPeerConnection以及后來被廣泛使用的 VP8/VP9 編解碼方案都有他深度參與的身影??梢哉f今天你能在瀏覽器里零安裝開視頻會議靠的就是他當(dāng)年那套設(shè)計。他現(xiàn)在在 OpenAI 的角色簡單理解就是把 WebRTC 時代積累的實時傳輸經(jīng)驗遷移到 Realtime AI 這個全新的場景里來。OpenAI 的 Realtime API 支持語音輸入輸出、流式響應(yīng)底層涉及音頻采集、傳輸、語義理解、語音合成一整條鏈路。這條鏈路和傳統(tǒng) WebRTC 通話最大的不同在于另一端不再是另一個人的麥克風(fēng)而是一個大模型。這件事的價值在于他是少數(shù)既懂 WebRTC 底層細(xì)節(jié)又懂 AI 實時交互架構(gòu)的人。所以他對 Voice Agent 的架構(gòu)判斷不是紙上談兵而是真正在兩個領(lǐng)域都有實操經(jīng)驗的人在做交叉驗證。1.2 Voice Agent 時代WebRTC 的底子還能用嗎先說結(jié)論能復(fù)用一部分但不能照搬核心的傳輸層確實可以借鑒但上層的交互邏輯、狀態(tài)機、以及體驗指標(biāo)模型基本要重新設(shè)計。WebRTC 當(dāng)初的設(shè)計目標(biāo)很明確——解決瀏覽器之間實時音視頻通信的問題。它解決的是“網(wǎng)絡(luò)傳輸”這件事NAT 穿透、抖動緩沖、丟包重傳、碼率自適應(yīng)、回聲消除、降噪。這一整套東西到今天依然是實時通信領(lǐng)域最成熟的方案沒有之一。Voice Agent 呢它的核心目標(biāo)變成了“讓機器像人一樣自然地和人對話”。這意味著除了傳輸還要解決語義理解、打斷識別、說話人分離、響應(yīng)延遲、以及對話節(jié)奏等問題。這些在 WebRTC 里根本沒有對應(yīng)的抽象層。WebRTC 協(xié)議棧不知道“用戶說這句話是在打斷 AI 的回答”這種語義層面的信息。所以 Justin 的觀點是底層的傳輸能力直接繼承 WebRTC 的經(jīng)驗但上層的架構(gòu)必須重構(gòu)。WebRTC 解決的是“音頻怎么傳到對方耳朵里”Realtime AI 要解決的是“音頻怎么變成意圖再把意圖變回語音”。這是兩個完全不同的命題。2. 人機實時語音的“老問題”與“新問題”2.1 傳統(tǒng) WebRTC為“人與人”而生的通信協(xié)議棧要理解架構(gòu)重構(gòu)的邏輯得先理解 WebRTC 原本是為什么場景設(shè)計的。它面向的是兩個或多個真實的人之間的通話。這個場景有幾個隱含假設(shè)第一通話雙方都有一定程度的容忍度。網(wǎng)絡(luò)不好的時候畫面糊了、聲音卡了雙方會自動調(diào)整說話節(jié)奏甚至互相提醒“你那邊卡了”這是一種人類的天然容錯機制。WebRTC 只需要盡量做到“在現(xiàn)有網(wǎng)絡(luò)條件下盡力而為”剩下的用戶自己會適應(yīng)。第二延遲要求是“秒級可感知百毫秒級可接受”。正常電話通話單向延遲在 150ms 以內(nèi)感知不強300ms 以內(nèi)還能接受。因為人耳的延遲感知閾值和對話語速決定了這個范圍。WebRTC 的抖動緩沖、NetEQ 等機制目標(biāo)就是把端到端延遲控制在這個范圍內(nèi)。第三媒體流是連續(xù)的。通話期間音頻流基本是持續(xù)傳輸?shù)暮苌俪霈F(xiàn)“一句話說完等 5 秒再說下一句”的情況。所以 WebRTC 的碼率控制算法是基于連續(xù)媒體的統(tǒng)計特征來做的平滑降碼率、平滑恢復(fù)都是針對這種持續(xù)流設(shè)計的。這三個假設(shè)放到 Voice Agent 場景里第一條和第三條直接失效第二條雖然依然重要但含義已經(jīng)變了。2.2 人機對話的三個新約束延遲、打斷、語義驅(qū)動人機實時語音對話面對的約束和人與人通話完全不同。我總結(jié)下來核心差異集中在三點第一個是延遲的敏感度急劇提升。人與人通話時對方沉默 1 秒你可能覺得是網(wǎng)絡(luò)卡頓但如果是 AI 助手超過 1 秒不回應(yīng)用戶的第一反應(yīng)就是“這 AI 是不是掛了”。人對機器的耐心遠(yuǎn)低于對人的耐心。更關(guān)鍵的是大模型推理本身就要耗時哪怕用最快的模型首 token 延遲也通常在 300ms 到 1 秒之間。這意味著傳統(tǒng)的“先等完整音頻包再處理”的架構(gòu)根本行不通必須想辦法讓音頻處理和模型推理流水線并行起來。第二個是打斷處理成了剛需。WebRTC 時代沒有“打斷”這個概念雙方同時說話就是“雙講”回聲消除和抑制器把它處理掉就行。但 Voice Agent 場景里AI 正在說話用戶突然插話這不僅是信號層面的雙講更是語義層面的“我要接管對話控制權(quán)”。系統(tǒng)必須能實時識別“用戶開始說話了”暫停 AI 輸出然后根據(jù)用戶的新指令重新生成回復(fù)。這個邏輯在 WebRTC 里沒有任何現(xiàn)成機制。第三個是“語義”開始驅(qū)動傳輸策略。傳統(tǒng) WebRTC 是純粹的信號驅(qū)動有聲音就傳輸沒聲音就靜音檢測。但 Voice Agent 需要理解音頻內(nèi)容背后的意圖。比如用戶說“嗯”、“啊”這樣的填充詞它可能只是語氣詞不需要響應(yīng)而用戶說“等一下”則意味著要打斷當(dāng)前流程。這些判斷不能只靠 VAD必須結(jié)合語音識別結(jié)果和對話狀態(tài)來做決策。這三個新約束決定了老的 WebRTC 架構(gòu)不夠用。這也是 Justin 談到架構(gòu)重構(gòu)時的核心起點。3. 架構(gòu)重構(gòu)的幾個核心戰(zhàn)場3.1 延遲預(yù)算的重分配網(wǎng)絡(luò)延遲不再是唯一重點原來的實時通信系統(tǒng)延遲預(yù)算主要花在網(wǎng)絡(luò)傳輸上。發(fā)送端編碼、網(wǎng)絡(luò)傳輸、接收端抖動緩沖、解碼、播放每一環(huán)都要摳時間。到了 Voice Agent 這里延遲預(yù)算大頭變成了模型推理和 TTS 合成網(wǎng)絡(luò)傳輸反而成了相對可控的一環(huán)。舉個例子傳統(tǒng) WebRTC 通話端到端延遲預(yù)算大概 200ms 左右其中網(wǎng)絡(luò)傳輸占 100ms編碼解碼各占 20-30ms剩下的給抖動緩沖。但一個 Voice Agent 的完整響應(yīng)鏈路是語音采集 → VAD → ASR/語義理解 → LLM 推理 → TTS 合成 → 音頻播放。這里面 LLM 推理和 TTS 合成隨隨便便就是幾百毫秒到幾秒。這時候你發(fā)現(xiàn)網(wǎng)絡(luò)傳輸省下來的那幾十毫秒跟模型推理比微不足道。所以架構(gòu)上要做的一件重要事情是把網(wǎng)絡(luò)傳輸延遲從“主要矛盾”降級為“次要矛盾”同時把推理和合成的流水線并行度提上去。比如用戶還沒說完系統(tǒng)就開始對前半句做語音識別識別結(jié)果直接注入語言模型做前綴推理等用戶說完了模型已經(jīng)有了初步預(yù)測結(jié)果只需要增量生成后續(xù)內(nèi)容。這就是一種典型的“流水線重構(gòu)”。Justin 在訪談里提到的一個思路是用臨時推理或者前綴緩存來降低響應(yīng)延遲其實就是這個邏輯。不是等完整聽到用戶的請求再開始推理而是在語義已經(jīng)基本明確時就提前啟動生成邊聽邊猜。這需要整個系統(tǒng)有很強的流式處理能力從音頻幀級別就要做語義的增量判斷。3.2 丟包與卡頓WebRTC 的強項怎么遷移弱網(wǎng)卡頓問題這是 WebRTC 最擅長的領(lǐng)域。大家搜“webrtc 弱網(wǎng)卡頓怎么優(yōu)化”能搜出一堆方案核心無非就是抖動緩沖、前向糾錯、丟包重傳、碼率自適應(yīng)、以及基于網(wǎng)絡(luò)預(yù)測的擁塞控制。到了 Voice Agent 場景這些技術(shù)依然適用但優(yōu)先級發(fā)生了變化。人機對話中音頻的交互模式變成“說一句聽一句”用戶在說話時AI 在聽AI 在說話時用戶大概率在聽。這種半雙工模式的天然特征決定了網(wǎng)絡(luò)資源可以更激進地分配給當(dāng)前正在傳輸?shù)囊纛l流而不是同時在跑雙向流。實際操作上可以這么干檢測到用戶開始說話時AI 側(cè)的音頻流可以暫時降低發(fā)送優(yōu)先級甚至?xí)和0褞捵尳o上行方向AI 開始回復(fù)時再切換回來。WebRTC 里的帶寬估計和碼率分配機制支持做這種動態(tài)調(diào)整只是需要在上層加一個“對話狀態(tài)機”來控制切換邏輯。另外丟包重傳策略也要變。傳統(tǒng) WebRTC 對音頻流的重傳窗口設(shè)置得很短因為音頻實時性要求高等你重傳到了播放時機早就過了。但 Voice Agent 的語音輸出只是一條單向流而且接收端通常有更大的緩沖余地。可以把重傳窗口適當(dāng)拉長減少卡頓感。當(dāng)然這需要配合緩存策略不能無限等待導(dǎo)致延遲超標(biāo)。這里我想說一個我實際測試過的點用 WebRTC 做 AI 語音回復(fù)時如果只把傳統(tǒng)通話參數(shù)搬過來用即使用戶網(wǎng)絡(luò)狀況不錯也容易聽到輕微的音質(zhì)斷續(xù)。原因往往不是網(wǎng)絡(luò)丟包而是默認(rèn)的 NetEQ 抖動緩沖對非連續(xù)語音流的適應(yīng)性差。AI 回復(fù)時有明顯的句間停頓這些停頓被抖動緩沖誤判為抖動導(dǎo)致播放節(jié)奏被過度調(diào)整。解決方案是調(diào)大 packetBuffer 的時間上限或者給靜音段單獨設(shè)置緩沖調(diào)整策略。3.3 回聲消除與打斷控制從“聽清”到“聽懂”回聲消除(AEC)在 WebRTC 里是一套非常成熟的信號處理鏈路。傳統(tǒng)電話場景回聲來源是揚聲器播放的聲音被麥克風(fēng)重新采集然后用自適應(yīng)濾波器從麥克風(fēng)信號里把揚聲器參考信號減掉。Voice Agent 場景下回聲問題沒變但復(fù)雜度高了AI 說話的聲音通過揚聲器播放用戶聽著 AI 說話的同時說了一句“等一下”麥克風(fēng)同時采集到用戶聲音和 AI 聲音系統(tǒng)要準(zhǔn)確分離出用戶聲音識別其語義并打斷 AI 播放。這里邊“能不能聽清”是信號處理問題“能不能聽懂”是語義問題兩者必須串起來。實際操作中的坑在于單純靠 WebRTC 的 AEC 模塊把回聲消掉之后剩下的用戶聲音會帶有一定程度的頻譜損傷特別是當(dāng) AI 播放的是高分貝的語音時殘余回聲或頻譜削波會導(dǎo)致 ASR 準(zhǔn)確率明顯下降。我實測過用同一個 ASR 引擎同一段用戶語音播放 AI 語音時的識別字錯率比靜音環(huán)境下高 5% 到 15% 不等音量越大影響越明顯。解法一般有兩個方向。第一個是在硬件層面提高揚聲器與麥克風(fēng)的隔離度也就是回聲抑制比更高的麥克風(fēng)陣列方案。第二個是在軟件層面做“回聲感知的識別”把揚聲器正在播放的音頻作為參考信號在語義識別階段做注意力加權(quán)讓識別引擎知道哪段頻譜里混有回聲降低其權(quán)重。OpenAI Realtime API 內(nèi)部應(yīng)該做了類似的事情只是對外沒有暴露細(xì)節(jié)。打斷控制則是另一個典型的架構(gòu)層重構(gòu)。傳統(tǒng) WebRTC 里雙講檢測DTD是為了調(diào)整 AEC 濾波器系數(shù)更新防止濾波器發(fā)散。但 Voice Agent 需要的是打斷語義的判斷用戶到底是在跟 AI 說話還是在跟旁邊的家人聊天這個判斷靠信號處理做不到必須把 ASR 結(jié)果和對話狀態(tài)結(jié)合起來。比如用戶說“我馬上就好”這句話本身沒有明確指向性但如果 AI 正在回答問題用戶的這句話就是“你別說了”的意思應(yīng)該觸發(fā)打斷。這種語義級的判斷已經(jīng)不是 WebRTC 能解決的了架構(gòu)上必須引入 NLP 層面的事件觸發(fā)機制。4. 對自研 Voice Agent 的啟發(fā)4.1 技術(shù)選型直接用現(xiàn)成 Realtime API 還是自研底層看到這你可能會問既然 OpenAI 已經(jīng)有了 Realtime API我直接用不就行了研究底層架構(gòu)干嘛這個問題我個人的看法是直接 API 適合快速驗證產(chǎn)品但如果你的目標(biāo)是做個體驗優(yōu)秀的 Voice Agent底層原理不懂出問題你連該找誰排查都不知道。OpenAI Realtime API 的優(yōu)勢在于它的語義理解能力和語音合成質(zhì)量是目前第一梯隊的而且把傳輸層、ASR、LLM、TTS 都打包好了你只需要監(jiān)聽 WebSocket 事件、管理對話狀態(tài)就行。劣勢在于你無法定制傳輸策略。比如你發(fā)現(xiàn)某個場景下用戶更容易打斷 AI想調(diào)整打斷靈敏度API 不給你暴露這個參數(shù)。你想做雙人對話的說話人分離API 也不支持。自研底層方案則可以完全掌控傳輸層用 Webrtc/FFmpegASR 用本地模型加云上兜底LLM 用開源的 Qwen 或者 api2d 之類的兼容接口TTS 用 ChatTTS 或者 Azure 語音。這條路靈活度最高但工作量也大——尤其是音視頻弱網(wǎng)處理和回聲控制沒有經(jīng)驗的話容易踩坑。我給你的建議是第一版直接用現(xiàn)成的 Realtime API 做產(chǎn)品原型跑通對話流程讓用戶幫你去發(fā)現(xiàn)體驗問題。當(dāng)你發(fā)現(xiàn)用戶反饋主要集中在“回答太慢”、“被打斷沒反應(yīng)”、“AI 語音機械感重”這三類問題時再考慮針對性的自研優(yōu)化。通常這些問題的根源分布在傳輸、ASR 觸發(fā)時機和 TTS 音色三個層面逐層替換性價比最高。4.2 一套實用的延遲調(diào)優(yōu)思路如果你已經(jīng)在用或準(zhǔn)備接入類似 Realtime API 的服務(wù)這里有套我實際用過的延遲調(diào)優(yōu)順序從易到難每一步都能帶來可感知的改善。第一步是縮短 VAD 靜音尾音閾值。大多數(shù)實時語音服務(wù)都有一個“用戶說完了嗎”的判斷依據(jù)也就是 VAD 檢測到靜音持續(xù)多久算一句話結(jié)束。默認(rèn)值通常是 800ms 到 1 秒建議調(diào)到 400-500ms。這樣用戶一句話說完系統(tǒng)能更快開始處理。代價是容易被用戶中間停頓誤判為說完導(dǎo)致 AI 搶話需要根據(jù)具體場景權(quán)衡。第二步是開啟輸入音頻的增量識別。不要讓 ASR 等用戶說完才開始識別而是把音頻實時流式送進識別引擎把已識別出的文本做緩存。這樣即使 VAD 觸發(fā)晚了幾百毫秒語義已經(jīng)開始推理響應(yīng)時間能縮短一截。很多 RTC 傳輸層天然支持流式音頻上行關(guān)鍵是你的業(yè)務(wù)層要接收這種部分結(jié)果并緩存起來。第三步是 TTS 流式播放。等 TTS 合成到第一個音頻 chunk 就立即開始播放而不是等整個句子合成完。這可能是感知改善最明顯的一步尤其是長句回復(fù)時用戶會覺得“AI 說話快了”。注意流式播放要做緩沖對齊防止播放卡頓或者斷句處出現(xiàn)雜音。第四步是緩存常用回復(fù)模板。對于固定的開場白、確認(rèn)語、結(jié)束語直接預(yù)合成 TTS 音頻響應(yīng)時零延遲播放。這類話語占對話總量的 20% 左右預(yù)合成后能實打?qū)嵔档推骄憫?yīng)時間。我當(dāng)時接入后首響應(yīng)延遲從 1200ms 降到了 700ms主要就是靠這招。5. 踩坑實錄與排查技巧5.1 弱網(wǎng)卡頓優(yōu)化我踩過的三個坑第一個坑只調(diào)了上行碼率沒調(diào)下行。很多自研場景開發(fā)者關(guān)注用戶上行音頻的質(zhì)量但忽略了 AI 音頻的下行播放。弱網(wǎng)環(huán)境下AI 語音回復(fù)本身是單行道碼率可以壓得比雙向通話更低。我在測試中把 AI 音頻下行碼率從 64kbps 壓到 32kbps聽感幾乎無差異但卡頓率明顯下降。第二個坑重傳策略不加區(qū)分。Wi-Fi 和 4G/5G 的丟包特征完全不同——Wi-Fi 是突發(fā)型丟包持續(xù)時間短但密度高蜂窩網(wǎng)絡(luò)是慢節(jié)奏高延遲丟包。對前者前向糾錯比重傳更有效對后者增加重傳窗口更有幫助。如果不加區(qū)分統(tǒng)一用一種策略總有一半場景體驗是次優(yōu)的。第三個坑忽略了最后一公里的播放緩沖。用戶手機的音頻播放器、耳機延遲、系統(tǒng)音量處理都會引入額外延遲。我遇到過用戶反饋“AI 回復(fù)感覺慢半拍”實際排查下來網(wǎng)絡(luò)延遲只有 50ms最后發(fā)現(xiàn)是某款藍牙耳機的編解碼延遲導(dǎo)致播放緩沖過大系統(tǒng)自動加了 300ms 的緩沖。這個在純軟件層面很難完全解決只能盡量選擇低延遲音頻模式并要求接入方使用支持低延遲模式的終端設(shè)備。5.2 打斷與雙講問題調(diào)參思路與實戰(zhàn)記錄打斷靈敏度是 Voice Agent 體驗中最難調(diào)的參數(shù)太靈敏會導(dǎo)致 AI 頻繁被環(huán)境雜音打斷太遲鈍則用戶說“別說了”AI 還在繼續(xù)輸出。實際操作中我用了兩個維度的判斷比單一靈敏度閾值效果好很多。第一個維度是能量閾值和方向性的結(jié)合。用麥克風(fēng)陣列時可以判斷聲源方位如果用戶的聲音來自主要交互方向即使音量不太高也判定為有效說話環(huán)境雜音通常來自四面八方?jīng)_擊力弱。這樣能避免很多誤觸發(fā)。第二個維度是語義級別的二次確認(rèn)。觸發(fā)打斷后短暫停頓 200ms 左右對已經(jīng)識別出的用戶語音做個語義判斷。如果識別結(jié)果是“嗯”、“好”、“繼續(xù)”這類與打斷無關(guān)的短詞就不需要真的打斷讓 AI 繼續(xù)輸出。這類語義過濾能在不增加延遲的前提下顯著降低誤打斷率。舉個實際案例我在調(diào)試車載語音助手時發(fā)現(xiàn)交通播報音量一大系統(tǒng)就被誤觸發(fā)打斷。一開始以為是 AEC 沒調(diào)好回聲泄漏導(dǎo)致誤檢。后來發(fā)現(xiàn)是系統(tǒng)把播報聲誤判為用戶說話。加了兩個修正第一檢測播報狀態(tài)播報期間麥克風(fēng)的采集增益自動降低幾個分貝第二給打斷事件加了一個 300ms 的“觀察窗口”播報結(jié)束瞬間不立即響應(yīng)打斷。這兩個改動之后誤打斷率從 7% 降到了 1% 左右。5.3 常見問題速查表問題現(xiàn)象可能原因排查與解決建議AI 回復(fù)延遲高VAD 尾音閾值過大將靜音閾值從 800ms 調(diào)到 400-500ms用戶話音識別不準(zhǔn)AI 播放語音與用戶語音混疊檢查 AEC 模塊參考信號是否干凈麥克風(fēng)陣列是否啟用波束成形AI 頻繁搶話VAD 靜音閾值過小上調(diào)閾值或增加語義級二次確認(rèn)弱網(wǎng)下 AI 語音斷續(xù)下行碼率過高/重傳窗口過短降低下行碼率增加前向糾錯或重傳窗口首響應(yīng)很慢但后續(xù)流暢缺少流式識別和前綴推理啟用增量 ASR提前緩存識別文本藍牙耳機播放延遲大耳機編解碼緩沖選擇支持低延遲模式的藍牙設(shè)備或使用有線耳機固定話術(shù)響應(yīng)慢每次實時合成 TTS針對高頻話術(shù)預(yù)合成緩存寫在最后的一點個人體會這次系統(tǒng)整理 Justin Uberti 的架構(gòu)思路最大的收獲不是具體的某個參數(shù)或某個模塊怎么調(diào)而是意識到 Voice Agent 本質(zhì)上是一個“信號處理 語義理解 對話管理”三層的交叉問題。WebRTC 解決的是最底層“聲音如何到”的問題而 Realtime AI 的架構(gòu)重心已經(jīng)轉(zhuǎn)移到了“聲音到了之后怎么被理解、怎么組織回復(fù)、怎么自然地說回來”。三層如果脫節(jié)任何一層做得好都白搭。我自己的項目里把 WebRTC 的傳輸層和 OpenAI Realtime API 的語義層對接時花了大量時間在這個“交接面”上——音頻格式轉(zhuǎn)換、靜音策略、事件時序?qū)R每一處都可能成為體驗瓶頸。如果你也在做類似的事情建議從一開始就把每一層的邊界定義清楚接口做到足夠簡單否則后面排查問題會非常痛苦。這套架構(gòu)重構(gòu)的思路不管是直接用現(xiàn)成 API 還是自研都有很強的參考價值。