FLV封装格式

FLV包含文件头和文件体两部分,文件体由多个Tag组成,Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种 。如下图

  • Type为0x08即音频

名称 音频编码类型 音频采样率 音频采样精度 音频类型
0 Linear PCM,platform endian 5.5kHz 8bits sndMono
1 ADPCM 11KHz 16bits sndStereo
2 MP3 22 kHz
3 Linear PCM,little endian 44 kHz
4 Nellymoser 16-kHz mono
5 Nellymoser 8-kHz mono
6 Nellymoser
7 G.711 A-law logarithmic PCM
8 G.711 mu-law logarithmic PCM
9 reserved
10 AAC
14 MP3 8-Khz
15 Device-specific sound
  • Type为0x09即视频

名称 帧类型 视频编码类型
1 keyframe (for AVC,a seekable frame) JPEG (currently unused)
2 inter frame (for AVC,a nonseekable frame) Sorenson H.263
3 disposable inter frame (H.263 only) Screen video
4 generated keyframe (reserved for server use) On2 VP6
5 video info/command frame On2 VP6 with alpha channel
6 Screen video version 2
7 AVC
  • Type为0x12即控制帧

    该类型Tag又通常被称为Metadata Tag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。通常该类型Tag会跟在File Header后面作为第一个Tag出现,而且只有一个。

    • AMF1: 第1个字节表示AMF包类型,一般总是0x02,表示字符串。第2-3个字节为UI16类型值,标识字符串的长度,一般总是0x000A(“onMetaData”长度)。后面字节为具体的字符串,一般总为“onMetaData”(6F,6E,4D,65,74,61,44,61,74,61)

    • 第1个字节表示AMF包类型,一般总是0x08,表示数组。第2-5个字节为UI32类型值,表示数组元素的个数。后面即为各数组元素的封装,数组元素为元素名称和值组成的对。

      | 值 | 含义 |
      | ————— | ———— |
      | duration | 时长 |
      | width | 视频宽度 |
      | height | 视频高度 |
      | videodatarate | 视频码率 |
      | framerate | 视频帧率 |
      | videocodecid | 视频编码方式 |
      | audiosamplerate | 音频采样率 |
      | audiosamplesize | 音频采样精度 |
      | stereo | 是否为立体 |
      | audiocodecid | 音频编码方式 |
      | filesize | 文件大小 |

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#pragma pack(1) //包大小设为1

#define TAG_TYPE_SCRIPT 18
#define TAG_TYPE_AUDIO 8
#define TAG_TYPE_VIDEO 9

typedef unsigned char byte;
typedef unsigned int uint;

typedef struct {
byte Signature[3];
byte Version;
byte Flags;
uint DataOffset;
} FLV_HEADER;

typedef struct {
byte TagType;
byte DataSize[3];
byte Timestamp[3];
uint Reserved;
} TAG_HEADER;


//大端转小端
uint reverse_bytes(byte *p, char c) {
int r = 0;
int i;
for (i=0; i<c; i++)
r |= ( *(p+i) << (((c-1)*8)-8*i));
return r;
}

