1. 程式人生 > >如何打造一款直播App(方法流程)

如何打造一款直播App(方法流程)

概要

分享內容

網際網路內容載體變遷歷程,文字——圖片/聲音——視訊——VR/AR——…….。從直播1.0秀場時代(YY),2.0遊戲直播(鬥魚、虎牙、熊貓)到如今全民直播3.0泛生活娛樂時代(映客、花椒),國外直播app(Meerkat 、Periscope),隨著VA/AR/MR提出的沉浸式視聽體驗,直播4.0時代很快就能到來。

在這個全民娛樂的時代,直播已經火得不要不要的,各大公司都有自己的直播產品。本文主要從直播的一些基本知識,一步步打造直播app。直播那麼火的背後有什麼樣的技術支撐呢?

先將這些APP按照視訊網站按照視訊網站、彈幕視訊、直播平臺、線上秀場、移動短視訊、移動直播來劃分類別。再按照內容和社交這個維度來進行區分,可以明顯看出視訊網站、彈幕網站和直播平臺更偏內容,他們對內容的需求更加高,使用者在上面進行社交沉澱相對比較淺。

而後面三者更加偏向社交,他們強調人而不強調內容。所以短期內不會有大的競爭關係,只是前三類、後三者之間的競爭會出現。

大體框架 
基本是下圖這個套路:

錄製->編碼->網路傳輸->解碼->播放

以上為直播的整體流程,根據該流程分為以下技術點:

  1. 怎樣錄製直播視訊
  2. 怎樣實時上傳直播視訊
  3. 怎樣播放直播視訊
  4. 直播間的使用者是如何互動

一、移動視訊直播發展

PC直播(固定場所)——>移動端(形式自由)。

隨著越來越多的直播類 App 上線,移動直播進入了前所未有的爆發階段,目前大多數移動直播以 Native 客戶端為主。但是H5端的直播在移動直播端也承載著不可替代的作用,例如 H5 有著傳播快,易釋出的優勢。

完整的直播包括:

  1. 視訊錄製端 
    電腦上的音視訊輸入裝置或者手機端的攝像頭或者麥克風,目前以移動端的手機視訊為主。
  2. 視訊播放端 
    可以是電腦上的播放器,手機端的 Native 播放器,還有 H5 的 video 標籤等。
  3. 流媒體伺服器端 
    用來接受視訊錄製端提供的視訊源,同時提供給視訊播放端流服務。目前開源的流媒體有RED5,CRTMPD,NGINX-RTMP,SRS。

二、錄製視訊

如何生產視訊資料

封裝格式的主要作用是把視訊碼流和音訊碼流按照一定的格式儲存在一個檔案中。

為什麼要分封裝格式和視訊編碼格式呢? 
這個其實跟網路分七層模型一個原理。解耦和,降低依賴,底層給上層提供基礎功能,底層和上層都都可以單獨擴充套件,可以以多種方案組合編碼與封裝,比如MP4與H264、MP4與MPEG、TS與H264等等。比如這裡面的這邊文章的編碼就只負責將最原始的音訊和視訊資料就行壓縮,而壓縮完的資料要怎麼組織就拜託給上層的封裝,封裝接到視訊音訊資料負責給資料編號,指定同步協議,加入字幕等操作。經過封裝後,得到的就是可以播放的上面提到的視訊檔案MP4或者MKV等等。把這個過程反過來就是上圖描述的視訊播放的過程。

1、流媒體源

  1. PC端的攝像頭、螢幕 
    對於PC端的流媒體源,可以使用Open Broadcaster Software串流(支援多種直播平臺)。

  2. 移動端iOSAndroid的攝像頭和麥克風。 
    iOS、Android主要是系統提供的API實現。

  3. webRTC (Web Real-Time Communication) 
    webRTC是一個支援網頁瀏覽器進行實時語音對話或視訊對話的技術,可以在網頁瀏覽器中進行採集、傳輸、播放,缺點是隻在 PC 的 Chrome 上支援較好,移動端支援不太理想。

使用 webRTC 錄製視訊基本流程是

  1. 呼叫window.navigator.webkitGetUserMedia() 獲取使用者的PC攝像頭視訊資料。
  2. 將獲取到視訊流資料轉換成 window.webkitRTCPeerConnection (一種視訊流資料格式)。
  3. 利用webscoket將視訊流資料傳輸到服務端

由於許多方法都要加上瀏覽器字首,所以很多移動端的瀏覽器還不支援 webRTC,所以真正的視訊錄製還是要靠客戶端(iOS,Android)來實現,效果會好一些。

2、編碼

推薦Andorid4.3(API18)或以上使用硬編,以下版本使用軟編;iOS使用全硬編方案。

  1. 軟編碼: 
    libffmpeg
  2. 硬編碼: 
    MediaCodec(sdk level 16+, Android 4.1, 4.1.1, the JELLY_BEAN)

