AlexxIT/go2rtc · error
flv: wrong header
Error message
flv: wrong header
What it means
readHeader reads the 9-byte FLV header from the incoming stream and requires the first 3 bytes to equal the FLV signature ("FLV"). This error means the remote endpoint did not return an FLV-formatted stream: the bytes at the start of the response are not the FLV magic signature, so the producer cannot parse the container.
Solutions
- Point the producer at a URL that actually serves an HTTP-FLV stream (verify with `curl -s <url> | head -c 3` — it must print FLV).
- Check camera configuration: stream type, channel, and that the FLV/HTTP-FLV service is enabled on the device.
- Confirm no auth portal or reverse proxy is intercepting the response (check for 3xx redirects or HTML bodies).
- Probe the URL with the library's probe path and fall back to an alternative stream URL on failure.
Example fix
// before: non-FLV endpoint
producer, err := flv.NewProducer("rtsp://camera/stream")
// after: real HTTP-FLV endpoint, probed first
const flvURL = "http://camera/flv?channel=0&streamType=main"
if err := flv.Probe(flvURL); err != nil {
log.Fatalf("%s does not serve FLV: %v", flvURL, err)
}
producer, err := flv.NewProducer(flvURL) Defensive patterns
Strategy: validation
Validate before calling
// verify the endpoint serves FLV before constructing the producer
resp, err := http.Get(url)
if err != nil { return err }
head := make([]byte, 3)
n, _ := io.ReadFull(resp.Body, head)
resp.Body.Close()
if string(head[:n]) != "FLV" {
return fmt.Errorf("%s is not an FLV stream", url)
} Type guard
func isFLVStream(header []byte) bool { return len(header) >= 3 && string(header[:3]) == "FLV" } Try / catch
if err := producer.Start(); err != nil {
if strings.Contains(err.Error(), "flv: wrong header") {
// fall back to an alternative stream URL
err = fallbackProducer.Start()
}
if err != nil { log.Fatal(err) }
} Prevention
- Always probe the URL (first 3 bytes must be "FLV") before wiring it into the producer.
- Use the camera's documented HTTP-FLV endpoint, never RTSP or snapshot URLs.
- Check for auth redirects/proxies that replace stream bodies with HTML.
When it happens
Trigger: Calling the flv producer's Dial/probe path against a URL that serves something other than an FLV stream: an RTSP URL, an HTML error page, a JSON API response, or a camera endpoint with the wrong stream type/channel parameter. Also fires when a proxy or auth portal intercepts the request and returns non-FLV content.
Common situations: Pointing the producer at a camera's RTSP or snapshot URL instead of its HTTP-FLV endpoint; camera web UI returning a login/HTML page because the session expired; reverse proxy or CDN error page replacing the stream body; firmware update changing the stream path.
Related errors
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/af9501eeb2a714e9.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/flv/producer.go:260
waitVideo = false
}
if !bytes.Contains(pkt.Payload, []byte("audiocodecid")) {
waitAudio = false
}
}
}
return nil
}
func (c *Producer) readHeader() error {
b := make([]byte, 9)
if _, err := io.ReadFull(c.rd, b); err != nil {
return err
}
if string(b[:3]) != Signature {
return errors.New("flv: wrong header")
}
_ = b[4] // flags (skip because unsupported by Reolink cameras)
if skip := binary.BigEndian.Uint32(b[5:]) - 9; skip > 0 {
if _, err := io.ReadFull(c.rd, make([]byte, skip)); err != nil {
return err
}
}
return nil
}
func (c *Producer) readPacket() (*rtp.Packet, error) {
// https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf
b := make([]byte, 4+11)
if _, err := io.ReadFull(c.rd, b); err != nil {
return nil, errView on GitHub (pinned to c245815e75)