@ltlovezh
2023-03-05T11:40:53.000000Z
字数 2439
阅读 1806
MP4
封装容器
一个普通Mp4的简化版Box结构如下所示:
每个Box的作用如下所示:
某视频Track的stbl如下所示:
H264是stsd -> avc1 ->avcc,H265是stsd -> hvc1(hev1) -> hvcc。
某音频Track的stbl如下所示:
主要包含音频流或者视频流的元数据。
针对视频流,avc1和hvc1(hev1)包含了视频流宽高,avcc包含了AVCDecoderConfigurationRecord,hvcc包含了HEVCDecoderConfigurationRecord,主要存储视频VPS、SPS和PPS。
针对音频流,mp4a包含了采样率、声道数和采样位数,如下所示:
esds box则包含了AudioSpecialConfig,如下所示:
sample数量和每个sample的duration,可间接计算出总Sample数量。
某视频Track的stts box如下所示:
前450帧,每帧的duration是512,最后一帧的duration是1024(单位是mdhd box记录的timescale)。
可以计算出共451个视频Sample
某音频Track的stts box如下所示:
前651帧,每帧的duration是1024,最后一帧的duration是1036(单位是mdhd box记录的timescale)。
可以计算出共652个音频Sample
每个Sample的Size,可以得到总的Sample数量。
某视频Track的stsz box如下所示:
总共451个视频Sample,并且指出了每个Sample的Size。
某音频Track的stsz box如下所示:
总共652个音频Sample,并且指出了每个Sample的Size。
chunk和sample数量的映射关系,每个Entry指定了从第几个trunk开始,每个trunk包含多少Sample。
可以计算出chunk数量,以及每个chunk包含多少Sample。
某视频Track的stsc box如下所示:
第1 ~ 26个trunk,每个trunk包含15个视频Sample。
第27 ~ 28个trunk,每个trunk包含30个视频Sample。
第29个trunk包含一个视频Sample。
可以计算出共29个trunk,451个视频Sample。
某音频Track的stsc box如下所示:
第1 ~ 2个trunk,每个trunk包含43个音频Sample。
第3个trunk包含2个音频Sample。
依次类推......
可以计算出共29个trunk,652个音频Sample。
每个chunk的地址偏移量。
某视频Track的stco box如下所示:
共29个trunk,指出了每个trunk的地址偏移,结合stsc,可以计算出每个视频Sample的地址偏移。
某音频Track的stco box如下所示:
共29个trunk,指出了每个trunk的地址偏移,结合stsc,可以计算出每个音频Sample的地址偏移。
所有关键帧的Sample序号,如果不存在stss,那么每一个sample都是关键帧。
某视频Track的stss box如下所示:
第1、251、412帧是关键帧(从1开始计数)
音频Track没有stss box,因为每一个音频帧都是关键帧。
有B帧的情况下,每个Sample PTS和DTS之间的差值。
Sample.pts = Sample.dts + Sample.sample_offset;
指出了每个Sample,PTS - DTS的差值,单位是mdhd box记录的timescale。每个音视频帧,它的PTS是大于等于DTS的,因为必须先解码,再渲染。
音频Track没有ctts box,因为dts和pts一致,不需要修正。
class Chunk{
// 当前Chunk包含的Sample数量
int sampleNum;
// 当前Chunk的地址偏移量
int offset;
}
class Sample{
// Sample序号,从0开始计数
int index;
// Sample的地址偏移量
int offset;
uint64_t pts;
uint64_t dts;
// Sample大小(字节)
int size;
// 当前Sample所属Chunk的索引
int chunkIndex;
// 是否关键帧
bool iSKeyFrame
}
首选,根据stts和stsz,创建出所有Sample结构体,计算出每个Sample的index、pts、dts、size属性。
其次,根据stsc和stco,创建出所有Chunk,计算出每个Chunk的sampleNum和offset属性。
接着,根据Chunk列表和Sample列表,计算出每个Sample的chunkIndex和offset属性。
然后,根据stss,标识每个Sample是否是关键帧。
最后,根据ctts,修正视频Sample的PTS。
默认情况下,FFmpeg Mp4 Muxer在写完mdat
之后,在文件末尾写入moov
。这导致播放器必须下载整个文件,才能开始播放,首帧时间比较长。可以通过-movflags + faststart
选项,把moov
移到ftyp
后面,尽快获取到moov
box。
ffmpeg -i input.wav -c:a libfdk_aac -movflags +faststart output.m4a
一个包含两个Fragment的FMP4:
FMP4的Fragment由一个Moof和Mdat构成,如下所示:
ftyp + moov + [moof + mdat] + [moof + mdat] + [moof + mdat] + [......]