聲音信號編碼和FFT解碼

        最近我在一個隊伍裏參加SOLVEFOR TOMORROM探知未來2014年全國青年科普創新實踐暨作品大賽。從廣州賽區毫無懸念地進入到北京賽區決賽,然後決賽的規則更加噁心,這個時候隊長提出要使用聲音傳遞信號,網上是有個開源的軟件:https://github.com/JesseGu/SinVoice

         隊長是我們專業裏的大牛,把這個開源軟件改寫了,但是發現聲音傳送的的速度無法滿足要求,於是轉而研究音頻傳輸文件。好了,我說了隊長是大牛,可是在研究的時候遇到一個問題,就是要將聲音信號採集,然後將聲音的時域信號轉化爲頻域信號,這個時候就要用到FFT(快速傅里葉變換),隊長見我一直在打醬油,就把這個任務交給了我。

       我當時心裏就一個靠,哥去年的高數都差點掛了,傅里葉我去你大爺的,爺們兒不會啊。於是乎便上百度,找到的FFT算法採樣點都特麼要是2的整數次方,隊長給我的卻是4410個採樣點,這尼瑪我在百度裏一番搗騰,要麼就是寫得太渣,要我自己去研究高數還不如弄死我。最後翻牆谷歌找到一個不錯的FFT的Java算法,之後對其進行了修改。

       好的,現在來談談,我解決的問題。我要在0.1秒鐘裏用聲音傳輸2個字節的數據,首先要對這段兩個字節進行編碼,例如我要傳送的是0x31,0xA7,首先將其編碼,片段如下:

private final static double sampling_rate = 4410;
	private static double[] encode = new double[4410];           //定義編碼數組
	private static boolean[] single = new boolean[16];       //定義標記數組
	private static double[] t = new double[4410];

public static double[] t_encode(){                     //初始化t
		for(int i = 0; i < t.length; i++){
			t[i] = (double) (1.0/sampling_rate*i);
		}
		return t;
	}
public static boolean[] single_encode(byte[] message){                 //編碼single	
		String ZERO="00000000";
		String[] s_ = new String[2];
		for(int i = 0; i < message.length; i++){
			String s = Integer.toBinaryString(message[i]);
			if(s.length() > 8){
				s = s.substring(s.length() - 8);
			}
			else if(s.length() < 8){
				s = ZERO.substring(s.length()) + s;
			}
//			System.out.println(s);
			s_[i] = s;
		}		
		
	    char[] c = new char[16];
	    String se = s_[0] + s_[1];
	    c = se.toCharArray();
//	    System.out.println(c);
	    for(int i = 0; i < 16; i++){
	    	if( c[i] == '1'){
	    		single[i] = true; 
	    	}
	    	else{
	    		single[i] = false;
	    	}
	    }
	    return single;
	}
	
	public static double[] encode_(boolean[] single){                                 //初始化encode
		for(int i = 0; i < encode.length; i++){
			for(int j = 0; j < single.length; j++){
				if(!single[j]) continue;
				int rate = 4000 + 1000 * j;
				encode[i] += 8 * Math.sin(2 * Math.PI * rate * t[i]);
			}
		}
		return encode;
	}
       上面的三個方法完成了對byte數組的編碼,然後再通過聲音發射出去,然後重點來了,下一個問題就是對採集的聲音信號經過FFT處理,將頻率散佈在0 -- 44100HZ之間,在3000HZ到20000HZ之間取1000HZ爲間隔設立信號點,3000HZ有峯值就代表start,4000HZ,5000HZ,6000HZ,......19000HZ剛好構成了兩個字節,20000HZ代表end。FFT經過變換後是一個對稱的圖形,我們去前面2205個點判斷峯值,並進行解碼得到數組。廢話不多說,上代碼。

FFT:

public class FFT {
	
	
	public static Complex[] fft(Complex[] x){
		int N = x.length;
		Complex[] y = new Complex[N];
		y = x;
		double[] real = new double[N];
		double[] imag = new double[N];
		for(int i = 0; i < N; i++){
			real[i] = x[i].re;
			imag[i] = x[i].im;
		}
		transform(real,imag);
		for(int i = 0; i < N; i++){
			y[i].re = real[i];
			y[i].im = imag[i];
		}
		return y;
	}
	/* 
	 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
	 * The vector can have any length. This is a wrapper function.
	 */
	public static void transform(double[] real, double[] imag) {
		if (real.length != imag.length)
			throw new IllegalArgumentException("Mismatched lengths");
		
		int n = real.length;
		if (n == 0)
			return;
		else if ((n & (n - 1)) == 0)  //N是2的整數次方
			transformRadix2(real, imag);
		else  //N不是2的整數次方,跳轉到更爲複雜的計算方法
			transformBluestein(real, imag);
	}
	
	
	/* 
	 * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector.
	 * The vector can have any length. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse.
	 */
	public static void inverseTransform(double[] real, double[] imag) {
		transform(imag, real);
	}
	
	
	/* 
	 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
	 * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm.
	 */
	public static void transformRadix2(double[] real, double[] imag) {
		// Initialization
		if (real.length != imag.length)
			throw new IllegalArgumentException("Mismatched lengths");
		int n = real.length;
		int levels = 31 - Integer.numberOfLeadingZeros(n);  // Equal to floor(log2(n))
		if (1 << levels != n)
			throw new IllegalArgumentException("Length is not a power of 2");
		double[] cosTable = new double[n / 2];
		double[] sinTable = new double[n / 2];
		for (int i = 0; i < n / 2; i++) {
			cosTable[i] = Math.cos(2 * Math.PI * i / n);
			sinTable[i] = Math.sin(2 * Math.PI * i / n);
		}
		
		// Bit-reversed addressing permutation
		for (int i = 0; i < n; i++) {
			int j = Integer.reverse(i) >>> (32 - levels);
			if (j > i) {
				double temp = real[i];
				real[i] = real[j];
				real[j] = temp;
				temp = imag[i];
				imag[i] = imag[j];
				imag[j] = temp;
			}
		}
		
		// Cooley-Tukey decimation-in-time radix-2 FFT
		for (int size = 2; size <= n; size *= 2) {
			int halfsize = size / 2;
			int tablestep = n / size;
			for (int i = 0; i < n; i += size) {
				for (int j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
					double tpre =  real[j+halfsize] * cosTable[k] + imag[j+halfsize] * sinTable[k];
					double tpim = -real[j+halfsize] * sinTable[k] + imag[j+halfsize] * cosTable[k];
					real[j + halfsize] = real[j] - tpre;
					imag[j + halfsize] = imag[j] - tpim;
					real[j] += tpre;
					imag[j] += tpim;
				}
			}
			if (size == n)  // Prevent overflow in 'size *= 2'
				break;
		}
	}
	
	
	/* 
	 * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
	 * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function.
	 * Uses Bluestein's chirp z-transform algorithm.
	 */
	public static void transformBluestein(double[] real, double[] imag) {
		// Find a power-of-2 convolution length m such that m >= n * 2 + 1
		if (real.length != imag.length)
			throw new IllegalArgumentException("Mismatched lengths");
		int n = real.length;
		if (n >= 0x20000000)
			throw new IllegalArgumentException("Array too large");
		int m = Integer.highestOneBit(n * 2 + 1) << 1;
		
		// Trignometric tables
		double[] cosTable = new double[n];
		double[] sinTable = new double[n];
		for (int i = 0; i < n; i++) {
			int j = (int)((long)i * i % (n * 2));  // This is more accurate than j = i * i
			cosTable[i] = Math.cos(Math.PI * j / n);
			sinTable[i] = Math.sin(Math.PI * j / n);
		}
		
		// Temporary vectors and preprocessing
		double[] areal = new double[m];
		double[] aimag = new double[m];
		for (int i = 0; i < n; i++) {
			areal[i] =  real[i] * cosTable[i] + imag[i] * sinTable[i];
			aimag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
		}
		double[] breal = new double[m];
		double[] bimag = new double[m];
		breal[0] = cosTable[0];
		bimag[0] = sinTable[0];
		for (int i = 1; i < n; i++) {
			breal[i] = breal[m - i] = cosTable[i];
			bimag[i] = bimag[m - i] = sinTable[i];
		}
		
		// Convolution
		double[] creal = new double[m];
		double[] cimag = new double[m];
		convolve(areal, aimag, breal, bimag, creal, cimag);
		
		// Postprocessing
		for (int i = 0; i < n; i++) {
			real[i] =  creal[i] * cosTable[i] + cimag[i] * sinTable[i];
			imag[i] = -creal[i] * sinTable[i] + cimag[i] * cosTable[i];
		}
	}
	
	
	/* 
	 * Computes the circular convolution of the given real vectors. Each vector's length must be the same.
	 */
	public static void convolve(double[] x, double[] y, double[] out) {
		if (x.length != y.length || x.length != out.length)
			throw new IllegalArgumentException("Mismatched lengths");
		int n = x.length;
		convolve(x, new double[n], y, new double[n], out, new double[n]);
	}
	
	
	/* 
	 * Computes the circular convolution of the given complex vectors. Each vector's length must be the same.
	 */
	public static void convolve(double[] xreal, double[] ximag, double[] yreal, double[] yimag, double[] outreal, double[] outimag) {
		if (xreal.length != ximag.length || xreal.length != yreal.length || yreal.length != yimag.length || xreal.length != outreal.length || outreal.length != outimag.length)
			throw new IllegalArgumentException("Mismatched lengths");
		
		int n = xreal.length;
		xreal = (double[]) xreal.clone();
		ximag = (double[]) ximag.clone();
		yreal = (double[]) yreal.clone();
		yimag = (double[]) yimag.clone();
		
		transform(xreal, ximag);
		transform(yreal, yimag);
		for (int i = 0; i < n; i++) {
			double temp = xreal[i] * yreal[i] - ximag[i] * yimag[i];
			ximag[i] = ximag[i] * yreal[i] + xreal[i] * yimag[i];
			xreal[i] = temp;
		}
		inverseTransform(xreal, ximag);
		for (int i = 0; i < n; i++) {  // Scaling (because this FFT implementation omits it)
			outreal[i] = xreal[i] / n;
			outimag[i] = ximag[i] / n;
		}
	}
	
    // display an array of Complex numbers to standard output
    public static void show(Complex[] x, String title) {
        System.out.println(title);
        System.out.println("-------------------");
        for (int i = 0; i < x.length; i++) {
            System.out.println(x[i]);
        }
        System.out.println();
    }
    
    public static void main(String[] args) { 
        int N = Integer.parseInt("5");
        Complex[] x = new Complex[N];

        // original data
        for (int i = 0; i < N; i++) {
            x[i] = new Complex(i, 0);
            x[i] = new Complex(-2*Math.random() + 1, 0);
        }
        show(x, "x");

        // FFT of original data
        Complex[] y = fft(x);
        show(y, "y = fft(x)");
    }
	
}
接下來是Complex:


/*************************************************************************
 *  Compilation:  javac Complex.java
 *  Execution:    java Complex
 *
 *  Data type for complex numbers.
 *
 *  The data type is "immutable" so once you create and initialize
 *  a Complex object, you cannot change it. The "final" keyword
 *  when declaring re and im enforces this rule, making it a
 *  compile-time error to change the .re or .im fields after
 *  they've been initialized.
 *
 *  % java Complex
 *  a            = 5.0 + 6.0i
 *  b            = -3.0 + 4.0i
 *  Re(a)        = 5.0
 *  Im(a)        = 6.0
 *  b + a        = 2.0 + 10.0i
 *  a - b        = 8.0 + 2.0i
 *  a * b        = -39.0 + 2.0i
 *  b * a        = -39.0 + 2.0i
 *  a / b        = 0.36 - 1.52i
 *  (a / b) * b  = 5.0 + 6.0i
 *  conj(a)      = 5.0 - 6.0i
 *  |a|          = 7.810249675906654
 *  tan(a)       = -6.685231390246571E-6 + 1.0000103108981198i
 *
 *************************************************************************/

public class Complex {
    public double re;   // the real part
    public double im;   // the imaginary part

    // create a new object with the given real and imaginary parts
    public Complex(double real, double imag) {
        re = real;
        im = imag;
    }

    // return a string representation of the invoking Complex object
    public String toString() {
        if (im == 0) return re + "";
        if (re == 0) return im + "i";
        if (im <  0) return re + " - " + (-im) + "i";
        return re + " + " + im + "i";
    }

    // return abs/modulus/magnitude and angle/phase/argument
    public double abs()   { return Math.hypot(re, im); }  // Math.sqrt(re*re + im*im)
    public double phase() { return Math.atan2(im, re); }  // between -pi and pi

    // return a new Complex object whose value is (this + b)
    public Complex plus(Complex b) {
        Complex a = this;             // invoking object
        double real = a.re + b.re;
        double imag = a.im + b.im;
        return new Complex(real, imag);
    }

    // return a new Complex object whose value is (this - b)
    public Complex minus(Complex b) {
        Complex a = this;
        double real = a.re - b.re;
        double imag = a.im - b.im;
        return new Complex(real, imag);
    }

    // return a new Complex object whose value is (this * b)
    public Complex times(Complex b) {
        Complex a = this;
        double real = a.re * b.re - a.im * b.im;
        double imag = a.re * b.im + a.im * b.re;
        return new Complex(real, imag);
    }

    // scalar multiplication
    // return a new object whose value is (this * alpha)
    public Complex times(double alpha) {
        return new Complex(alpha * re, alpha * im);
    }

    // return a new Complex object whose value is the conjugate of this
    public Complex conjugate() {  return new Complex(re, -im); }

    // return a new Complex object whose value is the reciprocal of this
    public Complex reciprocal() {
        double scale = re*re + im*im;
        return new Complex(re / scale, -im / scale);
    }

    // return the real or imaginary part
    public double re() { return re; }
    public double im() { return im; }

    // return a / b
    public Complex divides(Complex b) {
        Complex a = this;
        return a.times(b.reciprocal());
    }

    // return a new Complex object whose value is the complex exponential of this
    public Complex exp() {
        return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
    }

    // return a new Complex object whose value is the complex sine of this
    public Complex sin() {
        return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
    }

    // return a new Complex object whose value is the complex cosine of this
    public Complex cos() {
        return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
    }

    // return a new Complex object whose value is the complex tangent of this
    public Complex tan() {
        return sin().divides(cos());
    }
    


    // a static version of plus
    public static Complex plus(Complex a, Complex b) {
        double real = a.re + b.re;
        double imag = a.im + b.im;
        Complex sum = new Complex(real, imag);
        return sum;
    }
}
       最後求峯值我是直接設定一個高度,將FFT變換後的點取模值,然後設定一個高度,比如90000,大於10000的爲1,就是峯值,其他的爲0。這樣就解碼出來了。

      如果考慮到雜音的干擾,可以調高0bit的峯值,比如講0bit的峯值改成1bit峯值的三分之一。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章