3、封裝

FLV(Flash Video)是Adobe公司設計開發的一種流行的流媒體格式,FLV可以使用Flash Player進行播放,FLV封裝格式的檔案字尾通常為“.flv”。總體上看,FLV包括檔案頭(File Header)和檔案體(File Body)兩部分,其中檔案體由一系列的Tag組成。

特點:視訊檔案體積輕巧、封裝簡單

每個Tag前面還包含了Previous Tag Size欄位,表示前面一個Tag的大小。Tag的型別可以是視訊、音訊和Script,每個Tag只能包含以上三種類型的資料中的一種。圖2展示了FLV檔案的詳細結構。

Tag Data

  1. Audio Tag 

  2. Video Tag 

  3. Script Tag(控制幀)或叫meta data tag 
    該型別Tag又通常被稱為Metadata Tag,會放一些關於FLV視訊和音訊的元資料資訊如:duration、width、height等。通常該型別Tag會跟在File Header後面作為第一個Tag出現,而且只有一個。 

如圖以Android為例的推流的流程圖: 

三、視訊推流(Stream)

如何推 
往哪裡推

1、協議

國內常見公開的直播協議有幾個:RTMP、HDL(HTTP-FLV)、HLS、RTP。

RTMP

Real Time Messaging Protocol是 Macromedia 開發的一套視訊直播協議,現在屬於 Adobe。

使用RTMP技術的流媒體系統有一個非常明顯的特點:使用 Flash Player 作為播放器客戶端,而Flash Player 現在已經安裝在了全世界將近99%的PC上,因此一般情況下收看RTMP流媒體系統的視音訊是不需要安裝外掛的。使用者只需要開啟網頁,就可以直接收看流媒體。

和 HLS 一樣都可以應用於視訊直播,區別是 RTMP 基於 flash 無法在 iOS 的瀏覽器裡播放,但是實時性比 HLS 要好。所以一般使用這種協議來上傳視訊流,也就是視訊流推送到伺服器。

rtmp現在大部分國外的CDN已不支援,在國內流行度很高。原因有幾個方面:

  1. 開源軟體和開源庫的支援穩定完整。如鬥魚主播常用的OBS軟體,開源的librtmp庫,服務端有nginx-rtmp外掛。
  2. 播放端安裝率高。只要瀏覽器支援FlashPlayer就能播放RTMP的直播。相對其他協議而言,RTMP協議初次建立連線的時候握手過程過於複雜(RTMP協議本身的互動是基於TCP),視不同的網路狀況會帶來給首開帶來100ms以上的延遲。基於RTMP延遲在2~5秒。

HTTP-FLV

即使用HTTP協議流式的傳輸媒體內容,直接向後臺上傳編碼後的流媒體資料。相對於RTMP,HTTP更簡單和廣為人知,而且不擔心被Adobe的專利綁架。內容延遲同樣可以做到2~5秒,開啟速度更快,因為HTTP本身沒有複雜的狀態互動。所以從延遲角度來看,HTTP-FLV要優於RTMP。

SRS2.0支援該協議:GitHub

HLS

即Http Live Streaming,是由蘋果提出基於HTTP的流媒體傳輸協議。HLS有一個非常大的優點:HTML5可以直接開啟播放;這個意味著可以把一個直播連結通過微信等轉發分享,不需要安裝任何獨立的APP,有瀏覽器即可,所以流行度很高。社交直播APP,HLS可以說是剛需 。

Issue:SRS3.0提出了一種增強型HLS+

RTP

Real-time Transport Protocol,用於Internet上針對多媒體資料流的一種傳輸層協議。

實際應用場景下經常需要RTCP(RTP Control Protocol)配合來使用,可以簡單理解為RTCP傳輸互動控制的信令,RTP傳輸實際的媒體資料。 
RTP在視訊監控、視訊會議、IP電話上有廣泛的應用,因為視訊會議、IP電話的一個重要的使用體驗:內容實時性強。

對比與上述3種或實際是2種協議,RTP和它們有一個重要的區別就是預設是使用UDP協議來傳輸資料,而RTMP和HTTP-FLV是基於TCP協議傳輸。

  • UDP:單個數據報,不用建立連線,簡單,不可靠,會丟包,會亂序;
  • TCP:流式,需要建立連線,複雜,可靠 ,有序。

實時音視訊流的場景不需要可靠保障,因此也不需要有重傳的機制,實時的看到影象聲音,網路抖動時丟了一些內容,畫面模糊和花屏,完全不重要。TCP為了重傳會造成延遲與不同步,如某一截內容因為重傳,導致1秒以後才到,那麼整個對話就延遲了1秒,隨著網路抖動,延遲還會增加成2秒、3秒,如果客戶端播放是不加以處理將嚴重影響直播的體驗。

是否有除了HLS外更低延遲的方案?

