文章

Mkv

MKV格式(matroska/EBML)的初步研究 收藏

1
  近来研究了一下MKV格式,作为初步了解,看了下matroska论坛上的技术文档,主要对

其进行了大致的翻译,加了些自己的理解,希望能对和我一样刚了解MKV的朋友们有些帮助,

时间有限,不免出错,还望海涵!

1
                        ----Jeoy.H    2009.08.19

看这个之前大家要先去了解什么是RFC,什么是BNF,什么是DTD,如果和我一样什么都不知道

的话就去问万能的GOOGLE吧!这里不再赘述!

参考文档来源http://www.matroska.org/technical/specs/rfc/index.html

RFC v1.0

Draft M. Nilsson

Document: ebml-1.0.txt 15th March 2004

1
2
3
4
5
              EBML----Extensible Binary Markup Language



   1.简介

EBML格式,它是一种二进制语言,输入数据被分层存储,数据的结构紧凑而又容易被解析。

EBML是专门为Matroska视频容器格式(MKV)设计的框架语言。

2.结构

结构上来说,一个EBML文档是各个EBML元素的列表。每个元素有三个部分:ID,SIZE,DATA。

其中,ID和SIZE的表示方法,采用的是UTF-8编码那样的不定长前缀表示法。而DATA采用普通

的方法存储,但DATA既可以是该元素的值,也可以是一系列其他元素的列表。

EBML = *ELEMENT

ELEMENT = ELEMENT_ID SIZE DATA

DATA = VALUE / *ELEMENT

ELEMENT_ID = VINT

SIZE = VINT

上面的格式使用的是BNF规范(见RFC2234,具体可查http://www.ietf.org/rfc/rfc2234.txt,

简单的说下这里“/”表示“或”,“*”表示“可重复”),其中VINT表示“不定长整数”。

EBML使用大端格式的字节顺序,高位在前,上面所有的表示都要字节对齐。

1
2
3
4
5
   2.1不定长整数



   ID和SIZE都使用不定长整数表示法,用n个0开头(n可以是0)表示数据的字节数,字

节数为n+1,之后用一个1做间隔标志,1后面跟的是实际的整数值,该整数值由队列数据和尾

数据组成,尾数据的字节数为n。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   VINT              =VINT_WIDTH  VINT_MARKER  VINT_DATA



   VINT_WIDTH       =*%b0



   VINT_MARKER     =%b1



   VINT_DATA        =VINT_ALIGNMENT  VINT_TAIL



   VINT_ALIGNMENT  =*BIT



   VINT_TAIL          =*BYTE

或者这样表示:

VINT=( %b0 VINT 7 BIT ) / ( %b1 7 BIT )

所以它的整数是这样存储的:

Width Size Representation

1 2^7 1xxx xxxx

2 2^14 01xx xxxx xxxx xxxx

3 2^21 001x xxxx xxxx xxxx xxxx xxxx

4 2^28 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx

2.2元素ID

EBML元素的ID被编码成一个不定长整数,数据宽度的最大值默认为4字节。其他最大宽度值可

在EBML header的EBMLMaxIDWidth中设置。具体见5.1。ID总是被编码成最短的表示方式,例

如1总是被编码成0x81,而不会用0x4001。而且,所有的数据位的值全为1和全为0的数被保留

(不使用),例如127的最短编码是0x407f,因为0x7f被保留(本来应该用0xff)。

Class Width Number of Ids

A 1 2^7-2 = 126

B 2 2^14-2^7 = 16 256

C 3 2^21-2^14 = 2 080 768

D 4 2^28-2^21 = 266 338 304

2.3元素数据SIZE

EBML元素数据SIZE被编码成不定长整数,其最大数据宽度默认值为8字节。其他最大值可在E

BML header的EBMLMaxSizeWidth中设置。具体见5.1。在语意上,1被编码成1个字节和被编码

成8个字节是一样的,这允许在缩小元素数据长度的时候不需要同时缩小SIZE的宽度。

所有数据位的值为1时表示SIZE未知,这时候允许动态产生EBML流,而不用事先知道最终的数

据大小。SIZE未知的元素必须(MUST,见RFC2119)是以元素列表作为DATA的。该元素列表的

结束由元素的ID决定,当到来一个元素不是该未知SIZE元素的子元素时,那么该元素列表已

经结束。

2.4.元素的值(DATA)

除了以一个元素列表作为DATA之外,元素的DATA还可以是7种预定义的数据类型之一。实际的

类型信息不是存储在EBML中,而是通过元素ID的文件类型定义推断出来的。被定义的数据类

型为:有符号整数,无符号整数,单精度浮点型,ASCII字符串,UTF-8字符串,日期,二进

制。

VALUE = INT / UINT / FLOAT / STRING / DATE / BINARY

INT = *8 BYTE

有符号整数,represented in two’s complement notation,sizes from 0-8 bytes. 零字

节表示值为0的整数。

UINT = *8 BYTE

无符号整数,大小0-8字节。零字节表示值为0的整数。

FLOAT = *1 ( 4 BYTE / 8 BYTE / 10 BYTE )

IEEE float ,大小0,4,8或10字节。零字节表示该float值为0.0。

PADDING = %x00

STRING =- *BYTE *PADDING

UTF-8编码的Unicode字符串。字符串结尾可以(MAY)用0填充。注意一个字符串的长度可以

是0.

DATE = 8 BYTE

有符号的,64位(8字节)整数,以纳秒表示距离新千年开始的时间(2001-01-01 00:00:

00 UTC)

BINARY = *BYTE

二进制数据,即没有在EBML层中解释的数据。

3.语义解释

每个元素都有几个在文档类型定义中定义的属性,它们需要正确地按照结构和语义上进行信

息处理。这些属性是name, parent, ID, cardinality, value restrictions, default val

ue, value type , child order .

在结构上解析EBML数据,我们需要知道元素值的类型,在语义上解释数据我们还需要知道元

素的ID和元素的name .元素可以有一个默认值,这样,当解析EBML数据元素时,对于那些没

有被存入数据的元素最终也会被表示出来。按照可能有的父元素最终这些元素可能会有一些

开放限制,例如它们在EBML数据中出现的次数 ,在文件中的顺序,以及各种其他的对于它们的

DATA的限制.

3.1. Name 属性

Name是一个元素的标识符,而且与元素ID有一一对应的关系。只有字母、数字和下划线可以

用于Name,且不可以以数字开头,不区分大小写。

NAME = [A-Za-z_] 1 *[A-Za-z_0-9]

3.2. 值的类型属性(Value type property)

由于只通过EBML数据所给的信息无法得知是否要去寻找子元素,因此在EBML的DTD(文档类型

定义)中元素值的类型是最重要的信息。在2.4节所定义的数据类型之外,一个元素还可以作

为“container”类型,这表示这个元素的内容只是更多的元素。

3.3. ID属性

每个元素必须有一个相应的ID,ID在2.2节中有定义的。这些ID的编码使用十六进制的方式,

例如1a45dfa3.

ID = 1 * ( 2 HEXDIG )

3.4.默认值属性

每个非容器的元素可以被分配一个默认值。这样的话,如果元素的DATA中不存在其他值,那

么它的值会被加进EBML数据的解释中。

例如,考虑这样一个EBML DTD:

Weight :=4101 {

WeightValue := 41a1 uint;

WeightUnit := 41a2 string [ def : ”kilogram” ];

}

如果Weight元素只包含WeightValue元素,当对信息进行语义处理的时候,值为”kilogram”

的WeightUnit元素会被自动添加。当一个WeightUnit元素有其他的值时,默认值自动被放弃

默认值也可以是一个前面已经出现过的符号。但是如果这个符号没有出现过,即它还没有被

