java mp4 视频时间戳_MP4文件中音视频时间戳的计算
MP4文件的組成
MP4文件的格式遵循ISO/IEC 14496-12標準,即ISO base media file format。所有數據都封裝在被稱為Box的數據結構中,一個MP4文件,是由多個Box組成的。
MP4文件的最外層Box
如上圖所示,該MP4文件由ftype、free、mdat和moov四個Box組成。
其中moov Box屬于container box,它又可以包含有其他的Box。它里面保存的數據如下圖所示
moov box
在這里moov box及其子box包含了該MP4文件的元數據,用于指定音視頻數據的存儲位置,數據類型,時間戳之類的信息。
mdat box為長度最大的box,該文件中的音視頻數據都包含在該box中,可以通過解析moov box來獲取每幀音視頻數據具體保存的位置。
moov box包含有每幀音視頻數據在文件中的偏移量信息,所以一般都是位于文件尾,用于方便保存文件時記錄偏移量信息。但是也可以通過其他方式將其移動到文件前面的位置(MP4box和ffmpeg都可以做到),這樣做的好處是播放器在播放網絡上的MP4文件時,可以直接讀取到文件的索引信息,使得開播更快。
Box結構的定義
box
full box
size字段表示該Box的長度,如果size值為1,則表示box的長度超過了32位的表示范圍,需要由type之后的64位用于表示實際的長度。
type字段表示該Box的類型,一般使用4個可打印的字符組合表示,也稱為FOURCC,如ftyp、moov、meta、mdat等。
大部分box除了包含有size和type字段外,還包含有version和flag字段,用于處理在標準升級時產生的box內容定義不一致的問題。
除去以上數據后box剩余的數據為該box的實際數據,根據type不同,表示的含義也各不相同。
moov box
如上圖所示,moov box中會包含有一個mvhd box和一個或多個trak box,每個trak box表示一個音視頻的流。
mvhd box
定義如下:
mvhd box
mvhd box中的duration和timescale字段用來指定該文件的播放時長,duration/timescale的值即為單位為秒的時長。如果文件中多個流的時長不一致,該位置為最大時長。如下圖所示,該文件的播放時長為189167/1000=189.167秒。
mvhdbox
trak box
每個trak box表示一路單獨的流,可能是音頻也可能是視頻。
mdia box下的hdlr box用來指定該流是音頻還是視頻
stsd box的子box用于保存該流的編碼類型
avcC
上圖中avcC box指定了該流的編碼類型為H264,且存儲了解碼所需的SPS、PPS信息。
stsc stsz stco三個box用于保存沒幀視頻或音頻數據在文件中的保存位置。
stts stss ctts三個box用于保存媒體數據和時間戳的對應關系。
Sample(音視頻幀)保存位置的計算
stsz(SampleSizeBox)用于保存每個sample對應的大小
stsz
sample_count字段指明sample的個數;
如果每個sample大小都相等的話,則sample_size字段為sample的大小。否則sample_size設置為0,每個sample的大小由后續(xù)的一個數組來指定。
stsc (SampleToChunkBox)
多個sample組成一個chunk,stsc box保存了sample和chunk之間的對應關系。
stsc
每個chunk可以有一個或多個sample,如果相鄰的chunk含有相同的sample數量,則first_count字段用于指明第一個chunk的索引,sample_per_chunk指明該組chunk中每個chunk中sample的數量。
sample
如上圖
index為1的chunk含有3個sample;
index為2的chunk含有1個sample;
然后下一個first_chunk值為4,則表明index為3的chunk含有和2相同數量的sample,也是1個;
繼續(xù),index為4的chunk含有2個sample;
index為5和6的chunk含有1個sample;7有2個sample;8有1個sample;9含有2個sample;
stco(ChunkOffsetBox)
stco box指明了每個chunk在文件中的存儲位置
stco
entry_count指明了總的chunk的數量
chunk_offset指明了該chunk在文件中的偏移量
以上三個box結合起來,即可計算每個sample在文件中保存的位置和大小
void mp4Parser::GetSamplePosition(Stream* s)
{
int sample_count = s->stsz_count;
int chunk_count = s->stco_count;
if(sample_count > 0)
{
s->sample_position = new uint64_t[sample_count];
}
int remain_chunk_count = chunk_count;
int sample_index = 0;
for(int i=0;istsc_count;i++)
{
int c_count = 0;
if (i != s->stsc_count - 1)
{
c_count = s->stsc_data[i + 1].first_chunk - s->stsc_data[i].first_chunk;
remain_chunk_count -= c_count;
}
else
{
c_count = remain_chunk_count;
}
for (int j = 0; j < c_count; j++)
{
int chunk_index = s->stsc_data[i].first_chunk + j;
uint64_t offset = s->stco_data[chunk_index - 1];
for (int k = 0; k < s->stsc_data[i].samples_per_chunk; k++)
{
s->sample_position[sample_index] = offset;
offset += s->stsz_data[sample_index];
sample_index++;
if (sample_index > sample_count)
return;
}
}
}
}
PTS和DTS的計算
I P B 幀的概念
在視頻壓縮中,為了提高壓縮率,會將每幀畫面壓縮為不同類型的視頻幀數據。
I幀表示關鍵幀,包含有一幀畫面的完整信息,解碼時只需要本幀數據就可以解碼出完整的一幀畫面。
P幀表示前向參考幀,它保存了本幀與上一幀的差異信息,它不能單獨解碼,需要根據上一幀的畫面加上本幀保存的差值來獲取本幀的完整畫面。
B幀為雙向參考幀,它解碼時需要依賴它之前和之后的幀來獲取最終的畫面
因為B幀需要依賴它后面的幀來進行解碼,所以它的解碼順序就必然和顯示順序不能保持一致,這是就需要解碼時間戳(DTS)和顯示時間戳(PTS)來共同決定一幀視頻數據何時解碼,然后何時顯示了。
stts(TimeToSampleBox)
stts
根據stts box可以計算出每個sample的dts,其中sample_delta為該sample的dts相對于上一個smaple的差值,比如entry_count=1,sample_count=5,sample_delta=1024時,5個sample的dts將依次為0 1024 2048 3072 4096。
ctts(CompositionOffsetBox)
ctts
cttsbox保存了每個sample的composition time和decode time之間的差值,這里CompositionTime就直接理解成PTS吧。
如果不存在ctts box,則代表該流不存在B幀,那么PTS就直接等于DTS,例如音頻數據就不存在ctts box。
根據stts和ctts兩個box可以計算出sample的DTS和PTS
stss(SyncSampleBox)
stss
stss box保存了哪些幀是關鍵幀(即I幀),做seek跳轉時,視頻需要從關鍵幀開始解碼,否則解碼會出現異常。
示例
這里我們選擇一個只有5幀畫面的MP4文件進行分析
stsz內容:
sample_count = 5
index = 1, size = 919
index = 2, size = 39
index = 3, size = 36
index = 4, size = 36
index = 5, size = 36
stsc內容:
entry_count = 2
first_chunk = 1, samples_per_chunk = 3, sample_description_index = 1
first_chunk = 2, samples_per_chunk = 1, sample_description_index = 1
stco內容
entry_count = 3
index = 1, chunk_offset = 48
index = 2, chunk_offset = 1051
index = 3, chunk_offset = 1096
index為1、2、3的三幀組成為chunk1
chunk1的起始地址為48,則sample1的起始地址為48,sample2的起始地址為48+919=967(919為sample1的大小),sample3的起始地址為967+39=1006(39為sample2的大小)。
chunk2和chunk3只包含有1個sample,分別為sample4和sample5
chunk2的起始地址為1051,則sample4的起始地址為1051
chunk3的起始地址為1096,則sample5的起始地址為1096
stts內容:
stts_count = 1
count:5, delte:512
ctts內容:
ctts_count = 5
count:1, offset:1024
count:1, offset:2560
count:1, offset:1024
count:1, offset:0
count:1, offset:512
根據stts可知,5個sample的DTS分別為 0、512、1024、1536、2048
與ctts內容相加,可得PTS分別為1024、3072、2048、1536、2560
即實際顯示的順序應該是按照PTS從小到大的順序(1、4、3、5、2)
DTS和PTS值轉換為時間
以上計算出來的DTS和PTS為一個整形的數值,但是他們如何轉換為以秒為單位的實際時間呢?
參看上面第二幅圖,moov/trak/mdia/mdhd這個順序下的mdhd box
mdhd
此box中有和mvhd中同樣的timesacle和duration字段,兩處并不一定一致,mdhd box中的timescale和duration表示當前流的時長,duration/timescale的值即為當前流的時長。
同樣,PTS和DTS除以timescale即為相應的以秒為單位的時間
上面那個例子中,視頻流的timescale=15360,則相應的DTS和PTS應該為(0、0.033、0.067、0.1、0.133)(0.067、0.2、0.133、0.1、0.167)。
elst(EditListBox)
moov/trak/edts/elst box同樣對PTS會產生影響,它可以是實際時間戳產生偏移
elst
segment_duration:表示該edit段的時長,以Movie Header Box(mvhd)中的timescale為單位。
media_time:表示該edit段的起始時間,以track中Media Header Box(mdhd)中的timescale為單位。如果值為-1,表示是空edit,一個track中最后一個edit不能為空。
media_rate:edit段的速率為0的話,edit段相當于一個”dwell”,即畫面停止。畫面會在media_time點上停止segment_duration時間。否則這個值始終為1。
為使PTS從0開始,media_time字段一般設置為第一個CTTS的值,計算PTS和DTS的時候,他們分別都減去media_time字段的值就可以將PTS調整為從0開始的值
如果media_time是從一個比較大的值,則表示要求PTS值大于該值時畫面才進行顯示,這時應該將第一個大于或等于該值的PTS設置為0,其他的PTS和DTS也相應做調整
如果elst box中有多個設置,表示會有多段的顯示,具體用法這里不再說明,可以查詢elst box用法。
總結
以上是生活随笔為你收集整理的java mp4 视频时间戳_MP4文件中音视频时间戳的计算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java gui 控制台_在GUI面板中
- 下一篇: java的svn插件maver_ecli