espressif / esp32-camera

Apache License 2.0
1.94k stars 645 forks source link

Framebuffer timing occasionally longer #242

Closed NielschB closed 3 years ago

NielschB commented 3 years ago

Hi I am using Arduino environment 1.8.13 with ESP32 package 1.0.5-rc6 (also tested 1.0.4) with with an attempt to synchronize multiple cameras. AiThinker camera module with PSRAM and OV2640 cam.

I have observed that the time to get the framebuffer sometimes is longer than expected, e.g. Xclk = 10MHz, so a single frame takes 160ms (Vsync measured) When requesting the framebuffer at a certain moment this generally seems to take the 160ms as expected, but occasionally (1 out of 20 frames) this time increases to 2 frames or even 3. Even observed an additional delay < frame delay and it seems to take a few frames for it to 'recover'.

I have tried lowering the Xclk frequency, camera Pclk frequency, all with the expected results, but not solving this issue. I first had the impression this was a timing issue, but now I doubt this. as the xclk and pclk timing settings do not solve it. Tried running xclk 20MHz, with default pclk settings, jpg quality lowered, shows same issue.

I have observed the Vsync signal from the camera which seems fine.

Also ran my code on several ESP32-cam AiThinker modules from different manufacturers (Original and clone), no difference. Tried fb_count = 1 up to 4, no difference. JPG quality settings tried: 2,3,4,5,6 with xclk 20Mhz down to 6MHz

I have the feeling the rootcasue might be an interrupt disturbing the receival of the jpg data, but I am not sure what interrupts are used by the camera.

I'll try to strip down my code to bare minimum and attach it, just wanted to check for now if anybody has already observed this behaviour.

Any help would be appreciated, thanks!

NielschB commented 3 years ago

Ok, so stripped the code to minimum, just a loop getting framebuffers and serial printing the time it takes.

#include "esp_camera.h"
#include "Arduino.h"
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems
#include "driver/rtc_io.h"

// Pin definition for 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

// Camera vars
camera_config_t config;
camera_fb_t * fb0 = NULL; // Framebuffer 0 pointer
camera_fb_t * fb1 = NULL; // Framebuffer 1 pointer
camera_fb_t * fb2 = NULL; // Framebuffer 2 pointer
camera_fb_t * fb3 = NULL; // Framebuffer 3 pointer
sensor_t * s =  NULL; // Sensor settings pointer

void camera_init(void){
  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 = 10000000; // 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 

  config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
  config.jpeg_quality = 2;
  config.fb_count = 4; // Seems that 4 just fits in the PSRAM

  // Init Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  Serial.printf("PSRAM total/available: %u / %u", ESP.getPsramSize(), ESP.getFreePsram());  Serial.println(""); 

  s = esp_camera_sensor_get();
  s->set_brightness(s, 0);     // -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, 1);        // 0 to 4 - if awb_gain enabled (>0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_exposure_ctrl(s, 0);  // 0 = disable , 1 = enable
  s->set_aec2(s, 0);           // 0 = disable , 1 = enable
  s->set_ae_level(s, 1);       // -2 to 2
  s->set_aec_value(s, 500);    // 0 to 1200
  s->set_gain_ctrl(s, 1);      // 0 = disable , 1 = enable
  s->set_agc_gain(s, 3);       // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)1);  // 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  

  // Fetch initial data and block the buffers
  fb0 = esp_camera_fb_get();  
  fb1 = esp_camera_fb_get();  
  fb2 = esp_camera_fb_get();  
  fb3 = esp_camera_fb_get();  
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  Serial.begin(115200);
  // Powerdown camera manually with longer time as default time triggers fail occasionally
  pinMode(PWDN_GPIO_NUM, OUTPUT);
  digitalWrite(PWDN_GPIO_NUM, HIGH);   // HIGH = powerdown because its a PMOS
  Serial.println("Camera init");
  // Cam reset time
  delay(500); 
  camera_init();
}

void loop() {
    // Make sure to start at a defined moment to expect a constant capture time, rising edge of Vsync
    // (Not falling edge as this might just trigger frame start)
    while(digitalRead(VSYNC_GPIO_NUM)){ delay(0.1);}
    while(!digitalRead(VSYNC_GPIO_NUM)){ delay(0.1);}

    uint32_t total_runtime=millis();

    Serial.print("Capture");
    esp_camera_fb_return(fb0);
    esp_camera_fb_return(fb1);
    esp_camera_fb_return(fb2);
    esp_camera_fb_return(fb3);

    fb0 = esp_camera_fb_get();
    fb1 = esp_camera_fb_get();
    fb2 = esp_camera_fb_get();
    fb3 = esp_camera_fb_get();

    total_runtime=millis()-total_runtime;
    Serial.printf(" %ums done",total_runtime); Serial.println(""); 
}

This is the output which shows the non-constant camera fb time:

Capture 795ms done Capture 795ms done Capture 795ms done Capture 795ms done Capture 976ms done <<< Capture 1274ms done <<< Capture 955ms done <<< Capture 795ms done Capture 795ms done Capture 795ms done Capture 795ms done Capture 795ms done Capture 795ms done

