HTTP Live Streaming 簡介:Android 上的 HLS 等

已發表: 2022-03-11

視頻流是現代互聯網體驗不可或缺的一部分。 它無處不在:在手機、台式電腦、電視甚至可穿戴設備上。 它需要在每種設備和網絡類型上完美運行,無論是在慢速移動連接、WiFi、防火牆後面等等。Apple 的 HTTP Live Streaming (HLS) 正是針對這些挑戰而創建的。

幾乎所有現代設備都配備了足以播放視頻的現代硬件,因此網絡速度和可靠性成為最大的問題。 這是為什麼? 直到幾年前,存儲和發布視頻的規範方式是基於 UDP 的協議,如 RTP。 這在很多方面都被證明是有問題的,僅列出幾個:

  1. 您需要一個服務器(守護程序)服務來流式傳輸內容。
  2. 大多數防火牆配置為僅允許標準端口和網絡流量類型,例如 http、電子郵件等。
  3. 如果您的受眾是全球性的,您需要一份在所有主要區域運行的流式守護程序服務的副本。

當然,您可能認為所有這些問題都很容易解決。 只需將視頻文件(例如,mp4 文件)存儲在您的 http 服務器上,然後使用您喜歡的 CDN 服務在世界任何地方為它們提供服務。

傳統視頻流的不足之處

由於幾個原因,這遠非最佳解決方案,效率就是其中之一。 如果您以全分辨率存儲原始視頻文件,農村地區或世界上連接性較差的地區的用戶將很難享受這些文件。 他們的視頻播放器將難以下載足夠的數據以在運行時播放。

因此,您需要一個特殊版本的文件,以便下載的視頻量與可以播放的量大致相同。 例如,如果視頻分辨率和質量能夠在 5 秒內下載另外 5 秒的視頻,那就是最佳選擇。 但是,如果只下載 3 秒的視頻需要 5 秒,則播放器將停止並等待流的下一個塊下載。

另一方面,進一步降低質量和分辨率只會降低更快連接的用戶體驗,因為您會不必要地節省帶寬。 但是,還有第三種方法。

自適應比特率流

雖然您可以為不同的用戶上傳不同版本的視頻,但您需要能夠控制他們的播放器併計算最適合他們的連接和設備的流。 然後,玩家需要在它們之間進行切換(例如,當用戶從 3G 切換到 WiFi 時)。 即便如此,如果客戶端更改了網絡類型怎麼辦? 然後播放器必須切換到不同的視頻,但它必須不是從頭開始播放,而是在視頻中間的某個地方開始播放。 那麼如何計算請求的字節範圍呢?

如果視頻播放器能夠檢測到網絡類型和可用帶寬的變化,然後在不同的流(為不同速度準備的同一視頻)之間透明地切換,直到找到最佳流,這將是一件很酷的事情。

這正是自適應比特率流解決的問題。

注意:本 HLS 教程不涉及加密、同步播放和 IMSC1。

什麼是 HLS?

HTTP Live Streaming 是 Apple 於 2009 年推出的一種自適應比特率流協議。它使用 m3u8 文件來描述媒體流,並使用 HTTP 進行服務器和客戶端之間的通信。 它是所有 iOS 設備的默認媒體流協議,但它可以在 Android 和 Web 瀏覽器上使用。

HTTP Live Streaming 封面圖

HLS 流的基本構建塊是:

  1. M3U8 播放列表
  2. 各種流媒體文件

M3U8 播放列表

讓我們從回答一個基本問題開始:什麼是 M3U8 文件

M3U(或 M3U8)是一種純文本文件格式,最初是為了組織 MP3 文件的集合而創建的。 該格式針對 HLS 進行了擴展,用於定義媒體流。 在 HLS 中有兩種 m3u8 文件:

  • 媒體播放列表:包含流式傳輸所需文件的 URL(即要播放的原始視頻塊)。
  • 主播放列表:包含媒體播放列表的 URL,這些播放列表又包含為不同帶寬準備的同一視頻的變體。

