camfarm

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 23 Imported by: 0

README

CamFarm

ci codecov Quality Gate Status

Simulated fleet for pipeline testing. Not an ONVIF conformance tool.

CamFarm is a synthetic RTSP camera farm for testing video-analytics pipelines without physical hardware. It serves N fake cameras from one process, and it is built so that misbehaviour can be injected deterministically: a fault fires because a seed said it would, and the same seed and fleet spec reproduce it.

Quick start

go run ./cmd/camfarm --cameras 3

The binary prints dashboard: http://127.0.0.1:8080/. Open it: every camera is a card with a live snapshot and its RTSP and ONVIF URLs, each with a Copy button. Point your pipeline, ffprobe or VLC at an RTSP URL from there (the RTSP and ONVIF ports are picked at random unless you pass --rtsp-port and --onvif-port). Use your own footage with --media-dir DIR. Run with -h for every flag.

In a container (Docker or nerdctl), see docs/docker.md:

docker build -t camfarm .
docker run --rm -p 127.0.0.1:8080:8080 -p 127.0.0.1:8554:8554 -p 127.0.0.1:8000:8000 camfarm

The dashboard has no authentication. It can start servers, read local paths when you allow it, and show host details and camera credentials. It listens on loopback by default. Do not expose it. Details.

The dashboard

The screenshots below come from TestDocsScreenshots, a Playwright test. The committed images were made by running it on a development machine with the command at the end of this section; CI runs the same test on every change, which fails if the page stops showing these views, and keeps its own images as the dashboard-screenshots build artifact. Nothing is mocked: the host figures are the machine's own (the GPU tile says why it is unavailable when there is no nvidia-smi), the library verdicts come from the real validator, and the stream quality result is a real run.

Cameras. One card per camera, with the stream's source, codec and size, fault chips, and a stream quality test that reports frame rate, bitrate, jitter, packet loss and keyframe interval.

The fault drawer open on a camera, with the packet_loss kind selected and its parameters shown.

Faults. The drawer lists the catalogue's kinds and parameters, shows what is active on the camera and clears it. What each fault does, and where its determinism stops, is in docs/faults.md.

Library. Every file in your media directories is validated once per change, and each problem is written out, so a file that cannot be served is explained before it becomes a camera. See docs/library.md.

To regenerate the images:

CAMFARM_DOCS_SHOTS=$PWD/docs/assets go test -tags e2e -count=1 -run TestDocsScreenshots ./internal/dashboard/

Status

No tagged release yet, so the Go API and the JSON API can still change. What works, in brief:

  • N RTSP cameras behind one listener, H.264 and H.265, from MPEG-TS, MP4 or fragmented MP4 sources.
  • An ONVIF® Device and Media control plane, and an opt-in WS-Discovery responder.
  • Ten seeded fault effects, injectable at startup or at runtime, with a replayable journal.
  • A dashboard with a media library, a stream quality test and host metrics, and a JSON API.
  • A Go API: camfarm.Start(spec) in a TestMain, Fleet.AddCamera and Fleet.RemoveCamera.

Implements/supports the ONVIF® Device Service and Media Service specifications. This project is not ONVIF conformant, is not tested against the ONVIF conformance suite, and makes no claim to any ONVIF profile or add-on.

The full list, including what is not built yet, is in docs/features.md. More: dashboard and CLI, faults, media library, stream quality test, host metrics and WS-Discovery.

Fault catalogue

Kept in step with the catalogue in internal/fault by a test that fails if this table drifts.

Kind Implemented Models
frame_drop yes Encoder skipping frames: a frame is never encoded, so RTP sequence numbers stay contiguous and the loss shows only as a timestamp jump.
packet_loss yes Lossy path: packets of an encoded frame are withheld, leaving real sequence-number gaps with honest timestamps.
keyframe_starvation yes Long-GOP camera: only every Nth keyframe is sent, so a client joining mid-stream cannot start decoding.
rtsp_error yes Flaky camera that answers 5xx or 454 to some DESCRIBE, SETUP or PLAY requests.
teardown yes Camera that drops live sessions; TCP reset when mode is reset.
sdp_lie yes SDP that misdescribes the stream: wrong codec, wrong parameter sets, wrong geometry or wrong Content-Base.
jitter yes Inter-arrival jitter: each frame leaves a seeded random time late (order kept), which stresses jitter-buffer sizing.
burst_delivery yes Bursty uplink or encoder buffering: frames are held and then released back-to-back, which overflows NVR queues.
pts_dts_mismatch yes B-frame camera that stamps presentation time into RTP: timestamps run out of order, so decoders report non-monotonic dts.
pts_delayed yes Camera whose timestamps lag capture, constantly or by drift: what a latency profiler such as framelag meets.
accept_then_silence no Camera that accepts the TCP connection and never answers.

