diff --git a/.gitignore b/.gitignore
index 8d6a1b2..2bdf654 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,9 @@
+# Compiled binary
belacoder
-belacoder.o
+
+# Object files
+*.o
+
+# Editor files
+*.swp
+*~
diff --git a/Makefile b/Makefile
index bd9ec82..61017d6 100644
--- a/Makefile
+++ b/Makefile
@@ -2,14 +2,33 @@ VERSION=$(shell git rev-parse --short HEAD)
CFLAGS=`pkg-config gstreamer-1.0 gstreamer-app-1.0 srt --cflags` -O2 -Wall -DVERSION=\"$(VERSION)\"
LDFLAGS=`pkg-config gstreamer-1.0 gstreamer-app-1.0 srt --libs` -ldl
+# Source directory
+SRCDIR = src
+
+# Object files
+OBJS = $(SRCDIR)/belacoder.o \
+ $(SRCDIR)/bitrate_control.o \
+ $(SRCDIR)/config.o \
+ $(SRCDIR)/balancer_adaptive.o \
+ $(SRCDIR)/balancer_fixed.o \
+ $(SRCDIR)/balancer_aimd.o \
+ $(SRCDIR)/balancer_registry.o \
+ camlink_workaround/camlink.o
+
all: submodule belacoder
submodule:
git submodule init
git submodule update
-belacoder: belacoder.o camlink_workaround/camlink.o
+belacoder: $(OBJS)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
+# Compile source files with includes from src/
+$(SRCDIR)/%.o: $(SRCDIR)/%.c
+ $(CC) $(CFLAGS) -I$(SRCDIR) -c $< -o $@
+
clean:
- rm -f belacoder *.o camlink_workaround/*.o
+ rm -f belacoder $(SRCDIR)/*.o camlink_workaround/*.o
+
+.PHONY: all submodule clean
diff --git a/belacoder.conf.example b/belacoder.conf.example
new file mode 100644
index 0000000..7a36d7d
--- /dev/null
+++ b/belacoder.conf.example
@@ -0,0 +1,52 @@
+# belacoder configuration file
+#
+# Reload while running with: kill -HUP $(pidof belacoder)
+
+[general]
+# Bitrate limits (Kbps)
+# Examples: 500 = 500 Kbps, 6000 = 6 Mbps, 12000 = 12 Mbps
+min_bitrate = 500
+max_bitrate = 6000
+
+# Balancer algorithm - controls how bitrate adapts to network conditions
+# Options:
+# adaptive - RTT and buffer-based control, reacts to congestion (default)
+# fixed - Constant bitrate, no adaptation (uses max_bitrate)
+# aimd - TCP-style Additive Increase Multiplicative Decrease
+balancer = adaptive
+
+[srt]
+# SRT latency buffer (milliseconds)
+# Higher = more resilient to packet loss, but adds delay
+# Range: 100-10000, typical: 1500-3000 for mobile streaming
+latency = 2000
+
+# Note: stream_id is set via -s flag (not in config, rarely changes)
+
+# ============================================================================
+# ALGORITHM TUNING
+#
+# These sections customize each algorithm's behavior.
+# All values are optional - defaults are used if omitted.
+# ============================================================================
+
+[adaptive]
+# Bitrate adjustment steps (Kbps)
+incr_step = 30 # Increase step when stable (default: 30)
+decr_step = 100 # Decrease step on congestion (default: 100)
+
+# Timing (milliseconds)
+incr_interval = 500 # Minimum ms between increases (default: 500)
+decr_interval = 200 # Minimum ms between decreases (default: 200)
+
+# Note: loss_threshold is not yet configurable
+
+[aimd]
+# AIMD-specific tuning
+incr_step = 50 # Additive increase step (Kbps, default: 50)
+decr_mult = 0.75 # Multiplicative decrease (0.0-1.0, default: 0.75)
+ # 0.75 means reduce to 75% on congestion
+
+# Timing (milliseconds)
+incr_interval = 500 # Minimum ms between increases (default: 500)
+decr_interval = 200 # Minimum ms between decreases (default: 200)
diff --git a/docs/architecture.md b/docs/architecture.md
index 3fff593..1ce3bfb 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -15,18 +15,41 @@ The core value proposition is **adaptive bitrate control**: belacoder monitors S
```
belacoder/
-├── belacoder.c # Main application (single-file implementation)
-├── Makefile # Build system (links gstreamer + libsrt via pkg-config)
-├── Dockerfile # Container build (installs CERALIVE/srt fork)
-├── camlink_workaround/ # Git submodule for Elgato Cam Link quirks
-├── pipeline/ # GStreamer pipeline templates by platform
-│ ├── generic/ # Software encoding (x264)
-│ ├── jetson/ # NVIDIA Jetson hardware encoding (nvv4l2h265enc)
-│ ├── n100/ # Intel N100 hardware encoding
-│ └── rk3588/ # Rockchip RK3588 hardware encoding
-└── docs/ # Documentation (you are here)
+├── src/ # Source code
+│ ├── belacoder.c # Main application (GStreamer + SRT integration)
+│ ├── config.c/h # INI config file parser
+│ ├── balancer.h # Balancer algorithm interface
+│ ├── balancer_adaptive.c # Default adaptive algorithm (RTT/buffer-based)
+│ ├── balancer_fixed.c # Fixed bitrate (no adaptation)
+│ ├── balancer_aimd.c # AIMD algorithm (TCP-style)
+│ ├── balancer_registry.c # Algorithm registration and lookup
+│ └── bitrate_control.c/h # Adaptive algorithm internals (BitrateContext)
+├── camlink_workaround/ # Git submodule for Elgato Cam Link quirks
+├── pipeline/ # GStreamer pipeline templates by platform
+│ ├── generic/ # Software encoding (x264)
+│ ├── jetson/ # NVIDIA Jetson hardware encoding (nvv4l2h265enc)
+│ ├── n100/ # Intel N100 hardware encoding
+│ └── rk3588/ # Rockchip RK3588 hardware encoding
+├── docs/ # Documentation (you are here)
+├── Makefile # Build system
+├── Dockerfile # Container build (installs CERALIVE/srt fork)
+├── belacoder.conf.example # Example configuration file
+└── README.md
```
+## Module Overview
+
+| Module | Files | Responsibility |
+|--------|-------|----------------|
+| Main | `belacoder.c` | GStreamer pipeline, SRT connection, CLI parsing, main loop |
+| Config | `config.c/h` | INI config file parsing, runtime reload via SIGHUP |
+| Balancer Interface | `balancer.h` | Algorithm interface (`BalancerAlgorithm` struct) |
+| Balancer Registry | `balancer_registry.c` | Algorithm lookup by name |
+| Adaptive Algorithm | `balancer_adaptive.c`, `bitrate_control.c/h` | RTT/buffer-based adaptive control (default) |
+| Fixed Algorithm | `balancer_fixed.c` | Constant bitrate, no adaptation |
+| AIMD Algorithm | `balancer_aimd.c` | TCP-style congestion control |
+| Camlink Workaround | `camlink_workaround/` | USB quirks for Elgato Cam Link |
+
## Runtime Dataflow
```mermaid
@@ -90,7 +113,7 @@ flowchart TD
belacoder uses async-signal-safe signal handling:
- **SIGTERM/SIGINT**: Handled via `g_unix_signal_add()` which safely integrates with the GLib main loop
-- **SIGHUP**: Uses a volatile flag (`reload_bitrate_flag`) that is checked in `stall_check()` to safely reload bitrate settings
+- **SIGHUP**: Uses a volatile flag (`reload_config_flag`) that is checked in `stall_check()` to safely reload config file or bitrate settings
- **SIGALRM**: Used as a fallback to force exit if the pipeline fails to stop gracefully
## Resource Management
@@ -106,12 +129,16 @@ All resources are properly cleaned up on exit:
| Component | Location | Responsibility |
|-----------|----------|----------------|
-| CLI parser | `main()` | Parse options, validate ranges |
-| Pipeline loader | `main()` | Read pipeline file, call `gst_parse_launch` |
-| SRT sender | `new_buf_cb()` | Chunk samples into SRT packets, call `srt_send` |
-| Bitrate controller | `update_bitrate()` | Adaptive bitrate based on RTT + send buffer |
-| Connection monitor | `connection_housekeeping()` | ACK timeout detection, stats polling |
-| Stall detector | `stall_check()` | Exit on pipeline stall |
+| CLI parser | `belacoder.c:main()` | Parse options, validate ranges |
+| Config loader | `config.c` | Parse INI config file, reload on SIGHUP |
+| Pipeline loader | `belacoder.c:main()` | Read pipeline file, call `gst_parse_launch` |
+| SRT sender | `belacoder.c:new_buf_cb()` | Chunk samples into SRT packets, call `srt_send` |
+| Balancer interface | `balancer.h:BalancerAlgorithm` | Pluggable algorithm interface (init/step/cleanup) |
+| Balancer registry | `balancer_registry.c` | Algorithm lookup by name, default selection |
+| Adaptive algorithm | `balancer_adaptive.c`, `bitrate_control.c` | RTT/buffer-based adaptive control |
+| AIMD algorithm | `balancer_aimd.c` | TCP-style congestion control |
+| Connection monitor | `belacoder.c:connection_housekeeping()` | ACK timeout detection, stats polling |
+| Stall detector | `belacoder.c:stall_check()` | Exit on pipeline stall, config reload |
## GStreamer ↔ SRT Boundary
diff --git a/docs/bitrate-control.md b/docs/bitrate-control.md
index 959b2e0..2863c53 100644
--- a/docs/bitrate-control.md
+++ b/docs/bitrate-control.md
@@ -1,6 +1,73 @@
-# Bitrate Control Algorithm
+# Bitrate Control
-This document describes the adaptive bitrate control algorithm implemented in belacoder. The algorithm monitors SRT connection quality and adjusts the video encoder's bitrate in real-time to match available network capacity.
+belacoder includes a pluggable bitrate control system that adjusts the video encoder's bitrate in real-time based on network conditions.
+
+## Available Algorithms
+
+| Algorithm | Description | Best For |
+|-----------|-------------|----------|
+| `adaptive` | RTT and buffer-based control (default) | General use, mobile streaming |
+| `fixed` | Constant bitrate, no adaptation | Testing, stable networks |
+| `aimd` | TCP-style AIMD (Additive Increase Multiplicative Decrease) | Fair bandwidth sharing |
+
+Select algorithm via CLI or config file:
+```bash
+# CLI
+./belacoder -a aimd pipeline.txt host 4000
+
+# Config file
+[general]
+balancer = adaptive
+```
+
+## Module Structure
+
+All source files are in the `src/` directory:
+
+| File | Purpose |
+|------|---------|
+| `src/balancer.h` | Algorithm interface (`BalancerAlgorithm` struct) |
+| `src/balancer_adaptive.c` | Default adaptive algorithm |
+| `src/balancer_fixed.c` | Fixed bitrate (no adaptation) |
+| `src/balancer_aimd.c` | AIMD algorithm |
+| `src/balancer_registry.c` | Algorithm registration and lookup |
+| `src/bitrate_control.h` | Adaptive algorithm internals (BitrateContext, constants) |
+| `src/bitrate_control.c` | Adaptive algorithm implementation |
+
+## Configuration
+
+All algorithms can be tuned via the config file:
+
+```ini
+[general]
+min_bitrate = 500 # Kbps (applies to all algorithms)
+max_bitrate = 6000 # Kbps
+
+[adaptive]
+incr_step = 30 # Increase step (Kbps)
+decr_step = 100 # Decrease step (Kbps)
+incr_interval = 500 # ms between increases
+decr_interval = 200 # ms between decreases
+
+[aimd]
+incr_step = 50 # Additive increase (Kbps)
+decr_mult = 0.75 # Multiplicative decrease (0.75 = reduce to 75%)
+```
+
+Reload config at runtime: `kill -HUP $(pidof belacoder)`
+
+---
+
+# Adaptive Algorithm Details
+
+The default `adaptive` algorithm monitors SRT connection quality and makes decisions based on:
+
+1. **RTT (Round-Trip Time)** from SRT statistics
+2. **Send buffer occupancy** from the SRT socket
+3. **Packet loss** detection
+4. **Throughput estimate** from SRT statistics
+
+The goal is to maximize video quality (high bitrate) while avoiding congestion.
## Overview
@@ -29,53 +96,78 @@ The goal is to maximize video quality (high bitrate) while avoiding congestion t
| `SRTO_SNDDATA` | Current send buffer occupancy (packets) |
| `SRTO_PEERLATENCY` | Negotiated latency with receiver |
-## State Variables
+## BitrateContext Structure
-The controller maintains several smoothed/derived values using exponential moving averages.
-Smoothing factors are defined as named constants for clarity:
-
-| Constant | Value | Purpose |
-|----------|-------|---------|
-| `EMA_SLOW` | 0.99 | Slow decay for averages and jitter |
-| `EMA_FAST` | 0.01 | Fast adaptation (complement of EMA_SLOW) |
-| `EMA_RTT_DELTA` | 0.8 | RTT delta smoothing |
-| `EMA_THROUGHPUT` | 0.97 | Throughput smoothing |
-| `RTT_MIN_DRIFT` | 1.001 | Per-sample drift rate for min RTT |
-| `RTT_INITIAL` | 300 | Initial prev_rtt value |
-| `RTT_MIN_INITIAL` | 200.0 | Initial rtt_min value |
-| `RTT_IGNORE_VALUE` | 100 | RTT value indicating no valid measurement |
-
-### RTT State
+All algorithm state is encapsulated in a `BitrateContext` struct (defined in `bitrate_control.h`):
```c
-static double rtt_avg = 0; // Rolling average RTT (EMA_SLOW * old + EMA_FAST * new)
-static double rtt_min = RTT_MIN_INITIAL; // Minimum observed RTT (slowly drifts up: *= RTT_MIN_DRIFT)
-static double rtt_jitter = 0; // Maximum recent RTT increase (decays: *= EMA_SLOW)
-static double rtt_avg_delta = 0; // Average RTT change rate (EMA_RTT_DELTA * old + 0.2 * new)
-static int prev_rtt = RTT_INITIAL; // Previous RTT reading
+typedef struct {
+ // Configuration (set once at init)
+ int min_bitrate;
+ int max_bitrate;
+ int srt_latency;
+ int srt_pkt_size;
+
+ // Current bitrate
+ int cur_bitrate;
+
+ // Buffer size tracking
+ double bs_avg; // Rolling average (EMA_SLOW * old + EMA_FAST * new)
+ double bs_jitter; // Maximum recent increase (decays: *= EMA_SLOW)
+ int prev_bs; // Previous reading
+
+ // RTT tracking
+ double rtt_avg; // Rolling average RTT
+ double rtt_min; // Minimum observed (slowly drifts up: *= RTT_MIN_DRIFT)
+ double rtt_jitter; // Maximum recent increase (decays: *= EMA_SLOW)
+ double rtt_avg_delta; // Average RTT change rate
+ int prev_rtt; // Previous reading
+
+ // Throughput tracking
+ double throughput; // Rolling average (converted to bps)
+
+ // Timing for rate limiting
+ uint64_t next_bitrate_incr; // Earliest time for next increase
+ uint64_t next_bitrate_decr; // Earliest time for next decrease
+} BitrateContext;
```
-### Send Buffer State
+### API Functions
```c
-static double bs_avg = 0; // Rolling average buffer size (EMA_SLOW * old + EMA_FAST * new)
-static double bs_jitter = 0; // Maximum recent buffer increase (decays: *= EMA_SLOW)
-static int prev_bs = 0; // Previous buffer reading
+// Initialize context with configuration and tuning parameters
+void bitrate_context_init(BitrateContext *ctx, int min_br, int max_br,
+ int latency, int pkt_size,
+ int incr_step, int decr_step,
+ int incr_interval, int decr_interval);
+
+// Update bitrate based on current SRT stats, returns new bitrate (rounded to 100 Kbps)
+int bitrate_update(BitrateContext *ctx, int buffer_size, double rtt,
+ double send_rate_mbps, uint64_t timestamp,
+ int64_t pkt_loss_total, int64_t pkt_retrans_total,
+ BitrateResult *result);
```
-### Throughput State
+Tuning parameters (pass 0 to use defaults):
+- `incr_step` - Bitrate increase step (bps, default: 30000)
+- `decr_step` - Bitrate decrease step (bps, default: 100000)
+- `incr_interval` - Min interval between increases (ms, default: 500)
+- `decr_interval` - Min interval between decreases (ms, default: 200)
-```c
-static double throughput = 0.0; // Rolling average throughput (EMA_THROUGHPUT * old + 0.03 * new)
- // Converted from Mbps to bps
-```
+## Smoothing Constants
-### Timing State
+Smoothing factors are defined as named constants in `bitrate_control.h`:
-```c
-static uint64_t next_bitrate_incr = 0; // Earliest time for next increase
-static uint64_t next_bitrate_decr = 0; // Earliest time for next decrease
-```
+| Constant | Value | Purpose |
+|----------|-------|---------|
+| `EMA_SLOW` | 0.99 | Slow decay for averages and jitter |
+| `EMA_FAST` | 0.01 | Fast adaptation (complement of EMA_SLOW) |
+| `EMA_RTT_DELTA` | 0.8 | RTT delta smoothing |
+| `EMA_THROUGHPUT` | 0.97 | Throughput smoothing |
+| `RTT_MIN_DRIFT` | 1.001 | Per-sample drift rate for min RTT |
+| `RTT_INITIAL` | 300 | Initial prev_rtt value |
+| `RTT_MIN_INITIAL` | 200.0 | Initial rtt_min value |
+| `RTT_IGNORE_VALUE` | 100 | RTT value indicating no valid measurement |
## Thresholds
@@ -277,13 +369,15 @@ flowchart TD
3. **Uses multiple signals**: Combines RTT and buffer occupancy for robustness
4. **Adaptive thresholds**: Thresholds adjust based on observed conditions
-## Limitations and Improvement Opportunities
+## Limitations and Future Improvements
-1. **Single algorithm**: No way to select alternative strategies at runtime
+1. ~~**Single algorithm**~~: ✅ Resolved - Multiple algorithms now available via `-a` flag
2. **Fixed smoothing factors**: May not adapt well to different network characteristics
3. **Latency coupling**: Thresholds tied to configured SRT latency (1/3, 1/5)
4. **No bandwidth probing**: Only increases when conditions are stable, no active probing
-5. **Magic numbers**: Many tuning constants that could benefit from configuration
+
+> **Note**: New algorithms can be added by implementing the `BalancerAlgorithm` interface
+> in `balancer.h` and registering in `balancer_registry.c`.
## ACK Timeout Detection
diff --git a/src/balancer.h b/src/balancer.h
new file mode 100644
index 0000000..c2fac87
--- /dev/null
+++ b/src/balancer.h
@@ -0,0 +1,112 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef BALANCER_H
+#define BALANCER_H
+
+#include
+
+/*
+ * Balancer configuration - passed to init()
+ */
+typedef struct {
+ int min_bitrate; // Minimum allowed bitrate (bps)
+ int max_bitrate; // Maximum allowed bitrate (bps)
+ int srt_latency; // Configured SRT latency (ms)
+ int srt_pkt_size; // SRT packet size (bytes)
+
+ // Adaptive algorithm tuning (bps for bitrate values, ms for intervals)
+ int adaptive_incr_step; // Bitrate increase step (bps, default: 30000)
+ int adaptive_decr_step; // Bitrate decrease step (bps, default: 100000)
+ int adaptive_incr_interval; // Min interval between increases (ms, default: 500)
+ int adaptive_decr_interval; // Min interval between decreases (ms, default: 200)
+
+ // AIMD algorithm tuning
+ int aimd_incr_step; // Additive increase (bps, default: 50000)
+ double aimd_decr_mult; // Multiplicative decrease (0.0-1.0, default: 0.75)
+ int aimd_incr_interval; // Min interval between increases (ms, default: 500)
+ int aimd_decr_interval; // Min interval between decreases (ms, default: 200)
+} BalancerConfig;
+
+/*
+ * Balancer input - passed to step() every update cycle
+ */
+typedef struct {
+ int buffer_size; // Current SRT send buffer size (packets)
+ double rtt; // Current round-trip time (ms)
+ double send_rate_mbps;// Current send rate (Mbps)
+ uint64_t timestamp; // Current timestamp (ms)
+ int64_t pkt_loss_total; // Total packets lost (cumulative)
+ int64_t pkt_retrans_total; // Total packets retransmitted (cumulative)
+} BalancerInput;
+
+/*
+ * Balancer output - returned from step()
+ */
+typedef struct {
+ int new_bitrate; // Computed bitrate (bps, rounded to 100 Kbps)
+ double throughput; // Smoothed throughput (for overlay)
+ int rtt; // Current RTT (for overlay)
+ int rtt_th_min; // RTT threshold min (for overlay)
+ int rtt_th_max; // RTT threshold max (for overlay)
+ int bs; // Current buffer size (for overlay)
+ int bs_th1; // Buffer threshold 1 (for overlay)
+ int bs_th2; // Buffer threshold 2 (for overlay)
+ int bs_th3; // Buffer threshold 3 (for overlay)
+} BalancerOutput;
+
+/*
+ * Balancer algorithm interface
+ *
+ * Each algorithm implements these three functions:
+ * - init: Allocate and initialize algorithm state
+ * - step: Compute new bitrate based on current network stats
+ * - cleanup: Free algorithm state
+ */
+typedef struct {
+ const char *name; // Algorithm name (e.g., "adaptive", "fixed", "aimd")
+ const char *description; // Human-readable description
+
+ // Initialize algorithm state, returns opaque state pointer
+ void* (*init)(const BalancerConfig *config);
+
+ // Compute new bitrate, returns output with bitrate and debug info
+ BalancerOutput (*step)(void *state, const BalancerInput *input);
+
+ // Clean up algorithm state
+ void (*cleanup)(void *state);
+} BalancerAlgorithm;
+
+/*
+ * Registry functions
+ */
+
+// Get the default algorithm (used when --balancer not specified)
+const BalancerAlgorithm* balancer_get_default(void);
+
+// Find algorithm by name, returns NULL if not found
+const BalancerAlgorithm* balancer_find(const char *name);
+
+// Get array of all registered algorithms (NULL-terminated)
+const BalancerAlgorithm* const* balancer_list_all(void);
+
+// Print list of available algorithms to stderr
+void balancer_print_available(void);
+
+#endif /* BALANCER_H */
diff --git a/src/balancer_adaptive.c b/src/balancer_adaptive.c
new file mode 100644
index 0000000..637e23b
--- /dev/null
+++ b/src/balancer_adaptive.c
@@ -0,0 +1,113 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ * Adaptive balancer - RTT and buffer-based bitrate control
+ *
+ * This is the default algorithm that adapts bitrate based on:
+ * - Round-trip time (RTT) measurements
+ * - SRT send buffer occupancy
+ * - Throughput estimation
+ *
+ * It uses multiple congestion detection thresholds to provide
+ * graduated responses from gentle decreases to emergency drops.
+ */
+
+#include "balancer.h"
+#include "bitrate_control.h"
+#include
+
+/*
+ * State structure - wraps BitrateContext
+ */
+typedef struct {
+ BitrateContext ctx;
+} AdaptiveState;
+
+/*
+ * Initialize the adaptive balancer
+ */
+static void* adaptive_init(const BalancerConfig *config) {
+ AdaptiveState *state = malloc(sizeof(AdaptiveState));
+ if (state == NULL) {
+ return NULL;
+ }
+
+ bitrate_context_init(&state->ctx,
+ config->min_bitrate,
+ config->max_bitrate,
+ config->srt_latency,
+ config->srt_pkt_size,
+ config->adaptive_incr_step,
+ config->adaptive_decr_step,
+ config->adaptive_incr_interval,
+ config->adaptive_decr_interval);
+
+ return state;
+}
+
+/*
+ * Compute new bitrate based on current network stats
+ */
+static BalancerOutput adaptive_step(void *state_ptr, const BalancerInput *input) {
+ AdaptiveState *state = (AdaptiveState *)state_ptr;
+ BalancerOutput output = {0};
+
+ // Use existing bitrate_update with a temporary BitrateResult
+ BitrateResult result;
+ int new_bitrate = bitrate_update(&state->ctx,
+ input->buffer_size,
+ input->rtt,
+ input->send_rate_mbps,
+ input->timestamp,
+ input->pkt_loss_total,
+ input->pkt_retrans_total,
+ &result);
+
+ // Convert BitrateResult to BalancerOutput
+ output.new_bitrate = new_bitrate;
+ output.throughput = result.throughput;
+ output.rtt = result.rtt;
+ output.rtt_th_min = result.rtt_th_min;
+ output.rtt_th_max = result.rtt_th_max;
+ output.bs = result.bs;
+ output.bs_th1 = result.bs_th1;
+ output.bs_th2 = result.bs_th2;
+ output.bs_th3 = result.bs_th3;
+
+ return output;
+}
+
+/*
+ * Clean up adaptive balancer state
+ */
+static void adaptive_cleanup(void *state_ptr) {
+ free(state_ptr);
+}
+
+/*
+ * Adaptive balancer algorithm definition
+ */
+const BalancerAlgorithm balancer_adaptive = {
+ .name = "adaptive",
+ .description = "RTT and buffer-based adaptive control (default)",
+ .init = adaptive_init,
+ .step = adaptive_step,
+ .cleanup = adaptive_cleanup,
+};
diff --git a/src/balancer_aimd.c b/src/balancer_aimd.c
new file mode 100644
index 0000000..5b672a0
--- /dev/null
+++ b/src/balancer_aimd.c
@@ -0,0 +1,181 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ * AIMD balancer - Additive Increase Multiplicative Decrease
+ *
+ * Classic TCP-style congestion control algorithm:
+ * - Increase bitrate linearly when conditions are good
+ * - Decrease bitrate by a fraction when congestion is detected
+ *
+ * This provides fair bandwidth sharing and stable convergence,
+ * but may be slower to adapt than the default adaptive algorithm.
+ */
+
+#include "balancer.h"
+#include
+#include // for MIN/MAX
+
+// Default AIMD parameters (used if config values are 0)
+#define AIMD_DEF_INCR_RATE (50 * 1000) // Additive increase: 50 Kbps per step
+#define AIMD_DEF_DECR_MULT 0.75 // Multiplicative decrease: reduce to 75%
+#define AIMD_DEF_INCR_INTERVAL 500 // ms between increases
+#define AIMD_DEF_DECR_INTERVAL 200 // ms between decreases
+
+// Congestion detection thresholds
+#define AIMD_RTT_MULT 1.5 // Congestion if RTT > baseline * 1.5
+#define AIMD_RTT_BASELINE_EMA 0.95 // Slow EMA for RTT baseline
+#define AIMD_BS_THRESHOLD 100 // Buffer size threshold (packets)
+
+/*
+ * State structure
+ */
+typedef struct {
+ int min_bitrate;
+ int max_bitrate;
+ int cur_bitrate;
+ int srt_latency;
+
+ // Tuning parameters (from config)
+ int incr_step;
+ double decr_mult;
+ int incr_interval;
+ int decr_interval;
+
+ // RTT baseline tracking
+ double rtt_baseline;
+
+ // Timing
+ uint64_t next_incr;
+ uint64_t next_decr;
+} AimdState;
+
+/*
+ * Initialize the AIMD balancer
+ */
+static void* aimd_init(const BalancerConfig *config) {
+ AimdState *state = malloc(sizeof(AimdState));
+ if (state == NULL) {
+ return NULL;
+ }
+
+ state->min_bitrate = config->min_bitrate;
+ state->max_bitrate = config->max_bitrate;
+ state->cur_bitrate = config->max_bitrate; // Start optimistic
+ state->srt_latency = config->srt_latency;
+
+ // Tuning parameters (use defaults if 0)
+ state->incr_step = (config->aimd_incr_step > 0) ?
+ config->aimd_incr_step : AIMD_DEF_INCR_RATE;
+ state->decr_mult = (config->aimd_decr_mult > 0.0) ?
+ config->aimd_decr_mult : AIMD_DEF_DECR_MULT;
+ state->incr_interval = (config->aimd_incr_interval > 0) ?
+ config->aimd_incr_interval : AIMD_DEF_INCR_INTERVAL;
+ state->decr_interval = (config->aimd_decr_interval > 0) ?
+ config->aimd_decr_interval : AIMD_DEF_DECR_INTERVAL;
+
+ state->rtt_baseline = 0.0;
+ state->next_incr = 0;
+ state->next_decr = 0;
+
+ return state;
+}
+
+/*
+ * Compute new bitrate using AIMD
+ */
+static BalancerOutput aimd_step(void *state_ptr, const BalancerInput *input) {
+ AimdState *state = (AimdState *)state_ptr;
+
+ // Update RTT baseline (slow moving average of minimum RTT)
+ if (state->rtt_baseline == 0.0) {
+ state->rtt_baseline = input->rtt;
+ } else if (input->rtt < state->rtt_baseline) {
+ // Quick adaptation downward
+ state->rtt_baseline = input->rtt;
+ } else {
+ // Slow drift upward
+ state->rtt_baseline = state->rtt_baseline * AIMD_RTT_BASELINE_EMA +
+ input->rtt * (1.0 - AIMD_RTT_BASELINE_EMA);
+ }
+
+ // Detect congestion
+ int congested = 0;
+ int rtt_threshold = (int)(state->rtt_baseline * AIMD_RTT_MULT);
+
+ // Emergency: RTT exceeds latency/3
+ if (input->rtt >= state->srt_latency / 3) {
+ state->cur_bitrate = state->min_bitrate;
+ state->next_decr = input->timestamp + state->decr_interval;
+ congested = 1;
+ }
+ // Congestion: RTT exceeds threshold or buffer too full
+ else if (input->rtt > rtt_threshold || input->buffer_size > AIMD_BS_THRESHOLD) {
+ congested = 1;
+ }
+
+ if (congested && input->timestamp > state->next_decr) {
+ // Multiplicative decrease
+ state->cur_bitrate = (int)(state->cur_bitrate * state->decr_mult);
+ state->next_decr = input->timestamp + state->decr_interval;
+
+ } else if (!congested && input->timestamp > state->next_incr) {
+ // Additive increase
+ state->cur_bitrate += state->incr_step;
+ state->next_incr = input->timestamp + state->incr_interval;
+ }
+
+ // Clamp to valid range
+ state->cur_bitrate = MAX(state->min_bitrate, MIN(state->max_bitrate, state->cur_bitrate));
+
+ // Round to 100 kbps
+ int rounded_br = state->cur_bitrate / (100 * 1000) * (100 * 1000);
+
+ BalancerOutput output = {
+ .new_bitrate = rounded_br,
+ .throughput = 0, // Not tracked in AIMD
+ .rtt = (int)input->rtt,
+ .rtt_th_min = (int)state->rtt_baseline,
+ .rtt_th_max = rtt_threshold,
+ .bs = input->buffer_size,
+ .bs_th1 = AIMD_BS_THRESHOLD,
+ .bs_th2 = AIMD_BS_THRESHOLD,
+ .bs_th3 = AIMD_BS_THRESHOLD
+ };
+
+ return output;
+}
+
+/*
+ * Clean up AIMD balancer state
+ */
+static void aimd_cleanup(void *state_ptr) {
+ free(state_ptr);
+}
+
+/*
+ * AIMD balancer algorithm definition
+ */
+const BalancerAlgorithm balancer_aimd = {
+ .name = "aimd",
+ .description = "Additive Increase Multiplicative Decrease (TCP-style)",
+ .init = aimd_init,
+ .step = aimd_step,
+ .cleanup = aimd_cleanup,
+};
diff --git a/src/balancer_fixed.c b/src/balancer_fixed.c
new file mode 100644
index 0000000..0221067
--- /dev/null
+++ b/src/balancer_fixed.c
@@ -0,0 +1,96 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ * Fixed balancer - maintains constant bitrate
+ *
+ * This algorithm simply outputs the configured max_bitrate without
+ * any adaptation. Useful for:
+ * - Testing and debugging
+ * - Stable network connections where adaptation isn't needed
+ * - Comparing against adaptive algorithms
+ */
+
+#include "balancer.h"
+#include
+
+/*
+ * State structure
+ */
+typedef struct {
+ int fixed_bitrate; // The constant bitrate to output
+} FixedState;
+
+/*
+ * Initialize the fixed balancer
+ */
+static void* fixed_init(const BalancerConfig *config) {
+ FixedState *state = malloc(sizeof(FixedState));
+ if (state == NULL) {
+ return NULL;
+ }
+
+ // Use max_bitrate as the fixed output
+ state->fixed_bitrate = config->max_bitrate;
+
+ // Round to 100 kbps
+ state->fixed_bitrate = state->fixed_bitrate / (100 * 1000) * (100 * 1000);
+
+ return state;
+}
+
+/*
+ * Always return the fixed bitrate
+ */
+static BalancerOutput fixed_step(void *state_ptr, const BalancerInput *input) {
+ FixedState *state = (FixedState *)state_ptr;
+ (void)input; // Unused - we ignore network conditions
+
+ BalancerOutput output = {
+ .new_bitrate = state->fixed_bitrate,
+ .throughput = 0, // No tracking
+ .rtt = (int)input->rtt,
+ .rtt_th_min = 0,
+ .rtt_th_max = 0,
+ .bs = input->buffer_size,
+ .bs_th1 = 0,
+ .bs_th2 = 0,
+ .bs_th3 = 0
+ };
+
+ return output;
+}
+
+/*
+ * Clean up fixed balancer state
+ */
+static void fixed_cleanup(void *state_ptr) {
+ free(state_ptr);
+}
+
+/*
+ * Fixed balancer algorithm definition
+ */
+const BalancerAlgorithm balancer_fixed = {
+ .name = "fixed",
+ .description = "Constant bitrate, no adaptation",
+ .init = fixed_init,
+ .step = fixed_step,
+ .cleanup = fixed_cleanup,
+};
diff --git a/src/balancer_registry.c b/src/balancer_registry.c
new file mode 100644
index 0000000..80ebef1
--- /dev/null
+++ b/src/balancer_registry.c
@@ -0,0 +1,87 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ * Balancer registry - manages available algorithms
+ */
+
+#include "balancer.h"
+#include
+#include
+
+/*
+ * External algorithm definitions
+ */
+extern const BalancerAlgorithm balancer_adaptive;
+extern const BalancerAlgorithm balancer_fixed;
+extern const BalancerAlgorithm balancer_aimd;
+
+/*
+ * Registry of all available algorithms
+ * First entry is the default
+ */
+static const BalancerAlgorithm* const algorithms[] = {
+ &balancer_adaptive,
+ &balancer_fixed,
+ &balancer_aimd,
+ NULL // Sentinel
+};
+
+/*
+ * Get the default algorithm (first in registry)
+ */
+const BalancerAlgorithm* balancer_get_default(void) {
+ return algorithms[0];
+}
+
+/*
+ * Find algorithm by name
+ */
+const BalancerAlgorithm* balancer_find(const char *name) {
+ if (name == NULL) {
+ return NULL;
+ }
+
+ for (int i = 0; algorithms[i] != NULL; i++) {
+ if (strcmp(algorithms[i]->name, name) == 0) {
+ return algorithms[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Get array of all registered algorithms
+ */
+const BalancerAlgorithm* const* balancer_list_all(void) {
+ return algorithms;
+}
+
+/*
+ * Print list of available algorithms to stderr
+ */
+void balancer_print_available(void) {
+ fprintf(stderr, "Available balancer algorithms:\n");
+ for (int i = 0; algorithms[i] != NULL; i++) {
+ fprintf(stderr, " %-12s - %s\n",
+ algorithms[i]->name,
+ algorithms[i]->description);
+ }
+}
diff --git a/belacoder.c b/src/belacoder.c
similarity index 75%
rename from belacoder.c
rename to src/belacoder.c
index 941b41d..7ba6ca5 100644
--- a/belacoder.c
+++ b/src/belacoder.c
@@ -1,6 +1,7 @@
/*
belacoder - live video encoder with dynamic bitrate control
Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -32,6 +33,10 @@
#include
#include
+#include "bitrate_control.h"
+#include "balancer.h"
+#include "config.h"
+
// Ensure SRT version is at least 1.4.0 (required for SRTO_RETRANSMITALGO)
#ifndef SRT_VERSION_VALUE
#define SRT_VERSION_VALUE SRT_MAKE_VERSION_VALUE(SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH)
@@ -40,50 +45,11 @@
#error "SRT 1.4.0 or later required (for SRTO_RETRANSMITALGO)"
#endif
+// SRT configuration
#define SRT_MAX_OHEAD 20 // maximum SRT transmission overhead (when using appsink)
#define SRT_ACK_TIMEOUT 6000 // maximum interval between received ACKs before the connection is TOed
-#define MIN_BITRATE (300 * 1000)
-#define ABS_MAX_BITRATE (30 * 1000 * 1000)
-#define DEF_BITRATE (6 * 1000 * 1000)
-
-#define BITRATE_UPDATE_INT 20
-#define BITRATE_INCR_MIN (30*1000) // the minimum bitrate increment step (bps)
-#define BITRATE_INCR_INT 500 // the minimum interval for increasing the bitrate (ms)
-#define BITRATE_INCR_SCALE 30 // the bitrate is increased by
- // BITRATE_INCR_MIN + cur_bitrate/BITRATE_INCR_SCALE
-
-#define BITRATE_DECR_MIN (100*1000) // the minimum value to decrease the bitrate by (bps)
-#define BITRATE_DECR_INT 200 // (light congestion) min interval for decreasing the bitrate (ms)
-#define BITRATE_DECR_FAST_INT 250 // (heavy congestion) min interval for decreasing the bitrate (ms)
-#define BITRATE_DECR_SCALE 10 // under heavy congestion, the bitrate is decreased by
- // BITRATE_DECR_MIN + cur_bitrate/BITRATE_DECR_SCALE
-
-// Exponential moving average smoothing factors
-#define EMA_SLOW 0.99 // for bs_avg, rtt_avg, jitter decay
-#define EMA_FAST 0.01 // complement of EMA_SLOW (1 - 0.99)
-#define EMA_RTT_DELTA 0.8 // for rtt_avg_delta smoothing
-#define EMA_RTT_DELTA_NEW 0.2 // complement (1 - 0.8)
-#define EMA_THROUGHPUT 0.97 // for throughput smoothing
-#define EMA_THROUGHPUT_NEW 0.03 // complement (1 - 0.97)
-
-// RTT tracking constants
-#define RTT_MIN_DRIFT 1.001 // per-sample drift rate for min RTT tracking
-#define RTT_IGNORE_VALUE 100 // RTT value that indicates no valid measurement
-#define RTT_INITIAL 300 // initial prev_rtt value
-#define RTT_MIN_INITIAL 200.0 // initial rtt_min value
-
-// Threshold multipliers for congestion detection
-#define BS_TH3_MULT 4 // heavy congestion: (bs_avg + bs_jitter) * 4
-#define BS_TH2_JITTER_MULT 3.0 // medium congestion jitter multiplier
-#define BS_TH1_JITTER_MULT 2.5 // light congestion jitter multiplier
-#define BS_TH_MIN 50 // minimum buffer threshold
-#define RTT_JITTER_MULT 4 // rtt_th_max jitter multiplier
-#define RTT_AVG_PERCENT 15 // rtt_th_max percentage of average (15%)
-#define RTT_STABLE_DELTA 0.01 // max rtt_avg_delta for stable conditions
-#define RTT_MIN_JITTER 1 // minimum jitter for rtt_th_min calculation
-
-// settings ranges
+// Settings ranges
#define TS_PKT_SIZE 188
#define REDUCED_SRT_PKT_SIZE ((TS_PKT_SIZE)*6)
#define DEFAULT_SRT_PKT_SIZE ((TS_PKT_SIZE)*7)
@@ -111,17 +77,25 @@ SRTSOCKET sock = -1;
int quit = 0;
// Signal flag for async-signal-safe SIGHUP handling
-volatile sig_atomic_t reload_bitrate_flag = 0;
+volatile sig_atomic_t reload_config_flag = 0;
int enc_bitrate_div = 1;
int av_delay = 0;
-int min_bitrate = MIN_BITRATE;
-int max_bitrate = DEF_BITRATE;
-int cur_bitrate = MIN_BITRATE;
+// Balancer algorithm and state
+const BalancerAlgorithm *balancer_algo = NULL;
+void *balancer_state = NULL;
+BalancerConfig balancer_config;
+int min_bitrate = MIN_BITRATE; // Keep for read_bitrate_file compatibility
+int max_bitrate = DEF_BITRATE; // Keep for read_bitrate_file compatibility
char *bitrate_filename = NULL;
+char *config_filename = NULL; // CLI option for -c
+char *balancer_name = NULL; // CLI option for -a (can override config)
+
+// Global configuration
+BelacoderConfig g_config;
int srt_latency = DEF_SRT_LATENCY;
int srt_pkt_size = DEFAULT_SRT_PKT_SIZE;
@@ -179,7 +153,7 @@ int read_bitrate_file(void);
// Async-signal-safe handler for SIGHUP - just sets a flag
void sighup_handler(int sig) {
(void)sig;
- reload_bitrate_flag = 1;
+ reload_config_flag = 1;
}
// GLib signal handler for SIGTERM/SIGINT (called from main loop, not signal context)
@@ -203,11 +177,32 @@ gboolean stall_check(gpointer data) {
return TRUE;
}
- // Check for SIGHUP-triggered bitrate reload (async-signal-safe approach)
- if (reload_bitrate_flag) {
- reload_bitrate_flag = 0;
- if (bitrate_filename) {
+ // Check for SIGHUP-triggered config reload (async-signal-safe approach)
+ if (reload_config_flag) {
+ reload_config_flag = 0;
+ int reloaded = 0;
+
+ // Reload config file if specified
+ if (config_filename != NULL) {
+ if (config_load(&g_config, config_filename) == 0) {
+ min_bitrate = config_bitrate_bps(g_config.min_bitrate);
+ max_bitrate = config_bitrate_bps(g_config.max_bitrate);
+ // Update balancer config
+ balancer_config.min_bitrate = min_bitrate;
+ balancer_config.max_bitrate = max_bitrate;
+ fprintf(stderr, "Config reloaded: %d - %d Kbps\n",
+ min_bitrate / 1000, max_bitrate / 1000);
+ reloaded = 1;
+ } else {
+ fprintf(stderr, "Failed to reload config file: %s\n", config_filename);
+ }
+ }
+
+ // Also reload legacy bitrate file if specified
+ if (bitrate_filename && !reloaded) {
read_bitrate_file();
+ balancer_config.min_bitrate = min_bitrate;
+ balancer_config.max_bitrate = max_bitrate;
}
}
@@ -265,6 +260,14 @@ int read_bitrate_file() {
fclose(f);
min_bitrate = br[0];
max_bitrate = br[1];
+ // Update balancer config and reinitialize if needed
+ balancer_config.min_bitrate = min_bitrate;
+ balancer_config.max_bitrate = max_bitrate;
+ if (balancer_algo != NULL && balancer_state != NULL) {
+ // Reinitialize algorithm with new config (loses accumulated state)
+ balancer_algo->cleanup(balancer_state);
+ balancer_state = balancer_algo->init(&balancer_config);
+ }
return 0;
ret_err:
@@ -273,128 +276,37 @@ int read_bitrate_file() {
return -2;
}
-#define RTT_TO_BS(rtt) ((throughput / 8) * (rtt) / srt_pkt_size)
-void update_bitrate(SRT_TRACEBSTATS *stats, uint64_t ctime) {
- /*
- * Send buffer size stats
- */
+void do_bitrate_update(SRT_TRACEBSTATS *stats, uint64_t ctime) {
+ // Get send buffer size from SRT
int bs = -1;
int sz = sizeof(bs);
int ret = srt_getsockflag(sock, SRTO_SNDDATA, &bs, &sz);
if (ret != 0 || bs < 0) return;
- // Rolling average
- static double bs_avg = 0;
- bs_avg = bs_avg * EMA_SLOW + (double)bs * EMA_FAST;
-
- // Update the buffer size jitter
- static double bs_jitter = 0;
- static int prev_bs = 0;
- bs_jitter = EMA_SLOW * bs_jitter;
- int delta_bs = bs - prev_bs;
- if (delta_bs > bs_jitter) {
- bs_jitter = (double)delta_bs;
- }
- prev_bs = bs;
-
-
- /*
- * RTT stats
- */
- int rtt = (int)stats->msRTT;
-
- // Update the average RTT
- static double rtt_avg = 0;
- if (rtt_avg == 0.0) {
- rtt_avg = (double)rtt;
- } else {
- rtt_avg = rtt_avg * EMA_SLOW + EMA_FAST * (double)rtt;
- }
-
- // Update the average RTT delta
- static double rtt_avg_delta = 0;
- static int prev_rtt = RTT_INITIAL;
- double delta_rtt = (double)(rtt - prev_rtt);
- rtt_avg_delta = rtt_avg_delta * EMA_RTT_DELTA + delta_rtt * EMA_RTT_DELTA_NEW;
- prev_rtt = rtt;
-
- // Update the minimum RTT
- static double rtt_min = RTT_MIN_INITIAL;
- rtt_min *= RTT_MIN_DRIFT;
- if (rtt != RTT_IGNORE_VALUE && rtt < rtt_min && rtt_avg_delta < 1.0) {
- rtt_min = rtt;
- }
-
- // Update the RTT jitter
- static double rtt_jitter = 0;
- rtt_jitter *= EMA_SLOW;
- if (delta_rtt > rtt_jitter) {
- rtt_jitter = delta_rtt;
- }
-
-
- /*
- * Rolling average of the network throughput
- */
- static double throughput = 0.0;
- throughput *= EMA_THROUGHPUT;
- throughput += ((double)stats->mbpsSendRate * 1000.0 * 1000.0 / 1024.0) * EMA_THROUGHPUT_NEW;
-
-
- debug("bs: %d bs_avg: %f, bs_jitter %f, bitrate %d rtt %d, delta rtt %.0f, avg delta %.1f, avg rtt %.1f, rtt_jitter, %.2f, rtt_min %.1f\n",
- bs, bs_avg, bs_jitter, cur_bitrate, rtt, delta_rtt, rtt_avg_delta, rtt_avg, rtt_jitter, rtt_min);
-
-
- static uint64_t next_bitrate_incr = 0;
- static uint64_t next_bitrate_decr = 0;
-
- // Use int64_t for bitrate calculations to prevent overflow at high bitrates
- int64_t bitrate = cur_bitrate;
- int bs_th3 = (bs_avg + bs_jitter) * BS_TH3_MULT;
- int bs_th2 = max(BS_TH_MIN, bs_avg + max(bs_jitter * BS_TH2_JITTER_MULT, bs_avg));
- bs_th2 = min(bs_th2, RTT_TO_BS(srt_latency/2));
- int bs_th1 = max(BS_TH_MIN, bs_avg + bs_jitter * BS_TH1_JITTER_MULT);
- int rtt_th_max = rtt_avg + max(rtt_jitter * RTT_JITTER_MULT, rtt_avg * RTT_AVG_PERCENT / 100);
- int rtt_th_min = rtt_min + max(RTT_MIN_JITTER, rtt_jitter * 2);
-
-
- if (bitrate > min_bitrate && (rtt >= (srt_latency / 3) || bs > bs_th3)) {
- bitrate = min_bitrate;
- next_bitrate_decr = ctime + BITRATE_DECR_INT;
-
- } else if (ctime > next_bitrate_decr &&
- (rtt > (srt_latency / 5) || bs > bs_th2)) {
- bitrate -= BITRATE_DECR_MIN + bitrate/BITRATE_DECR_SCALE;
- next_bitrate_decr = ctime + BITRATE_DECR_FAST_INT;
-
- } else if (ctime > next_bitrate_decr &&
- (rtt > rtt_th_max || bs > bs_th1)) {
- bitrate -= BITRATE_DECR_MIN;
- next_bitrate_decr = ctime + BITRATE_DECR_INT;
-
- } else if (ctime > next_bitrate_incr &&
- rtt < rtt_th_min && rtt_avg_delta < RTT_STABLE_DELTA) {
- bitrate += BITRATE_INCR_MIN + bitrate / BITRATE_INCR_SCALE;
- next_bitrate_incr = ctime + BITRATE_INCR_INT;
- }
-
- // Clamp to valid range and convert back to int
- bitrate = min_max(bitrate, (int64_t)min_bitrate, (int64_t)max_bitrate);
- cur_bitrate = (int)bitrate;
-
- // round the bitrate we set to 100 kbps
- int rounded_br = cur_bitrate / (100*1000) * (100*1000);
-
- update_overlay(rounded_br, throughput, rtt, rtt_th_min, rtt_th_max, bs, bs_th1, bs_th2, bs_th3);
-
- // Check if bitrate actually changed (comparing int64_t with original int value)
+ // Prepare input for balancer
+ BalancerInput input = {
+ .buffer_size = bs,
+ .rtt = stats->msRTT,
+ .send_rate_mbps = stats->mbpsSendRate,
+ .timestamp = ctime,
+ .pkt_loss_total = stats->pktSndLossTotal,
+ .pkt_retrans_total = stats->pktRetransTotal
+ };
+
+ // Call the balancer algorithm
static int prev_set_bitrate = 0;
- if (rounded_br != prev_set_bitrate) {
- prev_set_bitrate = rounded_br;
+ BalancerOutput output = balancer_algo->step(balancer_state, &input);
- g_object_set (G_OBJECT(encoder), "bps", rounded_br / enc_bitrate_div, NULL);
+ // Update the overlay display
+ update_overlay(output.new_bitrate, output.throughput,
+ output.rtt, output.rtt_th_min, output.rtt_th_max,
+ output.bs, output.bs_th1, output.bs_th2, output.bs_th3);
- debug("set bitrate to %d, internal value %d\n", rounded_br, cur_bitrate);
+ // Set encoder bitrate if changed
+ if (output.new_bitrate != prev_set_bitrate) {
+ prev_set_bitrate = output.new_bitrate;
+ g_object_set(G_OBJECT(encoder), "bps", output.new_bitrate / enc_bitrate_div, NULL);
+ debug("set bitrate to %d\n", output.new_bitrate);
}
}
@@ -423,7 +335,7 @@ gboolean connection_housekeeping(gpointer user_data) {
// We can only update the bitrate when we have a configurable encoder
if (GST_IS_ELEMENT(encoder)) {
- update_bitrate(&stats, ctime);
+ do_bitrate_update(&stats, ctime);
}
r:
@@ -566,18 +478,22 @@ void exit_syntax() {
fprintf(stderr, "Syntax: belacoder PIPELINE_FILE ADDR PORT [options]\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -v Print the version and exit\n");
+ fprintf(stderr, " -c Configuration file (INI format)\n");
fprintf(stderr, " -d Audio-video delay in milliseconds\n");
fprintf(stderr, " -s SRT stream ID\n");
fprintf(stderr, " -l SRT latency in milliseconds\n");
fprintf(stderr, " -r Reduced SRT packet size\n");
- fprintf(stderr, " -b Bitrate settings file, see below\n\n");
- fprintf(stderr, "Bitrate settings file syntax:\n");
- fprintf(stderr, "MIN BITRATE (bps)\n");
- fprintf(stderr, "MAX BITRATE (bps)\n---\n");
- fprintf(stderr, "example for 500 Kbps - 60000 Kbps:\n\n");
- fprintf(stderr, " printf \"500000\\n6000000\" > bitrate_file\n\n");
- fprintf(stderr, "---\n");
- fprintf(stderr, "Send SIGHUP to reload the bitrate settings while running.\n");
+ fprintf(stderr, " -b Bitrate settings file (legacy, use -c instead)\n");
+ fprintf(stderr, " -a Bitrate balancer algorithm (overrides config)\n\n");
+ fprintf(stderr, "Config file example:\n");
+ fprintf(stderr, " [general]\n");
+ fprintf(stderr, " min_bitrate = 500 # Kbps\n");
+ fprintf(stderr, " max_bitrate = 6000 # Kbps (6 Mbps)\n");
+ fprintf(stderr, " balancer = adaptive\n\n");
+ fprintf(stderr, " [srt]\n");
+ fprintf(stderr, " latency = 2000 # ms\n\n");
+ fprintf(stderr, "Send SIGHUP to reload configuration while running.\n\n");
+ balancer_print_available();
exit(EXIT_FAILURE);
}
@@ -692,11 +608,17 @@ int main(int argc, char** argv) {
char *stream_id = NULL;
srt_latency = DEF_SRT_LATENCY;
- while ((opt = getopt(argc, argv, "d:b:s:l:rv")) != -1) {
+ while ((opt = getopt(argc, argv, "a:c:d:b:s:l:rv")) != -1) {
switch (opt) {
+ case 'a':
+ balancer_name = optarg;
+ break;
case 'b':
bitrate_filename = optarg;
break;
+ case 'c':
+ config_filename = optarg;
+ break;
case 'd': {
long delay;
if (parse_long(optarg, &delay, -MAX_AV_DELAY, MAX_AV_DELAY) != 0) {
@@ -765,7 +687,25 @@ int main(int argc, char** argv) {
g_signal_connect(bus, "message", (GCallback)cb_pipeline, gst_pipeline);
- // Optional dynamic video bitrate
+ // Initialize configuration with defaults
+ config_init_defaults(&g_config);
+
+ // Load config file if specified
+ if (config_filename != NULL) {
+ if (config_load(&g_config, config_filename) != 0) {
+ fprintf(stderr, "Failed to load config file: %s\n", config_filename);
+ exit(EXIT_FAILURE);
+ }
+ fprintf(stderr, "Loaded config from %s\n", config_filename);
+ // Apply config to globals (Kbps -> bps conversion)
+ min_bitrate = config_bitrate_bps(g_config.min_bitrate);
+ max_bitrate = config_bitrate_bps(g_config.max_bitrate);
+ if (g_config.srt_latency > 0) {
+ srt_latency = g_config.srt_latency;
+ }
+ }
+
+ // Legacy bitrate file support (overrides config if both specified)
if (bitrate_filename) {
int ret;
if ((ret = read_bitrate_file()) != 0) {
@@ -777,8 +717,45 @@ int main(int argc, char** argv) {
exit_syntax();
}
}
- cur_bitrate = max_bitrate;
- fprintf(stderr, "Max bitrate: %d\n", max_bitrate);
+
+ // Select balancer algorithm (CLI -a overrides config)
+ const char *algo_name = balancer_name ? balancer_name : g_config.balancer;
+ balancer_algo = balancer_find(algo_name);
+ if (balancer_algo == NULL) {
+ // Try default if config had invalid name
+ if (balancer_name != NULL) {
+ fprintf(stderr, "Unknown balancer algorithm: %s\n\n", balancer_name);
+ balancer_print_available();
+ exit(EXIT_FAILURE);
+ }
+ balancer_algo = balancer_get_default();
+ }
+ fprintf(stderr, "Balancer: %s\n", balancer_algo->name);
+
+ // Initialize the balancer
+ balancer_config.min_bitrate = min_bitrate;
+ balancer_config.max_bitrate = max_bitrate;
+ balancer_config.srt_latency = srt_latency;
+ balancer_config.srt_pkt_size = srt_pkt_size;
+
+ // Adaptive algorithm tuning (config uses Kbps, convert to bps)
+ balancer_config.adaptive_incr_step = config_bitrate_bps(g_config.adaptive.incr_step);
+ balancer_config.adaptive_decr_step = config_bitrate_bps(g_config.adaptive.decr_step);
+ balancer_config.adaptive_incr_interval = g_config.adaptive.incr_interval;
+ balancer_config.adaptive_decr_interval = g_config.adaptive.decr_interval;
+
+ // AIMD algorithm tuning
+ balancer_config.aimd_incr_step = config_bitrate_bps(g_config.aimd.incr_step);
+ balancer_config.aimd_decr_mult = g_config.aimd.decr_mult;
+ balancer_config.aimd_incr_interval = g_config.aimd.incr_interval;
+ balancer_config.aimd_decr_interval = g_config.aimd.decr_interval;
+
+ balancer_state = balancer_algo->init(&balancer_config);
+ if (balancer_state == NULL) {
+ fprintf(stderr, "Failed to initialize balancer algorithm\n");
+ exit(EXIT_FAILURE);
+ }
+ fprintf(stderr, "Bitrate range: %d - %d Kbps\n", min_bitrate / 1000, max_bitrate / 1000);
signal(SIGHUP, sighup_handler);
encoder = gst_bin_get_by_name(GST_BIN(gst_pipeline), "venc_bps");
@@ -787,7 +764,8 @@ int main(int argc, char** argv) {
enc_bitrate_div = 1000;
}
if (GST_IS_ELEMENT(encoder)) {
- g_object_set (G_OBJECT(encoder), "bps", cur_bitrate / enc_bitrate_div, NULL);
+ // Start at max bitrate (all algorithms should start optimistically)
+ g_object_set(G_OBJECT(encoder), "bps", max_bitrate / enc_bitrate_div, NULL);
} else {
fprintf(stderr, "Failed to get an encoder element from the pipeline, "
"no dynamic bitrate control\n");
@@ -907,6 +885,11 @@ int main(int argc, char** argv) {
// Clean up SRT library resources
srt_cleanup();
+ // Clean up balancer
+ if (balancer_algo != NULL && balancer_state != NULL) {
+ balancer_algo->cleanup(balancer_state);
+ }
+
// Clean up mmap'd pipeline file
munmap(launch_string, launch_string_len);
diff --git a/src/bitrate_control.c b/src/bitrate_control.c
new file mode 100644
index 0000000..1ad9283
--- /dev/null
+++ b/src/bitrate_control.c
@@ -0,0 +1,222 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "bitrate_control.h"
+#include // for MIN/MAX macros
+
+// Use GLib's MIN/MAX which are type-safe and don't double-evaluate
+#define min(a, b) MIN((a), (b))
+#define max(a, b) MAX((a), (b))
+#define min_max(a, l, h) (MAX(MIN((a), (h)), (l)))
+
+// Convert RTT to expected buffer size based on throughput
+#define RTT_TO_BS(ctx, rtt) ((ctx->throughput / 8) * (rtt) / ctx->srt_pkt_size)
+
+void bitrate_context_init(BitrateContext *ctx, int min_br, int max_br,
+ int latency, int pkt_size,
+ int incr_step, int decr_step,
+ int incr_interval, int decr_interval) {
+ // Configuration
+ ctx->min_bitrate = min_br;
+ ctx->max_bitrate = max_br;
+ ctx->srt_latency = latency;
+ ctx->srt_pkt_size = pkt_size;
+
+ // Tuning parameters (use defaults if 0)
+ ctx->incr_step = (incr_step > 0) ? incr_step : BITRATE_INCR_MIN;
+ ctx->decr_step = (decr_step > 0) ? decr_step : BITRATE_DECR_MIN;
+ ctx->incr_interval = (incr_interval > 0) ? incr_interval : BITRATE_INCR_INT;
+ ctx->decr_interval = (decr_interval > 0) ? decr_interval : BITRATE_DECR_INT;
+ ctx->decr_fast_interval = BITRATE_DECR_FAST_INT; // Not configurable yet
+
+ // Start at max bitrate
+ ctx->cur_bitrate = max_br;
+
+ // Buffer size tracking
+ ctx->bs_avg = 0.0;
+ ctx->bs_jitter = 0.0;
+ ctx->prev_bs = 0;
+
+ // RTT tracking
+ ctx->rtt_avg = 0.0;
+ ctx->rtt_min = RTT_MIN_INITIAL;
+ ctx->rtt_jitter = 0.0;
+ ctx->rtt_avg_delta = 0.0;
+ ctx->prev_rtt = RTT_INITIAL;
+
+ // Throughput tracking
+ ctx->throughput = 0.0;
+
+ // Packet loss tracking
+ ctx->prev_pkt_loss = 0;
+ ctx->prev_pkt_retrans = 0;
+ ctx->loss_rate = 0.0;
+
+ // Timing
+ ctx->next_bitrate_incr = 0;
+ ctx->next_bitrate_decr = 0;
+}
+
+// Packet loss detection threshold
+#define LOSS_RATE_THRESHOLD 0.5 // Trigger congestion if losing > 0.5 packets/interval
+#define EMA_LOSS 0.9 // Smoothing for loss rate
+#define EMA_LOSS_NEW 0.1
+
+int bitrate_update(BitrateContext *ctx, int buffer_size, double rtt,
+ double send_rate_mbps, uint64_t timestamp,
+ int64_t pkt_loss_total, int64_t pkt_retrans_total,
+ BitrateResult *result) {
+ int bs = buffer_size;
+ int rtt_int = (int)rtt;
+
+ /*
+ * Packet loss tracking
+ */
+ int64_t loss_delta = pkt_loss_total - ctx->prev_pkt_loss;
+ int64_t retrans_delta = pkt_retrans_total - ctx->prev_pkt_retrans;
+ ctx->prev_pkt_loss = pkt_loss_total;
+ ctx->prev_pkt_retrans = pkt_retrans_total;
+
+ // Smooth the loss rate (packet losses per update interval)
+ if (loss_delta > 0 || retrans_delta > 0) {
+ double new_loss = (double)(loss_delta + retrans_delta);
+ ctx->loss_rate = ctx->loss_rate * EMA_LOSS + new_loss * EMA_LOSS_NEW;
+ } else {
+ ctx->loss_rate *= EMA_LOSS; // Decay when no loss
+ }
+
+ // Flag for packet loss congestion
+ int pkt_loss_congestion = (ctx->loss_rate > LOSS_RATE_THRESHOLD);
+
+ /*
+ * Send buffer size stats
+ */
+ // Rolling average
+ ctx->bs_avg = ctx->bs_avg * EMA_SLOW + (double)bs * EMA_FAST;
+
+ // Update the buffer size jitter
+ ctx->bs_jitter = EMA_SLOW * ctx->bs_jitter;
+ int delta_bs = bs - ctx->prev_bs;
+ if (delta_bs > ctx->bs_jitter) {
+ ctx->bs_jitter = (double)delta_bs;
+ }
+ ctx->prev_bs = bs;
+
+ /*
+ * RTT stats
+ */
+ // Update the average RTT
+ if (ctx->rtt_avg == 0.0) {
+ ctx->rtt_avg = rtt;
+ } else {
+ ctx->rtt_avg = ctx->rtt_avg * EMA_SLOW + EMA_FAST * rtt;
+ }
+
+ // Update the average RTT delta
+ double delta_rtt = rtt - (double)ctx->prev_rtt;
+ ctx->rtt_avg_delta = ctx->rtt_avg_delta * EMA_RTT_DELTA + delta_rtt * EMA_RTT_DELTA_NEW;
+ ctx->prev_rtt = rtt_int;
+
+ // Update the minimum RTT
+ ctx->rtt_min *= RTT_MIN_DRIFT;
+ if (rtt_int != RTT_IGNORE_VALUE && rtt < ctx->rtt_min && ctx->rtt_avg_delta < 1.0) {
+ ctx->rtt_min = rtt;
+ }
+
+ // Update the RTT jitter
+ ctx->rtt_jitter *= EMA_SLOW;
+ if (delta_rtt > ctx->rtt_jitter) {
+ ctx->rtt_jitter = delta_rtt;
+ }
+
+ /*
+ * Rolling average of the network throughput
+ */
+ ctx->throughput *= EMA_THROUGHPUT;
+ ctx->throughput += (send_rate_mbps * 1000.0 * 1000.0 / 1024.0) * EMA_THROUGHPUT_NEW;
+
+ /*
+ * Compute thresholds
+ */
+ int bs_th3 = (ctx->bs_avg + ctx->bs_jitter) * BS_TH3_MULT;
+ int bs_th2 = max(BS_TH_MIN, ctx->bs_avg + max(ctx->bs_jitter * BS_TH2_JITTER_MULT, ctx->bs_avg));
+ bs_th2 = min(bs_th2, (int)RTT_TO_BS(ctx, ctx->srt_latency / 2));
+ int bs_th1 = max(BS_TH_MIN, ctx->bs_avg + ctx->bs_jitter * BS_TH1_JITTER_MULT);
+ int rtt_th_max = ctx->rtt_avg + max(ctx->rtt_jitter * RTT_JITTER_MULT, ctx->rtt_avg * RTT_AVG_PERCENT / 100);
+ int rtt_th_min = ctx->rtt_min + max(RTT_MIN_JITTER, ctx->rtt_jitter * 2);
+
+ /*
+ * Bitrate decision logic
+ *
+ * Congestion signals (in priority order):
+ * 1. Emergency: RTT >= latency/3 OR buffer > bs_th3
+ * 2. Heavy: RTT > latency/5 OR buffer > bs_th2 OR packet loss
+ * 3. Light: RTT > rtt_th_max OR buffer > bs_th1
+ * 4. Stable: RTT < rtt_th_min AND RTT not rising AND no packet loss
+ */
+ // Use int64_t for bitrate calculations to prevent overflow at high bitrates
+ int64_t bitrate = ctx->cur_bitrate;
+
+ if (bitrate > ctx->min_bitrate && (rtt_int >= (ctx->srt_latency / 3) || bs > bs_th3)) {
+ // Emergency: drop to minimum
+ bitrate = ctx->min_bitrate;
+ ctx->next_bitrate_decr = timestamp + ctx->decr_interval;
+
+ } else if (timestamp > ctx->next_bitrate_decr &&
+ (rtt_int > (ctx->srt_latency / 5) || bs > bs_th2 || pkt_loss_congestion)) {
+ // Heavy congestion: fast decrease (now includes packet loss)
+ bitrate -= ctx->decr_step + bitrate / BITRATE_DECR_SCALE;
+ ctx->next_bitrate_decr = timestamp + ctx->decr_fast_interval;
+
+ } else if (timestamp > ctx->next_bitrate_decr &&
+ (rtt_int > rtt_th_max || bs > bs_th1)) {
+ // Light congestion: slow decrease
+ bitrate -= ctx->decr_step;
+ ctx->next_bitrate_decr = timestamp + ctx->decr_interval;
+
+ } else if (timestamp > ctx->next_bitrate_incr &&
+ rtt_int < rtt_th_min && ctx->rtt_avg_delta < RTT_STABLE_DELTA &&
+ !pkt_loss_congestion) {
+ // Stable: increase (only if no packet loss)
+ bitrate += ctx->incr_step + bitrate / BITRATE_INCR_SCALE;
+ ctx->next_bitrate_incr = timestamp + ctx->incr_interval;
+ }
+
+ // Clamp to valid range
+ bitrate = min_max(bitrate, (int64_t)ctx->min_bitrate, (int64_t)ctx->max_bitrate);
+ ctx->cur_bitrate = (int)bitrate;
+
+ // Round to 100 kbps
+ int rounded_br = ctx->cur_bitrate / (100 * 1000) * (100 * 1000);
+
+ // Fill result structure if provided
+ if (result != NULL) {
+ result->new_bitrate = rounded_br;
+ result->throughput = ctx->throughput;
+ result->rtt = rtt_int;
+ result->rtt_th_min = rtt_th_min;
+ result->rtt_th_max = rtt_th_max;
+ result->bs = bs;
+ result->bs_th1 = bs_th1;
+ result->bs_th2 = bs_th2;
+ result->bs_th3 = bs_th3;
+ }
+
+ return rounded_br;
+}
diff --git a/src/bitrate_control.h b/src/bitrate_control.h
new file mode 100644
index 0000000..65256a1
--- /dev/null
+++ b/src/bitrate_control.h
@@ -0,0 +1,168 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef BITRATE_CONTROL_H
+#define BITRATE_CONTROL_H
+
+#include
+
+/*
+ * Bitrate control constants
+ */
+
+// Bitrate limits
+#define MIN_BITRATE (300 * 1000)
+#define ABS_MAX_BITRATE (30 * 1000 * 1000)
+#define DEF_BITRATE (6 * 1000 * 1000)
+
+// Update intervals (ms)
+#define BITRATE_UPDATE_INT 20
+#define BITRATE_INCR_INT 500 // min interval for increasing bitrate
+#define BITRATE_DECR_INT 200 // light congestion: min interval for decreasing
+#define BITRATE_DECR_FAST_INT 250 // heavy congestion: min interval for decreasing
+
+// Bitrate adjustment amounts (bps)
+#define BITRATE_INCR_MIN (30*1000) // minimum bitrate increment step
+#define BITRATE_INCR_SCALE 30 // bitrate increased by INCR_MIN + cur_bitrate/INCR_SCALE
+#define BITRATE_DECR_MIN (100*1000) // minimum bitrate decrement step
+#define BITRATE_DECR_SCALE 10 // heavy congestion: decrease by DECR_MIN + cur_bitrate/DECR_SCALE
+
+// Exponential moving average smoothing factors
+#define EMA_SLOW 0.99 // for bs_avg, rtt_avg, jitter decay
+#define EMA_FAST 0.01 // complement of EMA_SLOW (1 - 0.99)
+#define EMA_RTT_DELTA 0.8 // for rtt_avg_delta smoothing
+#define EMA_RTT_DELTA_NEW 0.2 // complement (1 - 0.8)
+#define EMA_THROUGHPUT 0.97 // for throughput smoothing
+#define EMA_THROUGHPUT_NEW 0.03 // complement (1 - 0.97)
+
+// RTT tracking constants
+#define RTT_MIN_DRIFT 1.001 // per-sample drift rate for min RTT tracking
+#define RTT_IGNORE_VALUE 100 // RTT value that indicates no valid measurement
+#define RTT_INITIAL 300 // initial prev_rtt value
+#define RTT_MIN_INITIAL 200.0 // initial rtt_min value
+
+// Threshold multipliers for congestion detection
+#define BS_TH3_MULT 4 // heavy congestion: (bs_avg + bs_jitter) * 4
+#define BS_TH2_JITTER_MULT 3.0 // medium congestion jitter multiplier
+#define BS_TH1_JITTER_MULT 2.5 // light congestion jitter multiplier
+#define BS_TH_MIN 50 // minimum buffer threshold
+#define RTT_JITTER_MULT 4 // rtt_th_max jitter multiplier
+#define RTT_AVG_PERCENT 15 // rtt_th_max percentage of average (15%)
+#define RTT_STABLE_DELTA 0.01 // max rtt_avg_delta for stable conditions
+#define RTT_MIN_JITTER 1 // minimum jitter for rtt_th_min calculation
+
+/*
+ * Bitrate controller context - holds all state for the adaptive bitrate algorithm
+ */
+typedef struct {
+ // Configuration (set once at init)
+ int min_bitrate;
+ int max_bitrate;
+ int srt_latency;
+ int srt_pkt_size;
+
+ // Tuning parameters (can be customized via config)
+ int incr_step; // Bitrate increase step (bps)
+ int decr_step; // Bitrate decrease step (bps)
+ int incr_interval; // Min interval between increases (ms)
+ int decr_interval; // Min interval between decreases (ms)
+ int decr_fast_interval; // Heavy congestion decrease interval (ms)
+
+ // Current bitrate
+ int cur_bitrate;
+
+ // Buffer size tracking
+ double bs_avg;
+ double bs_jitter;
+ int prev_bs;
+
+ // RTT tracking
+ double rtt_avg;
+ double rtt_min;
+ double rtt_jitter;
+ double rtt_avg_delta;
+ int prev_rtt;
+
+ // Throughput tracking
+ double throughput;
+
+ // Packet loss tracking
+ int64_t prev_pkt_loss; // Previous loss count (for delta)
+ int64_t prev_pkt_retrans; // Previous retrans count (for delta)
+ double loss_rate; // Smoothed packet loss rate (packets/interval)
+
+ // Timing for rate limiting bitrate changes
+ uint64_t next_bitrate_incr;
+ uint64_t next_bitrate_decr;
+} BitrateContext;
+
+/*
+ * Output structure with debug/overlay information
+ */
+typedef struct {
+ int new_bitrate; // Computed bitrate (rounded to 100 Kbps)
+ double throughput; // Smoothed throughput
+ int rtt; // Current RTT
+ int rtt_th_min; // RTT threshold min
+ int rtt_th_max; // RTT threshold max
+ int bs; // Current buffer size
+ int bs_th1; // Buffer threshold 1 (light congestion)
+ int bs_th2; // Buffer threshold 2 (medium congestion)
+ int bs_th3; // Buffer threshold 3 (heavy congestion)
+} BitrateResult;
+
+/*
+ * Initialize a bitrate context with configuration values
+ *
+ * Parameters:
+ * min_br, max_br - Bitrate limits (bps)
+ * latency - SRT latency (ms)
+ * pkt_size - SRT packet size (bytes)
+ * incr_step - Bitrate increase step (bps, 0 = use default)
+ * decr_step - Bitrate decrease step (bps, 0 = use default)
+ * incr_interval - Min interval between increases (ms, 0 = use default)
+ * decr_interval - Min interval between decreases (ms, 0 = use default)
+ */
+void bitrate_context_init(BitrateContext *ctx, int min_br, int max_br,
+ int latency, int pkt_size,
+ int incr_step, int decr_step,
+ int incr_interval, int decr_interval);
+
+/*
+ * Update the bitrate based on current SRT statistics
+ *
+ * Parameters:
+ * ctx - Bitrate context (holds state)
+ * buffer_size - Current SRT send buffer size (packets)
+ * rtt - Current round-trip time (ms)
+ * send_rate_mbps - Current send rate from SRT stats (Mbps)
+ * timestamp - Current timestamp in milliseconds
+ * pkt_loss_total - Total packets lost (cumulative from SRT stats)
+ * pkt_retrans_total - Total packets retransmitted (cumulative)
+ * result - Output structure (can be NULL if debug info not needed)
+ *
+ * Returns:
+ * The new bitrate in bps (rounded to 100 Kbps)
+ */
+int bitrate_update(BitrateContext *ctx, int buffer_size, double rtt,
+ double send_rate_mbps, uint64_t timestamp,
+ int64_t pkt_loss_total, int64_t pkt_retrans_total,
+ BitrateResult *result);
+
+#endif /* BITRATE_CONTROL_H */
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..43a3e76
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,169 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "config.h"
+#include
+#include
+#include
+#include
+
+// Default values
+#define DEF_MIN_BITRATE 300 // Kbps
+#define DEF_MAX_BITRATE 6000 // Kbps
+#define DEF_SRT_LATENCY 2000 // ms
+#define DEF_BALANCER "adaptive"
+
+// Adaptive defaults
+#define DEF_ADAPTIVE_INCR_STEP 30 // Kbps
+#define DEF_ADAPTIVE_DECR_STEP 100 // Kbps
+#define DEF_ADAPTIVE_INCR_INT 500 // ms
+#define DEF_ADAPTIVE_DECR_INT 200 // ms
+#define DEF_ADAPTIVE_LOSS_TH 0.5
+
+// AIMD defaults
+#define DEF_AIMD_INCR_STEP 50 // Kbps
+#define DEF_AIMD_DECR_MULT 0.75
+#define DEF_AIMD_INCR_INT 500 // ms
+#define DEF_AIMD_DECR_INT 200 // ms
+
+void config_init_defaults(BelacoderConfig *cfg) {
+ memset(cfg, 0, sizeof(*cfg));
+
+ // General
+ cfg->min_bitrate = DEF_MIN_BITRATE;
+ cfg->max_bitrate = DEF_MAX_BITRATE;
+ strncpy(cfg->balancer, DEF_BALANCER, sizeof(cfg->balancer) - 1);
+
+ // SRT
+ cfg->srt_latency = DEF_SRT_LATENCY;
+
+ // Adaptive
+ cfg->adaptive.incr_step = DEF_ADAPTIVE_INCR_STEP;
+ cfg->adaptive.decr_step = DEF_ADAPTIVE_DECR_STEP;
+ cfg->adaptive.incr_interval = DEF_ADAPTIVE_INCR_INT;
+ cfg->adaptive.decr_interval = DEF_ADAPTIVE_DECR_INT;
+ cfg->adaptive.loss_threshold = DEF_ADAPTIVE_LOSS_TH;
+
+ // AIMD
+ cfg->aimd.incr_step = DEF_AIMD_INCR_STEP;
+ cfg->aimd.decr_mult = DEF_AIMD_DECR_MULT;
+ cfg->aimd.incr_interval = DEF_AIMD_INCR_INT;
+ cfg->aimd.decr_interval = DEF_AIMD_DECR_INT;
+}
+
+// Trim whitespace from both ends
+static char* trim(char *str) {
+ while (isspace((unsigned char)*str)) str++;
+ if (*str == '\0') return str;
+
+ char *end = str + strlen(str) - 1;
+ while (end > str && isspace((unsigned char)*end)) end--;
+ end[1] = '\0';
+
+ return str;
+}
+
+// Parse a single key=value line within a section
+static void parse_line(BelacoderConfig *cfg, const char *section,
+ const char *key, const char *value) {
+ // [general] section
+ if (strcmp(section, "general") == 0) {
+ if (strcmp(key, "min_bitrate") == 0) {
+ cfg->min_bitrate = atoi(value);
+ } else if (strcmp(key, "max_bitrate") == 0) {
+ cfg->max_bitrate = atoi(value);
+ } else if (strcmp(key, "balancer") == 0) {
+ strncpy(cfg->balancer, value, sizeof(cfg->balancer) - 1);
+ }
+ }
+ // [srt] section
+ else if (strcmp(section, "srt") == 0) {
+ if (strcmp(key, "latency") == 0) {
+ cfg->srt_latency = atoi(value);
+ }
+ // Note: stream_id is CLI-only (-s flag), not in config
+ }
+ // [adaptive] section
+ else if (strcmp(section, "adaptive") == 0) {
+ if (strcmp(key, "incr_step") == 0) {
+ cfg->adaptive.incr_step = atoi(value);
+ } else if (strcmp(key, "decr_step") == 0) {
+ cfg->adaptive.decr_step = atoi(value);
+ } else if (strcmp(key, "incr_interval") == 0) {
+ cfg->adaptive.incr_interval = atoi(value);
+ } else if (strcmp(key, "decr_interval") == 0) {
+ cfg->adaptive.decr_interval = atoi(value);
+ } else if (strcmp(key, "loss_threshold") == 0) {
+ cfg->adaptive.loss_threshold = atof(value);
+ }
+ }
+ // [aimd] section
+ else if (strcmp(section, "aimd") == 0) {
+ if (strcmp(key, "incr_step") == 0) {
+ cfg->aimd.incr_step = atoi(value);
+ } else if (strcmp(key, "decr_mult") == 0) {
+ cfg->aimd.decr_mult = atof(value);
+ } else if (strcmp(key, "incr_interval") == 0) {
+ cfg->aimd.incr_interval = atoi(value);
+ } else if (strcmp(key, "decr_interval") == 0) {
+ cfg->aimd.decr_interval = atoi(value);
+ }
+ }
+}
+
+int config_load(BelacoderConfig *cfg, const char *filename) {
+ FILE *f = fopen(filename, "r");
+ if (f == NULL) {
+ return -1;
+ }
+
+ char line[512];
+ char section[64] = "general"; // Default section
+
+ while (fgets(line, sizeof(line), f) != NULL) {
+ char *trimmed = trim(line);
+
+ // Skip empty lines and comments
+ if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
+ continue;
+ }
+
+ // Section header [section]
+ if (trimmed[0] == '[') {
+ char *end = strchr(trimmed, ']');
+ if (end != NULL) {
+ *end = '\0';
+ strncpy(section, trimmed + 1, sizeof(section) - 1);
+ }
+ continue;
+ }
+
+ // Key = value
+ char *eq = strchr(trimmed, '=');
+ if (eq != NULL) {
+ *eq = '\0';
+ char *key = trim(trimmed);
+ char *value = trim(eq + 1);
+ parse_line(cfg, section, key, value);
+ }
+ }
+
+ fclose(f);
+ return 0;
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..9ed8ada
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,83 @@
+/*
+ belacoder - live video encoder with dynamic bitrate control
+ Copyright (C) 2020 BELABOX project
+ Copyright (C) 2026 CERALIVE
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include
+
+/*
+ * Configuration structure for belacoder
+ *
+ * All bitrates are in Kbps in the config file, converted to bps internally.
+ * Example: 6000 in config = 6 Mbps = 6,000,000 bps
+ */
+
+// Adaptive algorithm tuning
+typedef struct {
+ int incr_step; // Bitrate increase step (Kbps, default: 30)
+ int decr_step; // Bitrate decrease step (Kbps, default: 100)
+ int incr_interval; // Min interval between increases (ms, default: 500)
+ int decr_interval; // Min interval between decreases (ms, default: 200)
+ double loss_threshold; // Packet loss threshold (default: 0.5)
+} AdaptiveConfig;
+
+// AIMD algorithm tuning
+typedef struct {
+ int incr_step; // Additive increase (Kbps, default: 50)
+ double decr_mult; // Multiplicative decrease (default: 0.75)
+ int incr_interval; // Min interval between increases (ms, default: 500)
+ int decr_interval; // Min interval between decreases (ms, default: 200)
+} AimdConfig;
+
+// Main configuration
+typedef struct {
+ // General settings
+ int min_bitrate; // Minimum bitrate (Kbps, default: 300)
+ int max_bitrate; // Maximum bitrate (Kbps, default: 6000)
+ char balancer[32]; // Algorithm name (default: "adaptive")
+
+ // SRT settings
+ int srt_latency; // SRT latency (ms, default: 2000)
+ // Note: stream_id is CLI-only (-s flag)
+
+ // Algorithm-specific settings
+ AdaptiveConfig adaptive;
+ AimdConfig aimd;
+} BelacoderConfig;
+
+/*
+ * Initialize config with defaults
+ */
+void config_init_defaults(BelacoderConfig *cfg);
+
+/*
+ * Load config from file
+ * Returns 0 on success, -1 on error
+ */
+int config_load(BelacoderConfig *cfg, const char *filename);
+
+/*
+ * Get bitrate in bps (converts from Kbps)
+ */
+static inline int config_bitrate_bps(int kbps) {
+ return kbps * 1000;
+}
+
+#endif /* CONFIG_H */