kopia lustrzana https://github.com/xdsopl/robot36
Porównaj commity
28 Commity
1859f4b9e8
...
5b2ad998c3
Autor | SHA1 | Data |
---|---|---|
Ahmet Inan | 5b2ad998c3 | |
Ahmet Inan | f64d9e8254 | |
Ahmet Inan | c9ca4e69c3 | |
Ahmet Inan | 5a264c2431 | |
Ahmet Inan | 9f53c04b26 | |
Ahmet Inan | 4c11d82654 | |
Ahmet Inan | 391e73c1f3 | |
Ahmet Inan | 97820b504a | |
Ahmet Inan | 1a7c2d341f | |
Ahmet Inan | e0542ea00b | |
Ahmet Inan | f809555ec9 | |
Ahmet Inan | e4b6e84d8b | |
Ahmet Inan | 1bc83096f0 | |
Ahmet Inan | 5f1870bfa3 | |
Ahmet Inan | ace7962573 | |
Ahmet Inan | 1c6bb123a6 | |
Ahmet Inan | 214b9913a4 | |
Ahmet Inan | daf4d88702 | |
Ahmet Inan | cc7a6dcf71 | |
Ahmet Inan | f725809fa8 | |
Ahmet Inan | e5ce8a5ee1 | |
Ahmet Inan | 51c241b6fb | |
Ahmet Inan | c2efb8036f | |
Ahmet Inan | 0a506406f8 | |
Ahmet Inan | 6f3cea00b2 | |
Ahmet Inan | af06ae6fdc | |
Ahmet Inan | 97881c770c | |
Ahmet Inan | e81d29f89a |
|
@ -10,8 +10,8 @@ android {
|
|||
applicationId "xdsopl.robot36"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 52
|
||||
versionName "2.2"
|
||||
versionCode 54
|
||||
versionName "2.4"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
|
|
@ -11,9 +11,11 @@ import java.util.Arrays;
|
|||
|
||||
public class Decoder {
|
||||
|
||||
private final SimpleMovingAverage pulseFilter;
|
||||
private final Demodulator demodulator;
|
||||
private final PixelBuffer pixelBuffer;
|
||||
private final PixelBuffer scopeBuffer;
|
||||
private final PixelBuffer imageBuffer;
|
||||
private final float[] scanLineBuffer;
|
||||
private final float[] scratchBuffer;
|
||||
private final int[] last5msSyncPulses;
|
||||
|
@ -25,9 +27,16 @@ public class Decoder {
|
|||
private final float[] last5msFrequencyOffsets;
|
||||
private final float[] last9msFrequencyOffsets;
|
||||
private final float[] last20msFrequencyOffsets;
|
||||
private final int scanLineReserveSamples;
|
||||
private final float[] visCodeBitFrequencies;
|
||||
private final int pulseFilterDelay;
|
||||
private final int scanLineMinSamples;
|
||||
private final int syncPulseToleranceSamples;
|
||||
private final int scanLineToleranceSamples;
|
||||
private final int leaderToneSamples;
|
||||
private final int leaderToneToleranceSamples;
|
||||
private final int transitionSamples;
|
||||
private final int visCodeBitSamples;
|
||||
private final int visCodeSamples;
|
||||
private final Mode rawMode;
|
||||
private final ArrayList<Mode> syncPulse5msModes;
|
||||
private final ArrayList<Mode> syncPulse9msModes;
|
||||
|
@ -35,20 +44,38 @@ public class Decoder {
|
|||
|
||||
public Mode lastMode;
|
||||
private int curSample;
|
||||
private int leaderBreakIndex;
|
||||
private int lastSyncPulseIndex;
|
||||
private int lastScanLineSamples;
|
||||
private float lastFrequencyOffset;
|
||||
|
||||
Decoder(PixelBuffer scopeBuffer, int sampleRate) {
|
||||
Decoder(PixelBuffer scopeBuffer, PixelBuffer imageBuffer, int sampleRate) {
|
||||
this.scopeBuffer = scopeBuffer;
|
||||
this.imageBuffer = imageBuffer;
|
||||
imageBuffer.line = -1;
|
||||
pixelBuffer = new PixelBuffer(scopeBuffer.width, 2);
|
||||
demodulator = new Demodulator(sampleRate);
|
||||
double pulseFilterSeconds = 0.0025;
|
||||
int pulseFilterSamples = (int) Math.round(pulseFilterSeconds * sampleRate) | 1;
|
||||
pulseFilterDelay = (pulseFilterSamples - 1) / 2;
|
||||
pulseFilter = new SimpleMovingAverage(pulseFilterSamples);
|
||||
double scanLineMaxSeconds = 7;
|
||||
int scanLineMaxSamples = (int) Math.round(scanLineMaxSeconds * sampleRate);
|
||||
scanLineBuffer = new float[scanLineMaxSamples];
|
||||
double scratchBufferSeconds = 1.1;
|
||||
int scratchBufferSamples = (int) Math.round(scratchBufferSeconds * sampleRate);
|
||||
scratchBuffer = new float[scratchBufferSamples];
|
||||
double leaderToneSeconds = 0.3;
|
||||
leaderToneSamples = (int) Math.round(leaderToneSeconds * sampleRate);
|
||||
double leaderToneToleranceSeconds = leaderToneSeconds * 0.2;
|
||||
leaderToneToleranceSamples = (int) Math.round(leaderToneToleranceSeconds * sampleRate);
|
||||
double transitionSeconds = 0.0005;
|
||||
transitionSamples = (int) Math.round(transitionSeconds * sampleRate);
|
||||
double visCodeBitSeconds = 0.03;
|
||||
visCodeBitSamples = (int) Math.round(visCodeBitSeconds * sampleRate);
|
||||
double visCodeSeconds = 0.3;
|
||||
visCodeSamples = (int) Math.round(visCodeSeconds * sampleRate);
|
||||
visCodeBitFrequencies = new float[10];
|
||||
int scanLineCount = 4;
|
||||
last5msScanLines = new int[scanLineCount];
|
||||
last9msScanLines = new int[scanLineCount];
|
||||
|
@ -60,37 +87,34 @@ public class Decoder {
|
|||
last5msFrequencyOffsets = new float[syncPulseCount];
|
||||
last9msFrequencyOffsets = new float[syncPulseCount];
|
||||
last20msFrequencyOffsets = new float[syncPulseCount];
|
||||
double scanLineMinSeconds = 0.05;
|
||||
scanLineMinSamples = (int) Math.round(scanLineMinSeconds * sampleRate);
|
||||
double syncPulseToleranceSeconds = 0.03;
|
||||
syncPulseToleranceSamples = (int) Math.round(syncPulseToleranceSeconds * sampleRate);
|
||||
double scanLineToleranceSeconds = 0.001;
|
||||
scanLineToleranceSamples = (int) Math.round(scanLineToleranceSeconds * sampleRate);
|
||||
scanLineReserveSamples = sampleRate;
|
||||
rawMode = new RawDecoder(sampleRate);
|
||||
lastMode = rawMode;
|
||||
lastScanLineSamples = (int) Math.round(0.150 * sampleRate);
|
||||
Mode robot36 = new Robot_36_Color(sampleRate);
|
||||
lastMode = robot36;
|
||||
lastScanLineSamples = robot36.getScanLineSamples();
|
||||
lastSyncPulseIndex = curSample;
|
||||
syncPulse5msModes = new ArrayList<>();
|
||||
syncPulse5msModes.add(RGBModes.Wraase_SC2_180(sampleRate));
|
||||
syncPulse5msModes.add(RGBModes.Martin("1", 0.146432, sampleRate));
|
||||
syncPulse5msModes.add(RGBModes.Martin("2", 0.073216, sampleRate));
|
||||
syncPulse5msModes.add(RGBModes.Martin("1", 44, 0.146432, sampleRate));
|
||||
syncPulse5msModes.add(RGBModes.Martin("2", 40, 0.073216, sampleRate));
|
||||
syncPulse9msModes = new ArrayList<>();
|
||||
syncPulse9msModes.add(new Robot_36_Color(sampleRate));
|
||||
syncPulse9msModes.add(robot36);
|
||||
syncPulse9msModes.add(new Robot_72_Color(sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("1", 0.138240, sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("2", 0.088064, sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("DX", 0.3456, sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("1", 60, 0.138240, sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("2", 56, 0.088064, sampleRate));
|
||||
syncPulse9msModes.add(RGBModes.Scottie("DX", 76, 0.3456, sampleRate));
|
||||
syncPulse20msModes = new ArrayList<>();
|
||||
syncPulse20msModes.add(new PaulDon("50", 320, 0.09152, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("90", 320, 0.17024, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("120", 640, 0.1216, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("160", 512, 0.195584, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("180", 640, 0.18304, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("240", 640, 0.24448, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("290", 640, 0.2288, sampleRate));
|
||||
}
|
||||
|
||||
private void adjustSyncPulses(int[] pulses, int shift) {
|
||||
for (int i = 0; i < pulses.length; ++i)
|
||||
pulses[i] -= shift;
|
||||
syncPulse20msModes.add(new PaulDon("50", 93, 320, 256, 0.09152, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("90", 99, 320, 256, 0.17024, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("120", 95, 640, 496, 0.1216, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("160", 98, 512, 400, 0.195584, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("180", 96, 640, 496, 0.18304, sampleRate));
|
||||
syncPulse20msModes.add(new PaulDon("240", 97, 640, 496, 0.24448, sampleRate));
|
||||
}
|
||||
|
||||
private double scanLineMean(int[] lines) {
|
||||
|
@ -130,6 +154,13 @@ public class Decoder {
|
|||
return bestMode;
|
||||
}
|
||||
|
||||
private Mode findMode(ArrayList<Mode> modes, int code) {
|
||||
for (Mode mode : modes)
|
||||
if (mode.getCode() == code)
|
||||
return mode;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void copyUnscaled() {
|
||||
for (int row = 0; row < pixelBuffer.height; ++row) {
|
||||
int line = scopeBuffer.width * scopeBuffer.line;
|
||||
|
@ -160,20 +191,161 @@ public class Decoder {
|
|||
private void copyLines(boolean okay) {
|
||||
if (!okay)
|
||||
return;
|
||||
boolean finish = false;
|
||||
if (imageBuffer.line >= 0 && imageBuffer.line < imageBuffer.height && imageBuffer.width == pixelBuffer.width) {
|
||||
int width = imageBuffer.width;
|
||||
for (int row = 0; row < pixelBuffer.height && imageBuffer.line < imageBuffer.height; ++row, ++imageBuffer.line)
|
||||
System.arraycopy(pixelBuffer.pixels, row * width, imageBuffer.pixels, imageBuffer.line * width, width);
|
||||
finish = imageBuffer.line == imageBuffer.height;
|
||||
}
|
||||
int scale = scopeBuffer.width / pixelBuffer.width;
|
||||
if (scale == 1)
|
||||
copyUnscaled();
|
||||
else
|
||||
copyScaled(scale);
|
||||
if (finish)
|
||||
drawLines(0xff000000, 10);
|
||||
}
|
||||
|
||||
private void drawLines(int color, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Arrays.fill(scopeBuffer.pixels, scopeBuffer.line * scopeBuffer.width, (scopeBuffer.line + 1) * scopeBuffer.width, color);
|
||||
Arrays.fill(scopeBuffer.pixels, (scopeBuffer.line + scopeBuffer.height / 2) * scopeBuffer.width, (scopeBuffer.line + 1 + scopeBuffer.height / 2) * scopeBuffer.width, color);
|
||||
scopeBuffer.line = (scopeBuffer.line + 1) % (scopeBuffer.height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustSyncPulses(int[] pulses, int shift) {
|
||||
for (int i = 0; i < pulses.length; ++i)
|
||||
pulses[i] -= shift;
|
||||
}
|
||||
|
||||
private void shiftSamples(int shift) {
|
||||
if (shift <= 0 || shift > curSample)
|
||||
return;
|
||||
leaderBreakIndex -= shift;
|
||||
lastSyncPulseIndex -= shift;
|
||||
adjustSyncPulses(last5msSyncPulses, shift);
|
||||
adjustSyncPulses(last9msSyncPulses, shift);
|
||||
adjustSyncPulses(last20msSyncPulses, shift);
|
||||
int endSample = curSample;
|
||||
curSample = 0;
|
||||
for (int i = shift; i < endSample; ++i)
|
||||
scanLineBuffer[curSample++] = scanLineBuffer[i];
|
||||
}
|
||||
|
||||
private boolean handleHeader() {
|
||||
if (leaderBreakIndex < visCodeBitSamples + leaderToneToleranceSamples || curSample < leaderBreakIndex + leaderToneSamples + leaderToneToleranceSamples + visCodeSamples + visCodeBitSamples)
|
||||
return false;
|
||||
int breakPulseIndex = leaderBreakIndex;
|
||||
leaderBreakIndex = 0;
|
||||
float preBreakFreq = 0;
|
||||
for (int i = 0; i < leaderToneToleranceSamples; ++i)
|
||||
preBreakFreq += scanLineBuffer[breakPulseIndex - visCodeBitSamples - leaderToneToleranceSamples + i];
|
||||
float leaderToneFrequency = 1900;
|
||||
float centerFrequency = 1900;
|
||||
float toleranceFrequency = 50;
|
||||
float halfBandWidth = 400;
|
||||
preBreakFreq = preBreakFreq * halfBandWidth / leaderToneToleranceSamples + centerFrequency;
|
||||
if (Math.abs(preBreakFreq - leaderToneFrequency) > toleranceFrequency)
|
||||
return false;
|
||||
float leaderFreq = 0;
|
||||
for (int i = transitionSamples; i < leaderToneSamples - leaderToneToleranceSamples; ++i)
|
||||
leaderFreq += scanLineBuffer[breakPulseIndex + i];
|
||||
float leaderFreqOffset = leaderFreq / (leaderToneSamples - transitionSamples - leaderToneToleranceSamples);
|
||||
leaderFreq = leaderFreqOffset * halfBandWidth + centerFrequency;
|
||||
if (Math.abs(leaderFreq - leaderToneFrequency) > toleranceFrequency)
|
||||
return false;
|
||||
float stopBitFrequency = 1200;
|
||||
float pulseThresholdFrequency = (stopBitFrequency + leaderToneFrequency) / 2;
|
||||
float pulseThresholdValue = (pulseThresholdFrequency - centerFrequency) / halfBandWidth;
|
||||
int visBeginIndex = breakPulseIndex + leaderToneSamples - leaderToneToleranceSamples;
|
||||
int visEndIndex = breakPulseIndex + leaderToneSamples + leaderToneToleranceSamples + visCodeBitSamples;
|
||||
for (int i = 0; i < pulseFilter.length; ++i)
|
||||
pulseFilter.avg(scanLineBuffer[visBeginIndex++] - leaderFreqOffset);
|
||||
while (++visBeginIndex < visEndIndex)
|
||||
if (pulseFilter.avg(scanLineBuffer[visBeginIndex] - leaderFreqOffset) < pulseThresholdValue)
|
||||
break;
|
||||
if (visBeginIndex >= visEndIndex)
|
||||
return false;
|
||||
visBeginIndex -= pulseFilterDelay;
|
||||
visEndIndex = visBeginIndex + visCodeSamples;
|
||||
Arrays.fill(visCodeBitFrequencies, 0);
|
||||
for (int j = 0; j < 10; ++j)
|
||||
for (int i = transitionSamples; i < visCodeBitSamples - transitionSamples; ++i)
|
||||
visCodeBitFrequencies[j] += scanLineBuffer[visBeginIndex + visCodeBitSamples * j + i] - leaderFreqOffset;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
visCodeBitFrequencies[i] = visCodeBitFrequencies[i] * halfBandWidth / (visCodeBitSamples - 2 * transitionSamples) + centerFrequency;
|
||||
if (Math.abs(visCodeBitFrequencies[0] - stopBitFrequency) > toleranceFrequency || Math.abs(visCodeBitFrequencies[9] - stopBitFrequency) > toleranceFrequency)
|
||||
return false;
|
||||
float oneBitFrequency = 1100;
|
||||
float zeroBitFrequency = 1300;
|
||||
for (int i = 1; i < 9; ++i)
|
||||
if (Math.abs(visCodeBitFrequencies[i] - oneBitFrequency) > toleranceFrequency && Math.abs(visCodeBitFrequencies[i] - zeroBitFrequency) > toleranceFrequency)
|
||||
return false;
|
||||
int visCode = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
visCode |= (visCodeBitFrequencies[i + 1] < stopBitFrequency ? 1 : 0) << i;
|
||||
boolean check = true;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
check ^= (visCode & 1 << i) != 0;
|
||||
visCode &= 127;
|
||||
if (!check)
|
||||
return false;
|
||||
float syncPorchFrequency = 1500;
|
||||
float syncPulseFrequency = 1200;
|
||||
float syncThresholdFrequency = (syncPulseFrequency + syncPorchFrequency) / 2;
|
||||
float syncThresholdValue = (syncThresholdFrequency - centerFrequency) / halfBandWidth;
|
||||
int syncPulseIndex = visEndIndex - visCodeBitSamples;
|
||||
int syncPulseMaxIndex = visEndIndex + visCodeBitSamples;
|
||||
for (int i = 0; i < pulseFilter.length; ++i)
|
||||
pulseFilter.avg(scanLineBuffer[syncPulseIndex++] - leaderFreqOffset);
|
||||
while (++syncPulseIndex < syncPulseMaxIndex)
|
||||
if (pulseFilter.avg(scanLineBuffer[syncPulseIndex] - leaderFreqOffset) > syncThresholdValue)
|
||||
break;
|
||||
if (syncPulseIndex >= syncPulseMaxIndex)
|
||||
return false;
|
||||
syncPulseIndex -= pulseFilterDelay;
|
||||
Mode mode;
|
||||
int[] pulses;
|
||||
int[] lines;
|
||||
if ((mode = findMode(syncPulse5msModes, visCode)) != null) {
|
||||
pulses = last5msSyncPulses;
|
||||
lines = last5msScanLines;
|
||||
} else if ((mode = findMode(syncPulse9msModes, visCode)) != null) {
|
||||
pulses = last9msSyncPulses;
|
||||
lines = last9msScanLines;
|
||||
} else if ((mode = findMode(syncPulse20msModes, visCode)) != null) {
|
||||
pulses = last20msSyncPulses;
|
||||
lines = last20msScanLines;
|
||||
} else {
|
||||
drawLines(0xffff0000, 8);
|
||||
return false;
|
||||
}
|
||||
mode.reset();
|
||||
imageBuffer.width = mode.getWidth();
|
||||
imageBuffer.height = mode.getHeight();
|
||||
imageBuffer.line = 0;
|
||||
lastMode = mode;
|
||||
lastSyncPulseIndex = syncPulseIndex + mode.getFirstSyncPulseIndex();
|
||||
lastScanLineSamples = mode.getScanLineSamples();
|
||||
lastFrequencyOffset = leaderFreqOffset;
|
||||
for (int i = 0; i < pulses.length; ++i)
|
||||
pulses[i] = lastSyncPulseIndex + (i - pulses.length + 1) * lastScanLineSamples;
|
||||
Arrays.fill(lines, lastScanLineSamples);
|
||||
shiftSamples(lastSyncPulseIndex + mode.getBegin());
|
||||
drawLines(0xff00ff00, 8);
|
||||
drawLines(0xff000000, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean processSyncPulse(ArrayList<Mode> modes, float[] freqOffs, int[] pulses, int[] lines, int index) {
|
||||
for (int i = 1; i < lines.length; ++i)
|
||||
lines[i - 1] = lines[i];
|
||||
lines[lines.length - 1] = index - pulses[pulses.length - 1];
|
||||
for (int i = 1; i < pulses.length; ++i)
|
||||
pulses[i - 1] = pulses[i];
|
||||
pulses[pulses.length - 1] = index;
|
||||
for (int i = 1; i < lines.length; ++i)
|
||||
lines[i - 1] = lines[i];
|
||||
lines[lines.length - 1] = pulses[pulses.length - 1] - pulses[pulses.length - 2];
|
||||
for (int i = 1; i < freqOffs.length; ++i)
|
||||
freqOffs[i - 1] = freqOffs[i];
|
||||
freqOffs[pulses.length - 1] = demodulator.frequencyOffset;
|
||||
|
@ -181,38 +353,37 @@ public class Decoder {
|
|||
return false;
|
||||
double mean = scanLineMean(lines);
|
||||
int scanLineSamples = (int) Math.round(mean);
|
||||
if (scanLineSamples > scratchBuffer.length)
|
||||
if (scanLineSamples < scanLineMinSamples || scanLineSamples > scratchBuffer.length)
|
||||
return false;
|
||||
if (scanLineStdDev(lines, mean) > scanLineToleranceSamples)
|
||||
return false;
|
||||
float frequencyOffset = (float) frequencyOffsetMean(freqOffs);
|
||||
Mode mode = detectMode(modes, scanLineSamples);
|
||||
boolean pictureChanged = lastMode != mode
|
||||
|| Math.abs(lastScanLineSamples - scanLineSamples) > scanLineToleranceSamples
|
||||
|| Math.abs(lastSyncPulseIndex + scanLineSamples - pulses[pulses.length - 1]) > syncPulseToleranceSamples;
|
||||
boolean pictureChanged = false;
|
||||
if (imageBuffer.line < 0 || imageBuffer.line >= imageBuffer.height) {
|
||||
Mode prevMode = lastMode;
|
||||
lastMode = detectMode(modes, scanLineSamples);
|
||||
pictureChanged = lastMode != prevMode
|
||||
|| Math.abs(lastScanLineSamples - scanLineSamples) > scanLineToleranceSamples
|
||||
|| Math.abs(lastSyncPulseIndex + scanLineSamples - pulses[pulses.length - 1]) > syncPulseToleranceSamples;
|
||||
}
|
||||
if (pictureChanged) {
|
||||
drawLines(0xff000000, 10);
|
||||
drawLines(0xff00ffff, 8);
|
||||
drawLines(0xff000000, 10);
|
||||
}
|
||||
if (pulses[0] >= scanLineSamples && pictureChanged) {
|
||||
int endPulse = pulses[0];
|
||||
int extrapolate = endPulse / scanLineSamples;
|
||||
int firstPulse = endPulse - extrapolate * scanLineSamples;
|
||||
for (int pulseIndex = firstPulse; pulseIndex < endPulse; pulseIndex += scanLineSamples)
|
||||
copyLines(mode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulseIndex, scanLineSamples, frequencyOffset));
|
||||
copyLines(lastMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulseIndex, scanLineSamples, frequencyOffset));
|
||||
}
|
||||
for (int i = pictureChanged ? 0 : lines.length - 1; i < lines.length; ++i)
|
||||
copyLines(mode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulses[i], lines[i], frequencyOffset));
|
||||
int shift = pulses[pulses.length - 1] - scanLineReserveSamples;
|
||||
if (shift > scanLineReserveSamples) {
|
||||
adjustSyncPulses(last5msSyncPulses, shift);
|
||||
adjustSyncPulses(last9msSyncPulses, shift);
|
||||
adjustSyncPulses(last20msSyncPulses, shift);
|
||||
int endSample = curSample;
|
||||
curSample = 0;
|
||||
for (int i = shift; i < endSample; ++i)
|
||||
scanLineBuffer[curSample++] = scanLineBuffer[i];
|
||||
}
|
||||
lastMode = mode;
|
||||
copyLines(lastMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, pulses[i], lines[i], frequencyOffset));
|
||||
lastSyncPulseIndex = pulses[pulses.length - 1];
|
||||
lastScanLineSamples = scanLineSamples;
|
||||
lastFrequencyOffset = frequencyOffset;
|
||||
shiftSamples(lastSyncPulseIndex + lastMode.getBegin());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -223,15 +394,8 @@ public class Decoder {
|
|||
for (int j = 0; j < recordBuffer.length / channels; ++j) {
|
||||
scanLineBuffer[curSample++] = recordBuffer[j];
|
||||
if (curSample >= scanLineBuffer.length) {
|
||||
int shift = scanLineReserveSamples;
|
||||
syncPulseIndex -= shift;
|
||||
lastSyncPulseIndex -= shift;
|
||||
adjustSyncPulses(last5msSyncPulses, shift);
|
||||
adjustSyncPulses(last9msSyncPulses, shift);
|
||||
adjustSyncPulses(last20msSyncPulses, shift);
|
||||
curSample = 0;
|
||||
for (int i = shift; i < scanLineBuffer.length; ++i)
|
||||
scanLineBuffer[curSample++] = scanLineBuffer[i];
|
||||
shiftSamples(lastScanLineSamples);
|
||||
syncPulseIndex -= lastScanLineSamples;
|
||||
}
|
||||
}
|
||||
if (syncPulseDetected) {
|
||||
|
@ -239,11 +403,18 @@ public class Decoder {
|
|||
case FiveMilliSeconds:
|
||||
return processSyncPulse(syncPulse5msModes, last5msFrequencyOffsets, last5msSyncPulses, last5msScanLines, syncPulseIndex);
|
||||
case NineMilliSeconds:
|
||||
leaderBreakIndex = syncPulseIndex;
|
||||
return processSyncPulse(syncPulse9msModes, last9msFrequencyOffsets, last9msSyncPulses, last9msScanLines, syncPulseIndex);
|
||||
case TwentyMilliSeconds:
|
||||
leaderBreakIndex = syncPulseIndex;
|
||||
return processSyncPulse(syncPulse20msModes, last20msFrequencyOffsets, last20msSyncPulses, last20msScanLines, syncPulseIndex);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (lastSyncPulseIndex >= scanLineReserveSamples && curSample > lastSyncPulseIndex + (lastScanLineSamples * 5) / 4) {
|
||||
}
|
||||
if (handleHeader())
|
||||
return true;
|
||||
if (curSample > lastSyncPulseIndex + (lastScanLineSamples * 5) / 4) {
|
||||
copyLines(lastMode.decodeScanLine(pixelBuffer, scratchBuffer, scanLineBuffer, scopeBuffer.width, lastSyncPulseIndex, lastScanLineSamples, lastFrequencyOffset));
|
||||
lastSyncPulseIndex += lastScanLineSamples;
|
||||
return true;
|
||||
|
|
|
@ -64,6 +64,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
private Bitmap peakMeterBitmap;
|
||||
private PixelBuffer peakMeterBuffer;
|
||||
private ImageView peakMeterView;
|
||||
private PixelBuffer imageBuffer;
|
||||
private float[] recordBuffer;
|
||||
private AudioRecord audioRecord;
|
||||
private Decoder decoder;
|
||||
|
@ -95,6 +96,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
processFreqPlot();
|
||||
if (newLines) {
|
||||
processScope();
|
||||
processImage();
|
||||
setStatus(decoder.lastMode.getName());
|
||||
}
|
||||
}
|
||||
|
@ -120,12 +122,18 @@ public class MainActivity extends AppCompatActivity {
|
|||
int stride = freqPlotBuffer.width;
|
||||
int line = stride * freqPlotBuffer.line;
|
||||
int channels = recordChannel > 0 ? 2 : 1;
|
||||
int samples = recordBuffer.length / channels;
|
||||
int spread = 2;
|
||||
Arrays.fill(freqPlotBuffer.pixels, line, line + stride, 0);
|
||||
for (int i = 0; i < recordBuffer.length / channels; ++i) {
|
||||
for (int i = 0; i < samples; ++i) {
|
||||
int x = Math.round((recordBuffer[i] + 2.5f) * 0.25f * stride);
|
||||
if (x >= 0 && x < stride)
|
||||
freqPlotBuffer.pixels[line + x] = tint;
|
||||
if (x >= spread && x < stride - spread)
|
||||
for (int j = - spread; j <= spread; ++j)
|
||||
freqPlotBuffer.pixels[line + x + j] += 1 + spread * spread - j * j;
|
||||
}
|
||||
int factor = 960 / samples;
|
||||
for (int i = 0; i < stride; ++i)
|
||||
freqPlotBuffer.pixels[line + i] = 0xff000000 | 0x00010101 * Math.min(factor * freqPlotBuffer.pixels[line + i], 255);
|
||||
System.arraycopy(freqPlotBuffer.pixels, line, freqPlotBuffer.pixels, line + stride * (freqPlotBuffer.height / 2), stride);
|
||||
freqPlotBuffer.line = (freqPlotBuffer.line + 1) % (freqPlotBuffer.height / 2);
|
||||
int offset = stride * (freqPlotBuffer.line + freqPlotBuffer.height / 2 - height);
|
||||
|
@ -142,6 +150,13 @@ public class MainActivity extends AppCompatActivity {
|
|||
scopeView.invalidate();
|
||||
}
|
||||
|
||||
private void processImage() {
|
||||
if (imageBuffer.line < imageBuffer.height)
|
||||
return;
|
||||
imageBuffer.line = -1;
|
||||
storeBitmap(Bitmap.createBitmap(imageBuffer.pixels, imageBuffer.width, imageBuffer.height, Bitmap.Config.ARGB_8888));
|
||||
}
|
||||
|
||||
private void initAudioRecord() {
|
||||
boolean rateChanged = true;
|
||||
if (audioRecord != null) {
|
||||
|
@ -173,7 +188,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
audioRecord.setRecordPositionUpdateListener(recordListener);
|
||||
audioRecord.setPositionNotificationPeriod(frameCount);
|
||||
if (rateChanged)
|
||||
decoder = new Decoder(scopeBuffer, recordRate);
|
||||
decoder = new Decoder(scopeBuffer, imageBuffer, recordRate);
|
||||
startListening();
|
||||
} else {
|
||||
setStatus(R.string.audio_init_failed);
|
||||
|
@ -347,6 +362,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
createFreqPlot(config);
|
||||
peakMeterBuffer = new PixelBuffer(1, 16);
|
||||
createPeakMeter();
|
||||
imageBuffer = new PixelBuffer(640, 496);
|
||||
List<String> permissions = new ArrayList<>();
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(Manifest.permission.RECORD_AUDIO);
|
||||
|
|
|
@ -9,7 +9,19 @@ package xdsopl.robot36;
|
|||
public interface Mode {
|
||||
String getName();
|
||||
|
||||
int getCode();
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
int getBegin();
|
||||
|
||||
int getFirstSyncPulseIndex();
|
||||
|
||||
int getScanLineSamples();
|
||||
|
||||
void reset();
|
||||
|
||||
boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ package xdsopl.robot36;
|
|||
public class PaulDon implements Mode {
|
||||
private final ExponentialMovingAverage lowPassFilter;
|
||||
private final int horizontalPixels;
|
||||
private final int verticalPixels;
|
||||
private final int scanLineSamples;
|
||||
private final int channelSamples;
|
||||
private final int beginSamples;
|
||||
|
@ -18,11 +19,14 @@ public class PaulDon implements Mode {
|
|||
private final int yOddBeginSamples;
|
||||
private final int endSamples;
|
||||
private final String name;
|
||||
private final int code;
|
||||
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
PaulDon(String name, int horizontalPixels, double channelSeconds, int sampleRate) {
|
||||
PaulDon(String name, int code, int horizontalPixels, int verticalPixels, double channelSeconds, int sampleRate) {
|
||||
this.name = "PD " + name;
|
||||
this.code = code;
|
||||
this.horizontalPixels = horizontalPixels;
|
||||
this.verticalPixels = verticalPixels;
|
||||
double syncPulseSeconds = 0.02;
|
||||
double syncPorchSeconds = 0.00208;
|
||||
double scanLineSeconds = syncPulseSeconds + syncPorchSeconds + 4 * (channelSeconds);
|
||||
|
@ -51,11 +55,40 @@ public class PaulDon implements Mode {
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return horizontalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return verticalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return scanLineSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
|
||||
if (syncPulseIndex + beginSamples < 0 || syncPulseIndex + endSamples > scanLineBuffer.length)
|
||||
|
|
|
@ -9,6 +9,8 @@ package xdsopl.robot36;
|
|||
public class RGBDecoder implements Mode {
|
||||
private final ExponentialMovingAverage lowPassFilter;
|
||||
private final int horizontalPixels;
|
||||
private final int verticalPixels;
|
||||
private final int firstSyncPulseIndex;
|
||||
private final int scanLineSamples;
|
||||
private final int beginSamples;
|
||||
private final int redBeginSamples;
|
||||
|
@ -19,10 +21,14 @@ public class RGBDecoder implements Mode {
|
|||
private final int blueSamples;
|
||||
private final int endSamples;
|
||||
private final String name;
|
||||
private final int code;
|
||||
|
||||
RGBDecoder(String name, int horizontalPixels, double scanLineSeconds, double beginSeconds, double redBeginSeconds, double redEndSeconds, double greenBeginSeconds, double greenEndSeconds, double blueBeginSeconds, double blueEndSeconds, double endSeconds, int sampleRate) {
|
||||
RGBDecoder(String name, int code, int horizontalPixels, int verticalPixels, double firstSyncPulseSeconds, double scanLineSeconds, double beginSeconds, double redBeginSeconds, double redEndSeconds, double greenBeginSeconds, double greenEndSeconds, double blueBeginSeconds, double blueEndSeconds, double endSeconds, int sampleRate) {
|
||||
this.name = name;
|
||||
this.code = code;
|
||||
this.horizontalPixels = horizontalPixels;
|
||||
this.verticalPixels = verticalPixels;
|
||||
firstSyncPulseIndex = (int) Math.round(firstSyncPulseSeconds * sampleRate);
|
||||
scanLineSamples = (int) Math.round(scanLineSeconds * sampleRate);
|
||||
beginSamples = (int) Math.round(beginSeconds * sampleRate);
|
||||
redBeginSamples = (int) Math.round(redBeginSeconds * sampleRate) - beginSamples;
|
||||
|
@ -44,11 +50,40 @@ public class RGBDecoder implements Mode {
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return horizontalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return verticalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return firstSyncPulseIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return scanLineSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
|
||||
if (syncPulseIndex + beginSamples < 0 || syncPulseIndex + endSamples > scanLineBuffer.length)
|
||||
|
|
|
@ -9,7 +9,7 @@ package xdsopl.robot36;
|
|||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
public final class RGBModes {
|
||||
|
||||
public static RGBDecoder Martin(String name, double channelSeconds, int sampleRate) {
|
||||
public static RGBDecoder Martin(String name, int code, double channelSeconds, int sampleRate) {
|
||||
double syncPulseSeconds = 0.004862;
|
||||
double separatorSeconds = 0.000572;
|
||||
double scanLineSeconds = syncPulseSeconds + separatorSeconds + 3 * (channelSeconds + separatorSeconds);
|
||||
|
@ -19,12 +19,13 @@ public final class RGBModes {
|
|||
double blueEndSeconds = blueBeginSeconds + channelSeconds;
|
||||
double redBeginSeconds = blueEndSeconds + separatorSeconds;
|
||||
double redEndSeconds = redBeginSeconds + channelSeconds;
|
||||
return new RGBDecoder("Martin " + name, 320, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate);
|
||||
return new RGBDecoder("Martin " + name, code, 320, 256, 0, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate);
|
||||
}
|
||||
|
||||
public static RGBDecoder Scottie(String name, double channelSeconds, int sampleRate) {
|
||||
public static RGBDecoder Scottie(String name, int code, double channelSeconds, int sampleRate) {
|
||||
double syncPulseSeconds = 0.009;
|
||||
double separatorSeconds = 0.0015;
|
||||
double firstSyncPulseSeconds = syncPulseSeconds + 2 * (separatorSeconds + channelSeconds);
|
||||
double scanLineSeconds = syncPulseSeconds + 3 * (channelSeconds + separatorSeconds);
|
||||
double blueEndSeconds = -syncPulseSeconds;
|
||||
double blueBeginSeconds = blueEndSeconds - channelSeconds;
|
||||
|
@ -32,7 +33,7 @@ public final class RGBModes {
|
|||
double greenBeginSeconds = greenEndSeconds - channelSeconds;
|
||||
double redBeginSeconds = separatorSeconds;
|
||||
double redEndSeconds = redBeginSeconds + channelSeconds;
|
||||
return new RGBDecoder("Scottie " + name, 320, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate);
|
||||
return new RGBDecoder("Scottie " + name, code, 320, 256, firstSyncPulseSeconds, scanLineSeconds, greenBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, redEndSeconds, sampleRate);
|
||||
}
|
||||
|
||||
public static RGBDecoder Wraase_SC2_180(int sampleRate) {
|
||||
|
@ -46,6 +47,6 @@ public final class RGBModes {
|
|||
double greenEndSeconds = greenBeginSeconds + channelSeconds;
|
||||
double blueBeginSeconds = greenEndSeconds;
|
||||
double blueEndSeconds = blueBeginSeconds + channelSeconds;
|
||||
return new RGBDecoder("Wraase SC2-180", 320, scanLineSeconds, redBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, blueEndSeconds, sampleRate);
|
||||
return new RGBDecoder("Wraase SC2-180", 55, 320, 256, 0, scanLineSeconds, redBeginSeconds, redBeginSeconds, redEndSeconds, greenBeginSeconds, greenEndSeconds, blueBeginSeconds, blueEndSeconds, blueEndSeconds, sampleRate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,40 @@ public class RawDecoder implements Mode {
|
|||
return "Raw";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
|
||||
if (syncPulseIndex < 0 || syncPulseIndex + scanLineSamples > scanLineBuffer.length)
|
||||
|
|
|
@ -9,6 +9,7 @@ package xdsopl.robot36;
|
|||
public class Robot_36_Color implements Mode {
|
||||
private final ExponentialMovingAverage lowPassFilter;
|
||||
private final int horizontalPixels;
|
||||
private final int verticalPixels;
|
||||
private final int scanLineSamples;
|
||||
private final int luminanceSamples;
|
||||
private final int separatorSamples;
|
||||
|
@ -23,6 +24,7 @@ public class Robot_36_Color implements Mode {
|
|||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
Robot_36_Color(int sampleRate) {
|
||||
horizontalPixels = 320;
|
||||
verticalPixels = 240;
|
||||
double syncPulseSeconds = 0.009;
|
||||
double syncPorchSeconds = 0.003;
|
||||
double luminanceSeconds = 0.088;
|
||||
|
@ -56,11 +58,41 @@ public class Robot_36_Color implements Mode {
|
|||
return "Robot 36 Color";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return horizontalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return verticalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return scanLineSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
lastEven = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
|
||||
if (syncPulseIndex + beginSamples < 0 || syncPulseIndex + endSamples > scanLineBuffer.length)
|
||||
|
|
|
@ -9,6 +9,7 @@ package xdsopl.robot36;
|
|||
public class Robot_72_Color implements Mode {
|
||||
private final ExponentialMovingAverage lowPassFilter;
|
||||
private final int horizontalPixels;
|
||||
private final int verticalPixels;
|
||||
private final int scanLineSamples;
|
||||
private final int luminanceSamples;
|
||||
private final int chrominanceSamples;
|
||||
|
@ -21,6 +22,7 @@ public class Robot_72_Color implements Mode {
|
|||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
Robot_72_Color(int sampleRate) {
|
||||
horizontalPixels = 320;
|
||||
verticalPixels = 240;
|
||||
double syncPulseSeconds = 0.009;
|
||||
double syncPorchSeconds = 0.003;
|
||||
double luminanceSeconds = 0.138;
|
||||
|
@ -54,11 +56,40 @@ public class Robot_72_Color implements Mode {
|
|||
return "Robot 72 Color";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return horizontalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return verticalPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBegin() {
|
||||
return beginSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirstSyncPulseIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanLineSamples() {
|
||||
return scanLineSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean decodeScanLine(PixelBuffer pixelBuffer, float[] scratchBuffer, float[] scanLineBuffer, int scopeBufferWidth, int syncPulseIndex, int scanLineSamples, float frequencyOffset) {
|
||||
if (syncPulseIndex + beginSamples < 0 || syncPulseIndex + endSamples > scanLineBuffer.length)
|
||||
|
|
|
@ -37,15 +37,18 @@
|
|||
<string name="disable">Disable</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="privacy_policy">Privacy Policy</string>
|
||||
<string name="privacy_policy_text">To be able to decode SSTV encoded images the app needs access to the microphone.
|
||||
Having access to the microphone is considered to be a sensitive permission and you have the right to know what the app does with that access:
|
||||
The data recorded from the microphone is only used to fed the SSTV decoder, VU meter and the spectrum analyzer for visualization of its frequency content.
|
||||
The app uses a very small temporary buffer in volatile memory and constantly overwrites this buffer with new data from the microphone.
|
||||
The resulting images from the SSTV decoder is the only data that gets stored in persistent storage on your Android device.</string>
|
||||
<string name="privacy_policy_text">Microphone Access:
|
||||
\n\nThis app requires access to your device\'s microphone to decode Slow Scan Television (SSTV) signals.
|
||||
The microphone captures the audio containing the SSTV transmission.
|
||||
\n\nData Handling:
|
||||
\n\nThe app uses a small temporary buffer in memory to process the audio data in real-time.
|
||||
This buffer is constantly overwritten with new data as the decoding progresses.
|
||||
The app does not store the raw audio captured from the microphone.
|
||||
Only the decoded images resulting from the SSTV process are saved on your device\'s storage.</string>
|
||||
<string name="about">About Robot36</string>
|
||||
<string name="about_text">Robot36 %1$s\nCopyright 2024 Ahmet Inan
|
||||
\n\nPlease read DISCLAIMER at the bottom of this page.
|
||||
\n\nRobot36 decodes SSTV encoded audio signals to images.
|
||||
\n\nRobot36 decodes Slow Scan Television (SSTV) images from audio.
|
||||
\n\nImplementation:\nhttps://github.com/xdsopl/robot36\nBSD Zero Clause License
|
||||
\n\nMode specifications:\nhttp://www.barberdsp.com/downloads/Dayton%%20Paper.pdf\nby JL Barber - 2000
|
||||
\n\nDISCLAIMER:\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.</string>
|
||||
|
|
Ładowanie…
Reference in New Issue