CaptureMJPEG: tolerate missing Content-Type / Content-Length in MJPEG; add boundary sniffing & lengthless frame reads
#3603
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Some MJPEG endpoints (e.g., FFmpeg’s
-f mpjpegand various embedded devices) omitContent-Lengthper-part and/or do not send aContent-Typeheader at all. Previous implementation assumed both headers were always present, causing exceptions and a broken video pipeline.This PR makes the MJPEG client robust to missing headers by:
Detecting the multipart boundary from the HTTP
Content-Typeheader or (if absent) sniffing it from the body.Reading each part by exact length when
Content-Lengthexists, or streaming until the next boundary when it doesn’t.Making header parsing case-insensitive and resilient to minor formatting variations.
Hardening the HTTP request (keep-alive, explicit
Accept).Problem
KeyNotFoundExceptionatint.Parse(getHeader(br)["Content-Length"])when per-partContent-Lengthis missing.Failure to initialize the stream when
Content-Typeis absent or lacks aboundary=...parameter.Result: MP cannot display otherwise valid MJPEG streams (e.g., FFmpeg server, some IP cams), even though browsers can.
What’s changed (implementation)
Boundary detection
Try to parse
boundaryfromContent-Type.If not present,
DetectBoundaryFromBodyscans the preamble until--<token>and uses<token>as the boundary.Part reading
If
Content-Lengthis present →ReadExact(len).If missing →
ReadUntilNextBoundary(...)scans bytes until\r\n--<boundary>without over-reading.After length-based reads, safely consume trailing CR/LF (
SafeConsumeCrlf).Parsing
HTTP request hardening
req.KeepAlive = true,req.Accept = "multipart/x-mixed-replace".Optional read timeout guarded with try/catch.
Compatibility
Backwards-compatible: compliant streams with
Content-Type+Content-Lengthcontinue through the original exact-length path.Adds support for more “relaxed” MJPEG producers (FFmpeg, some IP cameras, microcontroller streams).
Performance notes
The boundary-scan path reads byte-by-byte to avoid over-reading on non-seekable streams. This is reliable at 720p60 MJPEG in testing; if benchmarks ever show pressure, could be micro-optimize with a small rolling buffer. The exact-length path remains unchanged and fastest.
Testing
Tested against:
Content-Length):FFmpeg with missing
Content-Type(simulate via proxy/stripper) + body-only boundary.Browser sanity:
http://<IP>:<PORT>/stream.mjpgrenders; closing the browser allows MP to connect (FFmpeg -listen 1 serves one client at a time).Repro in MP:
Start FFmpeg server (above).
In MP, set source to
http://<IP>:<PORT>/stream.mjpg.Video displays without exceptions even when
Content-Lengthis not sent per part.Risks / Mitigations
Stream variations: Multipart formatting in the wild can be quirky. Implementation handle both
Content-Lengthand lengthless parts; also tolerate missingContent-Typeby sniffing the boundary from the body.Latency: No additional buffering introduced; the boundary-scan path avoids building up frames and keeps latency low.
Follow-ups (optional)
Add small ring-buffer optimization for the boundary scanner if future profiling suggests it.
Add unit/IO tests for: (1) with/without
Content-Type, (2) with/withoutContent-Length, (3) different boundary tokens, (4) unusual CR/LF patterns.