ZigZag Sin
登 陆
上一篇:AnnexB 和 avcC 下一篇:EBSP, RBSP, SODB

读取 AnnexB 格式的 H.264 数据

乔红
2020-11-27 17:30 阅读 569

读取 AnnexB 的数据

之前的章节里,我们详细介绍了 AnnexB 的数据格式详情 (H264/AVC AnnexB)。这一章我们就来读取一个 AnnexB 的文件。

准备一个 H.264 文件

这里可以使用 FFmpeg 提供的命令行工具

ffmpeg -i demo.mp4 demo.h264

我们用一个二进制文件查看工具打开生成的文件,就可以看到文件详情。我们可以找一下文件里存在的起始码 0 0 0 1 或者 0 0 1,我们的程序,就是要按照起始码来分割这个文件里存在的 NALU。

YUV.jpg

接口设计

class AnnexBReader
{
public:
    AnnexBReader(std::string & filePath);
    ~AnnexBReader();

    // 用来打开文件
    int Open();

    // 用来关闭文件
    int Close();

    // 用来读取一个 Nalu 文件
    int ReadNalu(uint8_t * data, int * dataLen, int * startcodeLen);
};

我们先来看一下 Open 和 Close 函数。这两个函数用于打开和关闭构造函数传入的路径,实现很简单,我们直接看代码。
// 用来打开文件
int AnnexBReader::Open()
{
    f = fopen(filePath.c_str(), "rb");
    if(f == nullptr){
        return -1;
    }
    return 0;
}

// 用来关闭文件
int AnnexBReader::Close()
{
    if(f != nullptr){
        fclose(f);
        f = nullptr;
    }
    return 0;
}

在完成了打开和关闭之后,我们就要开始读取 NALU 了。整个读取 NALU 的过程是这样的:
  • 我们先从文件中读取一个 buffer 到内存中
  • 然后遍历这个 buffer,遇到 startcode,就从这段 buffer 中截断出一个 NALU
  • 当内存中的 buffer 用尽后,我们再从文件中继续读取,直到读到文件末尾

要完成以上的操作,首先我们要写一个私有函数,这个函数用来检查一个 buffer 的开头是不是 start code。在这个函数中,你可以传入一个指针,和这个指针指向数据的长度,这个函数会帮你检查这个 buffer 的开头是不是 start code。如果是 start code,那么返回 true,并且会将 startCodeLen 赋值成 start code 的长度 (3 或者 4)

bool EyerAnnexB::CheckStartCode(int & startCodeLen, uint8_t * bufPtr, int bufLen)
{
    if(bufLen <= 2){
        startCodeLen = 0;
        return false;
    }
    if(bufLen >= 4){
        if(bufPtr[0] == 0) {
            if (bufPtr[1] == 0) {
                if (bufPtr[2] == 0) {
                    if (bufPtr[3] == 1) {
                        startCodeLen = 4;
                        return true;
                    }
                }
                if(bufPtr[2] == 1){
                    startCodeLen = 3;
                    return true;
                }
            }
        }
    }
    if(bufLen <= 3){
        if(bufPtr[0] == 0) {
            if (bufPtr[1] == 0) {
                if(bufPtr[2] == 1){
                    startCodeLen = 3;
                    return true;
                }
            }
        }
    }

    startCodeLen = 0;
    return false;
}

接下来,我们实现 ReadNalu 函数

int AnnexBReader::ReadNalu(uint8_t * data, int * dataLen, int * startcodeLen)
{
    while(1){
        if(bufferLen <= 0){
            int readedLen = ReadFromFile();
            if(readedLen <= 0){
                isEnd = true;
            }
        }

        uint8_t * buf = buffer;

        int startCodeLen = 0;
        // Find Start Code
        bool isStartCode = CheckStartCode(startCodeLen, buf, bufferLen);
        if(!isStartCode){
            break;
        }

        *startcodeLen = startCodeLen;

        // Find End Code
        int endPos = -1;
        for(int i=2;i<bufferLen;i++){
            int startCodeLen = 0;
            bool isStartCode = CheckStartCode(startCodeLen, buf + i, bufferLen - i);
            if(isStartCode){
                endPos = i;
                break;
            }
        }

        if(endPos > 0){
            memcpy(data, buffer, endPos);
            uint8_t * _buffer = (uint8_t*)malloc(bufferLen - endPos);

            memcpy(_buffer, buffer + endPos, bufferLen - endPos);

            if(buffer != nullptr){
                free(buffer);
                buffer = nullptr;
            }
            buffer = _buffer;
            bufferLen = bufferLen - endPos;

            return 0;
        }
        else{
            if(isEnd == true){
                // 到达文件末尾,取所有 buffer 出来
                memcpy(data, buffer, bufferLen);
                if(buffer != nullptr){
                    free(buffer);
                    buffer = nullptr;
                }
                buffer = nullptr;
                bufferLen = 0;

                return 0;
            }
            int readedLen = ReadFromFile();
            if(readedLen <= 0){
                isEnd = true;
            }
        }
    }

    return -1;
}

完整代码如下:

AnnexBReader.hpp

#ifndef EYERLIB_EYER_AVC_BLOG_ANNEXBREADER_HPP
#define EYERLIB_EYER_AVC_BLOG_ANNEXBREADER_HPP

#include <stdint.h>
#include <string>

class AnnexBReader
{
public:
    AnnexBReader(std::string & _filePath);
    ~AnnexBReader();

    // 用来打开文件
    int Open();

    // 用来关闭文件
    int Close();