int simplest_flv_parser(char *url){

//whether output audio/video stream
int output_a=1;
int output_v=1;

FILE *ifh=NULL,*vfh=NULL, *afh = NULL;

//FILE *myout=fopen("output_log.txt","wb+");
FILE *myout=stdout;

FLV_HEADER flv;
TAG_HEADER tagheader;
uint previoustagsize, previoustagsize_z=0;
uint ts=0, ts_new=0;

ifh = fopen(url, "rb+");
if ( ifh== NULL) {
printf("Failed to open files!");
return -1;
}

//FLV file header
fread((char *)&flv,1,sizeof(FLV_HEADER),ifh);

fprintf(myout,"============== FLV Header ==============\n");
fprintf(myout,"Signature: 0x %c %c %c\n",flv.Signature[0],flv.Signature[1],flv.Signature[2]);
fprintf(myout,"Version: 0x %X\n",flv.Version);
fprintf(myout,"Flags : 0x %X\n",flv.Flags);
fprintf(myout,"HeaderSize: 0x %X %d\n",reverse_bytes((byte *)&flv.DataOffset, sizeof(flv.DataOffset)));
fprintf(myout,"========================================\n");

//move the file pointer to the end of the header
fseek(ifh, reverse_bytes((byte *)&flv.DataOffset, sizeof(flv.DataOffset)), SEEK_SET);

//process each tag
do {
previoustagsize = _getw(ifh);

fread((void *)&tagheader,sizeof(TAG_HEADER),1,ifh);

//int temp_datasize1=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize));
int tagheader_datasize=tagheader.DataSize[0]*65536+tagheader.DataSize[1]*256+tagheader.DataSize[2];
int tagheader_timestamp=tagheader.Timestamp[0]*65536+tagheader.Timestamp[1]*256+tagheader.Timestamp[2];

char tagtype_str[10];
switch(tagheader.TagType){
case TAG_TYPE_AUDIO:sprintf(tagtype_str,"AUDIO");break;
case TAG_TYPE_VIDEO:sprintf(tagtype_str,"VIDEO");break;
case TAG_TYPE_SCRIPT:sprintf(tagtype_str,"SCRIPT");break;
default:sprintf(tagtype_str,"UNKNOWN");break;
}
fprintf(myout,"[%6s] %6d %6d |",tagtype_str,tagheader_datasize,tagheader_timestamp);

//if we are not past the end of file, process the tag
if (feof(ifh)) {
break;
}

//process tag by type
switch (tagheader.TagType) {

case TAG_TYPE_AUDIO:{
char audiotag_str[100]={0};
strcat(audiotag_str,"| ");
char tagdata_first_byte;
tagdata_first_byte=fgetc(ifh);
int x=tagdata_first_byte&0xF0;
x=x>>4;
switch (x)
{
case 0:strcat(audiotag_str,"Linear PCM, platform endian");break;
case 1:strcat(audiotag_str,"ADPCM");break;
case 2:strcat(audiotag_str,"MP3");break;
case 3:strcat(audiotag_str,"Linear PCM, little endian");break;
case 4:strcat(audiotag_str,"Nellymoser 16-kHz mono");break;
case 5:strcat(audiotag_str,"Nellymoser 8-kHz mono");break;
case 6:strcat(audiotag_str,"Nellymoser");break;
case 7:strcat(audiotag_str,"G.711 A-law logarithmic PCM");break;
case 8:strcat(audiotag_str,"G.711 mu-law logarithmic PCM");break;
case 9:strcat(audiotag_str,"reserved");break;
case 10:strcat(audiotag_str,"AAC");break;
case 11:strcat(audiotag_str,"Speex");break;
case 14:strcat(audiotag_str,"MP3 8-Khz");break;
case 15:strcat(audiotag_str,"Device-specific sound");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x=tagdata_first_byte&0x0C;
x=x>>2;
switch (x)
{
case 0:strcat(audiotag_str,"5.5-kHz");break;
case 1:strcat(audiotag_str,"1-kHz");break;
case 2:strcat(audiotag_str,"22-kHz");break;
case 3:strcat(audiotag_str,"44-kHz");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x=tagdata_first_byte&0x02;
x=x>>1;
switch (x)
{
case 0:strcat(audiotag_str,"8Bit");break;
case 1:strcat(audiotag_str,"16Bit");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x=tagdata_first_byte&0x01;
switch (x)
{
case 0:strcat(audiotag_str,"Mono");break;
case 1:strcat(audiotag_str,"Stereo");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
fprintf(myout,"%s",audiotag_str);

//if the output file hasn't been opened, open it.
if(output_a!=0&&afh == NULL){
afh = fopen("output.mp3", "wb");
}

//TagData - First Byte Data
int data_size=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))-1;//-1是个迷 FirstBYteData为什么是1和减掉

if(output_a!=0){//读完TagData
for (int i=0; i<data_size; i++)
fputc(fgetc(ifh),afh);//将音频输出到mp3文件
}else{
for (int i=0; i<data_size; i++)
fgetc(ifh);
}
break;
}
case TAG_TYPE_VIDEO:{
char videotag_str[100]={0};
strcat(videotag_str,"| ");
char tagdata_first_byte;
tagdata_first_byte=fgetc(ifh);
int x=tagdata_first_byte&0xF0;
x=x>>4;
switch (x)
{
case 1:strcat(videotag_str,"key frame ");break;
case 2:strcat(videotag_str,"inter frame");break;
case 3:strcat(videotag_str,"disposable inter frame");break;
case 4:strcat(videotag_str,"generated keyframe");break;
case 5:strcat(videotag_str,"video info/command frame");break;
default:strcat(videotag_str,"UNKNOWN");break;
}
strcat(videotag_str,"| ");
x=tagdata_first_byte&0x0F;
switch (x)
{
case 1:strcat(videotag_str,"JPEG (currently unused)");break;
case 2:strcat(videotag_str,"Sorenson H.263");break;
case 3:strcat(videotag_str,"Screen video");break;
case 4:strcat(videotag_str,"On2 VP6");break;
case 5:strcat(videotag_str,"On2 VP6 with alpha channel");break;
case 6:strcat(videotag_str,"Screen video version 2");break;
case 7:strcat(videotag_str,"AVC");break;
default:strcat(videotag_str,"UNKNOWN");break;
}
fprintf(myout,"%s",videotag_str);

fseek(ifh, -1, SEEK_CUR);//往回走一位 迷 前四位是tagdata_first_byte&0xF0 后四位是tagdata_first_byte&0x0F 不应该啊

//if the output file hasn't been opened, open it.
if (vfh == NULL&&output_v!=0) {
//write the flv header (reuse the original file's hdr) and first previoustagsize
vfh = fopen("output.flv", "wb");
fwrite((char *)&flv,1, sizeof(flv),vfh);
fwrite((char *)&previoustagsize_z,1,sizeof(previoustagsize_z),vfh);
}
#if 0 //延长时间戳 变成双倍
//Change Timestamp
//Get Timestamp
ts = reverse_bytes((byte *)&tagheader.Timestamp, sizeof(tagheader.Timestamp));
ts=ts*2;
//Writeback Timestamp
ts_new = reverse_bytes((byte *)&ts, sizeof(ts));
memcpy(&tagheader.Timestamp, ((char *)&ts_new) + 1, sizeof(tagheader.Timestamp));//Timestamp只占三位 大端模式 去掉首位
#endif


//TagData + Previous Tag Size
int data_size=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))+4;
if(output_v!=0){
//TagHeader
fwrite((char *)&tagheader,1, sizeof(tagheader),vfh);
//TagData
for (int i=0; i<data_size; i++)
fputc(fgetc(ifh),vfh);
}else{
for (int i=0; i<data_size; i++)
fgetc(ifh);
}
//rewind 4 bytes, because we need to read the previoustagsize again for the loop's sake
fseek(ifh, -4, SEEK_CUR);

break;
}
default:

//skip the data of this tag
fseek(ifh, reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize)), SEEK_CUR);
}

fprintf(myout,"\n");

} while (!feof(ifh));


_fcloseall();

return 0;
}