FLV文件格式
FLV是流媒体封装格式,我们可以将其数据看为二进制字节流。
FLV包括文件头(Flv Header)和文件体(Flv Body)两部分,其中文件体由一系列的Tag及Tag Size对组成。Tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流(关键字或者文件信息之类)。
FLV Header
第1-3字节:为文件标识(Signature),总为“FLV”(0x46 0x4C 0x56);
第4字节:为版本,目前为1(0x01);
第5个字节:前5位保留必须为0,第6位表示是否存在音频Tag,第7位保留必须为0,第8位表示是否存在视频 Tag。
第6-9个字节:表示从File Header开始到File Body开始的字节数
FLV File Body的解析:
FLV body由若干个tag 组成;
每个tag header前面有4bytes记录着上一个tag的长度;
每一个tag第一部分是tag header,tag header长度为11bytes;
第1个字节:记录着tag的类型,音频(0x8),视频(0x9),脚本(0x12);
第2到4字节: 是数据区的长度,也就是tag data的长度;
第5到7字节: 是时间戳,单位是毫秒;
第8个字节: 是扩展时间戳,时间戳不够长的时候用;
第9到11字节:streamID,但是总为0;
这11个字节就是tag header的结构。
tag data如果是音频数据,第一个byte记录audio信息:
前4bits表示音频格式(全部格式请看官方文档):
·0 – 未压缩
·1 – ADPCM
·2 – MP3
·4 – Nellymoser 16-kHz mono
·5 – Nellymoser 8-kHz mono
·10 – AAC
下面两个bits表示samplerate:
·0 – 5.5KHz
·1 – 11kHz
·2 – 22kHz
·3 – 44kHz
下面1bit表示采样长度:
·0 – snd8Bit
·1 – snd16Bit
下面1bit表示类型:
·0 – sndMomo
·1 – sndStereo
之后是数据。
如果是视频数据,第一个byte记录video信息:
前4bits表示类型:
·1– keyframe
·2 – inner frame
·3 – disposable inner frame (h.263 only)
·4 – generated keyframe
后4bits表示解码器ID:
·2 – seronson h.263
·3 – screen video
·4 – On2 VP6
·5 – On2 VP6 with alpha channel
·6 – Screen video version 2
·7 – AVC (h.264)
之后是数据。
如果tag data是脚本数据,Script Tag Data,该类型Tag又通常被称为Metadata(元数据) Tag,会放一些关于FLV视频和音频的参数信息,如duration、width、height等。通常该类型Tag会跟在File Header后面作为第一个Tag出现,而且只有一个。
封装为flv文件
参考工作服务器 simplest_librtmp_example 中的simplest_librtmp_receive 的代码。
封装为flv文件还是比较简单的。
代码参考
void CFlvPushClient::SendMsg( uint32_t uiCode,PushMetaData& stPushMetaData )
{
m_szFlvHeader[0] = 'F';
m_szFlvHeader[1] = 'L';
m_szFlvHeader[2] = 'V';
m_szFlvHeader[3] = 0x01;
m_szFlvHeader[4] = stPushMetaData.ucFlags;
m_szFlvHeader[5] = 0x00;
m_szFlvHeader[6] = 0x00;
m_szFlvHeader[7] = 0x00;
m_szFlvHeader[8] = 0x09;
#ifdef _GRECORD_FLV
GAL_DEBUG_PRINT << "write FLV Header ...";
m_stFile.WriteData( m_szFlvHeader, 9 );
// previous tag size.
char pts[] = { (char)0x00, (char)0x00, (char)0x00, (char)0x00 };
m_stFile.WriteData( pts, 4 );
#endif
}
void CFlvPushClient::Write_Flv( SrsSharedPtrMessage* msg )
{
if ( msg->is_metadata() && m_ucMetadata == 0 )
{
m_ucMetadata = 1;
GAL_DEBUG_PRINT << "write metadata ...";
char szTagHeader[11];
memset(szTagHeader,0,11);
szTagHeader[0] = 0x12;
char* pszSize = (char*)&msg->size;
szTagHeader[1] = pszSize[2];
szTagHeader[2] = pszSize[1];
szTagHeader[3] = pszSize[0];
m_stFile.WriteData( szTagHeader,11 );
m_stFile.WriteData( msg->payload, msg->size );
char pre_size[4];
memset( pre_size,0,4 );
char* pp = (char*)&msg->size;
pre_size[0] = pp[3];
pre_size[1] = pp[2];
pre_size[2] = pp[1];
pre_size[3] = pp[0];
m_stFile.WriteData( pre_size, 4 );
}
else if ( msg->is_audio() )
{
int64_t llTimestamp = msg->timestamp;
int64_t delta = llTimestamp - m_llTimestampLast;
if ( delta < CONST_MAX_JITTER_MS_NEG || delta > CONST_MAX_JITTER_MS )
{
delta = DEFAULT_FRAME_TIME_MS;
GAL_WARN_PRINT << "detected last_pts = " << m_llTimestampLast << " pts = " << llTimestamp << " diff = " << delta << " last_time = " << m_llTimestampLastCorrect;
}
m_llTimestampLastCorrect = FPC_MAX( 0, m_llTimestampLastCorrect + delta );
m_llTimestampLast = llTimestamp;
GAL_DEBUG_PRINT << "write audio ... correct time = " << m_llTimestampLastCorrect;
char szTagHeader[11];
memset(szTagHeader,0,11);
szTagHeader[0] = 0x08;
char* pszSize = (char*)&msg->size;
szTagHeader[1] = pszSize[2];
szTagHeader[2] = pszSize[1];
szTagHeader[3] = pszSize[0];
int32_t iTimestamp = (int32_t)m_llTimestampLastCorrect;
char* pszTimestamp = (char*)&iTimestamp;
szTagHeader[4] = pszTimestamp[2];
szTagHeader[5] = pszTimestamp[1];
szTagHeader[6] = pszTimestamp[0];
szTagHeader[7] = (iTimestamp >> 24) & 0xFF;
m_stFile.WriteData( szTagHeader,11 );
m_stFile.WriteData( msg->payload, msg->size );
char pre_size[4];
memset( pre_size,0,4 );
char* pp = (char*)&msg->size;
pre_size[0] = pp[3];
pre_size[1] = pp[2];
pre_size[2] = pp[1];
pre_size[3] = pp[0];
m_stFile.WriteData( pre_size, 4 );
}
else if ( msg->is_video() )
{
int64_t llTimestamp = msg->timestamp;
int64_t delta = llTimestamp - m_llTimestampLast;
if ( delta < CONST_MAX_JITTER_MS_NEG || delta > CONST_MAX_JITTER_MS )
{
delta = DEFAULT_FRAME_TIME_MS;
GAL_WARN_PRINT << "detected last_pts = " << m_llTimestampLast << " pts = " << llTimestamp << " diff = " << delta << " last_time = " << m_llTimestampLastCorrect;
}
m_llTimestampLastCorrect = FPC_MAX( 0, m_llTimestampLastCorrect + delta );
m_llTimestampLast = llTimestamp;
GAL_DEBUG_PRINT << "write video ... correct time = " << m_llTimestampLastCorrect;
char szTagHeader[1];
memset(szTagHeader,0,11);
szTagHeader[0] = 0x09;
char* pszSize = (char*)&msg->size;
szTagHeader[1] = pszSize[2];
szTagHeader[2] = pszSize[1];
szTagHeader[3] = pszSize[0];
int32_t iTimestamp = (int32_t)m_llTimestampLastCorrect;
char* pszTimestamp = (char*)&iTimestamp;
szTagHeader[4] = pszTimestamp[2];
szTagHeader[5] = pszTimestamp[1];
szTagHeader[6] = pszTimestamp[0];
szTagHeader[7] = (iTimestamp >> 24) & 0xFF;
m_stFile.WriteData( szTagHeader,11 );
m_stFile.WriteData( msg->payload, msg->size );
char pre_size[4];
memset( pre_size,0,4 );
char* pp = (char*)&msg->size;
pre_size[0] = pp[3];
pre_size[1] = pp[2];
pre_size[2] = pp[1];
pre_size[3] = pp[0];
m_stFile.WriteData( pre_size, 4 );
}
}
参考文章
http://blog.chinaunix.net/uid-24567872-id-3471535.html
http://blog.csdn.net/weed_hz/article/details/10232407
http://blog.chinaunix.net/uid-24567872-id-3471547.html