http://blog.csdn.net/hjie8627/article/details/4464154
近来研究了一下MKV格式,作为初步了解,看了下matroska论坛上的技术文档,主要对其进行了大致的翻译,加了些自己的理解,希望能对和我一样刚了解MKV的朋友们有些帮助,时间有限,不免出错,还望海涵!
----Jeoy.H 2009.08.19
看这个之前大家要先去了解什么是RFC,什么是BNF,什么是DTD,如果和我一样什么都不知道的话就去问万能的GOOGLE吧!这里不再赘述!
参考文档来源http://www.matroska.org/technical/specs/rfc/index.html
Draft M. Nilsson
Document: ebml-1.0.txt 15th March 2004
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使用大端格式的字节顺序,高位在前,上面所有的表示都要字节对齐。
2.1不定长整数
ID和SIZE都使用不定长整数表示法,用n个0开头(n可以是0)表示数据的字节数,字节数为n+1,之后用一个1做间隔标志,1后面跟的是实际的整数值,该整数值由队列数据和尾数据组成,尾数据的字节数为n。
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字节。其他最大值可在EBML 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 value, 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 /NAME
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( “+”/“-”) 1*DIGIT )
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必须是Envelope的子元素,且如果文件中存在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;”所在的层,如果用前面第一个信和信封的例子(Letter-Envelope)来说的话,即SHA1是SHA1Content的父元素,而当Envelope是SHA1的父元素时,Letter元素可能成为SHA1Content的子元素。一个具有层属性的容器元素禁止(MUST NOT)使用2.3节介绍的未知SIZE,因为这样在任何情况下都无法确定元素的结束。
SHA1 := 190babe5 container [ level : 1..; card : *; ] {
SHA1Content := 20110f container {
%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 / DATE_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_DEF )
字符串和二进制的范围限制了字符串中每个字符/字节的范围。注意在字符串的情况下,这是针对未编码的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风格(LCOMMENT)和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)EBMLVersion元素也在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 (“{“ *DELEMENT”}”)/“;”
5. EBML标准元素
EBML定义了一小部分可以用于任何EBML应用的元素。一个EBML文档必须(MUST)以一个EBML header作为开始,其由EBML元素组成。一般来说,为了某个应用,将可能在文档类型定义中对EBML元素中的所有元素定义默认值,这样能够表示出整个header而不需要写任何一个字节。然而,为了能够在不同的应用中识别EBML文档,需要(REQUIRED)文档中其值与标准的默认值不同的EBML元素,被写进EBML data。实际中这意味着至少DocType总是被存储在所有EBML文档中。
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个字节。EBMLMaxIDWidth应该大于文档中使用的任何实际宽度。
EBMLMaxIDWidth := 42f2 uint [ def:4 ; parent :EBML; ]
5.1.4. EBMLMaxSizeWidth
文档中使用的SIZE的最大宽度。建议(RECOMMENDED)宽度不要超过8字节。EBMLMaxSizeWidth应该大于文档中使用的任何实际宽度。
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.
<url:http://www.unicode.org>
[UTF-8] F. Yergeau, 'UTF-8, a transformation format of ISO 10646',
RFC 3629, November 2003.