编码进EBML数据,且没有默认值,那么这个元素将不会在语义层作为子元素被自动添加。

例如:

Weight := 4101 {

WeightValue := 41a1 uint;

WeightUnit := 41a2 string [ def:WeightUnit ];

}

在这个例子中,所有的Weight元素将使用与前一个WeightUnit元素同样的weight unit值。为

了确定第一个出现的元素有值,它的基数(cardinality)应该被设置为“1”,见3.6节。

DEFAULT = INT_DEF / UINT_DEF / FLOAT_DEF / STRING_DEF / DATE_DEF / BINARY_DEF /N

AME

DATE_VALUE = *1DIGIT 2DIGIT 2DIGIT *1 ( %x54 2DIGIT “:” 2DIGIT “:” 2DIGIT *

1(“.” *1DIGIT ) )

INT_DEF = *1“-” 1 *DIGIT

UINT_DEF = 1 *DIGIT

FLOAT_DEF = INT “.”1 DIGIT *1 (“e” *1( “+”/“-”) 1DIGIT )

DATE_DEF = INT_DEF / DATE_VALUE

STRING_DEF = (“0x” 1*( 2HEXDIG )) / ( %x22 *( %x20-7e ) %x22 )

BINARY_DEF = STRING_DEF

其中,日期的默认值既可以用整数方式编码,也可以用IS0短格式。YYYYMMDD后跟字符T,时

间表示为HH:MM:DD,最后可以使用分数,选择合适的数字精度F表示成.F。例如:

ExampleInt := c0 int [ def:-114; ]

ExampleUInt := c1 uint [ def:0; ]

ExampleFloat := c2 float [ def:6.022E23 ]

ExampleDate := c3 date [ def:20011224T15:00:03.21; ]

ExampleBinary := c5 binary [ def:0x4944337632; ]

ExampleString := c6 string [ def:“Sweden”; ]

3.5. 父属性

在层次结构中放置元素,我们需要元素的相关信息。在EBML DTD中这由元素可能有的父元素

表示。这可以表示成两种方法:可允许的父元素的详细列表,或可允许的嵌入深度的一般定

义。

PARENTS = NAME / ( NAME “,” PARENTS )

LEVEL = 1 *DIGIT *1( “..” *DIGIT )

一个元素如果既没有父元素,也没有层定义,那么将被认为其位于EBML文件的顶层。一个元

素不能既有一个父元素的同时也有一个层属性。

例如:Envelope := a0 container;

Letter := b0 string [ parent : Envelope;]

上例中包含两个元素,Envelope 和Letter 。如果文件中存在Letter,那么Letter必须是En

velope的子元素,且如果文件中存在Envelope,那么Envelope必须处于顶层。

上面的例子也可以使用更简略的语法,写成:

Envelope := a0 container {

Letter := b0 string ;

}

下面的例子表明简略语法无法表示所有的关系。这里Letter元素可以既是Envelope元素的子

元素,也是Trashcan元素的子元素。在简略语法的表达中,具体的父信息被合并成一个。

Envelope := a0 container {

Letter := b0 string [ parent:Transhcan; ];

}

Trashcan := a1 container;

下面的例子展示的是用层次来代替父元素的用法。其Void元素可以在EBML文件的任何位置出

现。

Void := ec binary [ level : 1..; card : * ; ]

下面是一个和上面相似的例子,不同的是使用了容器类型。其中,SHA1父元素的子元素可能

(may,RFC2119)被压进“%children;”所在的层,如果用前面第一个信和信封的例子(Let

ter-Envelope)来说的话,即SHA1是SHA1Content的父元素,而当Envelope是SHA1的父元素时

,Letter元素可能成为SHA1Content的子元素。一个具有层属性的容器元素禁止(MUST NOT)

使用2.3节介绍的未知SIZE,因为这样在任何情况下都无法确定元素的结束。