    // 用来读取一个 Nalu 文件
    int ReadNalu(uint8_t * data, int * dataLen, int * startcodeLen);

private:
    bool CheckStartCode(int & startCodeLen, uint8_t * bufPtr, int bufLen);
    int ReadFromFile();

    std::string filePath;
    FILE * f = nullptr;

    bool isEnd = false;
    uint8_t * buffer = nullptr;
    int bufferLen = 0;
};

#endif //EYERLIB_EYER_AVC_BLOG_ANNEXBREADER_HPP

AnnexBReader.cpp

#include "AnnexBReader.hpp"

AnnexBReader::AnnexBReader(std::string & _filePath)
{
    filePath = _filePath;
}

AnnexBReader::~AnnexBReader()
{
    Close();
}

// 用来打开文件
int AnnexBReader::Open()
{
    f = fopen(filePath.c_str(), "rb");
    if(f == nullptr){
        return -1;
    }
    return 0;
}

// 用来关闭文件
int AnnexBReader::Close()
{
    if(f != nullptr){
        fclose(f);
        f = nullptr;
    }
    if(buffer != nullptr){
        free(buffer);
        buffer = nullptr;
    }
    return 0;
}

// 用来读取一个 Nalu 文件
int AnnexBReader::ReadNalu(uint8_t * data, int * dataLen, int * startcodeLen)
{
    while(1){
        if(bufferLen <= 0){
            int readedLen = ReadFromFile();
            if(readedLen <= 0){
                isEnd = true;
            }
        }

        uint8_t * buf = buffer;

        int startCodeLen = 0;
        // Find Start Code
        bool isStartCode = CheckStartCode(startCodeLen, buf, bufferLen);
        if(!isStartCode){
            break;
        }

        *startcodeLen = startCodeLen;

        // Find End Code
        int endPos = -1;
        for(int i=2;i<bufferLen;i++){
            int startCodeLen = 0;
            bool isStartCode = CheckStartCode(startCodeLen, buf + i, bufferLen - i);
            if(isStartCode){
                endPos = i;
                break;
            }
        }

        if(endPos > 0){
            memcpy(data, buffer, endPos);
            uint8_t * _buffer = (uint8_t*)malloc(bufferLen - endPos);

            memcpy(_buffer, buffer + endPos, bufferLen - endPos);

            if(buffer != nullptr){
                free(buffer);
                buffer = nullptr;
            }
            buffer = _buffer;
            bufferLen = bufferLen - endPos;

            return 0;
        }
        else{
            if(isEnd == true){
                // 到达文件末尾,取所有 buffer 出来
                memcpy(data, buffer, bufferLen);
                if(buffer != nullptr){
                    free(buffer);
                    buffer = nullptr;
                }
                buffer = nullptr;
                bufferLen = 0;

                return 0;
            }
            int readedLen = ReadFromFile();
            if(readedLen <= 0){
                isEnd = true;
            }
        }
    }

    return -1;
}

int AnnexBReader::ReadFromFile()
{
    int tempBufferLen = 1024;
    uint8_t * buf = (uint8_t *) malloc (tempBufferLen);
    int readedLen = fread(buf, 1, tempBufferLen, f);

    if(readedLen > 0){
        // 将新读取的 buf 添加到旧的 buffer 之后
        uint8_t * _buffer = (uint8_t *) malloc (bufferLen + tempBufferLen);
        memcpy(_buffer,                 buffer, bufferLen);
        memcpy(_buffer + bufferLen,     buf,    tempBufferLen);
        bufferLen = bufferLen + tempBufferLen;

        if(buffer != nullptr){
            free(buffer);
            buffer = nullptr;
        }

        buffer = _buffer;
    }

    free(buf);

    return readedLen;
}

bool AnnexBReader::CheckStartCode(int & startCodeLen, uint8_t * bufPtr, int bufLen)
{
    if(bufLen <= 2){
        startCodeLen = 0;
        return false;
    }
    if(bufLen >= 4){
        if(bufPtr[0] == 0) {
            if (bufPtr[1] == 0) {
                if (bufPtr[2] == 0) {
                    if (bufPtr[3] == 1) {
                        startCodeLen = 4;
                        return true;
                    }
                }
                if(bufPtr[2] == 1){
                    startCodeLen = 3;
                    return true;
                }
            }
        }
    }
    if(bufLen <= 3){
        if(bufPtr[0] == 0) {
            if (bufPtr[1] == 0) {
                if(bufPtr[2] == 1){
                    startCodeLen = 3;
                    return true;
                }
            }
        }
    }

    startCodeLen = 0;
    return false;
}

调用代码

int main()
{
    std::string filePath = "./demo_video_176x144_baseline.h264";
    AnnexBReader reader(filePath);
    int ret = reader.Open();
    if(ret){
        printf("Read Fail");
        return -1;
    }

    while(1){
        uint8_t buffer[1024 * 1024];
        int bufferLen = 0;
        int startcodeLen = 0;
        ret = reader.ReadNalu(buffer, &bufferLen, &startcodeLen);
        if(ret){
            break;
        }
        printf("=====================\n");
        printf("Buffer Len: %d\n", bufferLen);
        printf("Start Code Len: %d\n", startcodeLen);
        printf("%d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]);
    }

    reader.Close();

    return 0;
}

获取代码

https://github.com/redknotmiaoyuqiao/EyerH264Decoder/tree/main/Lesson_2_3_ReadAnnexB

上一篇:AnnexB 和 avcC 下一篇:EBSP, RBSP, SODB
给我买个键盘吧。。。求打赏。。。
欢迎加群,一起交流~~~