Files
proitlab 9ea6820a37 Preserve STATE_FILE after file-input recount; delete on start
File sources now keep STATE_FILE on graceful shutdown (EOF or SIGTERM):
the final batch snapshot is written instead of deleted, so the dashboard's
/api/current-batch still shows the recount result after the process exits.
On startup, STATE_FILE is cleared unless a live instance is detected via a
/proc cmdline scan (rtsp/http source in argv or config). Live sources keep
the old resume-at-start / delete-on-finish behavior.

SPEC: V19, T21, B2. AGENTS: STATE_FILE lifecycle section.
2026-08-12 15:05:48 +07:00

5.2 KiB

AGENTS.md — ByteTrack Counter C++

RKNN Dynamic Shape Requirement

The RKNN model (zenai_apc_cicalengka_20260609.rknn) is exported with dynamic input shapes (model inference type: dynamic_shape). The C API requires calling rknn_set_input_shapes() before rknn_inputs_set(). Without it, librknnrt.so crashes with:

std::out_of_range: vector::_M_range_check: __n (which is 18446744073709551615) >= this->size() (which is 1)

This is called in the constructor of RKNNYOLO at src/rknn_yolo.cpp:60, right after querying input attributes:

in_attr.dims[0] = 1;
in_attr.dims[1] = imgsz_;
in_attr.dims[2] = imgsz_;
in_attr.dims[3] = 3;
in_attr.n_dims = 4;
ret = rknn_set_input_shapes(rknn_ctx_, io_num.n_input, &in_attr);

Build

cd /opt/bytetrack-counter-cpp/build
cmake .. -DHAS_RKNN=ON
make -j$(nproc)

Service

systemctl restart bytetrack-counter
journalctl -u bytetrack-counter -f

Reset via .reset file

Place a .reset file in SHM_DIR (default /dev/shm/bytetrack-counter) to trigger a full state reset:

  • Startup: deleting .reset before service starts clears all state files on boot
  • Runtime: dropping .reset while service runs calls BatchStore::reset() — ends active batch, clears in-memory state, deletes STATE_FILE, clears live stream JPEG + writes blank frame on next write
touch /dev/shm/bytetrack-counter/.reset

STATE_FILE lifecycle (file sources)

For local file input, STATE_FILE persists the recount result so the dashboard's /api/current-batch still shows the final batch after the process exits:

  • Start: STATE_FILE is deleted unless a live instance is running — detected by a /proc/*/cmdline scan for another bytetrack-counter whose source is rtsp:///http:// (argv or SOURCE= in its config). If live is active, the file is kept and a [INFO] Live instance active — keeping STATE_FILE line is logged. Stop the live service before recounting so stale recount state is cleared.
  • Finish: on graceful shutdown (end_batch("shutdown") — EOF or SIGTERM) the final batch snapshot is written to STATE_FILE (in both the persist and the discard-below-minimum paths) instead of being deleted.
  • Live sources are unchanged: resume at start, delete on finish.

Shared memory

SHM_DIR (/dev/shm/bytetrack-counter by default) stores:

  • LIVE_STREAM_FRAME_PATH JPEG snapshot
  • RECORD_SHM_DIR video recordings before move to disk
  • .reset trigger file

Directory auto-created at startup.

Video recordings

  • Recordings are written to SHM_DIR as batch_pending_*.mp4, confirmed when ayam detected, and moved via fork() (non-blocking) to OUTPUT_DIR/<batch counting_date>/batch_<num>_<YYYYMMDD>_<HHMMSS>.mp4.
  • Live sources: recording speculates (SPECULATING) and is discarded after RECORD_DELAY_SECONDS if no ayam confirms; only CONFIRMED/STOPPING recordings are saved.
  • File sources (file_source_ flag): recording opens at frame 0, is never discarded by the SPECULATING timer, and any active recording is saved on batch close and on shutdown — so the output's first frame matches the source mp4's frame 0 (no RECORD_DELAY offset).
  • feed_frame is called every frame (outside the detections block), so recordings are continuous with no gaps on detection-less frames.
  • The day folder uses the batch counting date (today before DAILY_CUTOFF_TIME, tomorrow after), forwarded from BatchStore through the batch-closed callback — matching the SQLite counting_date.
  • Retention cleanup compares day folders with std::localtime to match folder naming.
  • H.264 encode quality is set via RECORD_CRF (lower = better, default unset) and RECORD_PRESET — start_recording() writes OPENCV_FFMPEG_WRITE_OPTIONS (crf;<n>|preset;<p>) before writer_.open().

Source override

bytetrack-counter <config.env> --source /tmp/test.mp4      # local file (single pass)
bytetrack-counter <config.env> --source rtsp://cam:554/x   # live stream
bytetrack-counter <config.env> -s path  # or --source=path

Capture setup (open_capture in src/main.cpp):

  • Live sources: CAP_PROP_BUFFERSIZE=1 is set (reconnect-agnostic).
  • File sources: CAP_PROP_POS_FRAMES=0 is set — the decoder is forced to frame 0. Never set CAP_PROP_BUFFERSIZE on a file: it makes OpenCV's FFMPEG build buffer land mid-file and the first decoded frame ≠ source frame 0 (e.g. the pause-on-first-frame view showed a ~11s-later camera clock).
  • First successful read logs one INFO line with pos_frames, pos_msec, frame_count, fps — pos_msec=0 ⇒ frame 0 is the true first frame.

Pause on first frame (file sources)

For local file input, hold the first frame as the live stream view until signalled:

  • Enable via --pause-on-first-frame (CLI, overrides env) or PAUSE_ON_FIRST_FRAME=true.
  • Frame 0 is fully processed (inference, tracking, HUD) then the loop idles: the annotated frame is re-written to LIVE_STREAM_FRAME_PATH while polling for SHM_DIR/.continue.
  • Resume by touch /dev/shm/bytetrack-counter/.continue (file is deleted on consume).
  • Ignored for live (rtsp/http) sources with a warning. A stale .continue is cleared at startup.