HLS的優點點是顯而易見的:移動端無需安裝APP使用相容HTML5的瀏覽器開啟即可觀看,所有主流的移動端瀏覽器基本都支援HTML5,在直播的傳播和體驗上有巨大的優勢。

下面是 HTTP-FLV、HLS 、 RTMP 的對比:

2、推流

所謂推流,就是將我們已經編碼好的音視訊資料發往視訊流伺服器中,常用的第三方庫 librtmp-iOS 進行推流,librtmp 封裝了一些核心的 API 供使用者呼叫。例如推流 API 等等,配置伺服器地址,即可將轉碼後的視訊流推往伺服器。一般的推流伺服器都配置了伺服器端資訊。

推流SDK

推流伺服器

那麼如何搭建一個推流伺服器呢?

簡單的推流伺服器搭建,伺服器支援 RTMP ,大概需要以下幾個步驟:

  1. 安裝一臺 nginx 伺服器。
  2. 配置 nginx 的 conf 檔案
  3. 重啟 nginx,將 RTMP 的推流地址寫為 rtmp://ip:1935/hls/mystream, 其中 hls_path 表示生成的 .m3u8 和 ts 檔案所存放的地址,hls_fragment 表示切片時長,mysteam 表示一個例項,即將來要生成的檔名可以先自己隨便設定一個。 
    更多配置可以參考:https://github.com/arut/nginx-rtmp-module/wiki/

下面是 nginx 的配置檔案 

騰訊雲直播

[後臺SDK]主要是呼叫騰訊雲API。

[伺服器API]提供了直播控制檯api概覽:

  • 建立直播頻道 CreateLVBChannel
  • 查詢直播頻道列表 DescribeLVBChannelList
  • 查詢直播頻道詳情 DescribeLVBChannel
  • 修改直播頻道 ModifyLVBChannel
  • 批量啟用直播頻道 StartLVBChannel
  • 批量停止直播頻道 StopLVBChannel
  • 查詢直播頻道當前併發收看數 DescribeLVBOnlineUsers
  • 刪除直播頻道 DeleteLVBChannel
  • 建立錄製任務 CreateRecord
  • 終止錄製任務 StopRecord
  • 查詢已錄製分片列表 DescribeRecord
  • 建立截圖任務 CreateLVBShot
  • 終止截圖任務 StopLVBShot
  • 檢視佇列訊息 DescribeQueueLog

騰訊雲直播方案整體流程

方案根據騰訊雲的快速對接,最終形成閉環邏輯。

  • APP 
    1. 視訊源推流
    2. 向後臺發起建立直播頻道請求
    3. 向後臺發起停止直播請求
  • 後臺 
    1. 向騰訊雲發起建立、刪除(刪除前先關閉)直播頻道請求
    2. 直播頻道快取佇列,處理殭屍頻道
    3. 向APP客戶端推送直播URL
  • Web 
    1. PC端的流視訊播放器
    2. 移動客戶端的流視訊播放器

流程圖

  • Step1:建立頻道 
    客戶端發起直播請求,後臺呼叫CreateLVBChannel,由返回的channel_id呼叫DescribeLVBChannel檢視頻道資訊。 
    後臺向客戶端返回推流url和Web直播地址(非flv流視訊地址)。
  • Step2:SDK推流 
    推流SDK
  • Step3:刪除頻道 
    APP端推流結束,向後臺傳送請求刪除頻道,只有關閉的頻道是可以刪除的,所以後臺刪除一個頻道之前,要先通過停止直播頻道介面StopLVBChannel,先將頻道狀態置為停止,之後在呼叫刪除直播頻道介面DeleteLVBChannel對頻道進行刪除。

四、播放直播視訊

如何看

下載直播視訊有以下方式: 
1. HLS 
2. rtmp 
3. flv

好看的指標引數 
位元速率:影響體積,與體積成正比:位元速率越大,體積越大;位元速率越小,體積越小。 
幀率:影響畫面流暢度,與畫面流暢度成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。如果位元速率為變數,則幀率也會影響體積,幀率越高,每秒鐘經過的畫面越多,需要的位元速率也越高,體積也越大。 
解析度:影響影象大小,與影象大小成正比:解析度越高,影象越大;解析度越低,影象越小。

1、HLS

對於H5視訊播放,可以使用 HLS(HTTP Live Streaming)協議播放直播流,iOS和 Android 都天然支援這種協議,配置簡單,直接使用 video 標籤即可。

使用 video在移動客戶端上播放直播視訊:

<code class="language-javascript hljs  has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;"><video controls autoplay>
    <span class="xml" style="box-sizing: border-box;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">source</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">src</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"xxx.m3u8"</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">type</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"application/vnd.apple.mpegurl"</span>/></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">video</span>></span></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

HTTP Live Streaming

HLS是一個由蘋果公司提出的基於HTTP的流媒體網路傳輸協議。

HLS直播最大的不同在於,直播客戶端獲取到的,並不是一個完整的資料流。

