【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种类型,如下表所示。
值 | 类型 | 说明 |
---|---|---|
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
表示false
,01
表示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文件解析(二)相关推荐
- 【Go】FLV文件解析(三)
生命不息,编程不止.本章我们继续解析FLV文件中的音频Tag的内容. Audio Tag Data 在[Go]FLV文件解析(一)中我们讲了FLV Tag的基本结构是Tag Header加Tag Da ...
- 【Go】FLV文件解析(一)
这是一个系列教程,一是为了解释FLV文件的结构,二是为了练习Go语言,希望大家多多支持. 在实战编码之前,我们需要首先了解FLV文件的格式.FLV是adobe出品的视频封装格式,注意它只是封装格式,不 ...
- CAD中的dxf文件解析(二):dxflib的使用
1.前言 上一篇中对dxf文件及文件中常见的需要解析的直线,圆,圆弧,椭圆,多段线的说明,对dxf文件有了初步的了解,并做好了下载dxflib,dxf帮助文档的准备(没有准备的可以回到上一篇). CA ...
- 【Go】FLV文件解析(四)
终于到了完结的时刻,这期我们来解析FLV中的视频Tag. Video Tag Data 视频Tag的总体结构和音频Tag相似,视频的Tag Data结构如下. 第一个字节的前4比特表示帧类型,共5种. ...
- CAD中的dxf文件解析(三):多段线篇
1.前言 在前面的CAD中的dxf文件解析(二)中讲到了一些CAD的dxf文件解析点.线.圆弧.圆.块等的思路.下面提供链接: (二): CAD中的dxf文件解析(二):dxflib的使用_不爱学习 ...
- jmeter----jtl文件解析
目录 一.jmeter----jtl文件解析 二.jtl文件转化成HTML报告 1. 命令行模式将jtl转成测试图表 2. 插件模式将jtl转成测试图表 一.jmeter----jtl文件解析 命令行 ...
- OpenCV读写视频文件解析(二)
OpenCV读写视频文件解析(二) VideoCapture::set 设置视频捕获中的属性. C++: bool VideoCapture::set(int propId, double value ...
- 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)
Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...
- C语言文件操作解析(二)【转载】
http://www.cnblogs.com/dolphin0520/archive/2011/10/05/2199598.html C语言文件操作解析(二) C语言中对文件进行操作必须首先打开文件, ...
最新文章
- c语言猜拳游戏中出现的关键词,C语言猜拳游戏代码及分析
- 全球缺芯+瑞萨火灾——网络营销之下一众车企减产的减产,停产的停产
- All are Same 思维,gcd
- lambda表达式pythonlist_Python 使用Lambda对list(列表)中指定格式字符串元素排序方法...
- R语言seqm_R语言seq()函数用法
- leetcode 122. 买卖股票的最佳时机 II 思考分析
- sql注入基于错误-单引号-字符型
- 前端中实时显示当前时间的js代码
- 力扣501. 二叉搜索树中的众数(JavaScript)
- WINRAR青绿色透明主题皮肤 Vista/win 7下效果极佳
- 无视硬件检测直接运行Win10混合现实门户
- “单向网闸”技术介绍-网络隔离的新型产品
- uniapp原生sdk插件极光短信·极光短信插件可快速对接收发短信·官方伙伴优雅草发布
- 系统操作问题:无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动--亲测解决
- 全国计算机一级ms office考试题型,全国计算机考试一级MS Office考试大纲(2017年)
- 对重装系统彻底说再见——电脑C盘备份
- 盒马“开吃”火锅行业,海底捞们还有机会“捞钱”吗?
- Android 车机初体验: Auto,Automotive 傻傻分不清楚?| 开发者说·DTalk
- 钉钉E应用开发踩过的小坑之钉钉官网有两个全局错误码链接,啥区别??
- C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数