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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Compiled binary
belacoder
belacoder.o

# Object files
*.o

# Editor files
*.swp
*~
23 changes: 21 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
52 changes: 52 additions & 0 deletions belacoder.conf.example
Original file line number Diff line number Diff line change
@@ -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)
61 changes: 44 additions & 17 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
174 changes: 134 additions & 40 deletions docs/bitrate-control.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Loading