jameszah commented 3 years ago

You could check the size of the jpeg delivered, to see if one of the jpegs during your slowdown is a bad format jpeg without the proper terminators. It is not a solution, but maybe explanation. I check all the jpegs to see if they have good format before using them.

NielschB commented 3 years ago

Hi, thank you for the suggestion. I have checked the jpegs that are returned by the FB, they are all OK. But it might be that a bad one was skipped indeed and therefore the FB takes a longer time. So I should actually check the one that was skipped, but I don't know how to get access to ones that are skipped and so not returned by the FB.

It does not seem size related as large JPG (>600kb) might be captured with constant delay while smaller ones show the additional delay, so it does not seem like a buffer issue.

One weird thing that I have noticed is that the delay might be constant when the camera's image is fixed but I can easily trigger the additional delay by moving my hand close to the lens.

NielschB commented 3 years ago

Hi jameszah, I think you are right after all with the end marker missing! I ran the test with using only 1 framebuffer which shows interesting output. The frame length is set to 160ms, I expect 320ms duration as it uses 1 frame to sync. Some frames show ~360ms duration as indicated below

Capture - size of images: 342706 kb - 315ms Capture - size of images: 352304 kb - 315ms Capture - size of images: 360449 kb - 315ms Capture - size of images: 369600 kb - 358ms <<< Capture - size of images: 366985 kb - 314ms Capture - size of images: 374859 kb - 314ms Capture - size of images: 384701 kb - 315ms Capture - size of images: 391696 kb - 315ms Capture - size of images: 398835 kb - 315ms Capture - size of images: 407456 kb - 315ms Capture - size of images: 406905 kb - 314ms Capture - size of images: 406331 kb - 314ms Capture - size of images: 406976 kb - 315ms Capture - size of images: 406924 kb - 315ms Capture - size of images: 406652 kb - 315ms Capture - size of images: 406941 kb - 314ms Capture - size of images: 407905 kb - 314ms Capture - size of images: 406470 kb - 315ms Capture - size of images: 405946 kb - 315ms Capture - size of images: 405880 kb - 315ms Capture - size of images: 407316 kb - 315ms Capture - size of images: 406793 kb - 314ms Capture - size of images: 408000 kb - 362ms <<< Capture - size of images: 408000 kb - 363ms <<< Capture - size of images: 407475 kb - 315ms Capture - size of images: 405910 kb - 315ms Capture - size of images: 407750 kb - 314ms Capture - size of images: 407737 kb - 314ms Capture - size of images: 407837 kb - 315ms Capture - size of images: 406677 kb - 315ms

What stands out is the image size of the failing frames, which is quite rounded:

Capture - size of images: 369600 kb - 358ms <<< Capture - size of images: 408000 kb - 362ms <<< Capture - size of images: 408000 kb - 363ms <<<

I checked these images in windows, they open and look perfectly fine, however the jpeg end marker is indeed missing! Still no clue how to solve this, but at least some indication on what is happening.

jameszah commented 3 years ago

Here is some code and discussion from my study of the issue - start at the end and read backwards. 😄

https://github.com/espressif/esp32-camera/issues/162#issuecomment-668222242

I never tracked down the actual error, but started counting the extra bytes transmitted after a good jpeg, and then abandoned the jpeg if I could not find a good terminator after a while.

NielschB commented 3 years ago

Indeed I also found the FFD9 somewhere a bit before the EOF. Looks to me that the check which is implemented should drop the zero's checking to fix this.

I agree that I could 'repair' the jpegs but in my case this is an issue as I am trying to synchronize the frames of multiple cameras, so if the FBs start shifting, the synchronization fails.

I'll check if I can modify the sourcecode locally and run the test again.

NielschB commented 3 years ago

So I modified the camera driver locally and indeed this solves the issue.

camera.c line 699

if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){

Modified to

if(dptr[0] == 0xFF && dptr[1] == 0xD9){

Thank you so much for your help jameszah! Hope this update will be implemented in the official release.

jameszah commented 3 years ago

Wow - that was quick.
Can you change camera.c and re-compile within the Arduino framework? I thought all that camera code was pre-compiled and embedded in arduino.bin or something. I tried to switch to platform IO and esp-idf framework but never completed the journey.

NielschB commented 3 years ago

I just downloaded the code from here and included it in my arduino sketch instead of the one that comes with the arduino package. Only had to rename some files as arduino was complaining. Renamed all files that are located here:

C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.5-rc6\tools\sdk\include\esp32-camera\

Might be possible to just rename the folder, but I didn't try.

jameszah commented 3 years ago

So you just put the ".c" files in that directory, and it will pick them up ? Do you do anything with libesp32-camera.a in tools/sdk/lib ?

Or change #include "esp_camera.h" in the program to point to the place with the modified ".h" and ".c" files ? I'll give it a try tonight. Thanks.

NielschB commented 3 years ago

Just put all files (also from private include folders and sensor folder from the package you download here) in the sketch folder, then drag them al in top bar of the arduino sketch. I changed #include "esp_camera.h" to #include "./esp_camera.h", but this should not even be required.