{"id":709,"date":"2022-12-15T16:17:06","date_gmt":"2022-12-15T08:17:06","guid":{"rendered":"http:\/\/wordpress.loc\/?p=709"},"modified":"2022-12-15T16:18:09","modified_gmt":"2022-12-15T08:18:09","slug":"esp32_ch2-10","status":"publish","type":"post","link":"https:\/\/www.nmking.io\/index.php\/2022\/12\/15\/709\/","title":{"rendered":"ESP32CAM\u591a\u4eba\u9023\u7dda\u63a1\u7528\u591a\u57f7\u884c\u7dd2"},"content":{"rendered":"\n<figure class=\"wp-block-pullquote\"><blockquote><p>\u4ee5\u5f80\u5728ESP32CAM\u6559\u5b78\u6642\uff0c\u5076\u723e\u6703\u9047\u5230\u5b78\u751f\u554f\u8aaa\uff0c\u70ba\u4ec0\u9ebc\u53ea\u6709\u4e00\u500b\u4eba\u80fd\u770b\uff0c\u6211\u90fd\u6703\u8aaa\u9019\u662f\u55ae\u6676\u7247\u7684\u9650\u5236\uff0c\u5fc3\u88e1\u60f3\u8aaa\uff0c\u4e00\u9846\u5169\u4e09\u767e\u7684\u55ae\u6676\u7247\u80fd\u505aStream Webserver\u5df2\u7d93\u5f88\u53b2\u5bb3\u4e86\uff0c\u9084\u8981\u591a\u4eba\u9023\u7dda\uff0c\u6703\u4e0d\u6703\u592a\u5f37\u4eba\u6240\u96e3\u3002<\/p><\/blockquote><\/figure>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>\u6211\u7684ESP32\u5be6\u505a\u66f8\u7c4d\uff1a<a rel=\"noreferrer noopener\" href=\"https:\/\/www.nmking.io\/index.php\/2022\/11\/10\/esp32_ch1-2\/\" target=\"_blank\">\u6211\u51fa\u66f8\u4e86 ESP32 \u7269\u806f\u7db2\u5c08\u984c<\/a><br>\u535a\u5ba2\u4f86\u7db2\u5740\uff1a<a rel=\"noreferrer noopener\" href=\"https:\/\/www.books.com.tw\/products\/0010901195\" target=\"_blank\">https:\/\/www.books.com.tw\/products\/0010901195<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p>\u4ee5\u5f80\u5728ESP32CAM\u6559\u5b78\u6642\uff0c\u5076\u723e\u6703\u9047\u5230\u5b78\u751f\u554f\u8aaa\uff0c\u70ba\u4ec0\u9ebc\u53ea\u6709\u4e00\u500b\u4eba\u80fd\u770b\uff0c\u6211\u90fd\u6703\u8aaa\u9019\u662f\u55ae\u6676\u7247\u7684\u9650\u5236\uff0c\u5fc3\u88e1\u60f3\u8aaa\uff0c\u4e00\u9846\u5169\u4e09\u767e\u7684\u55ae\u6676\u7247\u80fd\u505aStream Webserver\u5df2\u7d93\u5f88\u53b2\u5bb3\u4e86\uff0c\u9084\u8981\u591a\u4eba\u9023\u7dda\uff0c\u6703\u4e0d\u6703\u592a\u5f37\u4eba\u6240\u96e3\u3002<\/p>\n\n\n\n<p>\u4e0d\u904e\u6709\u4e00\u6b21\u4e00\u4f4d\u5f88\u597d\u5b78\u7684\u5b78\u751f\u554f\uff1a\u300c\u8001\u5e2b\uff0c\u60a8\u8aaaESP32\u6709\u96d9\u6838\u5fc3\uff0c\u9084\u80fd\u8dd1\u591a\u57f7\u884c\u7dd2\uff0c\u73fe\u5728\u4e0d\u80fd\u591a\u4eba\u9023\u7dda\u770b\u5f71\u50cf\u662f\u4e0d\u662f\u6709\u9ede\u8aaa\u4e0d\u904e\u53bb\u300d\u3002<\/p>\n\n\n\n<p>\u5fc3\u60f3\u4f3c\u4e4e\u4e5f\u662f\uff0c\u6211\u6bcf\u6b21\u4e0a\u8ab2\u90fd\u6703\u5f37\u8abf\u9019\u9846\u6676\u7247\u53ef\u4ee5\u8dd1\u591a\u57f7\u884c\u7dd2\uff0c\u7adf\u7136\u5982\u6b64\u61c9\u8a72\u4f86\u7814\u7a76\u4e00\u4e0b\u5982\u4f55\u5c07\u55ae\u4eba\u9023\u7dda\u7a0b\u5f0f\u6539\u5beb\u6210\u591a\u4eba\u9023\u7dda\u3002<\/p>\n\n\n\n<p>\u4e0d\u904e\u5728\u7814\u7a76\u4e4b\u524d\uff0c\u5148Google\u770b\u770b\uff0c\u54f2\u5b78\u5bb6\u90fd\u8aaa\uff1a\u300c\u4e0d\u7528\u91cd\u8907\u9020\u8f2a\u5b50\u300d\uff0c\u5c31\u767c\u73fe\u4e86\u9019\u7bc7\uff1a<a href=\"https:\/\/www.hackster.io\/anatoli-arkhipenko\/multi-client-mjpeg-streaming-from-esp32-47768f\">Multi-Client MJPEG Streaming From ESP32<\/a>\uff0c\u6c92\u60f3\u5230\u771f\u7684\u6709\u5927\u795e\u5df2\u7d93\u5beb\u597d\u4e86\uff0c\u99ac\u4e0a\u628a\u7db2\u5740\u50b3\u7d66\u90a3\u4f4d\u597d\u554f\u7684\u5b78\u751f\u53bb\u6e2c\u8a66\u770b\u770b\uff0c\u8ab0\u53eb\u4f60\u611b\u554f\uff0c\u6c92\u60f3\u5230\u4e00\u6e2c\u8a66\u5c31\u6210\u529f\u4e86\uff0c\u592a\u68d2\u4e86\u3002<\/p>\n\n\n\n<p>\u5f8c\u4f86\u53c8\u7d93\u904e\u6211\u5011\u591a\u6b21\u6539\u5beb\u53ca\u7cbe\u7c21\uff0c\u73fe\u5728\u628a<s>\u8f2a\u5b50<\/s>\u7a0b\u5f0f\u9644\u4e0a\u7d66\u5927\u5bb6\u3002<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/pic.pimg.tw\/youyouyou\/1595261107-2677668768-g_n.png?resize=845%2C382&#038;ssl=1\" alt=\"\" width=\"845\" height=\"382\"\/><figcaption class=\"wp-element-caption\">ESP32CAM\u591a\u4eba\u9023\u7dda\u6548\u679c<\/figcaption><\/figure>\n\n\n\n<p class=\"quote\">\u4e09\u500b\u5ba2\u6236\u7aef\u540c\u6642\u9023\u7dda\uff1aPCx2\uff0c\u624b\u6a5fx1\uff0c\u9023\u7dda\u72c0\u6cc1\u975e\u5e38\u597d\uff0c\u591a\u4eba\u9023\u7dda\u4e5f\u5e7e\u4e4e\u6c92\u6709\u5ef6\u9072\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;WiFi.h>\n#include &lt;WebServer.h>\n#include &lt;WiFiClient.h>\n#include &lt;Arduino.h>\n#include &lt;pgmspace.h>\n#include &lt;stdio.h>\n#include \"esp_log.h\"\n#include \"esp_attr.h\"\n#include \"esp_camera.h\"\n#include &lt;esp_bt.h>\n#include &lt;esp_wifi.h>\n#include &lt;esp_sleep.h>\n#include &lt;driver\/rtc_io.h>\n\n\/\/Setup Your wifi\nconst char* ssid = \"You\";\nconst char* password = \"0933932774\";\n\n\/\/Define AI_Thinker Camera_pins\n#define PWDN_GPIO_NUM     32\n#define RESET_GPIO_NUM    -1\n#define XCLK_GPIO_NUM      0\n#define SIOD_GPIO_NUM     26\n#define SIOC_GPIO_NUM     27\n#define Y9_GPIO_NUM       35\n#define Y8_GPIO_NUM       34\n#define Y7_GPIO_NUM       39\n#define Y6_GPIO_NUM       36\n#define Y5_GPIO_NUM       21\n#define Y4_GPIO_NUM       19\n#define Y3_GPIO_NUM       18\n#define Y2_GPIO_NUM        5\n#define VSYNC_GPIO_NUM    25\n#define HREF_GPIO_NUM     23\n#define PCLK_GPIO_NUM     22\n#define APP_CPU 1\n#define PRO_CPU 0\n\nclass OV2640\n{\n  public:\n    OV2640() {\n      fb = NULL;\n    };\n    ~OV2640() {\n    };\n    esp_err_t init(camera_config_t config);\n    void run(void);\n    size_t getSize(void);\n    uint8_t *getfb(void);\n    int getWidth(void);\n    int getHeight(void);\n    framesize_t getFrameSize(void);\n    pixformat_t getPixelFormat(void);\n\n    void setFrameSize(framesize_t size);\n    void setPixelFormat(pixformat_t format);\n\n  private:\n    void runIfNeeded(); \/\/ grab a frame if we don't already have one\n    \/\/ camera_framesize_t _frame_size;\n    \/\/ camera_pixelformat_t _pixel_format;\n    camera_config_t _cam_config;\n    camera_fb_t *fb;\n};\n\nvoid OV2640::run(void)\n{\n  if (fb)\n    \/\/return the frame buffer back to the driver for reuse\n    esp_camera_fb_return(fb);\n\n  fb = esp_camera_fb_get();\n}\n\nvoid OV2640::runIfNeeded(void)\n{\n  if (!fb)\n    run();\n}\n\nint OV2640::getWidth(void)\n{\n  runIfNeeded();\n  return fb->width;\n}\n\nint OV2640::getHeight(void)\n{\n  runIfNeeded();\n  return fb->height;\n}\n\nsize_t OV2640::getSize(void)\n{\n  runIfNeeded();\n  if (!fb)\n    return 0; \/\/ FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes?\n  return fb->len;\n}\n\nuint8_t *OV2640::getfb(void)\n{\n  runIfNeeded();\n  if (!fb)\n    return NULL; \/\/ FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes?\n\n  return fb->buf;\n}\n\nframesize_t OV2640::getFrameSize(void)\n{\n  return _cam_config.frame_size;\n}\n\nvoid OV2640::setFrameSize(framesize_t size)\n{\n  _cam_config.frame_size = size;\n}\n\npixformat_t OV2640::getPixelFormat(void)\n{\n  return _cam_config.pixel_format;\n}\n\nvoid OV2640::setPixelFormat(pixformat_t format)\n{\n  switch (format)\n  {\n    case PIXFORMAT_RGB565:\n    case PIXFORMAT_YUV422:\n    case PIXFORMAT_GRAYSCALE:\n    case PIXFORMAT_JPEG:\n      _cam_config.pixel_format = format;\n      break;\n    default:\n      _cam_config.pixel_format = PIXFORMAT_GRAYSCALE;\n      break;\n  }\n}\n\nesp_err_t OV2640::init(camera_config_t config)\n{\n  memset(&amp;_cam_config, 0, sizeof(_cam_config));\n  memcpy(&amp;_cam_config, &amp;config, sizeof(config));\n\n  esp_err_t err = esp_camera_init(&amp;_cam_config);\n  if (err != ESP_OK)\n  {\n    printf(\"Camera probe failed with error 0x%x\", err);\n    return err;\n  }\n  \/\/ ESP_ERROR_CHECK(gpio_install_isr_service(0));\n\n  return ESP_OK;\n} OV2640 cam;\n\nWebServer server(80);\n\n\/\/ ===== rtos task handles =========================\n\/\/ Streaming is implemented with 3 tasks:\nTaskHandle_t tMjpeg;   \/\/ handles client connections to the webserver\nTaskHandle_t tCam;     \/\/ handles getting picture frames from the camera and storing them locally\nTaskHandle_t tStream;  \/\/ actually streaming frames to all connected clients\n\n\/\/ frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame\nSemaphoreHandle_t frameSync = NULL;\n\n\/\/ Queue stores currently connected clients to whom we are streaming\nQueueHandle_t streamingClients;\n\n\/\/ We will try to achieve 25 FPS frame rate\nconst int FPS = 25;\n\n\/\/ We will handle web client requests every 50 ms (20 Hz)\nconst int WSINTERVAL = 50;\n\n\n\/\/ ======== Server Connection Handler Task ==========================\nvoid mjpegCB(void* pvParameters) {\n  TickType_t xLastWakeTime;\n  const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL);\n\n  \/\/ Creating frame synchronization semaphore and initializing it\n  frameSync = xSemaphoreCreateBinary();\n  xSemaphoreGive( frameSync );\n\n  \/\/ Creating a queue to track all connected clients\n  streamingClients = xQueueCreate( 10, sizeof(WiFiClient*) );\n\n  \/\/=== setup section  ==================\n\n  \/\/  Creating RTOS task for grabbing frames from the camera\n  xTaskCreatePinnedToCore(\n    camCB,        \/\/ callback\n    \"cam\",        \/\/ name\n    4096,         \/\/ stacj size\n    NULL,         \/\/ parameters\n    2,            \/\/ priority\n    &amp;tCam,        \/\/ RTOS task handle\n    APP_CPU);     \/\/ core\n\n  \/\/  Creating task to push the stream to all connected clients\n  xTaskCreatePinnedToCore(\n    streamCB,\n    \"strmCB\",\n    4096,\n    NULL, \/\/(void*) handler,\n    2,\n    &amp;tStream,\n    APP_CPU);\n\n  \/\/  Registering webserver handling routines\n  server.on(\"\/stream\", HTTP_GET, handleJPGSstream);\n  server.on(\"\/jpg\", HTTP_GET, handleJPG);\n  server.onNotFound(handleNotFound);\n\n  \/\/  Starting webserver\n  server.begin();\n\n  \/\/=== loop() section  ===================\n  xLastWakeTime = xTaskGetTickCount();\n  for (;;) {\n    server.handleClient();\n\n    \/\/  After every server client handling request, we let other tasks run and then pause\n    taskYIELD();\n    vTaskDelayUntil(&amp;xLastWakeTime, xFrequency);\n  }\n}\n\n\n\/\/ Commonly used variables:\nvolatile size_t camSize;    \/\/ size of the current frame, byte\nvolatile char* camBuf;      \/\/ pointer to the current frame\n\n\n\/\/ ==== RTOS task to grab frames from the camera =========================\nvoid camCB(void* pvParameters) {\n\n  TickType_t xLastWakeTime;\n\n  \/\/  A running interval associated with currently desired frame rate\n  const TickType_t xFrequency = pdMS_TO_TICKS(1000 \/ FPS);\n\n  \/\/ Mutex for the critical section of swithing the active frames around\n  portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED;\n\n  \/\/  Pointers to the 2 frames, their respective sizes and index of the current frame\n  char* fbs&#91;2] = { NULL, NULL };\n  size_t fSize&#91;2] = { 0, 0 };\n  int ifb = 0;\n\n  \/\/=== loop() section  ===================\n  xLastWakeTime = xTaskGetTickCount();\n\n  for (;;) {\n\n    \/\/  Grab a frame from the camera and query its size\n    cam.run();\n    size_t s = cam.getSize();\n\n    \/\/  If frame size is more that we have previously allocated - request  125% of the current frame space\n    if (s > fSize&#91;ifb]) {\n      fSize&#91;ifb] = s * 4 \/ 3;\n      fbs&#91;ifb] = allocateMemory(fbs&#91;ifb], fSize&#91;ifb]);\n    }\n\n    \/\/  Copy current frame into local buffer\n    char* b = (char*) cam.getfb();\n    memcpy(fbs&#91;ifb], b, s);\n\n    \/\/  Let other tasks run and wait until the end of the current frame rate interval (if any time left)\n    taskYIELD();\n    vTaskDelayUntil(&amp;xLastWakeTime, xFrequency);\n\n    \/\/  Only switch frames around if no frame is currently being streamed to a client\n    \/\/  Wait on a semaphore until client operation completes\n    xSemaphoreTake( frameSync, portMAX_DELAY );\n\n    \/\/  Do not allow interrupts while switching the current frame\n    taskENTER_CRITICAL(&amp;xSemaphore);\n    camBuf = fbs&#91;ifb];\n    camSize = s;\n    ifb = (++ifb) &amp; 1;  \/\/ this should produce 1, 0, 1, 0, 1 ... sequence\n    taskEXIT_CRITICAL(&amp;xSemaphore);\n\n    \/\/  Let anyone waiting for a frame know that the frame is ready\n    xSemaphoreGive( frameSync );\n\n    \/\/  Technically only needed once: let the streaming task know that we have at least one frame\n    \/\/  and it could start sending frames to the clients, if any\n    xTaskNotifyGive( tStream );\n\n    \/\/  Immediately let other (streaming) tasks run\n    taskYIELD();\n\n    \/\/  If streaming task has suspended itself (no active clients to stream to)\n    \/\/  there is no need to grab frames from the camera. We can save some juice\n    \/\/  by suspedning the tasks\n    if ( eTaskGetState( tStream ) == eSuspended ) {\n      vTaskSuspend(NULL);  \/\/ passing NULL means \"suspend yourself\"\n    }\n  }\n}\n\n\n\/\/ ==== Memory allocator that takes advantage of PSRAM if present =======================\nchar* allocateMemory(char* aPtr, size_t aSize) {\n\n  \/\/  Since current buffer is too smal, free it\n  if (aPtr != NULL) free(aPtr);\n\n\n  size_t freeHeap = ESP.getFreeHeap();\n  char* ptr = NULL;\n\n  \/\/ If memory requested is more than 2\/3 of the currently free heap, try PSRAM immediately\n  if ( aSize > freeHeap * 2 \/ 3 ) {\n    if ( psramFound() &amp;&amp; ESP.getFreePsram() > aSize ) {\n      ptr = (char*) ps_malloc(aSize);\n    }\n  }\n  else {\n    \/\/  Enough free heap - let's try allocating fast RAM as a buffer\n    ptr = (char*) malloc(aSize);\n\n    \/\/  If allocation on the heap failed, let's give PSRAM one more chance:\n    if ( ptr == NULL &amp;&amp; psramFound() &amp;&amp; ESP.getFreePsram() > aSize) {\n      ptr = (char*) ps_malloc(aSize);\n    }\n  }\n\n  \/\/ Finally, if the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition.\n  if (ptr == NULL) {\n    ESP.restart();\n  }\n  return ptr;\n}\n\n\n\/\/ ==== STREAMING ======================================================\nconst char HEADER&#91;] = \"HTTP\/1.1 200 OK\\r\\n\" \\\n                      \"Access-Control-Allow-Origin: *\\r\\n\" \\\n                      \"Content-Type: multipart\/x-mixed-replace; boundary=123456789000000000000987654321\\r\\n\";\nconst char BOUNDARY&#91;] = \"\\r\\n--123456789000000000000987654321\\r\\n\";\nconst char CTNTTYPE&#91;] = \"Content-Type: image\/jpeg\\r\\nContent-Length: \";\nconst int hdrLen = strlen(HEADER);\nconst int bdrLen = strlen(BOUNDARY);\nconst int cntLen = strlen(CTNTTYPE);\n\n\n\/\/\u63a7\u5236\u9023\u7dda\u670d\u52d9\nvoid handleJPGSstream(void)\n{\n  \/\/  Can only acommodate 10 clients. The limit is a default for WiFi connections\n  if ( !uxQueueSpacesAvailable(streamingClients) ) return;\n\n\n  \/\/  Create a new WiFi Client object to keep track of this one\n  WiFiClient* client = new WiFiClient();\n  *client = server.client();\n\n  \/\/  Immediately send this client a header\n  client->write(HEADER, hdrLen);\n  client->write(BOUNDARY, bdrLen);\n\n  \/\/ Push the client to the streaming queue\n  xQueueSend(streamingClients, (void *) &amp;client, 0);\n\n  \/\/ Wake up streaming tasks, if they were previously suspended:\n  if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam );\n  if ( eTaskGetState( tStream ) == eSuspended ) vTaskResume( tStream );\n}\n\n\n\/\/\u8655\u7406\u9023\u7dda\u670d\u52d9\nvoid streamCB(void * pvParameters) {\n  char buf&#91;16];\n  TickType_t xLastWakeTime;\n  TickType_t xFrequency;\n\n  \/\/  Wait until the first frame is captured and there is something to send\n  \/\/  to clients\n  ulTaskNotifyTake( pdTRUE,          \/* Clear the notification value before exiting. *\/\n                    portMAX_DELAY ); \/* Block indefinitely. *\/\n\n  xLastWakeTime = xTaskGetTickCount();\n  for (;;) {\n    \/\/ Default assumption we are running according to the FPS\n    xFrequency = pdMS_TO_TICKS(1000 \/ FPS);\n\n    \/\/  Only bother to send anything if there is someone watching\n    UBaseType_t activeClients = uxQueueMessagesWaiting(streamingClients);\n    if ( activeClients ) {\n      \/\/ Adjust the period to the number of connected clients\n      xFrequency \/= activeClients;\n\n      \/\/  Since we are sending the same frame to everyone,\n      \/\/  pop a client from the the front of the queue\n      WiFiClient *client;\n      xQueueReceive (streamingClients, (void*) &amp;client, 0);\n\n      \/\/  Check if this client is still connected.\n\n      if (!client->connected()) {\n        \/\/  delete this client reference if s\/he has disconnected\n        \/\/  and don't put it back on the queue anymore. Bye!\n        delete client;\n      }\n      else {\n\n        \/\/  Ok. This is an actively connected client.\n        \/\/  Let's grab a semaphore to prevent frame changes while we\n        \/\/  are serving this frame\n        xSemaphoreTake( frameSync, portMAX_DELAY );\n\n        client->write(CTNTTYPE, cntLen);\n        sprintf(buf, \"%d\\r\\n\\r\\n\", camSize);\n        client->write(buf, strlen(buf));\n        client->write((char*) camBuf, (size_t)camSize);\n        client->write(BOUNDARY, bdrLen);\n\n        \/\/ Since this client is still connected, push it to the end\n        \/\/ of the queue for further processing\n        xQueueSend(streamingClients, (void *) &amp;client, 0);\n\n        \/\/  The frame has been served. Release the semaphore and let other tasks run.\n        \/\/  If there is a frame switch ready, it will happen now in between frames\n        xSemaphoreGive( frameSync );\n        taskYIELD();\n      }\n    }\n    else {\n      \/\/  Since there are no connected clients, there is no reason to waste battery running\n      vTaskSuspend(NULL);\n    }\n    \/\/  Let other tasks run after serving every client\n    taskYIELD();\n    vTaskDelayUntil(&amp;xLastWakeTime, xFrequency);\n  }\n}\n\nconst char JHEADER&#91;] = \"HTTP\/1.1 200 OK\\r\\n\" \\\n                       \"Content-disposition: inline; filename=capture.jpg\\r\\n\" \\\n                       \"Content-type: image\/jpeg\\r\\n\\r\\n\";\nconst int jhdLen = strlen(JHEADER);\n\n\/\/\u8a2d\u5b9ajpg\u55ae\u5f35\u5f71\u50cf\u670d\u52d9\nvoid handleJPG(void)\n{\n  WiFiClient client = server.client();\n  if (!client.connected()) return;\n  client.write(JHEADER, jhdLen);\n  client.write((char*)cam.getfb(), cam.getSize());\n}\n\n\n\/\/\u4f7f\u7528\u8005\u8f38\u5165\u932f\u8aa4\u7db2\u5740\u6642\nvoid handleNotFound()\n{\n  String message = \"Server is running!\\n\\n\";\n  message += \"URI: \";\n  message += server.uri();\n  message += \"\\nMethod: \";\n  message += (server.method() == HTTP_GET) ? \"GET\" : \"POST\";\n  message += \"\\nArguments: \";\n  message += server.args();\n  message += \"\\n\";\n  server.send(200, \"text \/ plain\", message);\n}\n\nvoid setup()\n{\n\n  \/\/ Setup Serial connection:\n  Serial.begin(115200);\n  delay(1000); \/\/ wait for a second to let Serial connect\n  \/\/ Configure the camera\n  camera_config_t config;\n  config.ledc_channel = LEDC_CHANNEL_0;\n  config.ledc_timer = LEDC_TIMER_0;\n  config.pin_d0 = Y2_GPIO_NUM;\n  config.pin_d1 = Y3_GPIO_NUM;\n  config.pin_d2 = Y4_GPIO_NUM;\n  config.pin_d3 = Y5_GPIO_NUM;\n  config.pin_d4 = Y6_GPIO_NUM;\n  config.pin_d5 = Y7_GPIO_NUM;\n  config.pin_d6 = Y8_GPIO_NUM;\n  config.pin_d7 = Y9_GPIO_NUM;\n  config.pin_xclk = XCLK_GPIO_NUM;\n  config.pin_pclk = PCLK_GPIO_NUM;\n  config.pin_vsync = VSYNC_GPIO_NUM;\n  config.pin_href = HREF_GPIO_NUM;\n  config.pin_sscb_sda = SIOD_GPIO_NUM;\n  config.pin_sscb_scl = SIOC_GPIO_NUM;\n  config.pin_pwdn = PWDN_GPIO_NUM;\n  config.pin_reset = RESET_GPIO_NUM;\n  config.xclk_freq_hz = 20000000;\n  config.pixel_format = PIXFORMAT_JPEG;\n  config.frame_size = FRAMESIZE_VGA; \/\/FRAMESIZE_UXGA, SVGA, QVGA\n  config.jpeg_quality = 12;\/\/0-60\n  config.fb_count = 2;\n\n  if (cam.init(config) != ESP_OK) {\n    Serial.println(\"Error initializing the camera\");\n    delay(10000);\n    ESP.restart();\n  }\n\n\n  \/\/  Configure and connect to WiFi\n  IPAddress ip;\n\n  WiFi.mode(WIFI_STA);\n  WiFi.begin(ssid, password);\n  Serial.print(\"Connecting to WiFi\");\n  while (WiFi.status() != WL_CONNECTED)\n  {\n    delay(500);\n    Serial.print(F(\".\"));\n  }\n  ip = WiFi.localIP();\n  Serial.println(F(\"WiFi connected\"));\n  Serial.println(\"\");\n  Serial.print(\"Stream Link: http:\/\/\");\n  Serial.print(ip);\n  Serial.println(\"\/stream\");\n\n  Serial.print(\"Still Jpg Link: http:\/\/\");\n  Serial.print(ip);\n  Serial.println(\"\/jpg\");\n  \n  \/\/ Start mainstreaming RTOS task\n  xTaskCreatePinnedToCore(\n    mjpegCB,\n    \"mjpeg\",\n    4096,\n    NULL,\n    2,\n    &amp;tMjpeg,\n    APP_CPU);\n}\n\n\nvoid loop() {\n  \/\/put your code here\n}\n \n\n \n\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u4ee5\u5f80\u5728ESP32CAM\u6559\u5b78\u6642\uff0c\u5076\u723e\u6703\u9047\u5230\u5b78\u751f\u554f\u8aaa\uff0c\u70ba\u4ec0\u9ebc\u53ea\u6709\u4e00\u500b\u4eba\u80fd\u770b\uff0c\u6211\u90fd\u6703\u8aaa\u9019\u662f\u55ae\u6676\u7247\u7684\u9650\u5236\uff0c\u5fc3\u88e1\u60f3\u8aaa\uff0c\u4e00 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1,26],"tags":[],"class_list":["post-709","post","type-post","status-publish","format-standard","hentry","category-esp32","category-esp32cam"],"blocksy_meta":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/posts\/709","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/comments?post=709"}],"version-history":[{"count":1,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/posts\/709\/revisions"}],"predecessor-version":[{"id":710,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/posts\/709\/revisions\/710"}],"wp:attachment":[{"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/media?parent=709"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/categories?post=709"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.nmking.io\/index.php\/wp-json\/wp\/v2\/tags?post=709"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}