技術實戰 —— 快速實現語聊房搭建

點擊上方“LiveVideoStack”關注我們

語音相比文字圖片更豐富,比視頻又更簡便,是天然的社交工具。以95後爲代表的Z世代用戶,在微信、QQ、微博等主流社交工具以外,更願意嘗試基於不同興趣相對小衆的社交工具。ZEGO 即構科技推出語聊房解決方案,幫助客戶快速搭建語聊房。本次分享,我們邀請到了 即構科技交付解決方案專家 JIN 。他向我們分享了線上社交以及語聊房的發展、玩法,並詳細解析如何快速搭建語聊房,提供穩定、低延時,高品質的線上互動體驗。


文 | JIN
整理 | LiveVideoStack


大家晚上好,我是即構科技解決方案專家 JIN。很榮幸擔任語聊房專題的講師,今晚的主題就是語聊房的搭建,我將從三個方面爲大家講述,包括語聊房的衍生玩法及發展,ZEGO語聊房解決方案,以及語聊房搭建實戰。


01

語聊房的衍生與玩法

 

首先進入第一部分。

 

 

科技改變生活,在零幾年諾基亞盛行的時候,大家可以通過QQ發送文字消息進行聊天,隨着智能手機的興起,即時通訊、短視頻、視頻直播、語聊等逐漸火熱。而今晚的主題就是語聊。

 

 

近幾年語聊發展的相當火爆,尤其是年前的Clubhouse火了很長一段時間,具體火到什麼程度,可以看這個圖。這是2014-2019年中國語聊房行業用戶付費率及付費 ARPPU 值的統計圖,付費率指的是100人中,有多少人願意付費,ARPPU 值指的是付費的人中平均付費多少錢。在2014年,100人中只有3-4人願意付費,而這3-4人平均願意付費64.8元,到了2019年已經漲到了161.5元,付費意願人數比例也從之前的3-4人,提升到15人。

 

 

上圖數據是2021年8-10月Z世代用戶在語聊行業APP活躍度,左邊刻度單位爲“萬人”。Soul是在語聊行業中比較火的APP,紫色代表的是十月份的活躍度,大概有1993萬人在線,而8月有2375萬人在線,人數相當多,這只是一家APP的數據。

 

 

上圖展示的是今年8-10月Z世代用戶在語聊行業APP在線時長,數據來源於即構的後臺統計,Soul8月總計在線時長達到了1017336分鐘。從以上三組數據可以看出語聊房是非常火爆的,它爲泛娛樂行業APP帶來了很大的收益,同時也增加了用戶粘性及在線時長。

 

 

語聊房比較常見的形式是1V1聊天房,在一些相親類的APP裏,比較常見的是提供1V1的語聊服務,不過這些服務都是要付費的。

 


第二種形式是多人語聊房,這種玩法就比較多了,首先是多人純語聊,在線會議是比較常見場景,第二種是遊戲開黑,第三種是賽事直播,一起觀看賽事直播發表評論,第四種也是比較常見的一起看電影,第五種pia戲,就是多人根據劇本臺詞,進行實時的配音扮演,如右圖所示。

 

 

第三種形式是語音電臺,可以理解爲主播直播,用戶可以對他進行打賞。

 

 

第四種類型是KTV玩法,在語聊房技術上加上K歌,可以一邊唱歌一邊聊天進行打賞。


02

ZEGO語聊房解決方案

  

第二部分是即構提出的語聊房解決方案。

 


主要分爲三大模塊。

 

第一模塊多人語音聊天,我們在裏面做了四種適配,第一種是對接一線網絡運營商,節點資源豐富,無上限擴大容量。第二種是針對回聲消除進行的算法優化。第三種是降低端到端延遲,讓語聊更快。第四種是適應多種複雜網絡,同時兼容5000+安卓機型。

 

 

第二個模塊是音樂播放,單純語音聊天比較單調,通過播放背景音樂或者氣氛音效提升活躍度。我們支持播放MP3、MP4格式的背景音樂文件,支持播放器將播放的音頻混入推流中,同時支持音效播放器的音頻文件。

 

 

第三個模塊是支持APP後臺保持,切換到遊戲實現語音開黑。



以上就是即構解決方案的三大模塊,接下來介紹一下我們的優勢,首先是多人上麥語音聊天以及上下麥平滑切換,其次是支持實時變聲、立體聲、氣氛音效、混響等多種音頻效果,第三是支持彈幕、點贊、送禮等多種消息類型,第四是先進的不連續發射(DTX)和語音活動檢測(VAD)技術,這兩個技術會檢測當前用戶是否在說話,如果正在說話,我們將他的聲音錄製下來推出去,如果不說話的話,我們會發空白幀,減少用戶的流量。


 

除此之外,還有各種配套的功能支持,這裏有音效播放器、媒體播放器、混音、混響、聲浪與音頻頻譜、媒體次要信息以及房間信令。聲浪與音頻頻譜用來展示當前誰在說話與頻域分量信息,媒體次要信息屬於H.264中的一個類型,用來發用戶的自定義消息。


03

語聊房搭建實戰

  

第三部分進入我們的語聊房搭建實戰環節。



首先,介紹一下我們語聊房的技術架構。

 

語聊房把用戶分爲兩種,一種是麥上用戶,一種是麥下觀衆。對於麥上用戶來說,A連麥者需要推出自己了流A同時再去拉B的流,B連麥者也是一樣的,在上麥時將自己的流推出去,推到服務器,同時將A的流拉過來,這時A和B就可以進行語音互動。麥下的用戶只需要拉A和B的流收聽即可,麥下的觀衆可以有很多個。最右邊的混流服務可以根據業務需求自行選擇,有需要的可以用,沒有需要就可以不用,混流可以將A和B的流混成一條流,轉推到CDN,觀衆再從CDN拉流過來,這也是節省成本的一種方式。



接下來我們講一下語聊房的集成(代碼部分)。

 

