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

  1. 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).
  2. Check camera configuration: stream type, channel, and that the FLV/HTTP-FLV service is enabled on the device.
  3. Confirm no auth portal or reverse proxy is intercepting the response (check for 3xx redirects or HTML bodies).
  4. 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

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, err

View on GitHub (pinned to c245815e75)