实验目的
这是一个综合性的大型实验,通过搭建一个包括信源、信源编译码器、信道、信道编译码器等各模块在内的仿真通信系统,使学生能够加深对本课程各个重点章节的理解,更好地掌握通信的本质意义。
说明:
由于搭建一个完整通信系统的工作量较大,所以本实验可以使用Matlab等仿真工具。下面分别描述系统中各个模块的要求。
1.离散信源:要求能以指定的概率分布(p,p-1)产生0,1符号构成的二进制信源符号序列。
2.信源编码器:输入时上一步产生的二进制符号序列。要求能选择使用以下三种信源编码方式中的任何一种:
(1)无编码(直通)
(2)二进制香农-费诺编码
(3)二进制霍夫曼编码
当我们在上一步中指定信源的概率分布之后,就可以马上生成这几种编码的码表,实际的编码工作仅仅只是查表而已。当然,直接对上一步指定的信源进行编码是不合适的,需要先进行信源的扩展,换一句话说,需要确定信源分组的长度。这个长度N也是本系统的一个重要参数,是在系统运行之前由用户输入的。
3.信道编码器:输入是信源编码器输出的二进制符号序列。编码方式要求能选择使用以下三种信道编码方式中的任何一种:
(1)无编码
(2)3次重复编码
(3)Hamming(7,4)码
信道编码器是个简单的一一对应的函数转换模块,没有额外的控制参数,可以事先实现这三种编码器,统一其输入输出格式,运行时按照指定的类型直接使用即可。
4.信道:其输入时信道编码器输出的二进制符号序列。经过传输后输出被噪声干扰和损坏了的二进制符号序列。
要求能够模拟理想信道、给定错误概率为p的BSC以及给定符号0,1各自错误概率p,q的任意二进制信道。
5.信道译码器:由于信源经过信源编码器和信道编码器后的统计特性难以明确给出,所以此时理想译码器准则无法实施。
因此根据第四步给出的信道统计特性,选择采用极大似然译码准则进行译码。
6.信源译码器:在第二步确定信源编码器之后即可同时确定信源译码器。信源译码器的工作仅仅是简单的查表即可。实验要求
输入:各个模块的相关参数
输出:
1.信源产生的原始符号序列
2.信源译码器输出的符号序列
3.信道编码后的信息传输效率
4.整个通信过程的误比特率(BER)
5.信道编译码过程中产生的误码率(BLER)实验程序
package com.yrwan.commSysSL;
import java.util.Map;
import java.util.Scanner;
import com.yrwan.channel.BSC;
import com.yrwan.channelCode.ChannelCodeChoose;
import com.yrwan.sourceCode.SourceCodeChoose;
import com.yrwan.sourceCode.huffman.Huffman;
import com.yrwan.sourceCode.shannonfano.ShannonFano;
//信源编码中的huffman编码来源于网上资料,香农费诺编码尚未完成
public class Main {
private static double oriPr;//离散信源分布概率
private static int oriLen;//二进制序列长度
private static int sourceCodeType;//信源编码器
private static int channelCodeType;//信道编码器
private static int channelType;//信道选择
private static double errPr1=0;//传输错误概率
private static double errPr2=0;//传输错误概率
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
System.out.println("请输入离散信源概率分布和二进制序列长度:");
oriPr=scanner.nextDouble();
oriLen=scanner.nextInt();
System.out.println("请选择信源编码器(信源编码中的香农费诺编码尚未完成)");
System.out.println("1.无编码");
System.out.println("2.二进制香农-费诺编码");
System.out.println("3.二进制霍夫曼编码");
sourceCodeType=scanner.nextInt();
System.out.println("请选择信道编码器");
System.out.println("1.无编码");
System.out.println("2.3次重复编码");
System.out.println("3.Hamming(7,4)码");
channelCodeType=scanner.nextInt();
System.out.println("请选择信道");
System.out.println("1.理想信道");
System.out.println("2.给定错误概率为p的BSC信道");
System.out.println("3.给定符号0,1各自错误概率p,q");
channelType=scanner.nextInt();
if (channelType==1){
errPr1=0;
}else if (channelType==2){
System.out.println("请输入信道错误率");
errPr1=scanner.nextDouble();
}else if (channelType==3){
System.out.println("请输入0,1各自错误率如 0.2 0.5");
errPr1=scanner.nextDouble();
errPr1=scanner.nextDouble();
}
scanner.close();
/****开始运算****/
//生成离散信源,存放在int型数组中
int[] original = new int[oriLen];
for (int i = 0; i < oriLen; i++) {
if (Math.random() < oriPr) {
original[i] = 1;
}
else{
original[i] = 0;
}
}
//将int型数组转化为字符串
String originStr="";
for (int i = 0; i < original.length; i++) {
originStr=originStr+original[i];
}
//编码
Map<Character, Integer> statistics=null;
statistics=Huffman.statistics(originStr.toCharArray());
int[] sourceEncodeResult= SourceCodeChoose.encode(sourceCodeType,original,statistics);
int[] channelEncodeResult= ChannelCodeChoose.encode(channelCodeType,sourceEncodeResult);
int[] transmitResult;
if (channelType==1||channelType==2){
//如果是理想信道或者给定错误概率为p的BSC
transmitResult=BSC.send(channelEncodeResult,errPr1);
}else {
//如果指定0、1各自错误率
transmitResult=BSC.send2(channelEncodeResult,errPr1,errPr2);
}
//解码
int parityCount=0;//Hamming码校验位的长度
if (channelCodeType==3){
//如果是以海明码传输需要算出校验位的长度
parityCount=channelEncodeResult.length-sourceEncodeResult.length;
}
int[] channelDecodeResult=ChannelCodeChoose.decode(channelCodeType,transmitResult,parityCount);
int[] sourceDecodeResult=SourceCodeChoose.decode(sourceCodeType,channelDecodeResult,statistics);
//输出结果
println("原始序列",original);
println("信源编码后序列",sourceEncodeResult);
println("信道编码后序列",channelEncodeResult);
println("信道传输后序列",transmitResult);
println("信道解码后序列",channelDecodeResult);
println("信源解码后序列",sourceDecodeResult);
//BER 是在数据传输过程中比特被传错的概率
//误码率=传输中的误码/所传输的总码数*100%
//BLER 传输块经过CRC校验后的错误概率
double codeEffectiveness=(double) original.length/channelEncodeResult.length;//信道编码后的传输效率
double BER=(double)getErrorCount(sourceDecodeResult,original)/original.length;//整个过程中数据被传错的概率
double channelEncodeError=(double)getErrorCount(channelDecodeResult,sourceEncodeResult)/sourceDecodeResult.length;//信道编译码过程中传错的概率
System.out.printf("信道编码后的信息传输效率:%.2f%%\n",codeEffectiveness*100);
System.out.printf("整个通信过程中的误比特率:%.2f%%\n",BER*100);
System.out.printf("信道编译码过程中产生的误码率:%.2f%%\n",channelEncodeError*100);
}
private static int getErrorCount(int[] data1, int[] data2){
if (data1.length != data2.length) return 0;
int errorCount=0;
for (int i = 0; i < data1.length; i++) {
if (data1[i]!=data2[i]){
errorCount++;
}
}
return errorCount;
}
private static void println(String message,int[] data){
System.out.println(message);
for (int i:data){
System.out.printf(i+" ");
}
System.out.println("\n====================");
}
}
package com.yrwan.channel;
public class BSC {
//经BSC传输信号,返回传输后的值
public static int[] send(int[] data,double errPr){
int[] x=new int[data.length];
x = data;
for(int i = 0; i<data.length;i++)
if(Math.random()<errPr){
x[i] = 1 - x[i];
}
return x;
}
//为0,1时,error1为1的错误概率,error2为0的错误概率
public static int[] send2(int[] data,double error1,double error2){
int[] x=new int[data.length];
x = data;
for(int i = 0; i<x.length;i++)
if (x[i] == 1){
//如果是1
if(Math.random()<error1){
x[i] = 1 - x[i];
}
}else {
//如果是0
if(Math.random()<error2){
x[i] = 1 - x[i];
}
}
return x;
}
}
package com.yrwan.channelCode;
public class ChannelCodeChoose {
public static int[] encode(int type,int[] data){
int[] result;
switch (type){
case 1:
//无编码
result=data;
break;
case 2:
//3次重复编码
result= ThreeTimes.encode(data);
break;
case 3:
//Hamming(7,4)编码
result= Hamming.generateHamming(data);
break;
default:
System.out.println("请选择正确的信道编码器");
result=new int[0];
System.exit(0);
break;
}
return result;
}
public static int[] decode(int type,int[] data,int length){
int[] result;
switch (type){
case 1:
//无编码
result=data;
break;
case 2:
//3次重复编码
result= ThreeTimes.decode(data);
break;
case 3:
//Hamming(7,4)编码
result=Hamming.decode(data,length);
break;
default:
System.out.println("请选择正确的信道编码器");
result=new int[0];
System.exit(0);
break;
}
return result;
}
}
package com.yrwan.channelCode;
import java.util.ArrayList;
import java.util.List;
public class ThreeTimes {
//生成三次重复码
public static int[] encode(int[] data){
List<Integer> list=new ArrayList<>();
for (int i = 0; i < data.length; i++) {
list.add(data[i]);
list.add(data[i]);
list.add(data[i]);
}
int[] r=new int[list.size()];
for (int i = 0; i < list.size(); i++) {
r[i]=list.get(i);
}
return r;
}
//解码三次重复码
public static int[] decode(int[] data){
int[] result=new int[data.length/3];
for (int i = 0,m=0; i < data.length; i=i+3,m++) {
int i1=data[i];
int i2=data[i+1];
int i3=data[i+2];
if (i1==i2||i2==i3){
result[m]=i2;
}else if (i1==i3){
result[m]=i1;
}
}
return result;
}
}
package com.yrwan.channelCode;
import java.util.ArrayList;
//本节程序在网上查阅资料得到
public class Hamming {
//生成hamming 码
static public int[] generateHamming(int[] a) {
int b[];
int i=0, parity_count=0 ,j=0, k=0;
while(i < a.length) {
if(Math.pow(2,parity_count) == i+parity_count + 1) {
parity_count++;
}
else {
i++;
}
}
b = new int[a.length + parity_count];
for(i=1 ; i <= b.length ; i++) {
if(Math.pow(2, j) == i) {
b[i-1] = 2;
j++;
}
else {
b[k+j] = a[k++];
}
}
for(i=0 ; i < parity_count ; i++) {
b[((int) Math.pow(2, i))-1] = getParity(b, i);
}
return b;
}
static int getParity(int b[], int power) {
int parity = 0;
for(int i=0 ; i < b.length ; i++) {
if(b[i] != 2) {
int k = i+1;
String s = Integer.toBinaryString(k);
int x = ((Integer.parseInt(s))/((int) Math.pow(10, power)))%10;
if(x == 1) {
if(b[i] == 1) {
parity = (parity+1)%2;
}
}
}
}
return parity;
}
//将收到的hamming码解码出来
//a 收到的hamming码数组 校验码个数(生成的hamming码长度-原始长度)
public static int[] decode(int a[], int parity_count) {
int power;
int parity[] = new int[parity_count];
String syndrome = new String();
for(power=0 ; power < parity_count ; power++) {
for(int i=0 ; i < a.length ; i++) {
int k = i+1;
String s = Integer.toBinaryString(k);
int bit = ((Integer.parseInt(s))/((int) Math.pow(10, power)))%10;
if(bit == 1) {
if(a[i] == 1) {
parity[power] = (parity[power]+1)%2;
}
}
}
syndrome = parity[power] + syndrome;
}
int error_location = Integer.parseInt(syndrome, 2);
if(error_location != 0) {
a[error_location-1] = (a[error_location-1]+1)%2;
}
power = parity_count-1;
ArrayList<Integer> list=new ArrayList<>();
for(int i=a.length ; i > 0 ; i--) {
if(Math.pow(2, power) != i) {
list.add(a[i-1]);
}
else {
power--;
}
}
int[] result=new int[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i]=list.get(list.size()-1-i);
}
return result;
}
}
package com.yrwan.sourceCode;
import java.util.Map;
import com.yrwan.sourceCode.huffman.Huffman;
import com.yrwan.sourceCode.shannonfano.ShannonFano;
public class SourceCodeChoose {
public static int[] encode(int type, int[] data, Map<Character, Integer> statistics){
int[] result=new int[0];
String oriStr="";
for (int aData : data) {
oriStr = oriStr + aData;
}
String strResult="";
switch (type){
case 1:
//无编码
result=data;
break;
case 2:
//二进制香农-费诺编码
//strResult = ShonnonFano.encode(oriStr);
break;
case 3:
//二进制霍夫曼编码
strResult = Huffman.encode(oriStr,statistics);
break;
default:
System.out.println("请选择正确的信源编码器");
break;
}
//如果是ShannonFano或者Huffman编码将其转化为数组
if (strResult!=null&&strResult.length()!=0){
char[] charResult=strResult.toCharArray();
result=new int[charResult.length];
for (int i = 0; i < charResult.length; i++) {
if (charResult[i]=='1')
result[i]=1;
else
result[i]=0;
}
}
return result;
}
public static int[] decode(int type, int[] data, Map<Character, Integer> statistics){
int[] result = new int[0];
String oriStr="";
for (int i = 0; i < data.length; i++) {
oriStr=oriStr+data[i];
}
String strResult="";
switch (type){
case 1:
//无编码
result=data;
break;
case 2:
//二进制香农-费诺编码
//strResult = ShonnonFano.decode(oriStr);
break;
case 3:
//二进制霍夫曼编码
strResult = Huffman.decode(oriStr,statistics);
break;
default:
System.out.println("请选择正确的信源编码器");
break;
}
//如果是ShannonFano或者Huffman编码将其转化为数组
if (strResult!=null&&strResult.length()!=0){
char[] charResult=strResult.toCharArray();
result=new int[charResult.length];
for (int i = 0; i < charResult.length; i++) {
if (charResult[i]=='1')
result[i]=1;
else
result[i]=0;
}
}
return result;
}
}
package com.yrwan.sourceCode.huffman;
import java.nio.charset.Charset;
import java.util.*;
public class Huffman {
/**
* 参考来源:http://blog.csdn.net/kimylrong/article/details/17022319
* 既然要按频率来安排编码表,那么首先当然得获得频率的统计信息。
* 如果已经有统计信息,那么转为Map<Character,Integer>即可。
* 如果你得到的信息是百分比,乘以100或1000,或10000。总是可以转为整数。
* 比如12.702%乘以1000为12702,Huffman编码只关心大小问题。
* @param charArray
* @return
*/
public static Map<Character, Integer> statistics(char[] charArray) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
for (char c : charArray) {
Character character = new Character(c);
if (map.containsKey(character)) {
map.put(character, map.get(character) + 1);
} else {
map.put(character, 1);
}
}
return map;
}
/**构建树
* 构建树是Huffman编码算法的核心步骤。
* 思想是把所有的字符挂到一颗完全二叉树的叶子节点,
* 任何一个非页子节点的左节点出现频率不大于右节点。
* 算法为把统计信息转为Node存放到一个优先级队列里面,每一次从队列里面弹出两个最小频率的节点,
* 构建一个新的父Node(非叶子节点), 字符内容刚弹出来的两个节点字符内容之和,
* 频率也是它们的和,最开始的弹出来的作为左子节点,后面一个作为右子节点,
* 并且把刚构建的父节点放到队列里面。重复以上的动作N-1次,N为不同字符的个数(每一次队列里面个数减1)。
* 结束以上步骤,队列里面剩一个节点,弹出作为树的根节点。
*/
private static Tree buildTree(Map<Character, Integer> statistics,List<Node> leafs) {
Character[] keys = statistics.keySet().toArray(new Character[0]);
PriorityQueue<Node> priorityQueue = new PriorityQueue<Node>();
for (Character character : keys) {
Node node = new Node();
node.chars = character.toString();
node.frequence = statistics.get(character);
priorityQueue.add(node);
leafs.add(node);
}
int size = priorityQueue.size();
for (int i = 1; i <= size - 1; i++) {
Node node1 = priorityQueue.poll();
Node node2 = priorityQueue.poll();
Node sumNode = new Node();
sumNode.chars = node1.chars + node2.chars;
sumNode.frequence = node1.frequence + node2.frequence;
sumNode.leftNode = node1;
sumNode.rightNode = node2;
node1.parent = sumNode;
node2.parent = sumNode;
priorityQueue.add(sumNode);
}
Tree tree = new Tree();
tree.root = priorityQueue.poll();
return tree;
}
/**编码
* 某个字符对应的编码为,从该字符所在的叶子节点向上搜索,
* 如果该字符节点是父节点的左节点,编码字符之前加0,
* 反之如果是右节点,加1,直到根节点。
* 只要获取了字符和二进制码之间的mapping关系,编码就非常简单。
*/
public static String encode(String originalStr,Map<Character, Integer> statistics) {
if (originalStr == null || originalStr.equals("")) {
return "";
}
char[] charArray = originalStr.toCharArray();
List<Node> leafNodes = new ArrayList<Node>();
buildTree(statistics, leafNodes);
Map<Character, String> encodInfo = buildEncodingInfo(leafNodes);
StringBuffer buffer = new StringBuffer();
for (char c : charArray) {
Character character = new Character(c);
buffer.append(encodInfo.get(character));
}
return buffer.toString();
}
private static Map<Character, String> buildEncodingInfo(List<Node> leafNodes) {
Map<Character, String> codewords = new HashMap<Character, String>();
for (Node leafNode : leafNodes) {
Character character = new Character(leafNode.getChars().charAt(0));
String codeword = "";
Node currentNode = leafNode;
do {
if (currentNode.isLeftChild()) {
codeword = "0" + codeword;
} else {
codeword = "1" + codeword;
}
currentNode = currentNode.parent;
} while (currentNode.parent != null);
codewords.put(character, codeword);
}
return codewords;
}
/**解码
* 因为Huffman编码算法能够保证任何的二进制码都不会是另外一个码的前缀,解码非常简单,
* 依次取出二进制的每一位,从树根向下搜索,1向右,0向左,到了叶子节点(命中),退回根节点继续重复以上动作。
*/
public static String decode(String binaryStr,
Map<Character, Integer> statistics) {
if (binaryStr == null || binaryStr.equals("")) {
return "";
}
char[] binaryCharArray = binaryStr.toCharArray();
LinkedList<Character> binaryList = new LinkedList<Character>();
int size = binaryCharArray.length;
for (int i = 0; i < size; i++) {
binaryList.addLast(new Character(binaryCharArray[i]));
}
List<Node> leafNodes = new ArrayList<Node>();
Tree tree = buildTree(statistics, leafNodes);
StringBuffer buffer = new StringBuffer();
while (binaryList.size() > 0) {
Node node = tree.root;
do {
Character c = binaryList.removeFirst();
if (c.charValue() == '0') {
node = node.leftNode;
} else {
node = node.rightNode;
}
} while (!node.isLeaf());
buffer.append(node.chars);
}
return buffer.toString();
}
public static String getStringOfByte(String str, Charset charset) {
if (str == null || str.equals("")) {
return "";
}
byte[] byteArray = str.getBytes(charset);
int size = byteArray.length;
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < size; i++) {
byte temp = byteArray[i];
buffer.append(getStringOfByte(temp));
}
return buffer.toString();
}
public static String getStringOfByte(byte b) {
StringBuffer buffer = new StringBuffer();
for (int i = 7; i >= 0; i--) {
byte temp = (byte) ((b >> i) & 0x1);
buffer.append(String.valueOf(temp));
}
return buffer.toString();
}
}
package com.yrwan.sourceCode.huffman;
/**
* 参考来源:http://blog.csdn.net/kimylrong/article/details/17022319
* Huffman编码算法主要用到的数据结构是完全二叉树(full binary tree)和优先级队列。
* 后者用的是Java.util.PriorityQueue,前者自己实现(都为内部类),
*/
public class Node implements Comparable<Node> {
public String chars = "";
public int frequence = 0;
public Node parent;
public Node leftNode;
public Node rightNode;
public int compareTo(Node n) {
return frequence - n.frequence;
}
public boolean isLeaf() {
return chars.length() == 1;
}
public boolean isRoot() {
return parent == null;
}
public boolean isLeftChild() {
return parent != null && this == parent.leftNode;
}
public int getFrequence() {
return frequence;
}
public void setFrequence(int frequence) {
this.frequence = frequence;
}
public String getChars() {
return chars;
}
public void setChars(String chars) {
this.chars = chars;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public Node getLeftNode() {
return leftNode;
}
public void setLeftNode(Node leftNode) {
this.leftNode = leftNode;
}
public Node getRightNode() {
return rightNode;
}
public void setRightNode(Node rightNode) {
this.rightNode = rightNode;
}
}
class Tree{
public Node root;
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
}