Description: detect lost (and late) RTP packets for native PLC
 Original patch adapted by Jonas Smedegaard
 to fit Debian packaging,
 disable debugging NOTICEs,
 and to remove original patch file to help detect later upstream changes.
 .
 Only apply this to translation paths starting with a decoder that has
 native PLC (Opus, Speex, iLBC); other paths never drop late frames.
 Treat a sequence number jump of more than 50 packets either way as a
 new RTP stream (e.g. after an SSRC change), not as loss or lateness:
 the original version dropped every frame for up to 32767 packets after
 a backward jump, and synthesized up to 32767 lost frames at once after a
 forward jump. Also count across the 65535 -> 0 wrap correctly; frame 0
 was dropped as late.
Author: Alexander Traud <pabstraud@compuserve.com>
Author: Jonas Smedegaard <dr@jones.dk>
Author: Lukas Fabrykowski <tech@3angular.de>
Source: https://github.com/traud/asterisk-opus/blob/asterisk-13.7/enable_native_plc.patch
Bug-Debian: https://bugs.debian.org/1060275
Forwarded: not-needed
Last-Update: 2026-10-20
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
--- a/main/translate.c
+++ b/main/translate.c
@@ -414,6 +414,7 @@
 	pvt->f.offset = AST_FRIENDLY_OFFSET;
 	pvt->f.src = pvt->t->name;
 	pvt->f.data.ptr = pvt->outbuf.c;
+	pvt->f.seqno = 0x10000;
 
 	/*
 	 * If the translator has not provided a format
@@ -619,16 +620,62 @@
 	return ast_frdup(&res);
 }
 
+/*!
+ * \brief Largest RTP seqno gap concealed as lost packets, and the most a late
+ * packet may lag behind and still be dropped as late. A larger jump either way
+ * starts a new stream, e.g. after an SSRC change. 50 = 1 s of 20 ms packets.
+ */
+#define PLC_MAX_SEQNO_GAP 50
+
 /*! \brief do the actual translation */
 struct ast_frame *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f, int consume)
 {
+	const unsigned int rtp_seqno_max_value = 0xffff;
 	struct ast_trans_pvt *p = path;
-	struct ast_frame *out;
+	struct ast_frame *out_last, *out = NULL;
 	struct timeval delivery;
 	int has_timing_info;
 	long ts;
 	long len;
-	int seqno;
+	int seqno, frames_missing;
+
+	/* Determine the amount of lost packets for PLC */
+	/* But not at start with first frame = path->f.seqno is still 0x10000 */
+	/* But not when there is no sequence number = frame created internally */
+	/* But only if the decoder conceals lost frames itself = native PLC */
+	if (path->t->native_plc && (path->f.seqno <= rtp_seqno_max_value) && (path->f.seqno != f->seqno)) {
+		/* Distance from the previous frame, modulo 2^16: 1 = the next one */
+		unsigned int seqno_step = (f->seqno - path->f.seqno) & rtp_seqno_max_value;
+
+		if (seqno_step && seqno_step <= PLC_MAX_SEQNO_GAP + 1) {
+			frames_missing = seqno_step - 1;
+			if (frames_missing) {
+				ast_debug(3, "%d lost frame(s) before seqno %d\n", frames_missing, f->seqno);
+			}
+		} else if (seqno_step >= rtp_seqno_max_value + 1 - PLC_MAX_SEQNO_GAP) {
+			/* Out-of-order packet - more precise: late packet */
+			ast_debug(3, "Late frame seqno %d after %d dropped\n", f->seqno, path->f.seqno);
+			if (consume) {
+				ast_frfree(f);
+			}
+			/*
+			 * Do not pass late packets to any transcoding module, because that
+			 * confuses the state of any library (packets inter-depend). With
+			 * the next packet, this one is going to be treated as lost packet.
+			 */
+			return NULL;
+		} else {
+			/*
+			 * Too far off for loss or reordering: a new RTP stream (another
+			 * SSRC, a restarted sender). Continue with its numbering; there is
+			 * nothing to conceal and no frame is late.
+			 */
+			ast_debug(1, "Seqno jumped from %d to %d, new stream\n", path->f.seqno, f->seqno);
+			frames_missing = 0;
+		}
+	} else {
+		frames_missing = 0;
+	}
 
 	if (f->frametype == AST_FRAME_RTCP) {
 		/* Just pass the feedback to the right callback, if it exists.
@@ -669,19 +716,94 @@
 			 f->samples, ast_format_get_sample_rate(f->subclass.format)));
 	}
 	delivery = f->delivery;
-	for (out = f; out && p ; p = p->next) {
-		struct ast_frame *current = out;
 
-		do {
-			framein(p, current);
-			current = AST_LIST_NEXT(current, frame_list);
-		} while (current);
-		if (out != f) {
-			ast_frfree(out);
+	for (out_last = NULL; frames_missing + 1; frames_missing--) {
+		struct ast_frame *frame_to_translate, *inner_head;
+		struct ast_frame missed = {
+			.frametype = AST_FRAME_VOICE,
+			.subclass.format = f->subclass.format,
+			.datalen = 0,
+			/* In RTP, the amount of samples might change anytime  */
+			/* If that happened while frames got lost, what to do? */
+			.samples = f->samples, /* FIXME */
+			.src = __FUNCTION__,
+			.data.uint32 = 0,
+			.delivery.tv_sec = 0,
+			.delivery.tv_usec = 0,
+			.flags = 0,
+			/* RTP sequence number is between 0x0001 and 0xffff */
+			.seqno = (rtp_seqno_max_value + 1 + f->seqno - frames_missing) & rtp_seqno_max_value,
+		};
+
+		if (frames_missing) {
+			frame_to_translate = &missed;
+		} else {
+			frame_to_translate = f;
+		}
+
+		/* The translation path from one format to another might contain several steps */
+		/* out* collects the result for missed frame(s) and input frame(s) */
+		/* out is the result of the conversion of all frames, translated into the destination format */
+		/* out_last is the last frame in that list, to add frames faster */
+		for (p = path, inner_head = frame_to_translate; inner_head && p; p = p->next) {
+			struct ast_frame *current, *inner_last, *inner_prev = frame_to_translate;
+
+			/* inner* collects the result of each conversion step, the input for the next step */
+			/* inner_head is a list of frames created by each conversion step */
+			/* inner_last is the last frame in that list, to add frames faster */
+			for (inner_last = NULL, current = inner_head; current; current = AST_LIST_NEXT(current, frame_list)) {
+				struct ast_frame *tmp;
+
+				framein(p, current);
+				tmp = p->t->frameout(p);
+
+				if (!tmp) {
+					continue;
+				} else if (inner_last) {
+					struct ast_frame *t;
+
+					/* Determine the last frame of the list before appending to it */
+					while ((t = AST_LIST_NEXT(inner_last, frame_list))) {
+						inner_last = t;
+					}
+					AST_LIST_NEXT(inner_last, frame_list) = tmp;
+				} else {
+					inner_prev = inner_head;
+					inner_head = tmp;
+					inner_last = tmp;
+				}
+			}
+
+			/* The current step did not create any frames = no frames for the next step */
+			/* The steps are not lost because framein buffered those for the next input frame */
+			if (!inner_last) {
+				inner_prev = inner_head;
+				inner_head = NULL;
+			}
+			if (inner_prev != frame_to_translate) {
+				ast_frfree(inner_prev); /* Frees just the intermediate lists */
+			}
+		}
+
+		/* This frame created no frames after translation = continue with next frame */
+		/* The frame is not lost because framein buffered it to be combined with the next frame */
+		if (!inner_head) {
+			continue;
+		} else if (out_last) {
+			struct ast_frame *t;
+
+			/* Determine the last frame of the list before appending to it */
+			while ((t = AST_LIST_NEXT(out_last, frame_list))) {
+				out_last = t;
+			}
+			AST_LIST_NEXT(out_last, frame_list) = inner_head;
+		} else {
+			out = inner_head;
+			out_last = inner_head;
 		}
-		out = p->t->frameout(p);
 	}
 
