FFmpeg--FlvPaser源码:解析.flv输出.h264

程序功能:

解析flv文件,重写一个h264文件,如输入movie.flv , 输出movie.h264
(只有视频,没有声音)

函数调用流程:
1 
Process()  处理函数:1 读文件 2 解析flv 3 将flv的视频数据输出到.h264文件

2  
Parse() 解析flv 文件,入口函数
DumpH264() 生成h264文件

3 
CreateFlvHeader() 解析 flv head

4
CreateTag() 解析视频标签(tag)入口函数

5
CVideoTag() 创建video tag

6
ParseH264Tag() 解析 vido tag

7
ParseH264Configuration() 解析配置信息

8
ParseNalu() 解析nalu数据

9
DumpH264()  生成h264文件,通过_pMedia写入
部分FlvParse.h
class CFlvParser
{
public:
    CFlvParser();
    virtual ~CFlvParser();

    int Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen);
    int PrintInfo();
    int DumpH264(const std::string &path);
    int DumpAAC(const std::string &path);
    int DumpFlv(const std::string &path);

private:
    // FLV头
    typedef struct FlvHeader_s
    {
        int nVersion; // 版本
        int bHaveVideo; // 是否包含视频
        int bHaveAudio; // 是否包含音频
        int nHeadSize;  // FLV头部长度
        /*
        ** 指向存放FLV头部的buffer
        ** 上面的三个成员指明了FLV头部的信息,是从FLV的头部中“翻译”得到的,
        ** 真实的FLV头部是一个二进制比特串,放在一个buffer中,由pFlvHeader成员指明
        */
        uint8_t *pFlvHeader;
    } FlvHeader;

    // Tag头部
    struct TagHeader
    {
        int nType;      // 类型
        int nDataSize;  // 标签body的大小
        int nTimeStamp; // 时间戳
        int nTSEx;      // 时间戳的扩展字节
        int nStreamID;  // 流的ID,总是0

        uint32_t nTotalTS;  // 完整的时间戳nTimeStamp和nTSEx拼装

        TagHeader() : nType(0), nDataSize(0), nTimeStamp(0), nTSEx(0), nStreamID(0), nTotalTS(0) {}
        ~TagHeader() {}
    };

    class Tag
    {
    public:
        Tag() : _pTagHeader(NULL), _pTagData(NULL), _pMedia(NULL), _nMediaLen(0) {}
        void Init(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen);

        TagHeader _header;
        uint8_t *_pTagHeader;   // 指向标签头部
        uint8_t *_pTagData;     // 指向标签body,原始的tag data数据
        uint8_t *_pMedia;       // 指向标签的元数据,改造后的数据
        int _nMediaLen;         // 数据长度
    };

    class CVideoTag : public Tag
    {
    public:
        /**
         * @brief CVideoTag
         * @param pHeader
         * @param pBuf 整个tag的起始地址
         * @param nLeftLen
         * @param pParser
         */
        CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser);

        int _nFrameType;    // 帧类型
        int _nCodecID;      // 视频编解码类型
        int ParseH264Tag(CFlvParser *pParser);
        int ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData);
        int ParseNalu(CFlvParser *pParser, uint8_t *pTagData);
    };
	friend class Tag;

private:

    FlvHeader *CreateFlvHeader(uint8_t *pBuf);
    Tag *CreateTag(uint8_t *pBuf, int nLeftLen);

private:

    FlvHeader* _pFlvHeader;
    vector<Tag *> _vpTag;

部分FlvParse.cpp
int main()
{
    fstream fin;
    fin.open(argv[1], ios_base::in | ios_base::binary); // 打开文件
    if (!fin)
        return 0;

    Process(fin);  //输出文件

    fin.close();

    return 1;
}

