1

I have an ESP32 having a cam and it is transmitting compressed JPEG stream.

  • TTGO T-Journal ESP32
  • Camera Development Board
  • OV2640
  • SMA WiFi
  • 3dbi Antenna
  • 0.91 OLED Camera Board

I have an ESP32 having a 1.8 inch TFT screen and a SD card slot which act as receiver.

  • ESP32 TS V1.2
  • MPU9250
  • 1.8 Inch TFT
  • Bluetooth
  • Wifi
  • MicroSD Card Slot
  • Speakers Module

On Chrome browser, I get a display of the JPEG images as a continuous image after image (video).

I can see the incoming compressed JPEG stream and i print it on serial monitor . But I need to decode/ decompress the JPEGstream at receiving end and paint pixel of TFT screen .

Which steps am I missing? Can anybody suggest code of JPEG image rendering on TFT screen?

enter image description here enter image description here

Sketch of transmitter:

#include "OV2640.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>

#define ENABLE_OLED //if want use oled ,turn on thi macro

#ifdef ENABLE_OLED
#include "SSD1306.h"
#define OLED_ADDRESS 0x3c
#define I2C_SDA 14
#define I2C_SCL 13
SSD1306Wire display(OLED_ADDRESS, I2C_SDA, I2C_SCL, GEOMETRY_128_32);
#endif

OV2640 cam;
WebServer server(80);

IPAddress apIP = IPAddress(192, 168, 1, 1);

void handle_jpg_stream(void)
{
    WiFiClient client = server.client();
    String response = "HTTP/1.1 200 OK\r\n";
response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
server.sendContent(response);

while (1)
{
    cam.run();
    if (!client.connected())
        break;
    response = "--frame\r\n";
    response += "Content-Type: image/jpeg\r\n\r\n";
    server.sendContent(response);

    client.write((char *)cam.getfb(), cam.getSize());
    server.sendContent("\r\n");
    if (!client.connected())
        break;
    }
}

void handle_jpg(void)
{
    WiFiClient client = server.client();

    cam.run();
    if (!client.connected())
    {
        return;
    }
     String response = "HTTP/1.1 200 OK\r\n";
     response += "Content-disposition: inline; filename=capture.jpg\r\n";
    response += "Content-type: image/jpeg\r\n\r\n";
    server.sendContent(response);
    client.write((char *)cam.getfb(), cam.getSize());
}

void handleNotFound()
{
    String message = "Server is running!\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
   message += "\nArguments: ";
   message += server.args();
   message += "\n";
   server.send(200, "text/plain", message);
}

void setup()
{
    Serial.begin(115200);
    while (!Serial)
    {
        ;
     }
camera_config_t camera_config;
camera_config.ledc_channel = LEDC_CHANNEL_0;
camera_config.ledc_timer = LEDC_TIMER_0;
camera_config.pin_d0 = 17;
camera_config.pin_d1 = 35;
camera_config.pin_d2 = 34;
camera_config.pin_d3 = 5;
camera_config.pin_d4 = 39;
camera_config.pin_d5 = 18;
camera_config.pin_d6 = 36;
camera_config.pin_d7 = 19;
camera_config.pin_xclk = 27;
camera_config.pin_pclk = 21;
camera_config.pin_vsync = 22;
camera_config.pin_href = 26;
camera_config.pin_sscb_sda = 25;
camera_config.pin_sscb_scl = 23;
camera_config.pin_reset = 15;
camera_config.xclk_freq_hz = 20000000;
camera_config.pixel_format = CAMERA_PF_JPEG;
camera_config.frame_size = CAMERA_FS_SVGA;

cam.init(camera_config);

WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
bool result = WiFi.softAP("TTGO-CAMERA", "12345678", 1, 0);
if (!result)
{
    Serial.println("AP Config failed.");
    return;
}
else
{
    Serial.println("AP Config Success.");
    Serial.print("AP MAC: ");
    Serial.println(WiFi.softAPmacAddress());
}

#ifdef ENABLE_OLED
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(128 / 2, 32 / 2, WiFi.softAPIP().toString());
display.display();
#endif

server.on("/", HTTP_GET, handle_jpg_stream);
server.on("/jpg", HTTP_GET, handle_jpg);
server.onNotFound(handleNotFound);
server.begin();
}

void loop()
{
    server.handleClient();
}

Sketch of receiver:

#include <WiFi.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <Adafruit_ImageReader.h> // Image-reading functions
//#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <SPI.h>
#include "WiFi.h"
#include <JPEGDecoder.h>
//#include <FileIO.h>