SHA1 := 190babe5 container [ level : 1..; card : *; ] {

SHA1Content := 20110f container {

1
 %children;

}

SHA1Hash := 20110e binary;

}

3.6. 基数属性

一个元素的基数声明一个元素在当前范围可以出现的次数。根据默认值,一个元素在一个范

围里面最多只可以出现一次,例如,如果元素Weight已经被定义为Brick的子元素,那么在每

个Brick元素中只可以使用不超过一个Weight元素。但是基数值可以进行选择,选择默认值或

下面的任何一个值。注意这将对元素可以被插入的所有范围产生影响。

Symbol Interpretation

  • Zero or more occurrences.

? Zero or one occurrence (default).

1 Exactly one occurrence.

  • One or more occurrences.

CARDINALITY = “*”/“?”/“1”/“+”

3.7. 子元素顺序属性

子元素顺序属性只应用于容器元素。它只是声明元素的子元素是否必须按照被定义的顺序出

现。默认情况下,这个限制作用于所有元素的所有子元素。根据默认值来按顺序排列元素的

好处是,一旦一个元素被跳过,EBML解码器将立即知道,并能够改为输出合适的默认值。

YES = “yes” / “1”

NO = “no” / “0”

ORDERED = YES / NO

3.8. 值的限制属性

每个元素可以对它的值施加额外的限制。这些限制只是用来在编码的时候确认数据,并且使

编码的数据在解码或解析的时候保持一致。不同的元素类型有不同的限制,且语法不同。

3.8.1. 范围

一个元素值的范围决定元素的值允许的大小。该范围被表示为一个或多个特定的值,或允许

值的跨度,该跨度可以使用浮点和日期之外的所有类型的数据表示。

RANGE_LIST = RANGE_ITEM / ( RANGE_ITEM S “,” S RANGE_LIST )

RANGE_ITEM = INT_RANGE / UINT_RANGE / FLOAT_RANGE / STRING_RANGE / DAT

E_RANGE / BINARY_RANGE

对于整数来说,值的范围是一个若干个特定值组成的开放的或封闭的列表,例如“0,1”,

“1..5”,“..-1,1..”。最终的允许范围是所有范围列表的集合。如果一个值符合任何一个

列表,那么认为这个值有效。

INT_RANGE = INT_DEF / ( INT_DEF “..” ) / ( “..” INT_DEF ) / ( INT_DEF “

..” INT_DEF )

无符号整数也类似,但其范围不能向左开放。

UINT_RANGE = UINT_DEF *1( “..” UINT_DEF )

单精度浮点类型,既可以有兼容的范围终点,也可以有不兼容的范围终点。

FLOAT_RANGE = ( ( “<”/ “<=” / “>”/ “>=” ) FLOAT_DEF ) /

( FLOAT_DEF “<”/ “<=”“..”“<”/“<=” FLOAT_DEF )

日期类型的范围与整型的范围有相同的句法和语义,不同的只是实际的日期既可以用整型表

示也可以用IS0短格式表示。

DATE_RANGE = ( DATE_DEF “..”) / ( “..” DATE_DEF ) / ( DATE_DEF “..” DATE_D

EF )

字符串和二进制的范围限制了字符串中每个字符/字节的范围。注意在字符串的情况下,这是

针对未编码的UNICODE数据的,即可能的范围大于二进制的边界范围0-255。

STRING_RANGE = UINT_RANGE

BINARY_RANGE = UINT_RANGE

3.8.2. 大小(Size)

一个元素值的大小只是它在其编码格式中占的字节数。这意味着一个字符型的元素值的大小

不需要和字符的长度一样。

SIZE_LIST = UINT_RANGE / ( UINT_RANGE S “,” S SIZE_LIST )

4.文档类型定义

EBML 文档类型定义,EDTD,是一种基于ASCII的语言,它允许在第3章中描述的系统参数和关

