最近写了个解析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。