音视频编解码: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中一些关键信息。

扫描二维码关注公众号,回复: 14837691 查看本文章

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已经在文件末尾的码流,可以考虑使用云端转码,以实现秒起播。

猜你喜欢

转载自blog.csdn.net/achina2011jy/article/details/122090574