跳至主要內容
技術

按下播放之後,你的手機其實在狂抓上千個 6 秒小檔案:從我在 CatchPlay 做轉檔的經驗,聊聊串流、HLS 跟 DASH

按下播放之後,你的手機其實在狂抓上千個 6 秒小檔案:從我在 CatchPlay 做轉檔的經驗,聊聊串流、HLS 跟 DASH

先講結論:串流就是一張菜單,加上一大堆 6 秒的小檔案

你在手機上按下播放,感覺好像有一條水管,把整部電影咕嚕咕嚕灌過來。

其實不是這樣。

播放器會先下載一張純文字的「菜單」,上面寫著這部片有哪幾種畫質、每種畫質被切成哪些小檔案。接下來它就一段一段抓,每段大概 6 秒。每抓下一段之前,它都會先看一下自己現在網速多快,再決定這一段要抓高畫質還是低畫質。

算一下就知道有多碎:一部兩小時的電影,光一種畫質就會切出 1,200 個小檔案。

2016 到 2017 年我在 CatchPlay(台灣的影音串流平台)寫轉檔系統,每天在做的事,就是把一部部電影剁成這個樣子。這篇就把那套東西拆開來聊,裡面的檔案都是我在自己筆電上實際跑出來的。

先把整篇重點濃縮成一張表:

你可能以為其實是
影片用一條連線從頭傳到尾播放器一直發 HTTP 請求,一次抓一個 6 秒左右的小檔
畫質是網站幫你決定的是你手機上的播放器,每抓一段就自己決定一次
串流要用很特別的串流伺服器丟在最普通的網頁伺服器或 CDN 上就能播
HLS 跟 DASH 選一個就好2016 年的 CatchPlay 兩個都做,因為 Apple 的 DRM 走 HLS、Google 的 DRM 走 DASH
兩種格式就得存兩份影片現在可以用 CMAF,一份片段讓兩張菜單共用

丟一個 MP4 給你看不就好了?問題是你的網速一直在變

最直覺的做法,就是把電影轉成一個 MP4 放上網站,讓你邊下載邊看。

這招叫「漸進式下載」(progressive download),早年很多網路影片真的就是這樣播的。

但它有個大問題:畫質在你按下播放的那一刻就定死了。 你在家用 Wi-Fi 開了 1080p,出門搭捷運訊號一掉,它沒辦法半路換成低畫質,只能停在那邊轉圈圈。

串流的解法說穿了很暴力:既然網速會變,那就乾脆把影片切碎,讓播放器每隔幾秒重新挑一次。

這個做法叫「自適應串流」(Adaptive Bitrate Streaming,簡稱 ABR)。HLS 跟 DASH,就是兩種把它做出來的規格。

漸進式下載自適應串流
伺服器上放什麼一個 MP4好幾種畫質,每種切成上千段
畫質什麼時候決定按下播放那一刻每抓一段就重新決定一次
網速變差的時候卡住、轉圈圈下一段改抓低畫質,畫面糊一點但不會停

把它想成一間只賣 6 秒便當的店

這間店每道菜都有大、中、小三種份量,而且每個便當只裝 6 秒的劇情。櫃台貼著一張菜單,寫清楚大份要多少頻寬、小份要多少頻寬,還有每個便當放在哪一格。

你(播放器)坐在店裡,每吃完一個就瞄一下窗外的路況。路很順,下一個就點大份;開始塞車了,改點小份。畫面會糊一點,但至少不會停。

聰明一點的客人,還會在桌上先囤兩三個便當,就算外送突然延遲也不會斷糧。這疊存糧,就是播放器的緩衝區(buffer)。

至於老闆(伺服器),他根本不管你點什麼。他只負責把每種份量先做好、整整齊齊擺在櫃台,誰來拿就給誰。

聰明的全是客人,老闆越笨越好。 串流之所以便宜、又撐得住超大流量,關鍵就在這個分工,後面會一直繞回來講。

轉檔:同一部片,要先做成大中小好幾種份量

便當店開門之前,得先把每種份量都做好。這一步叫轉檔(transcoding),也就是我當年在 CatchPlay 主要在做的事。

為了寫這篇,我在自己的 M2 MacBook 上用 FFmpeg 8.0.1 重做了一個迷你版:拿一支 60 秒的彩色測試畫面,轉成 1080p、720p、360p 三種畫質的 HLS。

ffmpeg -i source.mp4 \
  -filter_complex "[0:v]split=3[a][b][c];[a]scale=1920:1080[v1];[b]scale=1280:720[v2];[c]scale=640:360[v3]" \
  -map "[v1]" -map "[v2]" -map "[v3]" -map 0:a -map 0:a -map 0:a \
  -c:v libx264 -preset veryfast -g 60 -keyint_min 60 -sc_threshold 0 \
  -b:v:0 5000k -maxrate:v:0 5350k -bufsize:v:0 7500k \
  -b:v:1 2800k -maxrate:v:1 2996k -bufsize:v:1 4200k \
  -b:v:2 800k  -maxrate:v:2 856k  -bufsize:v:2 1200k \
  -c:a aac -b:a 128k -ac 2 \
  -f hls -hls_time 6 -hls_playlist_type vod \
  -hls_segment_filename "%v/seg_%03d.ts" \
  -master_pl_name master.m3u8 \
  -var_stream_map "v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:360p" \
  "%v/index.m3u8"

跑出來長這樣:

畫質解析度設定的影像位元率實測(第 4 段,含音訊)平均每段大小
1080p1920×10805,000 kbps5,217 kbps3.96 MB
720p1280×7202,800 kbps2,993 kbps2.28 MB
360p640×360800 kbps964 kbps0.74 MB

這張表,業界叫它 bitrate ladder(位元率階梯)。實測會比設定值高一點點,多出來的是 128 kbps 的音訊,加上封裝本身的開銷。

階梯要排幾階、每一階給多少,其實是一門學問。Netflix 2015 年 12 月有一篇〈Per-Title Encode Optimization〉,講的就是「所有片子共用同一張階梯」的毛病:動畫根本用不到那麼高的位元率,底片顆粒很重的片子給到最高階卻還是有色塊。所以他們後來改成每部片各算一張自己的階梯。

60 秒的片、三種畫質,我的 M2 跑了 19.3 秒。

照這個速度粗估,一部兩小時的電影大概要 39 分鐘。而且這已經是最樂觀的算法了:正式上線的階梯通常不只三階,壓縮也會選更慢、更省頻寬的設定,再加上多語音軌跟字幕,每一項都會讓時間再往上乘。

這也是為什麼 CatchPlay 那套系統最忙的時候,會同時開 500 多台 AWS EC2 在轉檔。機器一開起來,會自己去中控資料庫問「我這台要做什麼」,領到工作就去把片子拉下來轉。那套系統是怎麼蓋起來的,我另外寫在〈500 台 EC2 同時轉檔的日子——CatchPlay〉。

切片:每 6 秒切一刀,而且每種畫質都得切在同一秒

轉好的影片不是一個大檔案,而是一疊小檔案:

$ ls -lh 1080p | head -5
total 77416
-rw-r--r--@ 1 bobochen  wheel   403B Sep 22 14:00 index.m3u8
-rw-r--r--@ 1 bobochen  wheel   3.8M Sep 22 14:00 seg_000.ts
-rw-r--r--@ 1 bobochen  wheel   3.8M Sep 22 14:00 seg_001.ts
-rw-r--r--@ 1 bobochen  wheel   3.8M Sep 22 14:00 seg_002.ts

1080p:       10 segments,  38M
720p:       10 segments,  22M
360p:       10 segments, 7.1M

三種畫質都是 10 段、每段 6 秒,加起來剛好 60 秒。(ls -h 顯示的 3.8M 是 1,024 進位的 MiB,換成上面表格用的 MB,大約是 3.96。)

重點在「同一秒」這件事。我把三份清單裡每一段的長度抓出來算雜湊值,結果三個一模一樣:

$ for d in 1080p 720p 360p; do grep EXTINF $d/index.m3u8 | md5; done
06e1bcc5a050beeb02f0b00dbd7e3f38
06e1bcc5a050beeb02f0b00dbd7e3f38
06e1bcc5a050beeb02f0b00dbd7e3f38

意思就是:三種畫質的切點完全對齊。播放器看完 720p 的第 3 段,下一段換成 360p 的第 4 段,畫面還是能無縫接上,因為這兩段都是從同一個畫面開始的。

能做到這樣,靠的是「關鍵影格」(keyframe)。

影片壓縮的時候,只有關鍵影格是一張完整的畫面,其他影格都只記「跟前一格差在哪」。所以每一段都一定要從關鍵影格開始。不然播放器一接手,手上只有一堆「差異」、沒有底圖,畫面就會花掉。