Use as a library

f, err := camfarm.Start(ctx, camfarm.Spec{
	Seed:    0x3f2a9c81,
	Cameras: []camfarm.CameraSpec{{ID: "front-door"}, {ID: "lobby"}},
})
if err != nil {
	return err
}
defer f.Close()

cam, _ := f.Camera("front-door")
fmt.Println(cam.RTSPURL()) // rtsp://127.0.0.1:41569/front-door

In a test, camfarm.StartT(t, spec) binds an ephemeral port, stays silent, and closes itself when the test ends.

Scope: what this does not do

  • It simulates. It is not a conformance-tested implementation of anything, and it does not claim to be. Testing a client against this farm tells you your client works against this farm. It does not tell you your client is correct against real hardware, and it says nothing about your own conformance to any specification.
  • No transcoding or rescaling. A camera serves the codec and geometry its source already has; VideoSpec is refused when it disagrees with the source. Serving H.265 means supplying an H.265 source. An encoder would be needed, no viable pure-Go H.265 encoder exists, and cgo and a runtime ffmpeg are excluded by design.
  • Narrow container support. MPEG-TS, plain MP4 and fragmented MP4, H.264 or H.265 video only; audio is ignored. Matroska and MPEG-PS are not supported. Fragmented MP4 needs each trun to carry a data-offset preset; a producer using the base-data-offset convention is refused rather than mis-parsed.
  • ONVIF: Device and Media services only. Enough for a client to discover a camera and get its real RTSP URL. PTZ and Imaging are not implemented, and GetCapabilities/GetServices never advertise them. WS-Discovery is opt-in and is also a simulation; its limits, including how multicast behaves on real networks, are in docs/discovery.md. With discovery off, a client is given the ONVIF endpoint directly (Camera.ONVIFEndpoint()).
  • The bundled video is a fixture, not a sample. A two-second 320×240 test pattern in H.264 and its H.265 counterpart, generated by scripts/gen-fixture.sh. Neither is representative of real camera bitrate, motion, noise or encoder behaviour.
  • Determinism has limits. The decision sequence is reproducible from a seed; wall-clock timing is not, because serving a real client happens in real time. RTP SSRC is not reproducible (the RTSP library assigns it), so replay assertions exclude it; sequence numbers are reproducible. A live upstream source, not implemented yet, would be outside the guarantee.
  • No network-path impairment. Bandwidth ceilings, reordering and other path effects need tc/netem and elevated privileges, and cannot be reproduced from a seed. packet_loss is a sender-side simulation (the camera chooses not to write packets). CamFarm owns only what a camera itself chooses not to send, or sends late.
  • Not a benchmark. No cameras-per-core or memory-per-camera figure is published, because none has been measured. When one is, it will come from a committed benchmark.

Requirements

Go 1.26 or later. No cgo, and no system packages at runtime. ffmpeg is needed only to run the test suite, which cross-checks the served streams with ffprobe and remuxes the bundled fixtures into MP4 to test the other container paths. Regenerating the H.265 fixture additionally needs an ffmpeg built with libx265; running the tests does not, because the fixture is committed.

Licence

MIT. See LICENSE.

Documentation

Overview

Package camfarm serves synthetic RTSP video streams and answers the control-plane requests that camera-discovery code uses to find and drive a fleet, so that video-analytics pipelines can be tested without physical hardware.

Its distinguishing property is deterministic fault injection: a fault fires because a seed said it would, at a reproducible point in the stream, and the same seed and fleet spec reproduce it exactly.

