AASDecoder for HDRadio PNG Images

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

Leave a Comment