c++ H264文件逐帧提取 每帧可单独解码使用
原理
IDR帧(关键帧)
1、IDR(Instantaneous Decoding Refresh)即时解码刷新。 在编码解码中为了方便,将GOP中首个I帧要和其他I帧区别开,把第一个I帧叫IDR,这样方便控制编码和解码流程,所以IDR帧一定是I帧,但I帧不一定是IDR帧;IDR帧的作用是立刻刷新,使错误不致传播,从IDR帧开始算新的序列开始编码。I帧有被跨帧参考的可能,IDR不会。
2、I帧不用参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样。
3、其核⼼作⽤是,是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤IDR之前的图像的数据来解码。
简而言之:IDR帧是H.264解码的关键,是码流的开始,该帧中包含完整的信息,可独立解码为一帧完整的YUV数据。其中IDR帧有包含SPS,PPS和I三种中类型。
SPS和PPS为描述信息,通过解析可以得到视频的分辨率,码率等信息,SPS经过(arr[4] & 0x1f)计算可得7,
PPS经过(arr[4] & 0x1f)计算可得8,I帧经过(arr[4] & 0x1f)计算可得5。
P帧又名前向参考帧,经过(arr[4] & 0x1f)计算可得1。
当我们使用ffmpeg解码H264数据,发现报错如下时:就意味着丢失帧信息
如果没错误,就是这样的
附上完整代码
#include <windows.h>
#include <iostream>
#include <string>
/* 在指定的文件中查找所有与str内容相同的内容,并将内容在文件的位置记录在arr中。
* @fp 指定查找的文件指针
* @str 要查找的内容
* @strLen 要查找的内容的长度
* @arr 存放位置的数组,要求数组足够大
* @len 两种含义,传入时len表示数组长度,函数结束后len表示数组中有效数据的个数
**/
int getAllContent(FILE* fp, char* str, int strLen, unsigned* arr, unsigned* len)
{
if (!fp || !arr || !len) return -1;
unsigned arrLen = *len;
long pos = 0;
long posEnd = 0;
char* buf = (char*)malloc(sizeof(char) * strLen);;
if (!buf) return -2;
fseek(fp, 0L, SEEK_END);
posEnd = ftell(fp) - strLen;
*len = 0;
int res = 0;
while (pos <= posEnd && *len < arrLen)
{
fseek(fp, pos, SEEK_SET);
res = fread(buf, sizeof(char), strLen, fp);
if (res != strLen) break;
if (memcmp(str, buf, strLen * sizeof(char)) == 0)
{
arr[*len] = pos;
(*len)++;
}
pos++;
}
fseek(fp, 0L, SEEK_SET);
free(buf);
return 0;
}
/*根据arr中保持的各帧的位置读取文件
* @fp 需要读取的文件
* @arr 保持指定内容在文件中的位置的数组
* @len 数组中可用数据的个数
**/
int SendAvcStream(FILE* fp, unsigned* arr, unsigned len)
{
static unsigned char* bufI = NULL;
static unsigned char* buf2 = NULL;
if (!bufI)
{
bufI = (unsigned char*)malloc(640 * 360);//视频的分辨率
if (!bufI) return -1;
}
if (!buf2)
{
buf2 = (unsigned char*)malloc(640 * 360);
if (!buf2) return -1;
}
unsigned i = 1;
int res = 0;
unsigned frameSize = 0;
std::string sFile("");
while (i < len)
{
unsigned size = arr[i] - arr[i - 1];
if (size == 0) { i++; continue; }
res = fread(buf2, 1, size, fp);
if (res != (signed)size) break;
int type = buf2[4] & 0x1f;
printf("frame type is %d\n", type);
if (type == 1)
{
if (frameSize)// get a whole IDR frame which maybe include dual slices
{
// TODO: to do something
sFile = "D:\\AudioPcm\\H264\\N\\" + std::to_string(i) + ".h264";
FILE* fpW = fopen(sFile.c_str(), "ab");
if (!fpW) continue;
fwrite(bufI, 1, frameSize, fpW);
fclose(fpW);
frameSize = 0;
}
// get a whole P frame
// TODO: to do something
}
else if(5 == type || 7 == type || 8 == type) // 7 8 6 5
{
// stroe 7 8 6 5 into an buffer
memcpy(bufI + frameSize, buf2, size);
frameSize += size;
}
i++;
}
fseek(fp, 0L, SEEK_SET);
return 0;
}
int main() {
//测试h264文件逐帧拆分
char cRead[1024] = { 0 };
static int nCnt = 1;
std::string sFile("");
char arrHeader[4] = { 0x00,0x00,0x00,0x01 };//筛查0x00 0x00 0x00 0x01开头的数据
FILE* fp = fopen("D:\\AudioPcm\\H264\\test10.h264", "rb");
if (!fp) return -1;
unsigned arrIndex[100] = { 0 };
unsigned int nLen = 100;
getAllContent(fp, arrHeader, 4, arrIndex, &nLen);
SendAvcStream(fp, arrIndex, nLen);
return 0;
}
gop_size = 10,所以10帧可以抽出一帧可解码的帧。
参考链接:https://blog.csdn.net/xy_kok/article/details/81237361