對mp4文件解析成264+AAC的過程總結(幀率計算)

 

MP4文件提取video,audio的過程,網上有大量的示例。無外乎參考ffmpeg, live555, mp4v2庫。git

因項目須要,這周基於mp4v2完成了一個功能性的示例,在這過程當中,對於視頻幀率的計算,遇到了一些有意思的事情。github


 

首先,mp4v2庫直接提供了幀率計算的方法:MP4GetTrackVideoFrameRate(),很簡單。算法

這個函數跟下去,能發現是經過整個mp4文件的幀數/時長得出來的:數組

double MP4File::GetTrackVideoFrameRate(MP4TrackId trackId) { MP4SampleId numSamples = GetTrackNumberOfSamples(trackId); uint64_t msDuration = ConvertFromTrackDuration(trackId, GetTrackDuration(trackId), MP4_MSECS_TIME_SCALE); if (msDuration == 0) { return 0.0; } return ((double)numSamples / double(msDuration)) * MP4_MSECS_TIME_SCALE; }

 沒毛病。ide

幀率爲30的mp4,提取完264幀存文件後,用Elecard StreamEye Tools驗證,也播放顯示正常。函數

  

可是,忽然好奇一點,這是264裸流。幀率確定不能像mp4v2庫同樣,經過時長來計算,一樣也沒有頭信息存儲它。工具

那Elecard怎麼知道的?ui

確定是在264幀裏有。spa

 

是的,在SPS NAL單元裏。3d

那就得對SPS解析了,這個嘛,本身寫是不可能的,這輩子都不會本身寫這種解析的。

找了網上的代碼,也不難。

bool h264_decode_sps(BYTE * buf, unsigned int nLen, int &width, int &height, int &fps) { ... if (timing_info_present_flag) { int num_units_in_tick = u(32, buf, StartBit); int time_scale = u(32, buf, StartBit); fps = time_scale / num_units_in_tick; int fixed_frame_rate_flag = u(1, buf, StartBit); if (fixed_frame_rate_flag) { fps = fps / 2; } } ... }

 

語法清晰易懂,語義徹底蒙圈。

若是這段代碼work正常,也就沒有後面的折騰了。

我試過多個文件,這樣計算出來的fps會有很高几率是錯的,並且確定是翻倍。即30幀的視頻計算出60幀,24幀的爲48幀。

緣由嘛,用Elecard看也很清楚,就是出錯的264文件SPS,其 fixed_frame_rate_flag 不爲1。因此沒有執行fps/2那個邏輯,就錯了

 

那看來這個算法有問題,須要再深刻解讀了,看看264規範吧,這個 fixed_frame_rate_flag 是啥意思。

好傢伙,這一看,可複雜了,還涉及一個因子: DeltaTfiDivisor

它又是根據另外幾個參數組合約定的。

 

從上表來看,裏面的幾個參數,不是在SPS裏的,因此也不知道該如何改進上面那段計算fps的代碼。

因而,繼續看源碼吧,再結合264標準啃。

找到live555看是怎麼整的,人家能直接對264裸流進行實時rtsp傳輸,確定也要計算這個幀率。

在 liveMedia\H264or5VideoStreamFramer.cpp 裏找到了答案。

原來live555還計算了SEI這個NAL單元,在函數analyze_sei_payload():

 

DeltaTfiDivisor = pic_struct == 0 ? 2.0 : pic_struct <= 2 ? 1.0 : pic_struct <= 4 ? 2.0 : pic_struct <= 6 ? 3.0 : pic_struct == 7 ? 4.0 : pic_struct == 8 ? 6.0 : 2.0; } 

 額外話,這裏把C語言三元運算符用到了新境界。

 

這個實現和上面的表徹底對上。但這個SEI (Supplemental enhancement information)看全稱就知道,它不是必須存在的,若是沒有SEI呢?

看看double DeltaTfiDivisor的初值吧,原來live555代碼寫死是2.0

那好吧,這貌似就能夠參照權威了。若是解析SPS得到視頻幀率,能夠粗暴的直接:

        if(num_units_in_tick > 0) fps = time_scale / num_units_in_tick / 2;

 

好了,先這樣。節後再看看 ffmpeg,看這個重量級工具是怎麼整這個幀率的。

待續。

 


 

完整工程代碼:
相關文章
相關標籤/搜索