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字节组成的矢量记为VQ。VQ在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)、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字节。

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