Skip to content

Releases: Lu-Fi/timps

v1.6.1

Choose a tag to compare

@Lu-Fi Lu-Fi released this 28 Jul 09:07

[1.6.1] - 2026-07-28

Fixed

  • Sub-stream permanent stall on a large (e.g. complex-scene) IDR frame.
    video_thread()'s AU assembly buffer starts at a ~0.5 byte/pixel
    estimate, clamped to [MS_AU_BUF_MIN 128KB, MS_AU_BUF_MAX 1MB]. For a
    small sub-stream (e.g. 640x360) that estimate sits at the 128KB floor,
    which a complex-scene IDR can exceed. The overflow handler used to drop
    the oversized frame and force a fresh IDR — but an IDR is the largest
    frame type, so the forced replacement overflowed too, forced another
    IDR, overflowed again: a permanent self-reinforcing stall that delivered
    zero decodable video on that stream from the first oversized frame
    onward (found as "only one stream works" — main stayed fine on its
    larger 1MB cap). Now sums the pack lengths before assembly and grows the
    buffer (bounded by MS_AU_BUF_MAX) to fit the real frame instead of
    truncating it; the IMP_Encoder_RequestIDR() call on the (now
    last-resort, >1MB-AU-or-failed-realloc-only) overflow path is dropped,
    since forcing an IDR there was the actual cause of the stall — a dropped
    frame already recovers via the existing
    fanqueue_take_dropped_key/hub_request_idr path on a real client.
    Verified on real hardware (Galayou Y4, T23n): the sub stream went from
    zero video across 20+ minutes and every reconnect to streaming
    correctly (640x360 h264@25+aac, IDR ~99KB) alongside the main stream,
    with zero overflow events since boot.

v1.6.0

Choose a tag to compare

@Lu-Fi Lu-Fi released this 27 Jul 20:21

[1.6.0] - 2026-07-27

Added

  • Native speaker output (IMP_AO) — timps now owns the camera speaker
    directly, no more /bin/iac.
    New src/rtsp/speaker.c is the sole
    IMP_AO owner and arbitrates two producers: the ONVIF backchannel
    (live RTP → PCM, always preempts) and a system-sound play queue
    driven by a FIFO at /run/timps/audio_out taking PLAY url=<path> [vol= gain= rate= format= loop= delay=] / STOP lines — the same
    protocol prudynt/raptor's /usr/sbin/play wrapper already speaks, so
    the WiFi captive-portal prompts, the post-upgrade chime and the ESPHome
    media_player/TTS integration all get a working speaker on a timps
    image for free. The play queue decodes Ogg-Opus (opusfile, gated on
    new USE_PLAY_OPUS like USE_BC_AAC gates the AAC backchannel), WAV,
    raw PCM16 and G.711 µ/A-law. New USE_PLAY/USE_PLAY_OPUS build flags
    (off by default; USE_BACKCHANNEL no longer needs /bin/iac present at
    all — bc_available() is always true once built in). New
    hal_ao_open/write/close/set_vol/set_gain in the HAL mirror the
    existing IMP_AI bring-up (rate-fallback loop, lazy open/close); a new
    src/codec/resample.c (extracted from backchannel.c) is shared by
    both producers.
  • Live speaker volume/gain + WebUI-driven system-sound play.
    audio.spk_volume/spk_gain were parsed and persisted but never
    actually reached the hardware before; now every IMP_AO open applies
    them, and POST /control {"audio":{"spk_volume":..,"spk_gain":..}}
    writes through live (caps.audio gains the two keys, gated on an AO
    pipeline being compiled in). GET /control gains
    caps.play={available,sounds:[...]}, enumerated from
    /usr/share/sounds (.wav always listed since the µ-law/PCM decoder
    needs no library; .opus only when USE_PLAY_OPUS is actually built,
    so the list never offers a file this exact build can't decode).
    POST {"speaker":{"play":"<file>"}} / {"stop":1} enqueues on the FIFO
    after validating the name against that directory (rejects /, ..,
    non-regular files) — this is what drives the thingino WebUI's
    test-sound dropdown and live speaker volume slider.
  • Day/night: time-window and sunrise/sunset override modes. The
    native detector could previously only decide from the ISP sensor
    (total_gain/brightness). daynight.mode (sensor/time/sun, a
    string token) adds two sensor-independent modes: time forces by
    the local wall clock — a fixed [time_night_start .. time_day_start]
    "HH:MM" window, wrapping past midnight (e.g. night 20:00, day 06:30).
    sun forces by today's real sunrise/sunset for
    sun_latitude/sun_longitude via the standard low-precision sunrise
    equation (pure math, UTC epoch throughout), each edge shiftable by
    sun_sunrise_offset_min/sun_sunset_offset_min (negative allowed);
    polar day/night degenerate cases fall back to permanent day/night
    instead of NaN. sensor stays the default and its gain/brightness
    branch is untouched; time/sun reuse the existing switch + minimum-
    dwell machinery, and daynight.enabled=0 (manual) still suppresses
    forcing in all three modes. GET /control exposes the new config
    fields plus today's computed sunrise/sunset (sun_computed_sunrise/
    sunset, local "HH:MM") so a UI can sanity-check the configured
    lat/long before trusting it.
  • T31(L) nrVBs buffer-count override (video.buffers, raptor-style):
    the T31 non-scaled-channel safety clamp (see Fixed below) now only
    applies to the default buffer count — an explicit buffers= in
    timps.conf is trusted as-is (with a warning, since a bad value fails
    silently down in the kernel/dmesg), letting a board/sensor combination
    that doesn't hit the constraint opt out without patching code.

