Out of memory for a simple 10Kb photo?

Who will help me?

In this case, helping is correcting the code below because it only sends lowercase pictures to the API, less than 10 Kb.

Just do it. Take the picture and send it to API.

But it's amazing, even having a 4 Mb PSRAM, it only sends very small pictures. Alright, the PSRAM is for another purpose but why not leave about 200 Kb free for work?

Anyway...

// ESP DEV MODULE PSRAM enable e Default 4 Mb with SPIFFS (1.2 mB APP / 1.5 mB SPIFFS)

#include "Arduino.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include "Base64.h"

int nowifi;

const char* ssid = "SXXXX_2Ghz";
const char* password = "sab56743";

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22
#define flash              4

void setup()  {

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
   
  Serial.begin(115200);
  delay(100);
  Serial.print("FreeHeap Ă©: ");Serial.println(ESP.getFreeHeap());
   
  pinMode(flash,OUTPUT);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_CIF; // UXGA(1600x1200) / SXGA(1280x1024 / XGA(1024x768) / SVGA(800x600) / VGA(640x480) / CIF(352x288) / QVGA(320x240) / QQVGA(160x120)
  config.jpeg_quality = 30;
  config.fb_count = 2;
                       
  if(psramFound()){
    Serial.println("PSRAM OK");
//    config.frame_size = FRAMESIZE_CIF;
//    config.jpeg_quality = 10;           
//    config.fb_count = 2;               
//  } else {
//    Serial.print("not found");
//    config.frame_size = FRAMESIZE_CIF;
//    config.jpeg_quality = 10;
//    config.fb_count = 1;
   }

  esp_err_t err = esp_camera_init(&config); //Inicialização da câmera   
   if (err != ESP_OK) {     
     Serial.printf("O início da câmera falhou com erro 0x%x", err);
     delay(1000);
     ESP.restart();     
  }

/*
  sensor_t * s = esp_camera_sensor_get();      //see certs.h for more info
  s->set_brightness(s, -1);     // -2 to 2 **************************
  s->set_contrast(s, 0);       // -2 to 2
  s->set_saturation(s, 0);     // -2 to 2
  s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  s->set_whitebal(s, 1);       // 0 = disable , 1 = enable
  s->set_awb_gain(s, 1);       // 0 = disable , 1 = enable
  s->set_wb_mode(s, 0);        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_exposure_ctrl(s, 1);  // 0 = disable , 1 = enable
  s->set_aec2(s, 0);           // 0 = disable , 1 = enable
  s->set_ae_level(s, 0);       // -2 to 2
  s->set_aec_value(s, 200);    // 0 to 1200
  s->set_gain_ctrl(s, 1);      // 0 = disable , 1 = enable
  s->set_agc_gain(s, 0);       // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
  s->set_bpc(s, 0);            // 0 = disable , 1 = enable
  s->set_wpc(s, 1);            // 0 = disable , 1 = enable
  s->set_raw_gma(s, 1);        // 0 = disable , 1 = enable
  s->set_lenc(s, 1);           // 0 = disable , 1 = enable
  s->set_hmirror(s, 0);        // 0 = disable , 1 = enable
  s->set_vflip(s, 0);          // 0 = disable , 1 = enable
  s->set_dcw(s, 1);            // 0 = disable , 1 = enable
  s->set_colorbar(s, 0);       // 0 = disable , 1 = enable
*/

  sensor_t * s = esp_camera_sensor_get();
  s->set_brightness(s, 2);
  s->set_contrast(s, 1);
  s->set_whitebal(s, 1);
  s->set_special_effect(s, 0);
  s->set_wb_mode(s, 1);
  s->set_aec_value(s, 0);
  s->set_lenc(s, 1);
  s->set_hmirror(s, 0); // disable, 1 - enable 

 
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
//    Serial.print(".");
      delay(1000);
      nowifi++;
      Serial.println(nowifi);
       if(nowifi > 3) {
           ESP.restart();    
       }
  }

    Serial.println(WiFi.localIP());

    Serial.println(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
    
    takeandsendphoto();
    delay(2000); 
}
 
void loop() {
}

