Kalan's Blog

Current Theme light

解析日本電視字幕之旅 | 興趣使然的研究之旅

前言

不知道大家有沒有在日本看過電視呢?在日本,放送的信號裡頭有許多資訊,包含節目時間表、電視影像(還可以切換解析度)、字幕等等。所以在電視裡頭可以開啟與關閉字幕。

那麼實際上到底是怎麼運作的呢?今天就來聊聊有關於日本電視放送的字幕解析。

會開始研究這部分的技術,是因為謙謙之前的推文,再加上之前就想要試試看能不能夠用 tuner 接電腦來看電視,意外發現裡頭有許多技術細節很值得學習。

日本的電視放送

日本的電視放送是由日本自主制定的協定,叫做 ISDB(Integrated Services Digital Broadcasting),透過無線基地台發送信號,各個家庭接收到電視信號之後,在解密並復原成原始資料。

B-CAS

日本的電視信號為了防止無限地被複製及保護版權,其實是有先經過加密才發送出來的。這部分被規範在 ARIB(STD-B25)裏頭。而 B-CAS 就像是一把鑰匙,必須要有這張卡片才能看電視。如果有在日本買過電視的話,買電視的時候通常會附上這張 B-CAS 卡一起賣,否則是沒辦法看到影像的。

廣播電台會和願意遵守複製規範的廠商簽約,並直接將密鑰訊息給這些廠商,廠商再去製作已經含有金鑰的硬體,例如現在很多 USB 型的調解器,只要插進去電腦就可以接收信號並看電視。然而這類型的調解器通常需要下載他們專用的播放器才能正常瀏覽。原因就是因為要防止隨意複製。

MPEG2-TS

MPEG2-TS 是一種影片的封裝格式,日本的放送傳輸通常都是使用 MPEG2-TS。這邊的 TS 全稱是 Transport Stream。視訊通常是用 h.262 編碼;音訊則是用 AAC 編碼。h.262 由於比較舊,所以同樣的影片編碼,大小會比較大。一般我們在電腦上看影片 .mp4 通常都是用 h.264 在編碼。

在封包傳送上,每個 TS 封包最大為 188bytes。在無線傳輸時可能會有雜訊、錯誤,188bytes 大小不大,可以減少延遲,同時也比較容易做錯誤復原。

TS 封包的各種敘述可以參考這張圖。

引用自 https://www.researchgate.net/figure/Detailed-structure-of-the-MPEG-2-transport-stream-TS_fig16_41949828

Detailed-structure-of-the-MPEG-2-transport-stream-TS

其中幾個比較重要的欄位有幾個:

  • sync_byte:都會是 0x47
  • PID:Packet 的識別符,通常用來判斷這個 packet 的內容是什麼
  • PAT:PID mapping table
  • PES:字幕、音訊、影像都會包在 PES 裏頭

解析字幕

關於放送的技術細節雖然有很多地方可以研究,不過礙於篇幅關係,我們只專注在解析字幕的部分。字幕的解析與傳送是由 ARIB-B24 所規範。

大致上的過程如下:

  • 讀取封包直到找到 0x47
  • 將資料切成 188bytes 方便解析
  • 從 PAT 當中尋找 pid 的 mapping(PAT 的 pid 為 0)
  • 找到 caption 的 pid 後開始解析資料

data unit

一個 caption data 除了「文字的訊息」之外,其實還包含了很多資訊,像是顏色、形狀、字幕應該要表示的位置等等,這些資料會透過 data unit 來區分。而文字訊息的 data unit 為 0x20

function parseText(data, length) {
  const str = data.slice(0, length + 1);
  let result = "";
  let i = 0;

  while (i < length) {
    if (str[i] === 0x20) {
      result += " ";
      i += 1;
    }

    // // JIS X 0208 (lead bytes)
    if (str[i] > 0xa0 && str[i] < 0xff) {
      const char = str.slice(i, i + 2);
      if (str[i] >= 0xfa) {
        result += parseGaiji(char);
        i += 2;
      } else {
        const decoded = new TextDecoder("EUC-JP").decode(char);
        result += decoded;
        i += 2;
      }
    } else if (Object.values(JIS_CONTROL_FUNCTION_TABLE).includes(str[i])) {
      console.log("JIS_CONTROL_TABLE!");
      i += 1;
    } else if (str[i] >= 0x80 && str[i] <= 0x87) {
      console.log("color map");
      i += 1;
    } else {
      i += 1;
    }
  }

  console.log(result);
  document.querySelector("#result").innerHTML += result + "<br/>";
}

由於這邊的文字是用 JIS-0208 的字集,所以要另外解碼。不過在 JavaScript 中可以使用 TextDecoder,剛好也有支援 EUC-JP,因此直接使用 new TextDecoder('EUC-JP').decode 即可。

另外在日文表示當中還有所謂的「外字」,是 ARIB 定義的,並不存在於 JIS-0208 的文字。主要用來顯示資訊,或是播放跑馬燈等等。https://zh.wikipedia.org/wiki/ARIB%E5%A4%96%E5%AD%97

![截圖 2023-09-30 23.17.08](/Users/chenkaiyi/Desktop/截圖 2023-09-30 23.17.08.png)

這類型的文字就必須另外去解析。成功解析字幕之後,最重要的是要如何在正確的時間點顯示在螢幕上,在信號傳輸的時候會傳送一個 time table 用來對時。

Demo

由於沒辦法直接將影片資料上傳的原因,這邊只能給大家看程式碼與截圖,如果有興趣的話可以自行實作看看。

![截圖 2023-09-30 23.26.40](/Users/chenkaiyi/Desktop/截圖 2023-09-30 23.26.40.png)

https://github.com/kjj6198/ts-arib-parse/tree/master

其他技術細節

由於是使用 h.262 編碼,因此對大部分瀏覽器來說都不支援,如果要在網頁上看的話,要嘛是用 WebAssembly 去軟解 h.262,但是這樣會非常吃 CPU 的效能,還沒看影片就要先下載一大坨 WASM,用來玩玩還好但實際使用者體驗會非常糟。因此大部分的情況下需要另外用 ffmpeg 等工具將 h.262 改為 h.264 才能在網頁上播放。

目前比較有名的開源方案是 mirakurun,原理是起一個伺服器然後不斷地將放送信號改為 h.264 回傳,這樣一來就算在瀏覽器也能直接觀看。

除此之外,也有一些開源方案可供參考:

後記

就算只是顯示字幕,背後仍然有許多技術細節值得學習,這次實作的過程中也學習到很多東西,像是我原本不知道 MPEG-TS 的格式;也完全不知道 ARIB、ISDB-T 的存在,但其實這些協定已經支撐著日本放送很多年了。

實際操作之後也會發現並不像想像中的那麼難(單就字幕解析而言,復元原始信號那邊我完全不懂),只要耐著性子看文件也可以實作出來。

If you found this article helpful, please consider buy me a drink ☕️ It'll make my ordinary day shine✨

Buy me a coffee