This version serves RTSP media for a fleet of cameras and carries the seed and fault-injection seams throughout, with every catalogued fault deliberately inert: a fault named in a spec is refused rather than silently ignored. The control plane and the fault effects follow. The architecture is recorded in docs/superpowers/specs/2026-09-05-camfarm-architecture-design.md.

Index

Constants

View Source
const (
	// DiscoveryMulticast joins the WS-Discovery multicast group.
	DiscoveryMulticast = "multicast"
	// DiscoveryUnicast binds a unicast UDP socket, for tests and CI where
	// multicast is unavailable.
	DiscoveryUnicast = "unicast"
)

Discovery modes.

Variables

View Source
var (
	// ErrUnknownCamera means no camera with the given ID exists in the fleet.
	ErrUnknownCamera = errors.New("camfarm: unknown camera")

	// ErrUnsupported means this build cannot do what was asked. It is not a
	// malfunction and not a transient condition.
	ErrUnsupported = errors.New("camfarm: unsupported")

	// ErrNotReady means the fleet or camera has not finished initialising.
	ErrNotReady = errors.New("camfarm: not ready")

	// ErrFaultActive means the request was refused *because* a fault is
	// deliberately in effect.
	//
	// This must never read as a malfunction. "This camera is lying on purpose"
	// and "this camera is broken" being indistinguishable would make the whole
	// tool useless.
	ErrFaultActive = errors.New("camfarm: refused by an active fault")

	// ErrDuplicateCamera means a camera with that ID already exists.
	ErrDuplicateCamera = errors.New("camfarm: duplicate camera")

	// ErrInvalidSpec means a spec was malformed or out of range, as opposed to
	// merely unsupported by this build.
	ErrInvalidSpec = errors.New("camfarm: invalid spec")
)

The error taxonomy. These are kept distinct because conflating them is a known pain for consumers of virtual cameras: a caller that cannot tell "this build cannot do that" from "this is not ready yet" has to guess.

Functions

This section is empty.

Types

type Addrs

type Addrs struct {
	RTSP *net.TCPAddr
}

Addrs are the fleet's bound addresses, read back after binding.

type AuthSpec

type AuthSpec struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
}

AuthSpec configures a camera's ONVIF HTTP Digest challenge.

The zero value means open — consistent with the pattern VideoSpec already establishes ("zero fields mean whatever"), and consistent with RTSP itself having no auth today. A camera only challenges when both fields are non-empty.

type Camera

type Camera struct {
	// contains filtered or unexported fields
}

Camera is one camera in a running fleet.

func (*Camera) Auth

func (c *Camera) Auth() (user, pass string, ok bool)

Auth returns the camera's ONVIF Digest credentials, and false if it is open.

func (*Camera) Clear

func (c *Camera) Clear() error

Clear removes every fault from a camera. With no faults active this succeeds and does nothing: asking for the state that already holds is not an error.

func (*Camera) Faults

func (c *Camera) Faults() []FaultSpec

Faults returns the camera's currently active faults.

func (*Camera) ID

func (c *Camera) ID() string

ID returns the camera's operator-chosen identifier.

func (*Camera) Inject

func (c *Camera) Inject(f FaultSpec) error

Inject adds a fault to a running camera, replacing any active fault of the same kind. A per-frame kind takes effect at the camera's next frame boundary, a control-plane kind (rtsp_error, sdp_lie, teardown) on the next request. Only implemented kinds are accepted; the rest return ErrUnsupported.

Faults come in two classes, per-frame (the pump) and per-request (the control engine). An inject or clear rebuilds the engine of every class whose fault list changed, from the camera seed, so that class's random streams restart at their first draw and keyframe_starvation's keyframe counter restarts too (the next keyframe counts as the first and is delivered). A class whose list did not change is left alone, and injecting a fault identical to the active one is a no-op that journals nothing. A replay must restart a class's streams at each journaled inject or clear of that class (see JournalEntry.Clock).

func (*Camera) LocalRTSPURL

func (c *Camera) LocalRTSPURL() string

LocalRTSPURL returns a URL for dialing the camera from this process: the bound address rather than the advertised host, which may not resolve from inside a container.

func (*Camera) ONVIFEndpoint

func (c *Camera) ONVIFEndpoint() string

