架構解讀:如何快速搭建直播答題方案

騰訊雲加社群發表於2018-01-26

歡迎大家前往雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

作者:騰訊視訊雲
原標題:騰訊雲直播答題方案解析
原文連結:https://cloud.tencent.com/developer/article/1027814

進入2018年最火的新鮮事物無疑就是“直播答題”了,動輒上百萬的獎金更是吸引了大量使用者的參與。一場直播動輒幾百萬的獎金,每人可以分到幾十元甚至百萬元,越來越多的人希望用自己的智商賺點零花錢。直播答題有多火?

多個直播平臺伺服器多次遭到當機。實際上,幾百萬人一同線上的確會帶給伺服器不小的壓力,更何況是新上線的 App,他們或許還沒有想到自己會火得這麼快。

近期很多直播答題app出現了個人賬號上顯示獎金額數不對、線上使用者無法進入遊戲、技術故障導致遊戲被迫臨時取消等情況。某視訊平臺還出現了系統將正確答案判定錯誤的情況,導致百萬人被迫中止遊戲。隔幾天就來一次的

技術故障除了說明App準備不足外,也從側面反映了這類遊戲的火爆程度。為了讓眾多公司及開發者儘快趕上風口浪尖,騰訊雲直播團隊也是第一時間上線了“線上知識競技解決方案”。

效果體驗

iOS平臺(ipa)   Android平臺(apk)     Obs Studio 定製版本(exe)

iOS                      Android                    Obs Studio

線上答題室

建立房間即可體驗主持人一端的效果;進入房間即可體驗觀眾端的效果。

iPhone 平臺的 Demo 採用了企業簽名方式,請先到“設定-通用-裝置管理”裡,新增信任證照。

Demo 僅作演示之用,用來展示騰訊雲的技術能力,不代表最終產品形態,接入請仔細閱讀文件。

體驗方式二:Obs Studio

這種體驗方式是直接使用騰訊雲改造的 Obs Studio 進行推流:

  • 我們在“工具”選單欄裡面加了一個“題目分發”按鈕,可以直接將題目打入直播流中,題目可以預先編輯在 ini 檔案中。
  • 使用 iOS 或者 Android 端的演示App,選擇 答題播放器 功能,就可以體驗觀眾端題目收取的效果了。
  • OBS Studio 的使用方法,詳見 騰訊雲定製版 Obs Studio 說明書

SDK下載

下載地址

我們的優勢

  • 精準的“音題畫”同步 騰訊雲 SDK 和雲端均支援在直播流中插入 題目 或 時間同步信令,可以實現聲音、畫面和題目彈出的完美同步。
  • 超低的觀眾端延遲差 騰訊雲 SDK 的 極速播放模式 所支援的延遲修正技術,可以讓觀眾與觀眾之間延遲差在 1s 以內,從而讓觀眾的答題同步性得到保證。
  • 支援微信小程式接入 騰訊雲 SDK 已經預設打包在微信版本中,並以 <live-player> 標籤的形式對外提供,設定 mode 為 live 模式,並將 min-cache 和 max-cache 都設定為 1,即可實現非常低延遲的播放效果。

方案解讀

方案一:題目透傳方案

原理描述

  • 訊息傳送(Obs): 如果你是在演播室使用 Obs 推流,可以直接使用騰訊雲改造的 Obs Studio 替換現有的 Obs 軟體,-我們在“工具”選單欄裡面加了一個“題目分發”按鈕,可以直接將題目打入直播流中,題目可以預先編輯在 ini 檔案中。
  • 訊息傳送(APP): 如果想要簡單使用 App 進行推流,可以使用騰訊雲終端 SDK 的 TXLivePusher 的 sendMessage 方法,該方案能將一段buffer塞到RTMP流中(buffer的最大長度限制為10K)。

    // iOS 示例程式碼 [_answerPusher sendMessage:[mesg dataUsingEncoding:NSUTF8StringEncoding]];

    //Android 示例程式碼 mTXLivePusher.sendMessage(questionInfo.getBytes("UTF-8"));

  • 訊息接收: 利用騰訊雲 SDK 的 TXLivePlayer 的 onPlayEvent(PLAY_EVT_GET_MESSAGE : 2012)功能,可以在播放器播放到指定畫面的時候,同步地將 MESSAGE 通知給您的 APP,從而將題目同步地擴散到海量的觀眾端。

