Asterisk 現(xiàn)有版本不支持播放視頻文件(支持視頻通話)锰悼,無法滿足發(fā)送視頻通知、視頻 IVR 等場景。本系列文章砚偶,通過學(xué)習(xí)音視頻的相關(guān)知識和工具,嘗試實現(xiàn)一個通過 Asterisk 播放 mp4 視頻文件的應(yīng)用洒闸。
- Asterisk播放mp4(1)——音頻和PCM編碼
- Asterisk播放mp4(2)——音頻封裝
- Asterisk播放mp4(3)——搭建開發(fā)環(huán)境
- Asterisk播放mp4(4)——H264&AAC
- Asterisk播放mp4(5)——MP4文件解析
- Asterisk播放mp4(6)——音視頻同步
- Asterisk播放mp4(7)——DTMF
本文關(guān)注在MP4文件中染坯,h264
和aac
的媒體數(shù)據(jù)是如何存放的,又如何進行訪問丘逸?為后續(xù)解析文件后单鹿,通過RTP進行發(fā)送進行準(zhǔn)備。
MP4文件格式
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)。MP4文件可以嵌入任何形式的數(shù)據(jù)湃鹊,不過通常存放的是AVC(H.264)編碼的視頻和AAC/MPEG-4(Part 2)編碼的音頻儒喊。
MP4文件由許多box
組成,box
可以嵌套币呵。box
分為header
和data
兩部分怀愧。header
包括size
(4字節(jié)),type
(4字節(jié))和largesize
(8字節(jié)余赢,可選)芯义。其中,size
指明了整個box
的大小妻柒,包括header
部分扛拨。如果box
很大(例如存放具體視頻數(shù)據(jù)的mdat box),超過了uint32的最大數(shù)值举塔,size
就被設(shè)置為1绑警,并用接下來的8位uint64來存放大小求泰。box
中的字節(jié)序為網(wǎng)絡(luò)字節(jié)序,也就是大端字節(jié)序(Big-Endian)计盒,就是一個32位的4字節(jié)整數(shù)存儲方式為高位字節(jié)在內(nèi)存的低端拜秧。
下面看看幾個主要的box
。
box類型 | 層級 | 名稱 | 說明 |
---|---|---|---|
ftyp | 1 | File Type Box | 有且只有1個章郁,只能被包含在文件層,而不能被其他 box 包含志衍。該 box 應(yīng)該被放在文件的最開始暖庄,指示該 MP4 文件應(yīng)用的相關(guān)信息。 |
moov | 1 | Movie Box | 有且只有1個楼肪,只能被包含在文件層培廓。moov是一個container box ,包含若干子box春叫,例如:mvhd肩钠,trak等,子box中存放媒體的元數(shù)據(jù)(metadata)信息暂殖。 |
mvhd | 2 | Movie Header Box | 描述了媒體一些基本信息价匠,例如:timescale,duration等呛每。 |
trak | 2 | Track Box |
trak 也是一個container box 踩窖,其子box包含了該track的媒體數(shù)據(jù)引用和描述(hint track除外)。一個MP4文件中的媒體可以包含1個或多個track晨横,它們之間彼此獨立洋腮,有自己的時間和空間信息。trak至少包含tkhd和mdia這兩個box手形,此外還有很多可選的box啥供。其中tkhd 為track header box ,mdia 為media box 库糠,該box是一個包含一些track媒體數(shù)據(jù)信息box的container box伙狐。 |
mdat | 1 | Media Data | 實際媒體數(shù)據(jù),最終解碼播放的音視頻數(shù)據(jù)都在這里面曼玩。對于h264 和aac 編碼的媒體來說鳞骤,其視頻mdat 中內(nèi)容是nal ,對于音頻來說黍判,其內(nèi)容為aac 的一幀豫尽。mdat中的幀依次存放,每個幀的位置顷帖、時間美旧、長度都由moov中的信息指定渤滞。 |
trak
是mp4中最復(fù)雜的部分,包含了讀取媒體數(shù)據(jù)所需的各種信息榴嗅。要理解trak
下的box
首先要掌握幾個基本概念妄呕,包括:track,sample嗽测,trunk绪励。
track:表示
sample
的集合,對于媒體數(shù)據(jù)來說唠粥,track表示一個視頻或音頻序列疏魏。sample:video sample即為一幀視頻,或一組連續(xù)視頻幀晤愧,audio sample即為一段連續(xù)的壓縮音頻大莫。
chunk:一個
track
中的幾個sample
組成的單元。
trak
下有很多box
官份,最重要也是最復(fù)雜的是trak/mdia/minf/stbl
只厘,它下面又包含了多個box
。sample table
指明sampe
時序和存儲地址的表舅巷。利用這個表羔味,可以解析sample
的時序、類型钠右、大小以及在文件中的位置介评。下面對這些box
做個簡單的說明:
box類型 | 名稱 | 說明 |
---|---|---|
stsd | Sample Description Box | 包含h264 編碼的sps 和pps ∨澜ⅲ可以有一個到多個sample description 们陆。 |
stsc | Sample To Chunk Box | 指定了chunk 和sample 的對應(yīng)關(guān)系。從first_chunk 這個chunk 序號開始情屹,每個chunk 都有samples_per_chunk 個sample 坪仇,每個sample 都可以通過sample_description_index 這個索引,在stsd 中找到描述信息垃你。 |
stsz | Sample Size Box | 指定了每個sample 的大小椅文。 |
stco | Sample Size Box | 指定了每個chunk 的在整個文件中的起始地址。 |
stss | Sync Sample Box | 關(guān)鍵幀惜颇。 |
stts | DecodingTime to Sample | 用于計算sample 的dts 皆刺,其中sample_counts 定義連續(xù)多少個sample的dts 具有相同的差值,sample_delta 為dts 的差值凌摄。 |
ctts | Composition Time to Sample | 每個sample 的構(gòu)成時間(Composition Time)和解碼時間(DT)之間的差值羡蛾。如果不存在ctts,則代表該流不存在B幀锨亏,那么CT就直接等于DT痴怨。 |
上圖是ISO/IEC 14496-12
規(guī)范中給出的示例忙干。第2行代表了視頻幀的存儲序列,幀后面的編號代表了顯示順序浪藻。視頻流編碼時捐迫,如果支持B幀,P幀會先于B幀編碼爱葵,因此幀編碼順序(存儲順序)和幀播放順序不一致施戴。通過這個圖我們就更容理解上面兩個和時間相關(guān)的box
的含義。stts
定義的是表格中的第3行萌丈,DT暇韧,用于計算出每個sample
的dts
;ctts
對應(yīng)的是表格中的第6行浓瞪,Composition offset,用于計算出每個sample
的pts(Compostion Time)
DTS(Decode Time Stamp):標(biāo)識讀入內(nèi)存中的視頻幀什么時候開始送入解碼器中進行解碼巧婶。PTS(Presentation Time Stamp):用于度量解碼后的視頻幀什么時候被顯示出來 乾颁。這兩個值對應(yīng)的并不是秒,毫秒這些時間單位艺栈,而是時間刻度英岭,它們的值除以mdhd
中的timescale
(每秒鐘有多少個時間刻度)轉(zhuǎn)換為時間。
制作樣本
為了控制篇幅我們忽略音頻流湿右,只分析視頻流诅妹。
ffmpeg -t 10 -lavfi sine -t 10 -lavfi color=red sine-red-10s.mp4
mdat
中存放的就是媒體數(shù)據(jù),視頻就是h264
的NALU
毅人,我們可以將mp4中的數(shù)據(jù)和h264裸流中的數(shù)據(jù)進行對比吭狡。
ffmpeg -t 10 -lavfi color=red red-default.h264
我們直接生成h264的裸流,其中I/P/B幀的數(shù)據(jù)和生成mp4文件中的h264
流的數(shù)量是一致的丈莺。
通過ffmpeg生成mp4文件時有很多參數(shù)可以指定划煮,faststart
是比較常用的一個,其作用是將moov
挪到mdat
前面缔俄,這樣就可以實現(xiàn)邊下載邊播放弛秋。
ffmpeg -t 10 -lavfi sine -t 10 -lavfi color=red -movflags faststart sine-red-10s-faststart.mp4
mp4文件的結(jié)構(gòu)太復(fù)雜了,直接看二進制數(shù)據(jù)很費勁俐载,下面我們通過一個在線工具解析mp4數(shù)據(jù)蟹略。
后面計算時序時要用到timescale
和duration
這兩個參數(shù),duration / timescale = 10秒遏佣。
從stsd/avc1/avcC
中可以獲得h264
的sps
和pps
挖炬。另外,需要特別注意lengthSizeMinusOne
這個參數(shù)状婶,其含義是用幾個字節(jié)表示nalu
的長度茅茂,實際的長度是值加1捏萍,這里就是3+1=4
。h264
裸流中空闲,分割nalu通常采用的是Annex B
這種用起始碼的方式(用3字節(jié)或4字節(jié)的起始碼)令杈,但是在MP4中使用的是AVCC
方式(填加字節(jié)指定nalu的大小)碴倾。
stsc
中指明track共有250個采樣逗噩,以及每個采樣的大小。這里需要注意兩點:1跌榔、采樣的大小是NALU
的實際大小加4异雁,因為前面有4字節(jié)記錄用來記錄尺寸;2僧须、采樣不一定是一個NALU
纲刀,實際上第一個采樣(759)就包括了SEI
和IDR
兩個NALU
。
stsc
中定義了chunk
和sample
的對應(yīng)關(guān)系担平,以及sample的描述信息(stsd)示绊。這里第一個chunk
包含了兩個sample
,后面每個chunk
包含一個sample
暂论。
stco
中定義了chunk
在文件中的地址面褐。通過stsz
,stsc
和stco
就可以在文件中獲得任意一個sample
取胎。9792
是第一個chunk
的偏移量展哭,第一個chunk
包含2個采樣,前2個采樣的大小分別為759
和17
闻蛀,可以計算出下一個chunk
的偏移量是10568
匪傍,它不等于下一個視頻chunk
的偏移量10991
,通過查看音頻的stco
觉痛,可知音頻流的第一個chunk
的偏移量是10568
析恢,這說明視頻和音頻的chunk
是交錯存儲的。
stts
中記錄了每個sample
間相差的時間刻度512秧饮。前面的timescale
的值為12800映挂,512/12800=0.04秒,說明每幀之間的解碼時間間隔為0.04秒盗尸。
ctts
提供的是composition time
和decoding time
的差值柑船,它們的和就是composition time
。
通過解析red.h264
文件的slice
可以知道每幀的大小和類型泼各,這樣方便我們理解decode time
:
1 | 視頻幀 | I | P | B | B | B | P | B | B | B |
---|---|---|---|---|---|---|---|---|---|---|
2 | DT(stts) | 0 | 512 | 1024 | 1536 | 2048 | 2560 | 3072 | 3584 | 4096 |
3 | Composition offset(ctts) | 1024 | 2560 | 1024 | 0 | 512 | 2560 | 1024 | 0 | 512 |
4 | CT | 1024 | 3072 | 2048 | 1536 | 2560 | 5120 | 4096 | 3584 | 4608 |
5 | seq | 1 | 5 | 3 | 2 | 4 | 9 | 7 | 6 | 8 |
6 | size | 65 | 13 | 10 | 10 | 10 | 19 | 12 | 10 | 10 |
上表中的第1行(DT)代表的是文件中視頻幀的存儲順序鞍时,第4行(CT)代表的是視頻幀的播放時間(單位是時間刻度),第5行(seq)代表的是視頻幀的播放順序∧嫖。可以看到存儲順序和播放順序是不一致的及塘,這是因為視頻幀中包含B幀,它是雙向依賴幀锐极,解析是可能要依賴P(或B幀)笙僚,所以雖然B幀的播放順序靠前,但是解碼的時候必須要先解碼P幀灵再,才能解碼B幀肋层。另外,在avcc
中一個sample
的前4個字節(jié)代表這個包的大小翎迁,因此stsz
中記錄的采樣大小比NALU
的大小多4栋猖。
通過對比h264裸流和mp4中的視頻數(shù)據(jù),我們可以得到一致的結(jié)果汪榔,就是說mp4中的stts
和ctts
記錄h264的解析結(jié)果蒲拉,方便了數(shù)據(jù)的直接訪問。