ONVIFEndpoint returns the camera's ONVIF base URL, e.g. "http://127.0.0.1:PORT/onvif/front-door". Append "/device" or "/media" for the two services this version serves.

func (*Camera) ONVIFUUID

func (c *Camera) ONVIFUUID() string

ONVIFUUID is the camera's ONVIF endpoint UUID. It is deterministic from the camera seed. The camera's ONVIF Device service returns it from GetEndpointReference with a "urn:uuid:" prefix; this accessor, the API and the dashboard show it bare.

func (*Camera) RTSPURL

func (c *Camera) RTSPURL() string

RTSPURL returns the camera's stream URL.

func (*Camera) Screenshot

func (c *Camera) Screenshot(ctx context.Context) (image.Image, error)

Screenshot returns one decoded frame from this camera's live RTSP stream.

It dials the camera's own RTSP server on its bound address (not the advertised host, which may not resolve here), so it exercises the real wire path, and blocks until the stream's next random access point (bounded by one loop of the source). It returns ErrNotReady if the fleet's RTSP server is not serving, and ctx's own error if ctx ends first.

func (*Camera) Spec

func (c *Camera) Spec() CameraSpec

Spec returns the camera's spec as it was added, with its currently active faults.

func (*Camera) Stats

func (c *Camera) Stats() Stats

Stats returns the camera's counters.

type CameraSpec

type CameraSpec struct {
	// ID is operator-chosen and appears in the camera's RTSP path.
	ID     string      `json:"id"`
	Source SourceSpec  `json:"source,omitempty"`
	Video  VideoSpec   `json:"video,omitempty"`
	Faults []FaultSpec `json:"faults,omitempty"`
	Auth   AuthSpec    `json:"auth,omitempty"`
}

CameraSpec describes one camera.

type DiscoverySpec

type DiscoverySpec struct {
	// Mode is "multicast" or "unicast".
	Mode string `json:"mode"`
	// Interface selects the interface (name or IP) that joins the multicast
	// group. Multicast mode only.
	Interface string `json:"interface,omitempty"`
	// Addr is the UDP address to bind in unicast mode, e.g. 127.0.0.1:0.
	// Required for unicast; forbidden for multicast.
	Addr string `json:"addr,omitempty"`
	// AnnounceAddr is where Hello and Bye go. Unicast mode only; empty
	// disables announcements.
	AnnounceAddr string `json:"announceAddr,omitempty"`
}

DiscoverySpec configures the WS-Discovery responder.

type Event

type Event struct {
	Seq    uint64     `json:"seq"`
	Type   EventType  `json:"type"`
	Camera string     `json:"camera"`
	Fault  *FaultSpec `json:"fault,omitempty"`
	// FrameIndex is the frame a fault change took effect on; -1 for add/remove.
	FrameIndex int `json:"frameIndex"`
	// Clock names what FrameIndex counts: "frame" or "request". Empty for
	// add/remove.
	Clock string `json:"clock,omitempty"`
}

Event is one change to a running fleet.

type EventType

type EventType string

EventType names a change to a running fleet.

const (
	EventCameraAdded   EventType = "camera_added"
	EventCameraRemoved EventType = "camera_removed"
	EventFaultInjected EventType = "fault_injected"
	EventFaultCleared  EventType = "fault_cleared"
)

Event types.

type FaultCount

type FaultCount struct {
	Applied    uint64
	Suppressed uint64
}

FaultCount is one fault kind's tally.

type FaultSpec

