之前的章节里,我们详细介绍了 AnnexB 的数据格式详情 (H264/AVC AnnexB)。这一章我们就来读取一个 AnnexB 的文件。
这里可以使用 FFmpeg 提供的命令行工具
ffmpeg -i demo.mp4 demo.h264
我们用一个二进制文件查看工具打开生成的文件,就可以看到文件详情。我们可以找一下文件里存在的起始码 0 0 0 1 或者 0 0 1,我们的程序,就是要按照起始码来分割这个文件里存在的 NALU。
class AnnexBReader
{
public:
AnnexBReader(std::string & filePath);
~AnnexBReader();
// 用来打开文件
int Open();
// 用来关闭文件
int Close();
// 用来读取一个 Nalu 文件
int ReadNalu(uint8_t * data, int * dataLen, int * startcodeLen);
};
// 用来打开文件
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;
}
要完成以上的操作,首先我们要写一个私有函数,这个函数用来检查一个 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