系以一种人类和计算机都可读的方式进行描述。 语法上它由块组成,不像大多数编程语言那

样包含类似BNF那样的定义和声明。格式上不区分空白,不区分大小写,支持C风格(LCOMME

NT)和C++风格(BCOMMENT)的comments。为了增加可读性,本章列出的BNF相对于附录B中的

完全的BNF是有点简化的。

COMMENT = LCOMMENT / BCOMMENT

S = *WSP / ( *WSP COMMENT *WSP );Optional white spaces

在EDTD的顶层,目前只有三个不同的块被定义,头声明,类型定义和元素定义。

DTD = *( S / HBLOCK / TBLOCK / EBLOCK )

4.1. 头声明

头声明的意思是声明什么值应该被加入元素的头,这些值应该和默认值不同。头声明块被写

成“declare header”加大括号,括入块声明。实际的声明非常直接,元素名后加“:=”再

加元素值,3.4节中有说明,最后再跟“;”。在一个DTD中必须只有一个头声明块。

HBLOCK = “declare” WSP “header” S “{“ *( S / STATEMENT ) ”}”

STATEMENT = NAME S “:=” S DEFS S “;”

因为DocType元素没有默认值,所有它必须在EDTD中被声明。此外,建议(RECOMMENDED)EB

MLVersion元素也在EDTD中声明。声明可以像这样:

Declare header {

DocType := “xhtml”;

EBMLVersion := 1 ;

}

4.2.类型定义

类型定义是一种创造更容易记忆的类型名的方法,使得DTD更小且更易读。类型定义块被写成

“define types”加大括号括入声明块。每个声明是一个类型名,后面加“:=”再加类型,

也可以选择加属性列表,用尖括号括起来。类型名和元素名遵循相同的规则,但是处于不同

的名称空间,即可以有元素名和类型名一样。

TBLOCK = “define” S WSP “types” S “{“ *(S / DTYPE)”}”

DTYPE = NAME S “:=” S TYPE S (PROPERTIES S *1 “;”)/ “;”

基本类型可以是另外定义的类型,需要的只是按顺序定义,如下面的例子:

Define types {

Digits := int;

Number := digits;

}

类型定义既允许2.4节描述的类型,也可以是在文件中前面已经定义了的NAME。

TYPE = VTYPE / CTYPE

VTYPE = “int” / “uint”/ “float” / “string” / “date”/ “binary” / NAME

CTYPE = “conainer” / NAME

如果类型定义没有属性列表,声明以“;”作为结束。如果有属性列表,那么以“;”作为

结束是可选择的。

PROPERTIES = “[“S 1*PROPERTY S ”]”

PROPERTY = PROP_NAME S “:”S PROP_VALUE S “;”

一些例子:

crc32 := binary [ size:4; ]

sha1 := binary [ size:20; ]

bool := uint [ range:0..1; ]

us_printable := binary [ range:32..126; ]

4.3. 元素定义

元素定义是DTD的真正目的。元素定义块被写成“define elements”后面跟大括号里面是声

明块。每个声明既可以是类似类型定义声明那样的单个的声明,也可以是包含更多声明的一

个声明块。

EBLOCK = “define” WSP “elements” S “{“ *(S / ELEMENT )”}”

DELEMENT = VELEMENT / CELEMENT / “%children;”

单个的声明通常用于值元素,且由一个名称后面跟“:=”,id,type,以及可选择的属性组

成。

VELEMENT = NAME S “:=” S ID WSP S TYPE S (PROPERTIES S *1“;”)/“;”

元素声明块的版本只用来表示父子关系。见3.5

CELEMENT = NAME S “:=” S ID WSP S “container” S *1PROPERTIES S (“{“ *DELE

MENT”}”)/“;”

  1. EBML标准元素

EBML定义了一小部分可以用于任何EBML应用的元素。一个EBML文档必须(MUST)以一个EBML

