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


 

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

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


 

首先,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; }

 沒毛病。

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

  

但是,突然好奇一點,這是264裸流。幀率肯定不能像mp4v2庫一樣,通過時長來計算,同樣也沒有頭信息存儲它。

那Elecard怎么知道的?

肯定是在264幀里有。

 

是的,在SPS NAL單元里。

那就得對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,看這個重量級工具是怎么整這個幀率的。

 

使用ffprobe.exe file1.264可以看到類似這樣的輸出:
    Stream #0:0: Video: h264 (High), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 24 fps, 24 tbr, 1200k tbn, 48 tbc

這個264文件,幀率為24。跟蹤代碼,看到數據是由 ffprobe.c 里調用 avformat_find_stream_info() 計算出的,在libavformat\utils.c

這個函數內容太豐富了,只能看關鍵部分。

在 AVStream* st 里,有兩個表示幀率的成員:r_frame_rate 和 avg_frame_rate
在ffprobe.exe輸出時,分別表示 tbr, 和 fps。這其實是兩個不同含義的值,
但如果是一段電影視頻,它們通常是一樣的。如果是流傳輸,只要過程中幀率不變,應該也是一樣的。

 1 int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)  2 {  3  ...  4 
 5         // Average framerate  6         // st->avg_frame_rate 是總幀數/時長這個幀率的含義  7         // 但這里的 codec_info_duration_fields 和 codec_info_duration 具體含義仍未理解到位。
 8         av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,  9               st->info->codec_info_duration_fields * (int64_t) st->time_base.den, 10               st->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000); 11        
12  ... 13        
14         /** 15  * Real base framerate of the stream. 16  * This is the lowest framerate with which all timestamps can be 17  * represented accurately (it is the least common multiple of all 18  * framerates in the stream). Note, this value is just a guess! 19  * For example, if the time base is 1/90000 and all frames have either 20  * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1. 21          */
22         // st->r_frame_rate 應該是根據每一幀的PTS算出來的幀率
23         av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, 24             avctx->time_base.den, (int64_t)avctx->time_base.num * avctx->ticks_per_frame, INT_MAX); 25             
26  ... 27 }

 

 

 

 


 

完整工程代碼:

 

 
 
 
 
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM