Skip to content

Instantly share code, notes, and snippets.

@me-no-dev
Last active January 15, 2025 06:46

Revisions

  1. me-no-dev revised this gist Nov 22, 2019. 1 changed file with 9 additions and 2 deletions.
    11 changes: 9 additions & 2 deletions AsyncWebCamera.cpp
    Original file line number Diff line number Diff line change
    @@ -292,6 +292,10 @@ void getCameraStatus(AsyncWebServerRequest *request){
    static char json_response[1024];

    sensor_t * s = esp_camera_sensor_get();
    if(s == NULL){
    request->send(501);
    return;
    }
    char * p = json_response;
    *p++ = '{';

    @@ -339,10 +343,13 @@ void setCameraVar(AsyncWebServerRequest *request){
    int val = atoi(request->arg("val").c_str());

    sensor_t * s = esp_camera_sensor_get();
    if(s == NULL){
    request->send(501);
    return;
    }


    int res = 0;

    if(!strcmp(variable, "framesize")) res = s->set_framesize(s, (framesize_t)val);
    else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
    else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
    @@ -375,7 +382,7 @@ void setCameraVar(AsyncWebServerRequest *request){
    request->send(404);
    return;
    }
    log_d("Got setting %s with value %d", var.c_str(), val);
    log_d("Got setting %s with value %d. Res: %d", var.c_str(), val, res);

    AsyncWebServerResponse * response = request->beginResponse(200);
    response->addHeader("Access-Control-Allow-Origin", "*");
  2. me-no-dev created this gist Jun 23, 2019.
    397 changes: 397 additions & 0 deletions AsyncWebCamera.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,397 @@
    #include "Arduino.h"
    #include "esp_camera.h"
    #include "ESPAsyncWebServer.h"

    typedef struct {
    camera_fb_t * fb;
    size_t index;
    } camera_frame_t;

    #define PART_BOUNDARY "123456789000000000000987654321"
    static const char* STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
    static const char* STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
    static const char* STREAM_PART = "Content-Type: %s\r\nContent-Length: %u\r\n\r\n";

    static const char * JPG_CONTENT_TYPE = "image/jpeg";
    static const char * BMP_CONTENT_TYPE = "image/x-windows-bmp";

    class AsyncBufferResponse: public AsyncAbstractResponse {
    private:
    uint8_t * _buf;
    size_t _len;
    size_t _index;
    public:
    AsyncBufferResponse(uint8_t * buf, size_t len, const char * contentType){
    _buf = buf;
    _len = len;
    _callback = nullptr;
    _code = 200;
    _contentLength = _len;
    _contentType = contentType;
    _index = 0;
    }
    ~AsyncBufferResponse(){
    if(_buf != nullptr){
    free(_buf);
    }
    }
    bool _sourceValid() const { return _buf != nullptr; }
    virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override{
    size_t ret = _content(buf, maxLen, _index);
    if(ret != RESPONSE_TRY_AGAIN){
    _index += ret;
    }
    return ret;
    }
    size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
    memcpy(buffer, _buf+index, maxLen);
    if((index+maxLen) == _len){
    free(_buf);
    _buf = nullptr;
    }
    return maxLen;
    }
    };

    class AsyncFrameResponse: public AsyncAbstractResponse {
    private:
    camera_fb_t * fb;
    size_t _index;
    public:
    AsyncFrameResponse(camera_fb_t * frame, const char * contentType){
    _callback = nullptr;
    _code = 200;
    _contentLength = frame->len;
    _contentType = contentType;
    _index = 0;
    fb = frame;
    }
    ~AsyncFrameResponse(){
    if(fb != nullptr){
    esp_camera_fb_return(fb);
    }
    }
    bool _sourceValid() const { return fb != nullptr; }
    virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override{
    size_t ret = _content(buf, maxLen, _index);
    if(ret != RESPONSE_TRY_AGAIN){
    _index += ret;
    }
    return ret;
    }
    size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
    memcpy(buffer, fb->buf+index, maxLen);
    if((index+maxLen) == fb->len){
    esp_camera_fb_return(fb);
    fb = nullptr;
    }
    return maxLen;
    }
    };

    class AsyncJpegStreamResponse: public AsyncAbstractResponse {
    private:
    camera_frame_t _frame;
    size_t _index;
    size_t _jpg_buf_len;
    uint8_t * _jpg_buf;
    uint64_t lastAsyncRequest;
    public:
    AsyncJpegStreamResponse(){
    _callback = nullptr;
    _code = 200;
    _contentLength = 0;
    _contentType = STREAM_CONTENT_TYPE;
    _sendContentLength = false;
    _chunked = true;
    _index = 0;
    _jpg_buf_len = 0;
    _jpg_buf = NULL;
    lastAsyncRequest = 0;
    memset(&_frame, 0, sizeof(camera_frame_t));
    }
    ~AsyncJpegStreamResponse(){
    if(_frame.fb){
    if(_frame.fb->format != PIXFORMAT_JPEG){
    free(_jpg_buf);
    }
    esp_camera_fb_return(_frame.fb);
    }
    }
    bool _sourceValid() const {
    return true;
    }
    virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override {
    size_t ret = _content(buf, maxLen, _index);
    if(ret != RESPONSE_TRY_AGAIN){
    _index += ret;
    }
    return ret;
    }
    size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
    if(!_frame.fb || _frame.index == _jpg_buf_len){
    if(index && _frame.fb){
    uint64_t end = (uint64_t)micros();
    int fp = (end - lastAsyncRequest) / 1000;
    log_printf("Size: %uKB, Time: %ums (%.1ffps)\n", _jpg_buf_len/1024, fp);
    lastAsyncRequest = end;
    if(_frame.fb->format != PIXFORMAT_JPEG){
    free(_jpg_buf);
    }
    esp_camera_fb_return(_frame.fb);
    _frame.fb = NULL;
    _jpg_buf_len = 0;
    _jpg_buf = NULL;
    }
    if(maxLen < (strlen(STREAM_BOUNDARY) + strlen(STREAM_PART) + strlen(JPG_CONTENT_TYPE) + 8)){
    //log_w("Not enough space for headers");
    return RESPONSE_TRY_AGAIN;
    }
    //get frame
    _frame.index = 0;

    _frame.fb = esp_camera_fb_get();
    if (_frame.fb == NULL) {
    log_e("Camera frame failed");
    return 0;
    }

    if(_frame.fb->format != PIXFORMAT_JPEG){
    unsigned long st = millis();
    bool jpeg_converted = frame2jpg(_frame.fb, 80, &_jpg_buf, &_jpg_buf_len);
    if(!jpeg_converted){
    log_e("JPEG compression failed");
    esp_camera_fb_return(_frame.fb);
    _frame.fb = NULL;
    _jpg_buf_len = 0;
    _jpg_buf = NULL;
    return 0;
    }
    log_i("JPEG: %lums, %uB", millis() - st, _jpg_buf_len);
    } else {
    _jpg_buf_len = _frame.fb->len;
    _jpg_buf = _frame.fb->buf;
    }

    //send boundary
    size_t blen = 0;
    if(index){
    blen = strlen(STREAM_BOUNDARY);
    memcpy(buffer, STREAM_BOUNDARY, blen);
    buffer += blen;
    }
    //send header
    size_t hlen = sprintf((char *)buffer, STREAM_PART, JPG_CONTENT_TYPE, _jpg_buf_len);
    buffer += hlen;
    //send frame
    hlen = maxLen - hlen - blen;
    if(hlen > _jpg_buf_len){
    maxLen -= hlen - _jpg_buf_len;
    hlen = _jpg_buf_len;
    }
    memcpy(buffer, _jpg_buf, hlen);
    _frame.index += hlen;
    return maxLen;
    }

    size_t available = _jpg_buf_len - _frame.index;
    if(maxLen > available){
    maxLen = available;
    }
    memcpy(buffer, _jpg_buf+_frame.index, maxLen);
    _frame.index += maxLen;

    return maxLen;
    }
    };


    void sendBMP(AsyncWebServerRequest *request){
    camera_fb_t * fb = esp_camera_fb_get();
    if (fb == NULL) {
    log_e("Camera frame failed");
    request->send(501);
    return;
    }

    uint8_t * buf = NULL;
    size_t buf_len = 0;
    unsigned long st = millis();
    bool converted = frame2bmp(fb, &buf, &buf_len);
    log_i("BMP: %lums, %uB", millis() - st, buf_len);
    esp_camera_fb_return(fb);
    if(!converted){
    request->send(501);
    return;
    }

    AsyncBufferResponse * response = new AsyncBufferResponse(buf, buf_len, BMP_CONTENT_TYPE);
    if (response == NULL) {
    log_e("Response alloc failed");
    request->send(501);
    return;
    }
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    }

    void sendJpg(AsyncWebServerRequest *request){
    camera_fb_t * fb = esp_camera_fb_get();
    if (fb == NULL) {
    log_e("Camera frame failed");
    request->send(501);
    return;
    }

    if(fb->format == PIXFORMAT_JPEG){
    AsyncFrameResponse * response = new AsyncFrameResponse(fb, JPG_CONTENT_TYPE);
    if (response == NULL) {
    log_e("Response alloc failed");
    request->send(501);
    return;
    }
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    return;
    }

    size_t jpg_buf_len = 0;
    uint8_t * jpg_buf = NULL;
    unsigned long st = millis();
    bool jpeg_converted = frame2jpg(fb, 80, &jpg_buf, &jpg_buf_len);
    esp_camera_fb_return(fb);
    if(!jpeg_converted){
    log_e("JPEG compression failed: %lu", millis());
    request->send(501);
    return;
    }
    log_i("JPEG: %lums, %uB", millis() - st, jpg_buf_len);

    AsyncBufferResponse * response = new AsyncBufferResponse(jpg_buf, jpg_buf_len, JPG_CONTENT_TYPE);
    if (response == NULL) {
    log_e("Response alloc failed");
    request->send(501);
    return;
    }
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    }


    void streamJpg(AsyncWebServerRequest *request){
    AsyncJpegStreamResponse *response = new AsyncJpegStreamResponse();
    if(!response){
    request->send(501);
    return;
    }
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    }

    void getCameraStatus(AsyncWebServerRequest *request){
    static char json_response[1024];

    sensor_t * s = esp_camera_sensor_get();
    char * p = json_response;
    *p++ = '{';

    p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
    p+=sprintf(p, "\"quality\":%u,", s->status.quality);
    p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
    p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
    p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
    p+=sprintf(p, "\"sharpness\":%d,", s->status.sharpness);
    p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect);
    p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode);
    p+=sprintf(p, "\"awb\":%u,", s->status.awb);
    p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain);
    p+=sprintf(p, "\"aec\":%u,", s->status.aec);
    p+=sprintf(p, "\"aec2\":%u,", s->status.aec2);
    p+=sprintf(p, "\"denoise\":%u,", s->status.denoise);
    p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level);
    p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value);
    p+=sprintf(p, "\"agc\":%u,", s->status.agc);
    p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain);
    p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling);
    p+=sprintf(p, "\"bpc\":%u,", s->status.bpc);
    p+=sprintf(p, "\"wpc\":%u,", s->status.wpc);
    p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma);
    p+=sprintf(p, "\"lenc\":%u,", s->status.lenc);
    p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
    p+=sprintf(p, "\"vflip\":%u,", s->status.vflip);
    p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
    p+=sprintf(p, "\"colorbar\":%u", s->status.colorbar);
    *p++ = '}';
    *p++ = 0;

    AsyncWebServerResponse * response = request->beginResponse(200, "application/json", json_response);
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    }

    void setCameraVar(AsyncWebServerRequest *request){
    if(!request->hasArg("var") || !request->hasArg("val")){
    request->send(404);
    return;
    }
    String var = request->arg("var");
    const char * variable = var.c_str();
    int val = atoi(request->arg("val").c_str());

    sensor_t * s = esp_camera_sensor_get();


    int res = 0;

    if(!strcmp(variable, "framesize")) res = s->set_framesize(s, (framesize_t)val);
    else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
    else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
    else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
    else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
    else if(!strcmp(variable, "sharpness")) res = s->set_sharpness(s, val);
    else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val);
    else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val);
    else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val);
    else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val);
    else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val);
    else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val);
    else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val);
    else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val);
    else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val);
    else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val);
    else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val);
    else if(!strcmp(variable, "denoise")) res = s->set_denoise(s, val);
    else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val);
    else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val);
    else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val);
    else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val);
    else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val);
    else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val);
    else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
    else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);

    else {
    log_e("unknown setting %s", var.c_str());
    request->send(404);
    return;
    }
    log_d("Got setting %s with value %d", var.c_str(), val);

    AsyncWebServerResponse * response = request->beginResponse(200);
    response->addHeader("Access-Control-Allow-Origin", "*");
    request->send(response);
    }

    AsyncWebServer server(80);

    void setup(){
    Serial.begin(115200);
    Serial.setDebugOutput(true);

    server.on("/bmp", HTTP_GET, sendBMP);
    server.on("/capture", HTTP_GET, sendJpg);
    server.on("/stream", HTTP_GET, streamJpg);
    server.on("/control", HTTP_GET, setCameraVar);
    server.on("/status", HTTP_GET, getCameraStatus);
    server.begin();
    }