如今视频直播非常火热,就需要对视频格式的知识做些储备,FLV(即Flash Video)这一视频格式是最简单的,通过对它的文件格式的研究,可以对这些应用于网络传输的视频格式,也即流媒体有一些基本的了解。
FLV
流媒体简介
流式媒体是按照时间顺序依次下载播放的媒体形式,依托于Flash这一强大播放器插件,可以实现边下载边播放,比如在优酷等视频平台观看节目时,并不需要把视频完整下载下来就可以播放,而这些视频网站都有Flash插件的支持;与之对应的是BT下载的视频,很难做到_顺序下载_,另外还要看播放器是否支持解码部分视频,来达到边下载边播放的目的,你可以尝试播放一个没有下载完的视频,拖动进度条是可以找到能看的片段,但很少能从视频开头开始。
从上面可以看出,流媒体传输最重要的特性是要按时间顺序传输。打个比方,如果流媒体是一列火车,火车进站就必须严格按照“火车头-第一节车厢-第二节车厢…”的方式进站,乱了顺序可不行 :P
FLV格式解析
FLV是流媒体视频传输格式中最最简单的一个,下面我们从格式方面入手,看看它是如何保证按时间顺序传播视频文件的。
为了方便理解,先上一张示意图:
火车头,也即FLV视频的第一部分,是用来表明自己身份的标识,“大家好,我叫FLV”,仅仅使用9个字节,其中3个字节是名字“FLV”,一个字节标识版本号,目前都为0,一个字节用来标识FLV里面是否包含音频和视频,最后用4个字节标识这个“火车头”,即整个头文件的长度。
使用vim打开一个flv文件,使用%!xxd以16进制方式浏览,可以看到文件的开头如下图:
前9个字节0x46 | 0x4c | 0x56 | 0x01 | 0x05 | 0x00 | 0x00 | 0x00 | 0x09,前3个是FLV三个字符的ASCII码,之后的0x01表示版本号,0x05表示有音频也有视频,如果仅有音频,则是0x04,如果仅有视频,则是0x01,最后的4个字节0x00 0x00 0x00 0x09表示了这个header长度为9。
有了上面这些知识,我们可以仅仅读取文件的前9个字节,就可以判断这个视频是否是FLV,下面是一小段golang语言的代码:
package main
import (
"fmt"
"os"
"encoding/binary"
"bytes"
)
func main() {
// 打开flv文件
f, err := os.Open("test.flv")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 读取前9个字节
header := make([]byte, 9, 9)
n, err := f.Read(header)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if n != 9 {
fmt.Println("read header length not equal 9")
os.Exit(1)
}
// 验证
if string(header[:3]) != "FLV" {
fmt.Println("header not start with FLV")
os.Exit(1)
}
var version uint8
if err := binary.Read(bytes.NewReader(header[3:4], binary.BigEndian, &version); err != nil {
fmt.Println(err)
os.Exit(1)
}
if version != 1 {
fmt.Println("version not equal 1")
os.Exit(1)
}
var flags uint8
if err := binary.Read(bytes.NewReader(header[4:5], binary.BigEndian, &flags); err != nil {
fmt.Println(err)
os.Exit(1)
}
switch flags {
case 1, 4, 5:
break;
default:
fmt.Println("invalid audio and video flags")
os.Exit(1)
}
var offset uint32
if err := binary.Read(bytes.NewReader(header[5:]), binary.BigEndian, &offset); err != nil {
fmt.Println(err)
os.Exit(1)
}
if offset != 9 {
fmt.Println("invalid offset")
os.Exit(1)
}
fmt.Println("this file is a flv")
}
得到FLV的头部并成功判断是FLV文件之后,就可以接收后面的数据了。后面的数据结构如下:
-------------------------
| Previous Tag Size |
-------------------------
| Tag |
-------------------------
| Previous Tag Size |
-------------------------
| Tag |
-------------------------
| Previous Tag Size |
-------------------------
| Tag |
-------------------------
| Previous Tag Size |
-------------------------
其中,每一段具体的数据叫做一个Tag,就如同上面图画中的一节节车厢;而每个Tag前面都会有四个字节用于表示前面的Tag大小,即Previous Tag Size。这个值可以用于验证数据的完整性,即播放器在接收完一个Tag后,可以再接收4个字节,得到前面的Tag的大小,判断是否一致。要知道,网络传输数据很容易发生数据丢失的情况。
Tag有三种类型,除了音频、视频,还有一种Script Data类型,音视频数据好理解,就是一段一段封装好的片段,Script Data这个类型的Tag一般都是作为第一个Tag出现。
每个Tag也包含了表示Tag信息的Header和具体数据的Data,结构如下所示:
-------------------------
| Tag Header |
-------------------------
| Tag Data |
-------------------------
其中Tag Header由11个字节组成: - 第1个字节:表示类型,0x08表示音频,0x09表示视频,0x12表示Script Data; - 第2-4字节:表示当前Tag Data的长度; - 第5-7字节:表示Tag的时间戳,以毫秒为单位; - 第8字节:时间戳的扩展字节,用于扩充上面的时间戳; - 第9-11字节:表示Stream ID,总为0。
由此可见,读取了11个字节后,我们就知道了接下来的Tag Data是什么类型,对应的长度是多少(这个决定了接下来读取的数据中有多少数据是Tag Data的),对应的时间戳是多少(这个决定了播放的顺序),读完接下来的Tag Data之后,再通过Previous Tag Data来验证下数据的长度。
Tag Data中就是具体的音视频数据了,因为涉及到具体的音视频格式,这里就不一一展开了。