FLV格式解析

参考文章:

Flv格式详解(表格形式,图解)

FLV格式解析(二进制形式,图解)

FLV文件解析代码


FLV文件布局:

image-20210604161420254

FLV Header

具体字段:

image-20210604161627649

代码表达:

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.flvimage-20210604120926407

在Windows下也可以使用Notepad++查看文件二进制,但需要先添加一个HEX-Editor插件。


FLV Body

Flv Body由一个个preTagSize和Tag组成,preTagSize标识前一个Tag的大小,其中第一个preTagSize即preTagSize0大小为0。

Tag的布局如下:image-20210604162436800

代码表达:

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);
}

编译运行:
image-20210604123921341

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字节)
image-20210604164904431

image-20210604171227573

第一个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;
        }
    }
}

image-20210604211643192


2)Audio Tag和Video Tag

image-20210604223213753

image-20210604223123801

代码表达(略)

image-20210604223627536

其实基本的音频和视频信息都在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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值