[FFmpeg-devel] [PATCH v0] Implement promeg decoder.

Romain Beauxis romain.beauxis at gmail.com
Thu Jan 16 22:08:28 EET 2025


This patch implements the decoding logic for the FEC error-
correction method described in the Pro-MPEG CoP #3-R2 FEC[1]

We are still in the process of testing this patch with our encoders but
I wanted to send it now in case it could use some early review.

The code seems clean enough and straight forward:
* The decoder stores packet using their index.
* Packets relevant to the decoding of the current packet are kept,
  using the received row and column FEC packets to determine the FEC
  matrix within with the current packet belongs to.
* Beside that, the decoder keeps a maximum number of packets and FEC
  packets.

There are some tricky bits around the RTSP socket initialization as the
current logic seems to open it twice, one for header read and one for
data read and the second open was not respecting the FEC options.

Likewise, I was not able to find a way to make the RTP protocol fail if
the underlying stream is not mpegts. However, the code will fail if
packets are not using a fixed size.

Please, let me know if you have any feedback to enhance this patch.

I plan on submiting it again for final review once we have concluded our
tests.

Thanks!
-- Romain

[1]: https://www.yumpu.com/en/document/read/8808550/pro-mpeg-code-of-practice-3-release-2-pro-mpeg-forum

---
 doc/protocols.texi          |  19 +-
 libavformat/Makefile        |   2 +-
 libavformat/prompeg.c       | 275 ++++++---------
 libavformat/prompeg.h       |  30 ++
 libavformat/prompeg_utils.c | 226 ++++++++++++
 libavformat/prompeg_utils.h |  58 ++++
 libavformat/prompegdec.c    | 667 ++++++++++++++++++++++++++++++++++++
 libavformat/prompegdec.h    |  82 +++++
 libavformat/rtpproto.c      |  25 +-
 libavformat/rtsp.c          |  13 +-
 10 files changed, 1228 insertions(+), 169 deletions(-)
 create mode 100644 libavformat/prompeg.h
 create mode 100644 libavformat/prompeg_utils.c
 create mode 100644 libavformat/prompeg_utils.h
 create mode 100644 libavformat/prompegdec.c
 create mode 100644 libavformat/prompegdec.h

diff --git a/doc/protocols.texi b/doc/protocols.texi
index ed70af4b33..a0bdcbb6fb 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -788,7 +788,7 @@ Pro-MPEG Code of Practice #3 Release 2 FEC protocol.
 The Pro-MPEG CoP#3 FEC is a 2D parity-check forward error correction mechanism
 for MPEG-2 Transport Streams sent over RTP.
 
-This protocol must be used in conjunction with the @code{rtp_mpegts} muxer and
+When muxing, the protocol must be used in conjunction with the @code{rtp_mpegts} muxer and
 the @code{rtp} protocol.
 
 The required syntax is:
@@ -799,6 +799,14 @@ The required syntax is:
 The destination UDP ports are @code{port + 2} for the column FEC stream
 and @code{port + 4} for the row FEC stream.
 
+When demuxing, the protocol must specified in the @code{rtp} url parameters:
+ at example
+rtp://@var{hostname}:@var{port}?fec=prompeg=@var{option}=@var{val}...
+ at end example
+
+Demuxing will fail if the RTP packets are not of fixed size or if their sequence
+number requires more than two bytes.
+
 This protocol accepts the following options:
 @table @option
 
@@ -808,6 +816,15 @@ The number of columns (4-20, LxD <= 100)
 @item d=@var{n}
 The number of rows (4-20, LxD <= 100)
 
+ at item min_decoder_packets=@var{n}
+Demuxing only: the number of packets to receive before decoding of the stream can begin.
+
+ at item max_decoder_packets=@var{n}
+Demuxing only: the maximum number of packets to buffer before a missing packet is dropped.
+
+ at item max_decoder_fec_packets=@var{n}
+Demuxing only: the maximum number of FEC packets to buffer before dropping new packets.
+
 @end table
 
 Example usage:
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 074efc118a..64e6e83096 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -688,7 +688,7 @@ OBJS-$(CONFIG_MD5_PROTOCOL)              += md5proto.o
 OBJS-$(CONFIG_MMSH_PROTOCOL)             += mmsh.o mms.o asf_tags.o
 OBJS-$(CONFIG_MMST_PROTOCOL)             += mmst.o mms.o asf_tags.o
 OBJS-$(CONFIG_PIPE_PROTOCOL)             += file.o
-OBJS-$(CONFIG_PROMPEG_PROTOCOL)          += prompeg.o
+OBJS-$(CONFIG_PROMPEG_PROTOCOL)          += prompeg_utils.o prompeg.o prompegdec.o
 OBJS-$(CONFIG_RTMP_PROTOCOL)             += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPE_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPS_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
diff --git a/libavformat/prompeg.c b/libavformat/prompeg.c
index 322eb6560a..98ecb83d9d 100644
--- a/libavformat/prompeg.c
+++ b/libavformat/prompeg.c
@@ -25,78 +25,16 @@
  * @author Vlad Tarca <vlad.tarca at gmail.com>
  */
 
-/*
- * Reminder:
-
- [RFC 2733] FEC Packet Structure
-
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                         RTP Header                            |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                         FEC Header                            |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                         FEC Payload                           |
-   |                                                               |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
-
- [RFC 3550] RTP header
-
-    0                   1                   2                   3
-    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                           timestamp                           |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |           synchronization source (SSRC) identifier            |
-   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
-   |            contributing source (CSRC) identifiers             |
-   |                             ....                              |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- [RFC 3550] RTP header extension (after CSRC)
-
-    0                   1                   2                   3
-    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |      defined by profile       |           length              |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                        header extension                       |
-   |                             ....                              |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- [Pro-MPEG COP3] FEC Header
-
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |      SNBase low bits          |        length recovery        |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |E| PT recovery |                 mask                          |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |                          TS recovery                          |
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-   |X|D|type |index|    offset     |      NA       |SNBase ext bits|
-   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- */
-
+#include "libavformat/network.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/random_seed.h"
 #include "avformat.h"
+#include "prompeg_utils.h"
+#include "prompegdec.h"
+#include "prompeg.h"
 #include "config.h"
