DSP tutorial: FSK decoder

This example is a very basic (A)FSK decoder. It doesn’t look for a start byte, uses no bit synchronization. It oversamples 3 times (divides one bit length count of samples into 3 pieces called chunks) and decides between 0 and 1 by calculating the dominant frequency in these chunks. After the loop gathered 8 bits it reconstructs the sent ASCII byte and displays it. Operates poorly even with a little noise on the channel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public void run() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); // this will store sound data

        int oneBitSampleCount = (int)Math.round(SAMPLERATE/BITSPERSEC);
        int oneChunkSampleCount = (int)Math.round(oneBitSampleCount/3);
        System.out.println("One bit length: " + 1/BITSPERSEC + " seconds, " + oneBitSampleCount + " samples");
        System.out.println("One chunk length: " + (1/BITSPERSEC)/3 + " seconds, " + oneChunkSampleCount + " samples");
        byte[] abBuffer = new byte[oneChunkSampleCount*2];
        double[] abBufferDouble = new double[oneChunkSampleCount];
        int[] chunkResults = new int[3];
        int chunkResultp = 0;
        int byteResult = 0;
        int byteResultp = 0;
        int numberOfInvalidResults = 0;

        fft = new DoubleFFT_1D(abBufferDouble.length);
        // we need to initialize a buffer where we store our samples as complex numbers. first value is the real part, second is the imaginary.
        double[] fftData = new double[abBuffer.length]; // size == samples * 2
        int binOfFreq0 = (int)Math.round((FREQ0/(double)SAMPLERATE)*fftData.length);
        int binOfFreq1 = (int)Math.round((FREQ1/(double)SAMPLERATE)*fftData.length);

        System.out.println("Bin of freq. 0 (" + FREQ0 + "Hz): " + binOfFreq0);
        System.out.println("Bin of freq. 1 (" + FREQ1 + "Hz): " + binOfFreq1);
       
        tdl.start();

        try {
            while (!Thread.interrupted()) {
                // waiting for the buffer to get filled
                while (tdl.available() < abBuffer.length)
                    Thread.sleep(0, 1); // without this, the audio will be choppy

                int bytesRead = tdl.read(abBuffer, 0, abBuffer.length);

                // converting frames stored as bytes to double values
                int samplesRead = bytesRead / 2;
                for (int i = 0; i < samplesRead; i++) {
                    abBufferDouble[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0;
                }

                for (int i = 0; i < fftData.length; i++)
                    fftData[i] = 0;
                for (int i = 0; i < samplesRead; i++) {
                    // copying audio data to the fft data buffer, imaginary part is 0
                    fftData[2 * i] = abBufferDouble[i];
                    fftData[2 * i + 1] = 0;
                }

                // calculating the fft of the data, so we will have spectral power of each frequency component
                // fft resolution (number of bins) is samplesNum, because we initialized with that value
                fft.complexForward(fftData);

                double PWRFreq0 = Math.sqrt(fftData[binOfFreq0] * fftData[binOfFreq0] + fftData[binOfFreq0+1] * fftData[binOfFreq0+1]);
                double PWRFreq1 = Math.sqrt(fftData[binOfFreq1] * fftData[binOfFreq1] + fftData[binOfFreq1+1] * fftData[binOfFreq1+1]);
               
                if (PWRFreq0 > PWRTHRESHOLD || PWRFreq1 > PWRTHRESHOLD) {
                    if (PWRFreq0*PWRFreq0-PWRFreq1*PWRFreq1 < 0)
                        chunkResults[chunkResultp] = 1;
                    else
                        chunkResults[chunkResultp] = 0;

                    //System.out.print("PWRFreq0: " + PWRFreq0 + " PWRFreq1: " + PWRFreq1 + " chunk[" + chunkResultp + "]=" + chunkResults[chunkResultp]);

                    chunkResultp++;
                    if (chunkResultp == 3) { // if we got 3 chunks
                        chunkResultp = 0;

                        int bitResult;
                        if (chunkResults[0] + chunkResults[1] + chunkResults[2] >= 2)
                            bitResult = 1;
                        else
                            bitResult = 0;
                        //System.out.print(" resulting bit: " + bitResult);

                        if (byteResultp == 0 && bitResult != 0) { // if the first bit of the byte is not 0
                            byteResultp = byteResult = chunkResultp = 0;
                        } else {
                            byteResult += bitResult << (7-byteResultp);
   
                            byteResultp++;
                            if (byteResultp == 8) {
                                System.out.println(" *** char: " + (char)byteResult + "(" + byteResult + ")");
                                byteResultp = byteResult = 0;
                            }
                        }
                    }
                    //System.out.println();
                    numberOfInvalidResults = 0;
                } else {
                    //System.out.println("invalid!");
                    numberOfInvalidResults++;
                }

                if (numberOfInvalidResults > 0) {
                    //System.out.println("reset");
                    numberOfInvalidResults = byteResultp = byteResult = chunkResultp = 0;
                }

                //System.out.println("Samples read: " + samplesRead + " sampleAfterSamples: " + bitChunkLengthInSamples);
                abBufferDouble[0] = 1;
                baos.write(getBytesFromDoubles(abBufferDouble, samplesRead), 0, samplesRead * 2);
            }
        } catch (InterruptedException e) {
        }

        tdl.stop();
        tdl.close();

        writeWavFile(baos.toByteArray(), baos.size() / 2, "output.wav");
    }

download (mirror2) (424.4 kb, 279 dls, today: 0)

Name (required)
E-mail (required - never shown publicly)
Webpage URL
Comment:
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> in your comment.

Trackback responses to this post

About me

Nonoo
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with. ... »

Twitter

Listening now

My favorite artists