上面那串指令裡,有兩個參數就是在管這件事:

  • -g 60 -keyint_min 60:每 60 格固定放一個關鍵影格。這支片是 30fps,等於每 2 秒一個,所以每 6 秒的切點一定會落在關鍵影格上
  • -sc_threshold 0:關掉「偵測到換場,就自動多插一個關鍵影格」的功能。不關的話,每種畫質可能在不一樣的地方多插,切點就對不齊了

那每段到底要切多長?這是取捨。切短一點,換畫質反應比較快、直播延遲也比較低,可是請求數會變多、壓縮效率也會變差;切長一點就剛好反過來。

我會選 6 秒,不是隨便挑的。Apple 的〈HLS Authoring Specification for Apple Devices〉(最近一次修訂是 2025 年 6 月)第 7 節講得很白:

7.5. Target durations SHOULD be 6 seconds.

翻成中文就是:每段的目標長度「應該」是 6 秒。規格裡的 SHOULD 分量很重,意思是「強烈建議,除非你有很好的理由」。

m3u8 打開來看,其實就是一張純文字菜單

這是剛剛產出來的總菜單 master.m3u8,一字不漏:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=5330928,AVERAGE-BANDWIDTH=5280894,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
1080p/index.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=3057882,AVERAGE-BANDWIDTH=3038982,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
720p/index.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1021968,AVERAGE-BANDWIDTH=990409,RESOLUTION=640x360,CODECS="avc1.64001e,mp4a.40.2"
360p/index.m3u8

每一個 #EXT-X-STREAM-INF,就代表一種份量:

  • BANDWIDTH=5330928:這個畫質最高大概要 5.3 Mbps。播放器就是拿自己的網速跟這個數字比
  • RESOLUTION:解析度
  • CODECS:要用哪種解碼器。avc1 是 H.264 影像,mp4a.40.2 是 AAC 音訊。播放器如果看到自己不支援的格式,就會直接跳過這一行
  • 再下一行:這個份量自己的清單放在哪

點進 720p/index.m3u8,就是第二層:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:6.000000,
seg_000.ts
#EXTINF:6.000000,
seg_001.ts
...
#EXTINF:6.000000,
seg_009.ts
#EXT-X-ENDLIST
  • #EXTINF:6.000000:下面這個檔案長 6 秒
  • #EXT-X-PLAYLIST-TYPE:VOD 加上最後的 #EXT-X-ENDLIST:這是隨選影片,清單不會再變長了

直播跟隨選影片,差就差在這裡。直播的清單沒有 ENDLIST,播放器會每隔幾秒重新下載一次清單,看看最後面有沒有多出新的段落。

所以說,整部電影的「串流」,底層就是這兩層文字檔,加上一疊 .ts 小檔案。沒有什麼神秘的串流黑科技。

播放器才是老大,伺服器只是負責擺便當的

口說無憑,我用 Python 內建的 http.server 開了一個最陽春的靜態檔案伺服器,它完全不懂什麼是影片。然後叫 FFmpeg 從它那邊讀 720p、播 15 秒。

伺服器的存取紀錄長這樣:

"GET /master.m3u8 HTTP/1.1"
"GET /1080p/index.m3u8 HTTP/1.1"
"GET /720p/index.m3u8 HTTP/1.1"
"GET /360p/index.m3u8 HTTP/1.1"
"GET /1080p/seg_000.ts HTTP/1.1"
"GET /720p/seg_000.ts HTTP/1.1"
"GET /360p/seg_000.ts HTTP/1.1"
"GET /720p/seg_001.ts HTTP/1.1"
"GET /720p/seg_002.ts HTTP/1.1"

我們一行一行看:

  1. 先拿總菜單
  2. 三份子清單全都拿了
  3. 三種畫質的第 1 段各抓一次
  4. 接下來就只抓 720p 的第 2、3 段。15 秒剛好落在第 3 段裡,所以就停在這裡

第 3 步我要老實講:FFmpeg 其實不是會自己換畫質的播放器。它一開始把三種畫質都摸過一遍、確認格式,之後就照我指定的只抓 720p。會一邊量網速、一邊換畫質的,是 Safari、hls.js 這種真正的播放器。

不過伺服器這邊的結論不會變:它從頭到尾只做一件事,你要哪個檔案,它就給你哪個檔案。 誰在看、看到哪、網速多快,它通通不知道。

那真正的播放器是怎麼決定的?拿上面的實測數字算一次就懂了:

畫質平均每段要在 6 秒內抓完,網速至少要網速 4 Mbps 時抓一段要
1080p3.96 MB5.3 Mbps7.9 秒(比播放還慢)
720p2.28 MB3.0 Mbps4.6 秒
360p0.74 MB1.0 Mbps1.5 秒

假設網速掉到 4 Mbps,1080p 抓一段要 7.9 秒,可是播一段只要 6 秒。每播一段,緩衝區就少快 2 秒,撐沒多久就會卡住。所以播放器會在下一段改抓 720p。

整個過程畫成時序圖,大概是這樣(這是示意圖,不是上面的實測紀錄):

sequenceDiagram
    participant P as 播放器
    participant C as CDN(一般的 HTTP 伺服器)
    P->>C: GET master.m3u8
    C-->>P: 總菜單(三種畫質)
    P->>C: GET 1080p/index.m3u8
    C-->>P: 1080p 的分段清單
    P->>C: GET 1080p/seg_000.ts
    C-->>P: 第 1 段(下載很快,網速夠)
    Note over P: 進隧道,量到的網速掉到 4 Mbps
    P->>C: GET 720p/index.m3u8
    P->>C: GET 720p/seg_001.ts
    C-->>P: 第 2 段改用 720p,畫面無縫接上

也因為全都是普通的 HTTP 檔案,所以可以直接丟給 CDN 快取。一萬個人同時看同一部片的第 3 段,每個 CDN 節點只要回源站拿一次就好。

不過這也有副作用:檔案一旦被 CDN 記住,你在源站把它換掉,CDN 也不會知道,得手動叫它清掉。我在 CatchPlay 的時候,同事每天都要做這件事,後來我乾脆寫了一個清 CDN 快取的小工具。

HLS 跟 DASH 在做同一件事,只是分成兩個陣營

講到這邊,其實 HLS 已經講完了。上面那兩層 .m3u8 加一疊 .ts 小檔,就是 HLS。

它是 Apple 在 2009 年跟著 iPhone OS 3.0 一起推出的,2017 年 8 月整理成 RFC 8216。這份 RFC 摘要的第一句是這樣寫的:

This document describes a protocol for transferring unbounded streams of multimedia data.

白話一點講:這是一個用來傳送「沒有固定長度」影音資料的協定。「沒有固定長度」是重點,代表它一開始就把直播考慮進去了,所以清單才會設計成可以一直往後長。RFC 8216 描述的是協定第 7 版;更新的第二版,到 2026 年 9 月都還是草案,還沒正式定案。

DASH 做的也是同一件事,只是菜單長得不一樣。我用同樣的三階設定轉了一份 DASH,它的菜單叫 manifest.mpd,是一份 XML(節錄):

<MPD type="static" mediaPresentationDuration="PT1M0.0S" maxSegmentDuration="PT6.0S" minBufferTime="PT12.0S">
  <Period id="0" start="PT0.0S">
    <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true">
      <Representation id="0" codecs="avc1.640028" bandwidth="5000000" width="1920" height="1080">
        <SegmentTemplate timescale="1000000" duration="6000000"
          initialization="init-stream$RepresentationID$.m4s"
          media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1"/>
      </Representation>
      <Representation id="1" codecs="avc1.64001f" bandwidth="2800000" width="1280" height="720">...</Representation>
      <Representation id="2" codecs="avc1.64001e" bandwidth="800000" width="640" height="360">...</Representation>
    </AdaptationSet>
    <AdaptationSet id="1" contentType="audio">...</AdaptationSet>
  </Period>
</MPD>

一樣拿便當店來對照:

  • AdaptationSet 是一道菜(影像一道、音訊一道)
  • Representation 是這道菜的其中一種份量
  • SegmentTemplate 用樣板來描述檔名。$Number%05d$ 會被換成 00001、00002……這樣就不用把每一段都列出來

另外可以注意 startWithSAP="1" 跟 segmentAlignment="true" 這兩個屬性。它們其實就是白紙黑字寫出上一節講的事:每一段都從關鍵影格開始,而且各種畫質的切點是對齊的。

兩種格式的差別,整理成表:

HLSDASH
誰定的Apple,2009 年推出,2017 年變成 RFC 8216MPEG,國際標準 ISO/IEC 23009-1,2012 年 4 月第一版,已公開的最新版是 2022 年第 5 版
菜單.m3u8,純文字.mpd,XML
片段早年是 .ts,2016 年起也能用 fMP4fMP4(.m4s)
誰能直接播Safari、iPhone、iPad、Android;桌面版 Chrome 從 142 版(2025 年 10 月)起也行瀏覽器都沒有內建,要靠 JavaScript 播放器,像 dash.js、Shaka Player
常見的 DRM 搭配FairPlay(Apple)Widevine(Google)、PlayReady(Microsoft)

