Releases: Lu-Fi/timps
Release list
v1.6.1
[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 byMS_AU_BUF_MAX) to fit the real frame instead of
truncating it; theIMP_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_idrpath 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
[1.6.0] - 2026-07-27
Added
- Native speaker output (
IMP_AO) — timps now owns the camera speaker
directly, no more/bin/iac. Newsrc/rtsp/speaker.cis the sole
IMP_AOowner 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_outtakingPLAY url=<path> [vol= gain= rate= format= loop= delay=]/STOPlines — the same
protocol prudynt/raptor's/usr/sbin/playwrapper 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
newUSE_PLAY_OPUSlikeUSE_BC_AACgates the AAC backchannel), WAV,
raw PCM16 and G.711 µ/A-law. NewUSE_PLAY/USE_PLAY_OPUSbuild flags
(off by default;USE_BACKCHANNELno longer needs/bin/iacpresent at
all —bc_available()is always true once built in). New
hal_ao_open/write/close/set_vol/set_gainin the HAL mirror the
existingIMP_AIbring-up (rate-fallback loop, lazy open/close); a new
src/codec/resample.c(extracted frombackchannel.c) is shared by
both producers. - Live speaker volume/gain + WebUI-driven system-sound play.
audio.spk_volume/spk_gainwere parsed and persisted but never
actually reached the hardware before; now everyIMP_AOopen applies
them, andPOST /control {"audio":{"spk_volume":..,"spk_gain":..}}
writes through live (caps.audiogains the two keys, gated on an AO
pipeline being compiled in).GET /controlgains
caps.play={available,sounds:[...]}, enumerated from
/usr/share/sounds(.wavalways listed since the µ-law/PCM decoder
needs no library;.opusonly whenUSE_PLAY_OPUSis 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:timeforces 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).
sunforces by today's real sunrise/sunset for
sun_latitude/sun_longitudevia 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.sensorstays the default and its gain/brightness
branch is untouched;time/sunreuse the existing switch + minimum-
dwell machinery, anddaynight.enabled=0(manual) still suppresses
forcing in all three modes.GET /controlexposes 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)
nrVBsbuffer-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 explicitbuffers=in
timps.confis 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 inhal_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 — adrainflag now distinguishes the two, discarding
immediately only on stop/preempt/backchannel-takeover. (2) the drain
path's fixed sleep (oneMS_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=1clamp scoped toPLATFORM_T31only. The non-
scaled-channel buffer-count safety clamp infs_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
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) — seedocs/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'singenic-audiodaemon) — seedocs/backchannel.md.
Changed
- Default
http.portmoved 8080 → 8880 (was clashing with the ONVIF daemon). - Sub-stream OSD default
font_size24 → 12 px.
Fixed / Security
- Command-injection hardening:
daynight.switch_cmdandmotion.on_motionnow run viafork()+execlp()instead ofsystem()(no shell). - Value clamping:
audio.gaintightened to the IMP-documented 0..31 mic PGA range;volume/alc_gain/spk_volume/spk_gainand OSDlogo_w/logo_h/outlineclamped against their real limits. hal_get()return value NULL-checked before first use.- Plus the accumulated
/controltoken+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)
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-strongand-D_FORTIFY_SOURCE=2on libcs that support
them (host + thingino musl); the thingino uClibc toolchain is built
--disable-libsspwith incomplete_*_chkwrappers, so it correctly gets
neither (use--libc-muslfor 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) andFORTIFY=0(keep SSP, drop only FORTIFY).
Mirrored in bothMakefile(coversmake sim/ directmake 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
timps v1.4.5
Fixed
-
Crash (use-after-free in libaudioProcess.so) on live AGC/NS/HPF toggle — definitive fix.
Togglingaudio.agc,audio.nsoraudio.high_passvia/controlon a
running stream could still segfaulttimpsd(SIGSEGV,epc=0, fault in
libaudioProcess.so, return address inlibimp.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,
andIMP_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_dbare 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=0still means "single file", as
documented). Prevents an unbounded segment file filling the disk. - N2 — hvcC
numTemporalLayerscorrected 0 → 1 (H.265 metadata; strict
validators like Bento4/Apple HLS). - N4 — RTSP
interleaved/client_portvalues are range-checked and fall
back to defaults on out-of-range input (protocol robustness). - N6 — the
/controland 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 thecontrol_get_jsoncap==0 path).
Full changelog: v1.4.4...v1.4.5
v1.4.4
timps v1.4.4
Fixed
-
Crash (use-after-free in libimp) on a live audio-module toggle.
Changingaudio.agc,audio.nsoraudio.high_passvia/controlwhile a
stream was running could segfaulttimpsd— SIGSEGV with the fault inside
libimp.so(epc=0, return address in libimp). The/controlrequest
thread calledIMP_AI_Enable/DisableAgc/Ns/Hpfon the live AI channel while
the audio thread was insideIMP_AI_GetFrame— where libimp processes those
modules — freeing state the frame path was still using.The toggle is now deferred:
/controlonly 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 onvifor--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 reala=b c=dline is
still detected.
Full changelog: v1.4.3...v1.4.4
v1.3.6
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
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_SetChnParamandIMP_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
whereIMP_AI_EnableChnreports 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.
- All three calls are now return-checked; on failure the thread logs,
Full diff: v1.3.4...v1.3.5
v1.3.4
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_bufallocation 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 hvcCarray_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-Infoto thePLAYresponse, correctly reassemble the
interleaved control stream, send RTCP from its own socket instead of the
RTP one. /controlPOST 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/eventsconfigSSE 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
ManagedMediaSourcewhere
MediaSourceisn'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
/controlJSON 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 ofmfps.
Full diff: v1.3.3...v1.3.4