Skip to content

Commit 8db8247

Browse files
committed
adaptive jitter buffer
1 parent b0d2b82 commit 8db8247

File tree

4 files changed

+188
-2
lines changed

4 files changed

+188
-2
lines changed

daemon/jitter_buffer.c

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ static void reset_jitter_buffer(struct jitter_buffer *jb) {
5656
jb->clock_drift_val = 0;
5757
jb->prev_seq_ts = rtpe_now;
5858
jb->prev_seq = 0;
59+
jb->jitter_mean = 0.0;
60+
jb->jitter_variance = 0.0;
61+
jb->jitter_m2 = 0.0;
62+
jb->jitter_samples = 0;
63+
jb->dynamic_capacity = 0;
5964

6065
jb->num_resets++;
6166
if(g_tree_nnodes(jb->ttq.entries) > 0)
@@ -92,6 +97,54 @@ static int get_clock_rate(struct media_packet *mp, int payload_type) {
9297
return clock_rate;
9398
}
9499

100+
// jb is locked
101+
static void update_jitter_statistics(struct jitter_buffer *jb, int64_t jitter_sample_us) {
102+
if (!rtpe_config.jb_adaptive)
103+
return;
104+
105+
jb->jitter_samples++;
106+
107+
double delta = (double)jitter_sample_us - jb->jitter_mean;
108+
jb->jitter_mean += delta / (double)jb->jitter_samples;
109+
double delta2 = (double)jitter_sample_us - jb->jitter_mean;
110+
jb->jitter_m2 += delta * delta2;
111+
112+
if (jb->jitter_samples > 1)
113+
jb->jitter_variance = jb->jitter_m2 / (double)(jb->jitter_samples - 1);
114+
}
115+
116+
// jb is locked
117+
static void calculate_adaptive_buffer_size(struct jitter_buffer *jb) {
118+
if (!rtpe_config.jb_adaptive || jb->jitter_samples < 10)
119+
return;
120+
121+
double std_dev_us = sqrt(jb->jitter_variance);
122+
123+
double optimal_buffer_us = jb->jitter_mean + (4.0 * std_dev_us);
124+
125+
int optimal_buffer_ms = (int)(optimal_buffer_us / 1000.0);
126+
127+
int min_capacity = rtpe_config.jb_adaptive_min;
128+
int max_capacity = rtpe_config.jb_adaptive_max;
129+
130+
if (max_capacity <= 0)
131+
max_capacity = 300;
132+
if (min_capacity < 0)
133+
min_capacity = 0;
134+
if (min_capacity > max_capacity)
135+
min_capacity = max_capacity;
136+
137+
if (optimal_buffer_ms < min_capacity)
138+
optimal_buffer_ms = min_capacity;
139+
if (optimal_buffer_ms > max_capacity)
140+
optimal_buffer_ms = max_capacity;
141+
142+
jb->dynamic_capacity = optimal_buffer_ms;
143+
144+
ilog(LOG_DEBUG, "Adaptive JB: mean=%.2fms, stddev=%.2fms, capacity=%dms (samples=%u)",
145+
jb->jitter_mean / 1000.0, std_dev_us / 1000.0, jb->dynamic_capacity, jb->jitter_samples);
146+
}
147+
95148
static struct jb_packet* get_jb_packet(struct media_packet *mp, const str *s) {
96149
if (!(mp->rtp = rtp_payload(&mp->payload, s, NULL)))
97150
return NULL;
@@ -113,10 +166,97 @@ static struct jb_packet* get_jb_packet(struct media_packet *mp, const str *s) {
113166
return p;
114167
}
115168

169+
// jb is locked (temporarily unlocked during operation, then relocked)
170+
static int remove_oldest_packets(struct jitter_buffer *jb, int num_to_remove) {
171+
if (num_to_remove <= 0)
172+
return 0;
173+
174+
int removed = 0;
175+
mutex_unlock(&jb->lock);
176+
177+
for (int i = 0; i < num_to_remove; i++) {
178+
struct timerthread_queue_entry *ttqe = rtpe_g_tree_first(jb->ttq.entries);
179+
if (!ttqe)
180+
break;
181+
182+
g_tree_remove(jb->ttq.entries, ttqe);
183+
if (jb->ttq.entry_free_func)
184+
jb->ttq.entry_free_func(ttqe);
185+
removed++;
186+
}
187+
188+
mutex_lock(&jb->lock);
189+
return removed;
190+
}
191+
192+
// jb is locked
193+
static int try_burst_aware_discard(struct jitter_buffer *jb, int current_buffer_size) {
194+
if (!jb->rtptime_delta || !jb->clock_rate || !jb->prev_seq_ts) {
195+
ilog(LOG_DEBUG, "Burst-aware discard: insufficient data for calculation");
196+
return 0;
197+
}
198+
199+
int64_t packetization_interval_us = ((int64_t)jb->rtptime_delta * 1000000) / jb->clock_rate;
200+
if (packetization_interval_us <= 0) {
201+
ilog(LOG_DEBUG, "Burst-aware discard: invalid packetization interval");
202+
return 0;
203+
}
204+
205+
int64_t delta_t = rtpe_now - jb->prev_seq_ts;
206+
if (delta_t < 0) {
207+
ilog(LOG_DEBUG, "Burst-aware discard: negative time delta");
208+
return 0;
209+
}
210+
211+
int estimated_burst_size = (int)(delta_t / packetization_interval_us);
212+
213+
int target_capacity = rtpe_config.jb_adaptive ? jb->dynamic_capacity : rtpe_config.jb_length;
214+
if (target_capacity <= 0)
215+
target_capacity = rtpe_config.jb_length;
216+
217+
int packets_to_remove = estimated_burst_size - target_capacity;
218+
219+
if (packets_to_remove <= 0) {
220+
ilog(LOG_DEBUG, "Burst-aware discard: no removal needed (burst: %d, capacity: %d)",
221+
estimated_burst_size, target_capacity);
222+
return 1;
223+
}
224+
225+
if (packets_to_remove >= current_buffer_size) {
226+
ilog(LOG_DEBUG, "Burst-aware discard: would remove all packets (burst: %d, capacity: %d, buffer: %d)",
227+
estimated_burst_size, target_capacity, current_buffer_size);
228+
return 0;
229+
}
230+
231+
int packets_saved = current_buffer_size - packets_to_remove;
232+
ilog(LOG_DEBUG, "Burst-aware discard: burst of %d packets detected, removing %d (saving %d packets)",
233+
estimated_burst_size, packets_to_remove, packets_saved);
234+
235+
int removed = remove_oldest_packets(jb, packets_to_remove);
236+
237+
if (removed > 0) {
238+
ilog(LOG_DEBUG, "Burst-aware discard: successfully removed %d packets", removed);
239+
return 1;
240+
}
241+
242+
return 0;
243+
}
244+
116245
// jb is locked
117246
static void check_buffered_packets(struct jitter_buffer *jb) {
118-
if (g_tree_nnodes(jb->ttq.entries) >= (3* rtpe_config.jb_length)) {
119-
ilog(LOG_DEBUG, "Jitter reset due to buffer overflow");
247+
int current_buffer_size = g_tree_nnodes(jb->ttq.entries);
248+
int target_capacity = rtpe_config.jb_adaptive ? jb->dynamic_capacity : rtpe_config.jb_length;
249+
250+
if (current_buffer_size > target_capacity) {
251+
if (jb->rtptime_delta && jb->clock_rate && jb->prev_seq_ts) {
252+
if (try_burst_aware_discard(jb, current_buffer_size)) {
253+
return;
254+
}
255+
}
256+
}
257+
258+
if (current_buffer_size >= (3 * rtpe_config.jb_length)) {
259+
ilog(LOG_DEBUG, "Emergency buffer overflow at 3x capacity - forcing reset");
120260
reset_jitter_buffer(jb);
121261
}
122262
}
@@ -311,6 +451,21 @@ int buffer_packet(struct media_packet *mp, const str *s) {
311451
// packet consumed?
312452
if (ret == 0)
313453
p = NULL;
454+
455+
// Update adaptive jitter buffer statistics
456+
if (rtpe_config.jb_adaptive && jb->first_send && jb->rtptime_delta && jb->clock_rate) {
457+
unsigned long ts = ntohl(mp->rtp->timestamp);
458+
long ts_diff = (uint32_t)ts - (uint32_t)jb->first_send_ts;
459+
int64_t expected_arrival = jb->first_send + (ts_diff * 1000000LL / jb->clock_rate);
460+
461+
int64_t jitter_us = llabs(rtpe_now - expected_arrival);
462+
463+
update_jitter_statistics(jb, jitter_us);
464+
465+
// Recalculate adaptive buffer size every 10 packets
466+
if (jb->jitter_samples % 10 == 0)
467+
calculate_adaptive_buffer_size(jb);
468+
}
314469

315470
check_buffered_packets(jb);
316471

daemon/main.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,9 @@ static void options(int *argc, char ***argv, charp_ht templates) {
796796
{ "endpoint-learning",0,0,G_OPTION_ARG_STRING, &endpoint_learning, "RTP endpoint learning algorithm", "delayed|immediate|off|heuristic" },
797797
{ "jitter-buffer",0, 0, G_OPTION_ARG_INT, &rtpe_config.jb_length, "Size of jitter buffer", "INT" },
798798
{ "jb-clock-drift",0,0, G_OPTION_ARG_NONE, &rtpe_config.jb_clock_drift,"Compensate for source clock drift",NULL },
799+
{ "jb-adaptive",0,0, G_OPTION_ARG_NONE, &rtpe_config.jb_adaptive,"Enable adaptive jitter buffer sizing",NULL },
800+
{ "jb-adaptive-min",0,0,G_OPTION_ARG_INT, &rtpe_config.jb_adaptive_min,"Minimum adaptive jitter buffer size (ms)","INT" },
801+
{ "jb-adaptive-max",0,0,G_OPTION_ARG_INT, &rtpe_config.jb_adaptive_max,"Maximum adaptive jitter buffer size (ms)","INT" },
799802
{ "debug-srtp",0,0, G_OPTION_ARG_NONE, &debug_srtp, "Log raw encryption details for SRTP", NULL },
800803
{ "reject-invalid-sdp",0,0, G_OPTION_ARG_NONE, &rtpe_config.reject_invalid_sdp,"Refuse to process SDP bodies with broken syntax", NULL },
801804
{ "dtls-rsa-key-size",0, 0, G_OPTION_ARG_INT,&rtpe_config.dtls_rsa_key_size,"Size of RSA key for DTLS", "INT" },
@@ -1268,6 +1271,26 @@ static void options(int *argc, char ***argv, charp_ht templates) {
12681271

12691272
if (rtpe_config.jb_length < 0)
12701273
die("Invalid negative jitter buffer size");
1274+
1275+
// Validate adaptive jitter buffer parameters
1276+
if (rtpe_config.jb_adaptive) {
1277+
if (rtpe_config.jb_adaptive_min < 0)
1278+
die("Invalid negative --jb-adaptive-min (%d)", rtpe_config.jb_adaptive_min);
1279+
if (rtpe_config.jb_adaptive_max < 0)
1280+
die("Invalid negative --jb-adaptive-max (%d)", rtpe_config.jb_adaptive_max);
1281+
if (rtpe_config.jb_adaptive_max > 1000)
1282+
die("--jb-adaptive-max too large (%d ms, maximum 1000 ms)", rtpe_config.jb_adaptive_max);
1283+
if (rtpe_config.jb_adaptive_min > rtpe_config.jb_adaptive_max)
1284+
die("--jb-adaptive-min (%d) must be <= --jb-adaptive-max (%d)",
1285+
rtpe_config.jb_adaptive_min, rtpe_config.jb_adaptive_max);
1286+
1287+
// Set reasonable defaults if not specified
1288+
if (rtpe_config.jb_adaptive_max == 0)
1289+
rtpe_config.jb_adaptive_max = 300; // Default max: 300ms
1290+
1291+
ilog(LOG_INFO, "Adaptive jitter buffer enabled: min=%dms, max=%dms",
1292+
rtpe_config.jb_adaptive_min, rtpe_config.jb_adaptive_max);
1293+
}
12711294

12721295
if (silence_detect > 0) {
12731296
rtpe_config.silence_detect_double = silence_detect / 100.0;

include/jitter_buffer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ struct jitter_buffer {
3737
int clock_drift_val;
3838
call_t *call;
3939
int disabled;
40+
int dynamic_capacity;
41+
double jitter_mean;
42+
double jitter_variance;
43+
double jitter_m2;
44+
unsigned int jitter_samples;
4045
};
4146

4247
void jitter_buffer_init(void);

include/main.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ enum endpoint_learning {
6868
X(mysql_port) \
6969
X(dtmf_digit_delay) \
7070
X(jb_length) \
71+
X(jb_adaptive_min) \
72+
X(jb_adaptive_max) \
7173
X(dtls_rsa_key_size) \
7274
X(dtls_mtu) \
7375
X(http_threads) \
@@ -123,6 +125,7 @@ enum endpoint_learning {
123125
X(dtmf_no_suppress) \
124126
X(dtmf_no_log_injects) \
125127
X(jb_clock_drift) \
128+
X(jb_adaptive) \
126129
X(player_cache) \
127130
X(poller_per_thread) \
128131
X(redis_resolve_on_reconnect) \

0 commit comments

Comments
 (0)