手撕FLV协议

首发地址,会更错

实现效果

纯代码实现分离FLV音视频流,并组装成AAC和H264文件,最后能正常播放。注:本文只对有AAC和H264格式音视频流组成的flv进行分离。

FLV协议

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyU21eaj-1610869332555)(07_flv/flv-pr.jpg)]

注:脚本部分本文未使用,可前往这查看

实现代码

代码注释已经很详细了,其实就是对照协议表进行数据解析,其中:

#include <cstdlib>

/**
 * @author 秦城季
 * @email xhunmon@126.com
 * @Blog https://qincji.gitee.io
 * @date 2021/01/01
 * description:   <br>
 */
#include <cstdio>
#include <iostream>

using namespace std;

typedef struct Tag {
    int TagType;
    unsigned DataSize;
    unsigned Timestamp;
    unsigned TimestampExtended;
    int StreamID;
    unsigned char *Data;
} Tag;

//从flv中第一帧获取的数据
typedef struct FLV_AAC_HEAD {
    unsigned char audioObjectType: 5;
    unsigned char samplingFrequencyIndex: 4;
    unsigned char channelConfiguration: 4;
    unsigned char other: 3;//占位;这个值不需要的 000
} FLV_AAC_HEAD;

//aac封装 每个帧头结构设定恒为7Byte
typedef struct AAC_ADST_HEAD {
    //1. adts_fixed_header
    unsigned short syncword: 12;           /* 恒为 1111 1111 1111*/
    unsigned char ID: 1;                    /* 0:MPEG-4,1:MPEG-2*/
    unsigned char layer: 2;                  /* 00*/
    unsigned char protection_absent: 1;     /* 是否使用error_check()。0:使用,1:不使用。*/
    unsigned char profile: 2;               /* AAC类型*/
    unsigned char sampling_frequency_index: 4;/* 采样率的数组下标,即:sampling frequeny[sampling_frequency_index] */
    unsigned char private_bit: 1;           /* 0*/
    unsigned char channel_configuration: 3; /* 声道数*/
    unsigned char original_copy: 1;         /* 0*/
    unsigned char home: 1;                  /* 0*/

    //2. adts_variable_header
    unsigned char copyright_identification_bit: 1;  /* 0*/
    unsigned char copyright_identification_start: 1; /* 0*/
    unsigned short frame_length: 13;                 /* 帧的长度,包括head*/
    unsigned short adts_buffer_fullness: 11;        /* 固定0x7FF*/
    unsigned char number_of_raw_data_blocks_in_frame: 2; /*在ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始数据块。number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC原始数据块。*/
} AAC_ADST_HEAD;

int checkFLV(FILE *fp_in) {
    int ret = 1;
    char *head = (char *) malloc(9);
    fread(head, 1, 9, fp_in);
    //Signature(FLV)
    if (head[0] != 'F' || head[1] != 'L' || head[2] != 'V') {
        printf("Not FLV File !\n");
        ret = 0;
    }
    //第5个字节为Flags(流信息),视频标志位:.... ...1;音频标志位:.... .1..
    int vFlags = head[4] & 0x01;
    int aFlags = (head[4] & 0x04) >> 1;
    if (!vFlags) {
        printf("Not Video Data !\n");
        ret = 0;
    }
    if (!aFlags) {
        printf("Not Audio Data !\n");
        ret = 0;
    }
    free(head);
    return ret;
}

int getTagAndTagSize(FILE *fp_in, Tag *tag) {
    int length = -1;
    //Tag表中除了Data之外,其他所有的字节
    unsigned char *buf = (unsigned char *) malloc(11);
    //读取Tag前面11个字节
    fread(buf, 1, 11, fp_in);
    tag->TagType = buf[0];
    tag->DataSize = (buf[1] << 16) + (buf[2] << 8) + buf[3];
    tag->Timestamp = (buf[4] << 16) + (buf[5] << 8) + buf[6];
    tag->TimestampExtended = buf[7];
    tag->StreamID = (buf[8] << 16) + (buf[9] << 8) + buf[10];
    tag->Data = (unsigned char *) calloc(tag->DataSize, sizeof(char));
    //读取Data
    fread(tag->Data, 1, tag->DataSize, fp_in);
    //读取PreviousTagSize的4个字节
    fread(buf, 1, 4, fp_in);
    //这个长度就是 Tag(n)+PreviousTagSize(n)的长度
    length = 11 + tag->DataSize + 4;
    unsigned PreviousTagSize = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
    free(buf);
    fprintf(stdout, "%9d| %9d| %10d| %18d| %9d| %16d|\n", tag->TagType, tag->DataSize, tag->Timestamp,
            tag->TimestampExtended,
            tag->StreamID, PreviousTagSize);
    return length;
}

