### YUV顏色空間
視頻是由一幀一幀的數據連接而成,而一幀視頻數據其實就是一張圖片。
yuv是一種圖片儲存格式,跟RGB格式類似。
RGB格式的圖片很好理解,計算機中的大多數圖片,都是以RGB格式存儲的。
yuv中,y表示亮度,單獨只有y數據就可以形成一張圖片,只不過這張圖片是灰色的。u和v表示色差(u和v也被稱為:Cb-藍色差,Cr-紅色差)
常用的I420(YUV420P),NV12(YUV420SP),YV12(YUV420P),NV21(YUV420SP)等都是屬于YUV420,NV12是一種兩平面存儲方式,Y為一個平面,交錯的UV為另一個平面
通常,用來遠程傳輸的是I420數據,而本地攝像頭采集的是NV12數據。(iOS)
所有在真正編碼的過程中, 需要將NV12數據轉成I420數據進行編碼
### 視頻編碼方式
硬編碼:使用非CPU進行編碼,如顯卡GPU、專用的DSP、FPGA、ASIC芯片等
軟編碼:使用CPU進行編碼,軟編碼通常使用:ffmpeg+x264
ffmpeg:是一套開源的、用于對音視頻進行編碼&解碼&轉化計算機程序
x264:x264是一種免費的、開源的、具有更優秀算法的H.264/MPEG-4 AVC視頻壓縮編碼方式
編碼的流程:采集--> 獲取到視頻幀--> 對視頻幀進行編碼 --> 獲取到視頻幀信息 --> 將編碼后的數據以NALU方式寫入到文件
幀率:每秒鐘多少幀畫面
碼率:單位時間內保存的數據量
(GOPsize)間隔:多少幀為一個GOP
### FFMPEG結構體 --AVPacket
AVPacket是存儲壓縮編碼數據相關信息的結構體。本文將會詳細分析一下該結構體里重要變量的含義和作用。
uint8_t *data:壓縮編碼的數據。
例如對于H.264來說。1個AVPacket的data通常對應一個NAL。
注意:在這里只是對應,而不是一模一樣。他們之間有微小的差別:使用FFMPEG類庫分離出多媒體文件中的H.264碼流
因此在使用FFMPEG進行視音頻處理的時候,常常可以將得到的AVPacket的data數據直接寫成文件,從而得到視音頻的碼流文件。
int ? size:data的大小
int64_t pts:顯示時間戳
int64_t dts:解碼時間戳
int ? stream_index:標識該AVPacket所屬的視頻/音頻流。
### FFMPEG結構體 --AVStream
AVStream是存儲每一個視頻/音頻流信息的結構體。本文將會分析一下該結構體里重要變量的含義和作用。
int index:標識該視頻/音頻流
AVCodecContext *codec:指向該視頻/音頻流的AVCodecContext(它們是一一對應的關系)
AVRational time_base:時基。通過該值可以把PTS,DTS轉化為真正的時間。FFMPEG其他結構體中也有這個字段,但是根據我的經驗,只有AVStream中的time_base是可用的。PTS*time_base=真正的時間
int64_t duration:該視頻/音頻流長度
AVDictionary *metadata:元數據信息
AVRational avg_frame_rate:幀率(注:對視頻來說,這個挺重要的)
AVPacket attached_pic:附帶的圖片。比如說一些MP3,AAC音頻文件附帶的專輯封面。
### FFMPEG結構體 --AVCodec
AVCodec是存儲編解碼器信息的結構體。
const char *name:編解碼器的名字,比較短
const char *long_name:編解碼器的名字,全稱,比較長
enum AVMediaType type:指明了類型,是視頻,音頻,還是字幕
enum AVCodecID id:ID,不重復
const AVRational *supported_framerates:支持的幀率(僅視頻)
const enum AVPixelFormat *pix_fmts:支持的像素格式(僅視頻)
const int *supported_samplerates:支持的采樣率(僅音頻)
const enum AVSampleFormat *sample_fmts:支持的采樣格式(僅音頻)
const uint64_t *channel_layouts:支持的聲道數(僅音頻)
int priv_data_size:私有數據的大小
& H.264解碼器的結構體如下:
AVCodec?ff_h264_decoder?=?{??
????.name???????????=?"h264",??
????.type???????????=?AVMEDIA_TYPE_VIDEO,??
????.id?????????????=?CODEC_ID_H264,??
????.priv_data_size?=?sizeof(H264Context),??
????.init???????????=?ff_h264_decode_init,??
????.close??????????=?ff_h264_decode_end,??
????.decode?????????=?decode_frame,??
????.capabilities???=?/*CODEC_CAP_DRAW_HORIZ_BAND?|*/?CODEC_CAP_DR1?|?CODEC_CAP_DELAY?|??
??????????????????????CODEC_CAP_SLICE_THREADS?|?CODEC_CAP_FRAME_THREADS,??
????.flush=?flush_dpb,??
????.long_name?=?NULL_IF_CONFIG_SMALL("H.264?/?AVC?/?MPEG-4?AVC?/?MPEG-4?part?10"),??
????.init_thread_copy??????=?ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),??
????.update_thread_context?=?ONLY_IF_THREADS_ENABLED(decode_update_thread_context),??
????.profiles?=?NULL_IF_CONFIG_SMALL(profiles),??
????.priv_class?????=?&h264_class,??
};??
### FFMPEG結構體 --AVIOContext
AVIOContext是FFMPEG管理輸入輸出數據的結構體。本文將會詳細分析一下該結構體里每個變量的含義和作用。
unsigned char *buffer:緩存開始位置
int buffer_size:緩存大小(默認32768)
unsigned char *buf_ptr:當前指針讀取到的位置
unsigned char *buf_end:緩存結束的位置
void *opaque:URLContext結構體
在解碼的情況下,buffer用于存儲ffmpeg讀入的數據。例如打開一個視頻文件的時候,先把數據從硬盤讀入buffer,然后在送給解碼器用于解碼。
### FFMPEG結構體 --AVCodecContext
enum AVMediaType codec_type:編解碼器的類型(視頻,音頻...)
struct AVCodec ?*codec:采用的解碼器AVCodec(H.264,MPEG2...)
int bit_rate:平均比特率
uint8_t *extradata;?int extradata_size:針對特定編碼器包含的附加信息(例如對于H.264解碼器來說,存儲SPS,PPS等)
AVRational time_base:根據該參數,可以把PTS轉化為實際的時間(單位為秒s)
int width, height:如果是視頻的話,代表寬和高
int refs:運動估計參考幀的個數(H.264的話會有多幀,MPEG2這類的一般就沒有了)
int sample_rate:采樣率(音頻)
int channels:聲道數(音頻)
enum AVSampleFormat sample_fmt:采樣格式
int profile:型(H.264里面就有,其他編碼標準應該也有)
int level:級(和profile差不太多)
### FFMPEG結構體 -- AVFormatContext
AVFormatContext是包含碼流參數較多的結構體。在使用FFMPEG進行開發的時候,AVFormatContext是一個貫穿始終的數據結構,很多函數都要用到它作為參數。它是FFMPEG解封裝(flv,mp4,rmvb,avi)功能的結構體。下面看幾個主要變量的作用(在這里考慮解碼的情況):
struct AVInputFormat *iformat:輸入數據的封裝格式
AVIOContext *pb:輸入數據的緩存
unsigned int nb_streams:視音頻流的個數
AVStream **streams:視音頻流
char filename[1024]:文件名
int64_t duration:時長(單位:微秒us,轉換為秒需要除以1000000)
int bit_rate:比特率(單位bps,轉換為kbps需要除以1000)
AVDictionary *metadata:元數據
### FFMPEG結構體 -- AVFrame
AVFrame結構體一般用于存儲原始數據(即非壓縮數據,例如對視頻來說是YUV,RGB,對音頻來說是PCM),此外還包含了一些相關的信息。比如說,解碼的時候存儲了宏塊類型表,QP表,運動矢量表等數據。編碼的時候也存儲了相關的數據。因此在使用FFMPEG進行碼流分析的時候,AVFrame是一個很重要的結構體。
uint8_t *data[AV_NUM_DATA_POINTERS]:解碼后原始數據(對視頻來說是YUV,RGB,對音頻來說是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”數據的大小。注意:未必等于圖像的寬,一般大于圖像的寬。
int width, height:視頻幀寬和高(1920x1080,1280x720...)
int nb_samples:音頻的一個AVFrame中可能包含多個音頻幀,在此標記包含了幾個
int format:解碼后原始數據類型(YUV420,YUV422,RGB24...)
int key_frame:是否是關鍵幀
enum AVPictureType pict_type:幀類型(I,B,P...)
AVRational sample_aspect_ratio:寬高比(16:9,4:3...)
int64_t pts:顯示時間戳
int coded_picture_number:編碼幀序號
int display_picture_number:顯示幀序號
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳過宏塊表
int16_t (*motion_val[2])[2]:運動矢量表
uint32_t *mb_type:宏塊類型表
short *dct_coeff:DCT系數,這個沒有提取過
int8_t *ref_index[2]:運動估計參考幀列表(貌似H.264這種比較新的標準才會涉及到多參考幀)
int interlaced_frame:是否是隔行掃描
uint8_t motion_subsample_log2:一個宏塊中的運動矢量采樣個數,取log的;<4->16x16, 3->8x8, 2-> 4x4, 1-> 2x2
### 運動矢量表的存儲方式
int16_t?(*motion_val[2])[2];//運動矢量表存儲了一幀視頻中的所有運動矢量
int?mv_sample_log2=?4?-?motion_subsample_log2;??
int?mb_width=?(width+15)>>4;??
int?mv_stride=?(mb_width?<<?mv_sample_log2)?+?1;??
motion_val[direction][x?+?y*mv_stride][0->mv_x,?1->mv_y];??
大概知道了該數據的結構:
1.首先分為兩個列表L0和L1
2.每個列表(L0或L1)存儲了一系列的MV(每個MV對應一個畫面,大小由motion_subsample_log2決定)
3.每個MV分為橫坐標和縱坐標(x,y)
注意,在FFMPEG中MV和MB在存儲的結構上是沒有什么關聯的,第1個MV是屏幕上左上角畫面的MV(畫面的大小取決于motion_subsample_log2),第2個MV是屏幕上第1行第2列的畫面的MV,以此類推。因此在一個宏塊(16x16)的運動矢量很有可能如下圖所示(line代表一行運動矢量的個數):
### 音頻編碼基本原理
(1)? 音頻信號的冗余信息
數字音頻信號如果不加壓縮地直接進行傳送,將會占用極大的帶寬。例如,一套雙聲道數字音頻若取樣頻率為44.1KHz,每樣值按16bit量化,則其碼率為:
2*44.1kHz*16bit=1.411Mbit/s
如此大的帶寬將給信號的傳輸和處理都帶來許多困難,因此必須采取音頻壓縮技術對音頻數據進行處理,才能有效地傳輸音頻數據。
數字音頻壓縮編碼在保證信號在聽覺方面不產生失真的前提下,對音頻數據信號進行盡可能大的壓縮。數字音頻壓縮編碼采取去除聲音信號中冗余成分的方法來實現。所謂冗余成分指的是音頻中不能被人耳感知到的信號,它們對確定聲音的音色,音調等信息沒有任何的幫助。
冗余信號包含人耳聽覺范圍外的音頻信號以及被掩蔽掉的音頻信號等。例如,人耳所能察覺的聲音信號的頻率范圍為20Hz~20KHz,除此之外的其它頻率人耳無法察覺,都可視為冗余信號。此外,根據人耳聽覺的生理和心理聲學現象,當一個強音信號與一個弱音信號同時存在時,弱音信號將被強音信號所掩蔽而聽不見,這樣弱音信號就可以視為冗余信號而不用傳送。這就是人耳聽覺的掩蔽效應,主要表現在頻譜掩蔽效應和時域掩蔽效應,現分別介紹如下:
(a)? 頻譜掩蔽效應
一個頻率的聲音能量小于某個閾值之后,人耳就會聽不到,這個閾值稱為最小可聞閾。當有另外能量較大的聲音出現的時候,該聲音頻率附近的閾值會提高很多,即所謂的掩蔽效應。如圖所示:
頻率掩蔽效應
由圖中我們可以看出人耳對2KHz~5KHz的聲音最敏感,而對頻率太低或太高的聲音信號都很遲鈍,當有一個頻率為0.2KHz、強度為60dB的聲音出現時,其附近的閾值提高了很多。由圖中我們可以看出在0.1KHz以下、1KHz以上的部分,由于離0.2KHz強信號較遠,不受0.2KHz強信號影響,閾值不受影響;而在0.1KHz~1KHz范圍,由于0.2KHz強音的出現,閾值有較大的提升,人耳在此范圍所能感覺到的最小聲音強度大幅提升。如果0.1KHz~1KHz范圍內的聲音信號的強度在被提升的閾值曲線之下,由于它被0.2KHz強音信號所掩蔽,那么此時我們人耳只能聽到0.2KHz的強音信號而根本聽不見其它弱信號,這些與0.2KHz強音信號同時存在的弱音信號就可視為冗余信號而不必傳送。
(b)? 時域掩蔽效應
當強音信號和弱音信號同時出現時,還存在時域掩蔽效應。即兩者發生時間很接近的時候,也會發生掩蔽效應。時域掩蔽過程曲線如圖所示,分為前掩蔽、同時掩蔽和后掩蔽三部分。
時域掩蔽效應
由圖我們可以看出,時域掩蔽效應可以分成三種:前掩蔽,同時掩蔽,后掩蔽。前掩蔽是指人耳在聽到強信號之前的短暫時間內,已經存在的弱信號會被掩蔽而聽不到。同時掩蔽是指當強信號與弱信號同時存在時,弱信號會被強信號所掩蔽而聽不到。后掩蔽是指當強信號消失后,需經過較長的一段時間才能重新聽見弱信號,稱為后掩蔽。這些被掩蔽的弱信號即可視為冗余信號。
(2)? 壓縮編碼方法
當前數字音頻編碼領域存在著不同的編碼方案和實現方式, 但基本的編碼思路大同小異, 如圖所示。
數字音頻編碼系統模型
對每一個音頻聲道中的音頻采樣信號,首先都要將它們映射到頻域中,這種時域到頻域的映射可通過子帶濾波器實現。每個聲道中的音頻采樣塊首先要根據心理聲學模型來計算掩蔽門限值, 然后由計算出的掩蔽門限值決定從公共比特池中分配給該聲道的不同頻率域中多少比特數,接著進行量化以及編碼工作,最后將控制參數及輔助數據加入數據之中,產生編碼后的數據流。
### 視頻編碼的基本原理
1)視頻信號的冗余信息
以記錄數字視頻的YUV分量格式為例,YUV分別代表亮度與兩個色差信號。例如對于現有的PAL制電視系統,其亮度信號采樣頻率為13.5MHz;色度信號的頻帶通常為亮度信號的一半或更少,為6.75MHz或3.375MHz。以4:2:2的采樣頻率為例,Y信號采用13.5MHz,色度信號U和V采用6.75MHz采樣,采樣信號以8bit量化,則可以計算出數字視頻的碼率為:13.5*8 + 6.75*8 + 6.75*8= 216Mbit/s
如此大的數據量如果直接進行存儲或傳輸將會遇到很大困難,因此必須采用壓縮技術以減少碼率。數字化后的視頻信號能進行壓縮主要依據兩個基本條件:
l? 數據冗余。例如如空間冗余、時間冗余、結構冗余、信息熵冗余等,即圖像的各像素之間存在著很強的相關性。消除這些冗余并不會導致信息損失,屬于無損壓縮。
l? 視覺冗余。人眼的一些特性比如亮度辨別閾值,視覺閾值,對亮度和色度的敏感度不同,使得在編碼的時候引入適量的誤差,也不會被察覺出來。可以利用人眼的視覺特性,以一定的客觀失真換取數據壓縮。這種壓縮屬于有損壓縮。
數字視頻信號的壓縮正是基于上述兩種條件,使得視頻數據量得以極大的壓縮,有利于傳輸和存儲。一般的數字視頻壓縮編碼方法都是混合編碼,即將變換編碼,運動估計和運動補償,以及熵編碼三種方式相結合來進行壓縮編碼。通常使用變換編碼來消去除圖像的幀內冗余,用運動估計和運動補償來去除圖像的幀間冗余,用熵編碼來進一步提高壓縮的效率。下文簡單介紹這三種壓縮編碼方法。
2)壓縮編碼的方法
a)? 變換編碼
變換編碼的作用是將空間域描述的圖像信號變換到頻率域,然后對變換后的系數進行編碼處理。一般來說,圖像在空間上具有較強的相關性,變換到頻率域可以實現去相關和能量集中。常用的正交變換有離散傅里葉變換,離散余弦變換等等。數字視頻壓縮過程中應用廣泛的是離散余弦變換。
離散余弦變換簡稱為DCT變換。它可以將L*L的圖像塊從空間域變換為頻率域。所以,在基于DCT的圖像壓縮編碼過程中,首先需要將圖像分成互不重疊的圖像塊。假設一幀圖像的大小為1280*720,首先將其以網格狀的形式分成160*90個尺寸為8*8的彼此沒有重疊的圖像塊,接下來才能對每個圖像塊進行DCT變換。
經過分塊以后,每個8*8點的圖像塊被送入DCT編碼器,將8*8的圖像塊從空間域變換為頻率域。下圖給出一個實際8*8的圖像塊例子,圖中的數字代表了每個像素的亮度值。從圖上可以看出,在這個圖像塊中各個像素亮度值比較均勻,特別是相鄰像素亮度值變化不是很大,說明圖像信號具有很強的相關性。
一個實際8*8圖像塊
下圖是上圖中圖像塊經過DCT變換后的結果。從圖中可以看出經過DCT變換后,左上角的低頻系數集中了大量能量,而右下角的高頻系數上的能量很小。
圖像塊經過DCT變換后的系數
信號經過DCT變換后需要進行量化。由于人的眼睛對圖像的低頻特性比如物體的總體亮度之類的信息很敏感,而對圖像中的高頻細節信息不敏感,因此在傳送過程中可以少傳或不傳送高頻信息,只傳送低頻部分。量化過程通過對低頻區的系數進行細量化,高頻區的系數進行粗量化,去除了人眼不敏感的高頻信息,從而降低信息傳送量。因此,量化是一個有損壓縮的過程,而且是視頻壓縮編碼中質量損傷的主要原因。
量化的過程可以用下面的公式表示:
其中FQ(u,v)表示經過量化后的DCT系數;F(u,v)表示量化前的DCT系數;Q(u,v)表示量化加權矩陣;q表示量化步長;round表示歸整,即將輸出的值取為與之最接近的整數值。
合理選擇量化系數,對變換后的圖像塊進行量化后的結果如圖所示。
量化后的DCT系數
DCT系數經過量化之后大部分經變為0,而只有很少一部分系數為非零值,此時只需將這些非0值進行壓縮編碼即可。
(b)? 熵編碼
熵編碼是因編碼后的平均碼長接近信源熵值而得名。熵編碼多用可變字長編碼(VLC,Variable Length Coding)實現。其基本原理是對信源中出現概率大的符號賦予短碼,對于出現概率小的符號賦予長碼,從而在統計上獲得較短的平均碼長。可變字長編碼通常有霍夫曼編碼、算術編碼、游程編碼等。其中游程編碼是一種十分簡單的壓縮方法,它的壓縮效率不高,但編碼、解碼速度快,仍被得到廣泛的應用,特別在變換編碼之后使用游程編碼,有很好的效果。
首先要在量化器輸出直流系數后對緊跟其后的交流系數進行Z型掃描(如圖箭頭線所示)。Z型掃描將二維的量化系數轉換為一維的序列,并在此基礎上進行游程編碼。最后再對游程編碼后的數據進行另一種變長編碼,例如霍夫曼編碼。通過這種變長編碼,進一步提高編碼的效率。
(c)? 運動估計和運動補償
運動估計(Motion Estimation)和運動補償(Motion Compensation)是消除圖像序列時間方向相關性的有效手段。上文介紹的DCT變換、量化、熵編碼的方法是在一幀圖像的基礎上進行,通過這些方法可以消除圖像內部各像素間在空間上的相關性。實際上圖像信號除了空間上的相關性之外,還有時間上的相關性。例如對于像新聞聯播這種背景靜止,畫面主體運動較小的數字視頻,每一幅畫面之間的區別很小,畫面之間的相關性很大。對于這種情況我們沒有必要對每一幀圖像單獨進行編碼,而是可以只對相鄰視頻幀中變化的部分進行編碼,從而進一步減小數據量,這方面的工作是由運動估計和運動補償來實現的。
運動估計技術一般將當前的輸入圖像分割成若干彼此不相重疊的小圖像子塊,例如一幀圖像的大小為1280*720,首先將其以網格狀的形式分成40*45個尺寸為16*16的彼此沒有重疊的圖像塊,然后在前一圖像或者后一個圖像某個搜索窗口的范圍內為每一個圖像塊尋找一個與之最為相似的圖像塊。這個搜尋的過程叫做運動估計。通過計算最相似的圖像塊與該圖像塊之間的位置信息,可以得到一個運動矢量。這樣在編碼過程中就可以將當前圖像中的塊與參考圖像運動矢量所指向的最相似的圖像塊相減,得到一個殘差圖像塊,由于殘差圖像塊中的每個像素值很小,所以在壓縮編碼中可以獲得更高的壓縮比。這個相減過程叫運動補償。
由于編碼過程中需要使用參考圖像來進行運動估計和運動補償,因此參考圖像的選擇顯得很重要。一般情況下編碼器的將輸入的每一幀圖像根據其參考圖像的不同分成3種不同的類型:I(Intra)幀、B(Bidirection prediction)幀、P(Prediction)幀。如圖所示。
典型的I,B,P幀結構順序
如圖所示,I幀只使用本幀內的數據進行編碼,在編碼過程中它不需要進行運動估計和運動補償。顯然,由于I幀沒有消除時間方向的相關性,所以壓縮比相對不高。P幀在編碼過程中使用一個前面的I幀或P幀作為參考圖像進行運動補償,實際上是對當前圖像與參考圖像的差值進行編碼。B幀的編碼方式與P幀相似,惟一不同的地方是在編碼過程中它要使用一個前面的I幀或P幀和一個后面的I幀或P幀進行預測。由此可見,每一個P幀的編碼需要利用一幀圖像作為參考圖像,而B幀則需要兩幀圖像作為參考。相比之下,B幀比P幀擁有更高的壓縮比。
(d)? 混合編碼
上面介紹了視頻壓縮編碼過程中的幾個重要的方法。在實際應用中這幾個方法不是分離的,通常將它們結合起來使用以達到最好的壓縮效果。下圖給出了混合編碼(即變換編碼+ 運動估計和運動補償+ 熵編碼)的模型。該模型普遍應用于MPEG1,MPEG2,H.264等標準中。
混合編碼模型
從圖中我們可以看到,當前輸入的圖像首先要經過分塊,分塊得到的圖像塊要與經過運動補償的預測圖像相減得到差值圖像X,然后對該差值圖像塊進行DCT變換和量化,量化輸出的數據有兩個不同的去處:一個是送給熵編碼器進行編碼,編碼后的碼流輸出到一個緩存器中保存,等待傳送出去。另一個應用是進行反量化和反變化后的到信號X’,該信號將與運動補償輸出的圖像塊相加得到新的預測圖像信號,并將新的預測圖像塊送至幀存儲器。
### RTSP協議剖析
RTSP(Real-TimeStream Protocol )是一種基于文本的應用層協議,在語法及一些消息參數等方面,RTSP協議與HTTP協議類似。
RTSP被用于建立的控制媒體流的傳輸,它為多媒體服務扮演“網絡遠程控制”的角色。盡管有時可以把RTSP控制信息和媒體數據流交織在一起傳送,但一般情況RTSP本身并不用于轉送媒體流數據。媒體數據的傳送可通過RTP/RTCP等協議來完成。
一次基本的RTSP操作過程是:首先,客戶端連接到流服務器并發送一個RTSP描述命令(DESCRIBE)。流服務器通過一個SDP描述來進行反饋,反饋信息包括流數量、媒體類型等信息。客戶端再分析該SDP描述,并為會話中的每一個流發送一個RTSP建立命令(SETUP),RTSP建立命令告訴服務器客戶端用于接收媒體數據的端口。流媒體連接建立完成后,客戶端發送一個播放命令(PLAY),服務器就開始在UDP上傳送媒體流(RTP包)到客戶端。 在播放過程中客戶端還可以向服務器發送命令來控制快進、快退和暫停等。最后,客戶端可發送一個終止命令(TERADOWN)來結束流媒體會話.
### 字節序
現代的計算機系統一般采用字節(Octet, 8 bit Byte)作為邏輯尋址單位。當物理單位的長度大于1個字節時,就要區分字節順序(Byte Order, orEndianness)。常見的字節順序有兩種:Big Endian(High-byte first)和Little Endian(Low-byte first),這就是表2.1中的BE和LE。Intel X86平臺采用Little Endian,而PowerPC處理器則采用了Big Endian。舉例來說,整型數字$1234ABCD存儲的時候就會有兩種方式:
字節順序內存數據備注
Big Endian (BE)0xAB 0xCD 0x12 0x34此時的0xAB被稱為most significant byte?(MSB)
Little Endian (LE)0xCD 0xAB 0x34 0x12此時的0xCD被稱為least significant byte?(LSB)
大端: 低地址存放最高有效位(MSB),既高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
小端: 低地址存放最低有效位(LSB),既低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
TCP/IP各層協議將字節序定義為Big-Endian,因此TCP/IP協議中使用的字節序通常稱之為網絡字節序。
### QoS(網絡視頻傳輸的服務質量)
QoS(Qualityof Service)服務質量,是網絡的一種安全機制, 是用來解決網絡延遲和阻塞等問題的一種技術。在正常情況下,如果網絡只用于特定的無時間限制的應用系統,并不需要QoS,比如Web應用,或E-mail設置等。但是對關鍵應用和多媒體應用就十分必要。當網絡過載或擁塞時,QoS 能確保重要業務量不受延遲或丟棄,同時保證網絡的高效運行。
ITU將服務質量定義為決定用戶對服務的滿意程度的一組服務性能指標。從另一角度來說,QoS參數也是流媒體媒體傳輸的性能指標。主要的QoS參數有如下幾項:傳輸帶寬,傳輸時延和抖動,丟包率。
1.傳輸帶寬
傳輸帶寬也指的是數據傳輸的速率。對于流媒體的播放,影響最大的屬性就是傳輸帶寬。如果帶寬過低,使得數據傳輸下載的速度小于視頻流播放的數率,那么在視頻的播放將會經常出現停頓和緩沖,極大的影響了客戶觀看的流暢性;而為了保證視頻觀看的流暢性,在低帶寬的條件下,只能選擇低品質、低碼流的視頻進行傳輸,這樣又會影響到客戶的光看效果。所以,一個良好的傳輸帶寬環境是客戶活動高品質的流媒體體驗的重要保證。
2.傳輸時延和抖動
傳輸時延定義為從服務器端發送數據到接受端接收到該數據之間的時間差,它是用來描述網絡時延的一個指標。時延抖動定義為網絡傳輸延時的變化率。流媒體最重要一個特性的就是實時性強,所以流媒體通信需求更難于滿足的是對通信系統的傳輸時延限制。時延限制主要是用在具有實時性要求的交互分布式實時流媒體應用中,如視頻會議系統,為防止時延給交互式通信帶來不便,建議的最大端到端的總時延不要超過150ms,否則交互雙方會感到明顯的時延,給雙方的信息交流帶來不便。端到端的時延可分為以下四個部分:
1.信息源的媒體采樣、壓縮編碼和打包的時延;
2.傳輸時延;
3.接收端的排隊和播放緩沖時延;
4.接收端的拆包、解碼和輸出時延。
抖動定義為網絡傳輸延時的變化率。時延抖動對流媒體播放質量的影響非常大,一般會采用緩存排隊的辦法平滑數據報的抖動。但如果數據傳輸的抖動較大,則必須采用大的緩存,這將直接造成更大的時延,直接影響流媒體的體驗效果。
3.丟包率
流媒體數據傳輸中的時延和抖動是可以通過緩存的辦法減少影響,所以流媒體業務可以允許在一定范圍內的時延和抖動。但丟包會對流媒體數,據播放質量造成極其重大的影響。丟包率會造成視頻和音頻質量嚴重惡化,小的丟包率會造成圖像的失真和語音的間歇中斷,過高的丟包率甚至可以導致業務的中斷。網絡設計的目標是丟包率為零,但顯然不存在這樣的理想網絡。所以丟包的大小將直接決定流媒體業務質量的好壞。
### 分離H264碼流
1.第一次存儲AVPacket之前需要在前面加上H.264的SPS和PPS。這些信息存儲在AVCodecContext的extradata里面。
并且需要使用FFMPEG中的名為"h264_mp4toannexb"的bitstream filter 進行處理。
然后將處理后的extradata存入文件.
? ? 1.FILE?*fp=fopen("test.264","ab");
? ? 2.AVCodecContext *pCodecCtx=... ?
? ? 3.unsigned?char?*dummy=NULL;???//輸入的指針??
? ? 4.int?dummy_len;??
? ? 5.AVBitStreamFilterContext*?bsfc?=??av_bitstream_filter_init("h264_mp4toannexb");????
? ? 6.av_bitstream_filter_filter(bsfc,?pCodecCtx,?NULL,?&dummy,?&dummy_len,?NULL,?0,?0);??
? ? 7.fwrite(pCodecCtx->extradata,pCodecCtx-->extradata_size,1,fp);??
? ? 8.av_bitstream_filter_close(bsfc);????
? ? 9.free(dummy);
2.通過查看FFMPEG源代碼我們發現,AVPacket中的數據起始處沒有分隔符(0x00000001), 也不是0x65、0x67、0x68、0x41等字節,所以可以AVPacket肯定這不是標準的nalu。其實,AVPacket前4個字表示的是nalu的長度,從第5個字節開始才是nalu的數據。所以直接將AVPacket前4個字節替換為0x00000001即可得到標準的nalu數據。
char?nal_start[]={0,0,0,1};??
fwrite(nal_start,4,1,fp);??
fwrite(pkt->data+4,pkt->size-4,1,fp);??
fclose(fp);
### AAC格式簡介
AAC的音頻文件格式有以下兩種:
ADIF:Audio Data Interchange Format 音頻數據交換格式。這種格式的特征是可以確定的找到這個音頻數據的開始,不需進行在音頻數據流中間開始的解碼,即它的解碼必須在明確定義的開始處進行。故這種格式常用在磁盤文件中。
ADTS:Audio Data Transport Stream 音頻數據傳輸流。這種格式的特征是它是一個有同步字的比特流,解碼可以在這個流中任何位置開始。它的特征類似于mp3數據流格式。這種格式可以用于廣播電視。
簡言之。ADIF只有一個文件頭,ADTS每個包前面有一個文件頭。
AAC的ADIF格式見下圖:
AAC的ADTS的一般格式見下圖:
### RTMP規范簡單分析
RTMP協議是一個互聯網TCP/IP五層體系結構中應用層的協議。RTMP協議中基本的數據單元稱為消息(Message)。當RTMP協議在互聯網中傳輸數據的時候,消息會被拆分成更小的單元,稱為消息塊(Chunk)。
1 消息
消息是RTMP協議中基本的數據單元。不同種類的消息包含不同的Message Type ID,代表不同的功能。RTMP協議中一共規定了十多種消息類型,分別發揮著不同的作用。例如,Message Type ID在1-7的消息用于協議控制,這些消息一般是RTMP協議自身管理要使用的消息,用戶一般情況下無需操作其中的數據。Message Type ID為8,9的消息分別用于傳輸音頻和視頻數據。Message Type ID為15-20的消息用于發送AMF編碼的命令,負責用戶與服務器之間的交互,比如播放,暫停等等。消息首部(Message Header)有四部分組成:標志消息類型的Message Type ID,標志消息長度的Payload Length,標識時間戳的Timestamp,標識消息所屬媒體流的Stream ID。消息的報文結構如圖3所示。
2 消息塊
在網絡上傳輸數據時,消息需要被拆分成較小的數據塊,才適合在相應的網絡環境上傳輸。RTMP協議中規定,消息在網絡上傳輸時被拆分成消息塊(Chunk)。消息塊首部(Chunk Header)有三部分組成:用于標識本塊的Chunk Basic Header,用于標識本塊負載所屬消息的Chunk Message Header,以及當時間戳溢出時才出現的Extended Timestamp。消息塊的報文結構如圖4所示。
在消息被分割成幾個消息塊的過程中,消息負載部分(Message Body)被分割成大小固定的數據塊(默認是128字節,最后一個數據塊可以小于該固定長度),并在其首部加上消息塊首部(Chunk Header),就組成了相應的消息塊。消息分塊過程如圖5所示,一個大小為307字節的消息被分割成128字節的消息塊(除了最后一個)。
### TI和SI的概念
SI表征一幀圖像的空間細節量。空間上越復雜的場景,SI值越高。
TI表征視頻序列的時間變化量。運動程度較高的序列通常會有更高的TI值。
SI計算方法:對第n幀視頻進行Sobel濾波,然后對濾波后圖像計算標準差。選這些幀中的最大值為SI。
TI計算方法:求n與n-1幀圖像的幀差,然后對幀差圖像計算標準差。選這些幀中的最大值為TI。
### 函數avformat_open_input
FFMPEG打開媒體的的過程開始于avformat_open_input,因此該函數的重要性不可忽視。
在該函數中,FFMPEG完成了:
- 輸入輸出結構體AVIOContext的初始化;
- 輸入數據的協議(例如RTMP,或者file)的識別(通過一套評分機制):1判斷文件名的后綴 2讀取文件頭的數據進行比對;
- 使用獲得最高分的文件協議對應的URLProtocol,通過函數指針的方式,與FFMPEG連接(非專業用詞);
- 剩下的就是調用該URLProtocol的函數進行open,read等操作了.
? ? ? URLProtocol結構如下,是一大堆函數指針的集合:
typedef?struct?URLProtocol?{??
????const?char?*name;??
????int?(*url_open)(URLContext?*h,?const?char?*url,?int?flags);??
????int?(*url_read)(URLContext?*h,?unsigned?char?*buf,?int?size);??
????int?(*url_write)(URLContext?*h,?const?unsigned?char?*buf,?int?size);??
????int64_t?(*url_seek)(URLContext?*h,?int64_t?pos,?int?whence);??
????int?(*url_close)(URLContext?*h);??
????struct?URLProtocol?*next;??
????int?(*url_read_pause)(URLContext?*h,?int?pause);??
????int64_t?(*url_read_seek)(URLContext?*h,?int?stream_index,??
?????????????????????????????int64_t?timestamp,?int?flags);??
????int?(*url_get_file_handle)(URLContext?*h);??
????int?priv_data_size;??
????const?AVClass?*priv_data_class;??
????int?flags;??
????int?(*url_check)(URLContext?*h,?int?mask);??
}?URLProtocol;??
### 視頻插入
視頻直播想在 HLS 流中無縫插入一段廣告的 ts 文件,有問題想請教一下:1、這段 ts 的分辨率是否一定要和之前的視頻流一致?2、pts 時間戳是否要和上一個 ts 遞增?
徐立:1、可以不一致。這種情況兩段視頻完全是獨立狀態,可以沒有任何關系,只需要插入 discontinue 標記,播放器在識別到這個標記之后重置解碼器參數就可以無縫播放,畫面會很平滑的切換。2、不需要遞增。舉個例子,視頻 A 正在直播,播放到 pts 在 5s 的時候,插入一個視頻 B,需要先插入一個 discontinue,再插入 B,等 B 播放完之后,再插入一個 discontinue,再插入 A,這個時候 A 的 pts 可以和之前遞增,也可以按照中間插入的 B 的時長做偏移,一般做點播和時移的時候 pts 會連續遞增,直播的話會算上 B 的時長。
### I幀和IDR幀的區別
中文都把 I 幀翻譯成關鍵幀了,不過既然提到了 IDR 幀,可以展開說明一下。所有的 IDR 幀都是 I 幀,但是并不是所有 I 幀都是 IDR 幀,IDR 幀是 I 幀的子集。I 幀嚴格定義是幀內編碼幀,由于是一個全幀壓縮編碼幀,通常用 I 幀表示 “關鍵幀”。IDR 是基于 I 幀的一個 “擴展”,帶了控制邏輯,IDR 圖像都是 I 幀圖像,當解碼器解碼到 IDR 圖像時,會立即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄。重新查找參數集,開始一個新的序列。這樣如果前一個序列出現重大錯誤,在這里可以獲得重新同步的機會。IDR 圖像之后的圖像永遠不會使用 IDR 之前的圖像的數據來解碼。
### 直播體驗優化
這其實是一個直播過程中傳輸網絡不可靠時的容錯問題。例如,播放端臨時斷網了,但又快速恢復了,針對這種場景,播放端如果不做容錯處理,很難不出現黑屏或是重新加載播放的現象。
為了容忍這種網絡錯誤,并達到讓終端用戶無感知,客戶端播放器可以考慮構建一個FIFO(先進先出)的緩沖隊列,解碼器從播放緩存隊列讀取數據,緩存隊列從直播服務器源源不斷的下載數據。通常,緩存隊列的容量是以時間為單位(比如3s),在播放端網絡不可靠時,客戶端緩存區可以起到“斷網無感”的過渡作用。
&物理上優化線路,邏輯上優化策略,比如選擇性丟幀,不影響編碼畫質的前提下減輕傳輸體積.
### 直播秒開策略
大部分播放器都是拿到一個完成的 GOP 后才能解碼播放,基于 FFmpeg 移植的播放器甚至需要等待音畫時間戳同步后才能播放.
GOP 的第一幀通常都是關鍵幀,由于加載的數據較少,可以達到 “首幀秒開”。
& 如果直播服務器支持 GOP 緩存,意味著播放器在和服務器建立連接后可立即拿到數據,從而省卻跨地域和跨運營商的回源傳輸時間。從緩存 GOP 改成緩存雙關鍵幀(減少圖像數量),這樣可以極大程度地減少播放器加載 GOP 要傳輸的內容體積。
& 提前做好 DNS 解析(省卻幾十毫秒),和提前做好測速選線(擇取最優線路)。經過這樣的預處理后,在點擊播放按鈕時,將極大提高下載性能。
& 除了移動端可以做體驗優化之外,直播流媒體服務端架構也可以降低延遲。例如收流服務器主動推送 GOP 至邊緣節點,邊緣節點緩存 GOP,播放端則可以快速加載,減少回源延遲。
### 采集端第三方框架推薦
& VideoCore 目前國內很多知名的推流框架都是對VideoCore的二次開發.<支持RTMP>
& LiveVideoCoreSDK 基于OpenGL,實現了美顏直播和濾鏡功能,是一款非常厲害的推流SDK.
& LFLiveKit 可讀性較好的推流框架,支持動態切換碼率和美顏功能.
& GPUImage 純OC語言,可以做出各種不同濾鏡,美顏也可以直接使用BeautifyFace
### m3u8文件簡介
m3u8,是HTTP Live Streaming直播的索引文件。m3u8基本上可以認為就是.m3u格式文件,區別在于,m3u8文件使用UTF-8字符編碼。
#EXTM3U???????????????????? m3u文件頭,必須放在第一行
#EXT-X-MEDIA-SEQUENCE?????? 第一個TS分片的序列號
#EXT-X-TARGETDURATION?????? 每個分片TS的最大的時長
#EXT-X-ALLOW-CACHE????????? 是否允許cache
#EXT-X-ENDLIST????????????? m3u8文件結束符
#EXTINF???????????????????? extra info,分片TS的信息,如時長,帶寬等
一個簡單的m3u8索引文件
### 各種音頻格式
AAC: AAC其實是“高級音頻編碼(advanced audio coding)”的縮寫,它是被設計用來取代MP3格式的。你可能會想,它壓縮了原始的聲音,導致容量占用少但是質量肯定會有所下降。不過這些質量的損失 取決于聲音比特率的大小,當比特率合適的時候,這些損失人耳是很難聽出來的。事實上,aac比mp3有更好的壓縮率,特別是在比特率低于128bit/s 的時候。
HE-AAC: HE-AAC是AAC的一個超集,這個“HE”代表的是“High efficiency”。 HE-AAC是專門為低比特率所優化的一種音頻編碼格式,比如streaming audio就特別適合使用這種編碼格式。
AMR: AMR全稱是“Adaptive Multi-Rate”,它也是另一個專門為“說話(speech)”所優化的編碼格式,也是適合低比特率環境下采用。
ALAC: 它全稱是“Apple Lossless”,這是一種沒有任何質量損失的音頻編碼方式,也就是我們說的無損壓縮。在實際使用過程中,它能夠壓縮40%-60%的原始數據。這種編碼格式的解碼速度非常快,這對iphone或者ipod這種小型設備來說非常適合。
iLBC: 這是另一種專門為說話所設計的音頻編碼格式,它非常適合于IP電話等其它需要流式音頻的場合。
IMA4: 這是一個在16-bit音頻文件下按照4:1的壓縮比來進行壓縮的格式。這是iphone上面一種非常重要的編碼格式。
它的中文意思是基于線性脈沖編碼調制,用于將模擬聲音數據轉換成數字聲音數據。簡而言之,就是意味著無壓縮數據。由于數據是非壓縮的,它可以非常快的播放,并且當空間不是問題時,這是在iphone上面首選的音頻編碼方式。
μ-law and a-law: 就我所知道的,這種編碼是交替的編碼模擬數據為數字格式數據,但是在speech優化方面比linear PCM更好。
MP3: 這種格式是我們都知道也喜歡的,雖然很多年過去了,但MP3到目前為止仍然是一種非常流行的編碼格式,它也能被iphone很好地支持。
LPCM也很早就被定義在DVD播放機 的標準內,為了和CD有所區別,DVD的的采樣規格為16bit/48KHz,隨著技術的發展,DVD的的采樣規格更提升到24bit/96KHz,以達 到更高的播放品質,用96KHz/24bit方式記錄的音頻信號所能達到的頻率上限是96÷2= 48KHz,而它的最大動態范圍將可以達到24×6=144dB。從指標上就可以看出:它的聲音比CD要好得多。pcm編碼的最大的優點就是音質好,最大的缺點就是體數據量大。
### OpenGL概述
1. GL表示Graphics Library,即圖形庫;OpenGL是一種可以對圖形硬件設備特性進行訪問的軟件庫.
2.我們需要通過一系列的幾何圖元<點,線,三角形,Patch>來創建3維空間的物體.
3.像素是顯示器上最小的可見單元.計算機系統將所有的像素保存到幀緩存當中;后者是由圖像硬件設備管理的一塊獨立內存區域,可以直接映射到最終的顯示設備上.
4.光柵化:將輸入圖元的數學描述轉為屏幕位置對應的像素片元,稱為光柵化.
5.OpenGL被設計為一個用來更新幀緩沖區內容的狀態機;將幾何圖元,圖像和位圖轉換為屏幕上的像素的過程是由非常多的狀態設置來控制的.這些狀態設置是彼此是正交的--設置某一個狀態不會影響其他狀態;
& 圖片的本質是像素點的數組.
### H264裸流結構組成
? ? ? H.264 的基本流由一系列NALU (Network Abstraction Layer Unit )組成,不同的NALU數據量各不相同。H.264 草案指出,當數據流是儲存在介質上時,在每個NALU 前添加起始碼:0x000001 或 0x00000001, 來指示一個NALU 的起始和終止位置。在這樣的機制下,在碼流中檢測起始碼,作為一 個NALU的起始標識,當檢測到下一個起始碼時,當前NALU結束。
? ? ? H.264 碼流中每個幀的開頭的3~4個字節是H.264 的start_code(起始碼),0x00000001或者0x000001。3字節的0x000001只有一種場合下使 ,就是一個完整的幀被編為多個slice(片)的時候,包含這些slice的NALU 使用3字節起始碼。其余場合都是4字節0x00000001的。 每個NALU單元由一個字節的 NALU頭(NALU Header)和若干個字節的載荷數據(RBSP)組成。
& 常見的幀類型
NAL_SLICE = 1 非關鍵幀? ? NAL_SLICE_DPA = 2? ? NAL_SLICE_DPB = 3? ? NAL_SLICE_DPC =4
NAL_SLICE_IDR =5 關鍵幀? NAL_SEI = 6 增強幀? ? NAL_SPS = 7 SPS幀
NAL_PPS = 8 PPS幀? ? ? ? ? NAL_AUD = 9 分隔符? ? NAL_FILLER = 12
enum AVPictureType {
? ? AV_PICTURE_TYPE_NONE = 0, ///< Undefined
? ? AV_PICTURE_TYPE_I,? ? ///< Intra
? ? AV_PICTURE_TYPE_P,? ? ///< Predicted
? ? AV_PICTURE_TYPE_B,? ? ///< Bi-dir predicted
? ? AV_PICTURE_TYPE_S,? ? ///< S(GMC)-VOP MPEG4
? ? AV_PICTURE_TYPE_SI,? ? ///< Switching Intra
? ? AV_PICTURE_TYPE_SP,? ? ///< Switching Predicted
? ? AV_PICTURE_TYPE_BI,? ? ///< BI type