發佈日期:

編碼與封裝

前言

要說到影片的編碼與封裝,就要先聊聊影片是怎麼產生的。相信大家小時後都有看過翻頁動畫吧,就是由很多張圖片組成的書,在快速翻書時就可以看到一幅連續的動畫,如下圖:
翻頁動畫
影片的原理也是如此,一張張的照片,經過快速的連續切換,就成為了動態影片。但是這樣子的原始影片大小非常的可觀,若影像的每個畫素的三個顏色RGB各需要一個位元組儲存,每一個畫素需要3位元組,解析度1280×720的影像的大小為2.76M位元組,若每秒FPS為25偵,所需的位元率會達到553Mb/s。這種大小不論是儲存或用網路傳輸都是有困難的,因此編碼壓縮勢在必行。

資料壓縮的難題

音檔和視訊有著完全不同的編碼方式和壓縮理論,音訊編碼的難點在於延時敏感、卡頓敏感、噪聲抑制(Denoise)、回聲消除(AEC)、靜音檢測(VAD)、混音演算法等…每個項目都有各別的演算法去深究如何優化音訊的擷取。

而視訊壓縮編碼的難點則在於編碼效率和編碼複雜度的平衡。例如H.265較H.264在相同的位元率之下有更好的畫質呈現、更小的檔案大小,但相較起來,編碼的複雜度約增加了近十倍。但在實際的應用當中,大多數狀況的編碼端及解碼端的電腦資源不固定,因此在編碼複雜度和編碼效率中取得平衡則是很重要的事。

H264和H265的比較圖
264vs265
來源: H.264和H.265(HEVC)深度解析及對比

影片壓縮衡量單位

位元速率

網路影音多媒體包括音視頻在單位時間內的資料傳輸率時通常使用碼流位元速率來表示,代表每秒傳輸或處理的位元量(資料流量),單位 Mbps (Mb/s) 。
亦可說是所需的最低下載速度,下載速度越低,需越高的壓縮,尤其是破壞性壓縮,故位元速率會影響到影片品質。

  • 相同解析度下,位元率越高,每秒包含的資訊越多,檔案越大、壓縮比越小,影音品質越佳,越吃頻寬及電腦的運算能力,配備不夠好者可能會很卡。
  • 使用較低的位元速率輸出,在靜止畫面差異較小,動態畫面容易產生色塊,畫質低落,整體較不清晰。

我們可以從位元速率去推算最終整個串流檔案的大小,完整的影片則包括『視頻』及『音頻』兩個軌道,其公式如下:

檔案大小(MB為單位) = (音訊編位元速率(KBit為單位)/8 + 視訊編位元速率(KBit為單位)/8)× 影片總長度(秒為單位

而音檔的位元速率公式如下:

位元速率(Bit Per Second) = 取樣率(Hz) x 採用位數 x 聲道數

通常,影片位元速率會受到FPS(每秒幀數)、解析度(指輸出的影像尺寸)、編碼方式(有VBR、CBR、ABR三種)、壓縮演算法(如:H.264、H.265和VP9)影響,音檔則受到取樣率、採用位數(指單次取樣儲存時所占大小,例如電話就是3kHZ取樣的7位聲音,而CD是44.1kHZ取樣的16位聲音)以及聲道數影響,以上幾項的選擇目標是達到位元速率的最小化播放畫質最佳化之間的理想的平衡。

下面針對上面提到的幾個名詞做解釋:

FPS

FPS是Frames Per Second的縮寫,是指每秒鐘重新整理的圖片的幀數,也可以理解為圖形處理器每秒鐘能夠重新整理幾次。越高的幀速率可以得到更流暢、更逼真的動畫。每秒鐘幀數(FPS)越多,所顯示的動作就會越流暢。

解析度

編碼方式

壓縮演算法

影像壓縮原理

資料壓縮是透過去除資料中的冗餘資訊而達成。就視訊資料而言,資料中的冗餘資訊可以分成四類:

  1. 時間上的冗餘資訊(temporal redundancy)
    在視訊資料中,相鄰的影格(frame)與影格之間通常有很強的關連性,這樣的關連性即為時間上的冗餘資訊。

  2. 空間上的冗餘資訊(spatial redundancy)
    在同一張影格之中,相鄰的像素之間通常有很強的關連性,這樣的關連性即為空間上的冗餘資訊。

  3. 統計上的冗餘資訊(statistical redundancy)
    統計上的冗餘資訊指的是欲編碼的符號(symbol)的機率分布是不均勻(non-uniform)的。

  4. 感知上的冗餘資訊(perceptual redundancy)
    感知上的冗餘資訊是指在人在觀看視訊時,人眼無法察覺的資訊。

發佈日期:

影音服務介紹

網路串流服務介紹

近年來影音相關的服務越來越火紅,許多的社交軟體都用直播影片來取代舊有的圖文內容。雖然網路影音服務在2000年左右就已經出現,但由於當時的移動設備和網路頻寬的限制,使得網路影音的發展受到很多限制。而在2013年後網路直播開始爆發,進入了直播影片的年代,一開始的網路直播以PC為主,而在移動設備普及後,各種社群媒體的APP更是紛紛支援直播串流功能。因為直播串流的普及、電腦設備及網路速度的進步下,也新興了如了Youtuber、實況主、直播主等這種專門經營此區塊的行業,可謂是非常火紅且受到矚目的一個領域。近年來,通信行業也更多的走向網路化,通訊軟體如Line、Facetime等,漸漸取代了過去的電話、簡訊。最近因5G和IoT的發展,未來應有更多的領域會走向網際網路化。

所有網路影音相關的服務,大致分為『點播』、『直播』和『錄播』。

  1. 所謂點播,其英文為Video On Demand,簡稱VOD。其中Demand意為需求,從字面上理解點播,指的是使用者點選想要看的影片,並將該影片使用實時串流的方式播放出來。相關的服務如:Netflix、Apple TV、HBO等…
    Video On Demand

  2. 直播的英文為Live broadcast,則是直播音視頻會以媒體流的形式推到服務器上(推流)。如果有觀眾收看直播,服務器收到用戶的請求後,會把視頻傳輸到網站、APP、客戶端的播放器,即時播放串流影片。相關的服務平台有Youtube、Facebook Live、Twitch等…
    Live broadcast

  3. 錄播: 一個完整的錄播系統包含了錄製剪輯、直播推送、影片處理等核心功能,配備了相關的軟體和硬體。能夠按照標準產出比較高質量的影片內容,較多使用在線上教學系統上。
    Live record

直播服務的原理

而這30天的系列文章,我們主要會著重在直播的研究,一個完整的直播服務會牽涉到非常多面項領域的技術,從視頻/音頻處理,圖形處理,視頻/音頻壓縮,CDN分發,即時通訊等技術等,每個項目都有很深的技術背景,都需要以年來計算的去鑽研,因此許多部份只會提及基本概念(但光概念就有一大堆艱深知識了…XD…推薦這個系列文,把許多概念知識都整理的很清楚: 30天之即時網路影音開發攻略(小白本))

本系列文主要介紹的重點會放在開源串流伺服器SRS的架設與影片品質調校上(以及相關必要知識)的介紹。

一般來說,一個影片的直播流程要經過以下環節:
採集影像 —> 影像處理 —> 編碼 —> 封裝 —> 推流 —> 串流伺服器 —> 拉流 —> 解封裝 —> 解碼 —> 播放
各個環節都有相關的技術或現成可使用的軟體,如下圖為以SRS伺服器的流程為例:
直播流程

其中,上述的事情發生在三個端點: 直播主的電腦、串流伺服器、觀眾的電腦,各個事件發生的地點如下圖:
直播流程

  1. 採集: 從系統的採集設備中獲取原始音視頻數據,將其輸出到下一個環節。一個影片的採集涉及兩方面數據的採集:音頻採集和影像採集,它們分別對應兩種完全不同的輸入源和數據格式。

  2. 編碼與封裝: 對於視訊資料而言,視訊編碼的最主要目的是資料壓縮。因為動態影像的畫素形式,資料量極為巨大,儲存空間和傳輸頻寬完全無法滿足儲存和傳輸的需求。舉例來說,若影像的每個畫素的三個顏色RGB各需要一個位元組儲存,每一個畫素需要3位元組,解析度1280×720的影像的大小為2.76M位元組,若每秒FPS為25偵,所需的位元率會達到553Mb/s。這樣的資料量無論是儲存或傳輸都不可能,因此編碼非常重要,編碼性能、編碼速度和編碼壓縮比會直接影響整個流媒體傳輸的用戶體驗和傳輸成本。這一部份之後會有各別的文章去介紹。

視訊資訊之所以存在大量可以被壓縮的空間,是因為其中本身就存在大量的資料冗餘。其主要型別有:

  1. 時間冗餘:視訊相鄰的兩幀之間內容相似,存在運動關係
  2. 空間冗餘:視訊的某一幀內部的相鄰畫素存在相似性
  3. 編碼冗餘:視訊中不同資料出現的概率不同
  4. 視覺冗餘:觀眾的視覺系統對視訊中不同的部分敏感度不同
    來源: https://www.itread01.com/content/1547220622.html
  1. 推流: 推流是影響整個直播串流能不能順暢播放的最根本因素,若是步驟2的影片編碼的編碼器效能不好、網路速度不夠,或者編碼的壓縮品質不佳,那麼後面的串流服務再怎麼好,使用者的影片觀看體驗和順暢度也不會好。因此步驟2的編碼,會連帶影響到步驟3的推流的順暢度。因此,像一些推流軟體如OBS,會自動偵測直播主的電腦配備和網路頻寬,去選擇適合的影片壓縮位元率(影響影片的品質)、編碼格式(VP9、MPEG或H.264)、編碼工具(如Quick Sync H.264或x264),以及設定適合的buffer,來達到讓推流能夠順暢的目的。
    現有推流最廣泛被使用的通訊協定為RTMP(Real Time Messaging Protocol),大部份的推流軟體都使用這個協定去做推流。
    FME推流介面
    圖片: FME推流介面

  2. 串流伺服器: 主要的工作為接收推流、轉發給拉流客戶端。現在的直播服務由於需要支援行動設備,隨著flash從網頁裡被淘汰,網頁端多已不能支持rtmp流協定的播放。但因推流的協定仍多為RTMP,因此大多需要經過轉碼的動作,轉為HLS或HTTP-FLV的格式,以支援行動端的播放。這部份伺服器的轉碼工作也會影響到直播的延遲時間。
    所謂『延遲』(latency)就是從直播端到播放端的時間差,造成延遲的原因有很多,因使用網路傳輸,影像串流需要經過編解碼並即時於使用者端播放。考量到網路狀況可能不穩定,又需顧及影片播放的順暢性,客戶端播放器的緩衝設定以及其解碼的速度,也是造成延遲的一大主因。不同傳輸方式其搭配的容器格式亦會影響到延遲的時間,一般來說RTMP的延遲時間約為0.3-1秒間,HTTP-FLV約1-3秒,而HLS則需要至少10秒以上的延遲(以最佳狀況來說)。
    目前市面上較受歡迎的串流伺服器有:
    *FMS: FMS是adobe的流媒體服務器,RTMP協議就是adobe提出來的,FMS一定是重量級的產品。
    *WOWZA: 由Wowza Media Systems開發的串流媒體服務器軟體
    *SRS: 本系列文主要探討的串流伺服器,產品定位是商用互動式社群直播伺服器叢集,支持K8S。
    *NGINX RTMP: 現在非常火紅並且被廣泛使用的開源伺服器
    *CRTMPD: 使用單線程異步socket,在當時處於領先水平,但是當NGINX出現後就漸漸淡出大眾視野了

  3. 拉流: 拉流是指伺服器已有直播內容,根據協議類型(如RTMP、RTP、RTSP、HTTP等),與伺服器建立連接並接收數據,進行拉取的過程。因為RTMP的協定較容易被防火牆檔掉,因此主要移動端的播放都採用HTTP的網路協定去做拉流,包括常見的HTTP-FLV與HLS。

  4. 解碼與播放: 其實所有的串流都會包括音視頻兩個部份,在解碼時會分別解碼音頻和視頻,並且將兩個搭配起來。在這邊播放會遇到的挑戰,很重要的部份就是buffer的設製,buffer會影響到三個點:『首屏』、『延遲』、『卡頓』。首屏指的是點擊畫面後到第一個畫面出來的時間、延遲是指與直播端的時間差、而卡頓則是影片播放時畫面不順暢的次數或時間。一般來說,若觀看端的buffer時間較長,從點擊到看到第一個畫面的時間也會較長、總延遲時間也會變長,但可以在網路狀況較不穩定下仍能維持一定的播放品質。另外若buffer設定的過短,機器的解碼的速度在電腦lag時短暫趕不上,就有可能會出現跳屏的狀況(ex: 從1秒直接跳到3秒),因此這部份也需要經過仔細的調校和設定。

各種傳輸協定比較表

RTMP HTTP-FLV HLS
延遲 0.3-1s 2-3s
傳輸協議 TCP HTTP
瀏覽器支持 N Y
數據分段 連續流 連續流

一般來說,在架設串流伺服器時,應考量用途和需求,去決定要使用那一種直播協議,每一種格式都有其優缺點,這邊有相關的比較文章:RTMP、HTTP-FLV、HLS,你了解常見的三大直播協議嗎

參考資料

發佈日期: 2 則留言

Linux 設定排程 – crontab

查看與編輯 crontab

查看自己的 crontab
crontab -l
查看指定使用者的 crontab
sudo crontab -u gtwang -l
編輯 crontab 內容
crontab -e
編輯指定使用者的 crontab
crontab -u gtwang -e
刪除 crontab 內容
crontab -r

crontab設定檔撰寫教學

# For details see man 4 crontabs

# Example of job definition:
# .—————- minute (0 – 59)
# | .————- hour (0 – 23)
# | | .———- day of month (1 – 31)
# | | | .——- month (1 – 12) OR jan,feb,mar,apr …
# | | | | .—- day of week (0 – 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

範例如下:
# 每天早上 8 點 30 分執行
30 08 * * * /home/gtwang/script.sh –your –parameter

# 每週日下午 6 點 30 分執行
30 18 * * 0 yourcommand

# 每週日下午 6 點 30 分執行
30 18 * * Sun yourcommand

# 每年 6 月 10 日早上 8 點 30 分執行
30 08 10 06 * yourcommand

# 每月 1 日、15 日、29 日晚上 9 點 30 分各執行一次
30 21 1,15,29 * * yourcommand

# 每隔 10 分鐘執行一次
*/10 * * * * yourcommand

# 從早上 9 點到下午 6 點,凡遇到整點就執行
00 09-18 * * * yourcommand

crontab設定檔的特殊字元

特殊字元 代表意義
星號(* 代表接受任意時刻,例如若在月份那一欄填入星號,則代表任一月份皆可。
逗號(, 分隔多個不同時間點。例如若要指定 3:00、6:00 與 9:00 三個時間點執行指令,就可以在第二欄填入 3,6,9
減號(- 代表一段時間區間,例如若在第二欄填入 8-12 就代表從 8 點到 12 點的意思,也就是等同於 8,9,10,11,12
斜線加數字(/n n 代表數字,這樣寫的意思就是「每隔 n 的單位」的意思,例如若在第一欄填入 */5 就代表每間隔五分鐘執行一次的意思,也可以寫成 0-59/5
發佈日期:

解決Windows下路徑名稱異常問題

名稱有中文或空白的問題

使用cmd指令時,有許多command line執行的工具,當路徑若出現中文或是空白時,會導致執行失敗。
例如C:\Program Files因為中間有一個空格,就很容易造成在執行時出現錯誤

若遇到這種狀況,建議可以改用資料夾的縮寫
例如
C:\PROGRA~1取代C:\Program Files
C:\PROGRA~2取代C:\Program Files (x86)

如何查找資料夾名稱縮寫

C:\Users\claire.chang>dir /x

以上圖來說,資料夾.android的縮寫即為ANDROI~1

發佈日期:

Linux下大檔案的log分割處理

使用指令分割大檔案

按檔案大小分割
[root@localhost]$ split -C 100M large_file.txt stxt
按行數分割
[root@localhost]$ split -l 1000 large_file.txt stxt
二進位檔案分割(以-b引數來指定分割後的檔案大小)
[root@localhost]$ split -b 100M data.bak sdata

固定時間切割LOG檔案

logrotate官方說明: https://linux.die.net/man/5/logrotate.conf

logrotate旨在簡化對生成大量日誌文件的系統的管理。它允許自動旋轉,壓縮,刪除和郵寄日誌文件。每個日誌文件可以每天,每週,每月或當文件太大時進行處理。

通常,logrotate作為日常cron作業運行。除非該日誌的標準基於日誌的大小並且每天要多次運行logrotate,或者除非使用了-f或–force選項,否則它不會在一天內多次修改日誌。

命令行上可以提供任意數量的配置文件。較新的配置文件可能會覆蓋較早的文件中提供的選項,因此列出logrotate配置文件的順序 很重要。通常,應使用包含所需其他任何配置文件的單個配置文件。有關如何使用include指令完成此操作的更多信息,請參見下文。如果在命令行上給出了目錄,則該目錄中的每個文件都將用作配置文件。

如果未提供命令行參數,logrotate將打印版本和版權信息以及簡短的使用摘要。如果輪換日誌時發生任何錯誤,logrotate將以非零狀態退出。

閱讀全文 Linux下大檔案的log分割處理

發佈日期: 2 則留言

Linux刪除檔案後空間未釋放

查詢硬碟使用狀況

[root@localhost ~]# df

使用df可以查看硬碟的使用狀況

查詢刪除卻正在被使用的檔案

lsof(lsof的全稱是list open files),此工具可以用來查看正在運行中的進程打開了哪些文件、目錄和套接字;是系統監測工具之一。
請參見: 好用的網管指令-lsof

查找打開,但是不能連接的文件
一個進程打開一個文件, 然後將其設為 unlinked 狀態, 則此文件資源仍能被進程使用, 但是其訪問路徑已經被刪除了.
因此, 使用ls不能將其列出. 只有當進程結束時, 才能釋放文件佔用的資源

查找unlinked 文件, 選項 +L, 作用: 列出打開文件的連接數

[root@localhost ~]# lsof +L


指定連接數的上限

[root@localhost ~]# lsof +L1
閱讀全文 Linux刪除檔案後空間未釋放
發佈日期:

Socket.io錯誤訊息意義

I have found:

  • “ping timeout”: client stopped responding to pings in the allotted amount of time (per the pingTimeout config setting).
  • “transport close”: this appears to happen if the client side stopped sending data at all… or maybe there’s some kind of callback causing this to happen. I can see it happen if I just close a tab or follow a link from a page where I have an active connection to the server. But I’m not clear if this is always a case of the client causing it to happen.

    Sorry for re-opening the issue. Regarding the transport close that is the reason when page is closed/reloaded, it also happens some times in bad network conditions specifically when the ping packets are not delivered to the client. For the latter I need to handle it in the server side by waiting for the client to reconnect. Is there a way to properly distinguish between these two?

  • “Client namespace disconnect”: When the client sends a disconnect packet (client.disconnect())
  • “server namespace disconnect”: Looks to be when the server performs a socket.disconnect() action.
  • “Transport error”: An error occurred, I assume this is a server side error, but I’m not totally clear, as I’ve not been able to trigger one on my own.
  • “io server disconnect” This occurs using a third party library socketIOAuth when authentication fails

閱讀全文 Socket.io錯誤訊息意義

發佈日期:

Socket.io介紹

Socket.io

socket.io是基於Websocket的Client-Server實時通信庫

Socket.io承繼了Node.js的事件處理方法,把Client端與Server端的程式統一成一至的操作方式,讓使用者可以只需專注在處理「事件」,就可以快速開發出應用,他也支援『房間』的概念,可以使用同一條WebSocket卻擁有不被彼此干擾的資料傳輸(多種聊天頻道的概念)。另外,他也提供了很好的fallback機制,即使用戶的瀏覽器不支援WebSocket,他還是可以利用Flash、XMLHttpRequest等方式來傳送資訊(速度會比較慢就是了)。這些機制都他都包裝好了,所以寫程式時並不需要知道這些細節,只需要設定好就可以運作。

Socket.io 特性整理

  • Events 自訂事件。
  • Rooms Room 的概念只存在於伺服器端。可以理解為訊息處理時的聽眾分組,可對同一個分組內的聽眾進行廣播。
  • Namespaces 命名空間,我理解為底層連線的分組管理,不同命名空間可以走同一條 Engine.io 連線或是各自連線,每個命名空間可以各自驗證是否接受連線。
  • ACK 回調 如同 HTTP 之於 TCP,HTTP 為 TCP 提供了一套請求與響應的模型。ACK 也為 Socket.io 提供了一套請求與響應的通訊模型。
  • 連線維護
  • 自動斷線重連
  • ping/pong 心跳

閱讀全文 Socket.io介紹

發佈日期:

Socket.io自行增加header

伺服器端

範例程式碼:

import express from "express";
import http from "http";

const app = express();
const server = http.createServer(app);

const sio = require("socket.io")(server, {
    handlePreflightRequest: (req, res) => {
        const headers = {
            "Access-Control-Allow-Headers": "Content-Type, Authorization",
            "Access-Control-Allow-Origin": req.headers.origin, //or the specific origin you want to give access to,
            "Access-Control-Allow-Credentials": true
        };
        res.writeHead(200, headers);
        res.end();
    }
});

sio.on("connection", () => {
    console.log("Connected!");
});

server.listen(3000);

閱讀全文 Socket.io自行增加header