diff --git a/app/src/routes/peripherals/camera/Camera.svelte b/app/src/routes/peripherals/camera/Camera.svelte index 9c7e2fe..89dc387 100644 --- a/app/src/routes/peripherals/camera/Camera.svelte +++ b/app/src/routes/peripherals/camera/Camera.svelte @@ -2,37 +2,16 @@ import { user } from '$lib/stores/user'; import SettingsCard from "$lib/components/SettingsCard.svelte"; import Camera from '~icons/mdi/camera-outline' - import VideoCamera from '~icons/mdi/videocam-outline' - import Reload from '~icons/mdi/reload' import Record from '~icons/mdi/radio-button-unchecked' - import Recording from '~icons/mdi/radio-button-checked' - - import Spinner from '$lib/components/Spinner.svelte'; import CameraSetting from './CameraSetting.svelte'; - import { onDestroy, onMount } from 'svelte'; + import { onDestroy } from 'svelte'; const ws_token = `?access_token=${$user.bearer_token}` - let stillId = 0 - - let recording = false - - let videoMode = false - - const takeStill = () => stillId += 1 - - const toggleMode = () => { - videoMode = !videoMode - } - - let timer:number - - onMount(() => { - timer = setInterval(takeStill, 1000) - }) + let source = "/api/camera/stream"+ ws_token; onDestroy(() => { - clearInterval(timer) + source = "" }) @@ -40,28 +19,6 @@ Camera - + Live-stream -
- -
- -
\ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/CameraService.cpp b/esp32/lib/ESP32-sveltekit/CameraService.cpp index 07fb827..2066a12 100644 --- a/esp32/lib/ESP32-sveltekit/CameraService.cpp +++ b/esp32/lib/ESP32-sveltekit/CameraService.cpp @@ -17,6 +17,16 @@ camera_fb_t *safe_camera_fb_get() { return fb; } +sensor_t *safe_sensor_get() { + sensor_t *s = NULL; + if (xSemaphoreTake(cameraMutex, portMAX_DELAY) == pdTRUE) { + s = esp_camera_sensor_get(); + } + return s; +} + +void safe_sensor_return() { xSemaphoreGive(cameraMutex); } + CameraService::CameraService(PsychicHttpServer *server, TaskManager *taskManager, SecurityManager *securityManager) @@ -30,9 +40,15 @@ void CameraService::begin() { STILL_SERVICE_PATH, HTTP_GET, _securityManager->wrapRequest( std::bind(&CameraService::cameraStill, this, std::placeholders::_1), - AuthenticationPredicates::IS_AUTHENTICATED)); + AuthenticationPredicates::IS_AUTHENTICATED)); + _server->on(STREAM_SERVICE_PATH, HTTP_GET, + _securityManager->wrapRequest( + std::bind(&CameraService::cameraStream, this, + std::placeholders::_1), + AuthenticationPredicates::IS_AUTHENTICATED)); ESP_LOGV("CameraService", "Registered GET endpoint: %s", STILL_SERVICE_PATH); + ESP_LOGV("CameraService", "Registered GET endpoint: %s", STREAM_SERVICE_PATH); } esp_err_t CameraService::InitializeCamera() { @@ -82,9 +98,69 @@ esp_err_t CameraService::cameraStill(PsychicRequest *request) { request->reply(500, "text/plain", "Camera capture failed"); return ESP_FAIL; } - PsychicStreamResponse response = PsychicStreamResponse(request, "image/jpeg", "capture.jpg"); + PsychicStreamResponse response = + PsychicStreamResponse(request, "image/jpeg", "capture.jpg"); response.beginSend(); response.write(fb->buf, fb->len); esp_camera_fb_return(fb); return response.endSend(); } + +void streamTask(void *pv) { + esp_err_t res = ESP_OK; + + PsychicRequest *request = static_cast(pv); + + httpd_req_t *copy = nullptr; + res = httpd_req_async_handler_begin(request->request(), ©); + if (res != ESP_OK) { + return; + } + PsychicHttpServer *server = request->server(); + PsychicRequest new_request = PsychicRequest(server, copy); + request = &new_request; + + PsychicStreamResponse response = PsychicStreamResponse(request, _STREAM_CONTENT_TYPE); + camera_fb_t *fb = NULL; + + char *part_buf[64]; + size_t buf_len = 0; + uint8_t *buf = NULL; + int64_t fr_start = esp_timer_get_time(); + + response.beginSend(); + + for (;;) { + fb = safe_camera_fb_get(); + if (!fb) { + ESP_LOGE("Stream", "Camera capture failed"); + break; + } + if(fb->format != PIXFORMAT_JPEG){ + if(!frame2jpg(fb, 80, &buf, &buf_len)) break; + } else { + buf_len = fb->len; + buf = fb->buf; + } + + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, buf_len); + size_t w = response.write((const char *)part_buf, hlen); + w += response.write((const char *)buf, buf_len); + w += response.write((char *)_STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + if (w == 62) break; + esp_camera_fb_return(fb); + buf = NULL; + taskYIELD(); + int64_t delay = 30000ll - esp_timer_get_time() - fr_start; + if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay)); + } + response.endSend(); + httpd_req_async_handler_complete(copy); + vTaskDelete(NULL); +} + +esp_err_t CameraService::cameraStream(PsychicRequest *request) { + _taskManager->createTask(streamTask, "Stream client task", 4096, request, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + return ESP_OK; +} \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/CameraService.h b/esp32/lib/ESP32-sveltekit/CameraService.h index b610eb5..620c8fa 100644 --- a/esp32/lib/ESP32-sveltekit/CameraService.h +++ b/esp32/lib/ESP32-sveltekit/CameraService.h @@ -1,15 +1,16 @@ #ifndef CameraService_h #define CameraService_h -#define CAMERA_MODEL_AI_THINKER - #include -#include #include #include #include #include #include +#include + +#define CAMERA_MODEL_AI_THINKER +#include #define STREAM_SERVICE_PATH "/api/camera/stream" #define STILL_SERVICE_PATH "/api/camera/still" @@ -17,6 +18,8 @@ #define PART_BOUNDARY "frame" camera_fb_t *safe_camera_fb_get(); +sensor_t* safe_sensor_get(); +void safe_sensor_return(); class CameraService { @@ -31,6 +34,7 @@ class CameraService SecurityManager *_securityManager; PsychicStream _videoStream; esp_err_t cameraStill(PsychicRequest *request); + esp_err_t cameraStream(PsychicRequest *request); esp_err_t InitializeCamera(); }; diff --git a/esp32/lib/ESP32-sveltekit/CameraSettingsService.h b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h index a7c14db..fe162ad 100644 --- a/esp32/lib/ESP32-sveltekit/CameraSettingsService.h +++ b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h @@ -1,6 +1,7 @@ #ifndef CameraSettingsService_h #define CameraSettingsService_h +#include #include #include #include @@ -137,11 +138,42 @@ class CameraSettingsService : public StatefulService { void begin() { _httpEndpoint.begin(); _eventEndpoint.begin(); + sensor_t *s = safe_sensor_get(); + _state.pixformat = s->pixformat; + _state.framesize = s->status.framesize; + _state.brightness = s->status.brightness; + _state.contrast = s->status.contrast; + _state.saturation = s->status.saturation; + _state.sharpness = s->status.sharpness; + _state.denoise = s->status.denoise; + _state.gainceiling = (gainceiling_t)s->status.gainceiling; + _state.quality = s->status.quality; + _state.colorbar = s->status.colorbar; + _state.awb_gain = s->status.awb_gain; + _state.wb_mode = s->status.wb_mode; + _state.aec2 = s->status.aec2; + _state.ae_level = s->status.ae_level; + _state.aec_value = s->status.aec_value; + _state.agc_gain = s->status.agc_gain; + _state.bpc = s->status.bpc; + _state.wpc = s->status.wpc; + _state.special_effect = s->status.special_effect; + _state.raw_gma = s->status.raw_gma; + _state.lenc = s->status.lenc; + _state.hmirror = s->status.hmirror; + _state.vflip = s->status.vflip; + _state.dcw = s->status.dcw; + safe_sensor_return(); } void updateCamera() { ESP_LOGI("CameraSettings", "Updating camera settings"); - sensor_t *s = esp_camera_sensor_get(); + sensor_t *s = safe_sensor_get(); + if (!s) { + ESP_LOGE("CameraSettings", "Failed to update camera settings"); + safe_sensor_return(); + return; + } s->set_pixformat(s, _state.pixformat); s->set_framesize(s, _state.framesize); s->set_brightness(s, _state.brightness); @@ -152,14 +184,11 @@ class CameraSettingsService : public StatefulService { s->set_gainceiling(s, _state.gainceiling); s->set_quality(s, _state.quality); s->set_colorbar(s, _state.colorbar); - s->set_whitebal(s, _state.whitebal); s->set_awb_gain(s, _state.awb_gain); s->set_wb_mode(s, _state.wb_mode); - s->set_exposure_ctrl(s, _state.exposure_ctrl); s->set_aec2(s, _state.aec2); s->set_ae_level(s, _state.ae_level); s->set_aec_value(s, _state.aec_value); - s->set_gain_ctrl(s, _state.gain_ctrl); s->set_agc_gain(s, _state.agc_gain); s->set_bpc(s, _state.bpc); s->set_wpc(s, _state.wpc); @@ -169,6 +198,7 @@ class CameraSettingsService : public StatefulService { s->set_hmirror(s, _state.hmirror); s->set_vflip(s, _state.vflip); s->set_dcw(s, _state.dcw); + safe_sensor_return(); } private: