整理:Pinocchio
PS的CD由一个数据轨和后续若干个音轨组成。例:
| 块 | 描述 | 额外信息 |
| 000000-000015 | 数据轨引导块 | 共16个扇区 |
| 000016-024520 | 数据轨程序区 | Mode2 Form1或2 |
| 024521-024670 | 数据轨后空隙 | 最少150扇区 |
| 024671-024820 | 第1音轨前间隙 | 最少150扇区 |
| 024821-048326 | 音轨#1 | |
| 048327-048476 | 第2音轨前间隙 | 0或150扇区 |
| 048477-072485 | 音轨#2 | |
| . . . . . . | ||
| 191281-191430 | 第9音轨前间隙 | |
| 191431-214349 | 音轨9 | |
| 214350-?????? | 导出轨 | 由刻录机写入,不可读 |
CD-ROM盘上可以存放信息的区域称为卷空间(volume space)。卷空间分成两个区:
从LSN 0到LSN 15称为系统区。从LSN 16开始到最后一个逻辑扇区称为数据区,
它用来记录卷描述符(volume descriptors)、文件目录、路径表、文件数据等内容。
前16个扇区是系统区,有的raw镜像在最前面还包含有光盘导入轨,是一片连续的空白扇区,忽略它。
PS检查轨道1的头5个扇区来判断国家区域。共有3种区域码。JAP、EUR、USA。相关文件请在文档页查看。
每卷数据区的开头(LSN 16)是卷描述符。卷描述符实际上是一种数据结构,或者说是一种描述表。
其中的内容用来说明整个CD-ROM盘的结构、提供许多非常重要的信息,如盘上的逻辑组织、根目录地址、
路径表的地址和大小、逻辑块的大小等等。卷描述符的结构如下表,它是一个由2048字节组成的固定长度记录。
| 字节位置 | 录域的名称 |
| 1 | 卷描述符的类型 |
| 2~6 | 标准卷标识符(用CD001表示) |
| 7 | 卷描述符的版本号 |
| 8~2048 | (取决于卷描述符的类型) |
卷描述符有五种类型:
辅助卷描述符(supplementary volume descriptor)
| 字节位置 | 主卷描述符记录域的名称 | 辅助卷描述符记录域的名称 |
| 1 | 卷描述符的类型 | 同左 |
| 2~6 | 标准卷标识符(CD001) | 同左 |
| 7 | 卷描述符版本号 | 同左 |
| 8 | 未使用(00) | 卷标志 |
| 9~40 | 系统标识符 | 同左 |
| 41~72 | 卷标识符 | 同左 |
| 73~80 | 未使用(00) | 同左 |
| 81~88 | 卷空间大小 | 同左 |
| 89~120 | 未使用(00) | 换码顺序 |
| 121~124 | 卷系列大小 | 同左 |
| 125~128 | 卷顺序号 | 同左 |
| 129~132 | 逻辑块大小 | 同左 |
| 133~140 | 路径表大小 | 同左 |
| 141~144L | 型路径表值位置,L型: 最低有效字节在先 | 同左 |
| 145~148L | 型路径表任选值位置 | 同左 |
| 149~152M | 型路径表值位置,最高有效字节在先 | 同左 |
| 153~156M | 型路径表任选值位置 | 同左 |
| 157~190 | 根目录的目录记录 | 同左 |
| 191~318 | 卷集标识符 | 同左 |
| 319~446 | 出版商标识符 | 同左 |
| 447~574 | 数据准备者标识符 | 同左 |
| 575~702 | 应用软件标识符(如CD-I) | 同左 |
| 703~739 | 版权文件标识符 | 同左 |
| 740~776 | 文摘标识符 | 同左 |
| 777~813 | 文献目录文件标识符 | 同左 |
| 814~830 | 卷创作日期和时间 | 同左 |
| 831~847 | 卷修改日期和时间 | 同左 |
| 848~864 | 卷到期日期和时间 | 同左 |
| 865~881 | 卷有效日期和时间 | 同左 |
| 882 | 文件结构版本号 | 同左 |
| 883 | (保留) | 同左 |
| 884~1395 | 应用程序使用 | 同左 |
| 1396~2048 | (保留) | 同左 |
| 字节位置 | 记录域的名称 |
| 1 | 卷描述符的类型 |
| 2~6 | 标准卷标识符(CD001) |
| 7 | 卷描述符版本号 |
| 8 | 未使用(00) |
| 9~40 | 系统标识符 |
| 41~72 | 卷分割标识符 |
| 73~80 | 卷分割位置 |
| 81~88 | 卷分块大小 |
| 89~2048 | 系统使用 |
| 字节位置 | 记录域的名称 |
| 1 | 卷描述符的类型 |
| 2~6 | 标准卷标识符(CD001) |
| 7 | 卷描述符版本号 |
| 8~39 | 引导系统标识符 |
| 40~71 | 引导标识符 |
| 72~2048 | 引导系统使用 |
| 字节位置 | 记录域的名称 |
| 1 | 卷描述符的类型 |
| 2~6 | 标准卷标识符(CD001) |
| 7 | 卷描述符版本号 |
| 8~2048 | 保留(00) |
五种描述符的前四种可以任意组合,组成卷描述符系列。这四个描述符可以在描述符系列中出现不只一次。
描述符系列有两个限制:主卷描述符至少要出现一次,卷描述符系列终止符只能出现一次,而且只能出现在最后。
卷描述符系列记录在从LSN 16开始的连续逻辑扇区上。
CD-ROM采用隐式分层目录结构,把目录当作文件看待,并且把整个目录包含在1个或少数几个文件中。
包含目录的文件称为目录文件。目录文件与普通的用户文件相类似,但对CD-ROM采用的目录文件结构
作了具体的规定。目录文件由一系列可变长度的目录记录组成。每个目录记录的格式如下表所示。可
以看到,一个目录记录包含有许多记录域。这些域中记录有文件标识符,以字节计算的文件长度、文
件域中的第一个逻辑块号(LBN),以及打开和使用这个文件所需要的其他信息。当一个文件放在多个
文件域中时,需要设置多个目录记录,每个目录记录中给出相应文件域的地址,并由文件标志记录域
来指明该文件域是不是最后一个。
| 字节位置 | 记录域的名称 |
| 1 | 目录记录长度(LEN_DR) |
| 2 | 扩展属性记录(XAR)长度 |
| 3~10 | 文件域地址 |
| 11~18 | 数据长度 |
| 19~25 | 日期和时间 |
| 26 | 文件标志 |
| 27 | 文件单元大小 |
| 28 | 交叉间隔大小 |
| 29~32 | 卷顺序号 |
| 33 | 文件标识符长度(LEN_FI) |
| 34-(33+LEN_FI) | 文件标识符 |
| 34+LEN_FI | 填充域 |
| (34+LEN_FI+1)-LEN_DR | 系统使用(保留) |
文件的附加信息可以记录在一个命名为扩展属性记录(extended attribute record,XAR)的记录上,
它放在文件的前面而不是放在目录记录上,这样做可以使目录记录变得较小。附加信息包括文件作者、
文件修改日期、访问文件的许可权等信息。凡是不常使用的信息都放到扩展属性记录上。这也是
CD-ROM目录结构的一个特点。如果一个文件有多个文件域,每个文件域都有XAR记录,在这些XAR记录
上的信息可能会不相同,文件系统应认为最后一个XAR记录上的信息是有效的。这个特性在卷集制作
过程中很有用。由于每个目录记录的长度不确定,因此在一个逻辑扇区中的目录记录的个数也不确定,
但必须要保证目录记录数的数目为整数。当一个目录在这个逻辑扇区中放不下的时候,应移到后面的
一个逻辑扇区。这样可以保证读到计算机内存中的目录不会出现支离破碎的现象。
路径表由许多称为路径表记录组成,它对应于根目录和每个子目录。每个路径表记录具有下表的格式。
路径表中包含有每一个子目录所在的开始地址,即逻辑块号LBN,这样就可以通过路径表直接访问任何
一个子目录。因此,如果一张完整的路径表能保存在计算机的RAM中,那末一次寻找就可以访问盘上的
任何一个子目录。
| 字节位置 | 记录域的名称 |
| 1 | 目录标识符的长度(LEN_DI) |
| 2 | 扩展属性记录(XAR)的长度 |
| 3~6 | 存放目录的地址 |
| 7~8 | 父目录号 |
| 9~(8+LEN_DI) | 目标标识符(不超过31个字符) |
| (9+LEN_DI) | 填充域 |
路径表只能保证访问目录的第一个物理扇区。如果有由成千个文件组成的大目录,那末整个目录可能跨
越盘上的好几个扇区。这么多的文件最好分散在各个子目录下,每个子目录下分配约40个左右的文件。
按每个目录记录的平均长度为50字节计算,差不多占据单个物理扇区。如果在一个子目录下分配太多的
文件数时,那末要找这个目录下的文件时,需要顺序读和检查好几个物理扇区才能找到这个文件,这样
就多化时间。
CD数据扇区有三种基本类型,PS使用Mode2 form1或2。
类型 #1: Mode 1
-----------------------------------------------------
0 1 2 3 4 5 6 7 8 9 A B C D E F
0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 01
0010h [---DATA...
...
0800h ...DATA---]
0810h [---EDC---] 00 00 00 00 00 00 00 00 [---ECC...
...
0920h ...ECC---]
-----------------------------------------------------
类型 #2: Mode 2 (XA), form 1
-----------------------------------------------------
0 1 2 3 4 5 6 7 8 9 A B C D E F
0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 02
0010h [--FLAGS--] [--FLAGS--] [---DATA...
...
0810h ...DATA---] [---EDC---] [---ECC...
...
0920h ...ECC---]
-----------------------------------------------------
类型 #3: Mode 2 (XA), form 2
-----------------------------------------------------
0 1 2 3 4 5 6 7 8 9 A B C D E F
0000h 00 FF FF FF FF FF FF FF FF FF FF 00 [-ADDR-] 02
0010h [--FLAGS--] [--FLAGS--] [---DATA...
...
0920h ...DATA---] [---EDC---]
-----------------------------------------------------
注:
ADDR: 扇区地址,用BCD编码成 分:秒:帧
FLAGS: 在Mode 2 (XA)扇区中用来描述扇区类型,冗余重复一次
DATA: 扇区里包含实际数据的区域
EDC: Error Detection Code检错码
ECC: Error Correction Code纠错码
每种扇区开头都是12个字节的同步区、然后是4个字节的子码区和8个字节的副标题。
struct
{
BYTE sync[12]={ 0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0 }; // 在所有的CD-ROM驱动器里同步区都不会被解码
struct
{
BYTE minutes; // 相对于碟片开始的时间代码
BYTE seconds; // 相对于碟片开始的时间代码
BYTE sectors; // 相对于碟片开始的时间代码
BYTE mode; // PS_CD扇区是Form 2
} header;
struct
{
BYTE file_number; // 文件号
BYTE channel; // 通道号
BYTE
{
bit 7: eof_marker; // 1 表示文件的最后一个扇区,其它是0
bit 6: real_time; // 1 表示实时模式,参见上面
bit 5: form; // 1 表示ADPCM扇区(form 2, 0 表示form 1)?
bit 4: trigger; // 用于应用程序
bit 3: data; // 对应于扇区类型,1 表示数据M扇区
bit 2: audio; // 对应于扇区类型,1 表示音频扇区
bit 1: video; // 对应于扇区类型,1 表示视频扇区
bit 0: end_of_record; // 表明记录的结束
} submode;
BYTE
{
bit 7: reserved; // =0 ?
bit 6: emphasis;
bit 5,4: bits_per_sample; // 00=4bits (B,C 格式) 01=8bits
bit 3,2: sample_rate; // 00=37.8kHz (A,B 格式) 01=18.9kHz
bit 1,0: mono_stereo; // 00=单声道 01=立体声,其他值未用
} coding_info;
BYTE copy_of_file_number; // =文件号
BYTE copy_of_channel; // =通道
BYTE copy_of_submode; // =副模式
BYTE copy_of_coding_info; // =编码信息
} subheader;
BYTE user[2328]; // 数据
//对于form 1,user里是{data[2048];edc[4];ecc[276]};
//对于form 2,user里是{data[2324];edc[4]};
//其中edc[4]是检错码,ecc[276]是纠错码;
} mode2_raw_sector;
XA 副标题信息是8个字节,分成重复的2块。
| 字节 | 描述 |
| 0 | 文件号 |
| 1 | 通道号 |
| 2 | 副模式 |
| 3 | 编码信息 |
| 4 | 文件号 |
| 5 | 通道号 |
| 6 | 副模式 |
| 7 | 编码信息 |
具体:
| 字节/位 | 域 | 描述 |
| 字节0 | 文件号 | |
| 0 = 标准数据文件 1-255 = 交叉存取的 | ||
| 字节1 | 通道号 | |
| 对于ADPCM是0-15 对于数据/视频是0-31 ADPCM数据最多是16个通道,在PS中如果没有倍速CD-ROM会被减到8 | ||
| 字节2 | 副模式 | |
| #7 | EOF | 1表示文件尾 |
| #6 | Real time sector | 1表示实时模式扇区 |
| #5 | Form 2 | |
| #4 | Trigger | 用于应用程序 |
| #3 | Data | 1表示数据扇区 |
| #2 | Audio | 1表示音频扇区 |
| #1 | Video | 1表示视频扇区 |
| #0 | EOR | 1表示记录结束 |
| 字节3 | 编码信息 | |
| #7 | Reserved | 0 |
| #6 | Emphasis | 强调 |
| #5,4 | Bits per sample | 00 = 4bit (level B/C) 01 = 8bit (level A) |
| #3,2 | Sample rate | 00 = 37.8kHz (level A/B) 01 = 18.9kHz (level C) |
| #1,0 | mono/stereo | 00 = 单声道 01 = 立体声 |
4字节检错码EDC:
CD-ROM扇区中,有一个4字节共32位的EDC字域,它就是用来存放CRC码。
CD-ROM的CRC校验码生成多项式是32阶的,P(x) = (x16+x15+x2+1)(x16+x2+x+1)。
计算CRC码时用的数据块是从扇区的开头到用户数据区结束为止的数据字节,以Mode1为例,
即字节0—2063共2064个字节。将数据加上4个字节0,然后除多项式,得到的余数为校验码。
在EDC中存放的CRC码的次序如下:
276字节纠错码ECC:
CD-ROM中的数据、地址、校验码等都可以看成是属于GF(2m)
= GF(28)中的元素或称符号。
GF(28)表示域中有256个元素,除0,1之外的254个元素由本原多项式P(x)生成。
CD-ROM用来构造GF(28)域的P(x)是:P(x)=X8+X4+X3+X2+1。
而GF(28)域中的本原元素为:α = (0 0 0 0 0 0 1 0)。
按ISO/IEC10149的规定,CD-ROM扇区中的ECC码采用GF(28)域上的RSPC码产生172个字节
的P校验符号和104个字节的Q校验符号。在每个扇区中,#12字节到#2075字节和ECC域中
#2076字节到#2351字节共2340个字节组成1170个字(word)。每个字s(n)由两个字节B组成,
一个称为最高有效位字节MSB,另一个叫做最低有效位字节LSB。第n个字由下面的字节组成,
s(n) = MSB[B(2n+13)]+LSB[B(2n+12)],其中n = 0,1,2,…,1169。
从#12字节开始到#2075字节共2064个字节组成的数据块排列成24×43的矩阵,如下所示:
(ISO /IEC1049)
矩阵中的元素是字。这个矩阵要把它想象成两个独立的矩阵才比较好理解和分析,
一个是由MSB字节组成的24×43矩阵,另一个是由LSB字节组成的24×43矩阵。
(1) P校验符号用(26,24)RS码产生
43列的每一列用矢量表示,记为Vp。每列有24个字节的数据再加2个字节的P校验字节,用下式表示:
其中:Np = 0,1,2,……,42; Mp =
0,1,2,……,25
s(43*24+Np)和s(43*25+Np)是P校验字节。对这列字节计算得到的是两个P校验字节,称为P校验符号。
两个P校验字节加到24行和25行的对应列上,这样构成了一个26×43的矩阵,并且满足方程HP×Vp=0。
其中HP校验矩阵为
(2) Q校验符号用(45,43)RS码产生
增加P校验字节之后得到了一个26×43矩阵,该矩阵按对角线元素重新排列后得到一个新的矩阵: 每条对角线上的43个MSB字节和LSB字节组成的矢量记为VQ。VQ在26×43矩阵中变成行矢量。 第NQ行上的VQ矢量包含的字节如下: 其中:NQ = 0,1,2,…,25;MQ
= 0,1,2,…,42
s(43*26+NQ)和s(44*26+NQ)是Q校验字节。VQ中的(44*MQ+43*NQ)字节号运算结果要做mod(1118)运算。
用(45,43)RS码产生的两个Q校验字节放到对应VQ矢量的末端,并且满足下面的方程:HQ×VQ=0
其中HQ校验矩阵为
(26,24)RS码和(45,43)RS码可以纠正出现在任何一行和任何一列上的一个错误,并且能相当可靠地
检测出行、列中的多重错误。如果在一个阵列中出现多重错误,Reference
Technology公司提供有一
种名叫Layered ECC的算法,它可以取消多重错误。它的核心思想是交替执行行纠错和列纠错。
ECC算法首先计算MSB矩阵和LSB矩阵中每一行的校正子Sri(i
= 0,1,…,25),以及每一列的校正 子Scj(j = 0,1,…,44)。因为用(45,43)RS码,所以每一个Sri和每一个Scj都有两个校正子分量。 如果Sri = 0,则说明第i行无错;如Scj
= 0,说明第j行无错。 ECC算法首先纠正只有一个的错误的行。这些错误取消后就纠正只有一个的错误的列。经过一次行列交 替纠错后,只剩下很少错误。再进行一次行列交替纠错后,就可以消除全部错误。 因为RS码纠错算法本身包含找错误的位置和错误值,而错误位置已经由校正子Sr(i-k)、Sri、Sr(i+m) 和Scj、Sc(j+l)确定,所以只剩下求错误值的问题。这个问题在讨论RS码时已经解决。 对CD-ROM存储器的数据,经CIRC校正后可以使以字节做单位的误码率小于10-9,再经RSPC进行纠错后, 字节误码率可以小于10-13,这样就满足了计算机要求误码率小于10-12的要求。 对于Mode1 EDC计算范围是从#0字节开始到#2063字节共2064字节。
对于Mode2 form1 EDC计算范围是从#16字节开始到#2071字节共2056字节。
对于Mode2 form2 EDC计算范围是从#16字节开始到第2347字节共2332字节。
ECC计算范围都是从#12字节开始到#2075字节共2064字节。
EDC:
x24-x31
x16-x23
x8-x15
x0-x7
字节号:
2064
2065
2066
2067
NP
0
1
2
3
41
42
0
000
0001
0002
…
…
…
0041
0042
1
0043
0044
0045
…
…
…
0084
0085
HEADER
2
0086
0087
0088
…
…
…
0127
0128
+
P
Q
用户数据
+
MP
22
0946
0947
0948
…
…
…
0987
0988
部分辅助数据
23
0989
0990
0991
…
…
…
1030
1031
24
1032
1033
1034
1073
1074
P-校验
25
1075
1076
1077
…
…
…
1116
1117
26
1118
1119
1120
…
1143
Q-校验
27
1144
1145
1146
…
1169
0
1
2
…
25
Vp=
s(43*0+Np)
s(43*1+Np)
s(43*2+Np)
s(....)
s(43*Mp+Np)
s(....)
s(43*22+Np)
s(43*23+Np)
s(43*24+Np)
s(43*25+Np)
HP=
1
1
......
1
1
1
a25
a24
......
a2
a1
1
MQ
0
1
2
40
41
42
Q0
Q1
0
0000
0044
0088
…
…
0642
0686
0730
1118
1144
1
0043
0087
0131
…
…
0685
0729
0773
1119
1145
2
0086
0130
0147
…
…
0728
0772
0816
1120
1146
3
0129
0137
0217
…
…
0771
0815
0859
1121
1147
4
0172
0216
0260
…
…
0814
0858
0902
1122
1148
22
0946
0990
1034
…
…
0470
0514
0558
1140
1166
NQ
23
0989
1033
1077
…
…
0513
0557
0601
1141
1167
24
1032
1076
0002
…
…
0556
0600
0644
1142
1168
25
1075
0001
0045
…
…
0599
0643
0687
1143
1169
VQ =
s(44*0+43*NQ)
s(44*1+43*NQ)
s(44*2+43*NQ)
s(....)
s(44*MQ+43*NQ)
s(....)
s(44*41+43*NQ)
s(44*42+43*NQ)
s(43*26+NQ)
s(44*26+NQ)
HQ=
1
1
......
1
1
1
a44
a43
......
a2
a1
1
程序:
/* LUTs used for computing ECC/EDC */
static BYTE ecc_f_lut[256];
static BYTE ecc_b_lut[256];
static DWORD edc_lut[256];
/* Init routine */
static void eccedc_init(void)
{
DWORD i, j, edc;
for(i = 0; i < 256; i++)
{
j = (i << 1) ^ (i & 0x80 ? 0x11D : 0);
ecc_f_lut[i] = j;
ecc_b_lut[i ^ j] = i;
edc = i;
for(j = 0; j < 8; j++) edc = (edc >> 1) ^ (edc & 1 ? 0xD8018001 : 0);
edc_lut[i] = edc;
}
}
/***************************************************************************/
// Compute EDC for a block
void edc_computeblock( const BYTE *src, WORD size, DWORD *dest )
{
DWORD edc=0x00000000;
while(size--) edc = (edc >> 8) ^ edc_lut[(edc ^ (*src++)) & 0xFF];
dest[0] = (edc >> 0) & 0xFF;
dest[1] = (edc >> 8) & 0xFF;
dest[2] = (edc >> 16) & 0xFF;
dest[3] = (edc >> 24) & 0xFF;
}
/***************************************************************************/
// Compute ECC for a block (can do either P or Q)
static void ecc_computeblock( BYTE *src, DWORD major_count, DWORD minor_count, DWORD major_mult, DWORD minor_inc, BYTE *dest)
{
DWORD size = major_count * minor_count;
DWORD major, minor;
for(major = 0; major < major_count; major++)
{
DWORD index = (major >> 1) * major_mult + (major & 1);
BYTE ecc_a = 0;
BYTE ecc_b = 0;
for(minor = 0; minor < minor_count; minor++)
{
BYTE temp = src[index];
index += minor_inc;
if(index >= size) index -= size;
ecc_a ^= temp;
ecc_b ^= temp;
ecc_a = ecc_f_lut[ecc_a];
}
ecc_a = ecc_b_lut[ecc_f_lut[ecc_a] ^ ecc_b];
dest[major ] = ecc_a;
dest[major + major_count] = ecc_a ^ ecc_b;
}
}
// Generate ECC P and Q codes for a block
static void ecc_generate( BYTE *sector, int zeroaddress)
{
BYTE address[4], i;
/* Save the address and zero it out */
if(zeroaddress) for(i = 0; i < 4; i++)
{
address[i] = sector[12 + i];
sector[12 + i] = 0;
}
/* Compute ECC P code */
ecc_computeblock(sector + 0xC, 86, 24, 2, 86, sector + 0x81C);
/* Compute ECC Q code */
ecc_computeblock(sector + 0xC, 52, 43, 86, 88, sector + 0x8C8);
/* Restore the address */
if(zeroaddress) for(i = 0; i < 4; i++) sector[12 + i] = address[i];
}
/***************************************************************************/
// Generate ECC/EDC information for a sector (must be 2352 = 0x930 bytes), Returns 0 on success
void eccedc_generate(BYTE *sector, int type)
{
DWORD i;
switch(type) {
case 1: /* Mode 1 */
edc_computeblock(sector + 0x00, 0x810, sector + 0x810);
/* Write out zero bytes */
for(i = 0; i < 8; i++) sector[0x814 + i] = 0;
ecc_generate(sector, 0);
break;
case 2: /* Mode 2 form 1 */
edc_computeblock(sector + 0x10, 0x808, sector + 0x818);
ecc_generate(sector, 1);
break;
case 3: /* Mode 2 form 2 */
edc_computeblock(sector + 0x10, 0x91C, sector + 0x92C);
break;
}
}