PS_CD资料

整理:Pinocchio

PS_CD布局

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 (取决于卷描述符的类型)

卷描述符有五种类型:

五种描述符的前四种可以任意组合,组成卷描述符系列。这四个描述符可以在描述符系列中出现不只一次。

描述符系列有两个限制:主卷描述符至少要出现一次,卷描述符系列终止符只能出现一次,而且只能出现在最后。

卷描述符系列记录在从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纠错码
 
Mode2扇区:

每种扇区开头都是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 副标题信息:

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 = 立体声

 

ECC/EDC算法

理论:

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码的次序如下:
EDC: x24-x31 x16-x23 x8-x15 x0-x7
字节号: 2064 2065 2066 2067

 

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的矩阵,如下所示:
                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      

(ISO /IEC1049)

矩阵中的元素是字。这个矩阵要把它想象成两个独立的矩阵才比较好理解和分析,

一个是由MSB字节组成的24×43矩阵,另一个是由LSB字节组成的24×43矩阵。

(1) P校验符号用(26,24)RS码产生

43列的每一列用矢量表示,记为Vp。每列有24个字节的数据再加2个字节的P校验字节,用下式表示:
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)

其中: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校验矩阵为
HP= 1 1 ...... 1 1 1
a25 a24 ...... a2 a1 1

(2) Q校验符号用(45,43)RS码产生

增加P校验字节之后得到了一个26×43矩阵,该矩阵按对角线元素重新排列后得到一个新的矩阵:

                  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

每条对角线上的43个MSB字节和LSB字节组成的矢量记为VQVQ在26×43矩阵中变成行矢量。

第NQ行上的VQ矢量包含的字节如下:

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)

其中: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校验矩阵为
HQ= 1 1 ...... 1 1 1
a44 a43 ...... a2 a1 1

(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)SriSr(i+m)

ScjSc(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字节。

程序:
/* 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;
  }
}