通信系统仿真

  • 实验目的
      这是一个综合性的大型实验,通过搭建一个包括信源、信源编译码器、信道、信道编译码器等各模块在内的仿真通信系统,使学生能够加深对本课程各个重点章节的理解,更好地掌握通信的本质意义。
      
    说明:
    由于搭建一个完整通信系统的工作量较大,所以本实验可以使用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;
    }
}

猜你喜欢

转载自blog.csdn.net/yrwan95/article/details/73697063
今日推荐