Old Bird

Features

A complete picture of what Old Bird does on the phone, the wire, and the page.

On this page

Web viewer over HTTPS

The phone runs a TLS-wrapped HTTP server on the configured HTTPS port (default 8443). On first run, TlsKeys generates a 20-year self-signed RSA-2048 certificate inside AndroidKeyStore under the alias oldbird-tls. Browsers warn once and remember.

Visiting https://<phone-ip>:8443/ serves the embedded viewer (viewer.html) bundled with the app:

The HTTP port (default 8080) serves a separate landing page only. It explains the self-signed-certificate warning with per-browser steps (Chrome / Edge / Firefox / Safari) and a button that builds the HTTPS URL from location.hostname. No camera endpoints are reachable over plain HTTP.

Video pipeline

Camera2 is the only path. Camera2Source opens an ImageReader in YUV_420_888, converts to NV21 on the camera handler thread, and publishes frames to two consumers:

Live tunables vs full restart

Only camera-facing changes need a real CameraDevice close+reopen. Resolution change reconfigures the capture session on the same open device. Quality, rotation, exposure, fps cap, zoom and flash apply via volatile field writes plus a fresh repeating CaptureRequest — the HAL is never touched. StreamService.applySettings dispatches to the lightest path that fits the delta, so changing JPEG quality from the viewer does not drop frames.

Camera quirks & mitigations

The MediaTek HAL on the Xperia XZ1 will, over time, wedge its exe_cq kernel thread until reboot if camera close+reopen is hammered. Three mitigations:

Audio

AudioSource opens AudioRecord on the device MIC in 16 kHz mono 16-bit signed PCM. Two consumers:

RTSP server

RtspServer speaks RTSP/1.0 over TCP, with RTP and RTCP over UDP unicast. Per-session UDP port pair (even/odd, RFC 3550), one packetiser per codec:

Same RequestGate as the HTTP server: IP allowlist, then rate limit, then Basic Auth. URL is rtsp://oldbird:<password>@<phone-ip>:<port>/. The DESCRIBE handler waits up to 1.5 s for SPS/PPS/ASC; if the encoder hasn't emitted format yet it returns 503 and the client should retry.

Note: RTP-over-TCP (interleaved) is not implemented. VLC defaults to TCP transport, so pass --rtsp-tcp=0 or set the preference off. ffplay defaults to UDP and works.

On-device recording

DeviceRecorder subscribes to EncoderHub and writes its encoded H.264 + AAC into MediaMuxer MP4 segments under getExternalFilesDir(DIRECTORY_MOVIES):

A second DeviceRecorder instance runs concurrently for WebDAV upload, writing into cacheDir/webdav/ and handing each finalised file to WebDavUploader.

WebDAV upload

WebDavUploader drains a bounded queue on a background thread:

Motion detection & alerts

MotionDetector reads NV21 directly off the camera (the first w*h bytes are the Y plane), sub-samples a 16×12 cell grid, and computes per-cell luminance differences against the previous frame. One sensitivity slider (1..100) scales both the per-cell pixel-diff threshold and the minimum-cells-changed threshold together. When motion is enabled:

The webhook shape is selected per provider — see Integrations → Motion alerts for the wire formats Old Bird produces.

Internet access (tunnel)

Optional. TunnelManager opens a one-way SSH reverse tunnel to localhost.run:

Switching this off restores LAN-only operation. The local server keeps running; only the tunnel and registration go away.

Access control

Every request — HTTP, HTTPS, RTSP — flows through RequestGate:

  1. IP allowlist (default on). 403 unless the source is RFC 1918, link-local, loopback, IPv6 ULA (RFC 4193), RFC 6598 (100.64.0.0/10) for CGNAT/Tailscale. Checked before auth so off-LAN clients can't probe credentials.
  2. Rate limit: 5 failed Basic-Auth attempts per IP per 60 s → 429 with Retry-After: 60. Lockout applies even to the correct password during the window (defends against observation attacks).
  3. Basic auth: username oldbird, password is a randomly-generated 12-character string created on first service start. Constant-time comparison.

Lazy lifecycle

The camera and microphone only run while a consumer is attached — a /stream client, an /audio client, an active recorder, an RTSP session, or motion detection. Five seconds after the last consumer disappears, the foreground service tears the camera down. This is what keeps the device cool and the battery healthy for 24/7 operation.

The persistent notification reflects the live state: video+audio+rec+rtsp:8554+motion on 192.168.1.4 https:8443 http:8080, or just idle when nothing's running.