所謂的 M3U8 直播 URL 無非就是 M3U8 文件的 URL,例如:https://s3-us-west-2.amazonaws.com/hls-playground/hls.m3u8。

HLS 流的示例 M3U8 文件

M3U8 文件包含 url 列表或本地文件路徑以及一些額外的元數據。 元數據行以 # 開頭。

此示例說明了簡單 HLS 流的 M3U8 文件的外觀:

 #EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-ALLOW-CACHE:YES #EXT-X-TARGETDURATION:11 #EXTINF:5.215111, 00000.ts #EXTINF:10.344822, 00001.ts #EXTINF:10.344822, 00002.ts #EXTINF:9.310344, 00003.ts #EXTINF:10.344822, 00004.ts ... #EXT-X-ENDLIST
  • 前四行是此 M3U8 播放列表的全局(標題)元數據。
  • EXT-X-VERSION是 M3U8 格式的版本(如果我們要使用EXTINF條目,必須至少為 3)。
  • EXT-X-TARGETDURATION標籤包含每個視頻“塊”的最長持續時間。 通常,此值約為 10 秒。
  • 文檔的其餘部分包含成對的行,例如:
 #EXTINF:10.344822, 00001.ts

這是一個視頻“塊”。 這個代表00001.ts塊,正好是 10.344822 秒長。 當客戶端視頻播放器需要從所述視頻中的某個點開始播放視頻時,它可以通過將之前查看的塊的持續時間相加來輕鬆計算出它需要請求哪個.ts文件。 第二行可以是本地文件名或該文件的 URL。

M3U8 文件及其.ts文件代表了 HLS 流的最簡單形式——媒體播放列表。

請記住,並非所有瀏覽器默認都可以播放 HLS 流。

主播放列表或索引 M3U8 文件

前面的 M3U8 示例指向一系列.ts塊。 它們是從原始視頻文件創建的,該文件經過調整大小編碼並分成塊。

這意味著我們仍然存在引言中概述的問題 - 非常慢(或異常快)網絡上的客戶端呢? 或者,屏幕尺寸非常小的快速網絡上的客戶端? 如果無法在閃亮的新手機上完整顯示文件,那麼以最大分辨率流式傳輸文件是沒有意義的。

HSL 中的 M3U8

HLS 通過引入 M3U8 的另一個“層”解決了這個問題。 此 M3U8 文件將不包含指向.ts文件的指針,但它具有指向其他 M3U8 文件的指針,而這些文件又包含為特定比特率和分辨率預先準備好的視頻文件。

以下是此類 M3U8 文件的示例:

 #EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1296,RESOLUTION=640x360 https://.../640x360_1200.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=264,RESOLUTION=416x234 https://.../416x234_200.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=464,RESOLUTION=480x270 https://.../480x270_400.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1628,RESOLUTION=960x540 https://.../960x540_1500.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2628,RESOLUTION=1280x720 https://.../1280x720_2500.m3u8

視頻播放器將選擇成對的行,例如:

 #EXT-X-STREAM-INF:BANDWIDTH=1296,RESOLUTION=640x360 https://.../640x360_1200.m3u8

這些被稱為為不同網絡速度和屏幕分辨率準備的同一視頻的變體。 這個特定的 M3U8 文件 ( 640x360_1200.m3u8 ) 包含調整為640x360像素並為1296kbps比特率準備的視頻的視頻文件塊。 請注意,報告的比特率必須同時考慮視頻中的視頻和音頻流

視頻播放器通常會從第一個流變開始播放(在前面的示例中,這是 640x360_1200.m3u8)。 因此,您必須特別注意確定哪個變體將是列表中的第一個。 其他變體的順序並不重要。

如果第一個 .ts 文件下載時間過長(導致“緩衝”,即等待下一個塊),視頻播放器將切換到比特率較小的流。 而且,當然,如果它的加載速度足夠快,則意味著它可以切換到質量更好的變體,但前提是它對顯示器的分辨率有意義。

如果索引 M3U8 列表中的第一個流不是最佳流,則客戶端將需要一到兩個週期,直到它使用正確的變體穩定下來。