// For the breakout, you can use any 2 or 3 pins
// These pins will also work for the 1.8" TFT shield
#define TFT_CS 16
#define TFT_RST 9  // you can also connect this to the Arduino reset
                  // in which case, set this #define pin to -1!
#define TFT_DC 17

// Option 2: use any pins but a little slower!
#define TFT_SCLK 5   // set these to be whatever pins you like!
#define TFT_MOSI 23   // set these to be whatever pins you like!
#define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 15
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   13



Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK,     TFT_RST);

String text="";


const char* ssid     = "TTGO-CAMERA";
const char* password = "12345678";
const char* host     = "192.168.1.1";
const char* url      = "/";

IPAddress local_IP(192, 168, 1, 3);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

//PImage logo;


#define BUFFPIXEL 20
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have     padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;               // pixel color for tft
  File     bmpFile; File     bmpFile1;
  uint32_t pos = 0, startTime ;
  uint8_t xx=0; uint16_t yy=0; int ww=0; int hh=0;
  int bmpfilepos;

//How to use -
//tft.fillScreen(ST7735_BLACK);
//testdrawtext(2,3,ST7735_GREEN,text);



void testdrawtext( int leftoffset , int topoffset , uint16_t color ,  String text ) {
  tft.setCursor(leftoffset, topoffset);  // (offset from left margin , offset from top margin. )
  tft.setTextColor(color);
  tft.setTextWrap(true);
  tft.setTextSize(1);
  tft.print(text  ); 
    }




void jpegbytesdisplay()
{
 // Create a buffer for the packet
    char dataBuff[240];

    delay(5000);

  Serial.print("connecting to ");
  Serial.println(host);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  int cnt=0;

  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }

  Serial.print("Requesting URL: ");
  Serial.println(url);

  // This will send the request to the cam server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
           "Host: " + host + "\r\n" +
           "Connection: close\r\n\r\n");
  unsigned long timeout = millis();



  while (client.available() == 0) 
  {
    if (millis() - timeout > 5000) 
    {
      Serial.println(">>> Client Timeout !");
      client.stop();
      return;
    }
  }


   int max_y=200;
   int ypos =200;
   int max_x=0 ;
   int xpos =0;

  // Read all the lines of the reply from server and print them to Serial

  ImageReturnCode stat;
  while (client.available()) 
  {
    //================================
   // String line = client.readStringUntil('\r');
   // Serial.print(line);


    //================================
    //bytes put into char[len] databuffer.
    size_t len=client.available();
    unsigned char buf[len];
    client.readBytes(buf, len);

    Serial.println("------------------------");
    Serial.println(len);
    //Serial.write(buf);

     //================================
     // bufffer to tft screen pixel rendering

      int col=0;     
  int buffidx =0;
  cnt++;

    tft.fillScreen(ST7735_BLACK);
    tft.startWrite();
    tft.setAddrWindow(0, 0, 500, 500);


String fname="/image2" + String(cnt) + ".jpg";
File myFile = SD.open(fname, FILE_WRITE);
myFile.write(buf,len);
myFile.close();  

     for (col=0; col<len; col++) 
       { 
          Serial.print(buf[col]);
       }
    Serial.println();
    Serial.println("------------------------");


      // For each pixel...
     for (col=0; col<len; col++) 
     { 
         if (buffidx >= sizeof(buf)) 
              {  
          buffidx = 0; // Set index to beginning
                break;
        }


        b = buf[buffidx++]; //          [B]
        g = buf[buffidx++]; //     [G]
        r = buf[buffidx++]; //   [R]



        tft.pushColor(tft.color565(r,g,b));   
                             // Display pixel on tft screen one after another.



   } // end pixel loop.

    //================================

   delay(1000);
    tft.endWrite();    
  }

    }







 void setupNormal()
{


   Serial.begin(115200);


   // Use this initializer if you're using a 1.8" TFT
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab

  Serial.println("Initialized");


  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }

  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }


  tft.fillScreen(ST7735_BLACK);

}


void setup()
{

    setupNormal();
    jpegbytesdisplay();


      }



void loop()
{

}
1

Here is what i found for JPEG image file display on any TFT -

i used JPEGDecoder library and instead of tft.pushColor , i used tft.drawPixel method.

  for (int jj  =mcu_y;jj<mcu_y + win_h;jj++)
     {
        for (int ii=mcu_x;ii<mcu_x + win_w ;ii++)
        {
            // TFTscreen.drawPixel(ii,jj,tft.color565(255,0,0));
             TFTscreen.drawPixel(ii,jj,*pImg++ ); 
        }
     } 

