参考文章:
FLV文件布局:
FLV Header
具体字段:
代码表达:
typedef struct {
uint8_t signature[3]; // 'f' 'l' 'v'
uint8_t version; // 1
uint8_t flags; // 00000 1 0 1,该字节第0位和第2位表示视频、音频存在
uint8_t offset[4]; // flv body起始位置,即flv header大小
} flv_header_t;
int calc_u32b(uint8_t data[4]) { // 转换int型
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
}
注意:数据偏移量字段不能直接声明为uint32_t,否则会出现字节对齐问题,使得fread读取后文件偏移12字节
。
在Linux下以二进制形式查看文件的前64字节:hexdump -n 64 -C xxx.flv
在Windows下也可以使用Notepad++查看文件二进制,但需要先添加一个HEX-Editor插件。
FLV Body
Flv Body由一个个preTagSize和Tag组成,preTagSize标识前一个Tag的大小,其中第一个preTagSize即preTagSize0大小为0。
Tag的布局如下:
代码表达:
typedef struct {
uint8_t tag_type; // 0x08 音频, 0x09视频, 0x12 script
uint8_t data_size[3]; // tag data大小,24位
uint8_t time_stamp[3]; // 该tag的时间戳
uint8_t ts_extended; // 时间戳的扩展位
uint8_t reserved[3]; // 总是为0
} tag_header_t;
int calc_u24b(uint8_t data[3]) { // 转换为int型
return (data[0] << 16) | (data[1] << 8) | data[2];
}
解析FLV Header和Tag Header
unsigned int get_filelen(FILE *fp) {
unsigned int save = ftell(fp);
fseek(fp, 0, SEEK_END);
unsigned int res = ftell(fp);
fseek(fp, save, SEEK_SET);
return res;
}
void flv_parser(const char *file) {
FILE *fp = fopen(file, "r");
// 1. analyses flv header
flv_header_t header;
fread(&header, sizeof(header), 1, fp);
// 如果offset直接声明为uint32_t,会出现字节对齐问题!!!
// printf("cur offset of file:%ld\n", ftell(fp));
printf("signature:%c%c%c version:%d\n", header.signature[0], \
header.signature[1], header.signature[2], header.version);
printf("audio:%d video:%d\n", header.flags & 1, (header.flags>>2) & 1);
printf("header size:%d\n", calc_u32b(header.offset));
// 2. analyses flv body
unsigned int filelen = get_filelen(fp);
uint32_t prev_tagsize = 0;
tag_header_t tag;
printf("TAG TYPE | DATA_SIZE | TIME_STAMP\n");
// while (!feof(fp)) { // fseek跳不到文件尾,无法退出循环
while (1) {
// 第一个previous_tag_size为0
fread(&prev_tagsize, sizeof(prev_tagsize), 1, fp);
fread(&tag, sizeof(tag), 1, fp);
char str[10];
switch (tag.tag_type) {
case 0x8: sprintf(str, "AUDIO");break;
case 0x9: sprintf(str, "VIDEO");break;
case 0x12: sprintf(str, "SCRIPT");break;
default: sprintf(str, "UNKOWN");break;
}
int data_size = calc_u24b(tag.data_size);
int time_stamp = calc_u24b(tag.time_stamp);
if (tag.ts_extended) { // 扩展位为高位
time_stamp += tag.ts_extended << 24;
}
printf("%8s | %6d | %6d\n", str, data_size, time_stamp);
if (ftell(fp) + data_size < filelen)
fseek(fp, data_size, SEEK_CUR); // 跳过tag_data
else
break;
}
fclose(fp);
}
编译运行:
Tag Data
Flv有三种类型的Tag:Script Tag, Audio Tag和Video Tag
1)Script Tag
通常作为第一个Tag出现在文件头后面,而且只存在一个Script Tag;用于存放一些关于FLV视频和音频的元数据信息,如duration、width、height等。
以二进制形式查看Script Tag(flv header、preTagSize0、tag header、tag_data,共386字节)
第一个AMF包:
第一个字节一般为0x02,表示字符串,第2-3个字节表示字符串的长度,一般为0x000A,后面跟的就是字符串,一般为"onMetaData"。
第二AMF包:
第一个字节为0x08,表示数组,第2-5个字节表示数组长度,后面跟着就是数组的元素,元素格式为:名字长度 + 名字 + 对应值(valuetype + value,0表示double型,1表示整型,2表示字符串),
最后以“009”结尾。
代码表达
:
void process_script_tag(uint8_t *data, int n) {
int idx = 0;
while (1) {
if (n - idx <= 3) // 解析完毕,只剩最后的结束标识00 00 09
break;
uint8_t amf_type = data[idx]; idx += 1;
switch (amf_type) {
case 0x02:
{
int len = data[idx] << 16 | data[idx+1]; idx += 2;
char str[len+1];
memcpy(str, &data[idx], len); idx += len;
str[len] = '\0';
printf("type:%d size:%d content:%s\n", amf_type, len, str);
} break;
case 0x08:
{
int array_count = calc_u32b(&data[idx]); idx += 4;
printf("arrary_count: %d\n", array_count);
for (int i = 0; i < array_count; i++) {
int key_size = calc_u16b(&data[idx]); idx += 2;
char key_str[key_size+1];
memcpy(key_str, &data[idx], key_size); idx += key_size;
key_str[key_size] = '\0';
printf("%s: ", key_str);
int valuetype = data[idx]; idx += 1;
switch (valuetype) {
case 0:
printf("%lf\n", get_double_data(&data[idx]));
idx += 8;
break;
case 1:
printf("%d\n", data[idx]);
idx += 1;
break;
case 2:
{
int size = calc_u16b(&data[idx]);
idx += 2;
char str[size+1];
memcpy(str, &data[idx], size); str[size] = '\0';
printf("%s\n", str);
idx += size;
} break;
default: break;
}
}
} break;
default:
break;
}
}
}
2)Audio Tag和Video Tag
代码表达(略)
其实基本的音频和视频信息都在Script Tag中可以找到,而且每个音频视频的Tag的参数基本没有变化,所以Tag Data中的参数部分只解析一次,或不解析也没有问题。
暂时就进行到这里吧,其中Video Tag Data的视频数据部分我们还可以继续解析下去,得到与解码相关的比较重要的sps和pps信息。
代码地址:https://gitee.com/zhoujiabo/audio-and-video-development/blob/master/push_stream/flv_parser.c