实战解决获取mp4格式文件信息,时长等及mp4存储结构探索
一、认识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
}