实战解决获取mp4格式文件信息,时长等及mp4存储结构探索

本文详细介绍了MP4文件的结构,包括其由多个box组成的特性,重点讲解了MovieBox(moov)及其包含的mvhd和trak等关键信息。同时,通过ffprobe工具展示了如何获取视频的时长和元信息,并提供了使用Go语言解析MP4获取时长的代码示例。
摘要由CSDN通过智能技术生成

一、认识mp4

MP4文件的格式主要由 MPEG-4 Part 12、MPEG-4 Part 14 两部分进行定义。其中,MPEG-4 Part 12 定义了ISO基础媒体文件格式,用来存储基于时间的媒体内容。MPEG-4 Part 14 实际定义了MP4文件格式,在MPEG-4 Part 12的基础上进行扩展。

MP4的组成

MP4文件由多个box组成,不同的box存储着不同的信息,采用树状结构

box主要类型:

  • 1.ftyp:File Type Box,描述文件遵从的MP4规范与版本;
  • 2.moov:Movie Box,媒体的metadata信息,有且仅有一个。
  • 3.mdat:Media Data Box,存放实际的媒体数据,一般有多个;
BOX简介
  • 1.box主要有两部分组成,box header、box body。
    • box header:box的元数据,比如box type、box size。
    • box body:box的数据部分,实际存储的内容跟box类型有关,比如mdat中body部分存储的媒体数据。
  • 2.box header中,只有type、size是必选字段。当size==0时,存在largesize字段。在部分box中,还存在version、flags字段,这样的box叫做Full Box。当box body中嵌套其他box时,这样的box叫做container box。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEnDF9xV-1645614591154)(https://note.youdao.com/yws/res/b/WEBRESOURCEc04aa13684ac1be5f8c2f650b6f5efdb)]

字段定义如下:

  • type:box类型,包括 “预定义类型”、“自定义扩展类型”,占4个字节;
    • 预定义类型:比如ftyp、moov、mdat等预定义好的类型;
    • 自定义扩展类型:如果type==uuid,则表示是自定义扩展类型。size(或largesize)随后的16字节,为自定义类型的值(extended_type)
  • size:包含box header在内的整个box的大小,单位是字节。当size为0或1时,需要特殊处理:
    • size等于0:box的大小由后续的largesize确定(一般只有装载媒体数据的mdat box会用到largesize);
    • size等于1:当前box为文件的最后一个box,通常包含在mdat box中;
  • largesize:box的大小,占8个字节;
  • extended_type:自定义扩展类型,占16个字节;
Movie Box,存储 mp4 的 metadata,一般位于mp4文件的开头。

moov中,最重要的两个box是 mvhd 和 trak:

  • mvhd:Movie Header Box,mp4文件的整体信息,比如创建时间、文件时长等;
  • trak:Track Box,一个mp4可以包含一个或多个轨道(比如视频轨道、音频轨道),轨道相关的信息就在trak里。trak是container box,至少包含两个box,tkhd、mdia;

字段含义如下:

  • creation_time:文件创建时间;
  • modification_time:文件修改时间;
  • timescale:一秒包含的时间单位(整数)。举个例子,如果timescale等于1000,那么,一秒包含1000个时间单位(后面track等的时间,都要用这个来换算,比如track的duration为10,000,那么,track的实际时长为10,000/1000=10s);
  • duration:影片时长(整数),根据文件中的track的信息推导出来,等于时间最长的track的duration;
  • rate:推荐的播放速率,32位整数,高16位、低16位分别代表整数部分、小数部分([16.16]),举例 0x0001 0000 代表1.0,正常播放速度;
  • volume:播放音量,16位整数,高8位、低8位分别代表整数部分、小数部分([8.8]),举例 0x01 00 表示 1.0,即最大音量;
  • matrix:视频的转换矩阵,一般可以忽略不计;
  • next_track_ID:32位整数,非0,一般可以忽略不计。当要添加一个新的track到这个影片时,可以使用的track id,必须比当前已经使用的track id要大。也就是说,添加新的track时,需要遍历所有track,确认可用的track id;

二、通过工具获取mp4时长及元信息

通过ffprobe对视频进行分析

实现代码

func ParseEndTime(dir, filename string) (int, error) {

	arg := []string{"-show_format", dir + filename}
	cmd := exec.Command("ffprobe", arg...)
	if stout, err := cmd.CombinedOutput(); err != nil {
		return 0, err
	} else {
		exec.Command(`printf "\033[2J\033[3J\033[1;1H"`)
		stoutSplit := strings.Split(string(stout), "duration=")
		if len(stoutSplit) == 2 {
			endTime := strings.Replace(strings.Split(stoutSplit[1], "size")[0], "\n", "", -1)
			return stringToInt(endTime)
		}
		parseErr := errors.New("this video not exist duration !")
		return 0, parseErr
	}

}

三、解析mp4获取关键BOX获取元信息

通过格式获取时长信息
// GetVideoTime 根据文件获取视频时长
func GetVideoTime(dir, files string) (uint32, error) {
	file, err := os.Open(dir + files)
	if err != nil {
		return 0, err
	}
	duration, err := GetMP4Duration(file)
	if err != nil {
		return 0, err
	}
	return duration, nil
}

// GetMP4Duration 获取视频时长,以秒计
func GetMP4Duration(reader io.ReaderAt) (lengthOfTime uint32, err error) {
	var info = make([]byte, 0x10)
	var boxHeader BoxHeader
	var offset int64 = 0
	// 获取moov结构偏移
	for {
		_, err = reader.ReadAt(info, offset)
		if err != nil {
			return
		}
		boxHeader = getHeaderBoxInfo(info)
		fourccType := getFourccType(boxHeader)
		if fourccType == "moov" {
			break
		}
		// 有一部分mp4 mdat尺寸过大需要特殊处理
		if fourccType == "mdat" {
			if boxHeader.Size == 1 {
				offset += int64(boxHeader.Size64)
				continue
			}
		}
		offset += int64(boxHeader.Size)
	}
	// 获取moov结构开头一部分
	moovStartBytes := make([]byte, 0x100)
	_, err = reader.ReadAt(moovStartBytes, offset)
	if err != nil {
		return
	}
	// 定义timeScale与Duration偏移
	timeScaleOffset := 0x1C
	durationOffest := 0x20
	timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
	Duration := binary.BigEndian.Uint32(moovStartBytes[durationOffest : durationOffest+4])
	lengthOfTime = Duration / timeScale
	return
}

// getHeaderBoxInfo 获取头信息
func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
	buf := bytes.NewBuffer(data)
	binary.Read(buf, binary.BigEndian, &boxHeader)
	// binary.Read(buf, binary.LittleEndian, &boxHeader)
	return
}

// getFourccType 获取信息头类型
func getFourccType(boxHeader BoxHeader) (fourccType string) {
	fourccType = string(boxHeader.FourccType[:])
	return
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抬头看天空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值