本博客运行环境为Windows下Visual C++6.0
一、主要内容
实现信源编解码:PCM编码+ 数据压缩+信道(加性噪声)+数据解压缩+PCM译码。利用C语言使编码器实现输入信号完成PCM技术的三个过程:采样、量化与编码,解码器实现还原原信号过程。
二、设计目的
脉冲编码调制PCM是模/数变换中最基本和最常用的编码方式,结合《通信原理教程》课程。培养我们的实际动手能力。对PCM编码和译码的原理及其性能有更深的认识。
三、设计要求
1、掌握PCM的编码和译码的原理;在同一台PC上完成编码与解码,编码与解码模块需要用C/C++语言编写。
2、掌握PCM编码的三个基本过程: 抽样、量化、编码。将输入的模拟语音信号变换为数字信号,在数字通信系统中进行传输。
3、掌握解码还原原信号过程,观察输出波形。
四、系统设计
1、通信系统的原理
通信的目的是传输信息。通信系统的作用就是将信息从信源发送到一一个或多个目的地。对于电通信来说,首先要把消息转变成电信号,然后经过发送设备,将信号送入信道,在接收端利用接收设备对接收信号作相应的处理后,送给信宿再转换为原来的消息。这一过程可用图1-3所示的通信系统一般模型来概括。
数字通信系统是利用数字信号来传递消息的通信系统,如下图所示。其中主要有信源编码和译码、信道编码与译码、数字调制与解调、同步以及加密与解密等。
(1)信源编码和信源解码
信源编码有两个作用,其一,进行模/数转换;其二,数据压缩,即设法降低数字信号的数码率,提高数字信号传输的有效性。信源解码的作用是进行数/模转换。
(2)信道编码与信道解码
数字信号在信道中传输时,由于噪声影响,会引起差错,信道编码就是要降低传输的差错率,对传输的信息码元按一定的规则加入保护成组成所谓“抗干扰编码”。接收端分(监督元),的信道解码器按一定规则进行解码,从解码过程中发现错误或纠正错误,从而提高通信系统抗干扰能力,提高传输可靠性。
(3) 加密器和解密器
在需要实现保密通信的场合,为了保证所传信息的安全,人为将被传输的数字序列扰乱,即加上密码,这种处理过程叫加密。在接收端利用与发送端相同的密码复制品对收到的数字序列恢复原来信息,这个过程叫做解密。
(4)数字调制与解调
数字调制就是把数字基带信号的频谱搬移到高频处,形成适合在信道中传输的带通信号。
此次我主要设计的子系统是将模拟信号变为二进制信号的脉冲编码调制系统(PCM编码译码系统)。
2.脉冲编码调制系统的原理
PCM系统原理如下图所示,在发送端,对输入的模拟信号m(t)进行抽样、量化和编码。编码后的PCM 信号是一个二进制数字序列,其传输方式可以采用数字基带传输,也可以是对载波调制后的带通传输。在接收端,PCM信号经译码后还原为量化值序列(有误差),再经低通滤波器除高频分量,便得到重建的模拟信号m(t)。
脉冲编码调制PCM主要包括抽样、量化与编码三个过程。抽样是把连续时间的模拟信号转换成离散时间连续幅度的抽样信号;量化是把离散时间连续幅度的抽样信号转换成离散时间离散幅度的数字信号;编码是用二进制代码来表示抽样后的信号。
在PCM中,对模报信号进行抽样、量化,将量化的信号电平值转化为对应的二进制码组的过程称为编码。其逆过程称为译码或解码。从理论上看,任何一个可逆的进制码组均可用于PCM.但是目前最常见的进制码有类:二进制自然码(NBC).折叠二进制(FBC)、格雷二进制码(RBC)。在PCM中实际采用的是折叠二进制码。
由下图可见,如果把16个量化级分成两部分: 0 ~ 7的8个量化级对于于负极性样值脉冲,8~15的8个量化级对应于正极性样值脉冲。自然二进制码就是一般的十进制正整数的二进制表示,在16个量化级中:2^ 4 =16.采用4位码元表示:b1=2^ 3,b2=2^ 2,b3=2^ 1,b4=2^ 0的有无组合来构成。比如第11个量化级可表示为11=2^ 3+0+2^ 1+2^0=8+0+2+1其对应的码组可表示为: 1011.其余依次类推。本程序中采用自然码的编码方式。
CCITT建议的PCM编码规则,电话语音信号的频带为300~34002,抽样速率为fs=8Hz,对每个抽样值进行A律或者r律对数压缩非均匀量化及非线性编码,每个样值八位二进制代码表示,这样,每路标准话路的比特率为64kbps。
负值编码是对称的,其绝对值与此表相同。整个信号动态范围共分13个段落,各段落的量化间隔都不同,并且有2的倍数关系。每个段落内位均匀分层量化,共16层。每个样值用8比特来表示,即[b1][b2b3b4][b5b6b7b8]这8比特分为三部分:b1为极性码,0代表负值,1代表正值。[b2~b4]称为段落码,表示段落的号码。其值为0-7.代表8个段落。[b5b6b7b8]表示每个段落内均匀分层的位置。其值为0-15,代表一段落内的16个均匀量化间隔。在PCM解码时,根据入比特码确定某段落内均勾分层的位置,然后去其量化间隔的中间值作为量化电平。
本程序首先产生一个正弦信号,并对其进行采样量化。生成一个幅值矩阵:然后利用编码子函数对此矩降中的每个元素按照A律13折线编码规则编码。并产生一个输出码组矩阵; 最后利用解码子函数对输出码组矩阵解码,并画出编码前与解码后的波形图。
3.程序流程图
五、详细设计与编码
1. 设计方案
模拟输入系统,产生模拟正弦信号,对模拟信号进行采样、量化、PCM编码,此时的编码信号是一个二进制数列,其传输方式可以采用数字基带传输,也可以是对载波调制后的带通传输。在接收端,PCM信号经译码后还原为量化值序列(有误差),再经低通滤波器除高频分量,便得到重建的模拟信号m(t)。
编码:先输入幅度值,然后根据幅度值得出PCM码。根据编码电平在得出编码后的量化误差;根据译码值在得出译码后的量化误差。译码:依次输入PCM码值,然后输出所求幅度值。
2. 编程工具的选择
使用Visual C++6.0编写程序来实现PCM编码与解码,由于前两年的学习中一直使用visual c++6.0编译程序,对它比较熟悉。
Visual C++6.0的特点:
Visual C++6.0以拥有“语法高亮”,自动编译功能以及高级除错功能而著称。比如,它允许用户进行远程调试,单步执行等。还有允许用户在调试期间重新编译被修改的代码,而不必重新启动正在调试的程序。其编译及创建预编译头文件(stdafx.h)、最小重建功能及累加连结(link)著称。这些特征明显缩短程序编辑、编译及连结的时间花费,在大型软件计划上尤其显著。
3. 编码与测试
代码如下:
#include<iostream>
#include<math.h>
#include <string>
using namespace std;
#define N 7
int pcmCode[N] = { 16,32,64,128,256,512,1024 };//通过数组,用来确定段落码
int pcmInsideCode[N + 1] = { 1,1,2,4,8,16,32,64 }; //段内量化间隔
int pcmInsideSart[N + 1] = { 0,16,32,64,128,256,512,1024 }; //段落起始电平
int flag;
void pcmJudge(string &result, int testNum)//确定段落码
{
int low = 0, high = N, mid;
for (int i = 0; i < 3; i++)
{
mid = (low + high) / 2; //逐次比较,判断段落码为1或者0
if (testNum >= pcmCode[mid])
{
result = result + '1';
low = mid + 1;
flag = mid;
}
else
{
result = result + '0';
high = mid - 1;
flag = mid - 1;
}
}
}
void pcmInsideJudge(string &result, int testNum, int pcmStart, int pcmSpace)//确定段内码
{
int low = 0, high = 17, mid = 8;
for (int i = 0; i < 4; i++)
{
int cost = pcmStart + pcmSpace * mid; //段落起始电平+量化间隔*序列号
if (cost > testNum)
{
result = result + '0';
high = mid;
mid = (low + high) / 2;
}
else
{
result = result + '1';
low = mid;
mid = (low + high) / 2;
}
}
}
int main()
{
int testNum,testNum1,pick,errorNum;
while (1) {
cout << "----PCM编码与解码----\n";
cout << "1、PCM编码程序\n";
cout << "2、PCM解码程序\n";
cout << "---------------------\n";
cout << "\n";
cout << "请输入你的选择:";
cin >> pick;
if (pick == 1)
{
cout << "请输入幅度值: ";
cin >> testNum; // 循环实现
string result; //字节用来得到编码出来的pcm码组
if (testNum < 0)
result = result + '0';
else
result = result + '1';
testNum1 = testNum;
testNum = fabs(testNum);//去符号
pcmJudge(result, testNum);//判断段内码
//处理段内码
int pcmSeq = flag + 1; //量化间隔级数
int pcmStart = pcmInsideSart[pcmSeq];//段落起始电平
int pcmSpace = pcmInsideCode[pcmSeq];//量化间隔
pcmInsideJudge(result, testNum, pcmStart, pcmSpace);
cout << "编译出来的PCM码组: ";
cout << result << endl;
//量化误差
int sum = 0;
int j, cvb, cvb1,jyc, bmh; //cvb译码值;jyc编码电平;bmh编码后的量化误差
for (j = 4; j < N + 1; j++)
if (result[j] == '1')
sum = sum + pow(2, 7 - j);//确定量化级的序列号
if(testNum1 < 0)
{
cvb = -fabs(pcmStart + sum * pcmSpace + pcmSpace / 2); //为了使量化误差小于量化间隔的一半,译码后的值应该加上量化间隔的一半
cvb1 = fabs(cvb);
jyc= -fabs(cvb1 - pcmSpace / 2); //7位非线性码编译的电平
}
else
{
cvb = fabs(pcmStart + sum * pcmSpace + pcmSpace / 2);
jyc = fabs(cvb - pcmSpace / 2);
}
cout << "编码电平:" << jyc << endl;
if(testNum1 >= 0)
{
bmh = fabs(testNum - jyc); //编码后的量化误差
errorNum = fabs(cvb- testNum);
}
else
{
bmh = fabs(testNum + jyc);
errorNum = fabs(cvb+ testNum);
}
cout << "编码后的量化误差:" << bmh << endl;
cout << "译码值:" << cvb << endl;
cout << "译码后的量化误差: " << errorNum << endl;
cout << endl; }
else if(pick == 2)
{
int i, j, m, n = 16, k, y;
int a[8] = { 0 };
for (i = 0; i < 8; i++)
{
cout << "请输入PCM编码a[" << i << "]:";
cin >> a[i];
}
m = a[1] * 4 + a[2] * 2 + a[3];//段落序列号
i = m;
if (m == 0)
n = 0;
else
{
while (--i)
n = n * 2;
}
k = a[4] * 8 + a[5] * 4 + a[6] * 2 + a[7];
if (m <= 1)
y = n + k * 2 + 1;
else
y = n + k * n / 16 + n / 32;
if (a[0] == 0)
y = -y;
cout << "幅度值:" << y << endl;
cout << endl;
}
else {
cout << "输入有误,请重新输入!\n";
}
}
return 0;
}
编码:先输入幅度值,然后根据幅度值得出PCM码。根据编码电平在得出编码后的量化误差;根据译码值在得出译码后的量化误差。
译码:依次输入PCM码值,然后输出所求幅度值。结果存在一定的量化误差。
4. 运行结果及分析
输入幅度值为1250,运行得到结果如下图所示:
输入码组为11110011,运行得到结果如下图所示:
输入幅度值为-1250,运行得到结果如下图所示:
输入码组为01110011,运行得到结果如下图所示: