http://blog.csdn.net/jwybobo2007/article/details/7662653
前段时间由于项目需要,要解avi文件,当时我第一时间想到用ffmpeg来处理,但想想觉得太大了,又是放到arm上跑的,感觉没必要。然后,搜索引擎上稍微搜了一下,没找到有用的示例,大部分都是利用windows的api进行读写,很明显linux下用不了。结果花了2-3天时间研究并写了一个avi文件的解封装代码,但是后来因某些原因没有去使用AVI了,所以代码也没进行后续的完善优化。这里贴出来供记录以及需要的人作参考。
/*!
@brief avi文件分析提取器
@author jwybobo2007
@file avifileparser.h
@note 代码在使用时,请标明作者,保留出处:http://blog.csdn.net/jwybobo2007
*/
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <map>
//
#define AVIIF_KEYFRAME 0x00000010L // 索引里面的关键帧标志
#define AVIIF_LIST 0x00000001L
// Flags for dwFlags|AVI头中的标志位
#define AVIFILEINFO_HASINDEX 0x00000010 // 是否有索引
#define AVIFILEINFO_MUSTUSEINDEX 0x00000020
#define AVIFILEINFO_ISINTERLEAVED 0x00000100
#define AVIFILEINFO_WASCAPTUREFILE 0x00010000
#define AVIFILEINFO_COPYRIGHTED 0x00020000
// 最大允许的AVI头大小
#define MAX_ALLOWED_AVI_HEADER_SIZE 131072
// 打不开文件
#define ERROR_OPEN_AVI 0
// 不是有效AVI
#define ERROR_INVALID_AVI 1
// 由于编译器c,c++标准过低,没有包含stdint.h头文件,并且现在也没有使用boost库,因此定义一些类型,来兼容以前的代码
#ifdef NO_STDINT
typedef char int8_t;
typedef unsigned char uint8_t;
typedef short int16_t;
typedef unsigned short uint16_t;
typedef int int32_t;
typedef unsigned int uint32_t;
typedef long long int64_t;
typedef unsigned long long uint64_t;
#endif
// 双字
typedef uint32_t DWORD;
// 单字
typedef uint16_t WORD;
// 定义长整型
typedef DWORD LONG;
// 字节
typedef uint8_t BYTE;
// 定义four cc;
typedef DWORD FourCC;
// 定义fourcc对应的整数值,avi文件中保存的是小端
const DWORD FOURCC_RIFF = 0x46464952;
const DWORD FOURCC_AVI = 0x20495641;
const DWORD FOURCC_LIST = 0x5453494C;
const DWORD FOURCC_hdrl = 0x6C726468;
const DWORD FOURCC_avih = 0x68697661;
const DWORD FOURCC_strl = 0x6C727473;
const DWORD FOURCC_strh = 0x68727473;
const DWORD FOURCC_strf = 0x66727473;
const DWORD FOURCC_STRD = 0x64727473;
const DWORD FOURCC_vids = 0x73646976;
const DWORD FOURCC_auds = 0x73647561;
const DWORD FOURCC_INFO = 0x4F464E49;
const DWORD FOURCC_ISFT = 0x54465349;
const DWORD FOURCC_idx1 = 0x31786469;
const DWORD FOURCC_movi = 0x69766F6D;
const DWORD FOURCC_JUNK = 0x4B4E554A;
const DWORD FOURCC_vprp = 0x70727076;
const DWORD FOURCC_PAD = 0x20444150;
const DWORD FOURCC_DIV3 = 861292868;
const DWORD FOURCC_DIVX = 1482049860;
const DWORD FOURCC_XVID = 1145656920;
const DWORD FOURCC_DX50 = 808802372;
const DWORD FOURCC_fmt = 0x20746D66; // for WAVE files
const DWORD FOURCC_data = 0x61746164; // for WAVE files
const DWORD FOURCC_WAVE = 0x45564157; // for WAVE files
// 调色板
typedef struct
{
BYTE rgbBlue; // 蓝
BYTE rgbGreen; // 绿
BYTE rgbRed; // 红
BYTE rgbReserved; // 保留
} RGBQUAD;
// AVI主头部
typedef struct
{
FourCC fcc; // 必须为 avih
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
DWORD dwMicroSecPerFrame; // 视频帧间隔时间(以毫秒为单位)
DWORD dwMaxBytesPerSec; // 这个AVI文件的最大数据率
DWORD dwPaddingGranularity; // 数据填充的粒度
DWORD dwFlags; // AVI文件的全局标记,比如是否含有索引块等
DWORD dwTotalFrames; // 总帧数
DWORD dwInitialFrames; // 为交互格式指定初始帧数(非交互格式应该指定为0)
DWORD dwStreams; // 本文件包含的流的个数
DWORD dwSuggestedBufferSize; // 建议读取本文件的缓存大小(应能容纳最大的块)
DWORD dwWidth; // 视频图像的宽(以像素为单位)
DWORD dwHeight; // 视频图像的高(以像素为单位)
DWORD dwReserved[4]; // 保留
} AVIMainHeader;
// 定义矩形区域
typedef struct
{
short int left; // 总边距
short int top; // 顶边距
short int right; // 右边距
short int bottom; // 底边距
}RECT;
// AVI流头部
typedef struct
{
FourCC fcc; // 必须为 strh
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
FourCC fccType; // 流的类型: auds(音频流) vids(视频流) mids(MIDI流) txts(文字流)
FourCC fccHandler; // 指定流的处理者,对于音视频来说就是解码器
DWORD dwFlags; // 标记:是否允许这个流输出?调色板是否变化?
WORD wPriority; // 流的优先级(当有多个相同类型的流时优先级最高的为默认流)
WORD wLanguage; // 语言
DWORD dwInitialFrames; // 为交互格式指定初始帧数
DWORD dwScale; // 每帧视频大小或者音频采样大小
DWORD dwRate; // dwScale/dwRate,每秒采样率
DWORD dwStart; // 流的开始时间
DWORD dwLength; // 流的长度(单位与dwScale和dwRate的定义有关)
DWORD dwSuggestedBufferSize; // 读取这个流数据建议使用的缓存大小
DWORD dwQuality; // 流数据的质量指标(0 ~ 10,000)
DWORD dwSampleSize; // Sample的大小
RECT rcFrame; // 指定这个流(视频流或文字流)在视频主窗口中的显示位置,视频主窗口由AVIMAINHEADER结构中的dwWidth和dwHeight决定
} AVIStreamHeader;
// 位图头
typedef struct
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BitmapInfoHeader;
// 位图信息
typedef struct
{
BitmapInfoHeader bmiHeader; // 位图头
RGBQUAD bmiColors[1]; // 调色板
} BitmapInfo;
// 音频波形信息
typedef struct
{
WORD wFormatTag;
WORD nChannels; // 声道数
DWORD nSamplesPerSec; // 采样率
DWORD nAvgBytesPerSec; // 每秒的数据量
WORD nBlockAlign; // 数据块对齐标志
WORD wBitsPerSample; // 每次采样的数据量
WORD cbSize; // 大小
} WaveFormatEx;
// 索引节点信息
typedef struct
{
DWORD dwChunkId; // 本数据块的四字符码(00dc 01wb)
DWORD dwFlags; // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
DWORD dwOffset; // 本数据块在文件中的偏移量
DWORD dwSize; // 本数据块的大小
} AVIIndexEntry;
// 索引信息
typedef struct
{
FourCC fcc; // 必须为‘idx1’
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
uint32_t position; // 数据起始位置偏移
std::map<uint32_t, std::vector<AVIIndexEntry> > videoIndexMap; // 视频索引表,00dc等转换成整形表示
std::map<uint32_t, std::vector<AVIIndexEntry> > audioIndexMap; // 音频索引表,00wb等转换成整形表示
} AVIIndex;
/*!
@brief avi文件分析提取器
*/
class AVIFileParser
{
public:
AVIFileParser();
AVIFileParser(const char* file);
~AVIFileParser(void);
public:
/*!
@brief 打开AVI文件
*/
void openAVI(const char* file);
/*!
@brief 是否是有效avi
*/
bool isValid(){return _isValid;}
/*!
@brief 是否有音频
*/
bool hasAudio(){return _hasAudio;}
/*!
@brief 是否有视频
*/
bool hasVideo(){return _hasVideo;}
/*!
@brief 返回avi头
*/
const AVIMainHeader* aviMainHeader(){return &_aviMainHeader;}
/*!
@brief 返回avi视频流头
*/
const AVIStreamHeader* aviVideoStreamHeader(){return &_aviVideoStreamHeader;}
/*!
@brief 返回avi音频流头
*/
const AVIStreamHeader* aviAudioStreamHeader(){return &_aviAudioStreamHeader;}
/*!
@brief 返回位图信息
*/
const BitmapInfo* bitmapInfo(){return &_bitmapInfo;}
/*!
@brief 返回音频信息
*/
const WaveFormatEx* waveFormatEx(){return &_waveInfo;}
/*!
@brief 最大视频帧大小
*/
uint32_t maxFrameSize(){return _maxFrameSize;}
/*!
@brief 获取视频帧
@param buf 视频帧存储缓冲
@param index 帧索引,默认-1,表示从当前的索引继续往下读取
@return 视频帧大小
*/
int32_t getVideoFrame(char* buf, int32_t index = -1);
/*!
@brief 偏移视频帧从指定位置开始
@param index 指定索引
*/
bool seekVideoFrame(int32_t index);
/*!
@brief 获取音频帧
*/
private:
/// 解析AVI
void _parseFile();
/// 从文件读指定长度的数据,出错自动抛出异常
bool readFile(char* buf, uint32_t len);
/// 从文件读12个字节并分析
bool readFileTagSize(DWORD& fourcc, uint32_t& size, DWORD& fourcc2);
/// 从文件读4字节
uint32_t readFileDW();
/// 从文件读2字节
uint16_t readFileW();
//
// 重载
/// 从文件读4字节
bool readFileDW(uint32_t& dw);
/// 从文件读2字节
bool readFileW(uint16_t& w);
private:
// 文件句柄
FILE* _aviFd;
// 是否有音频
bool _hasAudio;
// 是否有视频
bool _hasVideo;
// 是否有效
bool _isValid;
// 文件长度
uint32_t _fLen;
// avi主头
AVIMainHeader _aviMainHeader;
// avi视频流头部
AVIStreamHeader _aviVideoStreamHeader;
// avi音频流头部
AVIStreamHeader _aviAudioStreamHeader;
// 位图信息
BitmapInfo _bitmapInfo;
// 音频信息
WaveFormatEx _waveInfo;
// 编码程序
std::string _soft;
// 索引信息
AVIIndex _aviIndex;
// movi的开始位置偏移
uint32_t _moviOff;
// 最大帧大小
uint32_t _maxFrameSize;
// 当前视频位置索引(数组中的位置,下标)
int32_t _currVideoIndex;
};
#include <assert.h>
#include <errno.h>
#include "avifileparser.h"
#include "byte_write.h"
AVIFileParser::AVIFileParser(void)
:_aviFd(NULL),
_hasAudio(false),
_hasVideo(false),
_isValid(false),
_fLen(0),
_moviOff(0),
_maxFrameSize(0),
_currVideoIndex(0)
{
}
AVIFileParser::AVIFileParser( const char* file )
:_hasAudio(false),
_hasVideo(false),
_isValid(false),
_fLen(0),
_moviOff(0),
_maxFrameSize(0),
_currVideoIndex(0)
{
openAVI(file);
}
AVIFileParser::~AVIFileParser(void)
{
if (_aviFd != NULL)
fclose(_aviFd);
}
void AVIFileParser::openAVI( const char* file )
{
_aviFd = fopen(file, "rb");
if (_aviFd == NULL)
{
#ifndef NPRINT
printf("open avi error: %d [%s]\n", errno, strerror(errno));
#endif
// 抛出错误号
throw ERROR_OPEN_AVI;
}
// 开始解析
_parseFile();
_isValid = true;
}
void AVIFileParser::_parseFile()
{
// 第一次读12个字节
DWORD fourcc = 0;
DWORD fourcc2 = 0;
bool flag = true;
bool hasIndex = false;
readFileTagSize(fourcc, _fLen, fourcc2);
if (fourcc != FOURCC_RIFF || fourcc2 != FOURCC_AVI)
throw ERROR_INVALID_AVI;
while (flag)
{
uint32_t size = 0;
bool isEof = readFileDW(fourcc);
if (isEof)
return;
isEof = readFileDW(size);
if (isEof)
return;
if (fourcc == FOURCC_LIST)
{
fourcc2 = readFileDW();
switch (fourcc2)
{
case FOURCC_hdrl:
{
if (size > MAX_ALLOWED_AVI_HEADER_SIZE)
throw ERROR_INVALID_AVI;
// 跳过hdrl
uint32_t off = 4;
while (off < size)
{
fourcc = readFileDW();
switch (fourcc)
{
case FOURCC_avih:
{
_aviMainHeader.fcc = FOURCC_avih;
_aviMainHeader.cb = readFileDW();
_aviMainHeader.dwMicroSecPerFrame = readFileDW();
_aviMainHeader.dwMaxBytesPerSec = readFileDW();
_aviMainHeader.dwPaddingGranularity = readFileDW();
_aviMainHeader.dwFlags = readFileDW();
_aviMainHeader.dwTotalFrames = readFileDW();
_aviMainHeader.dwInitialFrames = readFileDW();
_aviMainHeader.dwStreams = readFileDW();
_aviMainHeader.dwSuggestedBufferSize = readFileDW();
_aviMainHeader.dwWidth = readFileDW();
_aviMainHeader.dwHeight = readFileDW();
if ((AVIFILEINFO_HASINDEX & _aviMainHeader.dwFlags) == AVIFILEINFO_HASINDEX)
hasIndex = true;
// 跳过保留字段
fseek(_aviFd, 16, SEEK_CUR);
// 跳过avih以及长度各四个字节
off += _aviMainHeader.cb + 8;
}
break;
case FOURCC_LIST:
{
int avListLen = readFileDW();
if (readFileDW() != FOURCC_strl)
throw ERROR_INVALID_AVI;
// 跳过strl
int tmpOff = 4;
AVIStreamHeader aviStreamHeader = {0};
while (tmpOff < avListLen)
{
fourcc = readFileDW();
tmpOff += 4;
if (fourcc == FOURCC_strh)
{
aviStreamHeader.fcc = FOURCC_strh;
aviStreamHeader.cb = readFileDW();
aviStreamHeader.fccType = readFileDW();
aviStreamHeader.fccHandler = readFileDW();
aviStreamHeader.dwFlags = readFileDW();
aviStreamHeader.wPriority = readFileW();
aviStreamHeader.wLanguage = readFileW();
aviStreamHeader.dwInitialFrames = readFileDW();
aviStreamHeader.dwScale = readFileDW();
aviStreamHeader.dwRate = readFileDW();
aviStreamHeader.dwStart = readFileDW();
aviStreamHeader.dwLength = readFileDW();
aviStreamHeader.dwSuggestedBufferSize = readFileDW();
aviStreamHeader.dwQuality = readFileDW();
aviStreamHeader.dwSampleSize = readFileDW();
aviStreamHeader.rcFrame.left = readFileW();
aviStreamHeader.rcFrame.top = readFileW();
aviStreamHeader.rcFrame.right = readFileW();
aviStreamHeader.rcFrame.bottom = readFileW();
// 跳过长度
tmpOff += 4;
tmpOff += aviStreamHeader.cb;
}
else if (fourcc == FOURCC_strf)
{
int tmpLen = readFileDW();
if (aviStreamHeader.fccType == FOURCC_vids)
{
_hasVideo = true;
_aviVideoStreamHeader = aviStreamHeader;
_bitmapInfo.bmiHeader.biSize = readFileDW();
_bitmapInfo.bmiHeader.biWidth = readFileDW();
_bitmapInfo.bmiHeader.biHeight = readFileDW();
_bitmapInfo.bmiHeader.biPlanes = readFileW();
_bitmapInfo.bmiHeader.biBitCount = readFileW();
_bitmapInfo.bmiHeader.biCompression = readFileDW();
_bitmapInfo.bmiHeader.biSizeImage = readFileDW();
_bitmapInfo.bmiHeader.biXPelsPerMeter = readFileDW();
_bitmapInfo.bmiHeader.biYPelsPerMeter = readFileDW();
_bitmapInfo.bmiHeader.biClrUsed = readFileDW();
_bitmapInfo.bmiHeader.biClrImportant = readFileDW();
if (tmpLen > _bitmapInfo.bmiHeader.biSize)
fseek(_aviFd, tmpLen - _bitmapInfo.bmiHeader.biSize, SEEK_CUR);
}
else if (aviStreamHeader.fccType == FOURCC_auds)
{
_hasAudio = true;
_aviAudioStreamHeader = aviStreamHeader;
_waveInfo.wFormatTag = readFileW();
_waveInfo.nChannels = readFileW();
_waveInfo.nSamplesPerSec = readFileDW();
_waveInfo.nAvgBytesPerSec = readFileDW();
_waveInfo.nBlockAlign = readFileW();
_waveInfo.wBitsPerSample = readFileW();
_waveInfo.cbSize = readFileW();
fseek(_aviFd, _waveInfo.cbSize, SEEK_CUR);
/*
#include <mmreg.h>
WAVE_FORMAT_PCM
WAVE_FORMAT_ALAW
*/
}
// 跳过长度
tmpOff += 4;
tmpOff += tmpLen;
}
else if (fourcc == FOURCC_JUNK)
{
int tmpLen = readFileDW();
fseek(_aviFd, tmpLen, SEEK_CUR);
// 跳过长度
tmpOff += 4;
tmpOff += tmpLen;
}
else if (fourcc == FOURCC_vprp)
{
int tmpLen = readFileDW();
fseek(_aviFd, tmpLen, SEEK_CUR);
// 跳过长度
tmpOff += 4;
tmpOff += tmpLen;
}
}
off += avListLen + 8;
}
break;
case FOURCC_JUNK:
{
// 跳过JUNK
off += 4;
int tmpLen = readFileDW();
fseek(_aviFd, tmpLen, SEEK_CUR);
// 跳过长度
off += 4;
off += tmpLen;
}
break;
}
}
}
break;
case FOURCC_INFO:
{
fourcc = readFileDW();
if (fourcc == FOURCC_ISFT)
{
int tmpLen = readFileDW();
_soft.resize(tmpLen);
readFile(&_soft[0], tmpLen);
}
}
break;
case FOURCC_movi:
{
_moviOff = ftell(_aviFd);
if (hasIndex)
{
// 跳过movi,直接到idx处
fseek(_aviFd, size - 4, SEEK_CUR);
}
}
break;
}
}
else if (fourcc == FOURCC_idx1)
{
_aviIndex.fcc = FOURCC_idx1;
_aviIndex.cb = size;
int tmpOff = 0;
while (tmpOff < _aviIndex.cb)
{
char tmpBuf[4] = {0};
readFile(tmpBuf, 4);
int index = read_le_dw(tmpBuf);
AVIIndexEntry tmpEntry;
tmpEntry.dwFlags = readFileDW();
tmpEntry.dwOffset = readFileDW();
tmpEntry.dwSize = readFileDW();
if (tmpEntry.dwSize > _maxFrameSize)
_maxFrameSize = tmpEntry.dwSize;
// 视频数据
if (tmpBuf[2] == 'd')
{
_aviIndex.videoIndexMap[index].push_back(tmpEntry);
}
else if (tmpBuf[2] == 'w')
{
_aviIndex.audioIndexMap[index].push_back(tmpEntry);
}
// 一个索引信息的长度
tmpOff += 16;
}
}
else if (fourcc == FOURCC_JUNK)
{
// 跳过
fseek(_aviFd, size, SEEK_CUR);
}
}
}
bool AVIFileParser::readFile( char* buf, uint32_t len )
{
uint32_t ret = fread(buf, 1, len, _aviFd);
if (ret != len)
{
if (feof(_aviFd) != 0)
return true;
#ifndef NPRINT
printf("fread avi error: %d [%s]\n", errno, strerror(errno));
#endif
throw ERROR_INVALID_AVI;
}
return false;
}
bool AVIFileParser::readFileTagSize( DWORD& fourcc, uint32_t& size, DWORD& fourcc2 )
{
char tmpBuf[12] = {0};
bool ret = readFile(tmpBuf, 12);
fourcc = read_le_dw(tmpBuf);
size = read_le_dw(tmpBuf + 4);
fourcc2 = read_le_dw(tmpBuf + 8);
return ret;
}
uint32_t AVIFileParser::readFileDW()
{
char tmpBuf[4] = {0};
readFile(tmpBuf, 4);
return read_le_dw(tmpBuf);
}
bool AVIFileParser::readFileDW( uint32_t& dw )
{
char tmpBuf[4] = {0};
bool ret = readFile(tmpBuf, 4);
dw = read_le_dw(tmpBuf);
return ret;
}
uint16_t AVIFileParser::readFileW()
{
char tmpBuf[2] = {0};
readFile(tmpBuf, 2);
return read_le_w(tmpBuf);
}
bool AVIFileParser::readFileW( uint16_t& w )
{
char tmpBuf[2] = {0};
bool ret = readFile(tmpBuf, 2);
w = read_le_w(tmpBuf);
return ret;
}
int32_t AVIFileParser::getVideoFrame( char* buf, int32_t index /*= -1*/ )
{
if (!_hasVideo)
return -1;
// 只取第一个
std::vector<AVIIndexEntry>& videoVec = _aviIndex.videoIndexMap.begin()->second;
int32_t tmpIndex = 0;
if (index == -1)
{
if (_currVideoIndex < videoVec.size())
tmpIndex = _currVideoIndex++;
else
return 0;
}
else
tmpIndex = index;
AVIIndexEntry& videoEntry = videoVec[tmpIndex];
// 从movi结尾加上偏移后是dc db的数据,然后再跳过4字节的一个长度值
fseek(_aviFd, _moviOff + videoEntry.dwOffset + 4, SEEK_SET);
int32_t ret = fread(buf, 1, videoEntry.dwSize, _aviFd);
assert(ret == videoEntry.dwSize);
return ret;
}
bool AVIFileParser::seekVideoFrame( int32_t index )
{
std::vector<AVIIndexEntry>& videoVec = _aviIndex.videoIndexMap.begin()->second;
if (index < videoVec.size())
{
_currVideoIndex = index;
return true;
}
return false;
}