許路平:Gvoice千萬在語音輸入的那些事

GVoice為騰訊旗下的主流遊戲提供低延遲語音通話服務,其特點是高併發、覆蓋全球。本次分享的嘉賓許路平是GVoice後臺負責人,他會詳細介紹遊戲業務的特點,以及GVoice針對性的架構與協議的設計原則和方法,高可用保障與成本控制,重點效能突破和未來展望等。

文 | 許路平

整理 | LiveVideoStack

許路平

公開課

今天的分享主要包括5個部分:第一部分是GVoice的產品形態以及在研發過程中的目標;第二部分是針對產品需求介紹GVoice的整體架構設計;第三部分是拆分架構中的每一個模組以及討論容災和擴縮容的方案;第四部分會詳細地展開介紹傳輸演算法並分享一些在GVoice語音傳輸中用到的調優方案及效果;第五部分對整個分享作總結以及思考有哪些可以改進的地方。

01

遊戲語音系統的需求和特點

遊戲實時語音系統中最常見的場景有兩種,一種是“王者榮耀”類遊戲可以五人組隊,這就是小隊語音形態,每個隊伍不超過10或20個人,但是會高頻率地交流;另一種場景是“御龍在天”類遊戲,遊戲中的國戰一個房間可以同時線上幾千甚至上萬人。在處理這兩種語音場景時解決的問題不同,需要分別進行討論處理。

GVoice是騰訊專門為遊戲打造的語音通話SDK。在談到語音通訊的時候,大家可能最先想到的是語音聊天等場景,但在遊戲中對於語音通話有一些特殊要求,接下來我們看一下GVoice在遊戲場景中有哪些要求。

因為我們不是做終端產品而是做SDK,那必然涉及到開發接入的問題,在長期做SDK的過程中我們摸索出了兩個關鍵點:第一是儘量少的人接SDK;第二是SDK的介面儘量少,因為多一個介面就要多寫許多宣告或是解釋許多問題,另外多出錯率也會越大。所以GVoice的產品要求只需兩點,一個是隻需要客戶端SDK接入即可,另一個是隻需要編寫五行程式碼即可接入GVoice語音服務。

為什麼只需要編寫五行程式碼?為了實現五行程式碼接入語音服務,GVoice的後臺需要做哪些配合呢?看似簡單的操作背後其實有著很複雜的設計。

遊戲語音是一個非常單純的場景,就是基於房間的轉發和通訊(如右圖最後一行程式碼所示),所以最重要的是讓客戶端知道房間位於的伺服器位置,剩下的就是客戶端和伺服器之間進行語音資料傳輸的問題。這些都是在做VoIP系統或是語音傳輸系統中大家非常熟悉的領域。

遊戲語音系統的需求包括四點:

首先需要高效能系統,因為遊戲都是大規模線上且業務成本敏感,實現高效能可以在極低的成本下為遊戲中的大部分使用者提供語音服務;

第二點是系統可伸縮,遊戲存在生命週期,從研發到玩家不斷增長再到衰退期,整個生命週期中對伺服器的伸縮和快速擴容要求非常高,比如擴容的速度趕不上游戲會造成事故,縮容的速度趕不上游戲會造成成本浪費;

第三點是高可用,遊戲中做到高效能、可伸縮的同時,如果還要求極高的高可用性就會大大增加整個系統的複雜度從而影響實際運維的效率,所以高可用性排在第三位。我們會拆解系統中的服務,針對關鍵服務做高可用,其他非關鍵服務或實現起來比較困難的服務儘量做到故障隔離,其次是做到柔性可用(比如有幾千臺伺服器,掛掉一兩臺只會影響百分之零點幾的玩家),要想辦法在這上面找到平衡點;

第四點是易使用,GVoice在全球四五十個國家部署,也就是說如果做了一套非常龐大或是複雜的系統,那麼在海外的運營環境(海外的裝置配置、網路配置等)會參差不齊。如果系統非常龐大且依賴第三方主件會導致整個GVoice的部署困難,使業務出海受到影響。

綜上,我們會始終以實現這四個需求為目標來設計整套系統。

02

GVoice架構設計

