Mjpeg multipart streaming. Wie geht das?

OP #7624289
Lesenswert?

Ich habe fortlaufende jpeg bilder die ich an einen browser streamen will. Kann mir jemand erklären welche header und was noch da gesendet werden muss?

Ich kenne den esp32-webcam code. Dieser läuft aber mit httpd und nicht mit dem esp32 <WebServer.h>. Und ich kapier das multipart zeugs nicht.

Einzelbilder läuft

1
void capture_handler()
2
{
3
    fb = esp_camera_fb_get();
4
    if (fb) 
5
    {
6
      if(fb->format == PIXFORMAT_JPEG) 
7
      {
8
        s_server.send(200, "image/jpg",String(fb->buf, fb->len));  
9
        esp_camera_fb_return(fb);
10
      }
11
    }
12
    else Serial.println("Camera capture failed");
13
}

Aber streamen bring ich nicht hin. Hat jemand einen link?

OP #7624331
Lesenswert?

Ich habs mir mal rausgesucht. Das müsste so sen:

1
httpd_resp_set_type(req, "multipart/x-mixed-replace;boundary=123456789000000000000987654321");
2

3
LOOP:
4
fb = esp_camera_fb_get();
5

6
httpd_resp_send_chunk(req, "Content-Type: image/jpeg\r\nContent-Length: xxxx\r\n\r\n"); //xxxx=_jpg_buf_len
7

8
httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
9

10
strlen(_STREAM_BOUNDARY));
11
httpd_resp_send_chunk(req, "\r\n--123456789000000000000987654321\r\n");

Was ich nicht verstehe ist das "httpd_resp_set_type". Wie heisst die entsprechende funktion im esp32 "webserver.h" ?

#7624633
Lesenswert?

Erstmal schauen, ob dein Browser das überhaupt noch unterstützt.

Chromium, die OpenSource-Basis von Chrome, wollte das schon vor über 10 Jahren aus dem Browser schmeißen:

https://issues.chromium.org/issues/41017445

Firefox könnte das vielleicht noch...

Nachtrag: Chrome kann das noch, wenn du den Bilder-Stream nicht toplevel aufmachst, sondern z.B. in einem img-tag als src angibst.

OP #7624682
Lesenswert?

Ist kein browser problem. Chrom macht multipart/x-mixed-replace; bei jpg bildern. Der code läuft:

1
#define PART_BOUNDARY "123456789000000000000987654321"
2
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
3
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
4
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
5

6
  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
7
 
8
  while(true)
9
  {    
10
    delay(10);
11
    fb = esp_camera_fb_get();
12
    if (!fb) res = ESP_FAIL;
13
     if(res == ESP_OK)
14
    {      
15
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, fb->len);
16
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
17
    }
18
    if(res == ESP_OK){
19
      res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
20
    }
21
    if(res == ESP_OK){
22
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
23
    }
24
    if(fb)
25
    {
26
      len = fb->len;
27
      esp_camera_fb_return(fb);
28
      fb = NULL;
29
    } 
30
    if(res != ESP_OK) break;
31
  }
32
  return res;

Ich möchte das nicht mit httpd server sondern mit https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/src/WebServer.h

also mit den funktionen machen:

1
  void send(int code, const char* content_type = NULL, const String& content = String(""));
2
  void send(int code, char* content_type, const String& content);
3
  void send(int code, const String& content_type, const String& content);
4
  void send(int code, const char* content_type, const char* content);
5

6
  void setContentLength(const size_t contentLength);
7
  void sendHeader(const String& name, const String& value, bool first = false);
8
  void sendContent(const String& content);
9
  void sendContent(const char* content, size_t contentLength);

Grund is httpd erlaubt mir keine UDP kommunikation nebenher zu fahren.

Eine beschreibung unterschied send - sendContent - sendHeader habe ich nicht gefunden.

OP #7624687
Lesenswert?

Meiner analyse nach macht der laufende code:

1
httpd_resp_set_type(req, "multipart/x-mixed-replace;boundary=123456789000000000000987654321");
2

3
LOOP
4
// get picture
5
fb = esp_camera_fb_get();
6

7
// set length, xxxx = length
8
res = httpd_resp_send_chunk(req, "Content-Type: image/jpeg\r\nContent-Length: xxxx\r\n\r\n"); 
9

10
// send data
11
httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
12

13
// boundary to next picture
14
httpd_resp_send_chunk(req, "\r\n--123456789000000000000987654321\r\n");
15
ENDLOOP
OP #7624769
Lesenswert?

Denke ich habs gefunden. Mit "WiFiServer server(80);"

Erstes mal:

1
  client->println("HTTP/1.1 200 OK");
2
  client->printf("Content-Type: %s\r\n", _STREAM_CONTENT_TYPE);
