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,看這個重量級工具是怎麼整這個幀率的。
待續。