DSP tutorial: RTTY decoder using FFT & DPLL
Last modified: March 4th, 2012This example is still a basic RTTY decoder, similar to the previously discussed one, the main difference is that it looks for the start bit and keeps bit sync using a simple but efficient DPLL algorithm. Like before, it uses the oversample & FFT & average method for bit decision making.
Problems with decoding using FFT
Note that this method would work well if we could increase the oversample count to more higher rates (decrease the size of the chunks).
The main problem is that if you use even such a low bitrate as 45.45 bauds, and 48kHz sampling rate, one bit is only 1056 samples long. If you are oversampling 4 times, one chunk will be only 1056/4 = 264 samples long. It means that you will have 264 bins after the FFT, representing the 0-24kHz range, so one bin will hold the spectral power of a 24000/264 = 91 Hz wide band.
If you are using the standard 170 Hz shift for RTTY decoding this means you have the two carriers in neighboring bins. So if you increase the oversampling, or decrease the sample rate the two carriers will be in the same bin and you won’t be able to separate them.
The DPLL algorithm
The algorithm is described in detail in the RTTY decoding using an IIR filter section of my tutorials. In this case it uses chunks instead of samples (the demodulator returns bit values of chunks).
Source code
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | // reads a chunk from the sound device public int getChunk(double[] chunkBuffer) { byte[] abBuffer = new byte[oneChunkSampleCount*2]; int samplesRead = 0; // waiting for the buffer to get filled try { 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 samplesRead = bytesRead / 2; for (int i = 0; i < samplesRead; i++) { chunkBuffer[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0; } } catch (InterruptedException e) { } return samplesRead; } // analyzes a chunk with FFT and returns the bit value based on the averaged spectral power at the mark and space freqs public int demodulator() { int samplesRead = getChunk(chunkBuffer); baos.write(getBytesFromDoubles(chunkBuffer, samplesRead), 0, samplesRead * 2); // writing to the output wav for debugging purposes 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] = chunkBuffer[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-PWRFreq1 < 0) return 1; else return 0; } return -1; } // this function returns at the half of a bit with the bit's value public int getBitDPLL() { boolean chunkPhaseChanged = false; int chunkVal = -1; int chunkPhase = 0; while (chunkPhase < CHUNKCOUNTPERBIT) { chunkVal = demodulator(); if (chunkVal == -1) break; if (!chunkPhaseChanged && chunkVal != oldChunkVal) { if (chunkPhase < CHUNKCOUNTPERBIT/2) chunkPhase++; // early else chunkPhase--; // late chunkPhaseChanged = true; } oldChunkVal = chunkVal; chunkPhase++; } // putting a tick to the output wav signing the moment when the DPLL returned baos.write(100); baos.write(100); baos.write(100); baos.write(100); baos.write(100); baos.write(100); return chunkVal; } // this function returns only when the start bit is successfully received public void waitForStartBit() { int bitResult; while (!Thread.interrupted()) { do { bitResult = demodulator(); } while ((bitResult == 0 || bitResult == -1) && !Thread.interrupted()); //System.out.println("sb0: 1"); do { bitResult = demodulator(); } while ((bitResult == 1 || bitResult == -1) && !Thread.interrupted()); //System.out.println("sb1: 0"); // waiting half bit time for (int i = 0; i < CHUNKCOUNTPERBIT/2; i++) bitResult = demodulator(); //System.out.println("sb2: " + bitResult); if (bitResult == 0) break; } //System.out.println("start bit ok"); } @Override public void run() { tdl.start(); int byteResult = 0; int byteResultp = 0; int bitResult; while (!Thread.interrupted()) { waitForStartBit(); System.out.print("0 "); // first bit is the start bit, it's zero // reading 7 more bits for (byteResultp = 1, byteResult = 0; byteResultp < 8; byteResultp++) { bitResult = getBitDPLL(); if (bitResult == -1) { byteResult = -1; break; } switch (byteResultp) { case 6: // stop bit 1 System.out.print(" " + bitResult); break; case 7: // stop bit 2 System.out.print(bitResult); break; default: System.out.print(bitResult); byteResult += bitResult << (byteResultp-1); } } if (byteResult == -1) continue; switch (byteResult) { case 31: mode = RTTYMode.letters; System.out.println(" ^L^"); break; case 27: mode = RTTYMode.symbols; System.out.println(" ^F^"); break; default: switch (mode) { case letters: System.out.println(" *** " + RTTYLetters[byteResult] + "(" + byteResult + ")"); break; case symbols: System.out.println(" *** " + RTTYSymbols[byteResult] + "(" + byteResult + ")"); break; } } } tdl.stop(); tdl.close(); } |
Trackback URL
1 Comment »
Trackback responses to this post
About me
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with.
... »
Can your code be used to decode RTTY baud = 200
data bits = 7
stop bits = 2. I tried to modify your code but without success. Any advice?