February 2016 Blog Posts

FLV文件完整性校验方法

MitchellChu 2016-02-28 其他技术

书接上回,我们讨论了FLV文件的结构,有了这些基础之后,我们就能够对FLV文件进行一些操作了,比如本文要讨论的——校验FLV文件的完整性。

完整的文件应该按照FLV文件的结构提供完整的数据信息,如:FLV HeaderFLV Body中的Previous Tag SizeTag。那我们下面由简入繁的步骤来对FLV文件校验一番。

最简单的校验方法,仅校验FLV Header信息,如(代码):

public static bool IsValidFlvHeader(byte[] bytes)
{
    if (bytes.Length != 9) return false;
    byte[] header0 = new byte[] { 0x46, 0x4C, 0x56 };
    byte flvVer = bytes[3]; 
    byte flvType = bytes[4];
    byte[] header_offset = new byte[] { 0x00, 0x00, 0x00, 0x09 };

    if (!header0.SequenceEqual(bytes.Take(3).ToArray())) return false;
    if (flvVer != 0x01) return false;
    if (flvType != 0x01
        && flvType != 0x04
        && flvType != 0x05)
        return false;
    return header_offset.SequenceEqual(bytes.Skip(5).Take(4).ToArray());
}

这种方法仅的Header信息是否是标准的FLV Header,在进行文件区分——特别是文件名缺失的情况下,这种方法能够快速的对FLV文件进行提取。

当然,如果我们不仅仅是要对文件进行简单的识别,要更进一步,那么我们就需要更多的校验,比如校验FLV文件的完整性,下面是FLV文件的整体结构:

------------------------
|       FLV Header     |
------------------------
|  Previous Tag Size#0 |
------------------------
|        Tag #1        |
------------------------
|  Previous Tag Size#1 |
------------------------
|        Tag #2        |
------------------------
|  Previous Tag Size#2 |
------------------------
|         ...          |
------------------------
|        Tag #n        |
------------------------
|  Previous Tag Size#n |
------------------------

从结构中可以明确,要校验,只需要校验每个FLV Header,Tag Size即可基本完成校验,关于具体的细节请参见FLV文件格式详解

有结构的特性,我们可以从文件末尾往前推演进行校验,代码就不放出来了,留给参阅的朋友自己写吧,这里给个思路:

  1. 读取末尾的4字节,转化成整数,得出这之前的Tag Size
  2. 根据获取的Tag Size,向前跳过Tag块,再读取更前面的一个Tag的Tag Size(这里重复1)
  3. 重复1,2两步,直到Tag Size为0,或者当前读取位置(Position)已经小于读取的Tag Size + 9。
  4. 如果,读取到Tag Size为0时,且前面的数据长度为9(如果Position移动后,可能是13)则认为是完整FLV
  5. 如果,读取的Tag Size + 9 已经超出了剩下的可用长度,则认为不是完整FLV。

 这样就能够简单的分析一个FLV是否完整,这在对文件完整性粗略校验中有比较好的表现。

当然,如果你知道FLV文件的结构的话,你也可以通过下面这些方法来校验文件的完整性:

  1. 顺序来读取FLV文件进行校验,因为Tag的大小可以在Tag Header中获取,不能100%确认
  2. 知道在onMetaData中能够得到FLV文件的大小的话,那么,通过这种方法来判断完整性也是可行的。

结合上面的多种校验方式,就能比较准确的了。

基本的校验方法就这些,在这里起个抛砖引玉的作用,如果你有更好的方法,欢迎讨论。

FLV文件格式详解

MitchellChu 2016-02-28 其他技术

在集体挺进HTML5的时代,来讨论相关的话题似乎有点过时,但现如今还是有很多的视频网站采用的是Flash播放器,播放的文件也依然还有很多是FLV格式,而且仅从一个文件格式的角度去了解和分析FLV应该也还说的过去的。

FLV(Flash Video)是Adobe的一个免费开放的音视频格式,babala~~ 省略若干字的介绍,要看,到官网看吧,这里不赘述,我们主要来讨论下FLV文件格式的细节,在此之后,我们会进一步讨论下的加密解密相关内容。

整体上,FLV分为HeaderBody两大块。

Header: 记录FLV的类型,版本,当前文件类型等信息,这些信息可以让我们对当前FLV文件有个概括的了解。

Body: FLV的Body是Flv的数据区域,这些是FLV的具体内容,因为FLV中的内容有多种,并可同时存在,因此,Body也不是一整块的数据,而是由更细分的块来组成,这个细分的块叫Tag。

这就是整个FLV的大概结构,下面我们进入到比特/字节数据的世界,看看FLV的内部世界。

 

HEADER

Flv 文件的Header总共由9个字节组成,他们构成如下:

           ----------------------------------------------
字节序     | 46 | 4c | 56 | 01 | 05 | 00 | 00 | 00 | 09 |
           ----------------------------------------------
字符序       F    L    V    1  /    \                9
                    ---------------------------------
bit序               | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
                    ---------------------------------
                                         1/0     1/0
                                        音频位  视频位

附注说明:字节序表示文件中的存放的字节流顺序,字符序是按照字节序展示出可见的字符数据,bit序此处为第5字节的bit位值.

FLV 文件Header的9字节意义如下:

  1. 第1-3字节:文件标志,FLV的文件标志为固定的“FLV",字节(0x46, 0x4C,0x56),见上面的字节序和字符序两行;
  2. 第4字节:当前文件版本,固定为1(0x01)
  3. 第5字节:此字节当前用到的只有第6,8两个bit位,分别标志当前文件是否存在音频,视频。参见上面bit序,即是第5字节的内容;
  4. 第6-9字节:此4字节共同组成一个无符号32位整数(使用大头序),表示文件从FLV Header开始到Flv Body的字节数,当前版本固定为9(0x00,0x00,0x00,0x09)

Header之后,即是FLV的Body了,Body定义了整个FLV的数据内容,他的结构又是如何的呢,让我们看下:

 

BODY

Body是由Tag Size(4字节)和Tag组成,其结构如下:

-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------

 

Previous Tag Size

这个比较好理解,就是前一个Tag的大小,这里同样存的是无符号32位整型数值。因为第一个Previous Tag Size是紧接着FLV Header的,因此,其值也是固定为0(0x00,0x00,0x00,0x00)。

 

TAG

FLV中的TAG不止一种,当前版本共有3种类型组成:音频(audio),视频(video),脚本数据(script data),这三种类型会在Tag内进行标志区分。其中:Audio Tag是音频数据,Video Tag是视频数据,Script Data存放的是关于FLV视频和音频的一些参数信息(亦称为Metadata Tag),通常该Tag会在FLV File Header后面作为第一个Tag出现,并且一个文件仅有一个Script Data Tag。

为了在Tag内存放不同的数据,并且能够方便区分,每个Tag被定义除了Tag HeaderTag Data两部分,他们的结构如下:

-------------------------
|       Tag Header      |
-------------------------
|       Tag  Data       |
-------------------------



                -------------------------
                |       Tag Header      |
                -------------------------
                 /                    \
--------------------------------------------------------
| 08 | 00 | 00 | 18 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
--------------------------------------------------------

Tag Header由11字节组成:

  1. 第1字节:标志当前Tag的类型,音频(0x08),视频(0x09),Script Data(0x12),除此之外,其他值非法;
  2. 第2-4字节:表示一个无符号24位整型数值,表示当前Tag Data的大小;
  3. 第5-7字节:无符号24位整型数值(UI24),当前Tag的时间戳(单位为ms),第一个Tag的时间戳总为0;
  4. 第8字节:为时间戳的扩展字节,当前24位不够用时,该字节作为最高位,将时间戳扩展为32位无符号整数(UI32)
  5. 第9-11字节:UI24类型,表示Stream ID,总是0

 

Tag Data

Tag Data由Tag Header标志后,就被分成音频,视频,Script Data三类,分别如下:

 

1. Audio Tag Data

Audio Tag Data开始的第一个字节包含了音频数据的参数信息,第二个字节开始为音频流数据:

第1字节:

  • UB[4],前4位标识音频数据的格式,如:0x2表示的是MP3数据,当前合法的数值为0,1,2,3,4,5,6,7,8,9,10,11,14,15(7,8,14,15保留为内部使用)
  • UB[2],第5,6位bit表示采样率(AAC,总是3)
  • UB[1],采样精度, 0为8bits, 1为16bits
  • UB[1],音频类型,mono=0, stereo=1

 

2. Video Tag Data

和音频一样,其第一个字节包含的是视频参数信息,第二字节开始为视频流数据。

第1字节:

  • UB[4],前4位标识帧类型。1: keyframe (for AVC, a seekable frame); 2: inter frame (for AVC, a nonseekable frame); 3: disposable inter frame (H.263 only); 4: generated keyframe (reserved for server use only); 5: video info/command frame
  • UB[4],后4位标识视频编码。1: JPEG (currently unused) ;2: Sorenson H.263; 3: Screen video; 4: On2 VP6; 5: On2 VP6 with alpha channel; 6: Screen video version 2; 7: AVC

第二字节开始的数据有以下规律:

If CodecID == 2
  H263VIDEOPACKET
If CodecID == 3
  SCREENVIDEOPACKET
If CodecID == 4
  VP6FLVVIDEOPACKET
If CodecID == 5
  VP6FLVALPHAVIDEOPACKET
If CodecID == 6
  SCREENV2VIDEOPACKET
if CodecID == 7
  AVCVIDEOPACKET

 

3. Script Tag Data

这个Tag Data里面在未加密的时候,是ScriptTagBody类型,里面的SCRIPTDATA编码为AMF(Action Message Format)。ScriptTagBody由Name和Value两个字段组成(类型均为SCRIPTDATAVALUE)。

----------------------------------
|     Name (SCRIPTDATAVALUE)     |
----------------------------------
|     Value (SCRIPTDATAVALUE)    |
----------------------------------

 SCRIPTDATAVALUE类型比较多,它使用UI8(1个字节)来表示类型,可用的类型如下:

  • 0 = Number
  • 1 = Boolean
  • 2 = String
  • 3 = Object
  • 4 = MovieClip (reserved, not supported)
  • 5 = Null
  • 6 = Undefined
  • 7 = Reference
  • 8 = ECMA array
  • 9 = Object end marker
  • 10 = Strict array
  • 11 = Date
  • 12 = Long string

每种类型有对应的定义,这个比较多,不加详述,可以参看官方文档。

这里需要特别提出的一个是onMetaData,在SCRIPTDATA Tag中,它名称即为onMetaData,它提供一系列可用于AS编程的变量(通过NetStream.onMetaData属性获取)。由于每种软件创建的FLV文件,均可有不同的变量,无法一一列出,但一般包含:FLV的音频,视频还有文件相关的基本信息,如:文件大小,时长,视频宽高等。

onMetaData的一般结构如下:

              -------------------------------------
              |       Name (SCRIPTDATAVALUE)      |
              -------------------------------------
               /                                 \
------------------------------------------------------------------
| 02 | 00 | 0a | 6f | 6e | 4d | 65 | 74 | 61 | 44 | 61 | 74 | 61 |
------------------------------------------------------------------

后面的Value由于每个软件产生的不同,但类型是0x08,ECMA ARRAY。大概结构如下:

----------------------------------
|     ECMAArray Length(UI32)     |
----------------------------------
|     Variables Array[]          |
----------------------------------
|     SCRIPTDATAOBJECTEND        |
----------------------------------

其中Variables Array为SCRIPTDATAOBJECTPROPERTY类型数组,最后用3字节的SCRIPTDATAOBJECTEND结束VALUE值。这个值固定为:0x00,0x00,0x09

 

到此,FLV的基本就讲解完成了,基于这个结构作为基石,我们可以对FLV进行校验,解析数据,当然,还有加密/解密。

 

注意:: FLV文件中,使用的是大头序字节

 

感谢

由于本人对于零基础,对于FLV更是门外汉,特别感谢Merry引荐的Colin让我在Flv文件的研究少走很多弯路(包含Specification文档),并在短时间内出色的完成了工作中对FLV文件的析构任务。

 

参考

1. Adobe Video File Format specification V10

2. Video File Format Spcification V10.1

 

Windows 7下启用对SATA硬盘支持的AHCI模式

MitchellChu 2016-02-18 其他技术
在主板支持AHCI(Serial ATA Advanced Host Controller Interface)模式的情况下,一般为了兼容也会将这个模式切到IDE的模式,在IDE模式下使用SATA硬盘,写问题不大,但读的速度大受影响,建议在Win7下开始对SATA硬盘的支持以获得更好的读性能。 如果Win7没有开启SATA模式的时候,切换到AHCI模式会出现蓝屏,因此,操作顺序应为: 1.Win7上先行切换SATA模式(后附方法) 2.重启到主板切换为SATA的AHCI模式 3.重启后进入Win7,自动安装AHCI相关的驱动 4.重启,正常使用.

用时间换空间的缓存算法

MitchellChu 2016-02-18 其他技术 编程语言

在使用Scrapy爬网站的时候,产生出来的附加产物,因为在爬取的时候,CPU的运行时间紧迫度不高(访问频次太高容易被封禁),借此机会难得来上一下,让自己的内存解放一下。

算法原理:

通过将要缓存的数据用二进制展开,得到的二进制数据映射到缓存字段上,要检验是否已经缓存过,仅需要去查找对应的映射位置即可,如果全部匹配上,则已经缓存。

# 二进制就是个二叉树
# 如下面可以表示出来的数据有0, 1, 2, 3四个(两个树独立)

  0      1
 / \    / \
0   1  0   1

因此对缓存的操作就转化为对二叉树的操作,添加和查找只要在二叉树上找到对应路径的node即可。

 

关键代码:

    def _read_bit(self, data, position):
        return (data >> position) & 0x1

    def _write_bit(self, data, position, value):
        return data | value << position

 

实际使用效果如何呢?

在和默认的set相比较,得出测试结果如下(存取整型,不定长字符串,定长字符串):

Please select test mode:4
Please enter test times:1000
====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.0                                     0.0209999084473
read(s)             0.0                                     0.0149998664856
hits                1000                                    1000
missed              0                                       0
size                32992                                   56
add(s/item)         0.0                                     2.09999084473e-05
read(s/item)        0.0                                     2.09999084473e-05
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set): N/A
read time (bytecache / set): N/A
====================================================================================================
...test fixed length & int data end...

====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.00100016593933                        6.1740000248
read(s)             0.0                                     7.21300005913
hits                999                                     999
missed              0                                       0
size                32992                                   56
add(s/item)         1.00016593933e-06                       0.0061740000248
read(s/item)        0.0                                     0.0061740000248
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set): 6172.97568534
read time (bytecache / set):  N/A
====================================================================================================
...test mutative length & string data end...

====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.0                                     0.513999938965
read(s)             0.0                                     0.421000003815
hits                999                                     999
missed              0                                       0
size                32992                                   56
add(s/item)         0.0                                     0.000513999938965
read(s/item)        0.0                                     0.000513999938965
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set):  N/A
read time (bytecache / set):  N/A
====================================================================================================
...test Fixed length(64) & string data end...

测试下来,内存消耗控制的比较好,一直在56字节,而是用set的内存虽然也不是很大,当相较于ByteCache来说,则大上很多。

ByteCache的方式来缓存,最大的问题是当碰到非常大的随机数据时,消耗时间会比较惊人。如下面这种随机长度的字符串缓存测试结果:

Please select test mode:2
Please enter test times:2000
====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               2000                                    2000
add(s)              0.00400018692017                        31.3759999275
read(s)             0.0                                     44.251999855
hits                1999                                    1999
missed              0                                       0
size                131296                                  56
add(s/item)         2.00009346008e-06                       0.0156879999638
read(s/item)        0.0                                     0.0156879999638
====================================================================================================
size (set / bytecache): 2344.57142857
add time (bytecache / set): 7843.63344856
read time (bytecache / set):  N/A
====================================================================================================
...test mutative length & string data end...

 在2000个数据中,添加消耗31s,查找消耗44s,而set接近于0,单条数据也需要16ms(均值)才能完成读/写操作。

 

不过,正如开头说的,在紧迫度不是很高的Scrapy中,这个时间并不会太过于窘迫,更何况在Scrapy中,一般是用来缓存哈希后的数据,这些数据的一个重要特性是定长,定长在本缓存算法中还是表现不错的,在64位长度的时候,均值才0.5ms。而与此同时倒是能在大量缓存的时候,释放出比较客观的内存。

如果有更好的能让速度在上新台阶,也是无比期待的。。。

 

总结:

1. 此方法的目标是用时间换取空间,切勿在时间紧迫度高的地方使用

2. 非常适用于大量定长,且数据本身比较小的情况下使用

3. 接2,非常不建议在大量不定长的数据,而且数据本身比较大的情况下使用

Python在Console下显示文本进度条的方法

MitchellChu 2016-02-13 其他技术

在用处理耗时的任务时,往往希望能够了解到任务当前的处理进度,这个时候需要在任务中不断打印出任务的进度信息。一般我们是这样的:

def process_mission():
    """ 任务处理方法 """
    # 这里是任务处理过程
    print('当前处理到第[%d]项' % count)
    # 这里是任务处理过程 

 这种方式会在输出一堆类似下面这样的信息:

当前处理到第1项
当前处理到第2项
当前处理到第3项
当前处理到第4项
当前处理到第5项
....

这种信息有可能非常非常的长,也有可能输出的时候非常非常的快,以至于根本就无法看清楚(输出非常快的时候)。

这时候,我们期待这样的功能:

[==================             ] 25.60%

但如果纯粹的使用print是无法达到效果的,会变成和前面一样,满屏都是这种杠杠。

如何正确显示呢?

Python提供了一个模块,叫:progressbar,当使用这个模块之后,你要显示进度的仅需如下:

import progressbar

# 先定义一个进度条
# http://blog.useasp.net/

pbar = progressbar.ProgressBar(maxval=100, \
    widgets=[progressbar.Bar('=', '[', ']'), ' ', \
    progressbar.Percentage()])
for i in xrange(100):
    # 更新进度条
    pbar.update(i+1)

pbar.finish()
# Ok,到此完结。

 是不是很容易就完成进度条的显示?不过遗憾的是,这个python模块并不是默认的,而是需要安装:

pip install progressbar

如果没有安装pip可以参考这里 。

对于只要一个简单进度条的人来说,安装个python包似乎有点动静太大,那么,自己动手写一个便是了。

下面是Mitchell自己写的一个类似的进度条,不用安装包,简单易用。

# 在使用本方法之前,请先做如下import
# from __future__ import division
# import math
# import sys
# ##blog.useasp.net##

def progressbar(cur, total):
    percent = '{:.2%}'.format(cur / total)
    sys.stdout.write('\r')
    sys.stdout.write("[%-50s] %s" % (
                            '=' * int(math.floor(cur * 50 / total)),
                            percent))
    sys.stdout.flush()

说明:

cur, total:一个是当前值,一个是总值。cur在任务的处理过程中会不断向total靠近,直到两者相等,任务结束

需要注意的是,这两个可能为整型,因此要引入division

在使用的时候,只需要调用即可在控制台输出进度条。

# 调用方式举例
progressbar(2, 100)
progressbar(3.9, 10)
progressbar(3283, 27379)

 到此,我们完成了一个会在控制台动态更新的进度条。

关于博主

  一枚成分复杂的网络IT分子,属于互联网行业分类中的杂牌军。