资源描述
课程设计报告
设计课题:
ADPCM编解码器的实现
姓 名:
专 业:
集成电路设计与集成系统
学 号:
日 期
指导教师:
一:课程设计任务与要求。
ADPCM编解码器的实现。
m转为Verilog,使用Matlab和Modesim联合仿真验证
二:系统设计原理。
2.1 脉冲编码调制PCM
脉冲编码调制就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程[9]。
抽样,就是对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号,抽样必须遵循奈奎斯特抽样定理。该模拟信号经过抽样后还应当包含原信号中所有信息,也就是说能无失真的恢复原模拟信号。它的抽样速率的下限是由抽样定理确定的。抽样速率采用8KHZ。
量化,就是把经过抽样得到的瞬时值将其幅度离散,即用一组规定的电平,把瞬时抽样值用最接近的电平值来表示,通常是用二进制表示。
量化误差:量化后的信号和抽样信号的差值。量化误差在接收端表现为噪声,称为量化噪声。 量化级数越多误差越小,相应的二进制码位数越多,要求传输速率越高,频带越宽。 为使量化噪声尽可能小而所需码位数又不太多,通常采用非均匀量化的方法进行量化。 非均匀量化根据幅度的不同区间来确定量化间隔,幅度小的区间量化间隔取得小,幅度大的区间量化间隔取得大。
一个模拟信号经过抽样量化后,得到已量化的脉冲幅度调制信号,它仅为有限个数值。
编码,就是用一组二进制码组来表示每一个有固定电平的量化值。然而,实际上量化是在编码过程中同时完成的,故编码过程也称为模/数变换,可记作A/D。
2.2 差分脉冲编码调制DPCM
差分脉冲编码调制DPCM是利用样本与样本之间存在的信息冗余度(预测样值与当前样值之差)来代替样值本身进行编码的一种数据压缩技术[10]。差分脉冲编码调制可以实现在量化台阶不变(即量化噪声不变)的情况下,编码位数显著减少,信号带宽大大压缩。根据过去的样本去估算下一个样本信号幅度大小,这个值称为预测值,对实际信号值与预测值之差进行量化编码,从而就减少了表示每个样本信号的位数。
差分脉冲编码的概念,如图2.2中,差分信号d(k)是离散输入信号s(k)和预测器输出的估计值之差。此刻,是对S(k)的预测值,而不是过去样本的实际值。DPCM系统实际上就是对这个差值的d(k)进行量化编码,用来补偿过去编码中产生的量化误差。DPCM系统实际就是一个负反馈系统,采用这种结构可以避免量化误差的积累。重建新号是由逆量化器产生的量化差分信号,与对过去样本信号的估算值求和得到。他们的和,即作为预测器确定下一个信号估算值的输入信号。由于在发送端和接收端都使用相同的逆量化器和预测器,所以接收端的重建信号可以传送信号I(k)中获得。
图2.2 DPCM原理图
在PCM中,每个波形样值都独立编码,与其他样值无关,样值的整个幅值编码需要较多位数,比特率较高,数字化的信号带宽大。然而,大多数以奈奎斯特或更高速率抽样的信源信号在相邻抽样间表现出很强的相关性,利用信源的这种相关性,一种比较简洁的解决方案是对相邻样值的差值而不是样值本身进行编码。由于相邻样值的差值比样值本身小,可以用较少的比特数表示差值。如果将样值之差仍用N位编码传送,则DPCM的量化信噪比显然优于PCM系统。
2.3 自适应差分脉冲编码调制ADPCM
前述概要地介绍了DPCM的工作原理。但为了能进一步提高DPCM方式的质量还需要采取一些辅助措施。即自适应措施。语声信号的变化是因人、因时而不同的,为了能在相当宽的变化范围内仍能得到最佳的性能,DPCM也需要自适应系统,自适应预测和自适应量化,称为ADPCM。
图2.3所示为采用固定预测并带有自适应量化的ADPCM系统。图中多电平量化与调制器用Q表示,积分器用P组成的预测系统表示。自适应量化的基本思想是:让量化间隔的变化,与输入信号方差相匹配,即量化器阶距随输入信号的方差而变化,它正比于量化器输入信号的方差。现有的自适应量化方案有两类:一类是其输入幅度或方差由输入信号本身估算,这种方案叫前馈自适应量化器。另一类是其阶距根据量化器的输出来进行自适应调整,或等效的用输出编码信号来进行自适应调整,这类自适应量化方案称为反馈自适应量化器。
无论是采用前馈式还是反馈式,自适应量化都可以改善动态范围及信噪比。反馈控制的主要优点是量化阶距的信息由码字序列提取,因此不需要传输或存储额外的阶距信息。但是在重建信号输出时,传输中的误码对质量的影响比较敏感,在前馈控制时,要求码字和阶距一起,用来得出信号。这样是增加了其复杂程度,但它有可能在差错控制保护下传输阶距从而大大改善高误码率传输时的输出信号质量。不论是前馈型还是反馈型自适应量化都可以希望得到超过相同电平数固定量化的10~20dB的改善。
为了进一步有效地克服语声通信过程中的不平稳行,要考虑量化器和预测器都适应匹配于语声信号瞬时变化,又设计了同时带有自适应量化和自适应阶距的ADPCM系统。自适应量化和自适应预测都可以是前馈型的或是反馈型的。对ADPCM来说,预测系统的预测系数的选择是很重要的。如果信号的样值用S(n)表示,的样值用表示,的样值用表示,则当采用线性预测,即是以前量化值的线性组合时,其预测值为
(2.3)
式中P为预测器的阶数。要使预测系数自适应,通常是假定短时间内语声信号的参量保持恒定的。并使短时间内的均方预测误差为最小值来选择预测系数[11]。
(a)编码器
(b)解码器
图2.3 ADPCM原理图
2.4 编码过程
编码的核心思想是对输入信号与预测信号差值进行编码和预测,采用的是非均匀量化,为了使不同幅值的信号信噪比接近一致,从而避免大幅值语音信号信噪比大而小幅值语音信号信噪比小[12]。ADPCM编码过程如图3.1所示输示。输入Si是一个线性16bit补码PCM数据,其范围在32767到-32767之间。如果是非线性的PCM则之前先加个线性转化模块使得输入的数据是线性的。经过预测器出来的预测采样值Sp=,与线性输入Si的差值为d,我们就可以在这个差值上进行处理。接下来量化器模块对有符号的4位编码数据I,由于是带符号的所以这个数据的表示范围在7到-7之间,最高位为符号位。 具体实现过程如下,首先计算16位的PCM值的当前采样值Si和上一时刻预测采样值之间的差值d,这个差值的量化编码即输出4位ADPCM值I(k)。在算法实现中,定义一个结构变量存储预测采样Sp和量化步长索引,并制定了两个表。一个表为索引调整表,其输入为差值量化编码I(k),用来更新步长索引。另一个表为步长调整表,其输入为步长索引,输出为步长q。编码时,首先用上一个采样点的步长索引查步长调整表求出步长q。如果当前采样值Si和采样预测值之间的差值d为负,则I的D3位置1。如果该差值绝对值大于步长q,则I的D2位置1。如果d-q大于q/2,则I的D1位置1,如果d-q-q/2大于q/4,则I的D0位置1。如果以上条件不满足,则相应位置0。这样就确定了编码值I(k)。然后编码值I(k)作为索引调整表的输入,查表输出索引调整,并和结构变量中原步长索引相加,产生新的步长索引,在下一个采样值的编码中使用。编码输出I后,编码还需要重复进行和解码完全一样的计算过程,求出新的预测采样值Sp。
图3.1 G.721协议编码
概括起来其主要步骤如下
① 计算Si与自适应预测器输出的差值;
② 通过自适应量化模块对进行量化,得到ADPCM码字I(k);
③ 通过自适应反量化模块对I(k)计算得到量化差分预测信号;
④ 根据重建信号和更新预测滤波器系数;
⑤ 利用新的系数,计算得到,重复上述5个步骤,压缩下一个语音采样数据。
图3.2 G.721协议解码
2.5 解码过程
解码是编码的逆过程,ADPCM的解码过程如图3.2所示。解码输入即编码输出I,逆量化器将I逆量化成差值dq。差值dq与预测采样Sp相加得到解码输出Sr。解码过程同编码过程中求预测采样的原理是一样的,当编码端的Sr能很好的跟踪上Si时,解码端的Sr也能同样再现Si。这里涉及算法本身的时延和实现平台、实现方法的实时性等因素。 解码时首先通过步长索引查步长调整表得到量化步长,差值量化编码I经逆量化器得到语音差值dq,这是求I的逆过程;然后与前次预测值一起重建当前语音信号Sr,最后用固定预测Sp=Sr,更新预测采样Sp,用I更新量化步长索引值[13][14][15]。
其主要步骤如下:
① 通过自适应逆量化器和自适应预测得到和。
② 通过同相加便得到编码输出Sr。
③ 采用与编码器相同的方法更新预测滤波器系数,。
④ 采用与编码器相同的方法更新步长索引表,得到新的
⑤ 利用新的滤波器系数,重复上述5个步骤,解码下一个I(k)。
三:测试与验证
主要采用Matlab来验证ADPCM的结果,以及和Modlsim来联合仿真。而我们利用 FPGA 资源消耗低、灵活性强、速度快、性价比突出等优势,使用 VerilogHDL 硬件描述语言设计并实现了ADPCM 编/解码器, 运用Modelsim 仿真测试,来确保编/解码器的正确性和稳定性, 整个设计充分利用了 FPGA 芯片的资源、硬件结构简单、可靠性高,具有良好的应用前景。
3.1Matlab仿真
根据G.721协议,再由上述原理编写ADPCM的编解码器C代码,将代码导入到matlab中[17]。我们用一段类似语音的正弦波取200点来验证波形。其代码如下:
t=0:pi/50:2*pi;
y=sin(t);
adpcm_y=adpcm_encoder(y);
Y=adpcm_decoder(adpcm_y);
其中adpcm_encoder( )为ADPCM的编码模块,代码见附录1,而adpcm_decoder( )为ADPCM的解码模块,其代码也见附录1。y为输入波,adpcm_y为ADPCM编码波形,Y为解码输出波形则输入波形如图4.2.1。
图4.2.1 输入波形
ADPCM编码波形如图4.2.2。
图4.2.2 ADPCM编码波形
ADPCM解码波形如图4.2.3。
图4.2.3 ADPCM解码
输入与输出波形的差值如图4.2.4。
图4.2.4 输入与输出差值波形
可以看出开始差距比较大后来慢慢便开始减小,是因为开始是初始化预测值我们无法知道,所以便设定为预测值为0。
3.2 Modelsim仿真
本设计采用verilog语言验证编解码器功能的实现[18]。
先用matlab生成一个类似于正弦波的图形来模拟为输入的语音波形,并将其值记录在一个名为“sin”的.txt文件,然后送入到测试文件中my_mem保存起来,2个时钟上升沿后便把my_mem保存的数据一一送入到编码器的PCMIN输入端,然后modelsim仿真结果如下图4.4.1。仿真测试verilog代码和编码器的代码见附录2。
图4.4.1 输入波形及编码波形
同理我让从编码器输出的编码值也保存在名为“code”的.txt文件中,并设计了一个my_mem的寄存器也来保存code.txt中的编码值,在经过2个时钟下降沿后便把my_mem中保存的数据一一送到解码器的CODEIN输入端,按照这种方法,每隔2个时钟下降沿便将my_mem中数据输入一个,最后modelsim仿真数据如图4.4.2。其中verilog的解码器代码和测试文件的代码见附录2。
图4.4.2 解码输出波形
经过解码后波形与输入波形相似,当系统稳定后,差异便慢慢减少,基本保持了语音的质量。
四:总结与展望
通过matlab仿真和modelsim的仿真,能够证明此次设计基本符合语音编解码的期望和结果。本次课题主要研究ADPCM编解码器实现,从而展开的工作。其主要的工作是根据文献资料调研ADPCM编解码器的原理和设计方法。利用Matlab和C语言实现ADPCM编解码器。利用verilog设计ADPCM编解码器的硬件电路,通过对modelsim仿真测试激励文件的描写和verilog编程语言的学习,对设计的verilog模型进行时序仿真验证。综合设计的verilog模型并生成FPGA文件。
通过此次课题让我对ADPCM编解码有了一定的了解,同时也对其使用的仿真软件有了更加熟悉的运用,让我对verilog硬件语言有了更加深刻的认识,为我积累了许多宝贵的经验。
附 录
附录1:
编码代码
function adpcm_y = adpcm_encoder(raw_y)
IndexTable = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8];
StepSizeTable = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767];
prevsample = 0;
previndex = 1;
Ns = length(raw_y);
n = 1;
raw_y = 32767 * raw_y;
while (n <= Ns)
predsample = prevsample;
index = previndex;
step = StepSizeTable(index);
diff = raw_y(n) - predsample;
if (diff >= 0)
code = 0;
else
code = 8;
diff = -diff;
end
tempstep = step;
if (diff >= tempstep)
code = bitor(code, 4);
diff = diff - tempstep;
end
tempstep = bitshift(tempstep, -1);
if (diff >= tempstep)
code = bitor(code, 2);
diff = diff - tempstep;
end
tempstep = bitshift(tempstep, -1);
if (diff >= tempstep)
code = bitor(code, 1);
end
diffq = bitshift(step, -3);
if (bitand(code, 4))
diffq = diffq + step;
end
if (bitand(code, 2))
diffq = diffq + bitshift(step, -1);
end
if (bitand(code, 1))
diffq = diffq + bitshift(step, -2);
end
if (bitand(code, 8))
predsample = predsample - diffq;
else
predsample = predsample + diffq;
end
if (predsample > 32767)
predsample = 32767;
elseif (predsample < -32768)
predsample = -32768;
end
index = index + IndexTable(code+1);
if (index < 1)
index = 1;
end
if (index > 89)
index = 89;
end
prevsample = predsample;
previndex = index;
adpcm_y(n) = bitand(code, 15);
n = n + 1;
End
解码模块
function raw_y = adpcm_decoder(adpcm_y)
IndexTable = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8];
StepSizeTable = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767];
prevsample = 0;
previndex = 1;
Ns = length(adpcm_y);
n = 1;
while (n <= Ns)
predsample = prevsample;
index = previndex;
step = StepSizeTable(index);
code = adpcm_y(n);
diffq = bitshift(step, -3);
if (bitand(code, 4))
diffq = diffq + step;
end
if (bitand(code, 2))
diffq = diffq + bitshift(step, -1);
end
if (bitand(code, 1))
diffq = diffq + bitshift(step, -2);
end
if (bitand(code, 8))
predsample = predsample - diffq;
else
predsample = predsample + diffq;
end
if (predsample > 32767)
predsample = 32767;
elseif (predsample < -32768)
predsample = -32768;
end
index = index + IndexTable(code+1);
if (index < 1)
index = 1;
end
if (index > 89)
index = 89;
end
prevsample = predsample;
previndex = index;
raw_y(n) = predsample / 32767;
n = n + 1;
end
附录2
编码模块
module ADPCMEncoder(RST,EN,CLK,PCMIN,CODEOUT);
input RST;
input EN;
input CLK;
input signed [15:0] PCMIN;
output[3:0] CODEOUT;
reg[3:0] CODEOUT;
reg tai;
reg sb;
reg signed[15:0] predsample,diff,prevsample;
reg[15:0] code;
reg signed[7:0] index;
reg start;
initial
begin
tai = 0;
prevsample = 0;
start = 0;
end
always @(posedge CLK)
begin
if(EN == 0)
begin
start = 1;
end
if(RST==0)
begin
tai = 0;
prevsample = 0;
index = 0;
end
if(start==1)
begin
if(!tai)
begin
predsample = PCMIN;
diff=predsample-prevsample;
if (diff<0)
begin
diff=-diff;
sb=1;
end
else sb=0;
if(diff>=StepSizeTable(index))
begin
diff = diff - StepSizeTable(index);
code[2] = 1;
end
else
code[2] = 0;
diff = diff<<1;
if(diff>=StepSizeTable(index))
begin
diff = diff - StepSizeTable(index);
code[1] = 1;
end
else
code[1] = 0;
diff = diff<<1;
if(diff>=StepSizeTable(index))
begin
code[0] = 1;
end
else
code[0] = 0;
end
else
begin
CODEOUT[0] <= code[0];
CODEOUT[1] <= code[1];
CODEOUT[2] <= code[2];
CODEOUT[3] <= sb;
index =index + IndexTable(code);
if (index<0) index=0;
else if (index>88) index=88;
prevsample=predsample;
start = 0;
end
tai = ~tai;
end
end
function[7:0] IndexTable;
input[2:0] index_a;
case(index_a)
3'd00:IndexTable = 8'hFF;
3'd01:IndexTable = 8'hFF;
3'd02:IndexTable = 8'hFF;
3'd03:IndexTable = 8'hFF;
3'd04:IndexTable = 2;
3'd05:IndexTable = 4;
3'd06:IndexTable = 6;
3'd07:IndexTable = 8;
default:IndexTable = 8'hFF;
endcase
Endfunction
function[15:0] StepSizeTable;
input[7:0] Index;
case(Index)
8'd00:StepSizeTable= 16'd7;
8'd01:StepSizeTable= 16'd8;
8'd02:StepSizeTable= 16'd9;
8'd03:StepSizeTable= 16'd10;
8'd04:StepSizeTable= 16'd11;
8'd05:StepSizeTable= 16'd12;
8'd06:StepSizeTable= 16'd13;
8'd07:StepSizeTable= 16'd14;
8'd08:StepSizeTable= 16'd16;
8'd09:StepSizeTable= 16'd17;
8'd10:StepSizeTable= 16'd19;
8'd11:StepSizeTable= 16'd21;
8'd12:StepSizeTable= 16'd23;
8'd13:StepSizeTable= 16'd25;
8'd14:StepSizeTable= 16'd28;
8'd15:StepSizeTable= 16'd31;
8'd16:StepSizeTable= 16'd34;
8'd17:StepSizeTable= 16'd37;
8'd18:StepSizeTable= 16'd41;
8'd19:StepSizeTable= 16'd45;
8'd20:StepSizeTable= 16'd50;
8'd21:StepSizeTable= 16'd55;
8'd22:StepSizeTable= 16'd60;
8'd23:StepSizeTable= 16'd66;
8'd24:StepSizeTable= 16'd73;
8'd25:StepSizeTable= 16'd80;
8'd26:StepSizeTable= 16'd88;
8'd27:StepSizeTable= 16'd97;
8'd28:StepSizeTable= 16'd107;
8'd29:StepSizeTable= 16'd118;
8'd30:StepSizeTable= 16'd130;
8'd31:StepSizeTable= 16'd143;
8'd32:StepSizeTable= 16'd157;
8'd33:StepSizeTable= 16'd173;
8'd34:StepSizeTable= 16'd190;
8'd35:StepSizeTable= 16'd209;
8'd36:StepSizeTable= 16'd230;
8'd37:StepSizeTable= 16'd253;
8'd38:StepSizeTable= 16'd279;
8'd39:StepSizeTable= 16'd307;
8'd40:StepSizeTable= 16'd337;
8'd41:StepSizeTable= 16'd371;
8'd42:StepSizeTable= 16'd408;
8'd43:StepSizeTable= 16'd449;
8'd44:StepSizeTable= 16'd494;
8'd45:StepSizeTable= 16'd544;
8'd46:StepSizeTable= 16'd598;
8'd47:StepSizeTable= 16'd658;
8'd48:StepSizeTable= 16'd724;
8'd49:StepSizeTable= 16'd796;
8'd50:StepSizeTable= 16'd876;
8'd51:StepSizeTable= 16'd963;
8'd52:StepSizeTable= 16'd1060;
8'd53:StepSizeTable= 16'd1166;
8'd54:StepSizeTable= 16'd1282;
8'd55:StepSizeTable= 16'd1411;
8'd56:StepSizeTable= 16'd1552;
8'd57:StepSizeTable= 16'd1707;
8'd58:StepSizeTable= 16'd1878;
8'd59:StepSizeTable= 16'd2066;
8'd60:StepSizeTable= 16'd2272;
8'd61:StepSizeTable= 16'd2499;
8'd62:StepSizeTable= 16'd2749;
8'd63:StepSizeTable= 16'd3024;
8'd64:StepSizeTable= 16'd3327;
8'd65:StepSizeTable= 16'd3660;
8'd66:StepSizeTable= 16'd4026;
8'd67:StepSizeTable= 16'd4428;
8'd68:StepSizeTable= 16'd4871;
8'd69:StepSizeTable= 16'd5358;
8'd70:StepSizeTable= 16'd5894;
8'd71:StepSizeTable= 16'd6484;
8'd72:StepSizeTable= 16'd7132;
8'd73:StepSizeTable= 16'd7845;
8'd74:StepSizeTable= 16'd8630;
8'd75:StepSizeTable= 16'd9493;
8'd76:StepSizeTable= 16'd10442;
8'd77:StepSizeTable= 16'd11487;
8'd78:StepSizeTable= 16'd12635;
8'd79:StepSizeTable= 16'd13899;
8'd80:StepSizeTable= 16'd15289;
8'd81:StepSizeTable= 16'd16818;
8'd82:StepSizeTable= 16'd18500;
8'd83:StepSizeTable= 16'd20350;
8'd84:StepSizeTable= 16'd22385;
8'd85:StepSizeTable= 16'd24623;
8'd86:StepSizeTable= 16'd27086;
8'd87:StepSizeTable= 16'd29794;
8'd88:StepSizeTable= 16'd32767;
default:StepSizeTable= 16'd7;
endcase
endfunction
endmodule
解码模块
module ADPCMDecoder(RST,EN,CLK,CODEIN,PCMOUT);
input RST;
input EN;
input CLK;
input[3:0] CODEIN;
output signed[15:0] PCMOUT;
reg signed[15:0] PCMOUT;
reg signed [15:0] de_predsample,diffq;
reg signed [16:0] de_prevsample;
reg signed [16:0] de_diffq;
reg signed [7:0] de_index;
reg state;
reg[2:0] de_code;
reg sb;
reg start;
initial
begin
state = 0;
de_index = 0;
end
always @(negedge CLK)
begin
if(EN == 0)
begin
start = 1;
end
if(RST==0)
begin
de_index = 0;
de_predsample = 0;
end
if(start==1)
begin
if(!state)
begin
de_code[0] = CODEIN[0];
de_code[1] = CODEIN[1];
de_code[2] = CODEIN[2];
sb = CODEIN[3];
de_diffq = ((de_step_table(de_index)*de_code)>>2)+(de_step_table(de_index) >>3);
end
else
begin
if(de_diffq[16])
diffq = 32767;
else
diffq = de_diffq[15:0];
if(sb)
de_prevsample = de_predsample-diffq;
else
de_prevsample = de_predsample+diffq;
if (de_prevsample>32767) de_prevsample=32767;
else if (de_prevsample<-32768) de_prevsample=-32768;
de_predsample= de_prevsample[15:0];
PCMOUT = de_predsamp
展开阅读全文