对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 }

 

 

 

 


 

完整工程代码:

 

 
 
 
 
 
 
 
 
 
 
posted @ 2020-09-30 18:29  余力出奇迹  阅读(2275)  评论(1)    收藏  举报