最近项目中遇到将流保存在FLV文件,录制成的文件发现如下现象:
- 用
VLC
播放时,只能显示首帧,然后马上闪退。 - 用
QQ影音
播放时,可播放但是跳的很快,感觉是只能播放I帧。
分析过程
刚开始的怀疑是AVCDecoderConfigurationRecord的各参数的问题,后来发现都正常,也就是说SPS+PPS参数都正常。
用VLC
播放时,发现解析出来的编解码参数也正常。
那可能是为什么呢?
后来发现是Tag Header中的时间戳问题导致的。
FLV tag
Tag 分为两部分: Tag Header和Tag Body。
其中Tag Header共11个字节,包括TagType、DataSize、Timestamp、TimestampExtended、StreamID
;Tag Body部分即实际的Data
部分。
-
TagType(1字节)
Tag
的类型,主要分为:- 0x08: 音频。
- 0x09: 视频。
-
0x12: 即十进制的
18
, 脚本Tag
, 一般指onMetaData
。
DataSize(3字节)
数据(Data)的长度。Timestamp(3字节)
时间戳,单位毫秒。
该时间戳是相对于首个Tag时间戳的相对时间戳。
首个Tag的时间戳为0。-
TimestampExtended(1字节)
扩展时间戳。和Timestamp
合在一起拼成一个32bit
的时间戳。
该时间戳占高8位。
StreamID(3字节)
为0。
文档中还有段话非常重要:
In playback, the time sequencing of FLV tags depends on the FLV timestamps only. Any timing mechanisms built into the payload data format shall be ignored.
FLV文件回放时,FLV tags
的时间顺序仅依赖于FLV的时间戳。
忽略任何payload data中的时间机制。
可见时间戳对FLV文件的播放有多重要。
导致VLC
只能播放首帧的原因:FLV文件中所有的时间戳未进行有效的初始化, Timestamp和TimestampExtended全是0, 只有首帧的时间戳是正确的(就是0, _)。
示例:
问题解决
在程序中对时间戳进行正常赋值后,用VLC
可正常播放录制的FLV
视频。
部分代码:
struct SPxFLVRecorderTagHeader
{
unsigned char uchTagType;
unsigned char uchDataSize[3];
unsigned char uchTimestamp[3];
unsigned char uchTimestampExtended;
unsigned char uchStreamID[3];
};
...
SPxFLVRecorderTagHeader m_sFlvFileTagHeader;
...
HRESULT CPxFLVMuxer::WriteVideoSample(unsigned char *in_pBuffer, int in_nBufferLen, int in_nTimeStamp)
{
HRESULT hr = NS_NOERROR;
...
m_sFlvFileTagHeader.uchTagType = 0x09; // 视频Tag
...
m_sFlvFileTagHeader.uchTimestamp[0] = (BYTE)((in_nTimeStamp >> 16) & 0xff);
m_sFlvFileTagHeader.uchTimestamp[1] = (BYTE)((in_nTimeStamp >> 8) & 0xff);
m_sFlvFileTagHeader.uchTimestamp[2] = (BYTE)((in_nTimeStamp >> 0) & 0xff);
m_sFlvFileTagHeader.uchTimestampExtended = (BYTE)((in_nTimeStamp >> 24) & 0xff);
...
return hr;
}
...
References:
Video File Format Specification Version 10