成熟的媒體應用往往面對這樣的需求:

  • 自定義封裝的視頻
  • 加密的音視頻
  • 對接第三方的非標準媒體源
  • 支持不同架構的播放器
  • ……

其中一種比較靈活的解決方案是把自定義媒體數據推流為http,而大部分播放器都能很好地支持http(vlc/ffmepg/mediaplayer/ijkplayer/kodi等)。

數據流示意:

本篇文章主要講解上圖的http協議部分。

http協議基礎

http協議是應用層協議,使用tcp進行傳輸。

請求報文是播放器(http客戶端)發給http伺服器的內容,由請求方法、請求URI、協議版本、可選的請求首部欄位和內容實體構成,如:

POST /media HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 10

path=/tmp/1.mp4

第1行包含了請求方法、請求URI、協議版本,第2~4行是請求首部欄位,最後一行是內容實體。

響應報文由協議版本、狀態碼、狀態碼原因短語、可選的響應首部欄位和實體主體構成,如:

HTTP/1.1 200 OK
Content-Length: 53
Content-Type: text/html

<html>
...

第一行包含了協議版本、狀態碼、狀態碼原因短語,第2~3行是響應首部欄位,最後幾行是主體,也就是通常瀏覽器要渲染的內容

流媒體 - http chunk

流媒體就是像流水一樣把視頻數據通過網路傳輸到終端上播放。

通過http推送流媒體的時候,對應的是http的chunk傳輸。

chunk傳輸的典型(響應)報文如下:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: video/mpeg

400
...ad....fxa...
...
400
xai..
...
0

即,通過Transfer-Encoding: chunked告知客戶端現在傳輸的是分塊數據,這樣客戶端就會維持這個連接,直到數據接收完成。

數據傳輸過程中,每個chunk都是以大小
數據
的格式傳輸,最後以大小0通知客戶端數據完成。

流傳輸在多媒體應用中常用於直播,因為直播的數據長度一般是不定的,這樣就可以藉助客戶端和服務端間的這個長連接持續不斷地傳輸媒體數據。

文件媒體 - http range

流媒體的缺點是不能跳進。

不能跳進不僅意味著用戶無法seek觀看節目,也意味著一些節目的信息無法獲取(如時長)。

為了支持跳進,可以藉助http的range請求。

range請求常以斷點續傳聞名,它允許客戶端從任何位置開始,向伺服器請求任意長度的數據。

比如:

POST /media HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Range: bytes=500-1000

path=/tmp/1.mp4

這個請求報文通過Range向伺服器請求了第500~1000位元組的數據(共501位元組,第一個位元組是索引0)

伺服器如果能正確返回這部分數據,就回復:

HTTP/1.1 206 OK
Content-Length: 53
Content-Type: video/mpeg
Accept-Ranges: bytes
Content-Range: bytes 500-1000/1024
Content-Length: 501

....

Accept-Ranges:bytes意思是接收按位元組為單位進行range請求;Content-Range告訴客戶端返回的數據對應的是哪個範圍的數據,這裡回復的是客戶端請求的500-1000,其中1024是整個媒體的數據長度;Content-Length表示返回的數據長度(1000-500+1 = 501)

所以,一般播放器在播放http源的時候要進行seek就是通過發起新的http請求,並在請求中加入Range欄位來從seek的目標位置讀取數據。

然而,實際情況會複雜一些。

其一,播放器可能會在開始播放的時候就會跳轉到尾部讀取視頻數據,以確定節目時長(比如ts封裝就需要讀取尾部數據來估算視頻時長);

其二,seek可能需要經過多次range請求才能跳轉到目標位置(如ffmpeg會用二分查找來查找目標時間點);

其三,http是無狀態的,所以每次客戶端來的請求所在的處理線程不一定相同,而且同次點播的多個http請求間是無關聯的。這對於靜態文件資源而言是無關緊要的,但對於動態內存資源(如動態解密的視頻)而言就需要謹慎處理多線程問題和session管理了。

點播管理 - http session

上節了解過,seek基於多次range request的實現機制會導致在一次點播期間,服務端與客戶端間會有多次的通信,而http的無狀態特性導致這幾次通信是無關聯的,伺服器無從知道這幾次通信對應的是同一次點播。

通用的解決方法是利用http的cookie機制,在多次通信中攜帶id欄位進行session關聯。示意圖如下:

對應於http報文是:

"第一次通信":

POST /media HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Range: bytes=0-

path=/tmp/1.mp4

"你的id是123":

HTTP/1.1 206 OK
Content-Length: 53
Content-Type: video/mpeg
Accept-Ranges: bytes
Content-Range: bytes 0-1023/1024
Content-Length: 1024
Set-Cookie: id=123

....

"我的id是123,Range 500-1000":

POST /media HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Range: bytes=500-1000
Cookie: id=123

path=/tmp/1.mp4

"你的id是123,你的請求已受理":

HTTP/1.1 206 OK
Content-Length: 53
Content-Type: video/mpeg
Accept-Ranges: bytes
Content-Range: bytes 500-1000/1024
Content-Length: 501
Set-Cookie: id=123

....

上面通信過程主要依賴Set-CookieCookie兩個欄位保證。協議也很簡單,服務端通過Set-Cookie給客戶端發送id=123,客戶端識別如果有Set-Cookie,則在下次請求中把Set-Cookie的內容放到Cookie中通知回服務端。

上述基本就是完成http推流所需要的核心協議了。總結下:

  • http傳輸基於tcp,是可靠連接
  • 傳輸流媒體可以使用chunk傳輸
  • 為支持seek,需要支持range請求
  • seek實現中,如果服務端資源是動態的,需要通過cookie引入session機制

我們將在http推流設計與實現一文中介紹xport,詳解如何實現http推流。

compilelife/xport?

github.com圖標
推薦閱讀:

相关文章