+
 	if (!out) {
 		out = generate_interpolated_slin(path, f);
 	}
--- a/Xopus/enable_native_plc.patch
+++ /dev/null
@@ -1,156 +0,0 @@
---- a/main/translate.c	(Asterisk 13.17.2)
-+++ b/main/translate.c	(working copy)
-@@ -359,4 +359,5 @@
- 	pvt->f.src = pvt->t->name;
- 	pvt->f.data.ptr = pvt->outbuf.c;
-+	pvt->f.seqno = 0x10000;
- 
- 	/*
-@@ -531,11 +532,44 @@
- struct ast_frame *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f, int consume)
- {
-+	const unsigned int rtp_seqno_max_value = 0xffff;
- 	struct ast_trans_pvt *p = path;
--	struct ast_frame *out;
-+	struct ast_frame *out_last, *out = NULL;
- 	struct timeval delivery;
- 	int has_timing_info;
- 	long ts;
- 	long len;
--	int seqno;
-+	int seqno, frames_missing;
-+
-+	/* Determine the amount of lost packets for PLC */
-+	/* But not at start with first frame = path->f.seqno is still 0x10000 */
-+	/* But not when there is no sequence number = frame created internally */
-+	if ((path->f.seqno <= rtp_seqno_max_value) && (path->f.seqno != f->seqno)) {
-+		if (f->seqno < path->f.seqno) { /* seqno overrun situation */
-+			frames_missing = rtp_seqno_max_value + f->seqno - path->f.seqno - 1;
-+		} else {
-+			frames_missing = f->seqno - path->f.seqno - 1;
-+		}
-+		/* Out-of-order packet - more precise: late packet */
-+		if ((rtp_seqno_max_value + 1) / 2 < frames_missing) {
-+			if (consume) {
-+				ast_frfree(f);
-+			}
-+			/*
-+			 * Do not pass late packets to any transcoding module, because that
-+			 * confuses the state of any library (packets inter-depend). With
-+			 * the next packet, this one is going to be treated as lost packet.
-+			 */
-+			return NULL;
-+		}
-+
-+		if (frames_missing > 96) {
-+			struct ast_str *str = ast_str_alloca(256);
-+
-+			/* not DEBUG but NOTICE because of WARNING in main/cannel.c:__ast_queue_frame */
-+			ast_log(LOG_NOTICE, "%d lost frame(s) %d/%d %s\n", frames_missing, f->seqno, path->f.seqno, ast_translate_path_to_str(path, &str));
-+		}
-+	} else {
-+		frames_missing = 0;
-+	}
- 
- 	has_timing_info = ast_test_flag(f, AST_FRFLAG_HAS_TIMING_INFO);
-@@ -567,16 +601,91 @@
- 	}
- 	delivery = f->delivery;
--	for (out = f; out && p ; p = p->next) {
--		struct ast_frame *current = out;
- 
--		do {
--			framein(p, current);
--			current = AST_LIST_NEXT(current, frame_list);
--		} while (current);
--		if (out != f) {
--			ast_frfree(out);
-+	for (out_last = NULL; frames_missing + 1; frames_missing--) {
-+		struct ast_frame *frame_to_translate, *inner_head;
-+		struct ast_frame missed = {
-+			.frametype = AST_FRAME_VOICE,
-+			.subclass.format = f->subclass.format,
-+			.datalen = 0,
-+			/* In RTP, the amount of samples might change anytime  */
-+			/* If that happened while frames got lost, what to do? */
-+			.samples = f->samples, /* FIXME */
-+			.src = __FUNCTION__,
-+			.data.uint32 = 0,
-+			.delivery.tv_sec = 0,
-+			.delivery.tv_usec = 0,
-+			.flags = 0,
-+			/* RTP sequence number is between 0x0001 and 0xffff */
-+			.seqno = (rtp_seqno_max_value + 1 + f->seqno - frames_missing) & rtp_seqno_max_value,
-+		};
-+
-+		if (frames_missing) {
-+			frame_to_translate = &missed;
-+		} else {
-+			frame_to_translate = f;
-+		}
-+
-+		/* The translation path from one format to another might contain several steps */
-+		/* out* collects the result for missed frame(s) and input frame(s) */
-+		/* out is the result of the conversion of all frames, translated into the destination format */
-+		/* out_last is the last frame in that list, to add frames faster */
-+		for (p = path, inner_head = frame_to_translate; inner_head && p; p = p->next) {
-+			struct ast_frame *current, *inner_last, *inner_prev = frame_to_translate;
-+
-+			/* inner* collects the result of each conversion step, the input for the next step */
-+			/* inner_head is a list of frames created by each conversion step */
-+			/* inner_last is the last frame in that list, to add frames faster */
-+			for (inner_last = NULL, current = inner_head; current; current = AST_LIST_NEXT(current, frame_list)) {
-+				struct ast_frame *tmp;
-+
-+				framein(p, current);
-+				tmp = p->t->frameout(p);
-+
-+				if (!tmp) {
-+					continue;
-+				} else if (inner_last) {
-+					struct ast_frame *t;
-+
-+					/* Determine the last frame of the list before appending to it */
-+					while ((t = AST_LIST_NEXT(inner_last, frame_list))) {
-+						inner_last = t;
-+					}
-+					AST_LIST_NEXT(inner_last, frame_list) = tmp;
-+				} else {
-+					inner_prev = inner_head;
-+					inner_head = tmp;
-+					inner_last = tmp;
-+				}
-+			}
-+
-+			/* The current step did not create any frames = no frames for the next step */
-+			/* The steps are not lost because framein buffered those for the next input frame */
-+			if (!inner_last) {
-+				inner_prev = inner_head;
-+				inner_head = NULL;
-+			}
-+			if (inner_prev != frame_to_translate) {
-+				ast_frfree(inner_prev); /* Frees just the intermediate lists */
-+			}
-+		}
-+
-+		/* This frame created no frames after translation = continue with next frame */
-+		/* The frame is not lost because framein buffered it to be combined with the next frame */
-+		if (!inner_head) {
-+			continue;
-+		} else if (out_last) {
-+			struct ast_frame *t;
-+
-+			/* Determine the last frame of the list before appending to it */
-+			while ((t = AST_LIST_NEXT(out_last, frame_list))) {
-+				out_last = t;
-+			}
-+			AST_LIST_NEXT(out_last, frame_list) = inner_head;
-+		} else {
-+			out = inner_head;
-+			out_last = inner_head;
- 		}
--		out = p->t->frameout(p);
- 	}
-+
- 	if (out) {
- 		/* we have a frame, play with times */