Fixed

  • Play-file tail no longer cut short on normal end-of-clip. Two
    layered bugs, both in hal_ao_close()'s drain path: (1) it
    unconditionally discarded the AO ring buffer (IMP_AO_ClearChnBuf) on
    every close, including a clip finishing normally, not just on
    stop/preempt — a drain flag now distinguishes the two, discarding
    immediately only on stop/preempt/backchannel-takeover. (2) the drain
    path's fixed sleep (one MS_AI_FRM_NUM-period ring's worth, ~0.24 s)
    assumed that was the whole story, but the IMP AO keeps its own
    playback cache on top of that ring — the real residual is ~0.7 s, so
    the fixed sleep still closed the channel ~0.5 s early (e.g.
    "Configuration portal is down" stopped after "portal"). Now uses
    IMP_AO_FlushChnBuf, the SDK's "wait for the last segment to finish
    playing" primitive, which blocks until the whole cache has actually
    reached the DAC regardless of depth. Verified acoustically via RTSP
    mic loopback across clips from 0.6 s to 2.7 s, before/after audible
    span matched against each source clip's real content window.
  • T31(L) nrVBs=1 clamp scoped to PLATFORM_T31 only. The non-
    scaled-channel buffer-count safety clamp in fs_create() (shared
    across every SoC family) fired for any chip's channel requesting >1
    buffer at native sensor resolution, but the kernel constraint requiring
    it was only ever observed on T31(L) — T10/T20/T21/T23/T30/T40/T41/C100
    now keep their untouched 2-buffer default.

v1.5.0

Choose a tag to compare

@Lu-Fi Lu-Fi released this 25 Jul 23:23

Added

  • Optional image rotation (USE_ROTATE/USE_SW_ROTATE, videoN.rotation: 0|90|180|270). 180° on every SoC; hardware 90/270 on T31/T40/T41; software 90/270 on T23. Restart-required. Known limitation: on T31, 90/270 can't carry a hardware OSD/privacy overlay (libimp IPU-OSD stride bug) — see docs/rotation.md.
  • Optional ONVIF audio backchannel (USE_BACKCHANNEL/USE_BC_AAC, audio.backchannel/backchannel_codec/backchannel_rate, caps.backchannel.available). ONVIF Profile T two-way audio via /bin/iac (thingino's ingenic-audiodaemon) — see docs/backchannel.md.

Changed

  • Default http.port moved 8080 → 8880 (was clashing with the ONVIF daemon).
  • Sub-stream OSD default font_size 24 → 12 px.

Fixed / Security

  • Command-injection hardening: daynight.switch_cmd and motion.on_motion now run via fork()+execlp() instead of system() (no shell).
  • Value clamping: audio.gain tightened to the IMP-documented 0..31 mic PGA range; volume/alc_gain/spk_volume/spk_gain and OSD logo_w/logo_h/outline clamped against their real limits.
  • hal_get() return value NULL-checked before first use.
  • Plus the accumulated /control token+CORS auth, motion grid detection, local recording, privacy masks and TLS/SRT support documented in CHANGELOG.md (undocumented since 1.2.0).

Full changelog: https://github.com/Lu-Fi/timps/blob/main/CHANGELOG.md

v1.4.6 — build hardening (M14)

Choose a tag to compare

@Lu-Fi Lu-Fi released this 19 Jul 06:36

timps v1.4.6

Build hardening (M14)

Compiler/linker defense-in-depth for the root-running network daemon, now part
of the build system (previously applied only in local builds):

  • -fstack-protector-strong and -D_FORTIFY_SOURCE=2 on libcs that support
    them (host + thingino musl); the thingino uClibc toolchain is built
    --disable-libssp with incomplete _*_chk wrappers, so it correctly gets
    neither (use --libc-musl for full compiler hardening).
  • Full RELRO (-Wl,-z,relro -Wl,-z,now) and non-executable stack
    (-Wl,-z,noexecstack), applied unconditionally (linker-only, safe under both
    libcs).
  • Two central switches to dial it back on a stubborn toolchain: HARDEN=0
    (no compiler hardening) and FORTIFY=0 (keep SSP, drop only FORTIFY).
    Mirrored in both Makefile (covers make sim / direct make target) and
    build.sh (the MIPS cross build), each libc-aware.

No source/behaviour change to the streamer itself — this only strengthens how
the binary is compiled and linked. See docs/M14-build-hardening.md.

Full changelog: v1.4.5...v1.4.6

v1.4.5 — definitive AGC live-toggle crash fix + audit hardening

Choose a tag to compare

@Lu-Fi Lu-Fi released this 19 Jul 06:27

timps v1.4.5

Fixed

  • Crash (use-after-free in libaudioProcess.so) on live AGC/NS/HPF toggle — definitive fix.
    Toggling audio.agc, audio.ns or audio.high_pass via /control on a
    running stream could still segfault timpsd (SIGSEGV, epc=0, fault in
    libaudioProcess.so, return address in libimp.so) even after the v1.4.4
    mitigation. Root cause, confirmed by disassembling the vendor libraries:
    libimp runs the AGC/NS/HPF DSP modules on its own internal record thread,
    and IMP_AI_Disable{Agc,Ns,Hpf} frees that module state and NULLs the
    processing function pointers with no lock — so any live enable/disable
    races the vendor thread and dereferences freed/NULL state. No caller-side
    lock or "frame-free" scheduling can close a race against a thread inside the
    closed-source library.

    agc, ns, high_pass, agc_target_dbfs, agc_compression_db are now
    restart-required (persist-only): they are written to config and applied
    only once at boot (single-threaded, before the capture loop starts — a
    one-shot Enable with no Disable, so no free/recreate churn to race). This
    eliminates the crash class by construction. Volume, gain and mic-mute remain
    live. The WebUI shows the five DSP fields as "applies on restart", like the
    codec/sample-rate settings.

Hardening (from the 2026-07-18 code audit)

  • N1 — record duration/size keys (segment_s, pre_roll_s, post_roll_s,
    min_free_mb) are now range-clamped, so a garbage value can't disable
    rotation or set absurd rolls (segment_s=0 still means "single file", as
    documented). Prevents an unbounded segment file filling the disk.
  • N2 — hvcC numTemporalLayers corrected 0 → 1 (H.265 metadata; strict
    validators like Bento4/Apple HLS).
  • N4 — RTSP interleaved/client_port values are range-checked and fall
    back to defaults on out-of-range input (protocol robustness).
  • N6 — the /control and status JSON accumulators no longer form an
    out-of-range pointer when the output buffer fills (latent UB removed;
    ASan-verified across all three sites plus the control_get_json cap==0 path).

Full changelog: v1.4.4...v1.4.5

v1.4.4

Choose a tag to compare

@Lu-Fi Lu-Fi released this 18 Jul 14:43

timps v1.4.4

Fixed

  • Crash (use-after-free in libimp) on a live audio-module toggle.
    Changing audio.agc, audio.ns or audio.high_pass via /control while a
    stream was running could segfault timpsd — SIGSEGV with the fault inside
    libimp.so (epc=0, return address in libimp). The /control request
    thread called IMP_AI_Enable/DisableAgc/Ns/Hpf on the live AI channel while
    the audio thread was inside IMP_AI_GetFrame — where libimp processes those
    modules — freeing state the frame path was still using.

    The toggle is now deferred: /control only flags the change; the audio
    thread re-applies it from config at a frame-free point (loop top, no frame
    held), serialized by a dedicated lock. Volume/gain/alc_gain stay direct
    (plain parameter writes, not module create/destroy). Rapid toggles coalesce
    and converge to the final configured value; no deadlock, no lost updates.

Tooling (tools/timps-qa.sh)

  • Added --only <sections> to run just selected QA sections (names or
    numbers, comma-separated), e.g. --only onvif or --only 3,10. Preflight
    always runs.
  • Fixed a false positive in the on-device config-integrity check: it counted
    = inside comments (e.g. # 0=err 1=warn, # 1 = 50Hz) as "glued" config
    lines. Comments are now stripped before the check; a real a=b c=d line is
    still detected.

Full changelog: v1.4.3...v1.4.4

v1.3.6

Choose a tag to compare

@Lu-Fi Lu-Fi released this 17 Jul 13:01

v1.3.6 — review hardening

Stabilitäts-, Sicherheits-, Performance- und Konformitätsfixes aus einem vollständigen Code-Review.

Security / Stabilität

  • RTSP: fd-Leak bei wiederholtem SETUP behoben (unauth. DoS)
  • OSD-Rasterizer: NULL-Check des Coverage-Puffers (Crash unter Speicherdruck)
  • gen_sdp: snprintf-Akkumulation gegen size_t-Underflow abgesichert
  • auth: konstantzeitige Credential-Vergleiche
  • httpd: negatives Content-Length abgewiesen; Snapshot/MJPEG-Header geklammert
  • config: runtime-mutable Strings gegen Torn-Reads gesperrt

Performance

  • hub_publish baut das Paket vor dem Lock (kein Publisher-Stall)
  • OSD-Scanline integer-begrenzt (vorher O(bbox) Soft-Float)
  • Timelapse/Snapshot interval-gegatet (Encoder nicht mehr 24/7 aktiv)
  • Piggyback-JPEG encodet mit Drain-Rate (kein ~80% Verschnitt)
  • fMP4: persistenter Per-Connection-Puffer (weniger Heap-Churn)
  • fanqueue: signal nach unlock; bei Overflow bis zum Keyframe durchdroppen

A/V-Konformität

  • AAC über RTP: korrekte Per-Fragment-AU-Header (RFC 3640)
  • fMP4: feste 1024-Sample-AAC-Dauer (kein MSE-A/V-Drift)
  • NAL-Iterator: Zero-Length-NALs überspringen statt abbrechen

Speicher / Robustheit

  • SRT: Client-Cap; reduzierte Fanqueue-Tiefe (begrenzter Pinned-RAM)
  • record: lstat + Depth-Bound beim Prunen; RING_MAX_BYTES überschreibbar
  • OSD: Font-Cache; retired-Puffer im Idle freigeben; /proc-Werte gecacht

v1.3.5

Choose a tag to compare

@Lu-Fi Lu-Fi released this 17 Jul 06:09

v1.3.5

One fix: audio input on the Ingenic HAL could silently fail to come up,
leaving RTSP/HTTP clients an advertised-but-permanently-silent audio track
with nothing in the log to explain why.

Fixed

  • hal_ingenic: check IMP_AI enable/config return codes, add a capture
    watchdog.
    IMP_AI_Enable, IMP_AI_SetChnParam and IMP_AI_EnableChn
    were fire-and-forget - the audio thread fell through to marking audio
    "up" and advertising it to RTSP/HTTP regardless of what they returned. On
    some T-series + sensor combinations one of these calls fails (the
    identical failure was reported against prudynt-t on a T23N + sc2336
    board), so the stream kept a live-looking audio track that never carried
    a single frame, with no error anywhere in the log.
    • All three calls are now return-checked; on failure the thread logs,
      tears down what it already brought up, and bails before advertising
      audio at all.
    • A new consecutive-failure watchdog in the capture loop catches the case
      where IMP_AI_EnableChn reports success but the channel still never
      yields a frame: after ~5 s of straight timeouts it logs once, clears
      the advertised audio state, and exits cleanly instead of spinning
      forever with a dead track.

Full diff: v1.3.4...v1.3.5

v1.3.4

Choose a tag to compare

@Lu-Fi Lu-Fi released this 16 Jul 19:02

v1.3.4

21 commits of security hardening, protocol-correctness fixes, and a new live
config-sync feature, none of which had shipped in a tagged release since
v1.3.3 (2026-07-14).

Security / correctness (critical)

  • fmp4: propagate ms_buf allocation failures instead of emitting a
    corrupt fragment.
    A failed buffer grow was silently ignored, risking a
    heap out-of-bounds write on the next box write.
  • RTSP digest auth now validates the nonce and refreshes it per
    challenge
    , and session IDs / nonces use a secure random source instead
    of a predictable one.
  • msttf: bounds-check the TTF parser against corrupt or malicious font
    data (table directory, glyph offsets, contour/point counts).
  • SRT: fix a double-ADTS risk, validate the channel, batch TS packets
    into fewer syscalls.
  • hal/ingenic: drop truncated video/JPEG frames instead of publishing
    corrupt data.

Protocol correctness

  • vparam: fix truncated parameter sets, add the missing avcC High-profile
    extension bytes, fix hvcC array_completeness.
  • RTP/RTCP: fix several packetization bugs - FU fragmentation MTU
    accounting, G.711 marker bit semantics, H.264/H.265 NAL-per-AU cap, RTCP SR
    timestamp mapping.
  • RTSP: add RTP-Info to the PLAY response, correctly reassemble the
    interleaved control stream, send RTCP from its own socket instead of the
    RTP one.
  • /control POST bodies over the size limit are now rejected, not
    silently truncated.
  • Config file writes are now fsync'd (file + directory entry) for
    durability across a power loss.

New

  • Live config-sync: setting changes now push to any open WebUI tab or
    client via the /events config SSE stream, instead of only applying on
    next reload.
  • OSD anti-aliasing quality is now configurable (osd.supersample,
    default 2 - was hardcoded to 4, same visual result at lower CPU cost for
    typical OSD text sizes).
  • Live preview now works on iOS Safari (uses ManagedMediaSource where
    MediaSource isn't available).
  • Per-stream JPEG enabled by default.

Reliability

  • Pre-roll recording ring is now capped by bytes, not just count/time.
  • HTTP request parsing accumulates headers instead of trusting a single
    recv().
  • The /control JSON response buffer moved off the stack to the heap, as
    did the hal_sim per-thread AU test buffer.
  • hub_get_fps() now locks its read of mfps.

Full diff: v1.3.3...v1.3.4