-#include "url.h"
-
-#define PROMPEG_RTP_PT 0x60
-#define PROMPEG_FEC_COL 0x0
-#define PROMPEG_FEC_ROW 0x1
-
-typedef struct PrompegFec {
-    uint16_t sn;
-    uint32_t ts;
-    uint8_t *bitstring;
-} PrompegFec;
 
 typedef struct PrompegContext {
     const AVClass *class;
@@ -114,6 +52,12 @@ typedef struct PrompegContext {
     int rtp_buf_size;
     int init;
     int first;
+
+    // Decoder only
+    PrompegDecoder *decoder;
+    int min_decoder_packets;
+    int max_decoder_packets;
+    int max_decoder_fec_packets;
 } PrompegContext;
 
 #define OFFSET(x) offsetof(PrompegContext, x)
@@ -123,6 +67,9 @@ static const AVOption options[] = {
     { "ttl",   "Time to live (in milliseconds, multicast only)", OFFSET(ttl), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = E },
     { "l", "FEC L", OFFSET(l), AV_OPT_TYPE_INT, { .i64 =  5 }, 4, 20, .flags = E },
     { "d", "FEC D", OFFSET(d), AV_OPT_TYPE_INT, { .i64 =  5 }, 4, 20, .flags = E },
+    { "min_decoder_packets", "Min decoder packets", OFFSET(min_decoder_packets), AV_OPT_TYPE_INT, { .i64 =  8 }, 0, INT_MAX, .flags = E },
+    { "max_decoder_packets", "Max decoder packets", OFFSET(max_decoder_packets), AV_OPT_TYPE_INT, { .i64 =  50 }, 0, INT_MAX, .flags = E },
+    { "max_decoder_fec_packets", "Max decoder FEC packets", OFFSET(max_decoder_fec_packets), AV_OPT_TYPE_INT, { .i64 =  50 }, 0, INT_MAX, .flags = E },
     { NULL }
 };
 
@@ -133,50 +80,9 @@ static const AVClass prompeg_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
-static void xor_fast(const uint8_t *in1, const uint8_t *in2, uint8_t *out, int size) {
-    int i, n, s;
-
-#if HAVE_FAST_64BIT
-    uint64_t v1, v2;
-
-    n = size / sizeof (uint64_t);
-    s = n * sizeof (uint64_t);
-
-    for (i = 0; i < n; i++) {
-        v1 = AV_RN64A(in1);
-        v2 = AV_RN64A(in2);
-        AV_WN64A(out, v1 ^ v2);
-        in1 += 8;
-        in2 += 8;
-        out += 8;
-    }
-#else
-    uint32_t v1, v2;
-
-    n = size / sizeof (uint32_t);
-    s = n * sizeof (uint32_t);
-
-    for (i = 0; i < n; i++) {
-        v1 = AV_RN32A(in1);
-        v2 = AV_RN32A(in2);
-        AV_WN32A(out, v1 ^ v2);
-        in1 += 4;
-        in2 += 4;
-        out += 4;
-    }
-#endif
-
-    n = size - s;
-
-    for (i = 0; i < n; i++) {
-        out[i] = in1[i] ^ in2[i];
-    }
-}
-
 static int prompeg_create_bitstring(URLContext *h, const uint8_t *buf, int size,
         uint8_t **bitstring) {
     PrompegContext *s = h->priv_data;
-    uint8_t *b;
 
     if (size < 12 || (buf[0] & 0xc0) != 0x80 || (buf[1] & 0x7f) != 0x21) {
         av_log(h, AV_LOG_ERROR, "Unsupported stream format (expected MPEG-TS over RTP)\n");
@@ -192,24 +98,8 @@ static int prompeg_create_bitstring(URLContext *h, const uint8_t *buf, int size,
         av_log(h, AV_LOG_ERROR, "Failed to allocate the bitstring buffer\n");
         return AVERROR(ENOMEM);
     }
-    b = *bitstring;
-
-    // P, X, CC
-    b[0] = buf[0] & 0x3f;
-    // M, PT
-    b[1] = buf[1];
-    // Timestamp
-    b[2] = buf[4];
-    b[3] = buf[5];
-    b[4] = buf[6];
-    b[5] = buf[7];
-    /*
-     * length_recovery: the unsigned network-ordered sum of lengths of CSRC,
-     * padding, extension and media payload
-     */
-    AV_WB16(b + 6, s->length_recovery);
-    // Payload
-    memcpy(b + 8, buf + 12, s->length_recovery);
+
+    ff_prompeg_pack_bitstring(*bitstring, buf, size);
 
     return 0;
 }
@@ -218,48 +108,12 @@ static int prompeg_write_fec(URLContext *h, PrompegFec *fec, uint8_t type) {
     PrompegContext *s = h->priv_data;
     URLContext *hd;
     uint8_t *buf = s->rtp_buf; // zero-filled
-    uint8_t *b = fec->bitstring;
     uint16_t sn;
     int ret;
 
     sn = type == PROMPEG_FEC_COL ? ++s->rtp_col_sn : ++s->rtp_row_sn;
 
-    // V, P, X, CC
-    buf[0] = 0x80 | (b[0] & 0x3f);
-    // M, PT
-    buf[1] = (b[1] & 0x80) | PROMPEG_RTP_PT;
-    // SN
-    AV_WB16(buf + 2, sn);
-    // TS
-    AV_WB32(buf + 4, fec->ts);
-    // CSRC=0
-    //AV_WB32(buf + 8, 0);
-    // SNBase low bits
-    AV_WB16(buf + 12, fec->sn);
-    // Length recovery
-    buf[14] = b[6];
-    buf[15] = b[7];
-    // E=1, PT recovery
-    buf[16] = 0x80 | b[1];
-    // Mask=0
-    //buf[17] = 0x0;
-    //buf[18] = 0x0;
-    //buf[19] = 0x0;
-    // TS recovery
-    buf[20] = b[2];
-    buf[21] = b[3];
-    buf[22] = b[4];
-    buf[23] = b[5];
-    // X=0, D, type=0, index=0
-    buf[24] = type == PROMPEG_FEC_COL ? 0x0 : 0x40;
-    // offset
-    buf[25] = type == PROMPEG_FEC_COL ? s->l : 0x1;
-    // NA
-    buf[26] = type == PROMPEG_FEC_COL ? s->d : s->l;
-    // SNBase ext bits=0
-    //buf[27] = 0x0;
-    // Payload
-    memcpy(buf + 28, b + 8, s->length_recovery);
+    ff_prompeg_pack_fec_packet(buf, fec, sn, type, s->l, s->d, s->rtp_buf_size);
 
     hd = type == PROMPEG_FEC_COL ? s->fec_col_hd : s->fec_row_hd;
     ret = ffurl_write(hd, buf, s->rtp_buf_size);
@@ -293,6 +147,9 @@ static int prompeg_open(URLContext *h, const char *uri, int flags) {
         av_dict_set_int(&udp_opts, "ttl", s->ttl, 0);
     }
 
+    if (h->flags & AVIO_FLAG_READ)
+        flags |= AVIO_FLAG_NONBLOCK;
+
     ff_url_join(buf, sizeof (buf), "udp", NULL, hostname, rtp_port + 2, NULL);
     if (ffurl_open_whitelist(&s->fec_col_hd, buf, flags, &h->interrupt_callback,
             &udp_opts, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
@@ -381,6 +238,30 @@ fail:
     return AVERROR(ENOMEM);
 }
 
+int ff_prompeg_add_packet(URLContext *h, const uint8_t *buf, int size) {
+    PrompegContext *s = h->priv_data;
+    int ret;
+
+    if (s->init) {
+        ret = prompeg_init(h, buf, size);
+        if (ret < 0)
+            return ret;
+
+        h->flags |= AVIO_FLAG_NONBLOCK;
+
+        s->decoder = ff_prompegdec_alloc_decoder(h, s->l, s->d,
+            s->packet_size, s->rtp_buf_size, s->bitstring_size,
+            s->min_decoder_packets, s->max_decoder_packets,
+            s->max_decoder_fec_packets);
+
+        if (!s->decoder)
+            return AVERROR(ENOMEM);
+    }
+
+    av_log(h, AV_LOG_DEBUG, "Packet add, index: %d\n", AV_RB16(buf + 2));
+    return ff_prompegdec_add_packet(s->decoder, PROMPEGDEC_PACKET, AV_RB16(buf + 2), buf, size);
+}
+
 static int prompeg_write(URLContext *h, const uint8_t *buf, int size) {
     PrompegContext *s = h->priv_data;
     PrompegFec *fec_tmp;
@@ -407,7 +288,7 @@ static int prompeg_write(URLContext *h, const uint8_t *buf, int size) {
         s->fec_row->sn = AV_RB16(buf + 2);
         s->fec_row->ts = AV_RB32(buf + 4);
     } else {
-        xor_fast(s->fec_row->bitstring, bitstring, s->fec_row->bitstring,
+        ff_prompeg_xor_fast(s->fec_row->bitstring, bitstring, s->fec_row->bitstring,
                 s->bitstring_size);
     }
 
@@ -423,7 +304,7 @@ static int prompeg_write(URLContext *h, const uint8_t *buf, int size) {
         s->fec_col_tmp[col_idx]->sn = AV_RB16(buf + 2);
         s->fec_col_tmp[col_idx]->ts = AV_RB32(buf + 4);
     } else {
-        xor_fast(s->fec_col_tmp[col_idx]->bitstring, bitstring,
+        ff_prompeg_xor_fast(s->fec_col_tmp[col_idx]->bitstring, bitstring,
                 s->fec_col_tmp[col_idx]->bitstring, s->bitstring_size);
     }
 
@@ -447,6 +328,69 @@ end:
     return ret;
 }
 
+static int prompeg_read_fec_packets(URLContext *h)
+{
+    PrompegContext *s = h->priv_data;
+    int i, ret;
+    PrompegDecoderPacketType packet_type;
+    URLContext *url_context;
+
+    if (ff_check_interrupt(&h->interrupt_callback))
+        return AVERROR_EXIT;
+
+    for (i = 0; i < 2; i++) {
+        url_context = i == 0 ? s->fec_row_hd : s->fec_col_hd;
+        packet_type = i == 0
+            ? PROMPEGDEC_FEC_ROW_PACKET
+            : PROMPEGDEC_FEC_COL_PACKET;
+
+        for (;;) {
+            ret = ffurl_read(url_context, s->rtp_buf, s->rtp_buf_size);
+            av_log(h, AV_LOG_DEBUG, "FEC %s read %d\n",
+                i == 0 ? "row" : "col", ret);
+
+            if (ret == AVERROR(EAGAIN))
+                break;
+
+            if (ret < 0)
+                return ret;
+
+            if (ret != s->rtp_buf_size)
+                return AVERROR(EINVAL); 
+
+            av_log(h, AV_LOG_DEBUG, "FEC packet add: type: %s, index: %d\n",
+                    packet_type == PROMPEGDEC_FEC_ROW_PACKET ? "row" : "col",
+                    AV_RB16(s->rtp_buf + 12));
+
+            ret = ff_prompegdec_add_packet(s->decoder, packet_type,
+                    AV_RB16(s->rtp_buf + 12), s->rtp_buf, s->rtp_buf_size);
+
+            if (ret < 0)
+                return ret;
+        }
+    }
+
+    return 0;
+}
+
+static int prompeg_read(URLContext *h, uint8_t *buf, int size)
+{
+    PrompegContext *s = h->priv_data;
+    int ret;
+
+    if (s->init)
+        return AVERROR(EAGAIN);
+
+    prompeg_read_fec_packets(h);
+
+    ret = ff_prompegdec_read_packet(s->decoder, buf, size);
+
+    if (4 < ret)
+        av_log(h, AV_LOG_DEBUG, "Got packet %d from FEC decoder\n", AV_RB16(buf + 2));
+
+    return ret;
+}
+
 static int prompeg_close(URLContext *h) {
     PrompegContext *s = h->priv_data;
     int i;
@@ -463,6 +407,8 @@ static int prompeg_close(URLContext *h) {
     }
     av_freep(&s->rtp_buf);
 
+    ff_prompegdec_free_decoder(s->decoder);
+
     return 0;
 }
 
@@ -470,6 +416,7 @@ const URLProtocol ff_prompeg_protocol = {
     .name                      = "prompeg",
     .url_open                  = prompeg_open,
     .url_write                 = prompeg_write,
+    .url_read                  = prompeg_read,
     .url_close                 = prompeg_close,
     .priv_data_size            = sizeof(PrompegContext),
     .flags                     = URL_PROTOCOL_FLAG_NETWORK,
diff --git a/libavformat/prompeg.h b/libavformat/prompeg.h
new file mode 100644
index 0000000000..62653a9a16
--- /dev/null
+++ b/libavformat/prompeg.h
@@ -0,0 +1,30 @@
+/*
+ * Pro-MPEG Code of Practice #3 Release 2 FEC
+ * Copyright (c) 2016 Mobibase, France (http://www.mobibase.com)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Pro-MPEG Code of Practice #3 Release 2 FEC protocol
+ * @author Vlad Tarca <vlad.tarca at gmail.com>
+ */
+
+#include "url.h"
+
+int ff_prompeg_add_packet(URLContext *h, const uint8_t *buf, int size);
diff --git a/libavformat/prompeg_utils.c b/libavformat/prompeg_utils.c
new file mode 100644
index 0000000000..81517d2202
--- /dev/null
+++ b/libavformat/prompeg_utils.c
@@ -0,0 +1,226 @@
+/*
+ * Pro-MPEG Code of Practice #3 Release 2 FEC
+ * Copyright (c) 2025 Radio France (https://radiofrance.fr)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Pro-MPEG Code of Practice #3 Release 2 FEC protocol -- Utils file
+ * @author Romain Beauxis <romain.beauxis at gmail.com>
+ */
+
+/*
+ * Reminder:
+
+ [RFC 2733] FEC Packet Structure
+
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                         RTP Header                            |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                         FEC Header                            |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                         FEC Payload                           |
+   |                                                               |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ [RFC 3550] RTP header
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                           timestamp                           |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |           synchronization source (SSRC) identifier            |
+   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+   |            contributing source (CSRC) identifiers             |
+   |                             ....                              |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ [RFC 3550] RTP header extension (after CSRC)
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |      defined by profile       |           length              |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                        header extension                       |
+   |                             ....                              |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ [Pro-MPEG COP3] FEC Header
+
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |      SNBase low bits          |        length recovery        |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |E| PT recovery |                 mask                          |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |                          TS recovery                          |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   |X|D|type |index|    offset     |      NA       |SNBase ext bits|
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ */
+
+#include "prompeg_utils.h"
+#include "config.h"
+#include "libavutil/intreadwrite.h"
+
+#include <string.h>
+
+void ff_prompeg_xor_fast(
+    const uint8_t *in1, const uint8_t *in2, uint8_t *out, int size)
+{
+    int i, n, s;
+
+#if HAVE_FAST_64BIT
+    uint64_t v1, v2;
+
+    n = size / sizeof(uint64_t);
+    s = n * sizeof(uint64_t);
+
+    for (i = 0; i < n; i++) {
+        v1 = AV_RN64A(in1);
+        v2 = AV_RN64A(in2);
+        AV_WN64A(out, v1 ^ v2);
+        in1 += 8;
+        in2 += 8;
+        out += 8;
+    }
+#else
+    uint32_t v1, v2;
+
+    n = size / sizeof(uint32_t);
+    s = n * sizeof(uint32_t);
+
+    for (i = 0; i < n; i++) {
+        v1 = AV_RN32A(in1);
+        v2 = AV_RN32A(in2);
+        AV_WN32A(out, v1 ^ v2);
+        in1 += 4;
+        in2 += 4;
+        out += 4;
+    }
+#endif
+
+    n = size - s;
+
+    for (i = 0; i < n; i++) {
+        out[i] = in1[i] ^ in2[i];
+    }
+}
+
+void ff_prompeg_pack_bitstring(uint8_t *b, const uint8_t *buf, int size)
+{
+    // P, X, CC
+    b[0] = buf[0] & 0x3f;
+    // M, PT
+    b[1] = buf[1];
+    // Timestamp
+    b[2] = buf[4];
+    b[3] = buf[5];
+    b[4] = buf[6];
+    b[5] = buf[7];
+    AV_WB16(b + 6, size - 12);
+    // Payload
+    memcpy(b + 8, buf + 12, size - 12);
+}
+
+void ff_prompeg_pack_fec_packet(uint8_t *buf, PrompegFec *fec, uint16_t sn,
+    uint8_t type, int l, int d, int size)
+{
+    uint8_t *b = fec->bitstring;
+
+    // V, P, X, CC
+    buf[0] = 0x80 | (b[0] & 0x3f);
+    // M, PT
+    buf[1] = (b[1] & 0x80) | PROMPEG_RTP_PT;
+    // SN
+    AV_WB16(buf + 2, sn);
+    // TS
+    AV_WB32(buf + 4, fec->ts);
+    // CSRC=0
+    // AV_WB32(buf + 8, 0);
+    // SNBase low bits
+    AV_WB16(buf + 12, fec->sn);
+    // Length recovery
+    buf[14] = b[6];
+    buf[15] = b[7];
+    // E=1, PT recovery
+    buf[16] = 0x80 | b[1];
+    // Mask=0
+    // buf[17] = 0x0;
+    // buf[18] = 0x0;
+    // buf[19] = 0x0;
+    // TS recovery
+    buf[20] = b[2];
+    buf[21] = b[3];
+    buf[22] = b[4];
+    buf[23] = b[5];
+    // X=0, D, type=0, index=0
+    buf[24] = type == PROMPEG_FEC_COL ? 0x0 : 0x40;
+    // offset
+    buf[25] = type == PROMPEG_FEC_COL ? l : 0x1;
+    // NA
+    buf[26] = type == PROMPEG_FEC_COL ? d : l;
+    // SNBase ext bits=0
+    // buf[27] = 0x0;
+    // Payload
+    memcpy(buf + 28, b + 8, size - 8);
+}
+
+void ff_prompeg_pack_fec_bitstring(uint8_t *b, const uint8_t *buf, int size)
+{
+    // P, X, CC
+    b[0] = buf[0] & 0x3f;
+    // M, PT
+    b[1] = buf[16] & 0x3f;
+    // Timestamp
+    b[2] = buf[20];
+    b[3] = buf[21];
+    b[4] = buf[22];
+    b[5] = buf[23];
+    // Length recovery
+    b[6] = buf[14];
+    b[7] = buf[15];
+
+    memcpy(b + 8, buf + 28, size - 28);
+}
+
+void ff_prompeg_restore_packet(uint8_t *packet, const uint8_t *buf, uint8_t m,
+    uint32_t ssrc, uint16_t index, int size)
+{
+    // P, X, CC
+    packet[0] = buf[0] | 0x80;
+    // M, PT
+    packet[1] = buf[1] | (m << 7);
+    // Packet index
+    AV_WB16(packet + 2, index);
+    // Timestamp
+    packet[4] = buf[2];
+    packet[5] = buf[3];
+    packet[6] = buf[4];
+    packet[7] = buf[5];
+    // SSRC
+    memcpy(packet + 8, &ssrc, 4);
+    // Payload
+    memcpy(packet + 12, buf + 8, size - 8);
+}
diff --git a/libavformat/prompeg_utils.h b/libavformat/prompeg_utils.h
new file mode 100644
index 0000000000..088c1e0ba9
--- /dev/null
+++ b/libavformat/prompeg_utils.h
@@ -0,0 +1,58 @@
+/*
+ * Pro-MPEG Code of Practice #3 Release 2 FEC
+ * Copyright (c) 2025 Radio France (https://radiofrance.fr)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Pro-MPEG Code of Practice #3 Release 2 FEC protocol -- Utils file
+ * @author Romain Beauxis <romain.beauxis at gmail.com>
+ */
+
+#ifndef AVFORMAT_PROMPEG_UTILS_H
+#define AVFORMAT_PROMPEG_UTILS_H
+
+#include <stdint.h>
+
+typedef struct PrompegFec {
+    uint16_t sn;
+    uint32_t ts;
+    uint8_t *bitstring;
+} PrompegFec;
+
+#define PROMPEG_RTP_PT 0x60
+#define PROMPEG_FEC_COL 0x0
+#define PROMPEG_FEC_ROW 0x1
+
+void ff_prompeg_xor_fast(
+    const uint8_t *in1, const uint8_t *in2, uint8_t *out, int size);
+
+void ff_prompeg_pack_bitstring(
+    uint8_t *bitstring, const uint8_t *packet, int packet_size);
+
+void ff_prompeg_pack_fec_bitstring(
+    uint8_t *bitstring, const uint8_t *fec_packet, int fec_packet_size);
+
+void ff_prompeg_pack_fec_packet(uint8_t *fec_packet, PrompegFec *fec,
+    uint16_t sn, uint8_t type, int l, int d, int bitstring_size);
+
+void ff_prompeg_restore_packet(uint8_t *packet, const uint8_t *bitstring,
+    uint8_t m, uint32_t ssrc, uint16_t index, int bitstring_size);
+
+#endif
diff --git a/libavformat/prompegdec.c b/libavformat/prompegdec.c
new file mode 100644
index 0000000000..569285fdfd
--- /dev/null
+++ b/libavformat/prompegdec.c
@@ -0,0 +1,667 @@
+/*
+ * Pro-MPEG Code of Practice #3 Release 2 FEC
+ * Copyright (c) 2025 Radio France (https://radiofrance.fr)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Pro-MPEG Code of Practice #3 Release 2 FEC protocol -- Decoder logic
+ * @author Romain Beauxis <romain.beauxis at gmail.com>
+ */
+
+#include "prompegdec.h"
+#include "libavutil/error.h"
+#include "libavutil/mem.h"
+#include "prompeg_utils.h"
+
+#include <memory.h>
+
+static const AVClass prompegdec_class = {
+    .class_name = "Prompeg Decoder",
+    .item_name = av_default_item_name,
+    .version = LIBAVUTIL_VERSION_INT,
+};
+
+static PrompegDecoderPacket *prompegdec_alloc_packet(uint16_t index, int length)
+{
+    PrompegDecoderPacket *packet = av_mallocz(sizeof(PrompegDecoderPacket));
+    if (!packet)
+        return NULL;
+
+    packet->index = index;
+    packet->bytes = av_malloc(length);
+    if (!packet->bytes) {
+        av_free(packet->bytes);
+        av_free(packet);
+        return NULL;
+    }
+
+    return packet;
+}
+
+PrompegDecoder *ff_prompegdec_alloc_decoder(URLContext *url_context, int l,
+    int d, int packet_size, int fec_packet_size, int bitstring_size,
+    int min_packets, int max_packets, int max_fec_packets)
+{
+    PrompegDecoder *decoder = av_mallocz(sizeof(PrompegDecoder));
+    if (!decoder)
+        return NULL;
+
+    decoder->av_class = &prompegdec_class;
+    decoder->l = l;
+    decoder->d = d;
+    decoder->packet_size = packet_size;
+    decoder->fec_packet_size = fec_packet_size;
+    decoder->bitstring_size = bitstring_size;
+    decoder->url_context = url_context;
+    decoder->min_packets = min_packets;
+    decoder->max_packets = max_packets;
+    decoder->max_fec_packets = max_fec_packets;
+
+    decoder->restore_buffer =
+        av_mallocz(sizeof(PrompegDecoderPacket *) * (l < d ? d : l));
+    if (!decoder->restore_buffer)
+        goto fail;
+
+    decoder->tmp_bitstring = av_malloc(bitstring_size);
+    if (!decoder->tmp_bitstring)
+        goto fail;
+
+    decoder->bitstring = av_malloc(bitstring_size);
+    if (!decoder->bitstring)
+        goto fail;
+
+    return decoder;
+fail:
+    av_free(decoder->tmp_bitstring);
+    av_free(decoder->bitstring);
+    av_free(decoder->restore_buffer);
+    av_free(decoder);
+    return NULL;
+}
+
+static void prompegdec_free_packet(PrompegDecoderPacket *packet)
+{
+    if (!packet)
+        return;
+
+    av_free(packet->bytes);
+    av_free(packet);
+}
+
+static int prompegdec_free_tree_elem(void *opaque, void *elem)
+{
+    prompegdec_free_packet((PrompegDecoderPacket *)elem);
+    return 0;
+}
+
+void ff_prompegdec_free_decoder(PrompegDecoder *decoder)
+{
+    if (!decoder)
+        return;
+
+    av_tree_enumerate(
+        decoder->fec_row_packets, NULL, NULL, prompegdec_free_tree_elem);
+    av_tree_destroy(decoder->fec_row_packets);
+    av_tree_enumerate(
+        decoder->fec_col_packets, NULL, NULL, prompegdec_free_tree_elem);
+    av_tree_destroy(decoder->fec_col_packets);
+    av_tree_enumerate(decoder->packets, NULL, NULL, prompegdec_free_tree_elem);
+    av_tree_destroy(decoder->packets);
+    av_free(decoder->restore_buffer);
+    av_free(decoder);
+}
+
+typedef struct PrompegDecoderPacketEnum {
+    uint16_t min_index;
+    uint16_t max_index;
+    PrompegDecoderPacket *packet;
+} PrompegDecoderPacketEnum;
+
+static int prompegdec_get_first_packet_enum(void *opaque, void *elem)
+{
+    PrompegDecoderPacketEnum *get_packet = (PrompegDecoderPacketEnum *)opaque;
+    PrompegDecoderPacket *packet = (PrompegDecoderPacket *)elem;
+
+    if (packet->index <= get_packet->min_index)
+        return 0;
+
+    if (get_packet->max_index <= packet->index)
+        return 0;
+
+    if (!get_packet->packet) {
+        get_packet->packet = packet;
+        return 0;
+    }
+
+    if (get_packet->packet->index <= packet->index)
+        return 0;
+
+    get_packet->packet = packet;
+    return 0;
+}
+
+static int prompegdec_get_first_packet_cmp(void *opaque, void *elem)
+{
+    PrompegDecoderPacketEnum *get_packet = (PrompegDecoderPacketEnum *)opaque;
+    PrompegDecoderPacket *packet = (PrompegDecoderPacket *)elem;
+
+    if (packet->index <= get_packet->min_index)
+        return -1;
+
+    if (get_packet->max_index <= packet->index)
+        return 1;
+
+    return 0;
+}
+
+static PrompegDecoderPacket *prompegdec_get_first_packet(
+    struct AVTreeNode *t, uint16_t min_index, uint16_t max_index)
+{
+    PrompegDecoderPacketEnum get_packet;
+    get_packet.min_index = min_index;
+    get_packet.max_index = max_index;
+    get_packet.packet = NULL;
+
+    av_tree_enumerate(t, &get_packet, prompegdec_get_first_packet_cmp,
+        prompegdec_get_first_packet_enum);
+
+    return get_packet.packet;
+}
+
+static int prompegdec_cmp_packet(const void *left, const void *right)
+{
+    PrompegDecoderPacket *left_packet = (PrompegDecoderPacket *)left;
+    PrompegDecoderPacket *right_packet = (PrompegDecoderPacket *)right;
+
+    if (left_packet->index == right_packet->index)
+        return 0;
+    if (left_packet->index < right_packet->index)
+        return -1;
+    return 1;
+}
+
+static PrompegDecoderPacket *prompegdec_find_packet(
+    struct AVTreeNode *t, uint16_t index)
+{
+    PrompegDecoderPacket packet_ref;
+
+    packet_ref.index = index;
+
+    return av_tree_find(t, &packet_ref, prompegdec_cmp_packet, NULL);
+}
+
+static int prompegdec_remove_packet(
+    struct AVTreeNode **t, PrompegDecoderPacket *packet)
+{
+    struct AVTreeNode *next = NULL;
+
+    av_tree_insert(t, packet, prompegdec_cmp_packet, &next);
+    prompegdec_free_packet(packet);
+    av_free(next);
+
+    return 0;
+}
+
+static int prompegdec_insert_packet(PrompegDecoder *decoder,
+    PrompegDecoderPacketType type, PrompegDecoderPacket *packet)
+{
+    struct AVTreeNode *next = av_tree_node_alloc();
+    struct AVTreeNode **packets;
+
+    if (!next)
+        return AVERROR(ENOMEM);
+
+    switch (type) {
+    case PROMPEGDEC_FEC_ROW_PACKET:
+        packets = &decoder->fec_row_packets;
+        break;
+
+    case PROMPEGDEC_FEC_COL_PACKET:
+        packets = &decoder->fec_col_packets;
+        break;
+
+    default:
+        packets = &decoder->packets;
+    }
+
+    av_tree_insert(packets, packet, prompegdec_cmp_packet, &next);
+
+    if (next) {
+        av_free(next);
+        av_free(packet);
+        AVERROR(EINVAL);
+    }
+
+    switch (type) {
+    case PROMPEGDEC_FEC_ROW_PACKET:
+        decoder->fec_row_packets_count++;
+        break;
+
+    case PROMPEGDEC_FEC_COL_PACKET:
+        decoder->fec_col_packets_count++;
+        break;
+
+    default:
+        decoder->packets_count++;
+    }
+
+    return 0;
+}
+
+typedef struct PrompegDecoderFecEnum {
+    int l;
+    int d;
+    uint16_t packet_index;
+    PrompegDecoderPacket *fec_packet;
+} PrompegDecoderFecEnum;
+
+static PrompegDecoderPacket *prompegdec_fec_packet(PrompegDecoder *decoder,
+    struct AVTreeNode *t, uint16_t packet_index,
+    int (*cmp)(void *opaque, void *elem), int (*enu)(void *opaque, void *elem))
+{
+    PrompegDecoderFecEnum fec_enum;
+    fec_enum.l = decoder->l;
+    fec_enum.d = decoder->d;
+    fec_enum.packet_index = packet_index;
+    fec_enum.fec_packet = NULL;
+
+    av_tree_enumerate(t, &fec_enum, cmp, enu);
+
+    return fec_enum.fec_packet;
+}
+
+static int prompegdec_fec_row_cmp(void *opaque, void *elem)
+{
+    PrompegDecoderFecEnum *row_enum = (PrompegDecoderFecEnum *)opaque;
+    PrompegDecoderPacket *fec_packet = (PrompegDecoderPacket *)elem;
+
+    if (fec_packet->index <= row_enum->packet_index - row_enum->l)
+        return -1;
+
+    if (row_enum->packet_index < fec_packet->index)
+        return 1;
+
+    return 0;
+}
+
+static int prompegdec_fec_row_enum(void *opaque, void *elem)
+{
+    PrompegDecoderFecEnum *row_enum = (PrompegDecoderFecEnum *)opaque;
+    PrompegDecoderPacket *fec_packet = (PrompegDecoderPacket *)elem;
+
+    if (fec_packet->index <= row_enum->packet_index &&
+        row_enum->packet_index < fec_packet->index + row_enum->l)
+        row_enum->fec_packet = fec_packet;
+
+    return 0;
+}
+
+static PrompegDecoderPacket *prompegdec_fec_row_packet(
+    PrompegDecoder *decoder, uint16_t packet_index)
+{
+    return prompegdec_fec_packet(decoder, decoder->fec_row_packets,
+        packet_index, prompegdec_fec_row_cmp, prompegdec_fec_row_enum);
+}
+
+static int prompegdec_fec_col_cmp(void *opaque, void *elem)
+{
+    PrompegDecoderFecEnum *col_enum = (PrompegDecoderFecEnum *)opaque;
+    PrompegDecoderPacket *fec_packet = (PrompegDecoderPacket *)elem;
+
+    if (fec_packet->index <= col_enum->packet_index - col_enum->l * col_enum->d)
+        return -1;
+
+    if (col_enum->packet_index < fec_packet->index)
+        return 1;
+
+    return 0;
+}
+
+static int prompegdec_fec_col_enum(void *opaque, void *elem)
+{
+    PrompegDecoderFecEnum *col_enum = (PrompegDecoderFecEnum *)opaque;
+    PrompegDecoderPacket *fec_packet = (PrompegDecoderPacket *)elem;
+    int col;
+
+    for (col = 0; col < col_enum->d; col++)
+        if (fec_packet->index + col * col_enum->l == col_enum->packet_index)
+            col_enum->fec_packet = fec_packet;
+
+    return 0;
+}
+
+static PrompegDecoderPacket *prompegdec_fec_col_packet(
+    PrompegDecoder *decoder, uint16_t packet_index)
+{
+    return prompegdec_fec_packet(decoder, decoder->fec_col_packets,
+        packet_index, prompegdec_fec_col_cmp, prompegdec_fec_col_enum);
+}
+
+static int prompeg_restore_packets_buffer(PrompegDecoder *decoder,
+    uint16_t index, int buffer_length, PrompegDecoderPacket *fec_packet,
+    PrompegDecoderPacketType type)
+{
+    PrompegDecoderPacket *packet = NULL;
+    int i, first = 1;
+    uint8_t m;
+    uint32_t ssrc;
+
+    ff_prompeg_pack_fec_bitstring(
+        decoder->bitstring, fec_packet->bytes, decoder->fec_packet_size);
+
+    for (i = 0; i < buffer_length; i++) {
+        if (decoder->restore_buffer[i]) {
+            if (first) {
+                m = decoder->restore_buffer[i]->bytes[1] >> 7;
+                memcpy(&ssrc, decoder->restore_buffer[i]->bytes + 8, 4);
+                first = 0;
+            }
+
+            ff_prompeg_pack_bitstring(decoder->tmp_bitstring,
+                decoder->restore_buffer[i]->bytes, decoder->packet_size);
+            ff_prompeg_xor_fast(decoder->bitstring, decoder->tmp_bitstring,
+                decoder->bitstring, decoder->bitstring_size);
+        }
+    }
+
+    packet = prompegdec_alloc_packet(index, decoder->packet_size);
+    if (!packet)
+        return AVERROR(ENOMEM);
+
+    ff_prompeg_restore_packet(packet->bytes, decoder->bitstring, m, ssrc, index,
+        decoder->bitstring_size);
+
+    av_log(decoder, AV_LOG_INFO, "Restored lost packet at index %d\n", index);
+
+    return prompegdec_insert_packet(decoder, PROMPEGDEC_PACKET, packet);
+}
+
+static int prompegdec_restore_fec_row(
+    PrompegDecoder *decoder, PrompegDecoderPacket *fec_row_packet)
+{
+    int i;
+    uint16_t missing_index, index;
+    int ret, packets_count = 0;
+
+    for (i = 0; i < decoder->l; i++) {
+        index = fec_row_packet->index + i;
+        decoder->restore_buffer[i] =
+            prompegdec_find_packet(decoder->packets, index);
+
+        if (decoder->restore_buffer[i]) {
+            packets_count++;
+        } else {
+            missing_index = index;
+        }
+    }
+
+    if (packets_count != decoder->l - 1)
+        return 0;
+
+    ret = prompeg_restore_packets_buffer(decoder, missing_index, decoder->l,
+        fec_row_packet, PROMPEGDEC_FEC_ROW_PACKET);
+
+    if (ret < 0)
+        return ret;
+
+    return missing_index;
+    ;
+}
+
+static int prompegdec_restore_fec_col(
+    PrompegDecoder *decoder, PrompegDecoderPacket *fec_col_packet)
+{
+    int i;
+    uint16_t missing_index, index;
+    int ret, packets_count = 0;
+
+    for (i = 0; i < decoder->d; i++) {
+        index = fec_col_packet->index + i * decoder->l;
+        decoder->restore_buffer[i] =
+            prompegdec_find_packet(decoder->packets, index);
+
+        if (decoder->restore_buffer[i]) {
+            packets_count++;
+        } else {
+            missing_index = index;
+        }
+    }
+
+    if (packets_count != decoder->d - 1)
+        return 0;
+
+    ret = prompeg_restore_packets_buffer(decoder, missing_index, decoder->d,
+        fec_col_packet, PROMPEGDEC_FEC_COL_PACKET);
+
+    if (ret < 0)
+        return ret;
+
+    return missing_index;
+}
+
+static int prompegdec_restore_fec_matrix(PrompegDecoder *decoder)
+{
+    int i, restored, ret;
+    uint16_t packet_index;
+    PrompegDecoderPacket *fec_packet;
+
+    do {
+        restored = 0;
+
+        for (i = 0; i < decoder->d; i++) {
+            packet_index = decoder->first_fec_packet_index + i * decoder->l;
+            fec_packet = prompegdec_fec_row_packet(decoder, packet_index);
+
+            if (fec_packet) {
+                ret = prompegdec_restore_fec_row(decoder, fec_packet);
+
+                if (ret < 0)
+                    return ret;
+
+                if (ret == decoder->next_index)
+                    return 1;
+
+                restored += 1;
+            }
+        }
+
+        for (i = 0; i < decoder->l; i++) {
+            fec_packet = prompegdec_fec_col_packet(
+                decoder, decoder->first_fec_packet_index + i);
+
+            if (fec_packet) {
+                ret = prompegdec_restore_fec_col(decoder, fec_packet);
+
+                if (ret < 0)
+                    return ret;
+
+                if (ret == decoder->next_index)
+                    return 1;
+
+                restored += 1;
+            }
+        }
+    } while (restored);
+
+    return 0;
+}
+
+static void prompegdec_populate_fec_data(PrompegDecoder *decoder)
+{
+    if (!decoder->next_fec_row)
+        decoder->next_fec_row =
+            prompegdec_fec_row_packet(decoder, decoder->next_index);
+
+    if (!decoder->next_fec_col)
+        decoder->next_fec_col =
+            prompegdec_fec_col_packet(decoder, decoder->next_index);
+
+    if (decoder->next_fec_row && decoder->next_fec_col)
+        decoder->first_fec_packet_index = decoder->next_fec_col->index -
+                                          decoder->next_index +
+                                          decoder->next_fec_row->index;
+    else
+        decoder->first_fec_packet_index =
+            decoder->next_index - decoder->l * decoder->d;
+}
+
+static PrompegDecoderPacket *prompegdec_get_next_packet(PrompegDecoder *decoder)
+{
+    PrompegDecoderPacket *packet =
+        prompegdec_find_packet(decoder->packets, decoder->next_index);
+    int restored = 0;
+
+    if (packet)
+        return packet;
+
+    prompegdec_populate_fec_data(decoder);
+
+    if (decoder->next_fec_row)
+        restored = prompegdec_restore_fec_row(decoder, decoder->next_fec_row);
+
+    if (!restored && decoder->next_fec_col)
+        restored = prompegdec_restore_fec_col(decoder, decoder->next_fec_col);
+
+    if (!restored && decoder->next_fec_row && decoder->next_fec_col)
+        restored = prompegdec_restore_fec_matrix(decoder);
+
+    if (!restored)
+        return NULL;
+
+    return prompegdec_find_packet(decoder->packets, decoder->next_index);
+}
+
+static int prompegdec_return_packet(
+    PrompegDecoder *decoder, PrompegDecoderPacket *packet, uint8_t *bytes)
+{
+    PrompegDecoderPacket *old_packet;
+
+    memcpy(bytes, packet->bytes, decoder->packet_size);
+    decoder->next_index = packet->index + 1;
+    decoder->next_fec_row = decoder->next_fec_col = NULL;
+    prompegdec_populate_fec_data(decoder);
+
+    do {
+        old_packet = prompegdec_get_first_packet(
+            decoder->packets, 0, decoder->first_fec_packet_index);
+
+        if (old_packet) {
+            prompegdec_remove_packet(&decoder->packets, old_packet);
+            decoder->packets_count--;
+        }
+    } while (old_packet);
+
+    do {
+        old_packet = prompegdec_get_first_packet(
+            decoder->fec_col_packets, 0, decoder->first_fec_packet_index);
+
+        if (old_packet) {
+            prompegdec_remove_packet(&decoder->fec_col_packets, old_packet);
+            decoder->fec_col_packets_count--;
+        }
+    } while (old_packet);
+
+    do {
+        old_packet = prompegdec_get_first_packet(
+            decoder->fec_row_packets, 0, decoder->first_fec_packet_index);
+
+        if (old_packet) {
+            prompegdec_remove_packet(&decoder->fec_row_packets, old_packet);
+            decoder->fec_row_packets_count--;
+        }
+    } while (old_packet);
+
+    return decoder->packet_size;
+}
+
+int ff_prompegdec_add_packet(PrompegDecoder *decoder,
+    PrompegDecoderPacketType type, uint16_t index, const uint8_t *bytes,
+    int length)
+{
+    PrompegDecoderPacket *packet;
+    int expected_length = type == PROMPEGDEC_PACKET ? decoder->packet_size
+                                                    : decoder->fec_packet_size;
+
+    if (length != expected_length)
+        return AVERROR(EINVAL);
+
+    if (index <= decoder->first_fec_packet_index)
+        return 0;
+
+    switch (type) {
+    case PROMPEGDEC_FEC_ROW_PACKET:
+        if (decoder->max_fec_packets <= decoder->fec_row_packets_count) {
+            av_log(decoder, AV_LOG_ERROR,
+                "Reached maximum of FEC row packets, dropping new packet..");
+            return 0;
+        }
+
+    case PROMPEGDEC_FEC_COL_PACKET:
+        if (decoder->max_fec_packets <= decoder->fec_col_packets_count) {
+            av_log(decoder, AV_LOG_ERROR,
+                "Reached maximum of FEC col packets, dropping new packet..");
+            return 0;
+        }
+
+    default:
+        if (!decoder->next_index ||
+            (decoder->packets_count < decoder->min_packets &&
+                decoder->next_index <= index))
+            decoder->next_index = index;
+    }
+
+    packet = prompegdec_alloc_packet(index, length);
+    if (!packet)
+        return AVERROR(ENOMEM);
+
+    memcpy(packet->bytes, bytes, length);
+
+    return prompegdec_insert_packet(decoder, type, packet);
+}
+
+int ff_prompegdec_read_packet(
+    PrompegDecoder *decoder, uint8_t *bytes, int length)
+{
+    PrompegDecoderPacket *packet;
+
+    if (length < decoder->packet_size)
+        return AVERROR(EINVAL);
+
+    if (decoder->packets_count < decoder->min_packets)
+        return AVERROR(EAGAIN);
+
+    packet = prompegdec_get_next_packet(decoder);
+
+    if (!packet) {
+        if (decoder->packets_count < decoder->max_packets)
+            return AVERROR(EAGAIN);
+
+        packet = prompegdec_get_first_packet(
+            decoder->packets, decoder->next_index, UINT16_MAX);
+
+        if (!packet)
+            return AVERROR(EAGAIN);
+
+        av_log(decoder, AV_LOG_ERROR,
+            "Could not restore lost packet at index %d\n", decoder->next_index);
+    }
+
+    return prompegdec_return_packet(decoder, packet, bytes);
+}
diff --git a/libavformat/prompegdec.h b/libavformat/prompegdec.h
new file mode 100644
index 0000000000..ccdf85fbb2
--- /dev/null
+++ b/libavformat/prompegdec.h
@@ -0,0 +1,82 @@
+/*
+ * Pro-MPEG Code of Practice #3 Release 2 FEC
+ * Copyright (c) 2025 Radio France (https://radiofrance.fr)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Pro-MPEG Code of Practice #3 Release 2 FEC protocol -- Decoder logic
+ * @author Romain Beauxis <romain.beauxis at gmail.com>
+ */
+
+#ifndef AVFORMAT_PROMPEGDEC_H
+#define AVFORMAT_PROMPEGDEC_H
+
+#include "libavformat/url.h"
+#include "libavutil/tree.h"
+
+typedef struct PrompegDecoderPacket {
+    uint16_t index;
+    uint8_t *bytes;
+} PrompegDecoderPacket;
+
+typedef struct PrompegDecoder {
+    const AVClass *av_class;
+    int l, d, packet_size, fec_packet_size, bitstring_size;
+    URLContext *url_context;
+    PrompegDecoderPacket **restore_buffer;
+    uint8_t *tmp_bitstring, *bitstring;
+
+    struct AVTreeNode *packets;
+    uint16_t next_index;
+    PrompegDecoderPacket *next_fec_col;
+    PrompegDecoderPacket *next_fec_row;
+    uint16_t first_fec_packet_index;
+    int packets_count;
+    int min_packets;
+    int max_packets;
+    int max_fec_packets;
+
+    struct AVTreeNode *fec_col_packets;
+    int fec_row_packets_count;
+
+    struct AVTreeNode *fec_row_packets;
+    int fec_col_packets_count;
+} PrompegDecoder;
+
+typedef enum PrompegDecoderPacketType {
+    PROMPEGDEC_PACKET,
+    PROMPEGDEC_FEC_ROW_PACKET,
+    PROMPEGDEC_FEC_COL_PACKET,
+} PrompegDecoderPacketType;
+
+PrompegDecoder *ff_prompegdec_alloc_decoder(URLContext *url_context, int l,
+    int d, int packet_size, int fec_packet_size, int bitstring_size,
+    int min_packets, int max_packets, int max_fec_packets);
+
+void ff_prompegdec_free_decoder(PrompegDecoder *decoder);
+
+int ff_prompegdec_add_packet(PrompegDecoder *decoder,
+    PrompegDecoderPacketType type, uint16_t index, const uint8_t *bytes,
+    int length);
+
+int ff_prompegdec_read_packet(
+    PrompegDecoder *decoder, uint8_t *bytes, int length);
+
+#endif
diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c
index 15d0050936..4713c5836c 100644
--- a/libavformat/rtpproto.c
+++ b/libavformat/rtpproto.c
@@ -29,6 +29,7 @@
 #include "libavutil/avstring.h"
 #include "libavutil/opt.h"
 #include "avformat.h"
+#include "prompeg.h"
 #include "rtp.h"
 #include "rtpproto.h"
 #include "url.h"
@@ -213,6 +214,7 @@ static void build_udp_url(RTPContext *s,
  *         'block=ip[,ip]'    : list disallowed source IP addresses
  *         'write_to_source=0/1' : send packets to the source address of the latest received packet
  *         'dscp=n'           : set DSCP value to n (QoS)
+ *         'fec=s'            : FEC decoder parameters
  * deprecated option:
  *         'localport=n'      : set the local port to n
  *
@@ -294,6 +296,12 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
             if (!s->localaddr)
                 goto fail;
         }
+        if (av_find_info_tag(buf, sizeof(buf), "fec", p)) {
+            av_freep(&s->fec_options_str);
+            s->fec_options_str = av_strdup(buf);
+            if (!s->fec_options_str)
+                goto fail;
+        }
     }
     if (s->rw_timeout >= 0)
         h->rw_timeout = s->rw_timeout;
@@ -359,6 +367,9 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
 
     s->fec_hd = NULL;
     if (fec_protocol) {
+        if (h->flags & AVIO_FLAG_READ)
+            flags |= AVIO_FLAG_NONBLOCK;
+
         ff_url_join(buf, sizeof(buf), fec_protocol, NULL, hostname, rtp_port, NULL);
         if (ffurl_open_whitelist(&s->fec_hd, buf, flags, &h->interrupt_callback,
                              &fec_opts, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
@@ -391,7 +402,7 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
 static int rtp_read(URLContext *h, uint8_t *buf, int size)
 {
     RTPContext *s = h->priv_data;
-    int len, n, i;
+    int len, ret, n, i;
     struct pollfd p[2] = {{s->rtp_fd, POLLIN, 0}, {s->rtcp_fd, POLLIN, 0}};
     int poll_delay = h->flags & AVIO_FLAG_NONBLOCK ? 0 : POLLING_TIME;
     struct sockaddr_storage *addrs[2] = { &s->last_rtp_source, &s->last_rtcp_source };
@@ -408,16 +419,28 @@ static int rtp_read(URLContext *h, uint8_t *buf, int size)
                 if (!(p[i].revents & POLLIN))
                     continue;
                 *addr_lens[i] = sizeof(*addrs[i]);
+
                 len = recvfrom(p[i].fd, buf, size, 0,
                                 (struct sockaddr *)addrs[i], addr_lens[i]);
+
                 if (len < 0) {
                     if (ff_neterrno() == AVERROR(EAGAIN) ||
                         ff_neterrno() == AVERROR(EINTR))
                         continue;
                     return AVERROR(EIO);
                 }
+
                 if (ff_ip_check_source_lists(addrs[i], &s->filters))
                     continue;
+
+                if (i == 0 && s->fec_hd) {
+                    ret = ff_prompeg_add_packet(s->fec_hd, buf, len);
+                    if (ret < 0)
+                        return ret;
+
+                    len = ffurl_read(s->fec_hd, buf, len);
+                }
+
                 return len;
             }
         } else if (n == 0 && h->rw_timeout > 0 && --runs <= 0) {
diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
index 5ea471b40c..17a3c2c5c4 100644
--- a/libavformat/rtsp.c
+++ b/libavformat/rtsp.c
@@ -2431,6 +2431,7 @@ static int sdp_read_header(AVFormatContext *s)
             AVDictionary *opts = map_to_opts(rt);
             char buf[MAX_URL_SIZE];
             const char *p;
+            char *fec = NULL;
 
             err = getnameinfo((struct sockaddr*) &rtsp_st->sdp_ip,
                               sizeof(rtsp_st->sdp_ip),
@@ -2441,12 +2442,20 @@ static int sdp_read_header(AVFormatContext *s)
                 av_dict_free(&opts);
                 goto fail;
             }
+
+            p = strchr(s->url, '?');
+            if (p) {
+                if (av_find_info_tag(buf, sizeof(buf), "fec", p))
+                    fec = buf;
+            }
+
             ff_url_join(url, sizeof(url), "rtp", NULL,
                         namebuf, rtsp_st->sdp_port,
-                        "?localport=%d&ttl=%d&connect=%d&write_to_source=%d",
+                        "?localport=%d&ttl=%d&connect=%d&write_to_source=%d%s%s",
                         rtsp_st->sdp_port, rtsp_st->sdp_ttl,
                         rt->rtsp_flags & RTSP_FLAG_FILTER_SRC ? 1 : 0,
-                        rt->rtsp_flags & RTSP_FLAG_RTCP_TO_SOURCE ? 1 : 0);
+                        rt->rtsp_flags & RTSP_FLAG_RTCP_TO_SOURCE ? 1 : 0,
+                        fec ? "&fec=" : "", fec ? fec : "");
 
             p = strchr(s->url, '?');
             if (p && av_find_info_tag(buf, sizeof(buf), "localaddr", p))
-- 
2.39.5 (Apple Git-154)



More information about the ffmpeg-devel mailing list