参考:
MPEG4码流简单分析:http://blog.csdn.net/axdc_qa_team/article/details/4042762
关于MPEG4 码流(mpeg4 raw data)的格式分析:http://blog.csdn.net/vblittleboy/article/details/6695973
mpeg4 码流格式:http://blog.csdn.net/wuzhong325/article/details/7195171
=====================================================================
MPEG4码流视频关键帧头部16个字节,非关键帧8个字节(均包含四字节ID),说明如下:
关键帧:
字节 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
值 |
0 |
0 |
1 |
FB |
XX |
R |
W&H |
Date time |
Length |
|||||||
含义 |
ID |
|
|
图像大小 |
时间戳 |
帧长度 |
非关键帧
字节 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
值 |
0 |
0 |
1 |
FA |
Length |
|||
含义 |
ID |
帧长度 |
XX:保留。
RATE:帧率,低5位表示帧率,目前取值从1到30,高三位表示解交错,可选0,1,2其中0做解交错,2不做解交错。
WIDTH和HEIGHT表示视频数据高度和宽度,一个字节最大256,所以存储的信息为真实高度和宽度的1/8。
TIMESTAMP:帧时间戳:
struct DateTime
{
DWORDsecond :6; // 秒 1-60
DWORDminute :6; // 分 1-60
DWORDhour :5; // 时 1-24
DWORDday :5; // 日 1-31
DWORDmonth :4; // 月 1-12
DWORDyear :6; // 年 2000-2063
};
非关键帧的时间戳是根据帧率和对关键帧的偏移计数计算出来的。
LENGTH:帧长度,低字节优先,不包括帧头长度(16字节或者8字节,这个需要注意)
H.264码流跟MPEG4的最大不同在于ID,关键帧ID为000001FD,非关键帧ID为000001FC。
给一个例子:
===============================================================================
测试解码器测试了很久,由于需要将H264和MPEG4的码流进行分析和判断,并逐帧输入解码器进行测试,如何正确的分析码流,并将Video送给我们的解码器做Decode的呢?耐着性子找了很多资料,主要也因为我本身对MPEG4和H264的码流的格式并不懂,自己在视频编码方面的积累也实在是太少了,所以也确实挺头疼的。后来就直接在网上找是否有对码流的各个部分意义的解释,开始搜索码流中的的StartCode。高兴的是,最终找到了一些有用的讯息,得以继续进行测试代码的撰写。
今天就把MPEG4码流的分析和它的I,P,B Frame的判定方法在这里简要记录一下吧,供日后的翻看和大家的参考。!
MPEG4码流分析:
0x00, 0x00, 0x01, 0xB0作为一个VOS的开始;
0x00, 0x00, 0x01, 0xB6作为一个VOP的开始,紧跟着VOP开始的,有一个2bit 的标志,用来表示这个Frame到底是一个 I Frame,P Frame,B Frame抑或是S Frame(GMS-VOP)
标志如下:
00: I Frame
01: P Frame
10: B Frame
11: S Frame
但是,有关这 2bit 是在0xB6的后面字节的高位还是低位,却没有很明确的描述。
于是又回头开始针对某个MPEG4编码好的文件开始分析,结果终于发现,判定方法如下:
1.可以写一个判定VOP,或者VOS开头的函数:
static unsigned char *Find_VOP_Start(unsigned char *addrp, unsigned int FindSizes)
{
while(pos < FindSizes)
{
if(addrp[pos] == 0x00)
if(addrp[pos + 1] == 0x00)
if(addrp[pos + 2] == 0x01)
if(addrp[pos + 3] == 0xB0)
break; //判断是否是VOS头;
if(addrp[pos] == 0x00)
if(addrp[pos + 1] == 0x00)
if(addrp[pos + 2] == 0x01)
if(addrp[pos + 3] == 0xB6)
break; //判断是否是VOP头
pos++;
}
if(pos< FindSizes - 4)
{
return addrp+pos+4;
}
else
return NULL;
}
size_t nRead = fread(lpSrc, 1, lSize, fp);
fseek(fp, 0, SEEK_SET);
while (!feof(fp))
{
unsigned char *p=Find_VOP_Start(lpSrc,lSize);
if (pos) //pos为文件当前指针
{
length=pos-poslast+header; //每帧长度为两个StartCode之间的字节数;
if (length
可以简单拿个图说明一下,下图中,第一个VOS的开头,第二个是一个I Frame:
by lydia
=====================================================================
MPEG4码流开头往往如下:
00 00 01 B0 F5 00 00 01 B5 09 00 00 01 00 00 00
01 20 08 86 84 00 3F 18 58 21 20 A3 1F 00 00 01
B2 58 76 69 44 30 30 36 32 00 00 01 B6
其格式为:
MP4V type b0 size 5 //vosh
MP4V type b5 size 5 //vo
MP4V type 0 size 4
MP4V type 20 size 16 //vol
ParseVol: timeBits 15 timeTicks 24000 frameDuration 1001
MP4V type b6 size 5606 //vop
其宏定义为:
#define MP4AV_MPEG4_VOL_START 0x20
#define MP4AV_MPEG4_VOSH_START 0xB0
#define MP4AV_MPEG4_VO_START 0xB5
#define MP4AV_MPEG4_VOP_START 0xB6
#define MP4AV_MPEG4_USER_DATA_START 0xB2
使用格式工厂,看到如下
根据标准,定义如下(http://bbs.lmtw.com/dispbbs.asp?boardID=20&ID=124481&star=2&page=15)
00 00 01 B0是视频对象序列开始标志(VISOBJSEQ_START_CODE),其后的数据只有一位F4,表示此视频对象编码序列编码的Profile与Level类型是XVID_PROFILE_AS_L4, Profile类型数值定义于xvid.h。
00 00 01 B5是视频对象开始标志(VISOBJ_START_CODE),其后只有一位16进制数据09,从这一位数据可以获得视频对象版本号、视频类型和视频信号类型信息,详细介绍见图5-11。
00 00 01 00是视频对象开始标志 (VIDOBJ_START_CODE),00 00 01 20是视频对象层开始标志(VIDOBJLAY_START_CODE),其后的11位16进制数据比较重要,解码所需要的VOP纵横比、视频对象形状和图像分辨率等数据都是从这11位数据获得的,详细介绍见图5-11,图中未标记用途的数据表示未使用。
00 00 01 B2是用户数据开始标志(USERDATA_START_CODE),十六进制用户数据共有8位。
00 00 01 B6是VOP开始标志(VOP_START_CODE),每个VOP编码数据都以VOP标识头开始,VOP标识头比较简单,如果用16进制数据表示其数值为00 00 01 B6。解码的时候,以00 00 01 B6来判定是不是一个VOP的开始,并从VOP标识头后的数据读取编码类型,量化值等参数后完成一帧VOP的解码。
然后利用ffmpeg转码为mp4文件。
附:
xvid yuv->m4v
xvid_encraw.exe -i foreman_cif.yuv -w 352 -h 288
-framerate 15 -csp i420 -o foreman_cif.m4v
ffmpeg m4v->mp4
ffmpeg -s cif -vcodec mpeg4 -i foreman_cif.m4v foreman_cif.mp4