网上有很多读取H264的封装类,但是大多数都是提取NAL单元的,而我想要的是提取每一帧的数据。并且,很多解析H264的代码都是有Bug的,不是太完善。在这篇博文里我向大家分享一个比较完善的H264的封装类,该代码可以读取H264(注意是裸流文件),并能获得每一帧的数据,以及获取视频的分辨率。
下面是这个类的头文件定义:
//支持分析H264/MPEG4/MPEG2的裸流文件,获得里面每一帧的数据
class CFrameExtractFilter
{
public:
CFrameExtractFilter();
virtual ~CFrameExtractFilter(void);
public:
void SetLoopMode(BOOL bLoop); //设置是否循环读取文件
int OpenFile(LPCTSTR pszFileName, SSBSIP_MFC_CODEC_TYPE file_codec_type); //读取文件,并获取每一帧的信息
SSBSIP_MFC_CODEC_TYPE GetStreamType(); //获取流的格式,H264/MPEG4/MPEG2
int GetNextFrame(unsigned char *pFrameBuf, unsigned int *pSize); //获得下一个帧的数据,如果是第一次调用则获取第一帧的数据
void ResetFileRead(); //设置读指针到文件头,重新开始读
BOOL HasReadToEnd() { return m_bReadFileEnd; } //文件是否已经读完(如果设置了循环读取,则该函数一直返回FALSE)
int CloseFile(); //关闭文件
// Implementations
protected:
void SetFileName(LPCTSTR pszFileName); //设置文件路径
// member variables
private:
TCHAR m_szFileName[MAX_PATH];
void *m_pFramexCtx; //读取文件数据的对象指针,实际上为CtxType*类型
BOOL m_bLoopAfterEnd; //是否循环读取文件
SSBSIP_MFC_CODEC_TYPE m_StrmType;
int m_first; //如果为1,从第一帧开始读取
unsigned int m_frame_num; //读取的当前帧的编号
//unsigned int m_FrameRate;
BOOL m_bReadFileEnd; // 已经读到文件尾部
};
类里已经对每个接口有注释说明了,我就不一一详述了。讲一下这些接口的调用顺序:
1. 调用SetLoopMode设置是否循环读取文件;
2. 调用OpenFile打开一个文件,并获取每个帧的文件位移信息和大小(还没有分配内存填充数据);
3. 调用GetNextFrame获得每一帧的数据;
4. 读取完毕,调用CloseFile关闭文件。
下面代码是使用这些接口的一个例子:
CFrameExtractFilter fileParser;
fileParser.SetLoopMode(FALSE);
if(fileParser.OpenFile(_T("D:\\videos\\185.1080P.264"), H264_DEC) != 0)
{
printf(("OpenFile failed \n"));
return -1;
}
unsigned int frame_duration = 1000/25; //帧的间隔时间
unsigned char * pFrameBuffer = (unsigned char*)malloc(1000*1024);
unsigned int nFrameSize = 0;
while(1)
{
nFrameSize = 0;
if(fileParser.GetNextFrame(pFrameBuffer, &nFrameSize) <= 0)
{
break;
}
printf(("GetNextFrame got size: %d \n"), nFrameSize);
Sleep(frame_duration/2);
}
free(pFrameBuffer);
其中,OpenFile和GetNextFrame是两个比较重要的函数,下面就分别对这两个函数的实现进行解释说明。
首先附上OpenFile函数的代码:
//
// Read the File and get every frame's info
//
int CFrameExtractFilter::OpenFile(LPCTSTR pszFileName, SSBSIP_MFC_CODEC_TYPE file_codec_type)
{
SSBSIP_MFC_CODEC_TYPE codec_type = UNKNOWN_TYPE;
TCHAR file_ext[6] = {0};
TCHAR file_name[128] = {0};
int nRead;
unsigned int width, height;
width = height = 0;
char * divider = NULL;
char ext[8] = {0};
USES_CONVERSION;
divider = strrchr(T2A(pszFileName), '.');
if(divider)
strcpy(ext, divider+1);
bool bFlag = false;
if(file_codec_type != UNKNOWN_TYPE) //根据输入的编码类型来确定格式
{
bFlag = true;
codec_type = file_codec_type;
}
else
{
bFlag = GetCodecType(ext, (SSBSIP_MFC_CODEC_TYPE&)codec_type); //根据文件后缀名获取视频编码格式
}
if(!bFlag) //不是支持的格式
return -1;
SetFileName(pszFileName); //保存文件路径
m_StrmType = codec_type;
ATLTRACE(_T("OpenFile: %s \n"), pszFileName);
if(m_pFramexCtx != NULL)
{
SsbSipParserDeInit(m_pFramexCtx);
}
MPEG4_CONFIG_DATA conf_data;
memset(&conf_data, 0, sizeof(MPEG4_CONFIG_DATA));
conf_data.bGetWH = TRUE; //是否获取分辨率
//读取整个文件,获得视频分辨率和每一帧的信息
if((m_pFramexCtx = SsbSipParserInit((SSBSIP_MFC_CODEC_TYPE)m_StrmType, T2A(pszFileName) ,MAX_DECODER_INPUT_BUFFER_SIZE, &conf_data)) == NULL)
{
::OutputDebugString(L"SsbSipParserInit Failed\n");
return -2;
}
width = conf_data.width;
height = conf_data.height;
ATLTRACE("Width: %d, Height: %d \n", width, height);
// Validate the width & height value
if ((width > 4096) || (width < 16) || (height > 2048) || (height < 16)) {
ATLTRACE(_T("video size is not vaild. (width=%d, height=%d)\n"), width, height);
return -3;
}
m_bReadFileEnd = FALSE;
return 0;
}
OpenFile函数负责打开文件,读取并获得视频的相关信息。函数需要传入一个文件路径和一个编码类型参数,其中H264_DEC是指H264类型。如果不知道类型,可以将编码类型设置为UNKNOWN_TYPE,这表示格式未知,这种情况下,函数会根据文件的后缀名判断文件的格式&