* [FFmpeg-devel] [PATCH v1] Implement promeg decoder.
@ 2025-01-21 22:17 Romain Beauxis
0 siblings, 0 replies; only message in thread
From: Romain Beauxis @ 2025-01-21 22:17 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
This patch implements the decoding logic for the FEC error-
correction method described in the Pro-MPEG CoP #3-R2 FEC[1]
This implementation was previously discussed here: [2]
Some preliminary testing results are available: [3]
Summary:
1% packet loss: 100% recovery ratio
5% packet loss: 98% recovery ratio
10% packet loss: 95% recovery ratio
20% packet loss: 82% recovery ratio
The changes still need to be tested with more hardware encoders.
Changes since v0:
* Bug fixes and cleanup.
* Marked the prompeg muxer/demuxer as experimental.
The code logic is the following:
* The decoder stores packets 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.
-- Romain
[1]: https://www.yumpu.com/en/document/read/8808550/pro-mpeg-code-of-practice-3-release-2-pro-mpeg-forum
[2]: https://ffmpeg.org/pipermail/ffmpeg-devel/2025-January/338463.html
[3]: https://ffmpeg.org/pipermail/ffmpeg-devel/2025-January/338673.html
---
doc/protocols.texi | 21 +-
libavformat/Makefile | 2 +-
libavformat/prompeg.c | 277 ++++++--------
libavformat/prompeg.h | 30 ++
libavformat/prompeg_utils.c | 226 ++++++++++++
libavformat/prompeg_utils.h | 58 +++
libavformat/prompegdec.c | 714 ++++++++++++++++++++++++++++++++++++
libavformat/prompegdec.h | 95 +++++
libavformat/rtpproto.c | 31 +-
libavformat/rtsp.c | 13 +-
10 files changed, 1297 insertions(+), 170 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..3741c4c5d2 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,16 @@ 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:
+@example
+rtp://@var{hostname}:@var{port}?fec=prompeg=@var{option}=@var{val}...
+@end example
+
+Demuxing will fail if the RTP packets are not of fixed size or if their sequence
+number requires more than two bytes.
+
+Please note that, at this time, prompeg demuxing is considered experimental.
+
This protocol accepts the following options:
@table @option
@@ -808,6 +818,15 @@ The number of columns (4-20, LxD <= 100)
@item d=@var{n}
The number of rows (4-20, LxD <= 100)
+@item min_decoder_packets=@var{n}
+Demuxing only: the number of packets to receive before decoding of the stream can begin.
+
+@item max_decoder_packets=@var{n}
+Demuxing only: the maximum number of packets to buffer before a missing packet is dropped.
+
+@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..6423c0ac35 100644
--- a/libavformat/prompeg.c
+++ b/libavformat/prompeg.c
@@ -25,78 +25,16 @@
* @author Vlad Tarca <vlad.tarca@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 = 60 }, 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,8 +416,9 @@ 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,
+ .flags = URL_PROTOCOL_FLAG_NETWORK | AVFMT_EXPERIMENTAL,
.priv_data_class = &prompeg_class,
};
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@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@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@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..08123e6447
--- /dev/null
+++ b/libavformat/prompegdec.c
@@ -0,0 +1,714 @@
+/*
+ * 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@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 float prompeg_restored_ratio(PrompegDecoder *decoder)
+{
+ double missed_packets = decoder->restored_packets + decoder->failed_packets;
+
+ if (!decoder->restored_packets)
+ return -1;
+
+ return ((double)decoder->restored_packets) / missed_packets * 100;
+}
+
+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, PrompegDecoderPacketType type,
+ PrompegDecoderPacket *fec_packet)
+{
+ 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);
+
+ decoder->restored_packets++;
+ av_log(decoder, AV_LOG_INFO,
+ "Restored lost packet at index %d using FEC %s.\n", index,
+ type == PROMPEGDEC_FEC_ROW_PACKET ? "row" : "col");
+
+ av_log(decoder, AV_LOG_VERBOSE,
+ "Restored ratio: %.02f%%, "
+ "packets count: %d, FEC row packets count: %d, FEC col packets "
+ "count: %d\n",
+ prompeg_restored_ratio(decoder), decoder->packets_count,
+ decoder->fec_row_packets_count, decoder->fec_col_packets_count);
+
+ decoder->pending_packets++;
+
+ return prompegdec_insert_packet(decoder, PROMPEGDEC_PACKET, packet);
+}
+
+static int32_t 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,
+ PROMPEGDEC_FEC_ROW_PACKET, fec_row_packet);
+
+ if (ret < 0)
+ return ret;
+
+ return missing_index;
+}
+
+static int32_t 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,
+ PROMPEGDEC_FEC_COL_PACKET, fec_col_packet);
+
+ if (ret < 0)
+ return ret;
+
+ return missing_index;
+}
+
+static int prompegdec_restore_fec_matrix(PrompegDecoder *decoder)
+{
+ int i, restored_count;
+ int32_t restored_index;
+ uint16_t packet_index;
+ PrompegDecoderPacket *fec_packet;
+
+ do {
+ restored_count = 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) {
+ restored_index =
+ prompegdec_restore_fec_row(decoder, fec_packet);
+
+ if (restored_index < 0)
+ return restored_index;
+
+ if (restored_index == decoder->next_index)
+ return 1;
+
+ if (restored_index)
+ restored_count++;
+ }
+ }
+
+ for (i = 0; i < decoder->l; i++) {
+ fec_packet = prompegdec_fec_col_packet(
+ decoder, decoder->first_fec_packet_index + i);
+
+ if (fec_packet) {
+ restored_index =
+ prompegdec_restore_fec_col(decoder, fec_packet);
+
+ if (restored_index < 0)
+ return restored_index;
+
+ if (restored_index == decoder->next_index)
+ return 1;
+
+ if (restored_index)
+ restored_count++;
+ }
+ }
+ } while (restored_count);
+
+ 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;
+ decoder->pending_packets--;
+ 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..\n");
+ return 0;
+ }
+ break;
+
+ 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..\n");
+ return 0;
+ }
+ break;
+
+ default:
+ // We keep a 1:1 ratio of packets I/O
+ // so we only account for the first added packets.
+ // Subsequent packets are assumed to be buffered for
+ // tentatively restoring a packet.
+ if (!decoder->pending_packets)
+ decoder->pending_packets++;
+
+ 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->pending_packets ||
+ decoder->packets_count < decoder->min_packets)
+ return AVERROR(EAGAIN);
+
+ packet = prompegdec_get_next_packet(decoder);
+
+ if (packet)
+ return prompegdec_return_packet(decoder, packet, bytes);
+
+ 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);
+
+ decoder->failed_packets++;
+
+ av_log(decoder, AV_LOG_ERROR,
+ "Could not restore lost packet at index %d.\n", decoder->next_index);
+
+ av_log(decoder, AV_LOG_VERBOSE,
+ "Restored ratio: %.02f%%, "
+ "packets count: %d, FEC row packets count: %d, FEC col packets "
+ "count: %d.\n",
+ prompeg_restored_ratio(decoder), decoder->packets_count,
+ decoder->fec_row_packets_count, decoder->fec_col_packets_count);
+
+ return prompegdec_return_packet(decoder, packet, bytes);
+}
diff --git a/libavformat/prompegdec.h b/libavformat/prompegdec.h
new file mode 100644
index 0000000000..1040a6e0f3
--- /dev/null
+++ b/libavformat/prompegdec.h
@@ -0,0 +1,95 @@
+/*
+ * 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@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;
+ uint64_t restored_packets;
+ uint64_t failed_packets;
+
+ // When data is received without loss, each received
+ // packet is followed by one packet taken out.
+ // However, when packets are restored, they are not
+ // accounted for. We relieve the buffer by outputing
+ // a corresponding number of packets.
+ // Thus, pending_packets is:
+ // <one packet added> + <number of packets restored>
+ // This exclude packets added as part of a buffering
+ // to restore packets.
+ uint8_t pending_packets;
+
+ 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..b554da291a 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,13 +402,19 @@ 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 };
socklen_t *addr_lens[2] = { &s->last_rtp_source_len, &s->last_rtcp_source_len };
int runs = h->rw_timeout / 1000 / POLLING_TIME;
+ if (s->fec_hd) {
+ ret = ffurl_read(s->fec_hd, buf, size);
+ if (ret != AVERROR(EAGAIN))
+ return ret;
+ }
+
for(;;) {
if (ff_check_interrupt(&h->interrupt_callback))
return AVERROR_EXIT;
@@ -408,16 +425,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 (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)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-01-21 22:22 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-01-21 22:17 [FFmpeg-devel] [PATCH v1] Implement promeg decoder Romain Beauxis
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git