【iOS】h264視頻解碼

H.264 中的 NAL 技術(shù)

NAL概述
NAL 全稱 Network Abstract Layer,即網(wǎng)絡(luò)抽象層。在 H.264/AVC 視頻編碼標(biāo)準(zhǔn)中,整個(gè)系統(tǒng)框架被分為 了兩個(gè)層面:視頻編碼層面(VCL)和網(wǎng)絡(luò)抽象層面(NAL)。其中,前者負(fù)責(zé)有效表示視頻數(shù)據(jù)的內(nèi)容, 而后者則負(fù)責(zé)格式化數(shù)據(jù)并提供頭信息,以保證數(shù)據(jù)適合各種信道和存儲介質(zhì)上的傳輸。 現(xiàn)實(shí)中的傳輸系統(tǒng)是多樣化的,其可靠性,服務(wù)質(zhì)量,封裝方式等特征各不相同,NAL 這一概念的提出 提供了一個(gè)視頻編碼器和傳輸系統(tǒng)的友好接口,使得編碼后的視頻數(shù)據(jù)能夠有效地在各種不同的網(wǎng)絡(luò)環(huán)境 中傳輸。

NAL單元
NAL 單元是 NAL 的基本語法結(jié)構(gòu),它包含一個(gè)字節(jié)的頭信息和一系列來自 VCL 的稱為原始字節(jié)序列載荷 (RBSP)的字節(jié)流。頭信息中包含著一個(gè)可否丟棄的指示標(biāo)記,標(biāo)識著該 NAL 單元的丟棄能否引起錯(cuò)誤 擴(kuò)散,一般,如果 NAL 單元中的信息不用于構(gòu)建參考圖像,則認(rèn)為可以將其丟棄;最后包含的是 NAL 單 元的類型信息,暗示著其內(nèi)含有效載荷的內(nèi)容。 送到解碼器端的 NAL 單元必須遵守嚴(yán)格的順序,如果應(yīng) 用程序接收到的 NAL 單元處于亂序,則必須提供一種恢復(fù)其正確順序的方法。

NAL 實(shí)現(xiàn)編解碼器與傳輸網(wǎng)絡(luò)的結(jié)合
NAL 提供了一個(gè)編解碼器與傳輸網(wǎng)絡(luò)的通用接口,而對于不同的網(wǎng)絡(luò)環(huán)境,具體的實(shí)現(xiàn)方案是不同的。 對于基于流的傳輸系統(tǒng)如 H.320、MPEG 等,需要按照解碼順序組織 NAL 單元,并為每個(gè) NAL 單元增加 若干比特字節(jié)對齊的前綴以形成字節(jié)流;對于 RTP/UDP/IP 系統(tǒng),則可以直接將編碼器輸出的 NAL 單元 作為 RTP 的有效載荷;而對于同時(shí)提供多個(gè)邏輯信道的傳輸系統(tǒng),甚至可以根據(jù)重要性將不同類型的 NAL 單元在不同服務(wù)質(zhì)量的信道中傳輸。

結(jié)論
為了實(shí)現(xiàn)編解碼器良好的網(wǎng)絡(luò)適應(yīng)性,需要做兩方面的工作:
第一、在 Codec 中將 NAL 這一技術(shù)完整而 有效的實(shí)現(xiàn);
第二、在遵循 H.264/AVC NAL 規(guī)范的前提下設(shè)計(jì)針對不同網(wǎng)絡(luò)的最佳傳輸方案。如果實(shí)現(xiàn) 了以上兩個(gè)目標(biāo),所實(shí)現(xiàn)的就不僅僅是一種視頻編解碼技術(shù),而是一套適用范圍很廣的多媒體傳輸方案, 該方案適用于如視頻會議,數(shù)據(jù)存儲,電視廣播,流媒體,無線通信,遠(yuǎn)程監(jiān)控等多種領(lǐng)域。

NALU 類型
標(biāo)識 NAL 單元中的 RBSP 數(shù)據(jù)類型,其中,nal_unit_type 為 1, 2, 3, 4, 5 的 NAL 單元稱為 VCL 的 NAL
單元,其他類型的 NAL 單元為非 VCL 的 NAL 單元。
0:未規(guī)定
1:非IDR圖像中不采用數(shù)據(jù)劃分的片段
2:非IDR圖像中A類數(shù)據(jù)劃分片段
3:非IDR圖像中B類數(shù)據(jù)劃分片段
4:非IDR圖像中C類數(shù)據(jù)劃分片段
5:IDR圖像的片段
6:補(bǔ)充增強(qiáng)信息(SEI)
7:序列參數(shù)集(SPS)
8:圖像參數(shù)集(PPS)
9:分割符
10:序列結(jié)束符
11:流結(jié)束符
12:填充數(shù)據(jù)
13:序列參數(shù)集擴(kuò)展
14:帶前綴的NAL單元
15:子序列參數(shù)集
16–18:保留
19:不采用數(shù)據(jù)劃分的輔助編碼圖像片段
20:編碼片段擴(kuò)展
21–23:保留
24–31:未規(guī)定

NAL 在多媒體傳輸、存儲系統(tǒng)中的應(yīng)用
NAL 的頭占用了一個(gè)字節(jié),按照比特自高至低排列可以表示如下:
0AABBBBB
其中,AA 用于表示該 NAL 是否可以丟棄(有無被其后的 NAL 參考),00b 表示沒有參考作用,可丟棄,如 B slice、SEI 等,非零——包括 01b、10b、11b——表示該 NAL 不可丟棄,如 SPS、PPS、I Slice、P Slice 等。
常用的 NAL 頭的取值如:
0x67: SPS
0x68: PPS
0x65: IDR
0x61: non-IDR Slice
0x01: B Slice
0x06: SEI
0x09: AU Delimiter

由于 NAL 的語法中沒有給出長度信息,實(shí)際的傳輸、存儲系統(tǒng)需要增加額外的頭實(shí)現(xiàn)各個(gè) NAL 單元的定界。 其中,AVI 文件和 MPEG TS 廣播流采取的是字節(jié)流的語法格式,即在 NAL 單元之前增加 0x00000001 的同步 碼,則從 AVI 文件或 MPEG TS PES 包中讀出的一個(gè) H.264 視頻幀以下面的形式存在:


屏幕快照 2018-04-28 上午11.37.54.png

而對于 MP4 文件,NAL 單元之前沒有同步碼,卻有若干字節(jié)的長度碼,來表示 NAL 單元的長度,這個(gè)長度 碼所占用的字節(jié)數(shù)由 MP4 文件頭給出;此外,從 MP4 讀出來的視頻幀不包含 PPS 和 SPS,這些信息位于 MP4 的文件頭中,解析器必須在打開文件的時(shí)候就獲取它們。從 MP4 文件讀出的一個(gè) H.264 幀往往是下面的形式,(假設(shè)長度碼為 2 字節(jié)):


屏幕快照 2018-04-28 上午11.39.17.png

MP4 格式基本概念

MP4 封裝格式核心概念
1.MP4封裝格式對應(yīng)標(biāo)準(zhǔn)為 ISO/IEC 14496-12(信息技術(shù) 視聽對象編碼的第12部分: ISO 基本媒體文 件格式/Information technology Coding of audio-visual objects Part 12: ISO base media file format)

2.MP4封裝格式是基于QuickTime容器格式定義,媒體描述與媒體數(shù)據(jù)分開,目前被廣泛應(yīng)用于封裝h.264 視頻和 ACC 音頻,是高清視頻/HDV 的代表。

3.MP4文件中所有數(shù)據(jù)都封裝在box中(對應(yīng)QuickTime中的atom),即MP4文件是由若干個(gè)box組成, 每個(gè)box有長度和類型,每個(gè)box中還可以包含另外的子box(稱container box)。
一個(gè) MP4 文件首先會有且只有一個(gè)“ftyp”類型的 box,作為 MP4 格式的標(biāo)志并包含關(guān)于文件的一些信 息;之后會有且只有一個(gè)“moov”類型的box(Movie Box),它是一種container box,子box包含了媒 體的metadata信息;MP4文件的媒體數(shù)據(jù)包含在“mdat”類型的box(Midia Data Box)中,該類型的b ox也是container box,可以有多個(gè),也可以沒有(當(dāng)媒體數(shù)據(jù)全部引用其他文件時(shí)),媒體數(shù)據(jù)的結(jié) 構(gòu)由 metadata 進(jìn)行描述。

4.MP4中box存儲方式為大端模式。一般,標(biāo)準(zhǔn)的box開頭會有四個(gè)字節(jié)的box size。

5.幾個(gè)名詞

屏幕快照 2018-04-28 下午2.03.37.png
讀取本地的h264視頻
NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:[[NSBundle mainBundle] pathForResource:@"abc" ofType:@"h264"]];
[inputStream open];


// reads up to length bytes into the supplied buffer, which must be at least of size len. Returns the actual number of bytes read.
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;

uint8_t*   inputBuffer = malloc(640 * 480 * 3 * 4);
long        inputSize = 0;
long size = [inputStream read:inputBuffer + inputSize maxLength:inputMaxSize - inputSize];
inputBuffer的值
00 00 00 01 67 XX XX XX 00 00 00 01 68 XX XX XX 00 00 00 01 66 xx xx xx 00 00 00 01 65 XXXXXXXXXXXXXXXXXXXXX

h264視頻是以 00 00 00 01 作為起始碼
第一個(gè) 00 00 00 01 67 代表67后面的是SPS,最后一位可能是27、47、67
第二個(gè) 00 00 00 01 68 代表68后面的是PPS,最后一位可能是28、48、68
第三個(gè) 00 00 00 01 66 代表66后面的是SEI,可能有可能沒有,這個(gè)我們可以不管
第三個(gè) 00 00 00 01 65 代表65后面的是IDR,后面就是真實(shí)的視頻數(shù)據(jù)(I frame 或者是B/P frame)

00 00 00 01 67 XX XX XX
    
00 00 00 01 68 XX XX XX
        
00 00 00 01 66 xx xx xx
        
00 00 00 01 65 XXXXXXXXXXXXXXXXXXXXX

我們要把上面的數(shù)據(jù)截成這樣,然后拿00 00 00 01 后的一位 & 0x1F 得到 nalType

0x27 & 0x1F = 0x07
0x47 & 0x1F = 0x07
0x67 & 0x1F = 0x07

0x28 & 0x1F = 0x08
0x48 & 0x1F = 0x08
0x68 & 0x1F = 0x08

0x25 & 0x1F = 0x05
0x45 & 0x1F = 0x05
0x65 & 0x1F = 0x05

下面這就可以判斷起始碼后面的數(shù)據(jù)是SPS還是PPS還是IDR frame還是B/P frame,我們要把起始碼后面的數(shù)據(jù)拿到解碼時(shí)要用到
switch (nalType) {
            case 0x05:
                YJLog(@"Nal type is IDR frame");
                break;
            case 0x07:
                YJLog(@"Nal type is SPS");
                break;
            case 0x08:
                YJLog(@"Nal type is PPS");
                break;
            default:
                YJLog(@"Nal type is B/P frame");
                break;
        }


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容