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的大小由后续的一个数组来指定。

  • 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个;
    继续,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用法。


本文用到的MP4解析工具:https://github.com/mayudong1/MediaParser

你可能感兴趣的:(MP4文件中音视频时间戳的计算)