This was written as a decoder for the Teensy HDRadio project using a Si468X from SiLabs
#ifndef LOTDEC_h
#define LOTDEC_h
#include "SDCard.h"
#include "yspng.h"
#include "tjpgd.h"
#define FILE_MAX 25 * 1024 // KB blocks memory per file/port
#define MAX_PORTS 2 // max data ports per audio program
#define FILEPATH "aasfiles"
#define JPEGMEM 4000 // memory buffer for TJpg
#define COLOR_BCKGND 0x0
#define OUTPIXL 100
#define AAS_PATH 20
#define AAS_ART "aas/%s%04x.ooo"
#define AAS_LOGO "aas/%s%d.ooo"
#define TEMP_PORT "aas/%04x.tmp"
extern SDCard sdcard;
enum IMAGEFORMAT
{
NON = 0,
JPG = 1,
PNG = 2,
RGB = 3
};
void newImageReceived(uint16_t lotid);
struct FileInfoHolder
{
uint16_t length, size, sequence;
File file;
bool startcollection = false;
char filename[AAS_PATH];
char tempfilename[AAS_PATH];
void reset(bool clearname)
{
length = 0;
sequence = 0;
size = 0;
startcollection = 0;
if (clearname)
{
memset(filename, 0, AAS_PATH);
memset(tempfilename, 0, AAS_PATH);
}
}
};
struct PORTDataHolder
{
uint16_t portid = 0;
FileInfoHolder finfo; // assume one port produces one file stream
void store(const char *sisshortname, const uint8_t hdprogram, const uint16_t port, uint8_t *inbff, const uint16_t length)
{
if (port != portid)
return;
const uint16_t LOTId = (inbff[3] << 8) | inbff[2];
uint8_t *buf = inbff;
uint32_t *p32 = (uint32_t *)buf;
const uint32_t insequence = p32[1];
uint16_t len = length;
buf += 8;
len -= 8;
if (insequence == 0)
{
p32 = (uint32_t *)buf;
uint32_t infilesize = p32[2];
if (infilesize > FILE_MAX)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File too large... will not store\n", portid);
#endif
return;
}
buf += 16;
len -= 16;
uint8_t *p = (uint8_t *)memchr(buf, '.', len - 16);
if (p == NULL)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File has invalid name\n", portid);
#endif
finfo.reset(true);
return;
}
uint8_t namelen = p - buf + 4;
char inboundfilename[namelen + 1];
memset(inboundfilename, 0, namelen + 1);
memcpy(inboundfilename, buf, namelen);
finfo.reset(true);
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File >> %s, %d bytes\n", portid, inboundfilename, infilesize);
#endif
bool proceed = determineFileName(sisshortname, hdprogram, inboundfilename, LOTId);
if (!proceed)
return;
buf += namelen;
len -= namelen;
// check if exists?
bool exists = checkifexists(infilesize);
if (exists)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : file exists: %s\n", portid, finfo.filename);
#endif
// dont expect any bytes
}
else
{
newOutFile();
if (finfo.file)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s, size %d, START\n", port, finfo.tempfilename, infilesize);
#endif
writeOutFile(buf, len);
finfo.sequence = 1;
finfo.size = infilesize;
finfo.startcollection = true;
}
}
}
else if (insequence == finfo.sequence)
{
if (finfo.startcollection)
{
finfo.sequence++;
if (finfo.length + len > finfo.size)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s overflowed\n", port, finfo.tempfilename);
#endif
discardOutFile();
return;
}
writeOutFile(buf, len);
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s, %d (%.1f %%)\n", portid, finfo.tempfilename, finfo.length, (float)finfo.length * 100 / finfo.size);
#endif
if (finfo.length == finfo.size)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s, size %d, SAVED\n", portid, finfo.tempfilename, finfo.size);
#endif
closeOutFile();
if (convertOutFile())
newImageReceived(LOTId);
}
}
}
else if (finfo.size)
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : %s expected %d, got %d\n", portid, finfo.tempfilename, finfo.sequence, insequence);
#endif
}
}
void writeOutFile(uint8_t *buf, uint16_t len)
{
if (finfo.file)
{
finfo.file.write(buf, len);
finfo.length += len;
}
}
void discardOutFile()
{
if (finfo.file)
{
finfo.file.close();
if (sdcard.remove(finfo.tempfilename)) // delete lot file
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s, DELETED\n", portid, finfo.tempfilename);
#endif
}
if (sdcard.remove(finfo.filename)) // delete ooo file
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File %s, DELETED\n", portid, finfo.filename);
#endif
finfo.reset(false);
}
}
}
void newOutFile()
{
sprintf(finfo.tempfilename, TEMP_PORT, portid);
discardOutFile();
finfo.file = sdcard.open(finfo.tempfilename, FILE_WRITE);
}
void closeOutFile()
{
if (finfo.file)
{
finfo.file.close();
finfo.reset(false);
}
}
bool convertOutFile()
{
bool status = false;
if (sdcard.exists(finfo.tempfilename))
{
#ifdef DEBUG
long tm = millis();
#endif
IMAGEFORMAT imgfmt;
uint8_t buffer[2];
uint16_t tsize = 0;
File f = sdcard.open(finfo.tempfilename);
if (f)
{
tsize = f.fileSize();
f.read(buffer, 2);
f.close();
}
const byte jpgh[2] = {0xFF, 0xD8};
const byte pngh[2] = {0x89, 0x50};
if (memcmp(buffer, jpgh, 2) == 0)
{
imgfmt = JPG;
}
else if (memcmp(buffer, pngh, 2) == 0)
{
imgfmt = PNG;
}
else
{
return false;
}
//
uint16_t tot = OUTPIXL * OUTPIXL;
uint16_t outb[tot];
for (int s = 0; s < tot; s++)
{
outb[s] = COLOR_BCKGND;
}
if (imgfmt == JPG)
{
TJpg tjpg(0, 0, outb);
if (tjpg.Decode(finfo.tempfilename) == JDR_OK)
status = true;
// tjpg.openJPG(finfo.tempfilename, 0, 0, outb);
// JDEC jdec;
// uint8_t work[JPEGMEM];
// if (tjpg.jd_prepare(&jdec, work, JPEGMEM) == JDR_OK)
// {
// Serial.printf("Image: %u by %u. %u bytes used. ", jdec.width, jdec.height, JPEGMEM - jdec.sz_pool);
// if (tjpg.jd_decomp(&jdec, 1) == JDR_OK)
// status = true;
// }
// tjpg.closeJPG();
}
else if (imgfmt == PNG)
{
YsRawPngDecoder png(0, 0, COLOR_BCKGND, outb);
if (png.Decode(finfo.tempfilename) == YSOK)
status = true;
}
if (status)
{
if (sdcard.exists(finfo.filename))
sdcard.remove(finfo.filename);
File fout = sdcard.open(finfo.filename, FILE_WRITE);
if (fout)
{
uint8_t header[8] = {'R', 'G', 'B', OUTPIXL, OUTPIXL, (uint8_t)(tsize >> 8), (uint8_t)(tsize & 0xFF), 0};
fout.write(header, 8);
uint8_t *pntr = (uint8_t *)outb;
fout.write(pntr, tot * 2);
fout.close();
#ifdef DEBUG
tm = millis() - tm;
Serial.printf("LOTDecoder P[%02x] : File %s -> %s, CONVERTED %dms\n", portid, finfo.tempfilename, finfo.filename, tm);
#endif
}
}
}
sdcard.remove(finfo.tempfilename);
return status;
}
bool checkifexists(uint16_t size)
{
uint32_t existsize = 0;
if (sdcard.exists(finfo.filename))
{
File f = sdcard.open(finfo.filename);
if (f)
{
uint8_t head[8];
f.read(head, 8);
f.close();
if (memcmp(head, "RGB", 3) == 0)
{
existsize = (head[5] << 8) | head[6];
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File << %s, %d bytes\n", portid, finfo.filename, existsize);
#endif
}
}
}
if (existsize == 0)
return false;
else
{
if (existsize != size) // compare filesize
{
sdcard.remove(finfo.filename);
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : File removed %s, Filesize not matching\n", portid, finfo.filename);
#endif
return false;
}
else
return true;
}
}
bool determineFileName(const char *sisshortname, const uint8_t hdprogram, char *infilename, uint16_t lotid)
{
const uint8_t fl = strlen(infilename);
if (fl < 6)
return false;
if (memcmp(infilename, "SL", 2) == 0)
{ // station logo
// SLWISX$$01109820000.png -> WISX1.lot
// SLWDAS$$01109910000.png -> WDAS1.lot
// SLWBEN$$130005.jpg
// uint8_t hdprogram = 1;
// if (fl > 9)
// {
// hdprogram = infilename[9] - '0';
// }
sprintf(finfo.filename, AAS_LOGO, sisshortname, hdprogram + 1);
return true;
}
else
{ // album art
// WISX016c60.jpg -> WISX6c60.lot
// WRFF017b57.jpg -> WRFF3fd2.lot
sprintf(finfo.filename, AAS_ART, sisshortname, lotid);
return true;
}
}
bool write_file(char *outname, uint8_t *buff, uint16_t size)
{
if (sdcard.available() && (sdcard.freeMB > 1))
{
uint32_t existsize = 0;
if (sdcard.exists(outname))
{
File f = sdcard.open(outname);
existsize = f.fileSize();
f.close();
}
if (existsize != size) // compare filesize
{
if (existsize)
{
if (sdcard.remove(outname)) // remove other size
existsize = 0;
}
if (existsize == 0)
{
File file = sdcard.open(outname, FILE_WRITE);
if (file)
{
uint16_t remaining = size;
uint8_t *ptr = buff;
while (remaining > 512)
{
file.write(ptr, 512);
ptr += 512;
remaining -= 512;
}
file.write(ptr, remaining);
file.close();
return 1;
}
}
}
#ifdef DEBUG
else
{
Serial.printf("LOTDecoder P[%02x] : file exists: %s\n", portid, outname);
}
#endif
}
return 0;
}
void reset()
{
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : reset()\n", portid);
#endif
discardOutFile();
portid = 0;
// delete all files received for the ports previously setup?
}
};
class LOTDecoder
{
public:
void reset()
{
clearPortSetup();
}
bool setupPort(uint16_t port)
{
bool s = putPortSetup(port);
#ifdef DEBUG
Serial.printf("LOTDecoder P[%02x] : setupPort() %d\n", port, s);
#endif
return s;
}
void processPortData(char *shortname, const uint8_t hdprogram, uint16_t port, uint8_t *buffer, uint16_t length)
{
// #ifdef DEBUG
// Serial.printf("processPortData P[%02x] %d bytes\n", port, length);
// #endif
signed char p = isPortSetup(port);
if ((p > -1) && (p < MAX_PORTS))
{
portdata[p].store(shortname, hdprogram, port, buffer, length);
}
}
IMAGEFORMAT checkLotIMGFormat(char *filename)
{
uint8_t buffer[3];
uint32_t size = 0;
File f = sdcard.open(filename);
if (f)
{
size = f.fileSize();
f.read(buffer, 3);
f.close();
}
// const byte jpgh[2] = {0xFF, 0xD8};
// const byte pngh[2] = {0x89, 0x50};
// const byte rgbh[3] = {'R', 'G', 'B'};
// #ifdef DEBUG
// Serial.printf("FILE DATA {0x%02x, 0x%02x}\n", buffer[0], buffer[1]);
// #endif
// if (memcmp(buffer, jpgh, 2) == 0)
// {
// return JPG;
// }
// else if (memcmp(buffer, pngh, 2) == 0)
// {
// return PNG;
// }
// else
if ((memcmp(buffer, "RGB", 3) == 0) && (size < FILE_MAX))
{
return RGB;
}
else
return NON;
}
IMAGEFORMAT getXHDRFile(const uint16_t lotid, const uint8_t hdprogram, char *shortname, char *filename, uint8_t length)
{
#ifdef DEBUG
Serial.printf("getXHDRFile lotid %4x, [%s] HD %d\n", lotid, shortname, hdprogram + 1);
#endif
IMAGEFORMAT imgfmt = NON;
extern SDCard sdcard;
if (sdcard.available())
{
if (lotid) // check album art
{
sprintf(filename, AAS_ART, shortname, lotid);
}
else
{
sprintf(filename, AAS_LOGO, shortname, hdprogram + 1);
}
if (sdcard.exists(filename))
{
imgfmt = checkLotIMGFormat(filename);
}
if (lotid && (imgfmt == NON)) // fallback to station logo
{
sprintf(filename, AAS_LOGO, shortname, hdprogram + 1);
if (sdcard.exists(filename))
{
return checkLotIMGFormat(filename);
}
}
}
return imgfmt;
}
PORTDataHolder portdata[MAX_PORTS];
signed char isPortSetup(uint16_t port)
{
for (int x = 0; x < MAX_PORTS; x++)
{
if (port == portdata[x].portid)
return x;
}
return -1;
}
void clearPortSetup()
{
for (int x = 0; x < MAX_PORTS; x++)
{
if (portdata[x].portid)
portdata[x].reset();
}
}
bool putPortSetup(uint16_t port)
{
for (int x = 0; x < MAX_PORTS; x++)
{
if (portdata[x].portid == 0)
{
portdata[x].portid = port;
portdata[x].finfo.reset(true);
return true;
}
}
return false;
}
};
#endif