unsigned char *sps_nalu = NULL;
unsigned char *pps_nalu = NULL;
char *buf_start = NULL;
unsigned sps_nalu_size = 0;
unsigned pps_nalu_size = 0;

int videoToH264(FILE *fp_video, Tag *tag) {
    int ret = 1;
    //tag->Data = FrameType + CodecID + VideoData ;
    //VideoData = AVCPacketType + CompositionTime + 【Data(注:视频数据)】
    unsigned FrameType = (tag->Data[0] & 0xF0) >> 4;//Data表(视频):高4bit
    unsigned CodecID = tag->Data[0] & 0x0F;         //Data表(视频):低4bit
    unsigned AVCPacketType = tag->Data[1];          //VideoData表( AVCVIDEOPACKE)
    unsigned CompositionTime = (tag->Data[2] << 16) + (tag->Data[3] << 8) + tag->Data[4];//VideoData表( AVCVIDEOPACKE)
    if (CodecID != 7) {//7: AVC(高级视频编码)
        ret = 0;
        printf("Not AVC Error\n");
        goto end;
    }
    printf("FrameType=%d\t|CodecID=%d\t|AVCPacketType=%d\t|CompositionTime=%d\t\n", FrameType, CodecID, AVCPacketType,
           CompositionTime);
    //从tag->Data[5]开始至结束为【Data(注:视频数据)】

    //一般FLV第一个视频Tag为sps和pps数据,需要根据AVCDecoderConfigurationRecord进行解析,单独抽出来封装成NALU数据
    if (AVCPacketType == 0) {//0: AVC sequence header
        printf("0: AVC sequence header\n");
        if (sps_nalu) {
            free(sps_nalu);
        }
        if (pps_nalu) {
            free(pps_nalu);
        }
        unsigned Version = tag->Data[5];//sps:版本
        unsigned AVCProfileIndication = tag->Data[6];//sps:编码规格
        unsigned profile_compatibility = tag->Data[7];//sps:编码规格
        unsigned AVCLevelIndication = tag->Data[8];//sps:编码规格
        unsigned reserved_And_lengthSizeMinusOne = tag->Data[9];//sps:应恒为0xFF
        unsigned numOfSequenceParameterSets = tag->Data[10] & 0x1F;//sps:sps个数,取低5位;tag->Data[10]一般为0xE1
        unsigned sps_len = (tag->Data[11] << 8) + tag->Data[12];//sps:sps长度
        sps_nalu_size = sps_len + 4;
        sps_nalu = (unsigned char *) malloc(sps_nalu_size);//加4个为起始位
        memcpy(sps_nalu, buf_start, 4);
        memcpy(sps_nalu + 4, tag->Data + 13, sps_len);
        printf("sps_len=%d\nsps_data=", sps_len);
        for (int i = 4; i < sps_nalu_size; ++i) {
            printf("%.2x", sps_nalu[i]);
        }
        printf("\n");
        fwrite(sps_nalu, 1, sps_nalu_size, fp_video);

        unsigned ppsIndex = 13 + sps_len;
        unsigned numOfPictureParameterSets = tag->Data[ppsIndex];//pps:pps个数,1个字节;
        unsigned pps_len = (tag->Data[ppsIndex + 1] << 8) + tag->Data[ppsIndex + 2];//pps:pps长度
        pps_nalu_size = pps_len + 4;
        pps_nalu = (unsigned char *) malloc(pps_nalu_size);//加4个为起始位
        memcpy(pps_nalu, buf_start, 4);
        memcpy(pps_nalu + 4, tag->Data + ppsIndex + 3, pps_len);
        printf("pps_len=%d\npps_data=", pps_len);
        for (int i = 4; i < pps_nalu_size; ++i) {
            printf("%.2x", pps_nalu[i]);
        }
        printf("\n");
        fwrite(pps_nalu, 1, pps_nalu_size, fp_video);
    } else if (AVCPacketType == 1) {//1: AVC NALU:可能包括多个NALU,需要重新封装
        printf("1: AVC NALU\n");
        //【Data(注:视频数据)】= [size1+pic1]+...+[sizen+picn] 转成 [起始码+pic1]+...+[起始码+picn]
        unsigned int pic_start = 5;
        unsigned int pic_size = 0;
        unsigned int pic_next_index = 0;
        while (1) {
            if (pic_start + 4 >= tag->DataSize) {
                printf("----break-----, pic_start + 4 >= tag->DataSize\n");
                break;
            }
            printf("pic_size=0x%.2x%.2x%.2x%.2x\n", tag->Data[pic_start], tag->Data[pic_start + 1],
                   tag->Data[pic_start + 2],
                   tag->Data[pic_start + 3]);
            pic_size =
                    (tag->Data[pic_start] << 24) + (tag->Data[pic_start + 1] << 16) + (tag->Data[pic_start + 2] << 8) +
                    tag->Data[pic_start + 3];
            pic_next_index = pic_start + 4 + pic_size;
            if (pic_size <= 0 || tag->DataSize < pic_next_index) {
                printf("----break-----, Data Error ! pic_start=%d\tpic_size=%d\tpic_next_index=%d\tDataSize=%d \n",
                       pic_start, pic_size, pic_next_index,
                       tag->DataSize);
                break;
            }
            printf("write pic data,pic_start=%d\tpic_size=%d\tpic_next_index=%d\tDataSize=%d \n", pic_start, pic_size,
                   pic_next_index,
                   tag->DataSize);
            //当前帧数据范围 pic_start + 4 ~ pic_next_index
            fwrite(buf_start, 1, 4, fp_video);
            fwrite(tag->Data + pic_start + 4, 1, pic_size, fp_video);
            pic_start = pic_next_index;
        }
    } else if (AVCPacketType == 2) {//2: AVC end of sequence
        printf("2: AVC end of sequence\n");
    }

    end:
    free(tag->Data);
    return ret;
}