由於本人是iOS開發,接下來會給各位展示下用OC實現語聊房集成。可以參考Zego音視頻集成文檔。 (https://doc-zh.zego.im/article/196)


- (void)createEngine{    //創建引擎    ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];    profile.appID = kKTVAppID;    profile.appSign = kKTVSign;    profile.scenario = ZegoScenarioGeneral;    self.engine = [ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];    //設置音頻編碼配置    ZegoAudioConfig *config = [[ZegoAudioConfig alloc] initWithPreset:ZegoAudioConfigPresetStandardQuality];    config.channel = ZegoAudioChannelMono;    config.codecID = ZegoAudioCodecIDLow3;    [self.engine setAudioConfig:config channel:ZegoPublishChannelMain];

//設置監聽聲浪回調 [self.engine startSoundLevelMonitor];}
首先需要創建引擎,創建引擎的時候需要設置一個配置,配置是ZegoEngineProfile對象,ZegoEngineProfile對象需要設置三個參數:appid、appsign、scenario。appid和appsign可以到zego官網申請獲得。


這裏需要重點講解下scenario這個參數。
/// Application scenario.typedef NS_ENUM(NSUInteger, ZegoScenario) { /// General scenario ZegoScenarioGeneral = 0, /// Communication scenario ZegoScenarioCommunication = 1, /// Live scenario ZegoScenarioLive = 2};


scenario這個參數是個枚舉值,通過SDK接口可以看到有三個值。第一個General適用在對音質要求高的場景,例如在線KTV。設置這個參數後SDK內部會關閉系統的回聲消除,我們也稱這個配置爲媒體音量模式。第二個和第三個枚舉值設置後SDK內部會開啓系統回聲消除,我們稱爲通話音量模式,通話音量的效果就跟使用手機的電話通話時候的效果一樣。因爲是使用回聲消除算法會對音質有損傷。一般使用在語聊場景,在線教育場景。語聊房場景下使用Communication即可。

 

接下來咱們設置下音頻編碼配置,先初始化一個ZegoAudioConfig對象,然後咱們設置下音頻碼率爲48k,使用默認參數即可。然後設置下聲道數爲Mono,在語聊場景下人聲使用雙聲道有點浪費帶寬,也不需雙聲道的效果。音頻編碼格式爲Low3,可以降低端到端延遲,提高用戶體驗。最後設置下音頻配置。

 

在語聊房中咱們通常會需要在麥位上展示聲浪,這裏需要打開聲浪的開關。聲浪分爲本端聲浪和遠端聲浪。這裏有兩個回調可以處理聲浪。這兩個回調接口中都帶有soundLevel參數,這個表示音量的大小,可以根據音量大小去決定是否展示聲浪。


//本端聲浪- (void)onCapturedSoundLevelUpdate:(NSNumber *)soundLevel {    if ([self.delegate respondsToSelector:@selector(onCapturedSoundLevelUpdate:)]) {        [self.delegate onCapturedSoundLevelUpdate:soundLevel];    }}//拉流聲浪- (void)onRemoteSoundLevelUpdate:(NSDictionary<NSString *,NSNumber *> *)soundLevels{    if ([self.delegate respondsToSelector:@selector(onRemoteSoundLevelUpdate:)]) {        [self.delegate onRemoteSoundLevelUpdate:soundLevels];    }}


接下來咱們需要登錄房間。登錄的時候需要設置用戶信息,用戶信息包括用戶ID和用戶暱稱,以及房間號。


- (void)joinRoomWithRoomID:(NSString *)roomID userID:(NSString *)userID userName:(NSString *)userName{ ZegoUser *user = [ZegoUser userWithUserID:userID userName:userName]; ZegoRoomConfig *config = [ZegoRoomConfig defaultConfig]; self.roomID = roomID; [[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:config];}

 

登錄房間後怎麼判斷登錄成功了?咱們需要在房間的回調中進行處理,這個回調是onRoomStateUpdate。這個回調中有個房間狀態枚舉值,對應有三種狀態。

 

/// Room state.typedef NS_ENUM(NSUInteger, ZegoRoomState) {    /// Unconnected state, enter this state before logging in and after exiting the room. If there is a steady state abnormality in the process of logging in to the room, such as AppID and AppSign are incorrect, or if the same user name is logged in elsewhere and the local end is KickOut, it will enter this state.    ZegoRoomStateDisconnected = 0,    /// The state that the connection is being requested. It will enter this state after successful execution login room function. The display of the UI is usually performed using this state. If the connection is interrupted due to poor network quality, the SDK will perform an internal retry and will return to the requesting connection status.    ZegoRoomStateConnecting = 1,    /// The status that is successfully connected. Entering this status indicates that the login to the room has been successful. The user can receive the callback notification of the user and the stream information in the room.    ZegoRoomStateConnected = 2};

咱們首先需要判斷狀態是否爲ZegoRoomStateConnected,然後在判斷errorCode是否爲0。這個時候就是登錄房間成功。如果狀態爲ZegoRoomStateConnected但是errorCode不爲0,表示當前情況爲掉線重連成功,errorCode會顯示對應的錯誤碼。退出房間也是一樣的道理。


 - (void)onRoomStateUpdate:(ZegoRoomState)state errorCode:(int)errorCode extendedData:(nullable NSDictionary *)extendedData roomID:(NSString *)roomID {    if (state == ZegoRoomStateConnected && errorCode == 0) {        if ([self.delegate respondsToSelector:@selector(onLoginRoom)]) {            [self.delegate onLoginRoom];        }    }}

 

登錄房間成功後需要拉流。一般情況下,在語聊房中除了房主其他用戶登錄房間後都不會馬上推流,需要上麥後才推流。房主登錄房間後可以在onRoomStateUpdate回調中推流,推流接口爲:

 

- (void)startPublishingStream:(NSString *)streamID channel:(ZegoPublishChannel)channel;


拉流接口爲:

 

- (void)startPlayingStream:(NSString *)streamID canvas:(nullable ZegoCanvas *)canvas;


這裏需要說明下,ZegoSDK是按照流去設計接口。其他市面上的廠商的SDK設計理念不一樣,他們是按照角色和場景SDK內部就實現了推拉流。使用ZegoSDK需要手動去推拉流。推流時機一般是在登錄成功後或者上麥成功後。拉流時機爲登錄房間後且在流變更回調中去拉流。

流變更回調爲onRoomStreamUpdate。

 

這個回調用有ZegoUpdateType枚舉值,對應的是流新增和流刪除。當某個用戶推流成功後房間內其他人會觸發流新增回調,當某個用戶停止推流後房間內其他人會收到流刪除回調。所以在這裏實現拉流的操作即可。


 - (void)onRoomStreamUpdate:(ZegoUpdateType)updateType streamList:(NSArray<ZegoStream *> *)streamList extendedData:(nullable NSDictionary *)extendedData roomID:(NSString *)roomID {    if (updateType == ZegoUpdateTypeAdd) {        for (ZegoStream *stream in streamList) {            [self.playStreams addObject:stream.streamID];            [self.engine startPlayingStream:stream.streamID canvas:nil];            if ([self.delegate respondsToSelector:@selector(onUserJoinedWithStreamdID:)]) {                [self.delegate onUserJoinedWithStreamdID:stream.streamID];            }        }    }else if(updateType == ZegoUpdateTypeDelete){        for (ZegoStream *stream in streamList) {            [self.playStreams removeObject:stream.streamID];            [self.engine stopPlayingStream:stream.streamID];            if ([self.delegate respondsToSelector:@selector(onUserLeavedWithStreamdID:)]) {                [self.delegate onUserLeavedWithStreamdID:stream.streamID];            }        }    }}

 

以上的功能實現集合在一起就可以搭建一個簡單的語聊房。

 

如果需要播放音樂可以使用媒體播放,接下來咱們創建個媒體播放器。

self.mediaPlayer = [[ZegoExpressEngine sharedEngine] createMediaPlayer];

播放之前需要先加載資源,使用loadResource接口,然後調用start播放即可。

 

以上就是集成語聊房需要用到的基本接口,其他接口可以參考文檔集成。

 

4、常見問題 —— 幽靈麥

 

接下來咱們聊一聊語聊房場景的問題。

 


比較常見的坑就是幽靈麥的問題,用戶已經不在麥上了,但還能聽到他的聲音。

 

針對幽靈麥問題,我們提出了三種解決方案:

 

  1. 使用Token鑑權:即用戶在登入房間時,對其身份進行校驗,如果校驗不成功,則不允許其進入房間。


  2. 流ID不和用戶ID綁定:我們常遇到的場景是會將流ID和用戶ID進行綁定,使用用戶的ID當做流ID進行推拉流。比如一個用戶登錄房間A進行聊天,此時直接關閉APP,立即重新登錄房間B,並上麥推拉流。由於流ID和用戶ID是一樣的,我們很難發現用戶是什麼時候掉線的,並且在用戶掉線時,會自動嘗試重連,重連有90s的時間,如果在這個時間內產生了上述操作,那麼,在之前房間A拉的流沒有停止,所以還是能聽到他的聲音。如果使用流ID不和用戶ID綁定的方案,每次登錄房間後推流的ID不一樣,即使上一次的流鏈接還存在,但是沒有數據,也就不會出現幽靈麥的問題。


  3. 麥位管理配合流變更通知:常用的麥位管理會建議用戶使用第三方麥位管理,同時爲避免不穩定,可以配合流新增的回調做處理。如果我們這裏流觸發新增了,再更新UI顯示在麥上,如果流沒有新增,即使第三方麥位管理顯示已經成功上麥,說話也不成功,刪除下麥也是同理。

 

以上是本次分享的全部內容,謝謝大家。



 

Q&A


1. 在多人通話連接或聊天當中,環境不同造成的大量回聲和噪聲嘈雜,有哪些處理保證通話質量?

我們會在創建引擎時,會選擇Communication場景模式,在此模式下,使用系統回聲消除會把回聲消除掉以及壓制噪音。這就像我們平時用手機系統打電話時的效果,已經把它消得特別乾淨了。如果你一定要使用General場景的話,我們也提供額外的軟件AEC進行處理,同時對噪聲也有個ANS,可以將它們打開。

 

2. 是用WebRTC嗎?

目前沒有使用WebRTC,用的是原生語言開發的,底層是C++。

 

3. 語聊房目前的應用場景多嗎?和實際應用的狀況怎麼樣?

語聊房目前應用場景是挺多的,之前也提到過1V1聊天房、多人語聊房、語音電臺、KTV語聊房,目前用的比較多的是多人語聊房,它在不同的APP都有非常多衍生玩法。

 

4. 本次演講沒有提到錄音功能,有很多人希望聊天能夠被記錄下來,後期剪輯變成更適合輸出和保存的內容,想問下有沒有類似的功能?

語聊房是有錄製功能的,可以在本地保留數據,再進行本地錄製,也可以在遠端進行錄製,主要看實際的場景需求。具體可以在即構官網開發者中心下的文檔中心中搜索,會有詳細的介紹。開發者文檔中心鏈接:https://doc-zh.zego.im



視頻回放地址:
https://sh2022.livevideostack.cn/live/4963




講師招募

LiveVideoStackCon 2022 音視頻技術大會 上海站,正在面向社會公開招募講師,無論你所處的公司大小,title高低,老鳥還是菜鳥,只要你的內容對技術人有幫助,其他都是次要的。歡迎通過 [email protected] 提交個人資料及議題描述,我們將會在24小時內給予反饋。

喜歡我們的內容就點個“在看”吧!


本文分享自微信公衆號 - LiveVideoStack(livevideostack)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章