c++ MP4文件解析

最近写了个解析MP4的类来解析MP4文件,现在来聊一聊,有不对的地方,请大家指正,谢谢。

说点要注意的地方:

1.首先MP4的数据是网络字节序,也就是按照大端进行存储(高位在低地址),所以我们在解析box的时候,需要做个转换。

类似这样:

uint32_t n_to_uInt(const uint32_t val)
{
    return (val >> 24) + ((val & 0xFF0000) >> 8) + ((val & 0xFF00) << 8) + ((val & 0xFF) << 24);
}

uint64_t n_to_uInt64(const uint64_t val)
{
    return (val >> 56) + ((val & 0xFF000000000000) >> 40) + ((val & 0xFF0000000000) >> 24) + ((val & 0xFF00000000) >> 8)
        + ((val & 0xFF000000) << 8) + ((val & 0xFF0000) << 24) + ((val & 0xFF00) << 40) + ((val & 0xFF) << 56);
}

2:对于文件比较大的情况,可能需要_fseeki64。

进入正题:

MP4是 由一系列的box组成,有的box是相对独立的,比如ftyp box。有的box 中还包含其他的Box,比如moov Box等。

box由header和body组成,其中header指明box的size和type。size是整个box的大小(包括header+body)。

这个size为1,则表示box长度需要64位来表示,在后面会有一个64bits位的largesize用来描述box的长度。

如果size为0,表示该box为文件的最后一个box,文件结尾。

box分为两种,Box和Fullbox。FullBox 是 Box 的扩展,字段中增加了version 和 flags字段,主要是在 trak box 中使用

聊聊音频和视频:

视频:我们关注 宽、高、帧率。帧率就是一秒钟多少张图片,一般来说,超过25帧,人眼就感觉不出画面中图片的切换。还有就是时间戳,h264编码的视频时间戳是90000,也就是把一秒切成90000份,每秒25帧。也就是每帧的时间戳是90000/25=3600.

看解码视频的时间戳就是以3600递增的,例如:0,3600,7200.。。。

音频:我们关注采样率、通道数。

这个MP4封装格式,有一些sample和chunk。我的理解,对于视频,一个sample就是一帧。chunk是包含一个或者多个sample。

为啥要这样?我看后面的box知道,其实有很多 sample的时间戳是一样的,封装在一个chunk,至少可以省点空间吧。当然每帧的数据长度是不一样的。

至于box的总图,网上到处都是,我就不贴图了。

先说说一些常见的box的结构,后面聊聊怎么用,比如我怎么找到MP4的30s的位置。

先解析前面的8个字节:得到size和type,根据type解析不同的box。

typedef struct
{
	uint32_t size;
	char type[4];
}Header;

(1)Ftyp Box:

//参考vlc 数据格式的定义
typedef struct MP4_Box_data_ftyp_s
{
    char major_brand[4];
    int  minor_version;
    //char compatible_brands[16]; 长度待定
}MP4_Box_data_ftyp_t;

Header header;
memset(&header, 0, sizeof(Header));

int read_len = fread(&header, 1, sizeof(Header), fp);
if (read_len != sizeof(Header)) 
		return ;
		
uint32_t  box_total_size = n_to_uInt(header.size);
		
if (strncmp("ftyp", header.type, 4) == 0)
{
				
	   MP4_Box_data_ftyp_s ftyp_box;
	   fread(&ftyp_box, 1, sizeof(MP4_Box_data_ftyp_t), fp);
	   uint32_t remain = box_total_size  - sizeof(Header)-sizeof(MP4_Box_data_ftyp_t);

	   char temp[100] = { 0 };
	   fread(temp, 1, remain, fp);
	
	   printf("ftyp box,%s\n",temp);
}

类似这样解析。

(2)mdat box 如果文件很大,这个size可能等于1,需要借助64位来解析。

(3)mvhd box 是moov的一个子box。

typedef struct MP4_Box_data_mvhd_s
{
	char version;//版本
	char flags[3];//标志 0 
	int creation_time;
	uint32_t modification_time;
	uint32_t timescale;
	uint32_t duration;
	uint32_t rate;//播放速度
	uint16_t volume;//音量
	char reserved[10];
	char matrix[36];//36字节视频变换矩阵
	int preview_time;
	int preview_duration;
	int poster_time;
	int selection_time;
	int selection_duration;
	int current_time;
	int next_track_id;
}MP4_Box_data_mvhd_t;

从mvhd 可以获取视频的时长。
uint32_t rate = mvhd.rate;
uint32_t volume = mvhd.volume;
	
uint32_t duration = n_to_uInt(mvhd.duration) / n_to_uInt(mvhd.timescale);
uint32_t hour = duration / 3600;
uint32_t minute = (duration - 3600 * hour)/60;
uint32_t second = (duration - 3600 * hour) % 60;
printf("  mvhd Box,播放时长=%02u:%02u:%02u,播放速度=%u:%u,播放音量=%u:%u\n", hour, minute, second ,(rate & 0xFFFF0000 >> 16), (rate & 0xFFFF),(volume & 0xFF00 >> 8), (rate & 0xFF));

(4)trak box ,分为视频或者音频trak。每种trak 一般包括 tkhd box,edts box ,mdia box。

(5)tkhd box,从此box中可以获取视频的宽高。

typedef struct MP4_Box_data_tkhd_s
{
	char version;//版本
	char flags[3];//标志 0 
	//uint32_t creation_time;//3byte

	char creation_time[3];//3byte

	uint32_t modification_time;
	uint32_t track_id;
	char reserved[4];
	uint32_t duration;
	char reserved1[8];
	short layer;//default:0
	short alternate_group;
	uint16_t volume;
	char reserved2[2];
	char matrix[36];

	uint8_t width[3];
	uint8_t width_Q;
	uint8_t height[3];
	uint8_t height_Q; 
	char padding;

}MP4_Box_data_tkhd_t;

MP4_Box_data_tkhd_t tkhd;
	uint32_t width  = ((tkhd.width[0]   & 0xFF)<<16) + ((tkhd.width[1]  & 0xFF) << 8) + (tkhd.width[2]  & 0xFF);
	uint32_t height = ((tkhd.height[0] & 0xFF)<<16) + ((tkhd.height[1] & 0xFF) << 8) + (tkhd.height[2] & 0xFF);

(6) mdia box,此box包含 mdhd box,hdlr box,minf box,从hdlr中可以解析数据类型。()

(7)minf box ,此box包含vmhd box,dinf  box,stbl box ,stbl 比较复杂。下面重点分析:

(6)stsd box,从此box 可以获取视频的数据类型和解码 用到的sps pps。

 

 


版权声明:本文为esdhhh原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。