For BMP , we need rgb values to render each pixel on TFT screen.

But in case of JPG , first we need to decode and create MCU tiles on TFT. Then we paint those MCU tiles by color which we receive from JPEG stream decoder. Here *pImg is the pointer which hold the color information. As we increment *pImg++ array , each time we receive new color code of type uInt8_t type . Within MCU tiles , these color have to be injected . Each MCU tile is colored and drawn in the given sequence by considering where the border line of TFT screen is.

It automatically create a JPEG image .

//////////////////////////////////////////////////////////////
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <Adafruit_ImageReader.h> // Image-reading functions

// For the breakout, you can use any 2 or 3 pins
// These pins will also work for the 1.8" TFT shield
#define TFT_CS 16
#define TFT_RST 9  // you can also connect this to the Arduino reset
                      // set these to be whatever pins you like!
// in which case, set this #define pin to -1!
#define TFT_DC 17

// Option 2: use any pins but a little slower!
#define TFT_SCLK 5   // set these to be whatever pins you like!
#define TFT_MOSI 23   

Adafruit_ST7735 TFTscreen = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

//////////////////////////////////////////////////////////////

#include <JPEGDecoder.h>  // JPEG decoder library

//////////////////////////////////////////////////////////////
// include the necessary libraries
#include <SPI.h>
#include <SD.h>
//////////////////////////////////////////////////////////////


// pin definition for the Mega
#define sd_cs  53
#define lcd_cs 49
#define dc     48
#define rst    47

#define TFT_WHITE 0xFFFF
#define TFT_BLACK 0x0000
#define TFT_RED   0xF800


 //////////////////////////////////////////////////////////////

 #define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 15
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   13



  Adafruit_ImageReader reader;
    ///////////////////////////////////////////////////////





String text="";

// this function determines the minimum of two numbers
#define minimum(a,b)     (((a) < (b)) ? (a) : (b))



  //====================================================================================
    //   setup
    //====================================================================================

void setup() {
  // initialize the GLCD and show a message
  // asking the user to open the serial line
  //////////////////////////////////////////////////////////////////  
   // Use this initializer if you're using a 1.8" TFT
  TFTscreen.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
//////////////////////////////////////////////////////////////////

  TFTscreen.fillScreen(TFT_WHITE); // Alternative: 

TFTscreen.background(255, 255, 255);



TFTscreen.setTextColor(TFT_RED); // Alternative: TFTscreen.stroke(0, 0, 255);
  TFTscreen.println();
  TFTscreen.println(F("Arduino TFT Jpeg Example"));

  TFTscreen.setTextColor(TFT_BLACK); // Alternative: TFTscreen.stroke(0, 0, 0);
  TFTscreen.println(F("Open serial monitor"));
  TFTscreen.println(F("to run the sketch"));

  // initialize the serial port: it will be used to
  // print some diagnostic info
  Serial.begin(115200);
  while (!Serial) {
    // wait for serial port to connect. Needed for native USB port only
  }

  // clear the GLCD screen before starting
//  TFTscreen.background(255, 255, 255);
  TFTscreen.fillScreen(TFT_WHITE);

  // try to access the SD card. If that fails (e.g.
  // no card present), the setup process will stop.
  Serial.print(F("Initializing SD card..."));

//////////////////////////////////////////////////////////////////


  SPI.begin(PIN_NUM_CLK, PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_CS);

  if (!SD.begin(PIN_NUM_CS)) {
    Serial.println(F("failed!"));
    while (1); // SD initialisation failed so wait here
  }
  Serial.println(F("OK!"));
//////////////////////////////////////////////////////////////////


  // initialize and clear the GLCD screen
 // TFTscreen.begin();
  TFTscreen.fillScreen(TFT_WHITE); // Alternative: TFTscreen.background(255, 255, 255);

  // now that the SD card can be accessed, check the
  // image file exists.
  if (SD.exists("/EagleEye.jpg")) {
    Serial.println("EagleEye.jpg found on SD card.");
  } else {
    Serial.println("EagleEye.jpg not found on SD card.");
    while (1); // Image file missing so stay here
  }

}