AAC_ADST_HEAD *aacAdstHead = NULL;

int getAACHeadBuf(unsigned char *buf, AAC_ADST_HEAD *aacAdstHead, const unsigned int frame_size) {
    int ret = 0;
    aacAdstHead->frame_length = frame_size;
    //取syncword高位1Byte
    buf[0] = aacAdstHead->syncword >> 4;
    //syncword(低4位)+ID(1)+layer(2)+protection_absent(1)
    buf[1] = ((aacAdstHead->syncword & 0x00F) << 4) + (aacAdstHead->ID >> 3) + (aacAdstHead->layer >> 1) +
             aacAdstHead->protection_absent;
    //profile(2)+sampling_frequency_index(4)+private_bit(1)+channel_configuration(3bit取最高1bit)
    buf[2] = (aacAdstHead->profile << 6) + (aacAdstHead->sampling_frequency_index << 2) + (aacAdstHead->private_bit
            << 1) + (aacAdstHead->channel_configuration
            >> 2);
    // ...00000 00000000
    //channel_configuration(3bit取低2bit)+original_copy(1)+home(1)+copyright_identification_bit(1)+copyright_identification_start(1)+frame_length(13bit取最高2bit)
    buf[3] = (aacAdstHead->channel_configuration << 6) + (aacAdstHead->original_copy << 5) + (aacAdstHead->home << 4)
             + (aacAdstHead->copyright_identification_bit << 3) + (aacAdstHead->copyright_identification_start << 2)
             + (aacAdstHead->frame_length >> 11);
    //frame_length(13bit取:去掉最高2bit开始8位)
    buf[4] = (aacAdstHead->frame_length >> 3) & 0x0FF;
    //frame_length(13bit取最低3bit)+adts_buffer_fullness(11bit取高5bit)
    buf[5] = (aacAdstHead->frame_length << 5) + (aacAdstHead->adts_buffer_fullness >> 6);
    //adts_buffer_fullness(11bit取低6bit) + number_of_raw_data_blocks_in_frame(2)
    buf[6] = (aacAdstHead->adts_buffer_fullness << 2) + aacAdstHead->number_of_raw_data_blocks_in_frame;
    return ret;
}

int audioToAac(FILE *fp_audio, Tag *tag) {
    int ret = 1;
    unsigned SoundFormat = tag->Data[0] >> 4;
    unsigned SoundRate = (tag->Data[0] & 0x0F) >> 2;
    unsigned SoundSize = (tag->Data[0] & 0x02) >> 1;
    unsigned SoundType = tag->Data[0] & 0x01;
    //SoundData = AACPacketType + Data
    unsigned AACPacketType = tag->Data[1];

    if (SoundFormat != 10) {//10 = AAC
        printf("Not AAC Error\n");
        ret = 0;
        goto end;
    }

    if (AACPacketType == 0) {//0: AAC sequence header(参考14496-3 AudioSpecificConfig https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Audio_Specific_Config)
        printf("0: AAC sequence header\n");
        //读取flv文件中的头信息
        FLV_AAC_HEAD *flvAacHead = (FLV_AAC_HEAD *) malloc(sizeof(FLV_AAC_HEAD));
        //只要前面两个字节就行,如0x1390=00010011 10010000
        flvAacHead->audioObjectType = tag->Data[2] >> 3;//UB[5]=00010... ........
        flvAacHead->samplingFrequencyIndex =
                ((tag->Data[2] & 0x07) << 1) + (tag->Data[3] >> 7);//UB[4]=.....011 1.......
        flvAacHead->channelConfiguration = (tag->Data[3] & 0x7F) >> 3;//UB[4]=........ .0010...
        flvAacHead->other = tag->Data[3] & 0x07;//两个字节剩余的bit,为0也行。

        //构造AAC_ADST_HEAD
        if (aacAdstHead) {
            free(aacAdstHead);
        }
        aacAdstHead = (AAC_ADST_HEAD *) malloc(sizeof(AAC_ADST_HEAD));
        aacAdstHead->syncword = 0xFFF;
        aacAdstHead->ID = 1;
        aacAdstHead->layer = 0;
        aacAdstHead->protection_absent = 1;
        aacAdstHead->profile = flvAacHead->audioObjectType;
//        aacAdstHead->profile = 1;
        aacAdstHead->sampling_frequency_index = flvAacHead->samplingFrequencyIndex;
        aacAdstHead->private_bit = 0;
        aacAdstHead->channel_configuration = flvAacHead->channelConfiguration;
        aacAdstHead->original_copy = 0;
        aacAdstHead->home = 0;

        aacAdstHead->copyright_identification_bit = 0;
        aacAdstHead->copyright_identification_start = 0;
//        aacAdstHead->frame_length = xx; //帧的长度动态计算的
        aacAdstHead->adts_buffer_fullness = 0x7FF;
        aacAdstHead->number_of_raw_data_blocks_in_frame = 0;
        printf("audioObjectType=%d\tsamplingFrequencyIndex=%d\t channelConfiguration=%d\n ",
               flvAacHead->audioObjectType, flvAacHead->samplingFrequencyIndex, flvAacHead->channelConfiguration);
        free(flvAacHead);
    } else if (AACPacketType == 1) {//1: AAC raw
        printf("1: AAC raw\n");
        unsigned char *buf = (unsigned char *) malloc(7);;
        if (getAACHeadBuf(buf, aacAdstHead, tag->DataSize - 2 + 7) == -1) {
            printf("get AAC Head Buf Error\n");
            ret = 0;
            goto end;
        }
        fwrite(buf, 1, 7, fp_audio);
        fwrite(tag->Data + 2, 1, tag->DataSize - 2, fp_audio);
        free(buf);
    }

    end:
    free(tag->Data);
    return ret;
}

int test() {
    return 0;
}

int main() {
    if (test()) {//非0位true
        exit(1);
    }
    printf("delete cache video file ? %d\n", remove("output/Kobe.h264"));
    printf("delete cache audio file ? %d\n", remove("output/Kobe.aac"));

    FILE *fp_in = fopen("assets/Kobe.flv", "rb+");
    FILE *fp_video = fopen("output/Kobe.h264", "wb+");
    FILE *fp_audio = fopen("output/Kobe.aac", "wb+");
    Tag *tag;
    if (!fp_in) {
        cout << "源文件不存在!" << endl;
        exit(1);
    }
    if (!fp_video) {
        cout << "输出文件不存在!" << endl;
        exit(1);
    }

    if (!checkFLV(fp_in)) {
        exit(1);
    }

    //跳过PreviousTagSize(0) 4byte
    fseek(fp_in, 4, SEEK_CUR);
    tag = (Tag *) calloc(1, sizeof(Tag));
    //初始化h264中NALU的起始码(0x00 00 00 01或者0x00 00 01)
    buf_start = (char *) malloc(4);
    buf_start[0] = 0x00;
    buf_start[1] = 0x00;
    buf_start[2] = 0x00;
    buf_start[3] = 0x01;
    if (!tag) {
        printf("Alloc Tag Error\n");
        return 0;
    }
    printf("---------+----------+----------Tag------------------+----------+-----------------+\n");
    printf(" TagType | DataSize | Timestamp | TimestampExtended | StreamID | PreviousTagSize |\n");
    printf("---------+----------+-----------+-------------------+----------+-----------------+\n");
    while (!feof(fp_in)) {
        //查找一组:Tag + PreviousTagSize
        int data_lenth;
        int ret;
        data_lenth = getTagAndTagSize(fp_in, tag);

        if (tag->TagType == 0x12) {//脚本数据

        }
        if (tag->TagType == 0x08) {//音频数据
            ret = audioToAac(fp_audio, tag);
        }
        if (tag->TagType == 0x09) {//视频数据
            ret = videoToH264(fp_video, tag);
        }
    }
    if (sps_nalu) {
        free(sps_nalu);
    }
    if (pps_nalu) {
        free(pps_nalu);
    }
    if (buf_start) {
        free(buf_start);
    }
    fclose(fp_video);
    return 0;
}

使用ffplay查看输出文件

h264文件播放:

ffplay xxx.h264

aac文件播放:

ffplay xxx.aac

测试文件下载地址

参考

  • https://www.cnblogs.com/chyingp/p/flv-getting-started.html
  • ISO/IEC_14496-3


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值