传送门:MP4 文档 P4 页
ftyp box 是 MP4 文件的第一个Box,这个 Box 里面包含了视频的编码格式和标准,这个 Box 的作用基本就是 MP4 这种封装格式的特有的标志,一个 MP4 中只有唯一 一个 ftyp Box。ftyp box通常放在文件的开始,通过对该box解析可以让我们的软件(播放器、demux、解析器)知道应该使用哪种协议对这该文件解析,是后续解读的基础。
名称 | 大小(byte) | 意义 | 说明 |
---|---|---|---|
Box length | 4 | Box 整体的大小 | 包含 Header 和 Data部分 |
Box type | 4 | ”ftyp”的ASCII码,表名是 ftyp Box | box 属性值,通常是固定值 |
Major brand | 4 | 商标 | 具体信息参考:http://www.ftyps.com |
Minor version | 4 | 商标版本号 | |
Compatible brand | 不定,但是可以根据长度算出(Box length - 16) | 兼容其他的版本号 | 表示可以兼容和遵从那些协议标准,是一个列表。 |
接下来我们根据上图16进制查看器展现出来的值进行分析:
定义 | 实际值(16进制) | 具体值(10进制 / ASCII) | 字段位置 |
---|---|---|---|
Box length(4 byte) | 0x00 0x00 0x00 0x18 | 24 | Header |
Box type(4 byte) | 0x66 0x74 0x78 0x70 | "ftyp" | Header |
Major brand(4 byte) | 0x6D 0x70 0x34 0x32 | "mp42" | Data |
Minor version(4 byte) | 0x00 0x00 0x00 0x00 | 0 | Data |
Compatible brand(8 byte) | 上图红框 | “mp41isom” | Data |
这里我们有个需求就是打印对应的 Box 信息,那么我们便要重构一下Box Header 和 Base Box
// BaseBox.h
struct BoxHeader{
// ...
// 其他都不变,这里就添加一条语句进行对应的Box类型的返回
BOX_TYPE GetType() {
if(_h_type[0]=='f'&&_h_type[1]=='t'&&_h_type[2]=='y'&&_h_type[3]=='p') return FTYP;
return ERROR;
}
};
class BaseBox{
public:
// ...
// 其他都不变,就加个虚函数,子Box有打印信息需求就重写这个方法
virtual void PrintDataInfo() {};
};
class TimeFTYBox : public BaseBox{
public:
/* 这里你可以把刚刚解析的属性值放在这,但是我认为这些不太重要就先不写了,直接解析里面的data数据
unsigned int Major_brand;
unsigned int Minor_version;
...
*/
public:
TimeFTYBox(BoxHeader h);
TimeFTYBox(BoxHeader h, Timebyte * d): BaseBox(h, d){};
~TimeFTYBox();
void PrintDataInfo() override;
};
实现
// TimeFTYBox.cpp
TimeFTYBox::TimeFTYBox(BoxHeader h) : BaseBox(h) {
if (h.GetSize()) {
data = new Timebyte[h.GetSize()];
}
}
void TimeFTYBox::PrintDataInfo() {
if (!data) return;
h.to_string();
if (h.GetSize() > 16) {
printf("Major brand: %c%c%c%c\n", data[0], data[1], data[2], data[3]);
printf("Minor version: %ud\n", data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
}
printf("Compatible brand: ");
for (int i = 8; i < h.GetSize() - 8; ++i) {
printf("%c", data[i]);
}
printf("\n");
}
TimeFTYBox::~TimeFTYBox() {
}
接下来我们定义一个用来打开 MP4 文件和解析文件的类,这个大家可以参考
// TimeMP4.h
#include <iostream>
enum OpenType {
RB,
WB
};
class TimeMP4 {
private:
const char * filePath;
OpenType t;
FILE *file;
TimeFTYBox *ftyBox = nullptr;
public:
TimeMP4(const char * path, OpenType t=RB);
~TimeMP4();
bool Open();
void LodingBox();
void test();
};
实现
//
// Created by Time on 2021/12/9.
//
#include "TimeMP4.h"
TimeMP4::TimeMP4(const char *path, OpenType t)
:filePath(path), t(t)
{
}
TimeMP4::~TimeMP4() {
if(ftyBox) {
delete ftyBox;
ftyBox = nullptr;
}
}
bool TimeMP4::Open() {
int re;
#ifdef _WINDOWS
if(t == WB)
re = fopen_s(&file, filePath, "wb+");
else
re = fopen_s(&file, filePath, "rb+");
#endif
#ifdef __APPLE__
if(t == WB)
file = fopen(filePath, "wb+");
else
file = fopen(filePath, "rb+");
re = file ? 0 : 1;
#endif
return !re;
}
void TimeMP4::LodingBox() {
if(!file) return;
while (!feof(file)) {
BoxHeader header;
unsigned int len;
fread(header._h_size, 1, 4, file);
fread(header._h_type, 1, 4, file);
if(header.GetSize() == 1) {
len = 16;
}else if(header.GetSize() == 0) {
break;
}
len = 8;
if(header.GetType() == FTYP) {
ftyBox = new TimeFTYBox(header);
fread(ftyBox->GetData(),1,header.GetSize()-len, file);
}else {
if(header.GetSize() >> 31) {
#ifdef _WINDOWS
_fseeki64(file, header.GetSize()-len, 1);
#endif
#ifdef __APPLE__
fread(nullptr, 1, header.GetSize()-len, file);
#endif
}else {
fseek(file, header.GetSize()-len, 1);
}
}
}
}
void TimeMP4::test() {
if(ftyBox) {
ftyBox->PrintDataInfo();
}
}
好了现在我们都写好了,进行测试一下
int main(){
cout << "testMP4" << endl;
TimeMP4 mp4("../../resource/demo1.mp4");
if(!mp4.Open()) {
cout << "open failed" << endl;
return;
}
mp4.LodingBox();
mp4.test();
}
最后执行情况和我们分析的一样那么这个 Box 的解析就到这里