寫在前面

從18年12月接手在基於x86平臺的邊緣計算設備上進行取流解碼的工作至今,已有數月。筆者還記得當初對流媒體、視頻、幀、圖像等概念完全雲裡霧裡,慢慢跟著項目一步步學習走過來,受益良多,以這篇文章勵志作為後續繼續學習的裡程碑吧!

本文將介紹的是:

  1. 視頻的基礎知識。包括:視頻協議和格式、視頻流。
  2. 視頻幀的基礎知識。包括:YUV幀格式、常見的幀名詞(幀率fps、分辨率、碼率)、“奇怪”的幀名詞(1080p和1080i)、視頻編解碼而衍生的幀名詞(GOP、IBP幀)。

修改記錄

2019年4月8日首次完成該文章,內容包括:

  1. 視頻協議、格式、播放原理等基礎內容;
  2. YUV格式、fps、分辨率、GOP和I/B/P幀等視頻幀相關的基礎知識;
  3. 提到瞭H264等視頻壓縮技術。

2019年9月7日進行二次修改,修改內容如下:

  1. 第一章節增加視頻流介紹,刪除瞭播放原理介紹;
  2. 第二章增加瞭YUV采樣和存儲格式的示意圖,場、1080p和1080i的介紹;刪除瞭YUV的顏色值域(Color Range)介紹;
  3. 刪除瞭原第三章的內容,這塊會在後續中出專文介紹。

I. 視頻的基礎知識

相信所有人對視頻一定不陌生,平時也一定經常在各大視頻網站(如騰訊視頻、嗶哩嗶哩)瀏覽,甚至偶爾也會把視頻緩存到本地,保存成.mkv,.avi文件之類啦。前者是我們常說的『網絡流媒體』,後者是『本地視頻文件』。提到這裡,兩個問題來瞭:

  1. 本地視頻文件常見有MP4、MKV、AVI等,這些都是什麼?有什麼區別?
  2. 在騰訊視頻、嗶哩嗶哩網上看的視頻,與本地播放的MP4、MKV、AVI文件,有什麼區別?

介紹第一個問題之前,必須引入一個名詞『視頻封裝格式』,簡稱『視頻格式』,也稱為『容器』。有的說法還要區分是視頻文件格式和視頻封裝格式,本文統一稱『視頻封裝格式』。

視頻格式

問題1:本地視頻文件常見有MP4、MKV、AVI等,這些都是什麼?有什麼區別?

首先,MP4、AVI、MKV都是本地視頻文件的後綴,在windows系統下,用於提示操作系統應該采用哪個應用程序打開。而在流媒體領域,這些都被稱為『視頻封裝格式』,因為除瞭音視頻流之外,它們還包含瞭一些輔助信息以及組織視音頻的方式。不同格式的視頻在不同平臺上用戶體驗不同,很大原因在於對視音頻的組織方式帶來的差異。筆者以為百度百科上的解釋蠻通俗易懂的(維基百科的說法不夠直白):

簡言之,視頻格式規定瞭和播放器的通信協議。

其次,筆者最近準備開始深入研究MP4、AVI、MKV等內部原理,主要是對視音頻的組織方式,比如在播放視頻的時候,我們可以選擇國語、粵語、英語等各種語言,這就意味著這段視音頻中包括瞭多個音頻流。【給自己留個坑吧。】

最後,筆者推薦一篇非常棒的博客:視頻文件格式知多少,匯總的非常全。

問題1引申:對要做視音頻處理的開發們來說,接觸MP4、MKV、AVI等各種格式視音頻文件時,有什麼需要註意的嗎?

視音頻處理可以延展出很多領域,包括解碼、編碼、過濾、增強處理等等。筆者目前隻在解碼領域探索,答案是:對於解碼而言,沒有區別。其他領域暫不清楚。

『視頻封裝格式』,是在編碼的視音頻基礎上進行一次“包裝”,添加與播放相關的協議數據(這個是筆者的認知,如有表述不準確,歡迎批評指正)。目前主流開源的框架,在“解包裝”工作上做的已經非常成熟瞭,如FFMpeg,提供瞭用於打開視音頻的API,開發人員無需關註具體視頻格式,直接可以取出視音頻流做處理。