所以,現在我們有了三層 HLS:

  1. 包含指向變體的指針 (URL) 的索引 M3U8 文件(主播放列表)
  2. 用於不同屏幕尺寸和網絡速度的不同流的變體 M3U8 文件(媒體播放列表) 。 它們包含指向 .ts 文件的指針 (URL)。
  3. .ts文件(塊)是包含部分視頻的二進製文件。

您可以在此處觀看示例索引 M3U8 文件(同樣,這取決於您的瀏覽器/操作系統)。

有時,您提前知道客戶端在慢速或快速網絡上。 在這種情況下,您可以通過提供具有不同第一個變體的索引 M3U8 文件來幫助客戶選擇正確的變體。 有兩種方法可以做到這一點。

  • 首先是為不同的網絡類型準備多個索引文件,並提前準備好客戶端以請求正確的索引文件。 客戶端必須檢查網絡類型,然後請求例如http://.../index_wifi.m3u8http://.../index_mobile.m3u8
  • 您還可以確保客戶端將網絡類型作為 http 請求的一部分發送(例如,如果它連接到 wifi 或移動 2G/3G/...),然後為每個請求動態準備索引 M3U8 文件。 只有索引 M3U8 文件需要動態版本,單個流(變體 M3U8 文件)仍然可以存儲為靜態文件。

為 HLS 準備視頻文件

Apple 的 HTTP Live Streaming 有兩個重要的構建塊。 一種是視頻文件的存儲方式(稍後通過 HTTP 提供),另一種是M3U8索引文件,它告訴播放器(流媒體客戶端應用程序)從哪裡獲取哪個視頻文件。

讓我們從視頻文件開始。 HLS 協議要求將視頻文件存儲在相同長度的較小塊中,通常每個 10 秒。 最初,這些文件必須存儲在 MPEG-2 TS 文件 ( .ts ) 中,並以 H.264 格式編碼,並帶有 MP3、HE-AAC 或 AC-3 中的音頻。

HLS 視頻

這意味著 30 秒的視頻將被分成 3 個較小的.ts文件,每個文件大約 10 秒長。

請注意,最新版本的 HLS 也允許使用碎片化的 .mp4 文件。 由於這還是一個新事物,並且一些視頻播放器還需要實現它,所以本文中的示例將使用.ts文件。

關鍵幀

必須在每個文件的開頭使用關鍵幀對塊進行編碼。 每個視頻都包含幀。 幀是圖像,但視頻格式不存儲完整的圖像,這會佔用太多磁盤空間。 它們僅編碼與前一幀的差異。 當您跳到視頻的中間點時,播放器需要一個“起點”來應用所有這些差異以顯示初始圖像,然後開始播放視頻。

這就是為什麼.ts塊必須在開始時有一個關鍵幀。 有時玩家需要從區塊的中間開始。 玩家總是可以通過添加來自第一個關鍵幀的所有“差異”來計算當前圖像。 但是,如果它從開始 9 秒開始,它需要計算 9 秒的“差異”。 為了使計算更快,最好每隔幾秒創建一次關鍵幀(最佳 cca 3s)。

HLS 斷點

在某些情況下,您希望連續播放多個視頻剪輯。 一種方法是合併原始視頻文件,然後使用該文件創建 HLS 流,但由於多種原因,這是有問題的。 如果您想在視頻之前或之後展示廣告怎麼辦? 也許您不想對所有用戶都這樣做,並且可能希望為不同的用戶提供不同的廣告。 而且,當然,您不希望提前準備帶有不同廣告的 HLS 文件。

為了解決這個問題,可以在 m3u8 播放列表中使用標籤#EXT-X-DISCONTINUITY 。 這一行主要是告訴視頻播放器提前準備好,從這一點開始, .ts文件可能會以不同的配置創建(例如,分辨率可能會改變)。 玩家將需要重新計算所有內容,並可能切換到另一個變體,並且需要為這種“不連續”點做好準備。

使用 HLS 進行直播

