Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
run: |
mkdir -p dist

# Create .deb package
# Create .deb package (also ships the systemd unit, ADR-0005)
fpm -s dir -t deb \
-n ceracoder \
-v "${VERSION}" \
Expand All @@ -139,7 +139,8 @@ jobs:
--depends "libgstreamer1.0-0" \
--depends "libgstreamer-plugins-base1.0-0" \
-p "dist/ceracoder_${VERSION}_${ARCH}.deb" \
build-output/usr/=/usr/
build-output/usr/=/usr/ \
packaging/systemd/ceracoder.service=/usr/lib/systemd/system/ceracoder.service

# Create .tar.gz archive
mkdir -p tarball/ceracoder-${VERSION}
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ ceracoder
# Test binaries
tests/test_balancer
tests/test_integration
tests/test_reconnect
tests/test_frame_liveness
tests/test_srt
tests/test_srt_integration
tests/test_srt_live_transmit
Expand All @@ -19,3 +21,6 @@ tests/test_srt_live_transmit
bindings/typescript/node_modules/
bindings/typescript/dist/
bindings/typescript/*.tsbuildinfo

# OpenCode agent state (local only)
.omo/
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ceracoder

Parent: [../AGENTS.md](../AGENTS.md)

## ROLE IN THE GROUP

GStreamer-based hardware video encoder. Reads a pipeline from file, streams output over SRT with dynamic bitrate control. Fork of [irlserver/belacoder](https://github.com/irlserver/belacoder) (itself from BELABOX/belacoder).

**Depends on:** `libsrt` (compile-time, `pkg-config srt`), GStreamer 1.0
**Consumed by:** CeraUI backend (TypeScript bindings at `bindings/typescript`), device image (packaged as `belacoder` .deb)

## STRUCTURE

```
ceracoder/
├── src/
│ ├── ceracoder.c # entry point
│ ├── core/ # bitrate control, balancers, config
│ ├── gst/ # encoder_control, overlay_ui
│ ├── io/ # cli_options, pipeline_loader
│ └── net/ # srt_client
├── camlink_workaround/ # submodule → BELABOX/camlink.git
├── bindings/typescript/ # TS bindings consumed by CeraUI backend
├── docs/ # architecture, bitrate-control, dependencies, versioning
├── tests/ # cmocka unit tests
└── Makefile # primary build entry
```

## BUILD

```bash
# Requires: libsrt >= 1.4.0, gstreamer-1.0, gstreamer-app-1.0, cmocka (tests)
make # builds ceracoder binary; inits camlink_workaround submodule
make test # runs cmocka tests
```

`VERSION` = `git rev-parse --short HEAD` (short SHA, not semver). Baked in at compile time via `-DVERSION`.

**pkg-config deps:** `gstreamer-1.0 gstreamer-app-1.0 srt` — all must be present before `make`.

## SUBMODULE

`camlink_workaround/` → `https://github.com/BELABOX/camlink.git`
Run `git submodule init && git submodule update` if missing. `make submodule` does this automatically.

## TYPESCRIPT BINDINGS

`bindings/typescript/` — consumed by CeraUI backend via `link:../../../ceracoder/bindings/typescript` in its `package.json`. Built with `bun`. Don't edit `dist/` directly; source is in `bindings/typescript/src/`.

## WHERE TO LOOK

| Task | Location |
|------|----------|
| Bitrate algorithm | `src/core/bitrate_control.c` + [docs/bitrate-control.md](docs/bitrate-control.md) |
| Architecture overview | [docs/architecture.md](docs/architecture.md) |
| Runtime dependencies | [docs/dependencies.md](docs/dependencies.md) |
| Versioning scheme | [docs/versioning.md](docs/versioning.md) |
| SRT send logic | `src/net/srt_client.c` |
| Pipeline loading | `src/io/pipeline_loader.c` |
| TS bindings source | `bindings/typescript/src/` |

## ANTI-PATTERNS

- Don't hardcode a semver string for VERSION — it's always the git short-SHA.
- Don't modify `camlink_workaround/` directly; it's a submodule.
- Don't duplicate docs content here — link to `docs/` instead.
- Don't run `make` without `libsrt >= 1.4.0` installed; pkg-config will fail silently on some systems.
21 changes: 17 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ TESTDIR = tests
OBJS = $(SRCDIR)/ceracoder.o \
$(SRCDIR)/io/cli_options.o \
$(SRCDIR)/io/pipeline_loader.o \
$(SRCDIR)/io/sd_notify.o \
$(SRCDIR)/net/srt_client.o \
$(SRCDIR)/net/srt_reconnect.o \
$(SRCDIR)/gst/encoder_control.o \
$(SRCDIR)/gst/overlay_ui.o \
$(SRCDIR)/gst/frame_liveness.o \
$(SRCDIR)/core/balancer_runner.o \
$(SRCDIR)/core/bitrate_control.o \
$(SRCDIR)/core/config.o \
Expand All @@ -44,10 +47,10 @@ $(SRCDIR)/%.o: $(SRCDIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@

# Test targets
test: submodule test_balancer test_integration
test: submodule test_balancer test_integration test_reconnect test_frame_liveness

# Full test suite including SRT network tests
test_all: submodule test_balancer test_integration test_srt test_srt_live_transmit
test_all: submodule test_balancer test_integration test_reconnect test_frame_liveness test_srt test_srt_live_transmit

test_balancer: $(TESTDIR)/test_balancer.o $(TEST_OBJS)
$(CC) $(TEST_CFLAGS) $^ -o $(TESTDIR)/$@ $(TEST_LDFLAGS)
Expand All @@ -57,6 +60,16 @@ test_integration: $(TESTDIR)/test_integration.o $(TEST_OBJS)
$(CC) $(TEST_CFLAGS) $^ -o $(TESTDIR)/$@ $(TEST_LDFLAGS)
./$(TESTDIR)/$@

# Reconnect state machine tests (pure logic, no live SRT socket / GStreamer)
test_reconnect: $(TESTDIR)/test_reconnect.o $(SRCDIR)/net/srt_reconnect.o
$(CC) $(TEST_CFLAGS) $^ -o $(TESTDIR)/$@ $(TEST_LDFLAGS)
./$(TESTDIR)/$@

# Frame-production liveness tests (pure logic, injectable clock, no GStreamer/SRT)
test_frame_liveness: $(TESTDIR)/test_frame_liveness.o $(SRCDIR)/gst/frame_liveness.o
$(CC) $(TEST_CFLAGS) $^ -o $(TESTDIR)/$@ $(TEST_LDFLAGS)
./$(TESTDIR)/$@

# SRT integration tests (requires network, runs actual SRT connections)
test_srt: $(TESTDIR)/test_srt_integration.o $(SRCDIR)/net/srt_client.o
$(CC) $(TEST_CFLAGS) $^ -o $(TESTDIR)/$@ $(TEST_LDFLAGS) -lpthread
Expand All @@ -79,7 +92,7 @@ lint:
clean:
rm -f ceracoder \
$(SRCDIR)/*.o $(SRCDIR)/core/*.o $(SRCDIR)/io/*.o $(SRCDIR)/net/*.o $(SRCDIR)/gst/*.o \
$(TESTDIR)/*.o $(TESTDIR)/test_balancer $(TESTDIR)/test_integration $(TESTDIR)/test_srt $(TESTDIR)/test_srt_live_transmit camlink_workaround/*.o
$(TESTDIR)/*.o $(TESTDIR)/test_balancer $(TESTDIR)/test_integration $(TESTDIR)/test_reconnect $(TESTDIR)/test_frame_liveness $(TESTDIR)/test_srt $(TESTDIR)/test_srt_live_transmit camlink_workaround/*.o

.PHONY: all submodule clean test test_all test_balancer test_integration test_srt test_srt_live_transmit lint
.PHONY: all submodule clean test test_all test_balancer test_integration test_reconnect test_frame_liveness test_srt test_srt_live_transmit lint

38 changes: 38 additions & 0 deletions bindings/typescript/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ceracoder/bindings/typescript

Parent: [../../AGENTS.md](../../AGENTS.md)

## OVERVIEW

Package `@ceralive/ceracoder` — type-safe TypeScript bindings for the ceracoder C encoder. Zod v4 schemas, config/CLI builders, process helpers, and a `PipelineBuilder` for hardware-specific GStreamer launch strings.

**Consumer:** `CeraUI/apps/backend` via `"@ceralive/ceracoder": "link:../../../ceracoder/bindings/typescript"` in its `package.json`. Breaking the public API here breaks CeraUI backend.

## PUBLIC API SURFACE

| Export | Source | Purpose |
|--------|--------|---------|
| `ceracoderConfigSchema`, `CeracoderConfig` | `types.ts` | Zod schema + inferred type for ceracoder config |
| `buildCeracoderConfig`, `serializeCeracoderConfig` | `config.ts` | Config object builder + serializer |
| `buildCeracoderArgs` | `cli.ts` | CLI args array builder (always uses `-c <config>`, never legacy `-b`) |
| `spawnCeracoder`, `sendHup`, `sendTerm` | `process.ts` | Process lifecycle helpers |
| `writeConfig`, `writePipeline` | `process.ts` | Write config/pipeline files to disk |
| `PipelineBuilder` | `pipeline/` | Hardware-specific GStreamer launch string generator |
| Constants (`DEFAULT_*`) | `constants.ts` | Bitrate/latency defaults aligned with C implementation |

## WHERE TO LOOK

| Task | Location |
|------|----------|
| Add/change a config field | `src/types.ts` (schema) + `src/config.ts` (defaults) + `src/constants.ts` |
| Add a new pipeline hardware target | `src/pipeline/` — add hardware dir + update `PipelineBuilder` |
| Change CLI flags | `src/cli.ts` — check C source `src/io/cli_options.c` stays in sync |
| Build / type-check | `bun run build` or `bun run lint` |

## ANTI-PATTERNS

- Don't edit `dist/` directly — generated by `bun run build` from `src/`.
- Don't remove or rename exported symbols without updating `CeraUI/apps/backend` first.
- Don't add a `-b` bitrate flag to `buildCeracoderArgs` — legacy, removed intentionally.
- Don't publish to npm; this package is consumed only via `link:` from sibling checkout.
- Don't bump `version` in `package.json` independently — ceracoder uses git-SHA versioning.
47 changes: 47 additions & 0 deletions packaging/systemd/ceracoder.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[Unit]
Description=Ceracoder - SRT video encoder with dynamic bitrate (CeraLive)
Documentation=https://github.com/CERALIVE/ceracoder
# Bonding link layer must be up first; ceracoder sends SRT to the local srtla_send.
After=network-online.target srtla-send.service
Wants=network-online.target
# Crash-loop damping (ADR-0005): if ceracoder exits >= StartLimitBurst times
# within StartLimitIntervalSec, systemd stops respawning and enters the failed
# state instead of a tight restart loop. The streaming failure is then surfaced
# to the operator rather than hammering the encoder.
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
# Type=notify: ceracoder calls sd_notify(READY=1) once the pipeline is PLAYING
# and the SRT connection is established, so systemd only considers the unit
# "active" when the encoder is actually running.
Type=notify
NotifyAccess=main

# Per-stream arguments (pipeline file, SRT host/port, stream id, bitrate config)
# are written by the CeraUI control plane before it starts this unit. The "-"
# prefix makes the file optional so the unit still validates when absent.
EnvironmentFile=-/run/ceralive/ceracoder.env
ExecStart=/usr/bin/ceracoder $CERACODER_ARGS

# systemd is the SOLE process-restart authority (ADR-0005). ceracoder's
# in-process SRT reconnect absorbs transient link blips WITHOUT exiting; only a
# permanent failure exits non-zero and hands authority here.
Restart=on-failure
RestartSec=5

# Hardware-style watchdog: ceracoder pets WATCHDOG=1 from its GLib main loop.
# If the process hangs/zombies (main loop stuck, or - after Task 12 - frame
# production stalls), the ping stops, systemd kills the process and Restart=
# respawns it.
#
# WatchdogSec MUST exceed ceracoder's in-process SRT reconnect backoff cap
# (30s, Task 8) so a legitimate reconnect window is never mistaken for a hang.
# 90s = 30s cap + headroom.
WatchdogSec=90

KillMode=mixed
SyslogIdentifier=ceracoder

[Install]
WantedBy=multi-user.target
Loading
Loading