訊息接收的接入方案可以參考我們的接入文件(iOS平臺 | Android平臺

方案二:NTP時間同步方案

原理描述

  1. 騰訊雲會每隔 1s 在您的直播流中實時插入經過 NTP 校時的國際標準時間戳。
  2. 演播室的導播員根據主持人的出題節奏,在合適的時間控制發題,發題系統會在每次下發的題目中帶上當時的國際標準時間。
  3. SDK 在播放這種打入時間戳的視訊流的時候,會定時通知您的 APP 當前 SDK 所播放的畫面是在什麼時間錄製下來的(因為導播臺到雲端一般都會有一個固定的延遲,需要您提前做一個誤差校調)。
  4. 您的 APP 可以根據 SDK 的時間通知(即當前畫面是什麼時間錄製的),按需顯示指定的題目即可。

總結一下:方案二跟方案一的最大區別就在於題目的擴散方式,方案二的核心思路是讓題目先通過IM通道快速送達觀眾端APP,在觀眾端先快取下來,等待播放器通知了預期的 NTP 時間戳之後,再把題目顯示出來。

方案三:小程式解決方案

方案一和方案二都可以實現“音-話-題”完美同步,但是相比於這種體驗上的小優化,APP的擴散能力則是更加重要的。小程式恰恰提供給了APP可以病毒式傳播的能力。

騰訊雲 SDK 已經預設打包在微信版本中,並以 <live-player> 標籤的形式對外提供,如果:

  • 採用 flv 播放地址
  • 設定 mode 為 live 模式
  • 將 min-cache 和 max-cache 都設定為 1
  • 推流端 gop 為 1

那麼就可以實現非常理想的播放延遲,並能夠讓各觀眾之間的延遲誤差在1s以內,雖然無法做到“音-話-題”分毫不差(在微信打包視訊雲 SDK 的時候,線上答題模式還沒有興起,所以不支援在音視訊流中夾帶訊息),但也能達到幾乎可以媲美APP的體驗效果。

剩下的事情,就是通過小程式的 websocket 通道,或者我們的 webim 解決方案,向小程式發題就可以了。

接入攻略(方案一)

步驟一:開通騰訊雲直播服務

聯絡我們開通騰訊雲 直播服務,如果您時間比較急,可以 400 客服電話我們加急稽核。

步驟二:獲取推流URL

想要簡單獲取一個推流URL,可以參考文件:快速獲得地址

想要了解推流地址和直播間ID的關係,可以參考文件:後臺自動拼裝

想要了解如果保護自己的推流地址不被盜用,可以參考文件:防盜鏈簽名

步驟三:獲取播放URL

播放URL和推流URL是一一對映的,對映規則參考下面的圖示您就能明白了:

請務必使用 FLV 格式的播放地址,RTMP在高併發場景下容易出現卡頓問題。

步驟四:配置推流端

如果您是使用APP進行推流,直接參考文件(iOS | Android)。

如果您是使用Obs推流,請注意以下幾個重要設定項:

I幀間隔(GOP)

一般演播臺的接入方式有兩種:OBS Studio 推流或者編碼盒推流,這兩種推流工具均有比較成熟的設定介面。建議將 GOP(也叫關鍵幀間隔)設定為 1s ,這樣可以讓觀眾端的延遲差異非常小。

x264 的 gop 設定對編碼效率的影響不是很大,但對延遲的影響非常大:gop越大,伺服器快取越多。由於 SDK 的延遲修正需要一個修正時間,如果 gop 太大,對於剛進入的觀眾會有很大的影響 。

如下是Obs Studio 設定關鍵幀間隔的圖示:

編碼引數

推薦推薦配置

解析度

視訊位元率

幀率

聲道數

取樣率

音訊位元率

優先畫質

540x960

1000kbps

25

1

48k

72kbps

優先成本

360x640

600kbps

20

1

48k

72kbps

步驟五:對接播放器

  1. 下載文件第二部分中列出的 SDK 版本。
  2. 參考接入文件(iOS | Android)完成播放器的接入。兩個平臺全部完成,大概需要 0.5 天的工作量。
  3. 修改預設配置 由於 SDK 的預設配置為普通直播場景,所以需要修改配置,操作方法如下:
//iOS原始碼
TXLivePlayConfig *config = [[TXLivePlayConfig alloc] init];
TXLivePlayer *player = [[TXLivePlayer alloc] init];
//
//開啟訊息接受,收不到訊息的話就是沒開啟這個(預設:關)
config.enableMessage = YES;
//
//設定延遲平衡點為1s(考慮到雲端和推流端引入的延遲,實際延遲為2s多,SDK推流:2s, obs推流:3-4秒)
config.bAutoAdjustCacheTime = YES;
config.maxAutoAdjustCacheTime = 1;
config.minAutoAdjustCacheTime = 1;
config.cacheTime = 1;
config.connectRetryCount = 3;
config.connectRetryInterval = 3;
config.enableAEC = NO;
//先setConfig再startPlay
[player setConfig:config];
//Android原始碼
mTXLivePlayConfig = new TXLivePlayConfig();
mTXLivePlayer = new TXLivePlayer(context);
//
//開啟訊息接收,收不到訊息的話就是沒開啟這個(預設:關)
mTXLivePlayConfig.setEnableMessage(true);
//
//設定延遲平衡點為1s(考慮到雲端和推流端引入的延遲,實際延遲為2s多,SDK推流:2s, obs推流:3-4秒)
mTXLivePlayConfig.setAutoAdjustCacheTime(true);
mTXLivePlayConfig.setCacheTime(1.0f);
mTXLivePlayConfig.setMaxAutoAdjustCacheTime(1.0f);
mTXLivePlayConfig.setMinAutoAdjustCacheTime(1.0f);
//
//先setConfig再startPlay
mTXLivePlayer.setConfig(mTXLivePlayConfig);

複製程式碼
請務必使用 FLV 格式的播放地址,RTMP在高併發場景下容易出現卡頓問題。複製程式碼

步驟六:題目的擴散

  • 如果是用APP發題,只需要參考TXLivePusher 的 sendMessage 呼叫方法即可,參考文件見 (iOS | Android)。
  • 如果是用我們的定製版 Obs Studio 發題,那麼可以使用本地ini檔案提前把題目編輯好,由導播員在合適的時間將題目擴散出去。

可靠性評估

有些客戶可能會擔心:音視訊通道本身不太穩定,那麼萬一卡了或者視訊資料丟了,會不會導致觀眾端看不到題目。

  • 首先,直播的音視訊資料丟幀是確定的按照gop為單位來丟幀的,如果gop=1,那麼每次會丟1s的音視訊資料
  • 其次,以騰訊雲目前的節點部署情況來看,視訊卡頓90%以上都以由於觀眾端網速不足引起的,這種情況下其他的網路通訊也不會很流暢。

所以,解決這個問題的辦法就是每秒鐘(gop設定為1s的前提條件下)傳送一次題目訊息,相同的題號在觀眾端剔重,這樣就可以避免偶爾的音視訊卡頓對題目到達可靠性的影響。

步驟七:接收題目訊息

在我們的推流 APP Demo 以及定製版本的 Obs Studio 中,我們以 json 格式將題目組織成一段buffer,並將其夾在音視訊流中送出去。

獲取到這個buffer以後,您就可以將其解析出來,並完成對應的 UI 展示。如果你需要調整json格式以支援更多的定製型,請修改原始碼或者聯絡我們。

  • TXLivePlayConfig 中的 enableMessage 開關置為 YES。
  • TXLivePlayer 通過 TXLivePlayListener 監聽訊息,訊息編號:PLAY_EVT_GET_MESSAGE (2012)
// iOS 的程式碼
 -(void) onPlayEvent:(int)EvtID withParam:(NSDictionary *)param {
    [self asyncRun:^{
        if (EvtID == PLAY_EVT_GET_MESSAGE) {
            dispatch_async(dispatch_get_main_queue(), ^{ //拋到主執行緒,避免執行緒安全問題
                if ([_delegate respondsToSelector:@selector(onPlayerMessage:)]) {
                    [_delegate onPlayerMessage:param[@"EVT_GET_MSG"]];
                }
            });
        }
    }];
}
//Android 示例程式碼
mTXLivePlayer.setPlayListener(new ITXLivePlayListener() {
        @Override
        public void onPlayEvent(int event, Bundle param) {
            if (event == TXLiveConstants.PLAY_ERR_NET_DISCONNECT) {
                roomListenerCallback.onDebugLog("[AnswerRoom] 拉流失敗:網路斷開");
                roomListenerCallback.onError(-1, "網路斷開,拉流失敗");
            }
            else if (event == TXLiveConstants.PLAY_EVT_GET_MESSAGE) {
                String msg = null;
                try {
                    msg = new String(param.getByteArray(TXLiveConstants.EVT_GET_MSG), "UTF-8");
                    roomListenerCallback.onRecvAnswerMsg(msg);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        }
});複製程式碼

步驟八:開發答題系統

由於騰訊雲 PAAS 的定位,所以對於跟客戶業務繫結比較緊密的答題和支付系統,我們就不涉及了,需要您來參與開發。

這裡普遍採用的方案是:將客戶的答案以 HTTP(S) 請求方式彙總到答題伺服器上,只是實現過程需要注意解決瞬間高併發的請求壓力。

有客戶可能會問,IM系統是否適合用來答題,這裡目前看是不適合的,因為 IM 系統的主要能力在於訊息擴散,而答題的主要目標是資訊的收攏。

步驟九:答題結果顯示

一般題目出來一段時間以後,就會進入閉題狀態。這時,答題系統會將結果進行統計彙總,並將彙總結果下發給觀眾。

如果是用我們的定製版 Obs Studio 擴散結果,搭一個簡單的伺服器,並提供一個 http 介面,然後按照我們約定的 json 格式跟 Obs 進行題目、答案以及人數的通訊。從而實現題目和答案的分發。

這部分協議由於文件篇幅限制,暫時不放在文件中詳細列出,有需要的話請 400 電話聯絡我們。

接入攻略(方案二)

步驟一:開通騰訊雲直播服務

同方案一,不再贅述。

步驟二:獲取推流URL & 加註NTP時間戳

參考方案一,與方案一不同之處在於,推流 URL 需要額外增加一個引數:

加註NTP時間戳

在推流 URL 之後新增引數 &txAddTimestamp=1 引數,伺服器會每隔1s向您的直播流中打入一個帶有國際標準時間(誤差在 100ms 以內)的 SEI 時間戳。如果您使用我們的播放器播放此視訊流,那麼就會每隔一秒鐘收到一次代表當前畫面 NTP 時間的訊息通知。

步驟三:獲取播放URL

同方案一,不再贅述。

步驟四:配置推流端

同方案一,不再贅述。

步驟五:對接播放器

參考方案一,與方案一不同之處在於,此時獲取的message不再是一個json,而是一個8位元組的64位時間戳。

long timeStamp = byteArrayToInt(param.getByteArray(TXLiveConstants.EVT_GET_MSG));
/**
* 將8位元組的byte陣列轉成一個long值
*/
public static long byteArrayToInt(byte[] byteArray) {
        byte[] a = new byte[8];
        int i = a.length - 1, j = byteArray.length - 1;
        for (; i >= 0; i--, j--) {// 從b的尾部(即int值的低位)開始copy資料
            if (j >= 0)
                a[i] = byteArray[j];
            else
                a[i] = 0;// 如果b.length不足4,則將高位補0
        }
        // 注意此處和byte陣列轉換成int的區別在於,下面的轉換中要將先將陣列中的元素轉換成long型再做移位操作,
        // 若直接做位移操作將得不到正確結果,因為Java預設運算元字時,若不加宣告會將數字作為int型來對待,此處必須注意。
        long v0 = (long) (a[0] & 0xff) << 56;// &0xff將byte值無差異轉成int,避免Java自動型別提升後,會保留高位的符號位
        long v1 = (long) (a[1] & 0xff) << 48;
        long v2 = (long) (a[2] & 0xff) << 40;
        long v3 = (long) (a[3] & 0xff) << 32;
        long v4 = (long) (a[4] & 0xff) << 24;
        long v5 = (long) (a[5] & 0xff) << 16;
        long v6 = (long) (a[6] & 0xff) << 8;
        long v7 = (long) (a[7] & 0xff);
        return v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7;
}複製程式碼

步驟六:題目的擴散

如果您使用自己的 IM 系統下發題目,請忽略這一部分,如果想使用騰訊雲 IM 服務發題,請對接如下幾步:

  • 1. 開通 IM 通訊服務:開通騰訊雲 雲通訊 服務。
  • 2. 配置 IM 通訊服務 :按照文件進行初始化配置,整合模式請務必選擇獨立模式。
  • 3. 使用 REST API 建立一個 BChatRoom 用於發題 :騰訊雲 IM 的 REST API 是專門提供給服務端接入用的,建立群組這個操作一般是由您的伺服器觸發,所以適合使用 REST API 方案進行接入。 BChatRoom 這種房間型別非常適合用來發題,因為它原本就是用來做系統通知的,所以訊息達到率高,訊息可靠性好。 建立群組使用 v4/group_open_http_svc/create_group 可以實現,測試方法見 SDK 開發包中的 IM後臺RESTAPI使用方法.pdf - 步驟3。
  • 4. 使用 REST API 建立一個 AVChatRoom 用於彈幕 :AVChatRoom 這種房間型別非常適合用來做聊天室的彈幕訊息,有比較嚴格的髒字過濾和頻率限制邏輯,專門為大型聊天室場景優化。 建立群組使用 v4/group_open_http_svc/create_group 可以實現,測試方法見 SDK 開發包中的 IM後臺RESTAPI使用方法.pdf - 步驟4。 AVChatroom中預設的頻率控制為40條/s,如果您需要調整限制請聯絡我們,因為訊息越多,頻寬費用越高。
  • 5. 使用 REST API 在 BChatRoom 裡傳送題目廣播: 訊息傳送可以使用 v4/group_open_http_svc/send_group_msg ,測試方法見 SDK 開發包中的 IM後臺RESTAPI使用方法.pdf - 步驟5。
  • 6. 如何實時獲取線上人數? :REST API 是不支援客戶端呼叫的,但即使是後臺呼叫,騰訊雲 REST API 的 後臺呼叫頻率限制也只有 100次/秒, 所以要實時獲取線上人數,就需要先由您的業務後臺通過 REST API 低頻的獲取到線上人數,再通過訊息通道,將線上人數下發到客戶端。 1)由您的業務伺服器通過 v4/group_open_http_svc/get_group_info 獲得群的線上人數。

2)之後,由您的業務伺服器通過 v4/group_open_http_svc/send_group_msg,定時將人數通過群訊息下發給客戶端。定時頻率可以設定為 3-5s 一次。

特別注意:REST API 不支援客戶端呼叫,在客戶端呼叫會引發私鑰洩漏,進而導致您的雲服務被惡意盜用和扣費。

步驟七:題目接收&彈幕收發

客戶端使用 IM SDK 完成訊息的接收和彈幕訊息的收發,這裡可以按照如下步驟對接

  • 1. 整合精簡版 IMSDK 精簡版:IMSDK 位於 SDK開發包 中,zip包中的 “接入指引-IMSDK.pdf” 有詳細的說明。
  • 2.參考原始碼完成對接: SDK開發包中有一個叫做 AnswerPlayIMCenter 的原始碼檔案,封裝了對於 IMSDK 的簡單呼叫,相當於對於接入文件的程式碼示例,您可以參考一下,如下是這個類的成員函式說明:

成員函式

作用

initIMCenter

用於初始化,需要填寫您在騰訊雲的 IM 服務相關資訊。

loginIMUser

用於登入,您可以把 imSDK 當成一個無介面的 QQ,用QQ收發訊息肯定要登入,只是將QQ號和登入密碼換成了您的Userid和您的伺服器簽發的UserSig。

joinIMGroup

用於加入在步驟六中由您的後臺伺服器通過 REST API 建立的 BChatRoom 和 AVChatRoom。

sendChatMessage

用來傳送彈幕評論訊息

onRecvChatMessage

用於接收來自 AVChatRoom 的彈幕訊息,注意做一下渲染的頻率限制,不要收到一條彈幕訊息就刷一次螢幕,手機效能可能扛不住,很多客戶都是在這裡疏忽了,因為測試期間訊息不多不易發現。

onRecvIssueMessage

用於接收來自 BChatRoom 的題目訊息,按照方案二的設計,每道題目裡面都有它應該顯示的 NTP 國際時間。

  • 3. 題目的顯示時機 :onRecvIssueMessage 收到的題目不要立刻顯示出來,要等來自播放器的 GET_MESSAGE 回撥,如果回撥的時間 >= 題目的 NTP 國際時間,就可以把指定的題目顯示出來了。
  • 4. UserSig怎麼算? :UserSig 是 loginIMUser 時需要的一個重要資訊,它的作用等同於登入QQ用的密碼,只是這個密碼由您的伺服器為您的使用者簽發;這裡使用的密碼簽發方案是基於 RSA 非對稱加密實現的,所以安全性非常高。 UserSig 一般是由您的伺服器簽發的,簽發方案可以參考: TLS 後臺 API ,不過為了能夠能讓您快速除錯起來,我們也提供了一個Windows 的簽發小工具,您可以在後臺同學加入專案之前就可以先把終端邏輯除錯起來。 注意:小工具裡的公私鑰都只能用來測試,您可千萬別當真的用了。

步驟八:開發答題系統

由於騰訊雲 PAAS 的定位,所以對於跟客戶業務繫結比較緊密的答題和支付系統,我們就不涉及了,需要您來參與開發。

這裡普遍採用的方案是:將客戶的答案以 HTTP(S) 請求方式彙總到答題伺服器上,只是實現過程需要注意解決瞬間高併發的請求壓力。

有客戶可能會問,IM系統是否適合用來答題,這裡目前看是不適合的,因為 IM 系統的主要能力在於訊息擴散,而答題的主要目標是資訊的收攏。

步驟九:答題結果顯示

一般題目出來一段時間以後,就會進入閉題狀態。這時,答題系統會將結果進行統計彙總,並將彙總結果下發給觀眾,結果下發可以繼續使用 步驟六 中的題目擴散通道。

相關閱讀

此文已由作者授權雲加社群釋出,轉載請註明原文出處



相關文章