概念
MP4(MPEG-4 Part 14)是一種標準的數字多媒體容器格式,其擴展名為.mp4
,以存儲數字音頻及數字視頻為主,也可存儲字幕和靜止圖像。因其可容納支持比特流的視頻流(如高級視頻編碼),MP4可以在網絡傳輸時使用流式傳輸。
術語
概念與術語是理解好MP4媒體封裝格式和其操作算法的關鍵,為了方便了解MP4文件格式,需先了解以下幾個概念與術語:
- Box:MP4文件是由一個個Box組成的,可以將其理解為一個數據塊,它由Header+Data組成,Data 可以存儲媒體元數據和實際的音視頻碼流數據。Box可直接存儲數據塊,也可包含其它Box,我們把包含其它Box的Box稱為container box。
- Sample:可理解為采樣,對于視頻可理解為一幀數據,音頻一幀數據就是一段固定時間的音頻數據,可以由多個Sample數據組成,存儲媒體數據的單位是sample。
-
Chunk:連續幾個
sample
組成的單元被稱為chunk,每個chunk在文件中有一個偏移量,整個偏移量從文件頭算起,在這個chunk內,sample是連續存儲的。 -
Track:表示一些
chunk
的集合,對于媒體數據而言就是一個視頻序列或者音頻序列,常說的音頻/視頻軌可對照該概念上。除了Video Track和Audio Track外,還可以有非媒體數據,比如Hint Track,這種類型的Track就不包含媒體數據,可以包含一些將其他數據打包成媒體數據的指示信息或者字幕信息。簡單來說,Track是音視頻中可以獨立操作的媒體單位。
可理解為MP4文件中有多個Track,一個Track由多個Chunk組成,每個Chunk包含一組連續的Sample。例如視頻流的一個Sample代表實際的nal數據,Chunk是數據存儲的基本單位,它是一系列Sample數據的集合。
MP4容器格式
MP4是一種描述較為全面的容器格式,被認為可以在其中嵌入任何形式的數據,以及各種編碼的音視頻等,我們常見的大部分的MP4文件都是存放的AVC(H.264)或MPEG-4(Part 2)編碼的視頻和AAC編碼的音頻。MP4的結構就像俄羅斯的套娃,Box套著Box,也可理解為一棵Box樹。下圖是常見的box的結構圖,可用來大致了解MP4文件的構造:
我們也可以通過在線工具MP4 Box來查看MP4文件的真實格式:
box結構
通過上面的介紹,我們了解了MP4格式就是由一個個的box組合成的box樹,所有的數據都包含在box里,下面來了解一下box的基本結構。一個box是由Header+Data組成,如下圖所示:
整個box以Header開頭,Header中包含了box的大小(size)和類型(type)等信息。其中,size指明了整個box所占用的大小,包括Header部分,如果box很大(例如存放具體視頻數據的mdat box),超過了uint32的最大數值,size就被設置為1,并用接下來的8位uint64的largesize來存放大小。box中的字節序為網絡字節序,也就是大端字節序(Big-Endian)。
box根據header部分包含的信息的不同可以分為box和full box,如下圖所示:
關于更多的box內容,在ISO_IEC_14496-12_2015中包含所有box的類型,詳細可以參考該文檔。
MP4基礎box
ftyp
ftyp是MP4文件的第一個box,通過判斷該box來確定文件的類型。該box有且僅有1個,并且只能被包含在文件層,而不能被其他box包含。該box放在文件的最開始,指示文件的相關信息。
文件的最開始是ftyp box的size,然后是該box的type。 ftyp的body依次包括1個32位的major brand(4個字符),1個32位的minor version(整數)和1個以32位(4個字符)為單位元素的數組compatible brands。這些都是用來指示文件應用級別的信息。
以一個MP4文件的ftyp box為例:
00000018:size,為24個字節,一般情況下為固定值
66747970:"ftyp"四個字符的ASCII值,也就是該box的type
6D703432:major brand,這里為"mp42"
00000000:minor version,值為0
6D703432 69736F6D:compatible brands,值為"mp42"和"isom"
雖然MP4、MOV、3GP等格式文件采用相同的封裝標準,但由于是由不同的廠商合成,因此還是存在差別的。即使使用同一種媒體文件,比如MP4文件,由不同developers開發的,MP4內容格式也是存在差別的。ftyp作用就是為了標識它的developer是誰,兼容哪些標準等。
比如上面的例子,"mp42"表示它的major brand是MP4 v2 [ISO 14496-14],而"mp42"和"isom"則表示它的compatible brands是MP4 v2 [ISO 14496-14]和MP4 Base Media v1 [IS0 14496-12:2003]。
更多的ftyp可參見:ftyps.com,其中列出了所有已知的ftyp及對其的描述。
moov
moov box是MP4文件中必須有但只能存在一個的box,該box一般存的是媒體文件的元數據,其本身很簡單,是一種container box,里面的數據是子box,自己更像是一個分界標識。
所謂的媒體元數據主要包含類似SPS PPS的編解碼參數信息,還有音視頻的時間戳等信息。對于MP4還有一個重要的采樣表stbl信息,這里面定義了采樣Sample、Chunk、Track的映射關系等,是MP4能夠進行隨機拖動、播放等操作的關鍵。
mvhd box
該box是全文件唯一的一個box,其對整個媒體文件所包含的媒體數據(Video Track、Audio Track等)進行全面的描述。其中包含了媒體的創建和修改時間,默認音量、色域、時長等信息。具體實例下:
mvhd是一個full box,對應字段的含義參考下圖:
真實數據內容如下:
具體數據的解析:
0000006C 6D766864 00000000 DB8665DB DB8665DD 0000AC44 0009EA0C 00010000 01000000 00000000 00000000 00010000 00000000 00000000 00000000 00010000 00000000 00000000 00000000 40000000 00000000 00000000 00000000 00000000 00000000 00000000 00000003
0000006C: 長度108
6D766864: mvhd的ASCII標識
00: version為0
000000: flags為0
DB8665DB: createTime,從UTC時間的1904年1月1日0點至今的秒數,不過這里的時間并不影響播放器識別并播放影片
DB8665DD: modification time
0000AC44: time scale,文件媒體在1秒時間內的刻度值,可理解為1秒長度的時間單元數.即將1s平均分為1/44100份,每份1/44100s
0009EA0C: duration,媒體可播放時長:duration / timescale = 可播放時長(s)
00010000: rate,推薦播放速率,高16位和低16位分別為小數點整數部分和小數部分,即[16.16] 格式,該值為1.0(0x00010000)表示正常前向播放
0100: volume,與rate類似,[8.8] 格式,1.0(0x0100)表示最大音量
0000 00000000 00000000: reverse,10字節保留位
00010000 00000000 00000000
00000000 00010000 00000000
00000000 00000000 40000000: matrix,36字節視頻變換矩陣
00000000 00000000 00000000
00000000 00000000 00000000 :pre-defined, 24字節,其中包括:
00000000: preview_time
00000000: preview_duration
00000000: poster_time
00000000: selection_time
00000000: selection_duration
00000000: current_time
00000003: next track id, 下一個track使用的id號
iods box
這個box為full box,非必須box,實際也是24字節的固定值,data定義的內容應該是Audio和Video ProfileLevel方面的描述。
對應字段的含義參考下圖:
trak box
trak box定義了媒體中一個Track的信息,視頻有Video Track,音頻有Audio Track,媒體文件中可以有多個Track,每個Track具有自己獨立的時間和空間的信息,可以進行獨立操作。每個Track Box都需要有一個tkhd box和mdia Box,其它的box都是可選擇的:
tkhd box
描述了Track的媒體整體信息包括時長、圖像的寬度和高度等,比較重要。
當trak為audio時,對應的width和height為0,對應字段的含義參考下圖:
真實數據內容如下:
具體數據的解析:
0000005C 746B6864 00000001 DB8665DB DB8665DD 00000001 00000000 0009EA0C 00000000 00000000 00000000 00000000 00010000 00000000 00000000 00000000 00010000 00000000 00000000 00000000 40000000 02D00000 05000000
0000005C: size 92
746B6864: type tkhd
00: version 0
000001: flag 1
DB8665DB: 創建時間 3683018203
DB8665DD:修改時間 3683018205
00000000: reverse
0009EA0C: duration 649740
00000000 00000000: reverse2
0000: layer, 視頻層,默認為0,值小的在上層
0000: group, track分組信息,默認為0表示該track未與其他track有群組關系
00 00: 音量
0000: reverse
00010000 00000000 00000000
00000000 00010000 00000000
00000000 00000000 40000000: matrix[36] 視頻變換矩陣
02D00000: width 47185920
05000000: height 83886080
edts/elst
Edit List Box,不是所有的mp4文件有這個box,作用是使某個track的時間戳產生偏移:
mdia box
這個Box也是Container Box,里面包含子Box,一般必須有mdhd box、hdlr box、minf box。基本就是當前Track媒體頭信息和媒體句柄以及媒體信息。它自身非常簡單,就是一個標識而已,但最復雜的還是里面包含的子box.
mdhd box
該box里面主要定義了該Track的媒體頭信息,其中我們最關心的兩個字段是timescale和duration,分別表示了該Track的時間戳和時長信息,這個時間戳信息也是PTS和DTS的單位。
對應字段的含義參考下圖:
真實mdhd box的數據如下:
具體數據的解析:
00000020 6D646864 00000000 DB8665DB DB8665DD 00000258 00002288 55C40000
00000020: size 32
6D646864: mdhd
00000000: version = 0, flag = 0
DB8665DB: creation_time
DB8665DD: modification_time
00000258: timescale
00002288: duration
55C40000 language 21956 languageString und(0X15+0x60, 0X0E+0x60, 0X04+0x60)
hdlr Box
該box解釋了媒體的播放過程信息,用來設置不同Track的處理方式,標識了該Track的類型,音頻Track的handler為soun,視頻Track的handler為video。
對應字段的含義參考下圖:
真實的數據如下:
具體數據的解析:
00000031 68646C72 00000000 00000000 76696465 00000000 00000000 00000000 436F7265 204D6564 69612056 6964656F
00000031: size 49
68646C72: hdlr
00000000: version flag 都為0
00000000: component type: 全0
76696465: component subtype soun 代表該track為 audio track
00000000: Component manufacturer 。 Reserved. Set to 0.
00000000: Component flags。Reserved. Set to 0.
00000000: Component flags mask。Reserved. Set to 0。
436F7265 204D6564 69612056 6964656F: Name(Core Media Video.)
minf box
minf
是moov
中最重要最復雜的box,內部還有子Box,我們從上而下從外到內地分析各個box。該box建立了時間到真實音視頻sample的映射關系,是音視頻數據操作的關鍵。該box是container box,含有三大必須的子Box:
- 媒體信息頭box: vmhd box(視頻)、smhd box(音頻)
- 數據信息box:dinf box
- 采樣表box:stbl box
其中stbl是moov中最復雜的部分,stbl包含了媒體流每一個sample在文件中的offset,pts,duration等信息。想要播放一個mp4文件,必須根據stbl正確找到每個sample并送給解碼器。stbl用來描述每個sample的信息,包含以下幾個主要的子box:
stsd
Sample Description Box,存放解碼必須的描述信息。
avc1 & mp4a
Video Track中會包含avc1 box,包含SPS PPS 等音視頻解碼信息:
avcC box:
真實的數據如下:
具體數據的解析:
0000007B 61766331 00000000 00000001 00000000 00000000 00000000 00000000 02D00500 00480000 00480000 00000000 00010000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000018 FFFF0000 00256176 63430164 0029FFE1 000A2764 0029AC56 C0B40A19 01000428 EE3CB0FD F8F800
0000007B: size 123
61766331: avc1
000000000000: reserve
0001: index 1
0000: pre_defined
0000: reserved
00000000: pre_defined
00000000: pre_defined
00000000: pre_defined
02D0: width 720
0500: height 1280
00480000: Horiz resolution 4718592
00480000: Ver resolution 4718592
00000000: reverse
0001: frame count 1
0000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000: 32字節compressr_name
0018:bit_depth
FFFF:pre_defined
0000 0025: size 37
61766343: avcC
01: configurationVersion
64: AVC profile indication(100, Main profile)
00: AVC profile compatibility
29: AVC level indication 41
FF: nulu size(4 暫時不知道怎么來的)
E1: SPS個數,低五位有效,1個sps
000A: sps長度,10
2764 0029AC56 C0B40A19: sps
01: pps個數
0004: pps長度
28 EE3CB0: pps數據
FD F8F800:ext
Audio Track中會包含mp4a(MPEG-4 Audio) box:
mp4a box包含的esds box:
真實的數據如下:
具體數據的解析:
00000057 6D703461 00000000 00000001 00000000 00000000 00020010 00000000 AC440000 00000033 65736473 00000000 03808080 22000000 04808080 14401400 18000000 FA000000 FA000580 80800212 10068080 800102
00000057: size 87
6D703461: mp4a
00000000 0000: 6字節保留,必須為0
0001: index 1
0000: unsigned int(16) pre_defined = 0;
0000: const unsigned int(16) reserved = 0;
00000000:暫時不知道什么作用
0002:channel_count 2
0010:samplesize 166
00000000: version 和 flag 均為 0
00 AC44:samplerate 44100
0000: 暫時不知道什么作用
00000033: size 51
65736473: esds
00000000: version 和 flag 均為 0
03: ES_DescrTag
80808022: 4字節size,這里有點奇怪(一般就一字節),4字節長度,但只取最后一個0x22當長度
0000:ES_ID
00:
:0000 0000(bits)
0: steamDependenceFlag,如果為1,則有16bits的dependsOn_ES_IS
0: URL_Flag,如果為1,后邊則有8bits URLlength, 和相應的URLstring(URLlength)
0: OCRstreamFlag, 如果為1,有16bits OCR_ES_id;
0 0000: streamPriority
04: DecoderConfigDescriptor TAG
80 80 80 14: 4字節size(一般就一字節),這里有點奇怪,4字節長度,但只取最后一個0x14(20)當長度
40: objectTypeIndication (14496-1 Table8), 0x40是Audio (ISO/IEC 14496-3)
14:
0001 0101
0001 01 :streamType 5是Audio Stream, 14496-1 Table9
0 :upStream
0 :reserved
00 1800: bufferSizeDB: 6144
0000 FA00: Max bitrate 64000 //可以獲取最大碼率
0000 FA00: Avg bitrate 64000 //可以獲取平均碼率
05: DecSpecificInfotag
80 80 80 02: 4字節size(一般就一字節),這里有點奇怪,4字節長度,但只取最后一個0x02當長度
12 10:
0001 0010 0001 0000:
0001 0 :audioObjectType 2 GASpecificConfig
010 0 :samplingFrequencyIndex
001 0 :channelConfiguration 1
00 :cpConfig
0 :directMapping
06: SLConfigDescrTag
80 80 80 01: 長度1
02: predefined 0x02 Reserved for use in MP4 files
stts
Time-to-Sample Box,保存每個sample時長,描述了sample時序的映射方法,我們通過它可以找到任何時間的sample。stts
包含一個壓縮的表來映射時間和sample序號,用其他的表來提供每個sample的長度和指針。表中每個條目提供了在同一個時間偏移量里面連續的sample序號,以及samples的偏移量。遞增這些偏移量,就可以建立一個完整的time to sample表。
對應字段的含義參考下圖:
真實數據內容分析如下:
00000018 73747473 00000000 00000001 000001BA
00000018: size 24
73747473: stts
00000000: version 和 flag
00000001: Entry count 1
000001BA: sample count 442
00000014: Sample delta 20
在mdhd的timescale為600,這里sample delta為20,600/20=30,即1秒30幀
stts只有一個entry,sample count442,delta 20, 442*20=56000, 與mdhd的duration相對應
ctts
Composition Time to Sample 時間合成偏移表,每個 sample 有自己解碼序(DTS)和顯示序(PTS)。對每個 sample 而言, DTS 和 PTS 不相同時,則存在該 BOX:
對應字段的含義參考下表:
stss
Sync Sample Box,存放關鍵幀列表:
對應字段的含義參考下圖:
真實數據內容如下:
具體數據的解析:
0000004C 73747373 00000000 0000000F 00000001 0000001F 0000003D 0000005B 00000079 00000097 000000B5 000000D3 000000F1 0000010F 0000012D 0000014B 00000169 00000187 000001A5
0000004C: size 56
73747373: stss
00000000: version flag
0000000F: entry count 10,有15個關鍵幀
00 00 00 01: 第1個關鍵幀位于第1幀
...
...
000001A5:第16個關鍵幀位于第421幀
stsc
Sample-To-Chunk Box,sample-chunk映射表。上文提到MP4通常把sample封裝到chunk中,一個chunk可能會包含一個或者幾個sample。
對應字段的含義參考下圖:
該box關鍵點在于里面的三個字段: first_chunk、samples_per_chunk、sample_description_index:
- first_chunk: 每一個 entry 開始的 chunk 位置
- samples_per_chunk: 每一個 chunk 里面包含多少的 sample
- sample_description_index: 每一個 sample 的描述,一般默認設為 1
這 3 個字段實際上決定了一個 MP4 中有多少個 chunks,每個 chunks 有多少個 samples。在 MP4 文件中,最小的基本單位是 Chunk 而不是 Sample:
- sample: 包含最小單元數據的 slice。里面有實際的 NAL 數據。
- chunk: 里面包含的是一個一個的 sample。為了是優化數據的讀取,讓 I/O 更有效率。
真實數據內容如下:
具體數據的解析:
00000034 73747363 00000000 00000003 00000001 0000000F 00000001 0000001B 0000001E 00000001 0000001C 00000016 00000001
00000034: size 52
73747363: stsc
00000000: version flag 0
00000003: entry count 3
00000001: first_chunk 1
0000000F: samples_per_chunk 15
00000001: sample_description_index 1
...
文件中Entrys數據如下:
first_chunk | samples_per_chunk | sample_description_index |
---|---|---|
1 | 15 | 1 |
27 | 30 | 1 |
28 | 22 | 1 |
- entry 1: 第 1 個 chunk 開始,有 26 個包含 15 個 sample的 chunk
- entry 2: 第 27 個 chunk,有 30 個 sample
- entry 3: 第 28 個 chunk,有 22 個 sample
所以全部的sample count = 390 + 30 + 22 = 442,與stts
中的sample_counts值保持一致。
stsz
Sample Size Box,指定了每個sample的size。stsz box包含兩sample總數和一張包含了每個sample size的表。
完整參數信息參見下圖:
真實數據分析如下:
000006FC 7374737A 00000000 00000000 000001BA 000042C2 00000738 00001A6A 00002383 0000025B 000047CF 00000545 00003A1B 00000546 00003DD0 000010DE 00003AB7 000007F8 00003CC4 00000C89 000039D9 00002282 00002988 00000EBB 000031F8 000015B9 000026A4 0000098D
000006FC: size 1788
7374737A: stsz
00000000: version flag
00000000: Sample size 0
000001BA: Sample count 442 視頻track可用于確定視頻幀數
000042C2: 第一個sample(幀)大小 17090
...
...
stco
Chunk Offset Box,指定了每個chunk在文件中的位置,這個表是確定每個sample在文件中位置的關鍵。該表包含了chunk個數和一個包含每個chunk在文件中偏移位置的表。
完整參數信息參見下圖:
真實數據分析如下:
00000080 7374636F 00000000 0000001C 000064C7 00026778 0004E1F5 00077C7A 0009996E 000BC0CD 000DD165 000F9A64 0010EA9C 0012D734 00147692 00166D4A 00183F7A 001ADEF2 001CC70B 001EB874 00204561 00221F51 0023C2E7 002676B4 00289C8B 002AB54B 002CCACE 002F1A66 0030F353 0032C3DF 003449E1 00379B7C
00000080: size 128
7374636F: stco
00000000: version flag
0000001C: entry count 28,有28個chunk
000064C7: 第一個Chunk offset 25799,與stsc(chunk包含sample的個數)和stsz(每個sample的大小)一起,可以找到具體序號的sample的位置和大小,用于讀取數據
...
...
需要注意,這里stco只是指定的每個chunk在文件中的偏移位置,并沒有給出每個sample在文件中的偏移。想要獲得每個sample的偏移位置,需要結合 Sample Size box(stss)和Sample-To-Chunk (stsc)計算后取得。
mdat box
mdat box用于存儲音視頻數據,可從該Box解封裝出真實的媒體數據。該Box一般都會存在,但非必須。
原始的NALU單元組成:
Start code + NALU header + NALU payload
但是在MP4文件中,H264 slice并不是以Start Code來分割,而是存儲在mdat box的Data中。mdat box的格式:
Box header + Box Data
==>
Box size + Box type + (NALU length + NALU Header + NALU Data)..+..(NALU length + NALU Header + NALU Data)
下面通過分析真實MP4文件的mdat box的Data數據:
0011A1C0 6D646174 01402280 A37D2085 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D 2D2D2D2D
0011A1C0: size 1155520
6D646174: type mdat
參考資料
ISO/IEC 14496 Part 12:http://standards.iso.org/ittf/PubliclyAvailableStandards/index.html