├── README.md ├── cam32websocket └── cam32websocket.ino ├── esp32_camera_mjpeg_multiclient ├── camera_pins.h ├── esp32_camera_mjpeg_multiclient.ino ├── home_wifi_multi.h └── src │ ├── OV2640.cpp │ └── OV2640.h ├── vue ├── esp.js └── esp.vue └── websocketserver.py /README.md: -------------------------------------------------------------------------------- 1 | ## 参考资料 2 | 3 | #### https://github.com/hellcat666/esp32_camera_mjpeg_multiclient 4 | 5 | #### https://github.com/Inglebard/esp32cam-relay 6 | 7 | #### http://element-ui.cn/vuejs/show-18130.aspx 8 | 9 | ## 注意 10 | 11 | cam32websocket 为esp32cam上面的websocket发送mjpeg流的代码 12 | 13 | esp32_camera_mjpeg_multiclient 为内网直连小程序看视频流的代码 14 | 15 | vue 为在网页上看websocket发过来的视频流代码 16 | 17 | websocketserver.py 为python做的websocket服务器,还有Bug,还没做好客户端管理。请自行解决。 18 | 19 | ## 缺陷 20 | 21 | 不能传太大分部率的视频,因为没有做分包,要换摄像头做高清的话,要做分包,jpeg都是以ff d8开始 ff d9结束。所以,你们看着办。加油。 22 | -------------------------------------------------------------------------------- /cam32websocket/cam32websocket.ino: -------------------------------------------------------------------------------- 1 | #include "WiFi.h" 2 | #include "esp_camera.h" 3 | //#include "base64.h" 4 | 5 | #include 6 | #include 7 | //#include 8 | 9 | // Pin definition for CAMERA_MODEL_AI_THINKER 10 | #define PWDN_GPIO_NUM 32 11 | #define RESET_GPIO_NUM -1 12 | #define XCLK_GPIO_NUM 0 13 | #define SIOD_GPIO_NUM 26 14 | #define SIOC_GPIO_NUM 27 15 | 16 | #define Y9_GPIO_NUM 35 17 | #define Y8_GPIO_NUM 34 18 | #define Y7_GPIO_NUM 39 19 | #define Y6_GPIO_NUM 36 20 | #define Y5_GPIO_NUM 21 21 | #define Y4_GPIO_NUM 19 22 | #define Y3_GPIO_NUM 18 23 | #define Y2_GPIO_NUM 5 24 | #define VSYNC_GPIO_NUM 25 25 | #define HREF_GPIO_NUM 23 26 | #define PCLK_GPIO_NUM 22 27 | 28 | 29 | // Replace with your network credentials 30 | const char* hostname = "ESP32CAM"; 31 | const char* ssid = "xxx"; 32 | const char* password = "xxx"; 33 | WebSocketsClient webSocket; 34 | 35 | void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { 36 | 37 | switch(type) { 38 | case WStype_DISCONNECTED: 39 | Serial.printf("[WSc] Disconnected!\n"); 40 | break; 41 | case WStype_CONNECTED: { 42 | Serial.printf("[WSc] Connected to url: %s\n", payload); 43 | webSocket.sendTXT("camlogin"); 44 | } 45 | break; 46 | case WStype_TEXT: 47 | Serial.printf("[WSc] get text: %s\n", payload); 48 | break; 49 | case WStype_BIN: 50 | // Serial.printf("[WSc] get binary length: %u\n", length); 51 | break; 52 | case WStype_PING: 53 | // pong will be send automatically 54 | Serial.printf("[WSc] get ping\n"); 55 | break; 56 | case WStype_PONG: 57 | // answer to a ping we send 58 | Serial.printf("[WSc] get pong\n"); 59 | break; 60 | } 61 | 62 | } 63 | 64 | void setupCamera() 65 | { 66 | 67 | camera_config_t config; 68 | config.ledc_channel = LEDC_CHANNEL_0; 69 | config.ledc_timer = LEDC_TIMER_0; 70 | config.pin_d0 = Y2_GPIO_NUM; 71 | config.pin_d1 = Y3_GPIO_NUM; 72 | config.pin_d2 = Y4_GPIO_NUM; 73 | config.pin_d3 = Y5_GPIO_NUM; 74 | config.pin_d4 = Y6_GPIO_NUM; 75 | config.pin_d5 = Y7_GPIO_NUM; 76 | config.pin_d6 = Y8_GPIO_NUM; 77 | config.pin_d7 = Y9_GPIO_NUM; 78 | config.pin_xclk = XCLK_GPIO_NUM; 79 | config.pin_pclk = PCLK_GPIO_NUM; 80 | config.pin_vsync = VSYNC_GPIO_NUM; 81 | config.pin_href = HREF_GPIO_NUM; 82 | config.pin_sscb_sda = SIOD_GPIO_NUM; 83 | config.pin_sscb_scl = SIOC_GPIO_NUM; 84 | config.pin_pwdn = PWDN_GPIO_NUM; 85 | config.pin_reset = RESET_GPIO_NUM; 86 | config.xclk_freq_hz = 20000000; 87 | config.pixel_format = PIXFORMAT_JPEG; 88 | 89 | config.frame_size = FRAMESIZE_VGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA 90 | config.jpeg_quality = 10; 91 | config.fb_count = 1; 92 | 93 | // Init Camera 94 | esp_err_t err = esp_camera_init(&config); 95 | if (err != ESP_OK) { 96 | Serial.printf("Camera init failed with error 0x%x", err); 97 | return; 98 | } 99 | 100 | 101 | } 102 | 103 | void setup(){ 104 | Serial.begin(115200); 105 | 106 | // Connect to Wi-Fi 107 | WiFi.begin(ssid, password); 108 | while (WiFi.status() != WL_CONNECTED) { 109 | delay(1000); 110 | Serial.println("Connecting to WiFi.."); 111 | } 112 | 113 | // Print ESP32 Local IP Address 114 | Serial.println(WiFi.localIP()); 115 | 116 | setupCamera(); 117 | 118 | // server address, port and URL 119 | webSocket.begin("192.168.31.181",5765); 120 | webSocket.onEvent(webSocketEvent); 121 | webSocket.setReconnectInterval(5000); 122 | webSocket.enableHeartbeat(15000, 3000, 2); 123 | 124 | } 125 | 126 | 127 | unsigned long messageTimestamp = 0; 128 | void loop() { 129 | webSocket.loop(); 130 | uint64_t now = millis(); 131 | 132 | if(now - messageTimestamp > 10) { 133 | messageTimestamp = now; 134 | 135 | camera_fb_t * fb = NULL; 136 | 137 | // Take Picture with Camera 138 | fb = esp_camera_fb_get(); 139 | if(!fb) { 140 | Serial.println("Camera capture failed"); 141 | return; 142 | } 143 | 144 | webSocket.sendBIN(fb->buf,fb->len); 145 | Serial.println("Image sent"); 146 | esp_camera_fb_return(fb); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /esp32_camera_mjpeg_multiclient/camera_pins.h: -------------------------------------------------------------------------------- 1 | 2 | #if defined(CAMERA_MODEL_WROVER_KIT) 3 | #define PWDN_GPIO_NUM -1 4 | #define RESET_GPIO_NUM -1 5 | #define XCLK_GPIO_NUM 21 6 | #define SIOD_GPIO_NUM 26 7 | #define SIOC_GPIO_NUM 27 8 | 9 | #define Y9_GPIO_NUM 35 10 | #define Y8_GPIO_NUM 34 11 | #define Y7_GPIO_NUM 39 12 | #define Y6_GPIO_NUM 36 13 | #define Y5_GPIO_NUM 19 14 | #define Y4_GPIO_NUM 18 15 | #define Y3_GPIO_NUM 5 16 | #define Y2_GPIO_NUM 4 17 | #define VSYNC_GPIO_NUM 25 18 | #define HREF_GPIO_NUM 23 19 | #define PCLK_GPIO_NUM 22 20 | 21 | #elif defined(CAMERA_MODEL_ESP_EYE) 22 | #define PWDN_GPIO_NUM -1 23 | #define RESET_GPIO_NUM -1 24 | #define XCLK_GPIO_NUM 4 25 | #define SIOD_GPIO_NUM 18 26 | #define SIOC_GPIO_NUM 23 27 | 28 | #define Y9_GPIO_NUM 36 29 | #define Y8_GPIO_NUM 37 30 | #define Y7_GPIO_NUM 38 31 | #define Y6_GPIO_NUM 39 32 | #define Y5_GPIO_NUM 35 33 | #define Y4_GPIO_NUM 14 34 | #define Y3_GPIO_NUM 13 35 | #define Y2_GPIO_NUM 34 36 | #define VSYNC_GPIO_NUM 5 37 | #define HREF_GPIO_NUM 27 38 | #define PCLK_GPIO_NUM 25 39 | 40 | #elif defined(CAMERA_MODEL_M5STACK_PSRAM) 41 | #define PWDN_GPIO_NUM -1 42 | #define RESET_GPIO_NUM 15 43 | #define XCLK_GPIO_NUM 27 44 | #define SIOD_GPIO_NUM 25 45 | #define SIOC_GPIO_NUM 23 46 | 47 | #define Y9_GPIO_NUM 19 48 | #define Y8_GPIO_NUM 36 49 | #define Y7_GPIO_NUM 18 50 | #define Y6_GPIO_NUM 39 51 | #define Y5_GPIO_NUM 5 52 | #define Y4_GPIO_NUM 34 53 | #define Y3_GPIO_NUM 35 54 | #define Y2_GPIO_NUM 32 55 | #define VSYNC_GPIO_NUM 22 56 | #define HREF_GPIO_NUM 26 57 | #define PCLK_GPIO_NUM 21 58 | 59 | #elif defined(CAMERA_MODEL_M5STACK_WIDE) 60 | #define PWDN_GPIO_NUM -1 61 | #define RESET_GPIO_NUM 15 62 | #define XCLK_GPIO_NUM 27 63 | #define SIOD_GPIO_NUM 22 64 | #define SIOC_GPIO_NUM 23 65 | 66 | #define Y9_GPIO_NUM 19 67 | #define Y8_GPIO_NUM 36 68 | #define Y7_GPIO_NUM 18 69 | #define Y6_GPIO_NUM 39 70 | #define Y5_GPIO_NUM 5 71 | #define Y4_GPIO_NUM 34 72 | #define Y3_GPIO_NUM 35 73 | #define Y2_GPIO_NUM 32 74 | #define VSYNC_GPIO_NUM 25 75 | #define HREF_GPIO_NUM 26 76 | #define PCLK_GPIO_NUM 21 77 | 78 | #elif defined(CAMERA_MODEL_AI_THINKER) 79 | #define PWDN_GPIO_NUM 32 80 | #define RESET_GPIO_NUM -1 81 | #define XCLK_GPIO_NUM 0 82 | #define SIOD_GPIO_NUM 26 83 | #define SIOC_GPIO_NUM 27 84 | 85 | #define Y9_GPIO_NUM 35 86 | #define Y8_GPIO_NUM 34 87 | #define Y7_GPIO_NUM 39 88 | #define Y6_GPIO_NUM 36 89 | #define Y5_GPIO_NUM 21 90 | #define Y4_GPIO_NUM 19 91 | #define Y3_GPIO_NUM 18 92 | #define Y2_GPIO_NUM 5 93 | #define VSYNC_GPIO_NUM 25 94 | #define HREF_GPIO_NUM 23 95 | #define PCLK_GPIO_NUM 22 96 | 97 | #else 98 | #error "Camera model not selected" 99 | #endif 100 | -------------------------------------------------------------------------------- /esp32_camera_mjpeg_multiclient/esp32_camera_mjpeg_multiclient.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM 4 | and ESP-EYE modules. 5 | This is tested to work with VLC and Blynk video widget and can support up to 10 6 | simultaneously connected streaming clients. 7 | Simultaneous streaming is implemented with FreeRTOS tasks. 8 | 9 | Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board 10 | (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/) 11 | 12 | Board: AI-Thinker ESP32-CAM or ESP-EYE 13 | Compile as: 14 | ESP32 Dev Module 15 | CPU Freq: 240 16 | Flash Freq: 80 17 | Flash mode: QIO 18 | Flash Size: 4Mb 19 | Patrition: Minimal SPIFFS 20 | PSRAM: Enabled 21 | */ 22 | 23 | // ESP32 has two cores: APPlication core and PROcess core (the one that runs ESP32 SDK stack) 24 | #define APP_CPU 1 25 | #define PRO_CPU 0 26 | 27 | #include "src/OV2640.h" 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // Select camera model 38 | //#define CAMERA_MODEL_WROVER_KIT 39 | //#define CAMERA_MODEL_ESP_EYE 40 | //#define CAMERA_MODEL_M5STACK_PSRAM 41 | //#define CAMERA_MODEL_M5STACK_WIDE 42 | #define CAMERA_MODEL_AI_THINKER 43 | 44 | #include "camera_pins.h" 45 | 46 | /* 47 | Next one is an include with wifi credentials. 48 | This is what you need to do: 49 | 50 | 1. Create a file called "home_wifi_multi.h" in the same folder OR under a separate subfolder of the "libraries" folder of Arduino IDE. (You are creating a "fake" library really - I called it "MySettings"). 51 | 2. Place the following text in the file: 52 | #define SSID1 "replace with your wifi ssid" 53 | #define PWD1 "replace your wifi password" 54 | 3. Save. 55 | 56 | Should work then 57 | */ 58 | #include "home_wifi_multi.h" 59 | 60 | OV2640 cam; 61 | 62 | WebServer server(80); 63 | 64 | // ===== rtos task handles ========================= 65 | // Streaming is implemented with 3 tasks: 66 | TaskHandle_t tMjpeg; // handles client connections to the webserver 67 | TaskHandle_t tCam; // handles getting picture frames from the camera and storing them locally 68 | TaskHandle_t tStream; // actually streaming frames to all connected clients 69 | 70 | // frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame 71 | SemaphoreHandle_t frameSync = NULL; 72 | 73 | // Queue stores currently connected clients to whom we are streaming 74 | QueueHandle_t streamingClients; 75 | 76 | // We will try to achieve 25 FPS frame rate 77 | const int FPS = 14; 78 | 79 | // We will handle web client requests every 50 ms (20 Hz) 80 | const int WSINTERVAL = 100; 81 | 82 | 83 | // ======== Server Connection Handler Task ========================== 84 | void mjpegCB(void* pvParameters) { 85 | TickType_t xLastWakeTime; 86 | const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL); 87 | 88 | // Creating frame synchronization semaphore and initializing it 89 | frameSync = xSemaphoreCreateBinary(); 90 | xSemaphoreGive( frameSync ); 91 | 92 | // Creating a queue to track all connected clients 93 | streamingClients = xQueueCreate( 10, sizeof(WiFiClient*) ); 94 | 95 | //=== setup section ================== 96 | 97 | // Creating RTOS task for grabbing frames from the camera 98 | xTaskCreatePinnedToCore( 99 | camCB, // callback 100 | "cam", // name 101 | 4096, // stacj size 102 | NULL, // parameters 103 | 2, // priority 104 | &tCam, // RTOS task handle 105 | APP_CPU); // core 106 | 107 | // Creating task to push the stream to all connected clients 108 | xTaskCreatePinnedToCore( 109 | streamCB, 110 | "strmCB", 111 | 4 * 1024, 112 | NULL, //(void*) handler, 113 | 2, 114 | &tStream, 115 | APP_CPU); 116 | 117 | // Registering webserver handling routines 118 | server.on("/mjpeg/1", HTTP_GET, handleJPGSstream); 119 | server.on("/jpg", HTTP_GET, handleJPG); 120 | server.onNotFound(handleNotFound); 121 | 122 | // Starting webserver 123 | server.begin(); 124 | 125 | //=== loop() section =================== 126 | xLastWakeTime = xTaskGetTickCount(); 127 | for (;;) { 128 | server.handleClient(); 129 | 130 | // After every server client handling request, we let other tasks run and then pause 131 | taskYIELD(); 132 | vTaskDelayUntil(&xLastWakeTime, xFrequency); 133 | } 134 | } 135 | 136 | 137 | // Commonly used variables: 138 | volatile size_t camSize; // size of the current frame, byte 139 | volatile char* camBuf; // pointer to the current frame 140 | 141 | 142 | // ==== RTOS task to grab frames from the camera ========================= 143 | void camCB(void* pvParameters) { 144 | 145 | TickType_t xLastWakeTime; 146 | 147 | // A running interval associated with currently desired frame rate 148 | const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS); 149 | 150 | // Mutex for the critical section of swithing the active frames around 151 | portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED; 152 | 153 | // Pointers to the 2 frames, their respective sizes and index of the current frame 154 | char* fbs[2] = { NULL, NULL }; 155 | size_t fSize[2] = { 0, 0 }; 156 | int ifb = 0; 157 | 158 | //=== loop() section =================== 159 | xLastWakeTime = xTaskGetTickCount(); 160 | 161 | for (;;) { 162 | 163 | // Grab a frame from the camera and query its size 164 | cam.run(); 165 | size_t s = cam.getSize(); 166 | 167 | // If frame size is more that we have previously allocated - request 125% of the current frame space 168 | if (s > fSize[ifb]) { 169 | fSize[ifb] = s * 4 / 3; 170 | fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]); 171 | } 172 | 173 | // Copy current frame into local buffer 174 | char* b = (char*) cam.getfb(); 175 | memcpy(fbs[ifb], b, s); 176 | 177 | // Let other tasks run and wait until the end of the current frame rate interval (if any time left) 178 | taskYIELD(); 179 | vTaskDelayUntil(&xLastWakeTime, xFrequency); 180 | 181 | // Only switch frames around if no frame is currently being streamed to a client 182 | // Wait on a semaphore until client operation completes 183 | xSemaphoreTake( frameSync, portMAX_DELAY ); 184 | 185 | // Do not allow interrupts while switching the current frame 186 | portENTER_CRITICAL(&xSemaphore); 187 | camBuf = fbs[ifb]; 188 | camSize = s; 189 | ifb++; 190 | ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence 191 | portEXIT_CRITICAL(&xSemaphore); 192 | 193 | // Let anyone waiting for a frame know that the frame is ready 194 | xSemaphoreGive( frameSync ); 195 | 196 | // Technically only needed once: let the streaming task know that we have at least one frame 197 | // and it could start sending frames to the clients, if any 198 | xTaskNotifyGive( tStream ); 199 | 200 | // Immediately let other (streaming) tasks run 201 | taskYIELD(); 202 | 203 | // If streaming task has suspended itself (no active clients to stream to) 204 | // there is no need to grab frames from the camera. We can save some juice 205 | // by suspedning the tasks 206 | if ( eTaskGetState( tStream ) == eSuspended ) { 207 | vTaskSuspend(NULL); // passing NULL means "suspend yourself" 208 | } 209 | } 210 | } 211 | 212 | 213 | // ==== Memory allocator that takes advantage of PSRAM if present ======================= 214 | char* allocateMemory(char* aPtr, size_t aSize) { 215 | 216 | // Since current buffer is too smal, free it 217 | if (aPtr != NULL) free(aPtr); 218 | 219 | 220 | size_t freeHeap = ESP.getFreeHeap(); 221 | char* ptr = NULL; 222 | 223 | // If memory requested is more than 2/3 of the currently free heap, try PSRAM immediately 224 | if ( aSize > freeHeap * 2 / 3 ) { 225 | if ( psramFound() && ESP.getFreePsram() > aSize ) { 226 | ptr = (char*) ps_malloc(aSize); 227 | } 228 | } 229 | else { 230 | // Enough free heap - let's try allocating fast RAM as a buffer 231 | ptr = (char*) malloc(aSize); 232 | 233 | // If allocation on the heap failed, let's give PSRAM one more chance: 234 | if ( ptr == NULL && psramFound() && ESP.getFreePsram() > aSize) { 235 | ptr = (char*) ps_malloc(aSize); 236 | } 237 | } 238 | 239 | // Finally, if the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition. 240 | if (ptr == NULL) { 241 | ESP.restart(); 242 | } 243 | return ptr; 244 | } 245 | 246 | 247 | // ==== STREAMING ====================================================== 248 | const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ 249 | "Access-Control-Allow-Origin: *\r\n" \ 250 | "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; 251 | const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; 252 | const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; 253 | const int hdrLen = strlen(HEADER); 254 | const int bdrLen = strlen(BOUNDARY); 255 | const int cntLen = strlen(CTNTTYPE); 256 | 257 | 258 | // ==== Handle connection request from clients =============================== 259 | void handleJPGSstream(void) 260 | { 261 | // Can only acommodate 10 clients. The limit is a default for WiFi connections 262 | if ( !uxQueueSpacesAvailable(streamingClients) ) return; 263 | 264 | 265 | // Create a new WiFi Client object to keep track of this one 266 | WiFiClient* client = new WiFiClient(); 267 | *client = server.client(); 268 | 269 | // Immediately send this client a header 270 | client->write(HEADER, hdrLen); 271 | client->write(BOUNDARY, bdrLen); 272 | 273 | // Push the client to the streaming queue 274 | xQueueSend(streamingClients, (void *) &client, 0); 275 | 276 | // Wake up streaming tasks, if they were previously suspended: 277 | if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam ); 278 | if ( eTaskGetState( tStream ) == eSuspended ) vTaskResume( tStream ); 279 | } 280 | 281 | 282 | // ==== Actually stream content to all connected clients ======================== 283 | void streamCB(void * pvParameters) { 284 | char buf[16]; 285 | TickType_t xLastWakeTime; 286 | TickType_t xFrequency; 287 | 288 | // Wait until the first frame is captured and there is something to send 289 | // to clients 290 | ulTaskNotifyTake( pdTRUE, /* Clear the notification value before exiting. */ 291 | portMAX_DELAY ); /* Block indefinitely. */ 292 | 293 | xLastWakeTime = xTaskGetTickCount(); 294 | for (;;) { 295 | // Default assumption we are running according to the FPS 296 | xFrequency = pdMS_TO_TICKS(1000 / FPS); 297 | 298 | // Only bother to send anything if there is someone watching 299 | UBaseType_t activeClients = uxQueueMessagesWaiting(streamingClients); 300 | if ( activeClients ) { 301 | // Adjust the period to the number of connected clients 302 | xFrequency /= activeClients; 303 | 304 | // Since we are sending the same frame to everyone, 305 | // pop a client from the the front of the queue 306 | WiFiClient *client; 307 | xQueueReceive (streamingClients, (void*) &client, 0); 308 | 309 | // Check if this client is still connected. 310 | 311 | if (!client->connected()) { 312 | // delete this client reference if s/he has disconnected 313 | // and don't put it back on the queue anymore. Bye! 314 | delete client; 315 | } 316 | else { 317 | 318 | // Ok. This is an actively connected client. 319 | // Let's grab a semaphore to prevent frame changes while we 320 | // are serving this frame 321 | xSemaphoreTake( frameSync, portMAX_DELAY ); 322 | 323 | client->write(CTNTTYPE, cntLen); 324 | sprintf(buf, "%d\r\n\r\n", camSize); 325 | client->write(buf, strlen(buf)); 326 | client->write((char*) camBuf, (size_t)camSize); 327 | client->write(BOUNDARY, bdrLen); 328 | 329 | // Since this client is still connected, push it to the end 330 | // of the queue for further processing 331 | xQueueSend(streamingClients, (void *) &client, 0); 332 | 333 | // The frame has been served. Release the semaphore and let other tasks run. 334 | // If there is a frame switch ready, it will happen now in between frames 335 | xSemaphoreGive( frameSync ); 336 | taskYIELD(); 337 | } 338 | } 339 | else { 340 | // Since there are no connected clients, there is no reason to waste battery running 341 | vTaskSuspend(NULL); 342 | } 343 | // Let other tasks run after serving every client 344 | taskYIELD(); 345 | vTaskDelayUntil(&xLastWakeTime, xFrequency); 346 | } 347 | } 348 | 349 | 350 | 351 | const char JHEADER[] = "HTTP/1.1 200 OK\r\n" \ 352 | "Content-disposition: inline; filename=capture.jpg\r\n" \ 353 | "Content-type: image/jpeg\r\n\r\n"; 354 | const int jhdLen = strlen(JHEADER); 355 | 356 | // ==== Serve up one JPEG frame ============================================= 357 | void handleJPG(void) 358 | { 359 | WiFiClient client = server.client(); 360 | 361 | if (!client.connected()) return; 362 | cam.run(); 363 | client.write(JHEADER, jhdLen); 364 | client.write((char*)cam.getfb(), cam.getSize()); 365 | } 366 | 367 | 368 | // ==== Handle invalid URL requests ============================================ 369 | void handleNotFound() 370 | { 371 | String message = "Server is running!\n\n"; 372 | message += "URI: "; 373 | message += server.uri(); 374 | message += "\nMethod: "; 375 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 376 | message += "\nArguments: "; 377 | message += server.args(); 378 | message += "\n"; 379 | server.send(200, "text / plain", message); 380 | } 381 | 382 | 383 | 384 | // ==== SETUP method ================================================================== 385 | void setup() 386 | { 387 | 388 | // Setup Serial connection: 389 | Serial.begin(115200); 390 | delay(1000); // wait for a second to let Serial connect 391 | 392 | 393 | // Configure the camera 394 | camera_config_t config; 395 | config.ledc_channel = LEDC_CHANNEL_0; 396 | config.ledc_timer = LEDC_TIMER_0; 397 | config.pin_d0 = Y2_GPIO_NUM; 398 | config.pin_d1 = Y3_GPIO_NUM; 399 | config.pin_d2 = Y4_GPIO_NUM; 400 | config.pin_d3 = Y5_GPIO_NUM; 401 | config.pin_d4 = Y6_GPIO_NUM; 402 | config.pin_d5 = Y7_GPIO_NUM; 403 | config.pin_d6 = Y8_GPIO_NUM; 404 | config.pin_d7 = Y9_GPIO_NUM; 405 | config.pin_xclk = XCLK_GPIO_NUM; 406 | config.pin_pclk = PCLK_GPIO_NUM; 407 | config.pin_vsync = VSYNC_GPIO_NUM; 408 | config.pin_href = HREF_GPIO_NUM; 409 | config.pin_sscb_sda = SIOD_GPIO_NUM; 410 | config.pin_sscb_scl = SIOC_GPIO_NUM; 411 | config.pin_pwdn = PWDN_GPIO_NUM; 412 | config.pin_reset = RESET_GPIO_NUM; 413 | config.xclk_freq_hz = 20000000; 414 | config.pixel_format = PIXFORMAT_JPEG; 415 | 416 | // Frame parameters: pick one 417 | // config.frame_size = FRAMESIZE_UXGA; 418 | // config.frame_size = FRAMESIZE_SVGA; 419 | // config.frame_size = FRAMESIZE_QVGA; 420 | config.frame_size = FRAMESIZE_VGA; 421 | config.jpeg_quality = 12; 422 | config.fb_count = 2; 423 | 424 | #if defined(CAMERA_MODEL_ESP_EYE) 425 | pinMode(13, INPUT_PULLUP); 426 | pinMode(14, INPUT_PULLUP); 427 | #endif 428 | 429 | if (cam.init(config) != ESP_OK) { 430 | Serial.println("Error initializing the camera"); 431 | delay(10000); 432 | ESP.restart(); 433 | } 434 | 435 | 436 | // Configure and connect to WiFi 437 | IPAddress ip; 438 | 439 | WiFi.mode(WIFI_STA); 440 | WiFi.begin(SSID1, PWD1); 441 | Serial.print("Connecting to WiFi"); 442 | while (WiFi.status() != WL_CONNECTED) 443 | { 444 | delay(500); 445 | Serial.print(F(".")); 446 | } 447 | ip = WiFi.localIP(); 448 | Serial.println(F("WiFi connected")); 449 | Serial.println(""); 450 | Serial.print("Stream Link: http://"); 451 | Serial.print(ip); 452 | Serial.println("/mjpeg/1"); 453 | 454 | 455 | // Start mainstreaming RTOS task 456 | xTaskCreatePinnedToCore( 457 | mjpegCB, 458 | "mjpeg", 459 | 4 * 1024, 460 | NULL, 461 | 2, 462 | &tMjpeg, 463 | APP_CPU); 464 | } 465 | 466 | 467 | void loop() { 468 | vTaskDelay(1000); 469 | } 470 | -------------------------------------------------------------------------------- /esp32_camera_mjpeg_multiclient/home_wifi_multi.h: -------------------------------------------------------------------------------- 1 | #define SSID1 "xxx" 2 | #define PWD1 "xxx" 3 | -------------------------------------------------------------------------------- /esp32_camera_mjpeg_multiclient/src/OV2640.cpp: -------------------------------------------------------------------------------- 1 | #include "OV2640.h" 2 | 3 | #define TAG "OV2640" 4 | 5 | // definitions appropriate for the ESP32-CAM devboard (and most clones) 6 | camera_config_t esp32cam_config{ 7 | 8 | .pin_pwdn = -1, // FIXME: on the TTGO T-Journal I think this is GPIO 0 9 | .pin_reset = 15, 10 | 11 | .pin_xclk = 27, 12 | 13 | .pin_sscb_sda = 25, 14 | .pin_sscb_scl = 23, 15 | 16 | .pin_d7 = 19, 17 | .pin_d6 = 36, 18 | .pin_d5 = 18, 19 | .pin_d4 = 39, 20 | .pin_d3 = 5, 21 | .pin_d2 = 34, 22 | .pin_d1 = 35, 23 | .pin_d0 = 17, 24 | .pin_vsync = 22, 25 | .pin_href = 26, 26 | .pin_pclk = 21, 27 | .xclk_freq_hz = 20000000, 28 | .ledc_timer = LEDC_TIMER_0, 29 | .ledc_channel = LEDC_CHANNEL_0, 30 | .pixel_format = PIXFORMAT_JPEG, 31 | // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space 32 | // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer 33 | // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb 34 | .frame_size = FRAMESIZE_SVGA, 35 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 36 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 37 | }; 38 | 39 | camera_config_t esp32cam_aithinker_config{ 40 | 41 | .pin_pwdn = 32, 42 | .pin_reset = -1, 43 | 44 | .pin_xclk = 0, 45 | 46 | .pin_sscb_sda = 26, 47 | .pin_sscb_scl = 27, 48 | 49 | // Note: LED GPIO is apparently 4 not sure where that goes 50 | // per https://github.com/donny681/ESP32_CAMERA_QR/blob/e4ef44549876457cd841f33a0892c82a71f35358/main/led.c 51 | .pin_d7 = 35, 52 | .pin_d6 = 34, 53 | .pin_d5 = 39, 54 | .pin_d4 = 36, 55 | .pin_d3 = 21, 56 | .pin_d2 = 19, 57 | .pin_d1 = 18, 58 | .pin_d0 = 5, 59 | .pin_vsync = 25, 60 | .pin_href = 23, 61 | .pin_pclk = 22, 62 | .xclk_freq_hz = 20000000, 63 | .ledc_timer = LEDC_TIMER_1, 64 | .ledc_channel = LEDC_CHANNEL_1, 65 | .pixel_format = PIXFORMAT_JPEG, 66 | // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space 67 | // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer 68 | // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb 69 | .frame_size = FRAMESIZE_SVGA, 70 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 71 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 72 | }; 73 | 74 | camera_config_t esp32cam_ttgo_t_config{ 75 | 76 | .pin_pwdn = 26, 77 | .pin_reset = -1, 78 | 79 | .pin_xclk = 32, 80 | 81 | .pin_sscb_sda = 13, 82 | .pin_sscb_scl = 12, 83 | 84 | .pin_d7 = 39, 85 | .pin_d6 = 36, 86 | .pin_d5 = 23, 87 | .pin_d4 = 18, 88 | .pin_d3 = 15, 89 | .pin_d2 = 4, 90 | .pin_d1 = 14, 91 | .pin_d0 = 5, 92 | .pin_vsync = 27, 93 | .pin_href = 25, 94 | .pin_pclk = 19, 95 | .xclk_freq_hz = 20000000, 96 | .ledc_timer = LEDC_TIMER_0, 97 | .ledc_channel = LEDC_CHANNEL_0, 98 | .pixel_format = PIXFORMAT_JPEG, 99 | .frame_size = FRAMESIZE_SVGA, 100 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 101 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 102 | }; 103 | 104 | void OV2640::run(void) 105 | { 106 | if (fb) 107 | //return the frame buffer back to the driver for reuse 108 | esp_camera_fb_return(fb); 109 | 110 | fb = esp_camera_fb_get(); 111 | } 112 | 113 | void OV2640::runIfNeeded(void) 114 | { 115 | if (!fb) 116 | run(); 117 | } 118 | 119 | int OV2640::getWidth(void) 120 | { 121 | runIfNeeded(); 122 | return fb->width; 123 | } 124 | 125 | int OV2640::getHeight(void) 126 | { 127 | runIfNeeded(); 128 | return fb->height; 129 | } 130 | 131 | size_t OV2640::getSize(void) 132 | { 133 | runIfNeeded(); 134 | if (!fb) 135 | return 0; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? 136 | return fb->len; 137 | } 138 | 139 | uint8_t *OV2640::getfb(void) 140 | { 141 | runIfNeeded(); 142 | if (!fb) 143 | return NULL; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? 144 | 145 | return fb->buf; 146 | } 147 | 148 | framesize_t OV2640::getFrameSize(void) 149 | { 150 | return _cam_config.frame_size; 151 | } 152 | 153 | void OV2640::setFrameSize(framesize_t size) 154 | { 155 | _cam_config.frame_size = size; 156 | } 157 | 158 | pixformat_t OV2640::getPixelFormat(void) 159 | { 160 | return _cam_config.pixel_format; 161 | } 162 | 163 | void OV2640::setPixelFormat(pixformat_t format) 164 | { 165 | switch (format) 166 | { 167 | case PIXFORMAT_RGB565: 168 | case PIXFORMAT_YUV422: 169 | case PIXFORMAT_GRAYSCALE: 170 | case PIXFORMAT_JPEG: 171 | _cam_config.pixel_format = format; 172 | break; 173 | default: 174 | _cam_config.pixel_format = PIXFORMAT_GRAYSCALE; 175 | break; 176 | } 177 | } 178 | 179 | esp_err_t OV2640::init(camera_config_t config) 180 | { 181 | memset(&_cam_config, 0, sizeof(_cam_config)); 182 | memcpy(&_cam_config, &config, sizeof(config)); 183 | 184 | esp_err_t err = esp_camera_init(&_cam_config); 185 | if (err != ESP_OK) 186 | { 187 | printf("Camera probe failed with error 0x%x", err); 188 | return err; 189 | } 190 | // ESP_ERROR_CHECK(gpio_install_isr_service(0)); 191 | 192 | return ESP_OK; 193 | } 194 | -------------------------------------------------------------------------------- /esp32_camera_mjpeg_multiclient/src/OV2640.h: -------------------------------------------------------------------------------- 1 | #ifndef OV2640_H_ 2 | #define OV2640_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "esp_log.h" 8 | #include "esp_attr.h" 9 | #include "esp_camera.h" 10 | 11 | extern camera_config_t esp32cam_config, esp32cam_aithinker_config, esp32cam_ttgo_t_config; 12 | 13 | class OV2640 14 | { 15 | public: 16 | OV2640(){ 17 | fb = NULL; 18 | }; 19 | ~OV2640(){ 20 | }; 21 | esp_err_t init(camera_config_t config); 22 | void run(void); 23 | size_t getSize(void); 24 | uint8_t *getfb(void); 25 | int getWidth(void); 26 | int getHeight(void); 27 | framesize_t getFrameSize(void); 28 | pixformat_t getPixelFormat(void); 29 | 30 | void setFrameSize(framesize_t size); 31 | void setPixelFormat(pixformat_t format); 32 | 33 | private: 34 | void runIfNeeded(); // grab a frame if we don't already have one 35 | 36 | // camera_framesize_t _frame_size; 37 | // camera_pixelformat_t _pixel_format; 38 | camera_config_t _cam_config; 39 | 40 | camera_fb_t *fb; 41 | }; 42 | 43 | #endif //OV2640_H_ 44 | -------------------------------------------------------------------------------- /vue/esp.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export default { 4 | components: { 5 | }, 6 | data() { 7 | return { 8 | url: "", 9 | websocket: null 10 | } 11 | }, 12 | mounted() { 13 | 14 | this.initWebSocket(); 15 | }, 16 | created() { 17 | }, 18 | methods: { 19 | initWebSocket() { 20 | var that = this 21 | var wsUri = "ws://192.168.31.181:5765/"; 22 | this.websocket = new WebSocket(wsUri); 23 | this.websocket.onopen = function (evt) { 24 | that.onOpen(evt) 25 | }; 26 | this.websocket.onclose = function (evt) { 27 | that.onClose(evt) 28 | }; 29 | this.websocket.onmessage = function (evt) { 30 | that.onMessage(evt) 31 | }; 32 | this.websocket.onerror = function (evt) { 33 | that.onError(evt) 34 | }; 35 | }, 36 | onOpen(evt) { 37 | console.log("CONNECTED") 38 | }, 39 | onClose(evt) { 40 | console.log("DISCONNECTED") 41 | }, 42 | onMessage(evt) { 43 | var reader = new FileReader(); 44 | 45 | reader.onload = function (eve) { 46 | if (eve.target.readyState == FileReader.DONE) { 47 | var img = document.getElementById("show"); 48 | img.src = this.result; 49 | } 50 | }; 51 | reader.readAsDataURL(event.data); 52 | }, 53 | onError(evt) { 54 | console.log(evt.data) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vue/esp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /websocketserver.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import websockets 3 | import cv2 as cv 4 | import binascii 5 | import numpy as np 6 | USERS = [] 7 | async def recv_msg(websocket): 8 | USERS.append(websocket) 9 | print('user',USERS,len(USERS)) 10 | while True: 11 | try: 12 | recv_text = await websocket.recv() 13 | # print(recv_text.hex()) 14 | data = binascii.a2b_hex(recv_text.hex()) 15 | data1 = np.frombuffer(data, dtype=np.uint8) 16 | img = cv.imdecode(data1, 1) 17 | cv.imshow('result', img) 18 | cv.waitKey(1) 19 | if len(USERS) >= 2: 20 | await USERS[len(USERS)-1].send(data) 21 | # print(USERS[1]) 22 | finally: 23 | pass 24 | async def main_logic(websocket, path): 25 | await recv_msg(websocket) 26 | 27 | # 把ip换成自己本地的ip 28 | start_server = websockets.serve(main_logic, "192.168.31.181",5765) 29 | asyncio.get_event_loop().run_until_complete(start_server) 30 | asyncio.get_event_loop().run_forever() 31 | --------------------------------------------------------------------------------