header作为开始,其由EBML元素组成。一般来说,为了某个应用,将可能在文档类型定义中

对EBML元素中的所有元素定义默认值,这样能够表示出整个header而不需要写任何一个字节

。然而,为了能够在不同的应用中识别EBML文档,需要(REQUIRED)文档中其值与标准的默

认值不同的EBML元素,被写进EBML data。实际中这意味着至少DocType总是被存储在所有EB

ML文档中。

5.1. EBML

EBML元素是EBML header的一个容器。

EBML := 1a45dfa3 container [ card:+; ]

5.1.1. EBMLVersion

EBMLVersion是文档符合的EBML版本。

EBMLVersion := 4286 uint [ def:1; parent:EBML; ]

5.1.2. EBMLReadVersion

解析器支持的用来读文档的最低的EBML 版本。

EBMLReadVersion := 42f7 uint [ def:1; parent:EBML; ]

5.1.3. EBMLMaxIDWidth

该文档中使用的ID的最大的宽度。建议(RECOMMENDED)不要使ID的宽度大于4个字节。EBML

MaxIDWidth应该大于文档中使用的任何实际宽度。

EBMLMaxIDWidth := 42f2 uint [ def:4 ; parent :EBML; ]

5.1.4. EBMLMaxSizeWidth

文档中使用的SIZE的最大宽度。建议(RECOMMENDED)宽度不要超过8字节。EBMLMaxSizeWid

th应该大于文档中使用的任何实际宽度。

EBMLMaxSizeWidth := 42f3 uint [ def:8; parent:EBML; ]

5.1.5. DocType

一个标识文档类型的ASCII字符串。

DocType := 4282 binary [ range:32..126; parent:EBML; ]

5.1.6. DocTypeVersion

DocTypeVersion是文档符合的文档类型版本。

DocTypeVersion := 4287 uint [ def:1; parent:EBML; ]

5.1.7. DocTypeReadVersion

读文档时,解释器(interpreter)支持的最低DocType版本。

DocTypeReadVersion := 4285 uint [ def:1; parent:EBML; ]

5.2. CRC32

CRC32容器能被放置在任何EBML元素的附近。储存在CRC32Value中的值是其他子元素上执行的

CRC-32检查值的结果。

CRC32 := c3 container [ level:1..; card:*; ]{

%children;

CRC32Value := 42fe binary [ size:4; ]

}

5.3. Void

Void元素能被用于为更多的数据做准备的填充,或者当数据被删除后用来填充空间的。当解

析文档的时候只需要忽略掉这种元素。

Void := ec binary [ level:1..; card:*; ]

6.参考文献

[ABNF] D. Crocker, P.Overell, ‘Augmented BNF for Syntax Specifications: ABNF’,

RFC 2234, November 1997.

url:ftp://ftp.isi.edu/in-notes/rfc2234.txt

[CRC32] International Organization for Standardization, ‘ISO

Information Processing Systems - Data Communication High-Level Data

Link Control Procedure - Frame Structure”, IS 3309, October 1984,

3rd Edition.

[FLOAT] Institute of Electrical and Electronics Engineers, ‘IEEE

Standard for Binary Floating-Point Arithmetic’, ANSI/IEEE Standard

754-1985, August 1985.

[RFC2119] S. Bradner, ‘Key words for use in RFCs to Indicate

Requirement Levels’, RFC 2119, March 1997.

[UNICODE] International Organization for Standardization,

‘Universal Multiple-Octet Coded Character Set (UCS), Part 1:

Architecture and Basic Multilingual Plane’, ISO/IEC 10646-1:1993.

1
  <url:http://www.unicode.org>

[UTF-8] F. Yergeau, ‘UTF-8, a transformation format of ISO 10646’,

RFC 3629, November 2003.

1
  url:ftp://ftp.isi.edu/in-notes/rfc3629.txt

7.附录 见英文文档

本文由作者按照 CC BY 4.0 进行授权