const char* takeandsendphoto() {

  camera_fb_t * fb = NULL;
  uint8_t* _jpg_buf = NULL;
  esp_err_t res = ESP_OK;
  size_t frame_size = 0;
  
   digitalWrite(flash,HIGH);
    delay(100);
  fb = esp_camera_fb_get();
    digitalWrite(flash,LOW);
    
  if (!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }

  Serial.println(String("Photo size is = ")+String(fb->len));
  
  char *input = (char *)fb->buf;
  char output[base64_enc_len(3)];
    String imageFile = "data:image/jpeg;base64,";
    for (int i=0;i<fb->len;i++) {
      base64_encode(output, (input++), 3);
//      if (i%3==0) imageFile += (String(output));
      if (i%3==0) imageFile += urlencode(String(output));
    }
//  Serial.println(imageFile);
   
    esp_camera_fb_return(fb);

    const char* host = "app.hall-api.com";

    String fLogin = "28QFGHTY-oWdqv7-FPOPIUJ2p-UPXODVM7NF62";
    String phoneNumber = "55219XCVFBG314";
    String message = "Teste API";
    String image_base64 = imageFile;
    
    String url = "/v1/instance/3RTF-000108-gIzE-lP4C-ZP97FGDXWHB9/token/38LYUTIHV-Ntht-aTUo-St62-JCEIUA4TUVSY/message"; // caminho da URL
    String postStr = "fLogin="+fLogin+"&ACTION=IMAGE&destination="+phoneNumber+"&text="+message+"&image_name=img.jpg&image_base64="+imageFile;

//    Serial.print("postStr Ă©: ");Serial.println(postStr);

 if (WiFi.status() == WL_CONNECTED) {

  WiFiClientSecure client;
  client.setInsecure();

    if (client.connect(host, 443)) {
        Serial.println("Send start: "+String(postStr.length()));
        client.print(String("POST ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: " + String(postStr.length()) + "\r\n\r\n" +
                 postStr + "\r\n");
    
        while (client.connected()) {
          String line = client.readStringUntil('\n');
          if (line == "\r") {
           Serial.println("body response HTTPS:");
           break;
          }
        }
    
       String body = client.readString();
        Serial.println(body);
     } else {
       Serial.println("No connect HTTPS");
       }  
 } 
 return("");
}
//---------------------------------------------------------------------------------------------------
String urlencode(String str)
{
    String encodedString="";
    char c;
    char code0;
    char code1;
    char code2;
    for (int i =0; i < str.length(); i++){
      c=str.charAt(i);
      if (c == ' '){
        encodedString+= '+';
      } else if (isalnum(c)){
        encodedString+=c;
      } else{
        code1=(c & 0xf)+'0';
        if ((c & 0xf) >9){
            code1=(c & 0xf) - 10 + 'A';
        }
        c=(c>>4)&0xf;
        code0=c+'0';
        if (c > 9){
            code0=c - 10 + 'A';
        }
        code2='\0';
        encodedString+='%';
        encodedString+=code0;
        encodedString+=code1;
        //encodedString+=code2;
      }
      yield();
    }
    return encodedString;
}
/////////////////////////////// END

Photos smaller than 10Kb works. Larger than 10K approximately gives this error:

You fill a 10k array by 3-byte Strings:

This causes very severe memory fragmentation. With this approach, your 10k can easily take up several times more memory space.

I already tried a block that uses malloc, with and without this mbedtls lib. Using just HttpClient, JSON...etc.

Do you have any ideas, any code snippets I can try here?

Please post a link to the documentation for that API.

I think you should rearrange the base64 encode function to get rid String using.

I also think. But let's see if someone comes along who also finds it and who brings codes, let's stay tuned.

Example of various languages ​​EXCEPT c++

Not all esp32 modules have PSRAM.
I suspect your's does not.
So it will store the picture in SRAM. which is only 520KB

I forgot that your method of writing programs is to wait for someone to give you code. Maybe I should put you on my ignore list again, as I done it month ago.
See you in April

1 Like

He forgot ? why ? put me on your forever ignore list.

But don't forget.

That number 4194252 is PSRAM.

Was my request unclear? Let me try again

I'm just wanting to see if it works to send a photo straight from espcam to any api.

Now I switched to this other API that receives photos, it's a photo hosting.

It does not work with base64 encoding, that is, it accepts the photo directly in jpg.

The API details are these:

API Documentation for Photo and Video Hosting
Overview
ThumbSnap offers a free API that can be used to upload and host images and videos programmatically (you can use this with cURL, PHP, Node.js, Java, tinyMCE, or via whatever language your application or website is programmed in).

If you have any questions, issues, or have commercial / custom-integration needs, please contact us -- we'll be happy to help.

Photo / Video Formats
ThumbSnap currently supports images in .jpg, .gif and .png. We also support webp, tiff, svg, bmp and a few other image formats that get auto-converted to .jpg.

For videos, we support .mp4, .avi, .mov, .qt, .mkv and a few other video formats (mp4 recommended).

Photos and videos may be up to 48 MB in size. If you need support for a different format or larger file size, contact us.

API Key
You will need an API key to use the ThumbSnap API. Any images or videos uploaded using your API key will appear in your "My Photos" section (you can manage/view/delete images from there also).

Your API key is: 1111331da19676758c4a1c73f9ee1d76a

API Method: Upload
API URL: https://thumbsnap.com/api/upload

This is the main API method used to upload images to ThumbSnap

Required fields:

media - Binary image/video data. Should be sent as an HTTP POST formatted as multipart/form-data
key - The API key provided by ThumbSnap
Upon successfully posting an image, a JSON-formatted document will be returned with a 'url' which is the full URL to the uploaded photo's page at ThumbSnap. This URL must be linked to any ThumbSnap-hosted thumbnails or images used by your application.

Sample Response
A successful image upload will return a JSON document as follows:

{
  "data": {
    "id": "soLHmGdX",
    "url": "https://thumbsnap.com/soLHmGdX",
    "media": "https://thumbsnap.com/i/soLHmGdX.png",
    "thumb": "https://thumbsnap.com/t/soLHmGdX.jpg",
    "width": 224,
    "height": 224
  },
  "success": true,
  "status": 200
}
Sample Error Response
If an image upload fails, a message similar to the following will be returned:

{
  "error": {
    "message": "Upload failed. Invalid image"
  },
  "success": false,
  "status": 400
}

How does the code of the first post#1 have to be for this to work ?

Anybody know ?

Your constant jumping around between different hosts, APIs, and upload methods makes trying to help you very difficult. That along with the fact that you frequently don't provide complete information or ignore questions/suggestions completely is a major disincentive to even wanting to help you at all.

Who wants to help solve my innocent questions in 3 minutes. As long as you know how to do it, and want to, of course. But, I don't want my posts to demotivate anyone. If this is happening to you, stop reading my posts.

I think I know how to solve your original question about "Out of memory for a simple 10Kb photo?" when using the original API ("app.hall-api.com"). In fact, I've written code that does, it seems to work with images larger than 200KB. I just need to check it against that API. But, now you've changed APIs (again) ....

It's not a job. I'd just like to see what they say works work. And in this case, it is to send a simple picture of the espcam to an API. I signed up for two API's, the doc of this last one only talks about 2 key and media requests. If this is complex for you. Don't worry. The tests I did went wrong, I decided to come here and ask HOW THE CODE HAS TO BE IN ORDER TO WORK.

Many have difficulty understanding this simple question. Any other answer will not be helpful for me.

I there any reason you can't share a link for the documentation of those APIs?

As I said, I've already coded a complete solution to the 10KB problem using the first API. Trouble is that API (host = app.hall-api.com) fails DNS resolution when 'client.connect(host, 443)' is called. No memory problems but DNS. So, there's something wrong with the URL that you provided (app.hall-api.com). If you provide the links for me to fix that, I'll share my solution.

gfvalvo, having learned a little about programming language will not make you richer, nor happier, nor will you live longer because of it. This goes for anyone who thinks otherwise.

This time we do it differently, post your solution and we'll move forward.

For now, I ask you to modify this code here to send a direct photo, without seeking base64, for this other photo hosting API:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Arduino.h>
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

String serverName = "192.168.1.XXX";   // REPLACE WITH YOUR Raspberry Pi IP ADDRESS
//String serverName = "example.com";   // OR REPLACE WITH YOUR DOMAIN NAME

String serverPath = "/upload.php";     // The default serverPath should be upload.php

const int serverPort = 80;

WiFiClient client;

// CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

const int timerInterval = 30000;    // time between each HTTP POST image
unsigned long previousMillis = 0;   // last time image was sent

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_CIF;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    config.fb_count = 1;
  }
  
  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  sendPhoto(); 
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= timerInterval) {
    sendPhoto();
    previousMillis = currentMillis;
  }
}

String sendPhoto() {
  String getAll;
  String getBody;

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }
  
  Serial.println("Connecting to server: " + serverName);

  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");    
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;
  
    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    client.println();
    client.print(head);
  
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0; n<fbLen; n=n+1024) {
      if (n+1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client.write(fbBuf, remainder);
      }
    }   
    client.print(tail);
    
    esp_camera_fb_return(fb);
    
    int timoutTimer = 10000;
    long startTimer = millis();
    boolean state = false;
    
    while ((startTimer + timoutTimer) > millis()) {
      Serial.print(".");
      delay(100);      
      while (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (getAll.length()==0) { state=true; }
          getAll = "";
        }
        else if (c != '\r') { getAll += String(c); }
        if (state==true) { getBody += String(c); }
        startTimer = millis();
      }
      if (getBody.length()>0) { break; }
    }
    Serial.println();
    client.stop();
    Serial.println(getBody);
  }
  else {
    getBody = "Connection to " + serverName +  " failed.";
    Serial.println(getBody);
  }
  return getBody;
}

That code is already not "seeking base64". There's nothing to modify, it's already what you asked for.