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

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

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

AMF0

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

类型 说明
0x00 Number 8字节浮点数
0x01 Boolean 布尔
0x02 String 字符串
0x03 Object 对象,键值对
0x04 MovieClip 保留,未使用
0x05 Null
0x06 Undefined 未定义类型
0x07 Reference 引用类型
0x08 ECMA array ECMA数组,键值对
0x09 Object end 对象和ECMA数组结束标志
0x0A Strict array 普通数组
0x0B Date 日期
0x0C Long string 长字符串
0x0D Unsupported 不支持类型
0x0E Recordset 保留,未使用
0x0F Xml document xml文档
0x10 Typed object 有类型对象
0x11 Avmplus 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       = 0x00AMF_BOOLEAN      = 0x01AMF_STRING       = 0x02AMF_OBJECT       = 0x03AMF_MOVIECLIP    = 0x04 //reservedAMF_NULL         = 0x05AMF_UNDEFINED    = 0x06AMF_REFERENCE    = 0x07AMF_ECMA_ARRAY   = 0x08AMF_OBJECT_END   = 0x09AMF_STRICT_ARRAY = 0x0AAMF_DATE         = 0x0BAMF_LONG_STRING  = 0x0CAMF_UNSUPPORTED  = 0x0DAMF_RECORDSET    = 0x0E //reservedAMF_XML_DOCUMENT = 0x0FAMF_TYPE_OBJECT  = 0x10AMF_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 byteif 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_ENDEDcase 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 uint16if 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 stringvar 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 uint32if err = s.U32(&size).Error(); err != nil {return}v = make(map[string]interface{}, size)for i := 0; i < int(size); i++ {var key stringvar val interface{}if key, err = DecodeAMFString(s); err != nil {return}if val, err = DecodeAMF(s); err != nil {return}v[key] = val}var end uint32if 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 uint32if 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 int16if 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 uint32if 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   FlvTagHeaderMethod   stringMateData map[string]interface{}
}

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

属性 类型 说明
duration float64 时长(秒)
width float64 视频宽(像素)
height float64 视频高(像素)
videodatarate float64 码率(kbps)
framerate float64 帧率
videocodecid float64 视频编码格式
audiosamplerate float64 音频采样频率
audiosamplesize float64 音频采样分辨率
stereo bool 是否是立体声
audiocodecid float64 音频编码格式
filesize float64 文件字节数

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

func DecodeFlvDataTag(t FlvTag) (d FlvDataTag, err error) {d.Header = t.Headervar val interface{}var ok boolif val, err = DecodeAMF(t.Data); err != nil {return} else if d.Method, ok = val.(string); !ok {err = FLV_FMT_ERRORreturn}if val, err = DecodeAMF(t.Data); err != nil {return} else if d.MateData, ok = val.(map[string]interface{}); !ok {err = FLV_FMT_ERRORreturn}return
}

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

【Go】FLV文件解析(二)相关推荐

  1. 【Go】FLV文件解析(三)

    生命不息,编程不止.本章我们继续解析FLV文件中的音频Tag的内容. Audio Tag Data 在[Go]FLV文件解析(一)中我们讲了FLV Tag的基本结构是Tag Header加Tag Da ...

  2. 【Go】FLV文件解析(一)

    这是一个系列教程,一是为了解释FLV文件的结构,二是为了练习Go语言,希望大家多多支持. 在实战编码之前,我们需要首先了解FLV文件的格式.FLV是adobe出品的视频封装格式,注意它只是封装格式,不 ...

  3. CAD中的dxf文件解析(二):dxflib的使用

    1.前言 上一篇中对dxf文件及文件中常见的需要解析的直线,圆,圆弧,椭圆,多段线的说明,对dxf文件有了初步的了解,并做好了下载dxflib,dxf帮助文档的准备(没有准备的可以回到上一篇). CA ...

  4. 【Go】FLV文件解析(四)

    终于到了完结的时刻,这期我们来解析FLV中的视频Tag. Video Tag Data 视频Tag的总体结构和音频Tag相似,视频的Tag Data结构如下. 第一个字节的前4比特表示帧类型,共5种. ...

  5. CAD中的dxf文件解析(三):多段线篇

    1.前言 在前面的CAD中的dxf文件解析(二)中讲到了一些CAD的dxf文件解析点.线.圆弧.圆.块等的思路.下面提供链接: (二): CAD中的dxf文件解析(二):dxflib的使用_不爱学习 ...

  6. jmeter----jtl文件解析

    目录 一.jmeter----jtl文件解析 二.jtl文件转化成HTML报告 1. 命令行模式将jtl转成测试图表 2. 插件模式将jtl转成测试图表 一.jmeter----jtl文件解析 命令行 ...

  7. OpenCV读写视频文件解析(二)

    OpenCV读写视频文件解析(二) VideoCapture::set 设置视频捕获中的属性. C++: bool VideoCapture::set(int propId, double value ...

  8. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  9. C语言文件操作解析(二)【转载】

    http://www.cnblogs.com/dolphin0520/archive/2011/10/05/2199598.html C语言文件操作解析(二) C语言中对文件进行操作必须首先打开文件, ...

最新文章

  1. c语言猜拳游戏中出现的关键词,C语言猜拳游戏代码及分析
  2. 全球缺芯+瑞萨火灾——网络营销之下一众车企减产的减产,停产的停产
  3. All are Same 思维,gcd
  4. lambda表达式pythonlist_Python 使用Lambda对list(列表)中指定格式字符串元素排序方法...
  5. R语言seqm_R语言seq()函数用法
  6. leetcode 122. 买卖股票的最佳时机 II 思考分析
  7. sql注入基于错误-单引号-字符型
  8. 前端中实时显示当前时间的js代码
  9. 力扣501. 二叉搜索树中的众数(JavaScript)
  10. WINRAR青绿色透明主题皮肤 Vista/win 7下效果极佳
  11. 无视硬件检测直接运行Win10混合现实门户
  12. “单向网闸”技术介绍-网络隔离的新型产品
  13. uniapp原生sdk插件极光短信·极光短信插件可快速对接收发短信·官方伙伴优雅草发布
  14. 系统操作问题:无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动--亲测解决
  15. 全国计算机一级ms office考试题型,全国计算机考试一级MS Office考试大纲(2017年)
  16. 对重装系统彻底说再见——电脑C盘备份
  17. 盒马“开吃”火锅行业,海底捞们还有机会“捞钱”吗?
  18. Android 车机初体验: Auto,Automotive 傻傻分不清楚?| 开发者说·DTalk
  19. 钉钉E应用开发踩过的小坑之钉钉官网有两个全局错误码链接,啥区别??
  20. C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数

热门文章

  1. 纪念我的第一个Python程序:猜数字
  2. PotPlayer直播源推荐
  3. 企业服务业务系统-业务模型梳理(中)
  4. 立创EDA专业版,建立自己的元件库
  5. 04 修改数据库用户密码
  6. STM32参考手册、数据手册和编程手册
  7. UG12.0安装 出现 General Fault Exception 是硬件还是软件问题
  8. 关于错误 LNK2005在对象中已定义符号
  9. linux ps stat dls,findbugs 常见问题 及解决方案
  10. 【Android-Service】基于MVP的音乐播放器demo实现思路(附源码)