【Go】FLV文件解析(二)

书接上回,我们继续来解析FLV文件的内容,这次要解析的是元数据Tag的内容,需要注意的是不是每个FLV文件都有这个Tag的。

在有些教程中,元数据Tag也被称作Script Tag。在官方文档中其实是称作Data Tag,其中的内容称为ScriptDataObject。不管叫什么,你需要知道的是我们在讨论同一个东西。

ScriptDataObject的编码格式是AMF,全称Action Message Format。也是Adobe出品,一共有两个版本,为了区分,将先前的版本称为AMF0,新的版本称为AMF3。FLV中用到的主要是AMF0。

AMF0

AMF编码的基本套路就是类型+数据,具体的格式后面详细介绍。AMF0一共定义了17种类型,如下表所示。

类型说明
0x00Number8字节浮点数
0x01Boolean布尔
0x02String字符串
0x03Object对象,键值对
0x04MovieClip保留,未使用
0x05Null
0x06Undefined未定义类型
0x07Reference引用类型
0x08ECMA arrayECMA数组,键值对
0x09Object end对象和ECMA数组结束标志
0x0AStrict array普通数组
0x0BDate日期
0x0CLong string长字符串
0x0DUnsupported不支持类型
0x0ERecordset保留,未使用
0x0FXml documentxml文档
0x10Typed object有类型对象
0x11Avmplus Object从AMF0切换到AMF3

以上是完整的AMF0类型,来自AMF0官方文档。而在FLV文档中,实际只有前12中类型,因此在这里我们也只对前12种类型做出说明。

AMF Number

AMF的Number类型都是浮点数,占8字节,格式是类型+值。比如以下示例表示的就是数字10。

00 40 24 00 00 00 00 00 00

AMF Boolean

布尔类型的格式与数字类型相同,也是类型+值的形式。布尔类型用一个字节表示,00表示false01表示true

AMF String

AMF中的字符串都是变长的,基本格式为类型+长度+值。比如下面的例子表示的是字符串onMetaData

00 0A 6F 6E 4D 65 74 61 44 61 74 61

AMF Object

AMF对象类型实际上是键值对集合,它以0x000009结束,其中0x0000是空字符串,0x09是AMF对象和ECMA数组的结束标识。ECMA数组和对象类型非常相似,都是键值对,唯一的区别是ECMA数组多了数组长度字段。

AMF对象的基本格式是类型+属性+值+...+0x000009。其中属性是字符串类型,值可以是任意类型,遵循AMF类型+值的基本套路,解析过程根据类型而定。

AMF Reference

AMF引用类型的基本格式是类型+值,其中值是2字节无符号整数。它表示的是对某个变量的引用,目的是减少重复的数据传输带来的网络带宽。

AMF ECMA Array

ECMA数组也是键值对集合,以0x000009结束,它与Object类型的唯一区别是在类型后面有一个长度字段,表示键值对数量。其基本格式为类型+size+键+值+...+0x000009,键依然是字符串类型,值可以是任意类型,遵循类型+值的基本套路。

AMF Object End

表示AMF对象和ECMA数组类型的结束。对象和ECMA数组其实是键值对集合,键的类型是字符串。AMF对象和ECMA数组的结束其实是空字符串+AMF Object End标识,所以很多教程都认为对象和ECAM数组是以0x000009结束的,但其实前面两字节的0x0000表示的是一个空字符串。它的格式还是键+值,只不过结尾部分键是空字符串,也就是0x0000,值是结束标识,也就是0x09。这也是为什么只有对象和ECMA数组有结束标识,而Strict Array没有的原因。

AMF Strict Array

StrictArray才是真正意义上的数组,其基本格式为类型+size+元素+...,它并没有结束标志,这里的元素也是类型+值的格式。

AMF Date

基本格式为类型+时间戳+时区,其中时间戳是8字节浮点数,也就是Double类型,时区是2字节有符号整数,因为它代表的是失去偏移,可正可负。但是官方并不建议使用时区,而是建议设置为0。

AMF Long String

AMF Long String与String的区别在于前者的长度字段占4字节,是32位无符号整数,基本格式还是类型+长度+值

实战

介绍完AMF0数据类型的基本格式,下面可是实战练习。新建amf.go文件,同样这里也需到导入"gitee.com/lJOSVDE/stream"这个工具。

首先我们定义一些需要用到的常量。

const (
    AMF_NUMBER       = 0x00
    AMF_BOOLEAN      = 0x01
    AMF_STRING       = 0x02
    AMF_OBJECT       = 0x03
    AMF_MOVIECLIP    = 0x04 //reserved
    AMF_NULL         = 0x05
    AMF_UNDEFINED    = 0x06
    AMF_REFERENCE    = 0x07
    AMF_ECMA_ARRAY   = 0x08
    AMF_OBJECT_END   = 0x09
    AMF_STRICT_ARRAY = 0x0A
    AMF_DATE         = 0x0B
    AMF_LONG_STRING  = 0x0C
    AMF_UNSUPPORTED  = 0x0D
    AMF_RECORDSET    = 0x0E //reserved
    AMF_XML_DOCUMENT = 0x0F
    AMF_TYPE_OBJECT  = 0x10
    AMF_AMF3         = 0x11
)

var (
    ERR_UNSUPPORT         = errors.New("unsupport amf type")
    ERR_NOT_IMPLEMENT     = errors.New("not implement")
    ERR_AMF_TYPE_MISMATCH = errors.New("amf type mismatch")
    ERR_EXPECT_OBJECT_END = errors.New("expect amf object end")
)

接下来我们需要一个可以读取任意类型数的函数。

func DecodeAMF(s stream.Stream) (v interface{}, err error) {
    var amfType byte
    if err = s.Byte(&amfType).Error(); err != nil {
        return
    }
    switch amfType {
    case AMF_NUMBER:
        v, err = DecodeAMFNumber(s)
    case AMF_BOOLEAN:
        v, err = DecodeAMFBool(s)
    case AMF_STRING:
        v, err = DecodeAMFString(s)
    case AMF_OBJECT:
        v, err = DecodeAMFObject(s)
    case AMF_NULL:
    case AMF_UNDEFINED:
    case AMF_REFERENCE:
        v, err = DecodeAMFRefrence(s)
    case AMF_ECMA_ARRAY:
        v, err = DecodeAMFECMAArray(s)
    case AMF_OBJECT_END:
        err = ERR_OBJECT_ENDED
    case AMF_STRICT_ARRAY:
        v, err = DecodeAMFStrictArray(s)
    case AMF_DATE:
        v, err = DecodeAMFDate(s)
    case AMF_LONG_STRING:
        v, err = DecodeAMFLongString(s)
    default:
        err = ERR_UNSUPPORT
    }
    return
}

下面我们来实现具体的解码函数。

// 解码AMF数字类型
func DecodeAMFNumber(s stream.Stream) (v float64, err error) {
    err = s.F64(&v).Error()
    return
}

// 解码AMF布尔类型
func DecodeAMFBool(s stream.Stream) (v bool, err error) {
    err = s.Bool(&v).Error()
    return
}

// 解码AMF字符串类型
func DecodeAMFString(s stream.Stream) (v string, err error) {
    var length uint16
    if err = s.U16(&length).Error(); err != nil {
        return
    }
    if length == 0 {
        return "", nil
    }
    return s.String(int(length))
}

// 解码AMF对象类型
func DecodeAMFObject(s stream.Stream) (v map[string]interface{}, err error) {
    v = make(map[string]interface{})
    for err == nil {
        var property string
        var value interface{}
        if property, err = DecodeAMFString(s); err != nil {
            continue
        }
        if value, err = DecodeAMF(s); err != nil {
            continue
        }
        v[property] = value
    }
    if errors.Is(err, ERR_OBJECT_ENDED) {
        err = nil
    }
    return
}

// 解码AMF引用类型
func DecodeAMFRefrence(s stream.Stream) (v uint16, err error) {
    err = s.U16(&v).Error()
    return
}

// 解码AMF ECMA数组类型
func DecodeAMFECMAArray(s stream.Stream) (v map[string]interface{}, err error) {
    var size uint32
    if err = s.U32(&size).Error(); err != nil {
        return
    }
    v = make(map[string]interface{}, size)
    for i := 0; i < int(size); i++ {
        var key string
        var val interface{}
        if key, err = DecodeAMFString(s); err != nil {
            return
        }
        if val, err = DecodeAMF(s); err != nil {
            return
        }
        v[key] = val
    }
    var end uint32
    if err = s.U24(&end).Error(); err != nil {
        return
    }
    if end != 0x09 {
        err = ERR_EXPECT_OBJECT_END
    }
    return
}

// 解码AMF数组类型
func DecodeAMFStrictArray(s stream.Stream) (v []interface{}, err error) {
    var size uint32
    if err = s.U32(&size).Error(); err != nil {
        return
    }
    v = make([]interface{}, size)
    for i := 0; i < int(size); i++ {
        if item, err := DecodeAMF(s); err != nil {
            break
        } else {
            v[i] = item
        }
    }
    return
}

// 解码AMF日期类型
func DecodeAMFDate(s stream.Stream) (v int64, err error) {
    var zone int16
    if err = s.I64(&v).I16(&zone).Error(); err != nil {
        return
    }
    if zone != 0 {
        //TODO:计算时区
    }
    return
}

// 解码AMF长字符串类型
func DecodeAMFLongString(s stream.Stream) (v string, err error) {
    var length uint32
    if err = s.U32(&length).Error(); err != nil {
        return
    }
    if length == 0 {
        return "", nil
    }
    return s.String(int(length))
}

以上就是AMF解码的全部代码,下面我们还需要定义一个结构来表示FLV Data Tag。

type FlvDataTag struct {
    Header   FlvTagHeader
    Method   string
    MateData map[string]interface{}
}

在FLV Data Tag中,一般有两部分内容,一个是字符串onMetaData,第二部分就是元数据,类型是ECMA数组。元数据的内容并不是完全固定的,也就是说不同的FLV文件它们的元数据从键值对的数量上来说都有可能是不同的。常见的元数据有以下这些。

属性类型说明
durationfloat64时长(秒)
widthfloat64视频宽(像素)
heightfloat64视频高(像素)
videodataratefloat64码率(kbps)
frameratefloat64帧率
videocodecidfloat64视频编码格式
audiosampleratefloat64音频采样频率
audiosamplesizefloat64音频采样分辨率
stereobool是否是立体声
audiocodecidfloat64音频编码格式
filesizefloat64文件字节数

最后我们将【Go】FLV文件解析(一)中的FlvTag转化成FlvDataTag

func DecodeFlvDataTag(t FlvTag) (d FlvDataTag, err error) {
    d.Header = t.Header
    var val interface{}
    var ok bool
    if val, err = DecodeAMF(t.Data); err != nil {
        return
    } else if d.Method, ok = val.(string); !ok {
        err = FLV_FMT_ERROR
        return
    }

    if val, err = DecodeAMF(t.Data); err != nil {
        return
    } else if d.MateData, ok = val.(map[string]interface{}); !ok {
        err = FLV_FMT_ERROR
        return
    }
    return
}

以上就是FLV Data Tag解析的全部内容,下一期我们继续音频Tag的解析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值