type FaultSpec struct {
	// Kind names a catalogued fault.
	Kind string `json:"kind"`
	// Rate is the probability for rate-based faults, in [0,1].
	Rate float64 `json:"rate,omitempty"`
	// ProtectKeyframes never applies frame_drop or packet_loss to a
	// random-access frame.
	ProtectKeyframes bool `json:"protectKeyframes,omitempty"`
	// MeanBurst, for packet_loss, selects burst loss when greater than 1.
	MeanBurst float64 `json:"meanBurst,omitempty"`
	// KeepEvery, for keyframe_starvation, delivers every Nth keyframe.
	KeepEvery int `json:"keepEvery,omitempty"`
	// On, for rtsp_error, names the failing method: describe, setup, play or all.
	On string `json:"on,omitempty"`
	// Status, for rtsp_error, is the RTSP status code of the reply.
	Status int `json:"status,omitempty"`
	// OnlyRequest, for rtsp_error and sdp_lie, fires on the Nth matching request only.
	OnlyRequest int `json:"onlyRequest,omitempty"`
	// AfterFrames, for teardown, is the frames delivered before teardown.
	AfterFrames int `json:"afterFrames,omitempty"`
	// Mode selects a variant of teardown (close, reset) or sdp_lie (codec,
	// sprop, resolution, contentBase).
	Mode string `json:"mode,omitempty"`
	// MaxMs, for jitter, bounds each frame's random delay in milliseconds.
	MaxMs int `json:"maxMs,omitempty"`
	// HoldFrames, for burst_delivery, is the frames held before a burst.
	HoldFrames int `json:"holdFrames,omitempty"`
	// ReorderDepth, for pts_dts_mismatch, is how far timestamps run out of order.
	ReorderDepth int `json:"reorderDepth,omitempty"`
	// DelayMs, for pts_delayed, is a constant timestamp lag in milliseconds.
	DelayMs int `json:"delayMs,omitempty"`
	// DriftPpm, for pts_delayed, is a timestamp drift in parts per million.
	DriftPpm int `json:"driftPpm,omitempty"`
}

FaultSpec configures one fault.

type Fleet

type Fleet struct {
	// contains filtered or unexported fields
}

Fleet is a running set of synthetic cameras.

func Start

func Start(ctx context.Context, spec Spec) (*Fleet, error)

Start brings up a fleet.

Cancelling ctx closes the fleet.

func StartT

func StartT(t TB, spec Spec) *Fleet

StartT brings up a fleet for a test: ephemeral ports, silent by default, and closed automatically when the test finishes.

func (*Fleet) AddCamera

func (f *Fleet) AddCamera(spec CameraSpec) (*Camera, error)

AddCamera starts a new camera on a running fleet. It applies the same validation Start does, so a spec that would be refused at Start is refused here too.

Its seed is derived from a monotonically increasing index that never resets, even across RemoveCamera calls, so a dynamically added camera's seed is a pure function of call order -- reproducible if that order is logged, per CLAUDE.md's "Determinism is the product."

func (*Fleet) Addr

func (f *Fleet) Addr() Addrs

Addr returns the fleet's bound addresses.

func (*Fleet) Camera

func (f *Fleet) Camera(id string) (*Camera, error)

Camera returns one camera.

func (*Fleet) Close

func (f *Fleet) Close() error

Close stops the fleet. It is safe to call more than once.

func (*Fleet) DiscoveryAddr

func (f *Fleet) DiscoveryAddr() string

DiscoveryAddr returns the bound WS-Discovery UDP address, or "" when discovery is disabled.

func (*Fleet) Journal

func (f *Fleet) Journal() []JournalEntry

Journal returns the runtime changes made so far, oldest first.

func (*Fleet) List

func (f *Fleet) List() []Status

List returns every camera's status, in spec order.

func (*Fleet) RemoveCamera

func (f *Fleet) RemoveCamera(id string) error

RemoveCamera stops and removes a camera from a running fleet.

It is best effort once id is known to exist: there is nothing a caller could sensibly do to retry a partial removal, so both the RTSP and ONVIF sides are always attempted regardless of whether the other failed.

func (*Fleet) ReplayLine

func (f *Fleet) ReplayLine() string

ReplayLine returns the one line a failing test should print to make its failure reproducible.

func (*Fleet) Seed

func (f *Fleet) Seed() uint64

Seed returns the fleet's root seed.

func (*Fleet) SpecHash

func (f *Fleet) SpecHash() string

SpecHash returns the digest of the spec this fleet was built from.

func (*Fleet) Subscribe

func (f *Fleet) Subscribe(ctx context.Context) <-chan Event

Subscribe returns a channel of fleet changes, closed when ctx ends, when the fleet closes, or if the subscriber falls too far behind.

type JournalEntry