這不僅是遊戲語音系統,更是一個典型的語音通話系統的設計架構圖。

舉個例子向大家解釋將信令和資料服務拆分開的原因,當你拿起手機準備撥號,這就是信令通訊的過程,可能就持續幾秒鐘,但建立完整個資料連線後,資料服務持續時間可能是半小時甚至更久。所以整個語音通話過程中,信令和資料傳輸流量佔比嚴重不對等,它們的傳輸需求如可靠性、可用性、成本佔用方面也各不相同。因此要拆分信令及資料服務,再根據它們的特徵和需求分別做設計。打電話第一步是請求信令服務,比如要到某個房間通話,信令服務就會返回資料服務地址,客戶端根據資料服務建立連線,這個連線是長期存在的,信令服務可能一個來回就解決了,但是語音通訊每秒都有幾十個包,所以將它們拆開分別做設計。

信令服務需要具備高可用性,因為如果一開始無法撥通電話,對使用者來說體驗感會很差。其次是可伸縮,我們的服務在外網運營的時候可能會受到各種攻擊,或者業務迅速擴張或縮小,這都需要信令服務具備可伸縮性以快速滿足業務的各種需求。

資料服務首先要具備良好的效能,一個客戶端連入伺服器時每秒互動的包可能達到二三十個或更多,這對資料服務的效能是非常大的考驗。第二是可伸縮,這對網際網路類的產品是必備的特徵,做任意型別的服務時都要保證根據業務規模的變化,迅速擴容或縮容。第三是低成本,多媒體通話的很大一塊成本都集中在頻寬,所以在做傳輸的過程中需要考慮如何節省頻寬並且提高傳輸效率。

上文已經將GVoice拆分為了兩大塊,分別是信令服務和資料傳輸服務,接下來要針對不同模組分配相應的職責。在處理不同業務邏輯的時候,可能對每一個業務的轉檯有不同要求,所以我們把整個服務分為了有狀態或無狀態,這也是一個網際網路後臺設計通用的思想。

無狀態服務有一個特徵,只要均勻或隨機分佈請求,那麼整個伸縮性和容災性都會非常好。所以根據請求是否有狀態,比如無狀態邏輯包括鑑權、限流、路由、計費,將它們放在第一層的信令叢集處理,這叫做信令接入伺服器。將有狀態的服務比如房間資訊管理(給每個房間分配轉發伺服器)、叢集管理(如何擴容縮容、每個叢集的存活狀態)放在中間層的轉發管理叢集處理。這樣做是因為前面的接入叢集直接面向官網客戶端,那麼接受的請求不一定來自合法的客戶端,也有可能來自攻擊和異常流量,透過將前面的信令服務設定為無狀態並大規模部署能夠抵禦異常攻擊,過濾異常流量後,再放到真正的轉發管理叢集中處理房間邏輯,整個轉發管理叢集的規模就可以得到有效控制而且中間有狀態的服務規模可以限制到很少。有狀態的服務是不可靠的,因此大規模有狀態服務會非常難管理。我們透過以上策略儘量縮小有狀態服務,儘量擴大無狀態服務能處理的邏輯,這就可以縮小有狀態叢集從而方便後續運維管理。

最底層是專門負責轉發服務的叢集,因為轉發服務的總量佔整個叢集規模的95%以上,這是非常大規模的伺服器管理,管理方法同部隊管理,分為班長、排長、連長等。我們不可能實時知道每一臺伺服器的狀態,那就把它們分組編隊,一組一組管理,所以在整個轉發叢集按分組方案來管理時,每組可能是很小的叢集,每次擴容縮容都以最小叢集進行操作,從而確保操作的叢集數量有限,不會出現一個叢集中有一萬臺機器,每次都要觀察每臺機器的狀態的情況,也就是說把1w臺機器分為100臺機器一隊,那麼只要管理100個叢集的狀態。這樣做的好處就體現在剛才提到的“國戰”大房間場景,首先單機傳輸難以滿足大房間場景的需求,我們預估遊戲最大的房間不超過100w同時線上,於是就把100w作為一個叢集部署,也就是隻需要一個叢集就可以滿足“國戰”遊戲的需求。

03

GVoice架構設計要點

3。1。信令服務設計要點

信令伺服器內部有多個模組包括客戶端接入,專門負責處理客戶端上行的各種請求,解決加密,處理異常資料包,統計監控(防止業務請求量加大或突發請求暴漲影響到其他業務)等。GVoice是面向全行業的語音引擎,所以內部有一套自成體系的業務賬號管理系統,在接入伺服器就有賬號資訊的快取,每個業務請求過來時都可以直接在接入伺服器上完成鑑權操作,避免了所有請求都直接透傳到最末端的資料庫中,這是最常見的提高處理效能的方法。此外,還有一些業務配置的管理,比如客戶端上來用的傳輸演算法、邏輯和引數都可以在接入伺服器一次性取回,不用多次訪問伺服器。還要對後面依賴的服務進行存活探測和容災,比如發現請求下游的伺服器出現故障就要遮蔽這臺伺服器並生成相應的告警資訊。

接入伺服器的設計重點是首先堅持一個原則——接入伺服器無狀態,所謂的無狀態是指任何客戶端任何時刻連線到任何一臺伺服器,只要能夠連線且網路通暢,那麼返回的結果都是有效的,除非後續業務出錯否則都能夠正確處理邏輯。第二是高效能,作為接入伺服器,經常需要接受外網各種突發流量的考驗,首先接入伺服器自身不能卡死,在受到攻擊後,如果自身的信令伺服器第一層就出現故障那麼整個系統的可用性就會變得很差。第三是可平行擴充套件,只要在實現無狀態的前提下,可平行擴充套件就會非常容易,只要不斷往叢集中加機器就可以。第四是高容錯,這是內部實現的特有邏輯或是說需要堅持的方法,程式碼要在服務或整個系統出現任一錯誤的情況下,依然可以保證基礎服務如語音通話服務的正常,GVoice中有許多語音服務包括離線語音、語音翻譯、語音轉文字、語音訊息,這些其他的周邊服務出現故障都要隔離開以保證語音通話服務的邏輯正常,因為只要玩家通話正常至少不會出現很嚴重的問題。

信令服務的擴容流程如圖,首先是拉起新部署的信令服務程序,然後測試程式撥測,通過後修改域名配置加入服務了,這是一個非常簡單的方案。縮容流程相比更加簡單,因為大家都沒有狀態,想刪掉的機器就直接在域名配置中摘掉,等待機器上流量掉0後停止服務程序,最後回收機器,就完成了信令服務的擴縮容。

容災方案分為兩塊,一塊是信令服務故障時的處理方法,另一塊是後端服務(依賴的其他服務)故障時的處理方法。

信令服務出現故障時依靠客戶端的自動輪詢重試,客戶端每次發生信令失敗都會將資料上報到監控伺服器,監控伺服器根據請求失敗率分析出最近一天的高失敗率的信令伺服器,引發告警從而人工介入檢查問題。透過重試手段可以保證信令請求的成功率。

後端服務出現故障時,我們使用的是一個簡單的容災探測邏輯,可用的原因是客戶端會主動發起重試,而在信令服務中沒有重試邏輯,因為如果加了重試邏輯,和客戶端的重試邏輯一起可能會引發錯誤請求的放大。也就是如果客戶端出錯重試之後這邊又重試,那後面的伺服器已經過載的情況下,大家都在重試反而會壓垮其他服務。所以整個重試鏈條完全是由客戶端主動出發的,信令請求服務中不會對錯誤請求進行重試。我們會採用一個簡單地容災邏輯,如果單臺機器出現連續10次的請求超時,就將這個機器標記為故障機,但後續會每秒放一個請求,觀察它是否能正常處理,因為後端服務的有些故障是可恢復的,可能只是因為臨時過載或是網路閃斷。每秒給故障機放一個請求後,如果連續3次請求成功,就將其標記為有效。這樣可以防止出現網路閃斷時所有服務都被標記為不可用,這會影響請求的成功率。

對這塊內容做一個小總結,整個信令伺服器是無狀態伺服器,因為它的設計要點是高效能,易處理,寫程式碼時要注意儘量隔離各個模組之間的故障,能快取的資料儘量快取,這樣就可以在外部系統出現故障時保證信令服務的正常,從而保證語音的基本服務功能。

3。2。轉發管理服務的設計要點

轉發管理服務的職責首先是負責處理客戶端上行的加入房間請求,再根據房間名分配不同的轉發叢集,這裡出現的問題是請求是有狀態的,比如來到A房間,要保證每次來A房間的都分配到同一個轉發叢集中,解決方法是在轉發管理伺服器做了Hash處理,在伺服器上分配了127個桶,每來一個請求就往桶裡放,每個桶裡都會掛一個轉發叢集,這樣就可以把請求按Hash規則均勻地分發到不同的轉發叢集。另外,在轉發管理服務上需要對轉發叢集進行存活探測和容災保護,在Hash的時候如果後面有一個叢集的服務全掛了是非常麻煩的,為此要做的有兩步,首先在轉發叢集內部做異地容災,每個轉發叢集內部有三臺信令接入器,每臺都會在轉發管理服務上進行註冊,這樣比如發現有一臺信令接入器掛了就可以迅速的切換到其他兩臺接入器上進行服務,確保服務過程中在轉發叢集的信令服務掛掉時仍然可以正常提供服務。

站在轉發管理服務的角度來說,它也是沒有狀態的服務,只不過有一個強制要求是保證所有轉發管理服務的配置是一致的,才能夠做好對後面轉發叢集的管理。

擴容的流程就是部署新的轉發管理叢集並做好撥測,測試好後修改信令接入服務配置,新增新的轉發管理服務,再繼續觀察新的叢集的流量是否正常即可。

縮容很簡單,在前端的信令請求服務中把想刪掉的機器在域名配置中摘掉,等待機器上流量掉0後再處理。

如果是轉發管理服務自身出問題,這是不影響的,因為所有轉發管理服務都是對等的,從信令接入服務角度來說只要按照自己的容災處理策略把出問題的轉發管理服務刪除即可。整個轉發管理叢集的容災都是透過信令接入服務來保證的。

3。3。轉發服務的設計要點

在整個語音通訊中,轉發叢集是佔用伺服器數量最大的叢集,大概佔據80%以上的機器,所以要對這些機器進行分組管理,目前設計的是每個叢集管理50臺機器,單個叢集支援50w的同時線上人數。

每一個集群裡面會部署三臺完全相同對等的叢集管理伺服器叫做RoomManager,它負責管理這個叢集中房間和機器的位置關係及機器上的IP。在部署的時候,一個叢集有三個RoomManager,也就是說三臺機器上的資訊完全一致。所有的VoiceServer一個集群裡大概有50臺語音伺服器都會和這些RoomManager定時同步狀態,可以理解為這整個叢集可以滿足遊戲高可用的要求。為了防止出現一個或三個RoomManager同時掛掉的情況。對外暴露的三個RoomManager是分開部署的,確保它們不在同一個機架上。首先保證它們的可用性,然後所有的VoiceServer都向這三臺RoomManager同步資訊,這個轉發叢集的可靠性就是有保障且符合設計要求的。

轉發叢集管理服務看每個叢集都是三臺RoomManager,所以就算叢集中有一兩臺RoomManager掛掉也不會影響整個業務服務。

每個叢集有3套完全相同的RoomManager,所以RoomManager出現故障不會影響整個遊戲語音服務。如果是VoiceServer出現故障,首先客戶端有重試機制,每次RoomManager會給客戶下發多個接入IP,客戶端在發現一個IP超時的時候會重試其他IP,就算整個叢集的VoiceServer全部重啟,我們還可以依賴客戶端的心跳來恢復整個房間的資訊,恢復之後所有資訊都會在RoomManager的記憶體重建,所以整個轉發叢集可以理解為不依賴資料庫,但是具有高可用性資訊恢復功能的叢集。

可以看到在VoiceServer出現故障時,客戶端可以透過重新加房間或者重試的方式來恢復。在RoomManager出現故障時,VoiceServer會定時上報每個人有幾個房間給RoomManager來解決問題。

04

系統迭代最佳化方案

前面僅僅是將GVoice的系統搭建起來,只是實現了一個能用的系統,在後續的開發過程中就需要進行迭代最佳化,這也是大家在業務過程中經常碰到的問題。通常會有這樣的大迴圈(右圖所示),業務上線之後會有統計監控、發現問題、制定方案、客戶端發版本、伺服器發版本,再看資料。整個GVoice上線之後就是不斷在這個迭代迴圈中跑。因為做監控或者一個專案肯定要提取三到五個關鍵指標,每天觀察指標是否出現問題,接下來就是根據監控資料分析問題,再決定要做的最佳化,然後在伺服器中開啟新特性進行驗證,最後在統計監控分析是否符合預期效果。

GVoice服務有四大重點觀察指標:

第一是PCU,在做專案或服務時,PCU可以反映當時有多少人在使用服務,同時它也是最容易反饋業務故障或是叢集故障的指標,某個地方出現故障時PCU肯定會掉下來。此外,還可以根據PCU來做擴縮容的決策,比如叢集容量使用率是否達到了擴容或縮絨的條件。最後GVoice的一個特徵是計費根據PCU來制定的,所以PCU是關鍵的觀察指標。

第二是丟包率,這是一個基礎的指標,直接衡量某個地區的網路質量好壞,這通常是受網路影響的,因為伺服器和客戶端版本穩定後,網路活動會引起丟包率的變化。

第三是延遲,從A說話到B聽見的整個過程其實包括很多段,GVoice會監控每一段的延遲包括資料處理延遲,甚至是採集播放的延遲,形成完整的延遲監控資料。

第四是卡頓,這也是大家經常遇到的問題。其實只要是軟體就會卡頓,但我們要想辦法監控卡頓並進一步分析其原因是網路傳輸引起還是程式BUG引起的,再根據原因做細分並解決。

GVoice最佳化過程中最典型案例的是語音傳輸丟包率的最佳化,這也是做傳輸的過程中大家比較關心的問題。站在應用層角度,除了對網路延遲做一些排程的處理,在演算法方面沒有較好的解決方法,但在丟包率方面卻可以做較多的最佳化。整個遊戲語音通話的卡頓是排除程式BUG之後由丟包率和延遲綜合影響的。

首先引入一個語音傳輸經常用到的演算法——FEC演算法。舉個例子,對1、2、3進行異或得出A,傳送第四個包的時候帶上A,如果傳輸過程中4號包丟了,在收到5、6、7時就可以把5、6、B三個數聯合起來算出4號包的內容,這就是大家常說的“3+1策略”。

實際應用中,不止可以實現“3+1”,因為根據RSCode演算法可以實現任意“M+N”的冗餘方式。理論上“M+N”取得越大,抗丟包越強,但要考慮的是延遲問題,幀間隔為40MS,恢復一個包至少要收到“M+N”個包之後才可以進行恢復,所以我們選擇“M+N”最大值為5,其次還要考慮頻寬利用率,最嚴重的突發情況是發1帶上2和3,發3帶上1和2,發4帶上2和3,這樣就是三倍冗餘策略,雖然對整個延遲和分佈演算法來說很簡單,但頻寬利用率不夠高。所以在考慮頻寬利用率的前提下,我們選擇的是“3+1”、“3+2”以及“2+2”三種冗餘方式。語音有一個好處是冗餘度大的時候可以降低位元速率,這就是語音相比遊戲中的傳輸更為靈活的地方。

FEC是被動恢復丟包的方式,那麼在做可靠傳輸時,更常見的其實是主動恢復丟包的方式——ARQ。比如發1、2、3,3在傳輸過程中丟了,那在收到4的時候馬上可以知道3丟了,就發一個請求3,再傳輸一次3,這類似於TCP中的ACK,細分下來還有ACK和NACK,所謂NACK是沒有收到的時候再請求,ACK是每次收到都會說明收到了。

在語音傳輸中,一般兩個人不會同時說話,所以使用更多的是ARQ,沒收到的時候再發起請求。透過分析ARQ的處理流程可以發現它請求重傳一個包需要再等一個RTT才能回來,也就是說有一個RTT的延遲,然後發現丟一個包至少要等一個幀間隔才可以知道,甚至丟包過多的時候要等好幾個幀間隔,可以理解為ARQ演算法對延遲敏感。舉個例子,發現丟包後請求一次ARQ,但此時網路延遲很大,需要等待1s才可以回來,這時ARQ就沒有意義了,因為語音可能已經播放到1s之後的位置了。

總結一下,要根據ARQ和FEC特徵進行選擇,FEC的優點在於只要收到“M+N”個包就可以恢復滿足條件的丟包內容。而ARQ對網路延遲敏感,如果網路延遲太大,收到丟掉的包後再發ARQ就沒有意義。

通常我們會混合使用FEC/ARQ,具體使用時根據客戶端當前丟包率和網路傳輸RTT進行選擇,方案有很多種,因為眼前至少有三個可調引數,首先是否用ARQ、選擇何等級的FEC、位元速率也可調。在低延遲低丟包率的情況下,偏向於使用ARQ,高延遲高丟包率的情況下,偏向於使用FEC,而且其級別隨著延遲增加而增加,FEC級別增加之後增大頻寬消耗,舉個例子“3+3”大概有兩倍冗餘,方法是加FEC冗餘的同時降低位元速率,這時使用者可以收到語音但是音質有一些細節的失幀,總體上是流暢的,聽語音和音樂的要求不同,語音的要求是在保證流暢的前提下再追求細節。

隨著業務不斷髮展變化,舉個例子使用者在玩和平精英的時候會發現他可以在多個房間裡通話,除了自身小隊的語音還可以聽到周圍其他使用者的聲音。最簡單的方法是一個客戶端進入兩個房間,說話可以同時被兩個房間聽到,同時也可以聽到兩個房間的聲音,這樣會出現的問題是客戶端在遊戲對局裡要和伺服器建立多條連線,導致客戶端狀態管理非常複雜。其次在伺服器看來相當於一位使用者在伺服器中有兩個例項,也是較複雜的情況。

在針對多房間的場景時,我們的實現最佳化方案是引入傳輸代理,客戶端和伺服器之間走傳輸代理,傳輸代理再和各個房間進行通訊。升級架構後,優勢在於首先可以實現玩家邏輯和資料的集中管理,從而簡化客戶端所做的邏輯。其次能夠聚合客戶端公網流量,包括在傳輸層的最佳化上,如果客戶端是單點接入,那我們可以做很多最佳化並且不引入額外的成本,客戶端和伺服器可以同時透過4G和WIFI進行通訊,可以確保語音傳輸在單挑鏈路出現故障和丟包時始終保持流暢。第三是能夠聚合客戶端的邏輯,因為每一個客戶端在伺服器上都有一個類似映象的例項,在做翻譯、反外掛或其他方案時就有非常好的接入點,因為可以直接找到另一個客戶端的位置,為訊息路由帶來很大的便利。

05

總結/展望

在服務海量使用者時,GVoice在全球的PCU是過千萬級別的,那麼如何低成本高效率地服務海量使用者呢?

首先要分模組,不能想著只做一個程式、一個架構或是隻依賴一個第三方的簡單模組就實現整個叢集,大系統一定要拆分為小模組並且每個子系統一定要分工明確,功能清晰。因為實現不同要求的程式對程式設計及開發技巧不同、對人員的要求也不相同,那麼將大系統拆分後每個人在自己擅長的領域工作就很容易提高開發效率。

第二是分層次,分完模組之後,每個模組該做的事情、如何做及每位員工擅長的模組也要細分清楚,這樣可以更容易且高效地搭建系統。

第三是分叢集,所有的雞蛋不要放在一個籃子裡,需要強調異地部署、叢集隔離、叢集之間要實現靈活排程,這也是做網際網路的基本思路,把全球各個IDC都部上伺服器,非必要不要融合通訊以保證單個叢集故障時不會擴張到所有伺服器。

最後是最佳化閉環(PCDA工作方法),搭起工作流後,大家要不斷地豐富監控資料、檢視監控資料、尋找問題、給出解決方案,不斷迭代。GVoice在服務海量使用者的本質是在進行資料驅動最佳化的過程,也就是搭建架子後,在資料上挖掘每個環節的問題比如某些服務成功率低、丟包率高,某個地區的玩家丟包率高等,再針對這些問題做最佳化,最後把資料分佈完全後會發現已經做好了全鏈路的監控系統,所有問題都可以透過檢視監控資料的方法進行定位。

以上是本次的分享,謝謝!

Q&A:

1.老師的方案更強調卡頓,在網路各種制約條件下優先保證的是流暢度,犧牲一部分延遲是嗎?

在語音通訊下,要看參照物,如果是和遊戲比,確實是要優先保證不卡頓,舉個例子:整個語音通訊延遲的容忍在300MS左右,但在遊戲對戰系統,只要延遲超過120MS,就會影響玩家體驗,所以需要優先保證流暢度,再保證延遲。

2.沒有用TRTC嗎?自建的?

沒有使用。因為我們需要在打遊戲的時候同時語音通訊,所以較大的挑戰是對頻寬的佔用有很高的要求,不能因為語音流暢導致遊戲卡頓,這樣也是不可取的。

3.冗餘大時會降低位元速率?這個不太懂,可否更進一步介紹下?

其實是丟包率高的時候會引起冗餘大,而冗餘大的同時每個包會變大,佔用的頻寬隨之增加,在網路傳輸中,本來網路就出現了卡頓,此時頻寬再增加就相當於在搶頻寬,這是不允許的,會嚴重影響使用者的體驗。所以我們選擇在冗餘增加的時候降低位元速率,這樣發出去的包不變,只不過從原來收到的包播放1s語音變成一個包播放2s語音,相當於優先保證流暢,雖然會導致一些高頻的細節聲音損失,但也是在追求流暢的過程中允許發生的。

4.根據不同的丟包率和rtt,採用重傳方案時,重傳的次數是如何定義的?根據rtt動態計算嗎?

兩位使用者聊天時,不存在一定要重傳完的概念。比如上一秒,使用者A說的話丟了,1s後再重傳回來,這句話已經沒有意義了,所以不存在一定可達的問題,這需要區別於遊戲傳輸。遊戲傳輸是嚴格的邏輯幀,少了一個輸入會影響表現,但是語音傳輸是“儘量可達”,不同於遊戲中丟失的包必須補回,否則遊戲邏輯會混亂導致兩個客戶端不同步。重傳次數可以理解為在有限的時間內儘量可達,比如超過1s丟了就算了。

5.傳輸協議採用的是rtp嗎?請問,應用層和傳輸層分別是什麼協議?

傳輸層用的協議是UDP,應用層用的是GVoice自定義的協議,可以認為是私有的協議。

6.能方便介紹一下,是如何監控語音質量的嗎?

可以理解為我們在接收端進行監控,因為接收端有抗抖動緩衝區,這裡會快取一部分包,語音播放時可以知道是否連續,比如發現要播放3號包時它還沒到,那麼就刷一次卡頓,這是監控卡頓的方法。監控丟包只需要有序列號、收發端就可以很快算出。

7.頻寬波動大的場景下,如何平衡遊戲自身和聲音佔用的頻寬?會優先保音訊嗎?

這種情況肯定是優先保證遊戲的頻寬。我們所做的是非必要不發包的網路處理,大家可以測一下整個GVoice其實在遊戲中佔用的頻寬非常低。

8.轉發服務有就近接入嗎?如果是大房間場景下,是不是需要在不同轉發叢集之間進行傳輸?

轉發服務是有就近接入的,在每一個省都有接入點,這些資料會就近接入。大房間場景下,我們不會做跨叢集傳輸,舉個例子來說在遊戲中的大房間不像廣播系統,遊戲房間規模可以認為不會超過100w,這就只需要一個叢集的機器就可以覆蓋。避免跨叢集傳輸也是為了整個部署隔離的需求。

9.連續丟包是如何處理的,使用類似於NetEQ的技術麼?

我們沒有使用NetEQ。對連續丟包的處理也是類似前面說的,有一個最高級別優先順序,因為語音傳輸的優點在於Best Effort,儘量做好就可以,實在丟了就是丟了,比如開到最高級別3+3、16K後還是丟包了,那就丟了,畢竟這種丟包的情況下可能遊戲也無法進行。

10.編碼位元速率配置有最佳實踐嗎?

根據目前對位元速率的測試所得,大量的普通使用者能夠聽出差別的時候位元速率大概在12k左右,遊戲場景下且位元速率大於12k時,一般使用者聽不出差別。我們儘量用32k的目的是使小孩的聲音聽起來更加豐富。

11.如何平衡語音音量和遊戲音效音量的關係?

這也是遊戲語音場景下的一個經典問題。在使用者玩遊戲時,要播放遊戲音效,同時會播放語音,我們平衡這兩者關係的方案是在遊戲的配合下,使用者收到語音後會降低一些遊戲音效。

12.面對面開黑時,經常有嘯叫產生,體驗比較差,這塊有什麼經驗嗎?如果要縮容的轉發服務的任務一直存在,怎麼辦?

嘯叫是一個長迴路反饋的問題。大家會經常對比影片會議系統和遊戲系統,疑惑為什麼前者不會嘯叫,但是遊戲就會。這是因為在玩遊戲的時候,使用者的手部可能會遮擋麥克風、揚聲器,導致嘯叫傳輸路徑不穩定,濾波器很難實時跟蹤變化,所以目前的解決方案是使用者面對面開黑的時候儘量關閉一個麥克風。

13.如果要縮容的轉發服務的任務一直存在,怎麼辦?

這個問題確實存在。使用者進入遊戲後大多數時間在對局中,而對局時長是有限的,比如一局2h。舉個例子,一個人一直不走,店鋪也不會一直不關門,類似的我們會給縮容一個時限,比如可以先觀察3天,3天后還有流量就可以停掉,這對使用者來說只是重新載入或是重新加入房間就可以恢復服務。

14.如果沒有跨叢集傳輸,那麼叢集會跨地域部署嗎?否則不同地域使用者怎麼保證就近接入?

這要分國內和國外各種場景進行討論,我們在部分割槽域做了跨叢集傳輸。實現方案是(倒數第三張ppt中的)傳輸代理層,一個叢集肯定有一個主要目標使用者區域,比如海外東南亞的使用者儘量接入新加坡,如果實在接入網路太差,我們會搭一個代理,從香港接入,然後代理到新加坡。

15.請問,多鏈路傳輸是指WIFI/4G同時使用嗎?它是怎麼決策用哪一個網路的呢?

兩個網路兩條鏈路是同時開著的,它們之間是主備的關係,通常認為優先走WIFI,但隨著4G包月流量越來越多,使用者對流量的使用不是很敏感,我們就會逐步放開在WIFI/4G同時傳輸。

16.請問GVoice有考慮WebRTC中的模組嗎?

這是完全不同的架構,我們在整個引擎裡已經把經典語音通話模組比如訊號處理相關的採集、3A處理等已經完全融合在一起了,這樣做的原因是做語音通訊的主件來說開啟手機的目的就是語音聊天,但是GVoice不同,此時開啟手機是為了打遊戲,聊天是附加功能,所以我們會把所有跟訊號處理相關的在WebRTC中分模組的東西融合在一起,儘量重用計算資源。

17.使用的是通用音訊處理器嗎,沒有用後處理,在極端情況下比如連續丟包,丟掉半秒語音後無法重傳,是否會在接收端做一些演算法,透過深度學習恢復語音?

這主要關係到CPU成本問題,我們雖然有這種演算法,但是會偏向於用在降噪及其他方面。對遊戲來說更敏感的是採集端的噪聲,比如使用者玩遊戲時手指敲擊螢幕的聲音,我們會集中算力做前處理而不太關注後處理。當然這是針對遊戲場景的特性說的操作。

18.Roommanager對於多個VoiceServer一個room只會分配到一個VoiceServer嗎?這裡有什麼分配策略麼?

儘量分配到一個。叢集內跨機轉發是允許的。