音视频编解码:MP4封装格式笔记

一、简介:
MP4封装格式以其跨平台特性而成为当前最常见的媒体封装格式之一。MP4文件由多个box组成,每个box存储不同的信息,且box之间会出现嵌套。MP4的box有很多,但最重要的顶层box主要有如下三个:

ftyp:File Type Box,描述文件遵从的MP4规范与版本
moov:Movie Box,媒体的metadata信息,有且仅有一个
mdat:Media Data Box,存放实际的媒体数据,一般有多个

每个box有两部分组成:box header 和 box body。
box header:box的元数据,比如box type、box size。
box body:box的数据部分,实际存储的内容跟box类型有关,比如mdat中body部分存储的媒体数据。
当box body中嵌套其他box时,这样的box叫做container box。

二、重要的box:

ftyp
mdat
moov-mvhd-(time_scale):1s包含的时间单位-(duration):影片时长,等于最长trak的duration-trak-tkhd:单个track的metadata-(id):当前track的唯一标识-(duration):当前track的持续时间,FFmpeg忽略了此值-(width):视频宽-(height):视频高-mdia:描述当前track的一些信息-hdlr:声明当前的track类型*vide:视频track*soun:音频track*m1a :MP2*subp/clcp:字幕-stbl:媒体数据的索引及时间信息(非常重要)-stsd:确认当前trak的format,匹配FFmpeg中的codec_id和codec_type等-stts:每个帧的时长-stss:该trak中关键帧的个数及序号-ctts:记录dts与pts的差值,仅B帧存在的码流才需要-stsc:每个chunk的sample数-stsz:当前trak包含的sample数-stco:chunk在文件中的偏移量-chunk_offsets:每个chunk相对于文件整体的偏移量

三、FFmpeg中对MP4相关box的解析:
FFmpeg源码中,解析MP4格式的demuxer是mov,文件所在路径为:

libavformat/mov.c

看一下结构体各成员定义:

const AVInputFormat ff_mov_demuxer = {.name           = "mov,mp4,m4a,3gp,3g2,mj2",.long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),.priv_class     = &mov_class,.priv_data_size = sizeof(MOVContext),.extensions     = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v",.flags_internal = FF_FMT_INIT_CLEANUP,.read_probe     = mov_probe,.read_header    = mov_read_header,.read_packet    = mov_read_packet,.read_close     = mov_read_close,.read_seek      = mov_read_seek,.flags          = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS | AVFMT_SHOW_IDS,
};

各个box的解析是在mov_read_header中完成的:

static int mov_read_header(AVFormatContext *s)
{MOVContext *mov = s->priv_data;AVIOContext *pb = s->pb;int j, err;/* atmo为box解析中的最小单位 */MOVAtom atom = { AV_RL32("root") };.../* check MOV header */do {if (mov->moov_retry)avio_seek(pb, 0, SEEK_SET);/* 读取box中内容,有嵌套的话持续往下读 */if ((err = mov_read_default(mov, pb, atom)) < 0) {av_log(s, AV_LOG_ERROR, "error reading header\n");return err;}} while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);if (!mov->found_moov) {	//是否读取完的标志位av_log(s, AV_LOG_ERROR, "moov atom not found\n");return AVERROR_INVALIDDATA;}...
}

看一下mov_read_default:

static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{int64_t total_size = 0;MOVAtom a;int i;/* 记录atom的嵌套层数 */if (c->atom_depth > 10) {av_log(c->fc, AV_LOG_ERROR, "Atoms too deeply nested\n");return AVERROR_INVALIDDATA;}c->atom_depth ++;if (atom.size < 0)atom.size = INT64_MAX;while (total_size <= atom.size - 8 && !avio_feof(pb)) {/* parse函数指针用于指向各个解析box */int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL;.../* 遍历各个数组,找根据type找到对应的box函数进行解析 */for (i = 0; mov_default_parse_table[i].type; i++)if (mov_default_parse_table[i].type == a.type) {parse = mov_default_parse_table[i].parse;break;}...if (!parse) { /* skip leaf atoms data */avio_skip(pb, a.size);} else {int64_t start_pos = avio_tell(pb);int64_t left;/* 调用对应的box解析函数 */int err = parse(c, pb, a);if (err < 0) {c->atom_depth --;return err;}...}...}...
}

MP4文件默认的box解析数组为mov_default_parse_table:

static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('A','C','L','R'), mov_read_aclr },
{ MKTAG('A','P','R','G'), mov_read_avid },
{ MKTAG('A','A','L','P'), mov_read_avid },
{ MKTAG('A','R','E','S'), mov_read_ares },
{ MKTAG('a','v','s','s'), mov_read_avss },
{ MKTAG('a','v','1','C'), mov_read_glbl },	
...
}

四、MP4的box解析工具:
1.mp4info:
在这里插入图片描述
方便获取一些box的基本信息,但无法完全显示box中一些关键信息。

2.MP4 exploer:
在这里插入图片描述
会比较详细的列出每个box的关键信息,有助于我们分析码流,下载地址。

五、MP4的一些常见问题分析:
1.Samba等共享访问时起播时间较长:
这个主要是因为记录metadata的box-moov在文件末尾的,需要将整个片源都下载下来之后才会获取metadata,解析出解码相关信息。
在这里插入图片描述
这种问题的解决方案为将moov移动到片源开始位置,对于本地片源,可以使用FFmpeg进行转码:

ffmpeg -i xxx.mp4 -codec copy -movflags faststart output.mp4

对于短视频类,建议在上传的时候统一进行moov的位移,如抖音等,对于线上moov已经在文件末尾的码流,可以考虑使用云端转码,以实现秒起播。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注