HLS協議在伺服器端將直播資料流儲存為連續的、很短時長的媒體檔案(MPEG-TS格式),而客戶端則不斷的下載並播放這些小檔案,因為伺服器端總是會將最新的直播資料生成新的小檔案,這樣客戶端只要不停的按順序播放從伺服器獲取到的檔案,就實現了直播。

由此可見,基本上可以認為,HLS是以點播的技術方式來實現直播。由於資料通過HTTP協議傳輸,所以不用考慮防火牆或者代理的問題,而且分段檔案的時長很短,客戶端可以很快的選擇和切換位元速率,以適應不同頻寬條件下的播放。不過HLS的這種技術特點決定了延遲一般總是會高於普通的流媒體直播協議。

每一個 .m3u8 檔案,分別對應若干個 ts 檔案,這些 ts 檔案才是真正存放視訊的資料,m3u8 檔案只是存放了一些 ts 檔案的配置資訊和相關路徑,當視訊播放時,.m3u8 是動態改變的,video 標籤會解析這個檔案,並找到對應的 ts 檔案來播放,所以一般為了加快速度,.m3u8 放在 Web 伺服器上,ts 檔案放在 CDN 上。

支援的視訊流編碼為H.264,音訊流編碼為AAC。

簡單講就是把整個流分成一個個小的,基於 HTTP 的檔案來下載,每次只下載一些,前面提到了用於 H5 播放直播視訊時引入的一個 .m3u8 的檔案,這個檔案就是基於 HLS 協議,存放視訊流元資料的檔案。

  • HLS的分段策略,基本上推薦是10秒一個分片,當然,具體時間還要根據分好後的分片的實際時長做標註
  • 為了快取等方面的原因,在索引檔案中會保留最新的三個分片地址,以“滑動視窗”的形式進行重新整理。

.m3u8 檔案,其實就是以 UTF-8 編碼的 m3u 檔案,這個檔案本身不能播放,只是存放了播放資訊的文字檔案。 
開啟之後就是這個樣子:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">#EXTM3U                     m3u檔案頭,必須放在第一行
#EXT-X-MEDIA-SEQUENCE       第一個TS分片的序列號
#EXT-X-TARGETDURATION       每個分片TS的最大的時長
#EXT-X-ALLOW-CACHE          是否允許cache
#EXT-X-ENDLIST              m3u8檔案結束符
#EXTINF                     extra info,分片TS的資訊,如時長,頻寬等</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>
<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">#EXTM3U
#EXT-X-TARGETDURATION:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11</span>
#EXT-X-VERSION:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>
#EXT-X-MEDIA-SEQUENCE:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10.133333</span>,
fileSequence0.ts
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10.000666</span>,
fileSequence1.ts
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10.667334</span>,
fileSequence2.ts
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9.686001</span>,
fileSequence3.ts
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9.768665</span>,
fileSequence4.ts
#EXTINF:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10.000000</span>,
fileSequence5.ts
#EXT-X-ENDLIST</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

ts 檔案,就是存放視訊的檔案: 

HLS只請求基本的HTTP報文,與實時傳輸協議(RTP)不同,HLS可以穿過任何允許HTTP資料通過的防火牆或者代理伺服器。它也很容易使用內容分發網路來傳輸媒體流。

HLS 的請求播放流程

  1. HTTP 請求 m3u8 的 url。
  2. 服務端返回一個 m3u8 的播放列表,這個播放列表是實時更新的,一般一次給出5段資料的 url。
  3. 客戶端解析 m3u8 的播放列表,再按序請求每一段的 url,獲取 ts 資料流。

HLS直播延時

我們知道 hls 協議是將直播流分成一段一段的小段視訊去下載播放的,所以假設列表裡面的包含5個 ts 檔案,每個 TS 檔案包含5秒的視訊內容,那麼整體的延遲就是25秒。因為當你看到這些視訊時,主播已經將視訊錄製好上傳上去了,所以時這樣產生的延遲。當然可以縮短列表的長度和單個 ts 檔案的大小來降低延遲,極致來說可以縮減列表長度為1,並且 ts 的時長為1s,但是這樣會造成請求次數增加,增大伺服器壓力,當網速慢時回造成更多的緩衝,所以蘋果官方推薦的 ts 時長時10s,所以這樣就會大改有30s的延遲。所以伺服器接收流,轉碼,儲存,切塊,再分發給客戶端,這裡就延時的根本原因。

HLS優勢

但是 H5 直播視訊卻有一些不可替代的優勢:

  1. 傳播性好,利於分享等操作。
  2. 可以動態釋出,有利於實時迭代產品需求並迅速上線。
  3. 不用安裝 App,直接開啟瀏覽器即可。

2、RTMP

RTMP協議是應用層協議,是要靠底層可靠的傳輸層協議(通常是TCP)來保證資訊傳輸的可靠性的。在基於傳輸層協議的連結建立完成後,RTMP協議也要客戶端和伺服器通過“握手”來建立基於傳輸層連結之上的NetConnection連結,在Connection連結上會傳輸一些控制資訊,如SetChunkSize,SetACKWindowSize。其中CreateStream命令會建立一個NetStream連結,用於傳輸具體的音視訊資料和控制這些資訊傳輸的命令資訊。他們的關係如圖所示: 

RTMP Message

RTMP協議傳輸時會對資料做自己的格式化,這種格式的訊息我們稱之為RTMP Message,而實際傳輸的時候為了更好地實現多路複用、分包和資訊的公平性,傳送端會把Message劃分為帶有Message ID的Chunk,每個Chunk可能是一個單獨的Message,也可能是Message的一部分,在接受端會根據chunk中包含的data的長度,message id和message的長度把chunk還原成完整的Message,從而實現資訊的收發。

  1. Basic Header(基本的頭資訊) 
    chunk stream ID(流通道Id)和chunk type(chunk的型別,2位fmt),chunk stream id一般被簡寫為CSID,用來唯一標識一個特定的流通道,chunk type決定了後面Message Header的格式。長度有1、2或3個位元組
  2. Message Header(訊息的頭資訊) 
    Message Header的格式和長度取決於Basic Header的chunk type,共有4種不同的格式,由上面所提到的Basic Header中的fmt欄位控制。包含timestamp,timestamp delta,message length,message type id,msg stream id,和0(表示與上一個相同)。 
    • timestamp(時間戳):佔用3個位元組,因此它最多能表示到16777215=0xFFFFFF=2 
      24-1, 當它的值超過這個最大值時,這三個位元組都置為1,這樣實際的timestamp會轉存到Extended Timestamp欄位中,接受端在判斷timestamp欄位24個位都為1時就會去Extended timestamp中解析實際的時間戳。
    • message length(訊息資料的長度):佔用3個位元組,表示實際傳送的訊息的資料如音訊幀、視訊幀等資料的長度,單位是位元組。注意這裡是Message的長度,也就是chunk屬於的Message的總資料長度,而不是chunk本身Data的資料的長度。
    • message type id(訊息的型別id):佔用1個位元組,表示實際傳送的資料的型別,如8代表音訊資料、9代表視訊資料。
    • msg stream id(訊息的流id):佔用4個位元組,表示該chunk所在的流的ID,和Basic Header的CSID一樣,它採用小端儲存的方式,
  3. Extended Timestamp(擴充套件時間戳) 
    4個位元組,當擴充套件時間戳啟用時,timestamp欄位或者timestamp delta要全置為1,表示應該去擴充套件時間戳欄位來提取真正的時間戳或者時間戳差。注意擴充套件時間戳儲存的是完整值,而不是減去時間戳或者時間戳差的值。
  4. Chunk Data(塊資料) 
    使用者層面上真正想要傳送的與協議無關的資料,長度在(0,chunkSize]之間。

RTMP在收發資料的時候並不是以Message為單位的,而是把Message拆分成Chunk傳送,而且必須在一個Chunk傳送完成之後才能開始傳送下一個Chunk。每個Chunk中帶有MessageID代表屬於哪個Message,接受端也會按照這個id來將chunk組裝成Message。

舉個例子

chunk表示例1

首先包含第一個Message的chunk的Chunk Type為0,因為它沒有前面可參考的chunk,timestamp為1000。type為0的header佔用11個位元組,假定CSID為3<127,因此Basic Header佔用1個位元組,再加上Data的32個位元組,因此第一個chunk共44=11+1+32個位元組。

第二個chunk和第一個chunk的CSID,TypeId,Data的長度都相同,因此採用Chunk Type=2,timestamp delta=1020-1000=20,因此第二個chunk佔用36=3+1+32個位元組。

第三個chunk和第二個chunk的CSID,TypeId,Data的長度和時間戳差都相同,因此採用Chunk Type=3省去全部Message Header的資訊,佔用33=1+32個位元組。

第四個chunk和第三個chunk情況相同,也佔用33=1+32個位元組。

最後實際傳送的chunk如下: 

chunk表示例2 

注意到Data的Length=307>128,因此這個Message要切分成幾個chunk傳送,第一個chunk的Type=0,Timestamp=1000,承擔128個位元組的Data,因此共佔用140=11+1+128個位元組。

第二個chunk也要傳送128個位元組,其他欄位也同第一個chunk,因此採用Chunk Type=3,此時時間戳也為1000,共佔用129=1+128個位元組。

第三個chunk要傳送的Data的長度為307-128-128=51個位元組,還是採用Type=3,共佔用1+51=52個位元組。

最後實際傳送的chunk如下: 

Q:為什麼RTMP要將Message拆分成不同的Chunk呢? 
A:通過拆分,資料量較大的Message可以被拆分成較小的“Message”,這樣就可以避免優先順序低的訊息持續傳送阻塞優先順序高的資料,比如在視訊的傳輸過程中,會包括視訊幀,音訊幀和RTMP控制資訊,如果持續傳送音訊資料或者控制資料的話可能就會造成視訊幀的阻塞,然後就會造成看視訊時最煩人的卡頓現象。同時對於資料量較小的Message,可以通過對Chunk Header的欄位來壓縮資訊,從而減少資訊的傳輸量。

阻塞 vs. CPU 
Chunk的預設大小是128位元組,在傳輸過程中,通過一個叫做Set Chunk Size的控制資訊可以設定Chunk資料量的最大值,在傳送端和接受端會各自維護一個Chunk Size,可以分別設定這個值來改變自己這一方傳送的Chunk的最大大小。大一點的Chunk減少了計算每個chunk的時間從而減少了CPU的佔用率,但是它會佔用更多的時間在傳送上,尤其是在低頻寬的網路情況下,很可能會阻塞後面更重要資訊的傳輸。小一點的Chunk可以減少這種阻塞問題,但小的Chunk會引入過多額外的資訊(Chunk中的Header),少量多次的傳輸也可能會造成傳送的間斷導致不能充分利用高頻寬的優勢,因此並不適合在高位元率的流中傳輸。在實際傳送時應對要傳送的資料用不同的Chunk Size去嘗試,通過抓包分析等手段得出合適的Chunk大小,並且在傳輸過程中可以根據當前的頻寬資訊和實際資訊的大小動態調整Chunk的大小,從而儘量提高CPU的利用率並減少資訊的阻塞機率。

播放一個RTMP協議的流媒體需要經過以下幾個步驟:握手,建立連線,建立流,播放。RTMP連線都是以握手作為開始的。建立連線階段用於建立客戶端與伺服器之間的“網路連線”;建立流階段用於建立客戶端與伺服器之間的“網路流”;播放階段用於傳輸視音訊資料。

握手(HandShake)

一個RTMP連線以握手開始,雙方分別傳送大小固定的三個資料塊 
1. 握手開始於客戶端傳送C0、C1塊。伺服器收到C0或C1後傳送S0和S1。 
2. 當客戶端收齊S0和S1後,開始傳送C2。當伺服器收齊C0和C1後,開始傳送S2。 
3. 當客戶端和伺服器分別收到S2和C2後,握手完成。

理論上來講只要滿足以上條件,如何安排6個Message的順序都是可以的,但實際實現中為了在保證握手的身份驗證功能的基礎上儘量減少通訊的次數,一般的傳送順序是這樣的: 
1. Client傳送C0+C1到Sever 
2. Server傳送S0+S1+S2到Client 
3. Client傳送C2到Server,握手完成

建立網路連線(NetConnection)

  1. 客戶端傳送命令訊息中的“連線”(connect)到伺服器,請求與一個服務應用例項建立連線。
  2. 伺服器接收到連線命令訊息後,傳送確認視窗大小(Window Acknowledgement Size)協議訊息到客戶端,同時連線到連線命令中提到的應用程式。
  3. 伺服器傳送設定頻寬(Set Peer Bandwitdh)協議訊息到客戶端。
  4. 客戶端處理設定頻寬協議訊息後,傳送確認視窗大小(Window Acknowledgement Size)協議訊息到伺服器端。
  5. 伺服器傳送使用者控制訊息中的“流開始”(Stream Begin)訊息到客戶端。
  6. 伺服器傳送命令訊息中的“結果”(_result),通知客戶端連線的狀態。
  7. 客戶端在收到伺服器發來的訊息後,返回確認視窗大小,此時網路連線建立完成。

伺服器在收到客戶端傳送的連線請求後傳送如下資訊: 
 
主要是告訴客戶端確認視窗大小,設定節點頻寬,然後伺服器把“連線”連線到指定的應用並返回結果,“網路連線成功”。並且返回流開始的的訊息(Stream Begin 0)。

建立網路流(NetStream)

  1. 客戶端傳送命令訊息中的“建立流”(createStream)命令到伺服器端。
  2. 伺服器端接收到“建立流”命令後,傳送命令訊息中的“結果”(_result),通知客戶端流的狀態。

推流流程

  1. 客戶端傳送publish推流指令。
  2. 伺服器傳送使用者控制訊息中的“流開始”(Stream Begin)訊息到客戶端。
  3. 客戶端傳送元資料(解析度、幀率、音訊取樣率、音訊位元速率等等)。
  4. 客戶端傳送音訊資料。
  5. 客戶端傳送伺服器傳送設定塊大小(ChunkSize)協議訊息。
  6. 伺服器傳送命令訊息中的“結果”(_result),通知客戶端推送的狀態。
  7. 客戶端收到後,傳送視訊資料直到結束。 

播流流程

  1. 客戶端傳送命令訊息中的“播放”(play)命令到伺服器。
  2. 接收到播放命令後,伺服器傳送設定塊大小(ChunkSize)協議訊息。
  3. 伺服器傳送使用者控制訊息中的“streambegin”,告知客戶端流ID。
  4. 播放命令成功的話,伺服器傳送命令訊息中的“響應狀態” NetStream.Play.Start & NetStream.Play.reset,告知客戶端“播放”命令執行成功。
  5. 在此之後伺服器傳送客戶端要播放的音訊和視訊資料。 

五、直播中的使用者互動

對於直播中的使用者互動大致可以分為: 
1. 送禮物 
2. 發表評論或者彈幕 
3. 對於送禮物,在 H5 端可以利用 DOM 和 CSS3 實現送禮物邏輯和一些特殊的禮物動畫,實現技術難點不大。

對於彈幕來說,要稍微複雜一些,可能需要關注以下幾點: 
1. 彈幕實時性,可以利用 webscoket 來實時傳送和接收新的彈幕並渲染出來。 
2. 對於不支援 webscoket 的瀏覽器來說,只能降級為長輪詢或者前端定時器傳送請求來獲取實時彈幕。 
3. 彈幕渲染時的動畫和碰撞檢測(即彈幕不重疊)等等

Html5直播聊天室元件

該元件主要適用於基於Html5的web 大群互動直播場景。具備如下特點: 
1)支援匿名身份入群,粉絲與主播進行親密互動 
2)支援多人聊天,主播同一個帳號多標籤頁收發訊息,粉絲再多也不用愁 
3)支援多種聊天方式,文字,表情,紅包,點贊,想怎麼互動就怎麼互動 
4)支援不同優先順序訊息的頻率控制,一鍵在手,權利盡在掌握中 
5)對互動直播場景進行了專門的優化,參與人數多,訊息量再大也能從容應對

前端技術點

  1. 秒開
  2. 時延
  3. 流暢
  4. 清晰度

六、總結

目前較為成熟的直播產品,大致都是以 Server 端、 H5直播前端 和 Native(Android,iOS)搭配實現直播。

主要從android客戶端出發,從最初的錄製視訊到客戶端觀看直播的整個流程,給出了各個技術點的概要和解決方案,從0到1完成了簡單的直播實現。從0到1易,從1到100還有更多的技術細節有待研究。

相關推薦

如何打造直播App方法流程

概要 分享內容: 網際網路內容載體變遷歷程,文字——圖片/聲音——視訊——VR/AR——…….。從直播1.0秀場時代(YY),2.0遊戲直播(鬥魚、虎牙、熊貓)到如今全民直播3.0泛生活娛樂時代(映客、花椒),國外直播app(Meerkat 、Periscope),隨

如何打造 android app

轉載請註明出處: 本文來自aspook的部落格:http://blog.csdn.net/ahence/article/details/47154419 開發工具的選擇 開發工具我將選用Android Studio,它是Google官方指定的Android開發工具,

Laravel5.6+dingo+jwt+Api Cloud 開發基於阿里雲的直播app0-0

一、環境要求php > 7.1.3二、laravel框架搭建1 安裝laravel(先安裝好composer)composer create-project --prefer-dist laravel/laravel live "5.6.*"2.1 安裝dingo修改c

打造可靠的WAFWeb應用防火牆

之前寫了一篇《WAF防禦能力評測及工具》,是站在安全運維人員選型WAF產品的角度來考慮的(優先從測試角度考慮是前職業病,畢竟當過3年遊戲測試?!)。本篇文章從WAF產品研發的角度來YY如何實現一款可靠的WAF,靈感來自ModSecurity等,感謝開源。 本片文章包

如何快速打造高清又極速的短視訊APP

整個短視訊的市場規模一直在增長,網路資料顯示2018年已經突破100億大關,在2019年預測將超過200億。縱觀行業,在生活資訊、美食、搞笑、遊戲、美妝等領域,短視訊流量巨大但競爭激烈,但是在教育、財經、軍事、旅遊等行業還存在較大的機會。那麼在這些垂直行業裡,我們如何結合短視訊能力,實現業務突破

從0到1打造react-native App()環境配置

前言 最近心血來潮,想要做一個全棧的App玩玩,在網上查閱一下現在的主流的技術棧,考慮的自身能力及開發成本,準備做一個node.js+koa2+react-native的app。目前個人的狀態是node.js會一點點點點,koa2不會,react-native

React實戰教程之從零開始手把手教你使用 React 最新特性Hooks API 打造計算機知識測驗App

## 專案演示地址 [專案演示地址](https://kamiba.gitee.io/react-hooks-exam-app/) ## 專案程式碼結構 ![](https://media.songbo.info/20200623130952.png) ## 前言 React 框架的優雅不言而喻,元件化的

打造個人的遠程協助軟件

-1 小數據 pan 視頻 代碼 心跳 解決方法 switch語句 方便 回到深圳的這段時間一直宅在家裏搞這個,學習了老狼的教程,照著做了一個,功能差不多都實現了,做這個僅僅是對編程,對安全領域感興趣做做實驗而已。 采用了gh0st開源遠控內核,它是一款基於C/S架構的遠

打造虛擬幣交易分析軟件

wire pyqt lan asc post請求 不能 baidu 手機流量 article   由於各大虛擬幣交易平臺更多地顯示自己平臺上的虛擬幣交易信息,如果想同時看不同平臺上的虛擬幣交易情況時,就要打開很多頁面而且需要不停地切換頁面,操作非常不方便,於是就想著做一款工

利用Python打造AV女優相似圖像檢索!同寢的究極大福利!

nbsp 提取 -c 測試 模型 pst 調整 框架 簡單的 簡單的流程 1:收集各女優的照片 2:使用dlib提取面部圖像並調整96*96的大小 3:使用數據擴張(DataAugmentation)將女優面部圖像的數據擴張到1000章 4:將數據轉換為numpy文件 5:

利用Python+ADB打造自動點贊和抽獎機制!這款項目值多少錢?

adb 漢語 pen stat sdcard -i enc 路徑和 備忘錄 為什麽要做這個呢? 鑒於之前已經有同學實現了自動挖掘抖音美女的案例,所以這個想法終於有了一絲付諸實踐的曙光,潘老師和這位同學一樣使用了Python+ADB的方式來實現。 Python

打造搶全網紅包現金券指令碼!搶了兩萬個紅包!Python也能實現

概述   電商的秒殺、搶購,春運搶票,微信QQ搶紅包,從技術的角度來說,這對於Web 系統是一個很大的考驗. 高併發場景下,系統的優化和穩定是至關重要的. 網際網路的開發包括 Java 後臺、 NoSQL、資料庫、限流、CDN、負載均衡等內容, 目前並沒有權威性

從零開發筆記APP——神馬筆記WhatsNote

從零開發一款筆記APP——神馬筆記WhatsNote 一、主要功能 二、開發過程 三、優質的筆記應用 四、附錄 一、主要功能 筆記的主要功能分為三個部分: 管理 目錄——

騰訊新出了音樂app,它能代替QQ音樂嗎?

在國內,QQ音樂擁有最大的音樂版權庫,是名副其實的財大氣粗。有許多好聽的歌,只能在QQ音樂找到。而且,它緊跟潮流趨勢,哪些歌曲點選率和收聽率高,短短几天內,如果使用者想下載到本地,就需要付費了,非常會抓取使用者的心思來盈利。不過,QQ音樂的付費情況也是能為大眾所接受的,每個月不到十塊錢。但是對於那些想聽簡

利用Python打造鍵下載器!管他是vip還是付費的!鍵下載

大家都知道,我們經常給大家推薦各種各樣的神器。但跟今天推薦的比起來,那些都不算什麼。我們今天要講的這個神器是真正的黑科技,既可以一鍵批量爬美圖、一鍵下電影,又可以自己做一個智慧聊天機器人,還能做資料分析等等...... 這個神奇的黑科技就是程式設計了。 就拿目前最火的Python語言來說,學

利用Python3開發小工具 介面背後的程式碼

介面設計參照上一篇部落格(https://blog.csdn.net/hitguolu/article/details/82620681), 1、介面中的控制元件 2、如何給一個控制元件新增事件響應 3、介面的約束 4、訊號

資料庫開發九 JDBC篇JDBC六大步操作流程

準備工作(使用自制的EasyBuy.sql指令碼進行練習): EasyBuy.sql --建立使用者 create user easybuy identified by easybuy; --授權 grant connect, resource to easybuy; --連線 conn

打造靈活的導航欄

前言:專案中經常會遇到功能樣式各種各樣的導航欄,使用系統的也能達到需求,但是改動方面比較麻煩,特別是對於改動的時機把控問題較多,所以很多情況下都是隱藏系統的導航欄,自定義一個view區代替導航欄,導航欄的樣式無非以下幾種: 文字,圖片,與圖文混合,這幾種的組合 那麼如何打造一款適

作為名83歲的長者,我開發了遊戲App

時間會讓人增長歲數,但不是每一個人都會變老。想要保持年輕的最好方式,就是不斷學習新知。 這就是為什麼我在過去的兩年中嘗試著開發了一款移動 App。更準確地說,我和一幫移動應用開發者們通力合作,將一種歷史至少可追溯到第二次世界大戰的經典紙牌遊戲帶入這個數字化時代中。現在這個 App 已經制作完成了,我很欣慰

大牛教你用Python打造屬於自己的專屬影院!再也不買電影票了

      2. 安裝完Python以後,如果不出意外,應該是已經安裝了pip。通過pip可以非常方便的管理Python第三方包。可以在CMD或者任何shell終端輸入pip -V,如果提示了找不到pip命令,說明pip沒有安裝成功。那麼可以通過