接下來,介紹第二個問題,筆者再引入名詞『視頻協議』,也有說法認為『視頻協議』也屬於『視頻封裝格式』。

視頻協議

問題2:在騰訊視頻、嗶哩嗶哩網上看的視頻,與本地播放的MP4、MKV、AVI文件,有什麼區別?

『視頻協議』是針對網絡流媒體而言的,也就是隻有在有網絡時通過瀏覽器或者移動端APP才能看到的視頻,目前常見的協議有RTSP、RTMP、HLS、HTTP等。筆者短暫地接觸過GStreamer開發,在連接到RSTP視頻時,發現除瞭視音頻流和metadata之外,還攜帶瞭播放的信令。

也有文章會把『視頻協議』歸入『視頻封裝格式』。筆者看來,這麼分類也有其道理:『視頻協議』和『視頻封裝格式』都同時攜帶瞭視音頻和metadata,以及協議/格式需要的其他信息。以FFMpeg為例,並不區分視頻格式和視頻協議;但是GStreamer的話,還時需要指定『視頻協議』,但是不區分『視頻封裝格式』。

剝開『視頻封裝格式』和『視頻協議』的外殼,接下來瞭解視音頻流本身,這才是流媒體領域中真正的主角。本文僅介紹視頻流。

視頻流

就視頻流而言,相信大傢平時一定經常聽到類似“h264碼流”、“yuv流”、“編碼流”、“解碼流”,“原始流”、“裸流”,“壓縮後的流”或者“未壓縮的流”等等。歸納而言,提到『視頻流』的時候,一定隻有兩種形式:

  • 經過壓縮算法壓縮的流數據,稱為『編碼流』,又因為目前壓縮/編碼算法以H264為主,因此也常常稱為『H264碼流』。
  • 未經壓縮的流數據,是解碼後的流數據,稱為『原始流』,可以想象視頻是由一幅一幅在時間上連續的“圖像”組成的,而因為視頻內部的“圖像”是『YUV』(後文將介紹),因此也常常稱為『YUV流』。

總結出現的名稱,“h264碼流”、“編碼流”、“壓縮後的流”是壓縮/編碼後的視頻流;而“yuv流”、“解碼流”、“未壓縮的流”則是未經壓縮/編碼的視頻流。“裸流”是一個具有歧義的詞,是上下文內容,既可以是前者,也可以是後者。

因此,如果以後閱讀任何流媒體相關的文章時,看到『視頻流』都應該搞清楚,這究竟是編碼/壓縮的,還是沒有。在生活中,接觸到的視頻文件絕大部分都是編碼/壓縮後的;在網絡傳輸場景中,絕大部分也是編碼/壓縮後的。隻有在視頻播放時,觀眾觀賞到的時一幀幀被『轉碼』為『RGB』的解碼後視頻流。

編碼/壓縮在流媒體領域是一項非常重要的技術:從『H264碼流』到『YUV流』的過程稱為解碼,反之稱為編碼。


II. 幀

流媒體領域,『流』很重要,『流』的基本元素『幀』同樣重要。原因在於:對於視頻編碼/壓縮而言,它的核心是采用盡量小的空間存儲一組時間上連續的幀數據;而對於視頻解碼而言,就是把被編碼/壓縮後的一組幀數據盡量恢復成原來的樣子。能夠被100%恢復的編碼/壓縮算法稱為無損壓縮,反之稱為有損壓縮(雖然無損壓縮是最理想的,但是在很多實際場景中為瞭追求高壓縮率,比如為瞭減小網絡帶寬壓力,常常不得不選擇有損壓縮)。由此可見,『幀』是視頻流媒體領域的核心。接下來,一起來認識什麼是『幀』。

『幀』,可以聯想成我們平時看到的一幅幅“圖像”,隻不過我們平時接觸的圖片是『RGB』格式的,而視頻幀通常是『YUV』格式的。既然提到瞭『RGB』和『YUV』,那麼就來瞭解下幀的格式『YUV』,引出第一個問題:

問題3:幀為什麼采用『YUV』格式?『YUV』是什麼?

為此,筆者曾經花瞭很久去瞭解顏色空間、電視成像的發展史等,整理結論如下:

  1. 在達到最大壓縮率的情況下,能夠保證對人眼感知的失真度最小。『YUV』的三通道中,其中"Y"表示明亮度(Lumina nce或Luma),也就是灰階值;而"U"和"V"表示的則是色度(Chrominance或Chroma)。有一堆科學傢研究發現,人眼對UV的敏感度最低,因此可以極大比例地壓縮UV兩個通道的數值。見視頻編解碼學習一 yuv格式。
  2. 為瞭向前兼容黑白電視。這個就涉及歷史原因瞭,筆者非常推薦零基礎入門音視頻開發。歷史上在制定視頻幀格式時,是有人提出過用RGB的,最終決定用YUV的真正原因其實是這個(見影像使用YUV格式,為什麼不用RGB呢?。

接下來解釋『YUV』是什麼,筆者以為,『YUV』是一種廣義的概念,在視頻領域,當提到『YUV』的時候,往往是以下幾個意思:

  1. 顏色空間

“Y”表示明亮度(Luminance、Luma),“U”和“V”則是色度(Chrominance)、濃度(Chroma)。這裡表示的是色彩空間的基,即類似XYZ坐標系的一種色標表示基準,也就是說每一種顏色可以通過三維向量<y^i^,u^i^,v^i^>來表示。與其類似的還有RGB顏色空間、HSV顏色空間等。下圖來自How does the YUV color coding work?

​圖1. YUV坐標軸示意圖

隨著通信行業的發展,實際應用之多之復雜,導致『YUV』衍生出瞭一個大傢族。接觸視頻領域的一定聽說過YCbCr,甚至還有YPbPr、YIQ等。它們有的已經被時代淘汰,有的現在還在使用。之所以出現『YUV』大傢族,完全是因為實際電路系統之間的差異,導致要從『YUV』轉到『RGB』空間,實際對應的轉化系數是有些許差異的,於是各個部門開始制定各種規范,才有瞭我們現在看到的『YUV』大傢族。

YCbCr是專門針對數字電路而誕生的;YPbPr則是模擬電路。但是,現在是數字時代,所以為瞭模擬電路而生的YPbPr已經逐漸被淘汰瞭,而YCbCr還一直發揮著作用。因此現在,YCbCr有時也會被簡單地稱為/認為『YUV』。

2. 采樣率

讀者可能聽說過“YUV444”,“YUV422”,“YUV420”,到這裡可能會納悶:“YUV不是顏色空間嗎?為什麼後面還會跟著一串數字?” 因為當你看到YUV後面跟著一串數字的時候,『YUV』已經不再是顏色空間的基的含義瞭,而是意味著在原始『YUV流』上的采樣。在以前流媒體剛剛興起時,還沒有什麼4G/5G,當時為瞭減小網絡傳輸的帶寬的壓力,可謂做瞭種種努力。除瞭編碼/壓縮之外,YUV采樣率也是一種。444,422和420是三種『YUV』(在數字電路中指代YCbCr)的采樣,三位數分別代表YUV(數字電路中為YCbCr,本段後同)通道的抽樣比。所以可以理解,444是全采樣;而422是對Y進行全采樣,對UV分別進行1/2均勻采樣。有趣的問題來瞭,420難道是完全丟棄瞭V通道/分量數據嘛?答案是否定的。首先,必須要搞明白一個問題,一幀圖像是由一個個像素組成的矩形,譬如4×4的尺寸的圖像,就是由16個像素點組成的。在平時接觸的『RGB』圖像中,每個像素必然至少由RGB這三個通道組成的(有的圖像還有alpha分量),每個分量的取值一般都是[0,255],也就是[2^0,2^8],因此經常說一個像素占用3字節(如果還有其他分量,比如RGBA,就另當別論)。『YUV』圖像同理,它的每個像素是由YUV組成的。接下來,從整張圖像宏觀考慮采樣問題。還是以4X4的圖像為例,444的如下圖2-1,這個是形象化成圖像的樣子,實際在機器內存儲並不是這樣,具體可以參見博客《圖像原始格式一探究竟》。422和420分別如下圖2-2和2-3。

​圖2-1. YUV444采樣示意圖

​圖2-1對應YUV444采樣,即全采樣,圖示中可以看出每個像素中的YUV通道都保留下來瞭,一般來說YUV444太大瞭,因此很少使用。

​圖2-2. YUV422采樣示意圖

圖2-2對應YUV422采樣,這種采樣方式是:每個掃描線或者說每行相鄰2個像素,隻取1個像素的UV分量。此外,可以計算出來,每個像素占用的大小為原來的2/3,因此說YUV422是YUV444的2/3大小。

這個時候就有一個問題,在『YUV』轉『RGB』時,被抽走瞭UV分量的像素要怎麼辦呢?做法很簡單,就是相鄰2個像素的Y分量公用保留著的UV分量。

​圖2-2. YUV420采樣示意圖

圖2-3對應YUV420采樣,這種采樣方式是:隔行進行YUV422每行采樣的辦法,即相鄰2個像素隻取1個像素的UV分量;下一行丟棄所有的UV分量。此外,可以計算出來,每個像素占用的大小為原來的1/2,因此說YUV420是YUV444的1/2大小。恢復UV分量的辦法同YUV422,隻不過這裡是2X2的矩陣共享保留著的UV分量。

這種設計辦法真的很巧妙!前文提到的"人眼對UV的敏感度最低,因此可以極大比例地壓縮UV兩個通道的數值",且對於圖像而言,相鄰的區域像素的色彩、飽和度一般非常接近,因此這種以2X2矩陣為基本單位,隻保留1組UV分量合情合理。

3. 編碼/存儲格式

大傢肯定還聽說過YV12、YU12、NV12、NV21吧,看到這裡是不是又納悶:“後面的數字怎麼變成2個瞭?而且前面的英文字母還變瞭?”以上統稱為『視頻的存儲格式』,也就是說,計算機是如何存儲一幀視頻的。首先,『視頻的存儲格式』總分為兩大類:『打包格式(packed)』和『平面格式(planar)』。前者又被稱作『緊湊格式(packed)』。其實除此之外還有『半平面模式(Semi-Planar)』,估計是使用的比較少,因此在很多文章中常被忽略。筆者很感興趣,為什麼會出現『打包格式』和『平面格式』兩大派系,網上搜瞭很多資料也沒找到原因,博客【音視頻基礎】:I420、YV12、NV12、NV21等常見的YUV420存儲格式提到瞭需要約定存儲格式,但也沒提到為什麼會分成這兩種。要麼就是派系之爭,類似貝葉斯學派和頻率學派;要麼就是實際應用中逐漸衍生出這兩大格式。時至今日,這兩個格式還在被使用,因此對於多媒體開發者們都有必要瞭解。『打包格式』是把YUV分量交叉存儲,『平面格式』則是把YUV嚴格分開存儲,『半平面模式』介於兩者之間,Y分量分開存儲,UV交叉存儲。以下圖為例說明『打包格式』、『平面格式』和『半平面模式』應該是非常清楚的,圖摘自博客YUV格式初探:

​圖3-1. YUV420P存儲示意圖​圖3-2. YUV420SP存儲示意圖​圖3-3. YUV420Packet存儲示意圖

但是關於上圖的『打包格式』,筆者是是有一點疑惑的,大多數的說法是”YUV通道交叉存儲,相鄰的像素盡量打包在一起“,圖3-3中U1後面跟著的是U2而不是V1,而且YUV的排列方式似乎也不完全是交叉?筆者嘗試在網上搜索『打包格式』更多的例子,沒有找到特別好的資料,【這裡給自己挖一個坑吧】。

接下來,我們繼續瞭解一些幀相關的概念。

常見的幀名詞

  1. 幀率(FPS)『幀率』,FPS,全稱Frames Per Second。指每秒傳輸的幀數,或者每秒顯示的幀數,一般來說,『幀率』影響畫面流暢度,且成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。一個較權威的說法:當視頻幀率不低於24fps時,人眼才會覺得視頻時連貫的,稱為“視覺暫留”現象。因此,才有說法:盡管『幀率』越高越流暢,但在很多實際應用場景中24fps就可以瞭。
  2. 分辨率(Resolution)『分辨率』,也常被俗稱為『圖像的尺寸』或者『圖像的大小』。指一幀圖像包含的像素的多少,常見有1280×720(720P),1920X1080(1080P)等規格。『分辨率』影響圖像大小,且與之成正比:『分辨率』越高,圖像越大;反之,圖像越小。
  3. 碼率(BPS)『碼率』,BPS,全稱Bits Per Second。指每秒傳送的數據位數,常見單位KBPS(千位每秒)和MBPS(兆位每秒)。筆者認為這個概念真正要理解起來還是需要好好說明的,網上一說:“『碼率』與體積成正比:碼率越大,體積越大;碼率越小,體積越小”;另一說:“『碼率』越大,說明單位時間內取樣率越大,數據流精度就越高,這樣表現出來的的效果就是:視頻畫面更清晰畫質更高”;還有說法是:”『碼率』就是『失真度』“。但是筆者有一段時間就是不理解,每秒傳輸的數據越大,為什麼必然就對應畫面更清晰?還有體積怎麼理解呢?且看下文”三者之間的關系“。

『幀率』『分辨率』和『碼率』三者之間的關系最理想的情況是畫面越清晰、越流暢是最好的。但在實際應用中,還需要結合硬件的處理能力、實際帶寬條件選擇。高『幀率』高『分辨率』,也就意味著高『碼率』,也意味著需要高帶寬和強大的硬件能力進行編解碼和圖像處理。所以『幀率』和『分辨率』應該視情況而定。要說三者之間的關系,其實就是對於『碼率』的理解。在碼率(BPS)概念中提到瞭幾段摘自網上的說法,說的都太模糊瞭,筆者直到閱讀瞭文章Video Bitrate Vs. Frame Rate,才真的理解瞭『碼率』。首先,這些說法都沒有交代一個前提:『幀率』、『分辨率』和『壓縮率』都會影響『碼率』。Video Bitrate Vs. Frame Rate](https://www.techwalla.com/articles/video-bitrate-vs-frame-rate)文章在一開始就明確指出:

文章後面又特別強調『分辨率』和『壓縮率』對『碼率』的影響:高分辨率意味著圖片可以包括更多的細節,低壓縮率意味著圖片壓縮損失越少,即失真越少,越清晰。那為什麼不特地討論『幀率』呢?筆者認為原因有二:一個是『幀率』的影響非常直觀,每秒幀數增加必然導致數據量增加;另一個是實際應用場景中『幀率』是相對固定的,我們觀看的一般視頻都在25-30fps之間,現在一些高幀視頻是60fps,可見視頻『幀率』在實際場景中被討論的很少。

奇怪的幀名詞:1080p和1080i、場

筆者僅僅出於覺得有趣才放上來的,1080p和1080i、場都是相對比較“老”的概念瞭,在還是CRT電視的時代,顯示器顯示畫面都是靠電子槍一行一行掃描畫面才能產生一副完整的圖像,這就被稱作『場』,後來這個名詞也不常使用瞭,被取代它的是『幀』。【科技在進步,過時的概念、應用都會被新興的替換,所以真的要不斷學習緊跟時代啊!】

1080p和1080i也是『場』同一時期的概念:

  • ${數字}i的字母”i“表示Interlace,代表隔行掃描,比如奇數『場』隻掃描奇數行,後一『場』即偶數『場』隻掃描偶數行。這在過去是非常有用的,當時網絡條件差,帶寬受限,隔行掃描可以很大程度上減少傳輸的數據,又不至於影響觀眾觀看體驗。
  • ${數字}p的字母”p“表示Progressive,即逐行掃描,也就是一『場』把全部畫面掃描完整。這是後來才提出的概念,這也代表時代進步,帶寬條件上來瞭。

既然都是老概念瞭,那為什麼還要再提呢?借用文章1080P和1080i是什麼意思?的一段來說:

視頻『幀』和編解碼密切相關,因此還有不少『幀』的概念是和視頻編解碼相關的。

視頻編解碼而衍生的幀名詞

  1. I幀、P幀、B幀和IDR幀但凡接觸過一點視頻編解碼的讀者,一定見過IPB幀,至於IDR可能見的少一些。下面,簡單解釋每種類型:
  • I幀,英文全寫Intra Picture,又稱幀內編碼幀,俗稱關鍵幀。一般來說I幀不需要依賴前後幀信息,可獨立進行解碼。有數據表明,僅I幀的壓縮率,可以達到7,這裡其實可以把I幀的壓縮等同於單獨壓縮一幅圖片。至於說I幀的壓縮隻壓縮瞭空間上的冗餘信息,放在後續編碼相關的系列文章中會詳述。【這裡再挖一個坑,免得自己忘記瞭】
  • P幀,英文全寫predictive-frame,又稱前向預測編碼幀,也有幀間預測編碼幀。顧名思義,P幀需要依賴前面的I幀或者P幀才能進行編解碼,因為一般來說,P幀存儲的是當前幀畫面與前一幀(前一幀可能是I幀也可能是P幀)的差別,較專業的說法是壓縮瞭時間冗餘信息,或者說提取瞭運動特性。P幀的壓縮率約在20左右,幾乎所有的H264編碼流都帶有大量的P幀。
  • B幀,英文全寫bi-directional interpolatedprediction frame,又稱 雙向預測內插編碼幀,簡稱雙向預測編碼幀。B幀非常特殊,它存儲的是本幀與前後幀的差別,因此帶有B幀的視頻在解碼時的邏輯會更復雜些,CPU開銷會更大。因此,不是所有的視頻都帶有B幀,筆者目前還沒有接觸過帶B幀的視頻。【找到帶B幀視頻一定要珍藏起來好好研究!】不過,B幀的壓縮率能夠達到50甚至更高,在壓縮率指標上還是很客觀的。
  • IDR幀,英文全寫Instantaneous Decoding Refresh,翻譯過來是即時解碼刷新。聽上去,這類幀並不是名詞概念,倒像是個動詞?IDR幀是一種特殊的I幀,它是為瞭服務於編解碼而提出的概念,IDR幀的作用是立刻刷新,使錯誤不致傳播,從IDR幀開始,重新算一個新的序列開始編碼(摘自博客H264中I幀和IDR幀的區別)。

I/P/B幀,並不是依據視頻幀數據內部的元素的不同來區分的,從解碼後的幀本身而言,它們沒有任何區別。僅僅是在編碼時,對幀處理的方式不同而已。

  1. GOP英文全稱Group Of Pictures,一般來說,指的就是兩個I幀之間的間隔,嚴格來說,是兩個IDR幀之間的間隔。筆者對GOP研究的不多,對於網上的說法:“GOP在一定程度上會影響視頻畫面質量 – 在碼率相同的情況下,GOP越大,意味著PB幀越多,也就更容易獲取較好的圖像質量”這個說法存疑。【這裡留個坑待填】
  2. PTS、DTS筆者是在對視頻文件硬做解碼的時候,發現實際解碼輸出的fps是硬解的能力上限,比如一個24fps的視頻文件,在用硬件解碼時,能夠達到100+,當時接到一個需求是:“需要控制視頻文件的解碼率,讓它和文件的fps保持一致”。後來查閱瞭大量的資料,進而瞭解瞭DTS和PTS的概念:
  • DTS,英文全稱Decoding Time Stamp,即解碼時間戳,這個時間戳的意義在於告訴解碼器該在什麼時候解碼這一幀的數據。
  • PTS,英文全稱Presentation Time Stamp,即顯示時間戳,這個時間戳用來告訴播放器該在什麼時候顯示這一幀的數據。

這個概念在做視音頻同步的時候特別重要,尤其是PTS,目前常見的視音頻同步的三種策略“同步到音頻的PTS”、“同步到視頻的PTS”和“同步到系統/外部時鐘”,都是基於PTS完成的。


寫在後面

盡管每個概念網上都可以搜到一大堆的資料,但是筆者從一個多媒體開發小白走過來,覺得能有相對系統入門的綜合性介紹就會更好瞭!本文每個地方,都是基於筆者自己的理解,而不是簡單地從網上“復制粘貼”過來的,希望能夠對大傢有所幫助!當然,文章中有不嚴謹的地方,歡迎留言告知;或者有什麼有趣的話題探討,也歡迎私信留言!

最後,筆者目前在騰訊優圖的邊緣計算開發小團隊,目前我們正在計劃開源一款能夠適配設備(以邊緣設備為主)視覺AI計算落地應用框架-RapidAIoT,內容包括視頻取流、AI計算、消息結果上報下發中間件。也歡迎大傢咨詢瞭解。