//====================================================================================
//   Main loop
//====================================================================================
void loop() {

  // open the image file
  File jpgFile = SD.open( "/EagleEye.jpg", FILE_READ);

  // initialise the decoder to give access to image information
  JpegDec.decodeSdFile(jpgFile);

  // print information about the image to the serial port
  jpegInfo();

  // render the image onto the screen at coordinate 0,0
  renderJPEG(0, 0);

  // wait a little bit before clearing the screen to random color and drawing again
  delay(4000);

  // clear screen
  TFTscreen.fillScreen(random(0xFFFF));  // Alternative: TFTscreen.background(255, 255, 255);
}

//====================================================================================
//   Print information about the image
//====================================================================================

void jpegInfo() {
  Serial.println(F("==============="));
  Serial.println(F("JPEG image info"));
  Serial.println(F("==============="));
  Serial.print(F(  "Width      :")); Serial.println(JpegDec.width);
  Serial.print(F(  "Height     :")); Serial.println(JpegDec.height);
  Serial.print(F(  "Components :")); Serial.println(JpegDec.comps);
  Serial.print(F(  "MCU / row  :")); Serial.println(JpegDec.MCUSPerRow);
  Serial.print(F(  "MCU / col  :")); Serial.println(JpegDec.MCUSPerCol);
  Serial.print(F(  "Scan type  :")); Serial.println(JpegDec.scanType);
  Serial.print(F(  "MCU width  :")); Serial.println(JpegDec.MCUWidth);
  Serial.print(F(  "MCU height :")); Serial.println(JpegDec.MCUHeight);
  Serial.println(F("==============="));
}

//====================================================================================
//   Decode and paint onto the TFT screen
//====================================================================================
void renderJPEG(int xpos, int ypos) {

  // retrieve infomration about the image
  uint16_t *pImg;
  uint16_t mcu_w = JpegDec.MCUWidth;
  uint16_t mcu_h = JpegDec.MCUHeight;
  uint32_t max_x = JpegDec.width;
  uint32_t max_y = JpegDec.height;

  // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
  // Typically these MCUs are 16x16 pixel blocks
  // Determine the width and height of the right and bottom edge image blocks
  uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
  uint32_t min_h = minimum(mcu_h, max_y % mcu_h);

  // save the current image block size
  uint32_t win_w = mcu_w;
  uint32_t win_h = mcu_h;

  // record the current time so we can measure how long it takes to draw an image
  uint32_t drawTime = millis();

  // save the coordinate of the right and bottom edges to assist image cropping
  // to the screen size
  max_x += xpos;
  max_y += ypos;

   char str[100];
  // read each MCU block until there are no more
  while ( JpegDec.read()) {

    // save a pointer to the image block
    pImg = JpegDec.pImage;

    // calculate where the image block should be drawn on the screen
    int mcu_x = JpegDec.MCUx * mcu_w + xpos;
    int mcu_y = JpegDec.MCUy * mcu_h + ypos;

    // check if the image block size needs to be changed for the right and bottom edges
    if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
    else win_w = min_w;
    if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
    else win_h = min_h;

    // calculate how many pixels must be drawn
    uint32_t mcu_pixels = win_w * win_h;

    // draw image block if it will fit on the screen
    if ( ( mcu_x + win_w) <= TFTscreen.width() && ( mcu_y + win_h) <= TFTscreen.height()) {
      // open a window onto the screen to paint the pixels into
      //TFTscreen.setAddrWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
      //TFTscreen.setAddrWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
      // push all the image block pixels to the screen


 ////////////////////////////////////////////////////////

 // while (mcu_pixels--)
 // {
 // TFTscreen.pushColor(*pImg++);
 // } // Send to TFT 16 bits at a time



 ////////////////////////////////////////////////////////



 //  Write all MCU pixels to the TFT window





  for (int jj=mcu_y;jj<mcu_y + win_h;jj++)
     {
        for (int ii=mcu_x;ii<mcu_x + win_w ;ii++)
        {
            // TFTscreen.drawPixel(ii,jj,tft.color565(255,0,0));
             TFTscreen.drawPixel(ii,jj,*pImg++ ); 
        }
     } 


 ////////////////////////////////////////////////////////

    }

    // stop drawing blocks if the bottom of the screen has been reached
    // the abort function will close the file
    else if ( ( mcu_y + win_h) >= TFTscreen.height()) JpegDec.abort();

  }

  // calculate how long it took to draw the image
  drawTime = millis() - drawTime; // Calculate the time it took



 // print the results to the serial port
  Serial.print  ("Total render time was    : "); Serial.print(drawTime); Serial.println(" ms");
  Serial.println("=====================================");

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.