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用法。

java mp4 视频时间戳_MP4文件中音视频时间戳的计算相关推荐

  1. .m3u8.sqlite文件转mp4,m3u8.sqlite文件转视频工具(开源免费)

    文章目录 一.预先准备 1. 前提 2. 主要思路 3. 准备工具 二.视频转换实战 2.1. 软件下载 2.2. TS转MP4工具 2.3. 操作流程 一.预先准备 1. 前提 如果已经买了课程,是 ...

  2. m3u8.sqlite文件转视频工具

    有一个工具,可以帮我们下载一些视频平台的视频,不过是要已购买的视频才可以,有些在线视频APP,把视频缓存到手机里,就是一个m3u8.sqlite文件 这个文件如果运气好,是可以转成视频的,比如事考帮. ...

  3. java mp4分割_Java 合并多个MP4视频文件

    局限性 只支持mp4文件 经过尝试对于一些mp4文件分割不了 依赖 com.googlecode.mp4parser isoparser 1.1.22 工具类 package com.example. ...

  4. java opencv 读取视频_java使用OpenCV从视频文件中获取帧

    本文实例为大家分享了java使用OpenCV从视频文件中获取帧的具体代码,供大家参考,具体内容如下 实现功能:使用Java获取mp4.mov.avi等视频文件中的图像帧,每秒获取一帧图像,并保存 环境 ...

  5. Java 添加音频到ppt_Java 添加 、读取以及删除PPT幻灯片中的视频、音频文件

    在PPT中,可以操作很多种元素,如形状.图形.文字.图片.表格等,也可以插入视频或者音频文件,来丰富幻灯片的内容呈现方式.下面将介绍在Java程序中如何来添加视频.音频文件到PPT幻灯片,读取和删除幻 ...

  6. Java 添加、读取、删除PPT幻灯片中的视频、音频文件

    概述及程序环境 在PPT中,可以操作很多种元素,如形状.图形.文字.图片.表格等,也可以插入视频或者音频文件,来丰富幻灯片的内容呈现方式.下面将介绍在Java程序中如何来添加视频.音频文件到PPT幻灯 ...

  7. php视频转音频文件怎么打开,视频怎么转音频格式 MP4格式怎样转MP3格式

    现在绝大多数人都会遇到歌曲或电影太大或者是格式不支持,而需要进行格式转换,有的也叫音频转换或视频转换.为实现将喜欢的视频放在不同的场合播放,我们常常需要进行视频转换.但是纵览诸多的视频转换器,要不就是 ...

  8. 如何将qsv格式视频转换为MP4格式?qsv文件怎么转换成mp4

    qsv视频格式是爱奇艺影音特有的视频格式,只能用爱奇艺播放器才能进行播放,要想对视频进行编辑就得先将爱奇艺视频格式转换成普通的视频格式. 打开视频转换器软件,点击添加文件,添加要转换的qsv格式文件. ...

  9. SONY索尼MP4视频变RSV文件修复方法

    索尼MP4变RSV的原因分析 索尼新型号相机或者摄像机,如SONY A7S3,A7M4,FX3, FX6, FX9等,如果录像过程中有发生如下异常情况,如断电,死机,电量不足,机器摔倒,非常规操作,换 ...

最新文章

  1. python基础类型
  2. 你知道Redis可以实现延迟队列吗?
  3. 我为什么用docker-compose来打包开发环境
  4. Qt 翻译文件的加载
  5. Solr常用查询语法笔记
  6. Linux下的JDK1.5安装
  7. bugfree 数据库配置 显示No such file or directory
  8. 第一个scrum会议
  9. c++如何在两个.cpp文件中使用同一个全局变量
  10. 大意导致Java访问DB2库时导出SQLCODE=-301, SQLSTATE=07006错误
  11. 百度文库免财富值下载漏洞
  12. python界面设计实例qt_Python GUI教程(六):使用Qt设计师进行窗口布局
  13. LaTeX中生成标题、摘要、关键词、目录方法以及自定义所需样式
  14. MongoDB学习(三)
  15. 解决插件在IE增强保护模式下无法运行的问题
  16. 二阶矩过程、平稳过程和随机分析
  17. 清理 Snapd以释放磁盘空间
  18. #LeetCode每日一题#的阶段回顾
  19. 【前端——vue】:过滤器、侦听器、计算属性、vue-cli、vue组件、动态组件、插槽、自定义属性、路由
  20. 前端js实现asr(语音转文字)

热门文章

  1. 阿里巴巴计算平台资深技术专家“一浪”对大数据领域近几年的技术趋势和变化的看法【强烈推荐数据岗细细品!】
  2. Hadoop权威指南 _01前言感悟
  3. JVM系列之:详解java object对象在heap中的结构
  4. java char判断相等_【Java面试考点4】java基础之运算符
  5. AbstractByteBuf源码分析
  6. 进程及 fork() 系统调用详解
  7. leetcode--200. 岛屿的个数
  8. Leecode06. Z 字形变换——Leecode大厂热题100道系列
  9. C语言:L1-031 到底是不是太胖了 (10分)(解题报告)
  10. 为什么消息队列不实现精准一次(exactly once)?