3
  client->println("Content-Disposition: inline; filename=capture.jpg");
4
  client->println("Access-Control-Allow-Origin: *");
5
  client->println();

Dann dauerschleife bild mit:

1
  client->print(_STREAM_BOUNDARY);
2
  client->printf(_STREAM_PART, fb);
3
  client->write(out_buf, len) // bilddaten
OP #7626686
Lesenswert?

Falls das interessiert, hier der ganze streaming code.

1
void stream_handler()
2
{
3
  WiFiClient wfc = s_server.client();
4
  String out;
5

6
  Serial.println("Image stream start");
7
  out  = "HTTP/1.1 200 OK\r\n";
8
  out += "Content-Type: ";
9
  out += "multipart/x-mixed-replace;boundary="; 
10
  out += "123456789000000000000987654321\r\n";
11
  out += "Content-Disposition: inline; filename=capture.jpg\r\n";
12
  out += "Access-Control-Allow-Origin: *\r\n"; 
13
  out += "\r\n";
14
     
15
  Serial.println(out);  
16
  s_server.sendContent(out);
17

18
  while(wfc.connected())
19
  {
20
    delay(100);
21
    fb = esp_camera_fb_get();
22
    if (fb) 
23
    {
24

25
      out  = "\r\n--123456789000000000000987654321\r\n";
26
      out += "Content-Type: image/jpeg\r\nContent-Length: ";
27
      out += fb->len;
28
      out += "\r\n\r\n";    
29
      s_server.sendContent(out);
30

31
      s_server.sendContent(String(fb->buf, fb->len)); 
32
       
33
      esp_camera_fb_return(fb);
34
    }
35
    else break;
36
  } 
37
  Serial.println("Disc"); 
38
}
#7626810
Lesenswert?

Schön dass es läuft. Du umgehst halt die Webserver-Klasse komplett und machst alles zu Fuß selber. Kann man machen.

Zwei Anmerkungen:

UDP Parallel dazu geht so auch nicht, das war doch der Grund für die ganze Übung? Du steckst immer in der "while connected" Schleife fest.

und:

s_server.sendContent(...

Das Senden direkt auf dem s_server fliegt in einer der nächsten Versionen des ESP32-Arduino-Frameworks raus. War nie klar definiert, auf welcher Verbindung (oder ob auf Allen) das senden sollte. Besser: mach die Ausgaben auf "wfc", dafür ist das da, und damit ist auch klar welche Verbindung welche Daten erhält.

Und statt sendContent(String(fb->buf, fb->len)) gäb's wfc.write(const uint8_t *buf, size_t size) das kommt ohne Kopieren des Framebuffers in einen String aus, sondern kann direkt auf dem fb arbeiten.

OP #7626850
Lesenswert?

Εrnst B. schrieb:

UDP Parallel dazu geht so auch nicht, das war doch der Grund für die ganze Übung?

Da läuft der bildserver auf port 81, der normale server auf port 80 und udp auf port 5005. Kein problem.

Die version mit #include <WebServer.h> hat den vorteil dass arduino OTA läuft. Das habe ich bei "esp_http_server.h / httpd" und "<WiFi.h> / WiFiServer server(80);" nicht hingebracht.

Du steckst immer in der "while connected" Schleife fest.

Das muss so sein. Den loop kann man auseinander nehmen und dazwischen anderes machen. Der muss nicht im handler code sein. Das ist test-code!

Besser: mach die Ausgaben auf "wfc", dafür ist das da, und damit ist auch klar welche Verbindung welche Daten erhält.

Ich habe gestern erstmals etwas von "wfc" gehört. etwas geduld bitte!

Und statt sendContent(String(fb->buf, fb->len)) gäb's wfc.write(const uint8_t *buf, size_t size) das kommt ohne Kopieren des Framebuffers in einen String aus, sondern kann direkt auf dem fb arbeiten.

Werde ich anschauen. TY

Edit:

1
      //s_server.sendContent(String(fb->buf, fb->len)); 
2
      wfc.write(fb->buf, fb->len);

Geht.

#7627111
Lesenswert?

Hadmut F. schrieb:

Wo ist eigentlich der dispatcher von WiFiClient auf die verschiedenen WebServer.h server.on("/", handler); zu finden?

Der Webserver hat eine Linked List der Handler, die "on()"-Methoden hängen da einen Eintrag an.

In Parsing.cpp (WebServer::_parseRequest) läuft er, nachdem die Request-Header geparst sind, über die Liste, nimmt sich den ersten Handler der sich zuständig fühlt:

1
  RequestHandler* handler;
2
  for (handler = _firstHandler; handler; handler = handler->next()) {
3
    if (handler->canHandle(_currentMethod, _currentUri))
4
      break;
5
  }

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren