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 */