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

*代码-读取 AnnexB 格式的 H.264 数据

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

读取 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

接口设计

首先,我们设计一个类,用来存放读取出来的 NALU。在这个类中,我们设计了三个成员变量,分别是 uint8_t * buf,int len 和 int startCodeLen。

其中,buf 和 len 用来存放提取出来的 NALU 的数据,startCodeLen 用来存放这个 NALU 中 Start Code 的长度。

除此之外,我们还设计了一个成员函数 int SetBuf(uint8_t * _buf, int _len);,这个函数中,通过这个函数把 NALU 的数据拷贝到这个对象中。

class Nalu {
public:
    Nalu();
    ~Nalu();

    int SetBuf(uint8_t * _buf, int _len);

public:
    uint8_t * buf = nullptr;
    int len = 0;
    int startCodeLen = 0;
};

接下来,我们设计一个读取工具类。在这个类中,我们可以通过构造函数设置 AnnexB 的文件路径,然后循环调用 ReadNalu 函数,不断地在读出 NALU,直到 ReadNalu 函数返回读取结束。

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

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

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

    // 用来读取一个 Nalu
    int ReadNalu(Nalu & nalu);
};

关键实现

我们先来看一下 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(Nalu & nalu)
{
    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;
        }

        nalu.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){
            nalu.SetBuf(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 出来
                nalu.SetBuf(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;
}

调用代码

#include "AnnexBReader.hpp"

int main(int argc, char const *argv[])
{
    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
给我买个键盘吧。。。求打赏。。。
欢迎加群,一起交流~~~