// 循环读文件
void Process(fstream &fin)
{
    CFlvParser parser;

    int nBufSize = 2*1024 * 1024;
    int nFlvPos = 0;
    uint8_t *pBuf, *pBak;
    pBuf = new uint8_t[nBufSize];
    pBak = new uint8_t[nBufSize];

    while (1)
    {
        int nReadNum = 0;
        int nUsedLen = 0;
        fin.read((char *)pBuf + nFlvPos, nBufSize - nFlvPos); // 循环读取数据,读到内存当中
        nReadNum = fin.gcount();
        if (nReadNum == 0)
            break;
        nFlvPos += nReadNum;

        parser.Parse(pBuf, nFlvPos, nUsedLen);
        if (nFlvPos != nUsedLen)
        {
            memcpy(pBak, pBuf + nUsedLen, nFlvPos - nUsedLen);
            memcpy(pBuf, pBak, nFlvPos - nUsedLen);
        }
        nFlvPos -= nUsedLen;
    }
    parser.PrintInfo();
    parser.DumpH264("parser.264");
    delete []pBak;
    delete []pBuf;
}
// 解析
int CFlvParser::Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen){
	CheckBuffer(9); // 检测9字节的head
    // 检测header
	while(1) {
		CheckBuffer(15); // nPrevSize(4字节) + Tag header(11字节)
	}
	Tag *pTag = CreateTag(pBuf + nOffset, nBufSize-nOffset); //创建头部
        if (pTag == NULL)
        {
            nOffset -= 4;
            break;
        }
        nOffset += (11 + pTag->_header.nDataSize);

        _vpTag.push_back(pTag); // vector<Tag *> _vpTag; 
}

// 写flv header
CFlvParser::FlvHeader *CFlvParser::CreateFlvHeader(uint8_t *pBuf)
{
	 pHeader->pFlvHeader = new uint8_t[pHeader->nHeadSize];
     memcpy(pHeader->pFlvHeader, pBuf, pHeader->nHeadSize);
}

// 写tag 头部
CFlvParser::Tag *CFlvParser::CreateTag(uint8_t *pBuf, int nLeftLen) {
	 TagHeader header;
    header.nType = ShowU8(pBuf+0);  // 类型
    header.nDataSize = ShowU24(pBuf + 1);   // 1 为 偏移 nType的字节数
    header.nTimeStamp = ShowU24(pBuf + 4);  // 4 为 偏移nType和 nDataSize的						   字数之和
    switch (header.nType) {
    case 0x09:  // 视频类型的Tag
        pTag = new CVideoTag(&header, pBuf, nLeftLen, this);
        break;
    case 0x08:  // 音频类型的Tag
        pTag = new CAudioTag(&header, pBuf, nLeftLen, this);
        break;
    case 0x12:  // script Tag
        pTag = new CMetaDataTag(&header, pBuf, nLeftLen, this);
        break;
    default:    // script类型的Tag
        pTag = new Tag();
        pTag->Init(&header, pBuf, nLeftLen);
    } 
}

// 之后解析tag的类型 :video  aac  script
// 构造函数
CFlvParser::CVideoTag::CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser)
{
    // 初始化
    Init(pHeader, pBuf, nLeftLen);  

    uint8_t *pd = _pTagData;
    _nFrameType = (pd[0] & 0xf0) >> 4;  // 帧类型
    _nCodecID = pd[0] & 0x0f;           // 视频编码类型
    // 开始解析
    if (_header.nType == 0x09 && _nCodecID == 7)  //类型为7进行解析
    {
        ParseH264Tag(pParser);
    }
}
 
int CFlvParser::CVideoTag::ParseH264Tag(CFlvParser *pParser)
{
    uint8_t *pd = _pTagData;
    /*
    ** 数据包的类型
    ** 视频数据被压缩之后被打包成数据包在网上传输
    ** 有两种类型的数据包:视频信息包(sps、pps等)和视频数据包(视频的压缩数据)
    */
    int nAVCPacketType = pd[1];
    int nCompositionTime = CFlvParser::ShowU24(pd + 2);

    // 如果是视频配置信息
    if (nAVCPacketType == 0)    // AVC sequence header
    {
        ParseH264Configuration(pParser, pd);
    }
    // 如果是视频数据
    else if (nAVCPacketType == 1) // AVC NALU
    {
        ParseNalu(pParser, pd);
    }
    return 1;
}
int CFlvParser::CVideoTag::ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData)
{
    uint8_t *pd = pTagData;
    // 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType(1字节) 和CompositionTime(3字节) 4字节)
    // 总共跨过5个字节

    // NalUnit长度表示占用的字节
    pParser->_nNalUnitLength = (pd[9] & 0x03) + 1;  // lengthSizeMinusOne 9 = 5 + 4   &0x03只占两个字节
    int sps_size, pps_size;
    // sps(序列参数集)的长度
    sps_size = CFlvParser::ShowU16(pd + 11);        // sequenceParameterSetLength 11 = 5 + 6
    // pps(图像参数集)的长度
    pps_size = CFlvParser::ShowU16(pd + 11 + (2 + sps_size) + 1);   // pictureParameterSetLength

    // 元数据的长度
    _nMediaLen = 4 + sps_size + 4 + pps_size;   // 添加start code
    _pMedia = new uint8_t[_nMediaLen];
    // 保存元数据
    memcpy(_pMedia, &nH264StartCode, 4);
    memcpy(_pMedia + 4, pd + 11 + 2, sps_size);
    memcpy(_pMedia + 4 + sps_size, &nH264StartCode, 4);
    memcpy(_pMedia + 4 + sps_size + 4, pd + 11 + 2 + sps_size + 2 + 1, pps_size);

    return 1;
}

int CFlvParser::CVideoTag::ParseNalu(CFlvParser *pParser, uint8_t *pTagData)
{
    uint8_t *pd = pTagData;
    int nOffset = 0;

    _pMedia = new uint8_t[_header.nDataSize+10];
    _nMediaLen = 0;
    // 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType和CompositionTime 4字节)
    nOffset = 5; // 总共跨过5个字节 132 - 5 = 127 = _nNalUnitLength(4字节)  + NALU(123字节)
    //                                           startcode(4字节)  + NALU(123字节) = 127
    while (1)
    {
        // 如果解析完了一个Tag,那么就跳出循环
        if (nOffset >= _header.nDataSize)
            break;
        // 计算NALU(视频数据被包装成NALU在网上传输)的长度,
        // 一个tag可能包含多个nalu, 所以每个nalu前面有NalUnitLength字节表示每个nalu的长度
        // video tag data  如果有两个nalu ,一个长度是300字节,一个500字节
        // 0x00 0x00 0x01 0x2c (300) ------nalu--------0x00 0x00 0x01 0xF4(500)
        // _pMeida 先拷贝startcode,再拷贝nalu 
        int nNaluLen;
        switch (pParser->_nNalUnitLength)
        {
        case 4:
            nNaluLen = CFlvParser::ShowU32(pd + nOffset);
            break;
        case 3:
            nNaluLen = CFlvParser::ShowU24(pd + nOffset);
            break;
        case 2:
            nNaluLen = CFlvParser::ShowU16(pd + nOffset);
            break;
        default:
            nNaluLen = CFlvParser::ShowU8(pd + nOffset);
        }
        // 获取NALU的起始码
        memcpy(_pMedia + _nMediaLen, &nH264StartCode, 4);
        // 复制NALU的数据
        memcpy(_pMedia + _nMediaLen + 4, pd + nOffset + pParser->_nNalUnitLength, nNaluLen);
        // 解析NALU
//        pParser->_vjj->Process(_pMedia+_nMediaLen, 4+nNaluLen, _header.nTotalTS);
        _nMediaLen += (4 + nNaluLen); // 4 startcode
        nOffset += (pParser->_nNalUnitLength + nNaluLen);
    }

    return 1;
}

// 写h264数据
int CFlvParser::DumpH264(const std::string &path)
{
    fstream f;
    f.open(path.c_str(), ios_base::out|ios_base::binary);

    vector<Tag *>::iterator it_tag;
    for (it_tag = _vpTag.begin(); it_tag != _vpTag.end(); it_tag++)
    {
        if ((*it_tag)->_header.nType != 0x09)
            continue;

        f.write((char *)(*it_tag)->_pMedia, (*it_tag)->_nMediaLen);
    }
    f.close();

    return 1;
}

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-10 15:46:06       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-10 15:46:06       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-10 15:46:06       82 阅读
  4. Python语言-面向对象

    2024-03-10 15:46:06       91 阅读

热门阅读

  1. Spring MVC HandlerMethodReturnValueHandler原理解析

    2024-03-10 15:46:06       48 阅读
  2. lvs集群

    lvs集群

    2024-03-10 15:46:06      44 阅读
  3. jmeter快速使用

    2024-03-10 15:46:06       44 阅读
  4. HTTPS安全机制解析:如何保护我们的数据传输

    2024-03-10 15:46:06       42 阅读
  5. 读书·基于RISC-V和FPGA的嵌入式系统设计·第二章

    2024-03-10 15:46:06       40 阅读
  6. Clickhouse: 随笔杂记

    2024-03-10 15:46:06       49 阅读