基本上有兩種“視頻流”。 一種是視頻點播( VOD ),用於預先錄製並在用戶決定時流式傳輸給用戶的視頻。 還有直播。 儘管 HLS 是 HTTP Live Streaming 的縮寫,但到目前為止所解釋的一切都圍繞 VOD,但也有一種方法可以使用 HLS 進行直播。

您的 M3U8 文件有一些更改。 首先,變體 M3U8 文件中必須有#EXT-X-MEDIA-SEQUENCE:1標籤。 然後,M3U8 文件不得#EXT-X-ENDLIST (否則必須始終放在末尾)。

在錄製流時,您將不斷擁有新的.ts文件。 您需要將它們附加到 M3U8 播放列表中,並且每次添加新播放列表時, #EXT-X-MEDIA-SEQUENCE:<counter>中的計數器必須增加 1。

視頻播放器將檢查計數器。 如果從上次更改,它知道是否有新的塊要下載和播放。 確保 M3U8 文件使用無緩存標頭提供服務,因為客戶端將不斷重新加載 M3U8 文件以等待新塊播放。

VTT

HLS 流的另一個有趣功能是您可以在其中嵌入 Web 視頻文本軌道 (VTT) 文件。 VTT 文件可用於各種用途。 例如,對於網絡 HLS 播放器,您可以為視頻的各個部分指定圖像快照。 當用戶將鼠標移到視頻計時器區域(視頻播放器下方)上時,播放器可以顯示視頻中該位置的快照。

VTT 文件的另一個明顯用途是字幕。 HLS 流可以為多種語言指定多個字幕:

 #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-,NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/prog_index.m3u8"

然後, theprog_index.m3u8看起來像:

 #EXTM3U #EXT-X-TARGETDURATION:30 #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:30, 0000.webvtt #EXTINF:30, 0001.webvtt ...

實際的 VTT(例如0000.webvtt ):

 WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:01.000 --> 00:00:03.000 Subtitle -Unforced- (00:00:01.000) 00:00:03.000 --> 00:00:05.000 <i>...text here... -Unforced- (00:00:03.000)</i> <i>...text here...</i>

除了 VTT 文件,Apple 最近還宣布 HLS 將支持 IMSC1,這是一種針對流媒體傳輸進行了優化的新字幕格式。 它最重要的優點是可以使用 CSS 設置樣式。

HTTP 實時流媒體工具和潛在問題

Apple 引入了許多有用的 HSL 工具,在官方 HLS 指南中有更詳細的描述。

  • 對於實時流,Apple 準備了一個名為mediastreamsegmenter的工具,用於從正在進行的視頻流中動態創建片段文件。
  • 另一個重要的工具是mediastreamvalidator 。 它將檢查您的 M3U8 播放列表、下載視頻文件並報告各種問題。 例如,當報告的比特率與從 .ts 文件計算的不同時。
  • 當然,當你必須編碼/解碼/mux/demux/chunk/strip/merge/join/...視頻/音頻文件時,還有ffmpeg。 準備好為特定用例編譯您自己的自定義版本的 ffmpeg。

視頻中最常見的問題之一是音頻同步。 如果您發現某些 HLS 流中的音頻與視頻不同步(即演員張嘴,但您注意到聲音提前或延遲了幾毫秒),則可能是原始視頻文件被拍攝了使用可變幀率。 確保將其轉換為恆定比特率。

如果可能,最好確保您的軟件設置為以恆定幀速率錄製視頻。

HTTP 實時流媒體示例

我準備了一個 HLS Android 應用程序,它使用 Google 的 ExoPlayer 播放器流式傳輸預定義的 HLS。 它將在其下方顯示一個視頻和 HLS“事件”列表。 這些事件包括:下載的每個.ts文件,或每次播放器決定切換到更高或更低比特率的流時。

讓我們來看看查看器初始化的主要部分。 在第一步中,我們將檢索設備的當前連接類型並使用該信息來決定要檢索哪個m3u8文件。

 String m3u8File = "hls.m3u8"; ConnectivityManager connectivity = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo(); if (activeNetwork != null && activeNetwork.isConnectedOrConnecting()) { int type = activeNetwork.getType(); int subType = activeNetwork.getSubtype(); if (type == ConnectivityManager.TYPE_MOBILE && subType == TelephonyManager.NETWORK_TYPE_GPRS) { m3u8File = "hls_gprs.m3u8"; } } String m3u8URL = "https://s3-us-west-2.amazonaws.com/hls-playground/" + m3u8File;

請注意,這不是絕對必要的。 HLS 播放器總是會在幾塊之後調整到正確的 HLS 變體,但這意味著在前 5-20 秒內,用戶可能看不到流的理想變體。

請記住, m3u8文件中的第一個變體是查看器將開始使用的變體。 由於我們在客戶端,並且可以檢測到連接類型,因此我們至少可以通過請求預先為該連接類型準備的m3u8文件來嘗試避免初始播放器在變體之間切換。

在下一步中,我們初始化並啟動我們的 HLS 播放器:

 Handler mainHandler = new Handler(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder() .setEventListener(mainHandler, bandwidthMeterEventListener) .build(); TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); LoadControl loadControl = new DefaultLoadControl(); SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);

然後我們準備播放器,並為這種網絡連接類型提供正確的 m3u8:

 DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "example-hls-app"), bandwidthMeter); HlsMediaSource videoSource = new HlsMediaSource(Uri.parse(m3u8URL), dataSourceFactory, 5, mainHandler, eventListener); player.prepare(videoSource);

結果如下:

Android 中的 HLS 流式傳輸示例

HLS 瀏覽器兼容性,未來發展

Apple 要求 iOS 上的視頻流應用程序必須使用 HLS,如果視頻超過 10 分鐘或大於 5mb。 這本身就是 HLS 將繼續存在的保證。 有人擔心 HLS 和 MPEG-DASH 以及哪一個將成為 Web 瀏覽器領域的贏家。 HLS 並未在所有現代瀏覽器中實現(您可能會注意到,如果您單擊前面的 m3u8 url 示例)。 例如,在 Android 上,在低於 4.0 的版本中,它根本無法工作。 從 4.1 到 4.4,它只能部分工作(例如,音頻丟失,或視頻丟失但音頻有效)。

但最近這場“戰鬥”變得稍微簡單了一些。 Apple 宣布新的 HLS 協議將允許分段的 mp4 文件 ( fMP4 )。 以前,如果您想要同時支持 HLS 和 MPEG-DASH,您必須對視頻進行兩次編碼。 現在,您將能夠重複使用相同的視頻文件,並且僅重新打包元數據文件(用於 HLS 的.m3u8和用於 MPEG-DASH 的.mpd )。

最近的另一個公告是對高效視頻編解碼器 (HEVC) 的支持。 如果使用,必須打包成碎片化的 mp4 文件。 這可能意味著 HLS 的未來是fMP4

瀏覽器領域的當前情況是,只有<video>標籤的某些瀏覽器實現會開箱即用地播放 HLS。 但也有提供 HLS 兼容性的開源和商業解決方案。 他們中的大多數通過 Flash 後備提供 HLS,但也有一些完全用 JavaScript 編寫的實現。

包起來

本文特別關注 HTTP Live Streaming,但從概念上講,它也可以理解為對自適應比特率流 (ABS) 工作原理的解釋。 總之,我們可以說 HLS 是一種解決視頻流中許多重要問題的技術:

  • 它簡化了視頻文件的存儲
  • 內容分發網絡
  • 客戶端播放器處理不同的客戶端帶寬並在流之間切換
  • 本文未涉及的字幕、加密、同步播放和其他功能

無論您最終使用 HLS 還是 MPEG-DASH,這兩種協議都應該提供相似的功能,並且通過在 HLS 中引入分段 mp4 (fMP4),您可以使用相同的視頻文件。 這意味著在大多數情況下,您需要了解這兩種協議的基礎知識。 幸運的是,它們似乎朝著同一個方向發展,這應該使它們更容易掌握。