DSP tutorial: SSTV encoder
Last modified: April 3rd, 2013An SSTV transmission consists of the following parts:
- VOX tones (optional, but most SSTV apps use it)
- VIS code
- Image data
- FSK ID (optional)
The example encoder can produce the VOX tones, VIS code and image data, we don’t deal with the FSK ID here. If you want to use the FSK ID, here are the parameters: 6-bit bytes, LSB first, 45.45 baud, 1900 Hz = 1, 2100 Hz = 0, text data starts with 20 2A and ends in 01, add 0x20 and the data becomes ASCII.
VOX tones can be anything, I’ve heard musical tones playing as VOX tones several times on HF. :) Most of the time, these notes are played as SSTV VOX tones:
100 ms 1900 Hz
100 ms 1500 Hz
100 ms 1900 Hz
100 ms 1500 Hz
100 ms 2300 Hz
100 ms 1500 Hz
100 ms 2300 Hz
100 ms 1500 Hz
My encoder reads an image file, opens a window, plays the VOX tone, the VIS code and produces a Martin M1 SSTV signal. The sent image can be viewed in the previously opened window.
Resources
There’s a very good PDF for the VIS code and image data timings and format.
Here’s a good technical reference for SSTV.
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 | private static void outputSineSample(double frequency) { oscPhase += (2 * Math.PI * frequency) / SAMPLERATE; double sample = Math.sin(oscPhase) * 0.2; if (oscPhase >= 2 * Math.PI) oscPhase -= 2 * Math.PI; outputToDevice(sample); } private static void outputToDevice(double sample) { outBuffer[outBufferPos++] = sample; if (outBufferPos == outBuffer.length) { sdl.write(getBytesFromDoubles(outBuffer, outBuffer.length), 0, outBuffer.length*2); wavOut.write(getBytesFromDoubles(outBuffer, outBuffer.length), 0, outBuffer.length*2); outBufferPos = 0; } } private static void flushDeviceBuffer() { sdl.write(getBytesFromDoubles(outBuffer, outBufferPos), 0, outBufferPos*2); wavOut.write(getBytesFromDoubles(outBuffer, outBufferPos), 0, outBufferPos*2); outBufferPos = 0; } private static void playTone(double frequency, double lengthInMSecs) { double samplesNeeded = SAMPLERATE*(lengthInMSecs/1000.0); for (double i = 0; i < samplesNeeded; i++) outputSineSample(frequency); } @Override public void run() { outBuffer = new double[BUFFERSIZE]; //playTone(5000, 4576); outputToDeviceBuffer(); writeWavFile(wavOut.toByteArray(), wavOut.size() / 2, "output.wav"); System.exit(0); System.out.println("Playing VOX tones..."); playTone(1900, 100); playTone(1500, 100); playTone(1900, 100); playTone(1500, 100); playTone(2300, 100); playTone(1500, 100); playTone(2300, 100); playTone(1500, 100); // VIS code System.out.print("Playing VIS (" + VIS + "): "); playTone(1900, 300); // leader tone playTone(1200, 10); // break playTone(1900, 300); // leader tone playTone(1200, 30); // VIS start bit int parityBit = 0; for (int i = 0; i < 7; i++) { int bit = (VIS >> i) & 1; playTone((bit == 1 ? 1100 : 1300), 30); System.out.print(bit); parityBit ^= bit; } System.out.print(" " + parityBit); playTone(parityBit == 1 ? 1100 : 1300, 30); playTone(1200, 30); // VIS stop bit System.out.println(); // sending image double pixelLengthInS = 0.0004576; //double pixelLengthInS = 0.00045762; // qsstv uses this double syncLengthInS = 0.004862; double porchLengthInS = 0.000572; double separatorLengthInS = 0.000572; double channelLengthInS = pixelLengthInS*320; double channelGStartInS = syncLengthInS + porchLengthInS; double channelBStartInS = channelGStartInS + channelLengthInS + separatorLengthInS; double channelRStartInS = channelBStartInS + channelLengthInS + separatorLengthInS; double lineLengthInS = syncLengthInS + porchLengthInS + channelLengthInS + separatorLengthInS + channelLengthInS + separatorLengthInS + channelLengthInS + separatorLengthInS; double imageLengthInSamples = (lineLengthInS*256)*SAMPLERATE; double t, linet; for (int s = 0; s < imageLengthInSamples; s++) { t = s/(double)SAMPLERATE; linet = t % lineLengthInS; if (linet < syncLengthInS) outputSineSample(1200); if (linet >= syncLengthInS && linet < syncLengthInS + porchLengthInS) outputSineSample(1500); if (linet >= channelGStartInS && linet < channelGStartInS + channelLengthInS) { int y = (int)Math.floor(t/lineLengthInS); int x = (int)Math.floor(((linet-channelGStartInS)/channelLengthInS)*320); Color c = new Color(image.getRGB(x, y)); outputSineSample(1500+c.getGreen()*3.1372549); } if (linet >= channelGStartInS + channelLengthInS && linet < channelBStartInS) outputSineSample(1500); if (linet >= channelBStartInS && linet < channelBStartInS + channelLengthInS) { int y = (int)Math.floor(t/lineLengthInS); int x = (int)Math.floor(((linet-channelBStartInS)/channelLengthInS)*320); Color c = new Color(image.getRGB(x, y)); outputSineSample(1500+c.getBlue()*3.1372549); } if (linet >= channelBStartInS + channelLengthInS && linet < channelRStartInS) outputSineSample(1500); if (linet >= channelRStartInS && linet < channelRStartInS + channelLengthInS) { int y = (int)Math.floor(t/lineLengthInS); int x = (int)Math.floor(((linet-channelRStartInS)/channelLengthInS)*320); Color c = new Color(image.getRGB(x, y)); outputSineSample(1500+c.getRed()*3.1372549); displayedImage[x][y] = c; repaint(); } if (linet >= channelRStartInS + channelLengthInS) outputSineSample(1500); } } |
Trackback URL
4 Comments »
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.
... »
I cannot download the files of your DSP tutorial19,20 in your blog. Can you help me?I am interested in this program. I am learning it.
I’ve corrected the links, sorry. Please try them now.
Thanks. I have download the whole java files in your DSP tutorial part. Some Files from No.12 to 23 cannot be unzip by using WinARA software under windowS xp. There is a error in unzipping operation. Why?
Those are 7zip files, not zip files. Use 7zip and untar (or Total Commander with 7zip plugin).