技術上它們比較像表兄弟,要選哪個,幾乎不是看誰技術比較強。真正決定你要做哪一個的,是你的使用者拿什麼裝置,還有片商要求你用什麼 DRM。

2016 年我們為什麼兩套都要做:因為 DRM 各自選了邊

CatchPlay 播的是片商授權的電影,影片得加密,也就是大家常聽到的 DRM(數位版權管理)。

DRM 大概是這樣運作的:片段本身是加密過的,就算被偷下載,拿到的也只是一堆亂碼。播放器要播之前,得先拿憑證去授權伺服器換一把金鑰。這把金鑰會被送進瀏覽器或手機裡一個你碰不到的解密模組,解出來的畫面直接送上螢幕,中間你完全摸不到。

麻煩的是,DRM 也分陣營:

DRM誰家的用在哪裡搭配的格式
FairPlayAppleSafari、iPhone、iPadHLS
WidevineGoogleChrome、AndroidDASH

Apple 的〈FairPlay Streaming Overview〉一開頭就寫,FairPlay 保護的內容 “is delivered over the Web using HTTP Live Streaming (HLS)“,HLS 規格第 13 節也只定義了 FairPlay 搭配 HLS 的加密方式。FairPlay 搭 DASH 要怎麼做,Apple 從來沒公開過。

反過來看,DASH 在 2016 年的 iPhone 上根本沒戲唱。瀏覽器要播 DASH,得靠 JavaScript 播放器把片段一塊一塊餵給瀏覽器,這需要一個叫 Media Source Extensions 的 API。而 iPhone 的 Safari,一直等到 iOS 17.1(2023 年 10 月)才開放類似的功能。

所以要讓 iPhone 跟 Android 的使用者都能看,就只能兩邊都做。我那時候就是 Widevine 跟 FairPlay 一起實作,DASH 跟 HLS 兩種封裝也一起產。

兩套並行的代價,就是每部片的封裝、加密、測試全都變成兩份。只要有一個環節沒對上,不管是金鑰錯了、格式切不過去,還是授權過期,使用者看到的都是同一個畫面:一片黑。

如果今天重做:CMAF 讓兩張菜單共用同一批片段

2016 年的時候,兩套格式的片段容器還不一樣:HLS 用 .ts,DASH 用 fMP4。同一部片就得存兩份。

巧的是,轉機剛好就在那一年。2016 年 6 月的 WWDC,Apple 宣布 HLS 也能用 fMP4 了,剛好就是我去 CatchPlay 報到的那個月。同一年 2 月,Apple 跟 Microsoft 一起向 MPEG 提案,想把這種兩邊都能用的片段格式定成標準,最後在 2018 年 1 月以 ISO/IEC 23000-19 發布,名字叫 CMAF(Common Media Application Format)。

從此片段只要做一份,HLS 跟 DASH 兩張菜單都指向同一批檔案就好。

FFmpeg 在輸出 DASH 的時候加上 -hls_playlist 1,就能一次產出兩張菜單。我跑出來的資料夾長這樣:

$ ls dash/
chunk-stream0-00001.m4s ... chunk-stream3-00011.m4s   (影像與音訊片段)
init-stream0.m4s ... init-stream3.m4s                  (初始化片段)
manifest.mpd                                           (DASH 的菜單)
master.m3u8  media_0.m3u8 ... media_3.m3u8             (HLS 的菜單)

打開 HLS 那邊的 media_1.m3u8,你會發現它指的就是 DASH 在用的同一批檔案:

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-MAP:URI="init-stream1.m4s"
#EXTINF:6.000000,
chunk-stream1-00001.m4s
#EXTINF:6.000000,
chunk-stream1-00002.m4s

用同一支 60 秒測試片算算看儲存量:

做法片段總大小能給誰用
HLS 用 .ts69.8 MB只有 HLS
DASH 用 fMP465.6 MB只有 DASH
2016 年的做法:兩份都存135.4 MB兩邊
CMAF:一份 fMP4、兩張菜單65.6 MB兩邊

(CMAF 那一列的 65.6 MB 已經把兩張菜單算進去了,反正文字檔也才幾 KB。)

如果今天要我重做這套系統,片段我只會做一份。

