终于到了完结的时刻,这期我们来解析FLV中的视频Tag。
Video Tag Data
视频Tag的总体结构和音频Tag相似,视频的Tag Data结构如下。
第一个字节的前4比特表示帧类型,共5种。
值 | 类型 | 说明 |
---|---|---|
1 | key frame | 关键帧 |
2 | inter frame | 非关键帧 |
3 | disposable inter frame | 仅H.263 |
4 | generated keyframe | 保留 |
5 | video info/command frame | 命令帧 |
如果帧类型为5,消息流包含一个8位无符号整数,含义如下:
0:Start of client-side seeking video frame sequence
1:End of client-side seeking video frame sequence
后4比特表示视频编码格式,共7种。
值 | 类型 | 说明 |
---|---|---|
1 | JPEG | |
2 | Sorenson H.263 | |
3 | Screen video | |
4 | On2 VP6 | |
5 | On2 VP6 with alpha channel | |
6 | Screen video version 2 | |
7 | AVC |
视频数据根据编码格式而不同,以AVC为例,结构如下。
AVC包类型有3种。
值 | 类型 | 说明 |
---|---|---|
0 | AVC sequence header | sps,pps |
1 | AVC NALU | |
2 | AVC end of sequence | 视频数据部分为空 |
当AVC包类型等于0或2时,CompositionTime为0。AVC包类型为0时,负载部分是一个叫AVCDecoderConfigurationRecord的结构,它在ISO 14496-15, 5.2.4.1中定义。其基本结构如下。
关于SPS和PPS的具体结构属于H264编码的内容,涉及到指数哥伦布编码,这里就不再展开了,有机会在讲H264编码时在详细介绍。
AVC包类型为1时,负载部分是NALUs,其基本结构是长度+NALU[+长度+NALU]
。NALU里面就是经过编码的视频数据。
实战
第一步还是先定义出需要的数据结构。
type FlvVideoTag struct {
Header FlvTagHeader
FrameType uint8
CodecID uint8
Data stream.Stream
}
type AVCPacket struct {
AVCPacketType uint8
CompositionTimeOffset uint32
Data stream.Stream
}
type AVCDecoderConfigurationRecord struct {
ConfigurationVersion uint8
AVCProfileIndication uint8
ProfileCompatibility uint8
AVCLevelIndication uint8
LengthSizeMinusOne uint8
NumOfSPS uint8
SPSLength uint16
SPS []byte
NumOfPPS uint8
PPSLength uint16
PPS []byte
}
接下来是解码代码。
// 解码FLV视频Tag
func DecodeFlvVideoTag(t FlvTag) (v FlvVideoTag, err error) {
v.Header = t.Header
var b byte
if err = t.Data.Byte(&b).Error(); err != nil {
return
}
v.FrameType = b >> 4 & 0x0F
v.CodecID = b & 0x0F
v.Data, err = t.Data.Produce(t.Data.Remain())
return
}
// 解码AVC包
func DecodeAVCPacket(t FlvVideoTag) (p AVCPacket, err error) {
if t.CodecID != CODEC_AVC {
err = errors.New("not avc format")
}
err = t.Data.U8(&p.AVCPacketType).
U24(&p.CompositionTimeOffset).
Error()
if err != nil {
return
}
p.Data, err = t.Data.Produce(t.Data.Remain())
return
}
// 解码AVCDecoderConfigurationRecord
func DecodeAVCDecoderConfigurationRecord(p AVCPacket) (a AVCDecoderConfigurationRecord, err error) {
err = p.Data.U8(&a.ConfigurationVersion).
U8(&a.AVCProfileIndication).
U8(&a.ProfileCompatibility).
U8(&a.AVCLevelIndication).
U8(&a.LengthSizeMinusOne).
U8(&a.NumOfSPS).
U16(&a.SPSLength).
Error()
a.LengthSizeMinusOne &= 0x03
a.NumOfSPS &= 0x1F
if err != nil {
return
}
if a.SPS, err = p.Data.Slice(int(a.SPSLength)); err != nil {
return
}
if err = p.Data.U8(&a.NumOfPPS).U16(&a.PPSLength).Error(); err != nil {
return
}
a.PPS, err = p.Data.Slice(int(a.PPSLength))
return
}
关于FLV文件的格式和解析就介绍到这里,完结撒花。