feat(framecache): phase 1 — framecache container + consumer library
- services/framecache/: new standalone container
- slot.h/slot.c: shm ring buffer (120 frames, FC_MAGIC header, atomic
write_cursor, POSIX semaphore per slot)
- registry.h/registry.c: in-memory slot registry + /dev/shm/framecache/
registry.json persistence
- framecache.c: HTTP API server (libmicrohttpd, port 7435)
POST /slots, GET /slots, GET /slots/:id, DELETE /slots/:id, GET /health
- fc_client.h/fc_client.c: consumer library — fc_consumer_open/read/close
with per-consumer cursor, timeout via sem_timedwait, automatic skip+count
when consumer falls behind writer by > ring_depth frames
- fc_test_consumer.c: dev utility to attach to any slot and print fps/stats
- CMakeLists.txt: framecache server + fc_client static lib + test consumer
- Dockerfile: builder + slim runtime stages
- docker-compose.worker.yml: add framecache service (profile: capture,
ipc: host, shm_size from FC_SHM_SIZE_GB env var, healthcheck)
- .env.example: document FC_SHM_SIZE_GB with per-node guidance
2026-06-03 10:53:51 -04:00
|
|
|
cmake_minimum_required(VERSION 3.16)
|
|
|
|
|
project(framecache C)
|
|
|
|
|
|
|
|
|
|
set(CMAKE_C_STANDARD 11)
|
|
|
|
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -O2")
|
|
|
|
|
|
|
|
|
|
# ── libmicrohttpd ────────────────────────────────────────────────────
|
|
|
|
|
find_library(MHD_LIB microhttpd REQUIRED)
|
|
|
|
|
find_path(MHD_INCLUDE microhttpd.h REQUIRED)
|
|
|
|
|
include_directories(${MHD_INCLUDE})
|
|
|
|
|
|
|
|
|
|
# ── framecache server ────────────────────────────────────────────────
|
|
|
|
|
add_executable(framecache
|
|
|
|
|
src/framecache.c
|
|
|
|
|
src/slot.c
|
|
|
|
|
src/registry.c
|
|
|
|
|
)
|
|
|
|
|
target_link_libraries(framecache ${MHD_LIB} rt pthread)
|
|
|
|
|
|
|
|
|
|
# ── fc_client static library (used by bridges + test) ───────────────
|
|
|
|
|
add_library(fc_client STATIC
|
|
|
|
|
client/fc_client.c
|
|
|
|
|
src/slot.c # client needs fc_slot_shm_size / fc_frame_at
|
|
|
|
|
)
|
|
|
|
|
target_include_directories(fc_client PUBLIC src client)
|
|
|
|
|
target_link_libraries(fc_client rt pthread)
|
|
|
|
|
|
feat(framecache): phase 5 — network ingest (RTMP/SRT) via framecache
- services/framecache/src/net_ingest.c: new network ingest process
- Spawns ffmpeg to decode SRT/RTMP → raw UYVY422 stdout
- Reads decoded frames and writes into framecache slot via shm
- Registers slot with framecache HTTP API on startup
- Deregisters slot on clean exit (SIGTERM)
- Reconnect loop for listener mode (stays alive between sessions)
- --url, --slot-id, --fc-url, --width, --height, --fps-num/den,
--source-type, --listen, --listen-port, --stream-key args
- Emits format JSON to stderr on first frame
- services/framecache/CMakeLists.txt: add net_ingest target
- services/framecache/Dockerfile: copy net_ingest to runtime image
- services/node-agent/index.js:
- startNetIngest() / stopNetIngest(): lifecycle management per recorder
- Spawns net_ingest before sidecar start for srt/rtmp sourceTypes
- Injects FC_SLOT_ID=net-<containerId> into sidecar env
- Sets IpcMode=host for network sidecars using framecache
- Maps temp id → real containerId after container create
- stopNetIngest() called on sidecar stop
- NET_INGEST_BIN env var (default: docker exec framecache net_ingest)
- services/capture/src/capture-manager.js:
- _buildInputArgs srt/rtmp: framecache path when FC_SLOT_ID set
(spawns fc_pipe, uses pipe:0 rawvideo input — same as SDI path)
- Falls back to direct URL when FC_SLOT_ID not set (legacy path)
- audioMap: network via framecache uses '0:a:0?' (video-only fc_pipe,
no audio FIFO — audio-in-shm is roadmap)
- HLS tee: sdiHlsDir covers network-via-framecache; legacy tee gated
on !FC_SLOT_ID to avoid duplicate HLS outputs
- fc_pipe piped to ffmpeg stdin for network framecache path
- docker-compose.worker.yml: FC_URL + NET_INGEST_BIN in node-agent env
2026-06-03 11:37:17 -04:00
|
|
|
# ── net_ingest — network source (RTMP/SRT) → framecache slot ─────────
|
|
|
|
|
# Spawned by node-agent when a network recorder starts.
|
|
|
|
|
# Decodes the network stream to raw UYVY422 via ffmpeg and writes frames
|
|
|
|
|
# into a framecache slot, giving capture-manager the same fc_pipe consumer
|
|
|
|
|
# interface as SDI sources.
|
|
|
|
|
add_executable(net_ingest
|
|
|
|
|
src/net_ingest.c
|
|
|
|
|
src/slot.c
|
|
|
|
|
)
|
|
|
|
|
target_include_directories(net_ingest PRIVATE src)
|
|
|
|
|
target_link_libraries(net_ingest rt pthread)
|
|
|
|
|
install(TARGETS net_ingest DESTINATION bin)
|
|
|
|
|
|
2026-06-03 11:32:40 -04:00
|
|
|
# ── fc_pipe — slot → stdout adapter (used by capture-manager.js) ─────
|
|
|
|
|
# Spawned by capture-manager as a child process; writes raw UYVY422
|
|
|
|
|
# frames from a framecache slot to stdout so ffmpeg reads them as
|
|
|
|
|
# rawvideo pipe input. Multiple fc_pipe instances on the same slot
|
|
|
|
|
# each get an independent cursor — zero-copy fan-out.
|
|
|
|
|
add_executable(fc_pipe
|
|
|
|
|
client/fc_pipe.c
|
|
|
|
|
)
|
|
|
|
|
target_link_libraries(fc_pipe fc_client)
|
|
|
|
|
target_include_directories(fc_pipe PRIVATE src client)
|
|
|
|
|
|
feat(framecache): phase 1 — framecache container + consumer library
- services/framecache/: new standalone container
- slot.h/slot.c: shm ring buffer (120 frames, FC_MAGIC header, atomic
write_cursor, POSIX semaphore per slot)
- registry.h/registry.c: in-memory slot registry + /dev/shm/framecache/
registry.json persistence
- framecache.c: HTTP API server (libmicrohttpd, port 7435)
POST /slots, GET /slots, GET /slots/:id, DELETE /slots/:id, GET /health
- fc_client.h/fc_client.c: consumer library — fc_consumer_open/read/close
with per-consumer cursor, timeout via sem_timedwait, automatic skip+count
when consumer falls behind writer by > ring_depth frames
- fc_test_consumer.c: dev utility to attach to any slot and print fps/stats
- CMakeLists.txt: framecache server + fc_client static lib + test consumer
- Dockerfile: builder + slim runtime stages
- docker-compose.worker.yml: add framecache service (profile: capture,
ipc: host, shm_size from FC_SHM_SIZE_GB env var, healthcheck)
- .env.example: document FC_SHM_SIZE_GB with per-node guidance
2026-06-03 10:53:51 -04:00
|
|
|
# ── test consumer (dev utility) ──────────────────────────────────────
|
|
|
|
|
if(BUILD_TESTS)
|
|
|
|
|
add_executable(fc_test_consumer
|
|
|
|
|
client/fc_test_consumer.c
|
|
|
|
|
)
|
|
|
|
|
target_link_libraries(fc_test_consumer fc_client)
|
|
|
|
|
target_include_directories(fc_test_consumer PRIVATE src client)
|
|
|
|
|
endif()
|
|
|
|
|
|
2026-06-03 11:32:40 -04:00
|
|
|
install(TARGETS framecache fc_pipe DESTINATION bin)
|
feat(framecache): phase 1 — framecache container + consumer library
- services/framecache/: new standalone container
- slot.h/slot.c: shm ring buffer (120 frames, FC_MAGIC header, atomic
write_cursor, POSIX semaphore per slot)
- registry.h/registry.c: in-memory slot registry + /dev/shm/framecache/
registry.json persistence
- framecache.c: HTTP API server (libmicrohttpd, port 7435)
POST /slots, GET /slots, GET /slots/:id, DELETE /slots/:id, GET /health
- fc_client.h/fc_client.c: consumer library — fc_consumer_open/read/close
with per-consumer cursor, timeout via sem_timedwait, automatic skip+count
when consumer falls behind writer by > ring_depth frames
- fc_test_consumer.c: dev utility to attach to any slot and print fps/stats
- CMakeLists.txt: framecache server + fc_client static lib + test consumer
- Dockerfile: builder + slim runtime stages
- docker-compose.worker.yml: add framecache service (profile: capture,
ipc: host, shm_size from FC_SHM_SIZE_GB env var, healthcheck)
- .env.example: document FC_SHM_SIZE_GB with per-node guidance
2026-06-03 10:53:51 -04:00
|
|
|
install(FILES client/fc_client.h src/slot.h DESTINATION include/framecache)
|
|
|
|
|
install(TARGETS fc_client DESTINATION lib)
|