加密這關也在慢慢合流,只是腳步慢一點。FairPlay 用的加密模式叫 cbcs,Widevine 跟 PlayReady 早年只支援另一種叫 cenc 的模式。PlayReady 是從 4.0 版(2017 年 9 月)、Chrome 裡的 Widevine 大約是 2018 年中,才開始支援 cbcs。

換句話說,只要裝置夠新,連加密過的片段也能只做一份;但如果還要照顧舊電視、舊機上盒,就還是得多留一份 cenc。加密這段我這次沒有實測,上面的時間點是我查 Microsoft 跟 Chrome 的公開文件整理出來的。

3 分鐘,自己動手做一個 HLS

只要有 FFmpeg 跟 Python(macOS 內建就有)就能玩:

# 1. 先裝 FFmpeg(我用的是 8.0.1)
brew install ffmpeg

# 2. 隨便拿一支影片,轉成 6 秒一段的 HLS(每 6 秒放一個關鍵影格,不管影格率多少都適用)
ffmpeg -i input.mp4 -c:v libx264 -force_key_frames "expr:gte(t,n_forced*6)" \
  -c:a aac -f hls -hls_time 6 -hls_playlist_type vod out.m3u8

# 3. 開一個最普通的網頁伺服器
python3 -m http.server 8000

最小可用流程:跑完第 2 步 → cat out.m3u8 看看切了幾段 → 第 3 步把伺服器開起來 → 另開一個終端機跑 ffprobe http://127.0.0.1:8000/out.m3u8,有印出影像跟音訊資訊就代表讀得到。想看畫面的話,用 Safari 或 142 版以後的 Chrome 打開同一個網址,它們都能直接播 HLS。

我拿上面那支 60 秒測試片跑第 2 步,M2 花了 22.4 秒,產出 out0.ts 到 out9.ts,一共 10 段。

常用參數:

  • -hls_time 6:每段切幾秒
  • -hls_playlist_type vod:隨選影片模式,清單最後會加上 #EXT-X-ENDLIST
  • -force_key_frames 或 -g:控制關鍵影格的間隔,也就是決定能在哪裡下刀
  • -sc_threshold 0:做多畫質階梯時記得加,免得換場時多插關鍵影格、害切點對不齊
  • -var_stream_map 加 -master_pl_name:一次產出多種畫質跟總菜單
  • -f dash -seg_duration 6 -hls_playlist 1:產出 DASH,順便產一份共用片段的 HLS 菜單

反思:串流聰明的地方,全在你手上那台裝置

技術面

串流能撐住一萬個人同時看同一部片,靠的就是伺服器夠笨。所有判斷都丟給播放器,伺服器只剩「給檔案」這一個動作,所以可以整個交給 CDN,要複製幾份都行。

到了 2026 年,HLS 跟 DASH 的差別主要在 DRM 跟裝置生態,不是誰技術比較好。把 2016 年跟今天擺在一起看最清楚:

2016 年的做法今天重做
片段HLS 用 .ts、DASH 用 fMP4,存兩份CMAF,fMP4 存一份
菜單.m3u8 跟 .mpd 各一份一樣各一份,幾 KB 的文字檔
加密FairPlay 跟 Widevine 各加密一次裝置夠新就用 cbcs 加密一次
iPhone 網頁播 DASH不可能iOS 17.1 起可以,但通常還是直接給 HLS

心態面

當年在 CatchPlay,我一直覺得轉檔系統最難的是「規模」:500 台機器要怎麼調度、哪台掛了要怎麼重派。

現在回頭看,規模反而是最能用錢解決的那一塊。真正應該一開始就想清楚的,是每段切幾秒、關鍵影格怎麼放、階梯排幾階這些決定。它們在指令裡只是幾個參數,可是一旦設錯,就得把整個片庫重轉一遍。這點我當年其實沒看得這麼清楚。

有趣發現

  • 我本來想拿 FFmpeg 示範「播放器換畫質」,結果一看伺服器紀錄才發現,它根本不會換,只是一個看得懂 m3u8 的下載器。果然看紀錄比相信自己的印象可靠。
  • m3u8 這個副檔名,來自 MP3 年代的播放清單格式 M3U,後面那個 8 代表 UTF-8 編碼。所以串流影片的菜單,本質上跟你二十年前存在電腦裡的歌單是同一種東西。

說到底,串流其實沒有在「流」,它是你的播放器每 6 秒做一次的選擇。

留言討論

esc
輸入關鍵字搜尋文章...
查看收藏 →