type JournalEntry struct {
	Seq    uint64    `json:"seq"`
	Action string    `json:"action"` // add, remove, inject, clear
	Camera string    `json:"camera"`
	Fault  FaultSpec `json:"fault"`
	// FrameIndex is when a fault change landed. It depends on when the change
	// was requested, which is the limit of replaying an interactive run.
	FrameIndex int `json:"frameIndex"`
	// Clock says whether FrameIndex counts frames (per-frame faults) or RTSP
	// requests (control-plane faults). Part of the replay hash.
	Clock string `json:"clock,omitempty"`
}

JournalEntry records one runtime change. Start-time cameras and faults are not journaled: the spec hash already identifies them.

type ListenSpec

type ListenSpec struct {
	// Host defaults to 127.0.0.1, so a test fixture is not exposed to the
	// network by accident.
	Host string `json:"host,omitempty"`
	// AdvertiseHost, when set, is the host written into every URL a client is
	// told to dial (RTSP URLs, ONVIF endpoints and XAddrs, stream URIs,
	// WS-Discovery XAddrs, the dashboard's links), instead of Host. Host
	// stays the bind address. It is required when Host is a wildcard
	// (0.0.0.0 or ::), as in a container. It must be a bare host or IP. The
	// ports in those URLs are the real bound ports, so a published container
	// port must equal the container port, or the ports must be fixed.
	AdvertiseHost string `json:"advertiseHost,omitempty"`
	// RTSPPort of zero requests an ephemeral port, readable back via
	// Fleet.Addr.
	RTSPPort int `json:"rtspPort,omitempty"`
	// ONVIFPort of zero requests an ephemeral port for the ONVIF HTTP
	// listener, readable back via Camera.ONVIFEndpoint.
	ONVIFPort int `json:"onvifPort,omitempty"`
	// Discovery, when set, makes the fleet answer WS-Discovery probes. Nil
	// (the default) leaves discovery off and the spec's hash unchanged.
	// Enabling it changes Spec.Hash() and therefore the replay line, so a run
	// with discovery on does not share a replay line with the same fleet
	// without it.
	Discovery *DiscoverySpec `json:"discovery,omitempty"`
}

ListenSpec describes what the fleet binds.

type SourceKind

type SourceKind string

SourceKind selects where a camera's video comes from.

const (
	// SourceFixture uses the media bundled with camfarm. It is the default.
	SourceFixture SourceKind = "fixture"
	// SourceFile reads an MPEG-TS file from disk.
	SourceFile SourceKind = "file"
)

Source kinds.

type SourceSpec

type SourceSpec struct {
	// Kind defaults to SourceFixture.
	Kind SourceKind `json:"kind,omitempty"`
	// Path is required when Kind is SourceFile.
	Path string `json:"path,omitempty"`
}

SourceSpec describes a camera's media source.

type Spec

type Spec struct {
	// Seed roots every random decision the fleet makes.
	Seed    uint64       `json:"seed"`
	Cameras []CameraSpec `json:"cameras"`
	Listen  ListenSpec   `json:"listen,omitempty"`

	// Log receives fleet events. Nil means silent, which is the default: a
	// consumer may run camfarm inside a process whose stdout carries a protocol
	// stream, so nothing is ever written there.
	Log *slog.Logger `json:"-"`
}

Spec describes a fleet.

func (Spec) Hash

func (s Spec) Hash() string

Hash returns a stable digest of the fleet's identity.

The logger is excluded: it changes what a run prints, not which fleet it is. A replay reports this alongside the seed so that reproducing a failure verifies it is the same fleet rather than assuming it.

type Stats

type Stats struct {
	// Seed is this camera's derived seed, not the fleet root.
	Seed uint64
	// FramesServed counts access units served. It is monotonic across loops of
	// the source, so it doubles as the camera's position in the run.
	FramesServed uint64
	// LoopsCompleted counts completed passes over the source media.
	LoopsCompleted uint64
	// FaultsFired counts fault decisions that have fired for this camera. For
	// packet_loss it counts frames that had loss, not packets; use FaultCounts
	// for packet counts.
	FaultsFired uint64
	// FaultCounts tallies each fault kind's effects: frames dropped, packets
	// or keyframes withheld (Applied) and decisions spared by
	// ProtectKeyframes (Suppressed). Never nil.
	FaultCounts map[string]FaultCount
	// OutboundRTPPackets counts RTP packets sent on this camera's stream,
	// read from the RTSP stream's own counters.
	OutboundRTPPackets uint64
	// OutboundBytes counts bytes sent on this camera's stream, read from the
	// RTSP stream's own counters.
	OutboundBytes uint64
	// Readers is the number of sessions currently set up against this
	// camera. Unlike the other fields, it is a live count, not a running
	// total.
	Readers int
}

Stats are a camera's counters, correlated with the seed that produced them.

type Status

type Status struct {
	ID      string
	RTSPURL string
	Source  string
	Codec   string
	Width   int
	Height  int
	FPS     float64
	Readers int
}

Status is a camera's current state.

type TB

type TB interface {
	Cleanup(func())
	Fatalf(format string, args ...any)
	Helper()
}

TB is the part of *testing.T that StartT needs.

An interface rather than *testing.T so that this library does not import testing, which would register test flags in every consumer's binary. *testing.T and *testing.B both satisfy it.

type VideoSpec

type VideoSpec struct {
	Codec  string  `json:"codec,omitempty"`
	Width  int     `json:"width,omitempty"`
	Height int     `json:"height,omitempty"`
	FPS    float64 `json:"fps,omitempty"`
}

VideoSpec describes the video a camera should produce.

Zero fields mean "whatever the source provides". A non-zero field is a request for that output value, which this version can satisfy only when it already matches the source, because it can pass a source through but not transform it. Any other value is refused with ErrUnsupported.

Note for callers pinning this API: these fields name the *desired output*, so a spec that is refused today will be satisfied by rescaling or transcoding in a later version rather than continuing to be refused. That is a deliberate trade, taken for a smaller API surface than a separate output block would give; the refusal messages name the behaviour that will eventually replace them so the change is legible before it happens.

Directories

Path Synopsis
cmd
camfarm command
Command camfarm (CamFarm) runs a simulated camera fleet with a web dashboard.
Command camfarm (CamFarm) runs a simulated camera fleet with a web dashboard.
internal
buildinfo
Package buildinfo reports which build of camfarm is running.
Package buildinfo reports which build of camfarm is running.
clock
Package clock supplies the time source the media pump is paced by.
Package clock supplies the time source the media pump is paced by.
dashboard
Package dashboard serves an HTTP API and an embedded web page that drive a camfarm fleet.
Package dashboard serves an HTTP API and an embedded web page that drive a camfarm fleet.
fault
Package fault decides, deterministically, when a camera should misbehave.
Package fault decides, deterministically, when a camera should misbehave.
hostmetrics
Package hostmetrics samples host CPU, memory and GPU load plus camfarm's own process usage.
Package hostmetrics samples host CPU, memory and GPU load plus camfarm's own process usage.
library
Package library is the set of video files the farm can serve: directories ("roots") that are scanned, an optional upload target, and a validity report per file.
Package library is the set of video files the farm can serve: directories ("roots") that are scanned, an optional upload target, and a validity report per file.
media
Package media turns a source of encoded video into the immutable slice of access units the RTSP pumps serve.
Package media turns a source of encoded video into the immutable slice of access units the RTSP pumps serve.
media/fixture
Package fixture holds the bundled default media.
Package fixture holds the bundled default media.
obs
Package obs records what a fleet did, so a test can assert it and a failing test can report enough to reproduce it.
Package obs records what a fleet did, so a test can assert it and a failing test can report enough to reproduce it.
onvif
internal/onvif/auth.go
internal/onvif/auth.go
quality
Package quality runs rtspeek stream analyses against a fleet's own cameras and keeps the latest result per camera in memory.
Package quality runs rtspeek stream analyses against a fleet's own cameras and keeps the latest result per camera in memory.
rtsp
Package rtsp serves the fleet's synthetic streams.
Package rtsp serves the fleet's synthetic streams.
screenshot
Package screenshot grabs one decoded frame from a live RTSP stream.
Package screenshot grabs one decoded frame from a live RTSP stream.
seed
Package seed derives the deterministic random streams camfarm runs on.
Package seed derives the deterministic random streams camfarm runs on.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL