RadioLibSmol/src/protocols/SSTV/SSTV.cpp

307 lines
9.5 KiB
C++

#include "SSTV.h"
#if !defined(RADIOLIB_EXCLUDE_SSTV)
const SSTVMode_t Scottie1 {
.visCode = RADIOLIB_SSTV_SCOTTIE_1,
.width = 320,
.height = 256,
.scanPixelLen = 432,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 9000, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }
}
};
const SSTVMode_t Scottie2 {
.visCode = RADIOLIB_SSTV_SCOTTIE_2,
.width = 320,
.height = 256,
.scanPixelLen = 275,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 9000, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }
}
};
const SSTVMode_t ScottieDX {
.visCode = RADIOLIB_SSTV_SCOTTIE_DX,
.width = 320,
.height = 256,
.scanPixelLen = 1080,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 9000, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 1500, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }
}
};
const SSTVMode_t Martin1 {
.visCode = RADIOLIB_SSTV_MARTIN_1,
.width = 320,
.height = 256,
.scanPixelLen = 458,
.numTones = 8,
.tones = {
{ .type = tone_t::GENERIC, .len = 4862, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 }
}
};
const SSTVMode_t Martin2 {
.visCode = RADIOLIB_SSTV_MARTIN_2,
.width = 320,
.height = 256,
.scanPixelLen = 229,
.numTones = 8,
.tones = {
{ .type = tone_t::GENERIC, .len = 4862, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 572, .freq = 1500 }
}
};
const SSTVMode_t Wrasse {
.visCode = RADIOLIB_SSTV_WRASSE_SC2_180,
.width = 320,
.height = 256,
.scanPixelLen = 734,
.numTones = 5,
.tones = {
{ .type = tone_t::GENERIC, .len = 5523, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 500, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }
}
};
const SSTVMode_t PasokonP3 {
.visCode = RADIOLIB_SSTV_PASOKON_P3,
.width = 640,
.height = 496,
.scanPixelLen = 208,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 5208, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 1042, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1042, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1042, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }
}
};
const SSTVMode_t PasokonP5 {
.visCode = RADIOLIB_SSTV_PASOKON_P5,
.width = 640,
.height = 496,
.scanPixelLen = 312,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 7813, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 1563, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1563, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 1563, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }
}
};
const SSTVMode_t PasokonP7 {
.visCode = RADIOLIB_SSTV_PASOKON_P7,
.width = 640,
.height = 496,
.scanPixelLen = 417,
.numTones = 7,
.tones = {
{ .type = tone_t::GENERIC, .len = 10417, .freq = 1200 },
{ .type = tone_t::GENERIC, .len = 2083, .freq = 1500 },
{ .type = tone_t::SCAN_RED, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 2083, .freq = 1500 },
{ .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 },
{ .type = tone_t::GENERIC, .len = 2083, .freq = 1500 },
{ .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }
}
};
SSTVClient::SSTVClient(PhysicalLayer* phy) {
phyLayer = phy;
#if !defined(RADIOLIB_EXCLUDE_AFSK)
audioClient = nullptr;
#endif
}
#if !defined(RADIOLIB_EXCLUDE_AFSK)
SSTVClient::SSTVClient(AFSKClient* audio) {
phyLayer = audio->phyLayer;
audioClient = audio;
}
#endif
#if !defined(RADIOLIB_EXCLUDE_AFSK)
int16_t SSTVClient::begin(const SSTVMode_t& mode) {
if(audioClient == nullptr) {
// this initialization method can only be used in AFSK mode
return(RADIOLIB_ERR_WRONG_MODEM);
}
return(begin(0, mode));
}
#endif
int16_t SSTVClient::begin(float base, const SSTVMode_t& mode) {
// save mode
txMode = mode;
// calculate 24-bit frequency
baseFreq = (base * 1000000.0) / phyLayer->getFreqStep();
// configure for direct mode
return(phyLayer->startDirect());
}
int16_t SSTVClient::setCorrection(float correction) {
// check if mode is initialized
if(txMode.visCode == 0) {
return(RADIOLIB_ERR_WRONG_MODEM);
}
// apply correction factor to all timings
txMode.scanPixelLen *= correction;
for(uint8_t i = 0; i < txMode.numTones; i++) {
txMode.tones[i].len *= correction;
}
return(RADIOLIB_ERR_NONE);
}
void SSTVClient::idle() {
phyLayer->transmitDirect();
this->tone(RADIOLIB_SSTV_TONE_LEADER);
}
void SSTVClient::sendHeader() {
// save first header flag for Scottie modes
firstLine = true;
phyLayer->transmitDirect();
// send the first part of header (leader-break-leader)
this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH);
this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BREAK_LENGTH);
this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH);
// VIS start bit
this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
// VIS code
uint8_t parityCount = 0;
for(uint8_t mask = 0x01; mask < 0x80; mask <<= 1) {
if(txMode.visCode & mask) {
this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
parityCount++;
} else {
this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
}
}
// VIS parity
if(parityCount % 2 == 0) {
// even parity
this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
} else {
// odd parity
this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
}
// VIS stop bit
this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH);
}
void SSTVClient::sendLine(uint32_t* imgLine) {
// check first line flag in Scottie modes
if(firstLine && ((txMode.visCode == RADIOLIB_SSTV_SCOTTIE_1) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_2) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_DX))) {
firstLine = false;
// send start sync tone
this->tone(RADIOLIB_SSTV_TONE_BREAK, 9000);
}
// send all tones in sequence
for(uint8_t i = 0; i < txMode.numTones; i++) {
if((txMode.tones[i].type == tone_t::GENERIC) && (txMode.tones[i].len > 0)) {
// sync/porch tones
this->tone(txMode.tones[i].freq, txMode.tones[i].len);
} else {
// scan lines
for(uint16_t j = 0; j < txMode.width; j++) {
uint32_t color = imgLine[j];
switch(txMode.tones[i].type) {
case(tone_t::SCAN_RED):
color &= 0x00FF0000;
color >>= 16;
break;
case(tone_t::SCAN_GREEN):
color &= 0x0000FF00;
color >>= 8;
break;
case(tone_t::SCAN_BLUE):
color &= 0x000000FF;
break;
case(tone_t::GENERIC):
break;
}
this->tone(RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), txMode.scanPixelLen);
}
}
}
}
uint16_t SSTVClient::getPictureHeight() const {
return(txMode.height);
}
void SSTVClient::tone(float freq, uint32_t len) {
Module* mod = phyLayer->getMod();
uint32_t start = mod->hal->micros();
#if !defined(RADIOLIB_EXCLUDE_AFSK)
if(audioClient != nullptr) {
audioClient->tone(freq, false);
} else {
phyLayer->transmitDirect(baseFreq + (freq / phyLayer->getFreqStep()));
}
#else
phyLayer->transmitDirect(baseFreq + (freq / phyLayer->getFreqStep()));
#endif
mod->waitForMicroseconds(start, len);
}
#endif