[FFmpeg-devel] [PATCH] avformat: Add parityfec and ulpfec protocol
Camille Gonnet
camille at sound4.biz
Wed Apr 21 10:38:52 EEST 2021
I did not have any feedback on this patch.
Thanks,
On April 9, 2021 at 4:08 PM, Camille Gonnet (camille at sound4.biz) wrote:
Parityfec (RFC 2733) and ulpfec (RFC 5109) generic FEC encoding for RTP streams.
Signed-off-by: Camille Gonnet <camille at sound4.biz>
---
Changelog | 1 +
doc/general_contents.texi | 1 +
doc/protocols.texi | 106 +++++
libavformat/Makefile | 1 +
libavformat/fecrtp.c | 884 ++++++++++++++++++++++++++++++++++++++
libavformat/protocols.c | 1 +
libavformat/rtpproto.c | 2 +-
7 files changed, 995 insertions(+), 1 deletion(-)
create mode 100644 libavformat/fecrtp.c
diff --git a/Changelog b/Changelog
index a96e350e09..e83ec2c60d 100644
--- a/Changelog
+++ b/Changelog
@@ -83,6 +83,7 @@ version <next>:
- msad video filter
- gophers protocol
- RIST protocol via librist
+- FEC RTP (RFC5109+RFC2733) protocol (encoding only)
version 4.3:
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 33ece6e884..2b70dfb2ad 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1368,6 +1368,7 @@ performance on systems without hardware floating point support).
@multitable @columnfractions .4 .1
@item Name @tab Support
@item AMQP @tab E
+ at item FECRTP @tab X
@item file @tab X
@item FTP @tab X
@item Gopher @tab X
diff --git a/doc/protocols.texi b/doc/protocols.texi
index d3f6cbefcf..403e7b415d 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -243,6 +243,112 @@ For example, to convert a GIF file given inline with @command{ffmpeg}:
ffmpeg -i "data:image/gif;base64,R0lGODdhCAAIAMIEAAAAAAAA//8AAP//AP///////////////ywAAAAACAAIAAADF0gEDLojDgdGiJdJqUX02iB4E8Q9jUMkADs=" smiley.png
@end example
+ at section fecrtp
+
+FEC for RTP Protocol. Supports ULP FEC (RFC 5109) and Parity FEC (RFC 2733)
+
+The FEC is a generic parity-check forward error correction mechanism
+for RTP Streams.
+
+This protocol must be used in conjunction with the @code{rtp} protocol.
+
+The required syntax is:
+ at example
+-fec fecrtp=@var{option}=@var{val}... rtp://@var{hostname}:@var{port}
+ at end example
+
+This protocol accepts the following options:
+ at table @option
+
+ at item payload_type=@var{n}
+Specify RTP payload type for FEC packets (0-127).
+Default value is 98.
+
+ at item port=@var{n}
+Specify RTP port for FEC packets (0-127).
+Default value is (RTP port + 2).
+
+ at item host=@var{destination}
+Specify RTP destination for FEC packets.
+If not provided, use same as RTP.
+
+ at item kind=@var{kind}
+Set the kind of FEC to use. Default is @code{ulpfec}
+Accepts the following options:
+ at table @samp
+ at item parityfec
+parityfec (RFC 2733)
+ at item ulpfec
+ulpfec (RFC 5109)
+ at end table
+
+ at item length=@var{n}
+Length of recovery, or 0 for full. (0-1472)
+For @code{kind=ulpfec} only.
+Default value is 0 (full).
+
+ at item algorithm=@var{algorithm}
+Set the covered packets algorithm. Default is @code{simple}
+Accepts the following options:
+ at table @samp
+ at item simple
+Simple usage using @code{span}, at code{every} and @code{delay},
+ at item rotate
+Combine @code{span-1} packets in the last @code{span} packets in every way.@*
+With span=4 and packets a,b,c,d, will produce (a,b,c) (a,b,d) (a,c,d) (b,c,d).
+ at item 1d
+Put the packets in a matrix and compute columns FEC (similar to ProMPEG).
+Use @code{col} and @code{row}.
+ at end table
+
+ at item span=@var{n}
+The number of RTP packets covered by each FEC packet.
+For @code{algorithm=simple} and @code{algorithm=rotate}.
+Default value is 2.
+
+ at item every=@var{n}
+How often we add a FEC packet after a RTP packet (1-20).
+This must be a divider of span.
+For @code{algorithm=simple}.
+Default value is 2.
+
+ at item delay=@var{n}
+How many RTP packet we should delay the sending of our FEC packet
+when it is ready (0-20)
+For @code{algorithm=simple}.
+Default value is 0.
+
+ at item col=@var{n}
+Number of columns in the matrix.
+For @code{algorithm=1d}.
+Default value is 4.
+
+ at item row=@var{n}
+Number of rows in the matrix.
+For @code{algorithm=1d}.
+Default value is 4.
+
+ at end table
+
+Example usage:
+ at itemize @bullet
+ at item
+For 1/4 recovery, 25% overhead
+ at example
+-fec fecrtp=span=4:every=4 rtp://@var{hostname}:@var{port}
+ at end example
+ at item
+For 1/10 recovery, 10% overhead in ulpfec
+ at example
+-fec fecrtp=kind=ulpfec,span=10:every=10 rtp://@var{hostname}:@var{port}
+ at end example
+ at item
+For 1d fec with 4 rows and 4 columns
+ at example
+-fec fecrtp=algorithm=1d:col=4:row=4 rtp://@var{hostname}:@var{port}
+ at end example
+ at end itemize
+
@section file
File access protocol.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 0f340f74a0..f393ce14ca 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -618,6 +618,7 @@ OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o
OBJS-$(CONFIG_DATA_PROTOCOL) += data_uri.o
OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdigest.o rtmpdh.o
OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o
+OBJS-$(CONFIG_FECRTP_PROTOCOL) += fecrtp.o
OBJS-$(CONFIG_FILE_PROTOCOL) += file.o
OBJS-$(CONFIG_FTP_PROTOCOL) += ftp.o urldecode.o
OBJS-$(CONFIG_GOPHER_PROTOCOL) += gopher.o
diff --git a/libavformat/fecrtp.c b/libavformat/fecrtp.c
new file mode 100644
index 0000000000..7e4de1b267
--- /dev/null
+++ b/libavformat/fecrtp.c
@@ -0,0 +1,884 @@
+/*
+ * FEC for RTP - RFC 5109 and RFC 2733
+ * Copyright (c) 2021 SOUND4, France (http://www.sound4.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
+ * FEC for RTP - RFC 5109 and RFC 2733
+ * @author Camille Gonnet <camille at sound4.biz>
+ */
+
+/*
+ * Note: see also prompeg.c which use a derivative of the RFC 2733.
+ *
+ * Algorithm:
+ * Those FEC compute parity of previous RTP frames, but choice of frame is open.
+ * Here 3 algorithm can be used:
+ * + Simple
+ * Uses 3 parameters: span, every and delay
+ * It computes the parity of 'span' sequential packets and output a FEC packet
+ * every 'every' RTP packets, delaying the send by 'delay' packets when all
+ * have been send.
+ * + Rotate
+ * Uses 1 parameter: span
+ * It computes parity of 'span'-1 packets over the last 'span' packets, by
+ * rotating the missing packet in every way.
+ * With span=4 and packets a,b,c,d, will produce (a,b,c) (a,b,d) (a,c,d) (b,c,d).
+ * + 1d
+ * Uses 2 parameters: col,row
+ * Put the packets in a matrix and compute columns FEC (similar to ProMPEG)
+ *
+ * A typical set is for instance :
+ * span=5, every=5, delay=0
+ * which recovers one packet loss over 5 packets with 20% overhead and 5 delay.
+ *
+ * Reminder:
+
+ [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 2733] FEC Packet Structure
+
+ 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
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | RTP Header |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Header |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Payload |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ [RFC 2733] Parity Header Format
+
+ 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
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SN base | length recovery |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |E| PT recovery | mask |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | TS recovery |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ [RFC 5109] FEC Packet Structure
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | RTP Header (12 octets or more) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Header (10 octets) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Level 0 Header |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Level 0 Payload |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Level 1 Header |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | FEC Level 1 Payload |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Cont. |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ [RFC 5109] FEC Header Format
+
+ 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
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |E|L|P|X| CC |M| PT recovery | SN base |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | TS recovery |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | length recovery |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+ [RFC 5109] ULP Level Header Format
+
+ 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
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Protection Length | mask |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | mask cont. (present only when L = 1) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ */
+
+#include "libavutil/avstring.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/random_seed.h"
+#include "avformat.h"
+#include "config.h"
+#include "url.h"
+
+typedef struct Fecrtp {
+ uint16_t sn;
+ uint32_t ssrc;
+ uint16_t length_recovery;
+ int size; // size of the bitstring, so the bigger input packet size
+ uint8_t *bitstring; // We will store the xor of the full packets including RTP header.
+} Fecrtp;
+
+enum {
+ FECRTP_5109 = 1,
+ FECRTP_2733 = 2,
+};
+enum {
+ FECRTP_ALGO_SIMPLE = 1,
+ FECRTP_ALGO_1D = 2,
+ FECRTP_ALGO_ROTATE = 3,
+};
+typedef struct FecrtpContext {
+ const AVClass *class;
+
+ int kind;
+ int algorithm;
+
+ int ttl;
+ uint8_t pt;
+ const char *host;
+ uint8_t port;
+ int span, every, delay;
+ int protection_length;
+ int col, row;
+
+ int mask_length; // in bits
+ int long_header; // if mask_length>16 (L==1) RFC5109 only
+ URLContext *fec_hd;
+ int fec_arr_len;
+ uint64_t mask;
+
+ Fecrtp **fec_arr;
+ uint8_t *rtp_buf;
+ uint16_t rtp_sn;
+ int packet_idx, packet_idx_max;
+ int bitstring_size;
+ int rtp_buf_size;
+ int init;
+ int first;
+ int last_idx;
+} FecrtpContext;
+
+#define OFFSET(x) offsetof(FecrtpContext, x)
+#define E AV_OPT_FLAG_ENCODING_PARAM
+
+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 },
+ { "payload_type", "Specify RTP payload type for FEC packets", OFFSET(pt),
+ AV_OPT_TYPE_INT, { .i64 = 98 }, 0, 127, .flags = E },
+ { "port", "Specify RTP port. Default is RTP port + 2", OFFSET(port),
+ AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 127, .flags = E },
+ { "host", "Specify destination host. Default is same as RTP", OFFSET(host),
+ AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = E },
+ { "kind", "Set the FEC kind",
+ OFFSET(kind), AV_OPT_TYPE_INT, { .i64 = FECRTP_5109 }, 1, 2, E, "kind" },
+ { "parityfec", "parityfec (RFC 2733)",
+ 0, AV_OPT_TYPE_CONST, { .i64 = FECRTP_2733 }, 1, 2, E, "kind" },
+ { "ulpfec", "ulpfec (RFC 5109)",
+ 0, AV_OPT_TYPE_CONST, { .i64 = FECRTP_5109 }, 1, 2, E, "kind" },
+ { "algorithm", "Set the covered packets algorithm",
+ OFFSET(algorithm), AV_OPT_TYPE_INT, { .i64 = FECRTP_ALGO_SIMPLE }, 1, 3, E, "algorithm" },
+ { "simple", "Simple: use span/every/delay",
+ 0, AV_OPT_TYPE_CONST, { .i64 = FECRTP_ALGO_SIMPLE }, 1, 3, E, "algorithm" },
+ { "rotate", "Rotating: combine (span-1) packets in the last (span) packets in every way.",
+ 0, AV_OPT_TYPE_CONST, { .i64 = FECRTP_ALGO_ROTATE }, 1, 3, E, "algorithm" },
+ { "1d", "1D: column mode (similar to ST2022-1). Use col/row",
+ 0, AV_OPT_TYPE_CONST, { .i64 = FECRTP_ALGO_1D }, 1, 3, E, "algorithm" },
+ { "span", "Number of packets covered each time (algorithm=simple,rotate)", OFFSET(span),
+ AV_OPT_TYPE_INT, { .i64 = 2 }, 2, 24, .flags = E },
+ { "every", "Insert a FEC packet every # RTP packets (algorithm=simple)", OFFSET(every),
+ AV_OPT_TYPE_INT, { .i64 = 2 }, 1, 24, .flags = E },
+ { "delay", "Send FEC packet # packets after last covered (algorithm=simple)", OFFSET(delay),
+ AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 24, .flags = E },
+ { "col", "Number of matrix column (algorithm=1d)", OFFSET(col),
+ AV_OPT_TYPE_INT, { .i64 = 4 }, 2, 24, .flags = E },
+ { "row", "Number of matrix row (algorithm=1d)", OFFSET(row),
+ AV_OPT_TYPE_INT, { .i64 = 4 }, 2, 24, .flags = E },
+
+ { NULL }
+};
+
+static const AVClass fecrtp_class = {
+ .class_name = "fecrtp",
+ .item_name = av_default_item_name,
+ .option = options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+static void xor_fast(const uint8_t *in1, int size1, const uint8_t *in2, int size2, uint8_t *out, int size) {
+ int i, n, s, sizemin;
+#if HAVE_FAST_64BIT
+ uint64_t v1, v2;
+#else
+ uint32_t v1, v2;
+#endif
+
+ size = FFMAX(size1, size2);
+ sizemin = FFMIN(size1, size2);
+
+ // Do the XOR where both have data
+#if HAVE_FAST_64BIT
+ n = sizemin / 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
+ n = sizemin / 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 = sizemin - s;
+
+ for (i = 0; i < n; i++) {
+ out[i] = in1[i] ^ in2[i];
+ }
+ s += n;
+
+ // Compute the part where only one has data (padding 0)
+ n = size - s;
+ if (n > 0) {
+ if (size1 >= size2) {
+ memcpy(out,in1, n);
+ } else {
+ memcpy(out,in2, n);
+ }
+ }
+}
+
+static uint16_t bitrev16(uint16_t x)
+{
+ x = (((x & 0xaaaa) >> 1) | ((x & 0x5555) << 1));
+ x = (((x & 0xcccc) >> 2) | ((x & 0x3333) << 2));
+ x = (((x & 0xf0f0) >> 4) | ((x & 0x0f0f) << 4));
+ return((x >> 8) | (x << 8));
+}
+
+static uint32_t bitrev32(uint32_t x)
+{
+ x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
+ x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
+ x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
+ x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
+ return((x >> 16) | (x << 16));
+}
+
+static int fecrtp_init_fecpacket(URLContext *h, const uint8_t *buf, int size) {
+ FecrtpContext *s = h->priv_data;
+ int idx;
+
+ s->last_idx++;
+ if (s->last_idx == s->fec_arr_len)
+ s->last_idx = 0;
+ idx = s->last_idx;
+
+ // sanity check only, should not happen if algorithm are handled properly.
+ if (s->fec_arr[idx]->size>0) {
+ av_log(h, AV_LOG_ERROR, "Internal error, buffer already used (%d)\n", idx);
+ return AVERROR(EINVAL);
+ }
+
+ s->fec_arr[idx]->sn = AV_RB16(buf + 2);
+ // The SSRC value will generally be the same as the SSRC value of the media stream it protects
+ // It MAY be different if the FEC stream is being demultiplexed via the SSRC
+ s->fec_arr[idx]->ssrc = AV_RB32(buf + 8);
+ // Initialize it with the content
+ memcpy(s->fec_arr[idx]->bitstring, buf, size);
+ s->fec_arr[idx]->size = size;
+ s->fec_arr[idx]->length_recovery = (size-12);
+
+ //av_log(h, AV_LOG_DEBUG, "Starting new FEC[%d] SN=%hu SSRC=%X size=%d (packet idx %d)\n",
+ //idx, s->fec_arr[idx]->sn, s->fec_arr[idx]->ssrc, s->fec_arr[idx]->size, s->packet_idx);
+ return 0;
+}
+
+static int fecrtp_add_fecpacket(URLContext *h, int idx, const uint8_t *buf, int size) {
+ FecrtpContext *s = h->priv_data;
+
+ if (s->fec_arr[idx]->size>0) {
+ //av_log(h, AV_LOG_DEBUG, "Adding to FEC[%d] (packet idx %d)\n", idx, s->packet_idx);
+
+ xor_fast(s->fec_arr[idx]->bitstring, s->fec_arr[idx]->size,
+ buf, size,
+ s->fec_arr[idx]->bitstring, FFMAX(size, s->fec_arr[idx]->size));
+ s->fec_arr[idx]->length_recovery ^= (size-12);
+ if (s->fec_arr[idx]->size < size) {
+ s->fec_arr[idx]->size = size;
+ }
+ }
+ return 0;
+}
+
+static int fecrtp_write_fec2733(URLContext *h, Fecrtp *fec, uint32_t ts_current, uint64_t mask) {
+ FecrtpContext *s = h->priv_data;
+ uint8_t *buf = s->rtp_buf; // zero-filled
+ uint8_t *b = fec->bitstring;
+ uint16_t sn;
+ int ret;
+
+ sn = ++s->rtp_sn;
+
+ // RTP Header (12 bytes)
+ // V=2, P=xor, X=xor, CC=xor
+ buf[0] = 0x80| (b[0] & 0x3f);
+ // M=xor, PT=fixed
+ buf[1] = (b[1] & 0x80) | s->pt;
+ // SN=ours
+ AV_WB16(buf + 2, sn);
+ // TS=current
+ AV_WB32(buf + 4, ts_current);
+ // SSRC=original
+ AV_WB32(buf + 8, fec->ssrc);
+ // no CSRC or extension, even if bits X or E are set.
+
+ // FEC Header (12 bytes)
+ // SN:original first
+ AV_WB16(buf + 12, fec->sn);
+ // length recovery:xor
+ AV_WB16(buf + 14, fec->length_recovery);
+ // E=0, PT=xor
+ buf[16]=(b[1] & 0X7f);
+ // MASK:MASK (MSb First)
+ AV_WB24(buf + 17, mask);
+ // TS:xor
+ memcpy(buf + 20, b + 4, 4);
+
+ // Payload : part after basic RTP header (12 bytes) of all frames
+ memcpy(buf + 24, b + 12, fec->size-12);
+
+ ret = ffurl_write(s->fec_hd, buf, 24 + fec->size - 12);
+ return ret;
+}
+
+static int fecrtp_write_fec5109(URLContext *h, Fecrtp *fec, uint32_t ts_current, uint64_t mask) {
+ FecrtpContext *s = h->priv_data;
+ uint8_t *buf = s->rtp_buf; // zero-filled
+ uint8_t *b = fec->bitstring;
+ uint16_t sn;
+ int ret;
+ int headerlen;
+
+ sn = ++s->rtp_sn;
+
+ // RTP Header (12 bytes)
+ // V=2, P=0, X=0, CC=0
+ buf[0] = 0x80;
+ // M=0, PT=fixed
+ buf[1] = s->pt;
+ // SN=ours
+ AV_WB16(buf + 2, sn);
+ // TS=current
+ AV_WB32(buf + 4, ts_current);
+ // SSRC=original
+ AV_WB32(buf + 8, fec->ssrc);
+ // no CSRC
+
+ // FEC Header (10 bytes)
+ // E=0, L=0|1, P,X,CC:xor
+ buf[12] = (s->long_header<<6) | (b[0] & 0x3F);
+ // M,PT:xor
+ buf[13] = b[1];
+ // SN:original first
+ AV_WB16(buf + 14, fec->sn);
+ // TS:xor
+ memcpy(buf + 16, b + 4, 4);
+ // length:xor
+ AV_WB16(buf + 20, fec->length_recovery);
+
+ // FEC Level 0 Header
+ // Protection Length: note clear in RFC, guess this is the level payload length
+ AV_WB16(buf + 22, fec->size-12);
+ // Mask : (LSb First)
+ AV_WB16(buf + 24, bitrev16(mask));
+ headerlen = 24+2;
+ if (s->long_header) {
+ mask >>= 16;
+ AV_WB32(buf + 26, bitrev32(mask));
+ headerlen += 4;
+ }
+ // Payload : part after basic RTP header (12 bytes) of all frames
+ memcpy(buf + headerlen, b + 12, fec->size-12);
+
+ ret = ffurl_write(s->fec_hd, buf, headerlen + fec->size - 12);
+ return ret;
+}
+
+static int fecrtp_send_fecpacket(URLContext *h, int idx, const uint8_t *buf, uint64_t mask) {
+ FecrtpContext *s = h->priv_data;
+ uint32_t ts_current;
+ int ret = 0;
+
+ // Sanity check, should not happen if algorithm are handled properly.
+ if (!s->fec_arr[idx]->size) {
+ av_log(h, AV_LOG_ERROR, "Internal error, want to send uninitialized buffer (%d)\n", idx);
+ return AVERROR(EINVAL);
+ }
+
+ // The timestamp MUST be set to the value of the media
+ // RTP clock at the instant the FEC packet is transmitted
+ ts_current = AV_RB32(buf + 4);
+ switch (s->kind) {
+ case FECRTP_2733:
+ if ((ret = fecrtp_write_fec2733(h, s->fec_arr[idx], ts_current, mask)) < 0)
+ goto end;
+ break;
+ case FECRTP_5109:
+ if ((ret = fecrtp_write_fec5109(h, s->fec_arr[idx], ts_current, mask)) < 0)
+ goto end;
+ break;
+ };
+ // mark packet as free for reuse (internal check)
+ s->fec_arr[idx]->size = 0;
+
+end:
+ return ret;
+}
+
+static int fecrtp_open(URLContext *h, const char *uri, int flags) {
+ FecrtpContext *s = h->priv_data;
+ AVDictionary *udp_opts = NULL;
+ int rtp_port, fec_port;
+ char rtp_hostname[256];
+ const char *hostname;
+ char buf[1024];
+
+ s->fec_hd = NULL;
+
+ av_url_split(NULL, 0, NULL, 0, rtp_hostname, sizeof (rtp_hostname), &rtp_port,
+ NULL, 0, uri);
+
+ if (s->port) {
+ fec_port = s->port;
+ } else {
+ if (rtp_port < 1 || rtp_port > UINT16_MAX - 2) {
+ av_log(h, AV_LOG_ERROR, "Invalid RTP base port %d\n", rtp_port);
+ return AVERROR(EINVAL);
+ }
+ fec_port = rtp_port + 2;
+ }
+ if (s->host) {
+ hostname = s->host;
+ } else {
+ hostname = rtp_hostname;
+ }
+
+ if (s->ttl > 0) {
+ av_dict_set_int(&udp_opts, "ttl", s->ttl, 0);
+ }
+
+ ff_url_join(buf, sizeof (buf), "udp", NULL, hostname, fec_port, NULL);
+ if (ffurl_open_whitelist(&s->fec_hd, buf, flags, &h->interrupt_callback,
+ &udp_opts, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
+ goto fail;
+
+ h->max_packet_size = s->fec_hd->max_packet_size;
+ s->init = 1;
+
+ av_dict_free(&udp_opts);
+ return 0;
+
+fail:
+ ffurl_closep(&s->fec_hd);
+ av_dict_free(&udp_opts);
+ return AVERROR(EIO);
+}
+
+static int fecrtp_init(URLContext *h, const uint8_t *buf, int size) {
+ FecrtpContext *s = h->priv_data;
+ uint32_t seed;
+ int i;
+ uint64_t mask_bit;
+
+ s->fec_arr = NULL;
+ s->rtp_buf = NULL;
+
+ if (size < 12 || size > UINT16_MAX + 12) {
+ av_log(h, AV_LOG_ERROR, "Invalid RTP packet size\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ switch (s->algorithm) {
+ case FECRTP_ALGO_SIMPLE:
+ if ( (s->span < s->every) || (s->span % s->every)!=0 ) {
+ av_log(h, AV_LOG_ERROR, "'span' must be a multiple of 'every'\n");
+ return AVERROR_INVALIDDATA;
+ }
+ s->mask_length = s->span;
+ break;
+ case FECRTP_ALGO_ROTATE:
+ s->mask_length = s->span - 1;
+ break;
+ case FECRTP_ALGO_1D:
+ s->span = s->row * s->col;
+ s->mask_length = s->row * s->col;
+ break;
+ };
+
+ switch (s->kind) {
+ case FECRTP_2733:
+ if (s->mask_length > 24) {
+ av_log(h, AV_LOG_ERROR, "Invalid FEC parameters: maximum mask is 24 bits\n");
+ return AVERROR_INVALIDDATA;
+ }
+ break;
+ case FECRTP_5109:
+ s->long_header = (s->mask_length > 16)? 1 : 0;
+ if (s->mask_length > 48) {
+ av_log(h, AV_LOG_ERROR, "Invalid FEC parameters: maximum mask is 48 bits\n");
+ return AVERROR_INVALIDDATA;
+ }
+ break;
+ };
+ s->mask=0;
+ mask_bit=1;
+ switch (s->algorithm) {
+ case FECRTP_ALGO_SIMPLE:
+ for (i=0;i<s->span;i++) {
+ s->mask |= mask_bit;
+ mask_bit <<= 1;
+ }
+ s->fec_arr_len = s->span / s->every + s->delay;
+ // Round up packet index to a multiple of fec_arr_len
+ s->packet_idx_max = INT_MAX - (INT_MAX % s->fec_arr_len);
+ break;
+ case FECRTP_ALGO_ROTATE:
+ // mask will change each time, by removing 1 bit
+ s->mask = (1<<s->span) -1;
+ s->fec_arr_len = s->span;
+ // Round up packet index to a multiple of fec_arr_len
+ s->packet_idx_max = INT_MAX - (INT_MAX % s->fec_arr_len);
+ break;
+ case FECRTP_ALGO_1D:
+ for (i=0;i< s->row;i++) {
+ s->mask |= mask_bit;
+ mask_bit <<= s->col;
+ }
+ s->fec_arr_len = s->col * 2;
+ // Round up packet index to a multiple of fec_arr_len
+ s->packet_idx_max = INT_MAX - (INT_MAX % (s->col * s->row));
+ break;
+ };
+
+ s->packet_idx = 0;
+ if (h->flags & AVFMT_FLAG_BITEXACT) {
+ s->rtp_sn = 0;
+ } else {
+ seed = av_get_random_seed();
+ s->rtp_sn = seed & 0x0fff;
+ }
+
+ if (s->kind == FECRTP_2733) {
+ s->rtp_buf_size = 24 + h->max_packet_size; // 12 + 12: RTP + FEC headers
+ s->bitstring_size = 10 + h->max_packet_size; // 10: P, X, CC, M, PT, SN, TS, Length
+ } else /* if (s->kind == FECRTP_5109) */ {
+ s->rtp_buf_size = 28 + (s->protection_length?s->protection_length:h->max_packet_size); // 12 + 16: RTP + FEC headers
+ s->bitstring_size = 10 + (s->protection_length?s->protection_length:h->max_packet_size); // 10: P, X, CC, M, PT, SN, TS, Length
+ }
+
+ s->fec_arr = av_malloc_array(s->fec_arr_len, sizeof (Fecrtp*));
+ if (!s->fec_arr) {
+ goto fail;
+ }
+ for (i = 0; i < s->fec_arr_len; i++) {
+ s->fec_arr[i] = av_malloc(sizeof (Fecrtp));
+ if (!s->fec_arr[i]) {
+ goto fail;
+ }
+ s->fec_arr[i]->size=0;
+ s->fec_arr[i]->bitstring = av_malloc_array(s->bitstring_size, sizeof (uint8_t));
+ if (!s->fec_arr[i]->bitstring) {
+ av_freep(&s->fec_arr[i]);
+ goto fail;
+ }
+ }
+ // Simplifying: first one will be 0 in array
+ s->last_idx = s->fec_arr_len-1;
+
+ s->rtp_buf = av_malloc_array(s->rtp_buf_size, sizeof (uint8_t));
+ if (!s->rtp_buf) {
+ goto fail;
+ }
+ memset(s->rtp_buf, 0, s->rtp_buf_size);
+
+ s->init = 0;
+ switch (s->algorithm) {
+ case FECRTP_ALGO_SIMPLE:
+ s->first = s->span + s->delay - 1;
+ av_log(h, AV_LOG_INFO, "FEC: %s algorithm simple, span=%d every=%d delay=%d\n",
+ s->kind==FECRTP_5109?"ulpfec":"parityfec", s->span, s->every, s->delay);
+ break;
+ case FECRTP_ALGO_ROTATE:
+ s->first = 0;
+ av_log(h, AV_LOG_INFO, "FEC: %s algorithm rotate, span=%d\n",
+ s->kind==FECRTP_5109?"ulpfec":"parityfec", s->span);
+ break;
+ case FECRTP_ALGO_1D:
+ s->first = s->col * s->row;
+ av_log(h, AV_LOG_INFO, "FEC: %s algorithm 1d, col=%d, row=%d\n",
+ s->kind==FECRTP_5109?"ulpfec":"parityfec", s->col, s->row);
+ break;
+ };
+
+ return 0;
+
+fail:
+ av_log(h, AV_LOG_ERROR, "Failed to allocate the FEC buffer\n");
+ return AVERROR(ENOMEM);
+}
+
+static int fecrtp_write(URLContext *h, const uint8_t *buf, int size) {
+ FecrtpContext *s = h->priv_data;
+ int idx, first_is_new=0;
+ int ret = 0;
+ int protected_size = size;
+ uint64_t mask_clear;
+ int cur_row, cur_col;
+
+ if (s->kind == FECRTP_5109) {
+ // if protection_length is set, forget all after
+ if (s->protection_length > 0 && protected_size > s->protection_length)
+ protected_size = s->protection_length;
+ }
+
+ if (s->init && ((ret = fecrtp_init(h, buf, protected_size)) < 0))
+ goto end;
+
+ if (protected_size > s->bitstring_size) {
+ av_log(h, AV_LOG_ERROR, "The RTP packet size exceed the max_packet_size\n");
+ return AVERROR(EINVAL);
+ }
+
+ idx = s->last_idx;
+ // Create/Update needed FEC packets
+ switch (s->algorithm) {
+ case FECRTP_ALGO_SIMPLE:
+ if (s->packet_idx % s->every == 0) {
+ if ((ret = fecrtp_init_fecpacket(h, buf, protected_size)) <0)
+ goto end;
+
+ // Do not xor this one
+ first_is_new = 1;
+ }
+ // One RTP packet will be used in (span/every) FEC packets
+ for (int n=0; n < s->span/s->every; n++) {
+ if (first_is_new) {
+ first_is_new--;
+ } else {
+ // next one to process
+ if (!idx)
+ idx = s->fec_arr_len;
+ idx--;
+ if ((ret=fecrtp_add_fecpacket(h, idx, buf, protected_size)) < 0)
+ goto end;
+ }
+ }
+ break;
+ case FECRTP_ALGO_ROTATE:
+ // comments for case span==4
+ if (s->packet_idx % s->span == 0) {
+ // create all but last (a,b,c) (a,b,d) (a,c,d)
+ for (int n=0; n < s->span-1; n++) {
+ if ((ret = fecrtp_init_fecpacket(h, buf, protected_size)) <0)
+ goto end;
+ }
+ // idx is on last
+ } else if (s->packet_idx % s->span == 1) {
+ // create last now (b,c,d)
+ if ((ret = fecrtp_init_fecpacket(h, buf, protected_size)) <0)
+ goto end;
+ // And XOR all but two last (last just created)
+ for (int n=0; n < s->span-2; n++) {
+ // Add the xor to all but
+ idx=s->last_idx+1+n;
+ if (idx >= s->fec_arr_len)
+ idx -= s->fec_arr_len;
+ if ((ret=fecrtp_add_fecpacket(h, idx, buf, protected_size)) < 0)
+ goto end;
+ }
+ // idx is on last-1
+ } else {
+ // XOR all but last-packet_idx
+ for (int n=0; n < s->span; n++) {
+ idx=s->last_idx+1+n;
+ if (idx >= s->fec_arr_len)
+ idx -= s->fec_arr_len;
+ if (n != (s->span - 1 - (s->packet_idx % s->span))) {
+ if ((ret=fecrtp_add_fecpacket(h, idx, buf, protected_size)) < 0)
+ goto end;
+ }
+ }
+ }
+ break;
+ case FECRTP_ALGO_1D:
+ // Where are we in matrix
+ cur_col = s->packet_idx % s->col;
+ cur_row = (s->packet_idx / s->col ) % s->row;
+ if (!cur_row) {
+ // First row, we create the FEC at each column
+ if ((ret = fecrtp_init_fecpacket(h, buf, protected_size)) <0)
+ goto end;
+ } else {
+ // we process current column
+ idx = (s->last_idx - s->col + 1) + cur_col;
+ if (idx >= s->fec_arr_len)
+ idx -= s->fec_arr_len;
+ if ((ret=fecrtp_add_fecpacket(h, idx, buf, protected_size)) < 0)
+ goto end;
+ }
+ break;
+ };
+
+ // Send needed FEC packets
+ if (s->first) {
+ // Not enough data to send
+ //av_log(h, AV_LOG_DEBUG, "not sending (first=%d)\n", s->first);
+ s->first--;
+ } else {
+ switch (s->algorithm) {
+ case FECRTP_ALGO_SIMPLE:
+ // Should we send now ?
+ if ((s->packet_idx - (s->span-1) - s->delay) % s->every == 0) {
+ // Skip the delay if needed
+ idx-=s->delay+1;
+ if (idx < 0)
+ idx += s->fec_arr_len;
+ if ((ret=fecrtp_send_fecpacket(h, idx, buf, s->mask)) < 0)
+ goto end;
+ }
+ break;
+ case FECRTP_ALGO_ROTATE:
+ // comments for case span==4
+ if (s->packet_idx % s->span == s->span - 2) {
+ // Send first one at one before last
+ mask_clear=1<<(s->span-1); // This one is not in the xor
+ idx=s->last_idx+1; // circular -> go first
+ if (idx >= s->fec_arr_len)
+ idx -= s->fec_arr_len;
+ if ((ret=fecrtp_send_fecpacket(h, idx, buf, s->mask & (~mask_clear))) < 0)
+ goto end;
+ } else if (s->packet_idx % s->span == s->span - 1) {
+ // Send all others on last
+ for (int n=1; n < s->span; n++) {
+ mask_clear=1<<(s->span-1-n); // This one is not in the xor
+ // For the last one, we started one later, so clear MSB
+ if (n == s->span-1)
+ mask_clear=1<<(s->span-1);
+ idx=s->last_idx+1+n;
+ if (idx >= s->fec_arr_len)
+ idx -= s->fec_arr_len;
+ if ((ret=fecrtp_send_fecpacket(h, idx, buf, s->mask & (~mask_clear))) < 0)
+ goto end;
+ }
+ }
+ break;
+ case FECRTP_ALGO_1D:
+ // cur_col and cur_row already set to where we are in matrix
+ // evenly space them, starting of 1st of matrix
+ // we have s->col to send in s->col*s->row packets
+ if ((cur_col+cur_row*s->col)%s->row == 0) {
+ // Find index of first of previous matrix
+ if (!cur_row) {
+ // only cur_col has been added for new matrix
+ idx = (s->last_idx - cur_col + s->col);
+ } else {
+ // all have been added for new matrix
+ idx = (s->last_idx + 1);
+ }
+ idx += (cur_col+cur_row*s->col)/s->row;
+ idx %= s->fec_arr_len;
+ if ((ret=fecrtp_send_fecpacket(h, idx, buf, s->mask)) < 0)
+ goto end;
+ }
+ break;
+ };
+ }
+
+ // prepare for next packet
+ s->packet_idx++;
+ if (s->packet_idx == s->packet_idx_max)
+ s->packet_idx = 0;
+
+ ret = protected_size;
+
+end:
+ return ret;
+}
+
+static int fecrtp_close(URLContext *h) {
+ FecrtpContext *s = h->priv_data;
+ int i;
+
+ ffurl_closep(&s->fec_hd);
+
+ if (s->fec_arr) {
+ for (i = 0; i < s->fec_arr_len; i++) {
+ av_free(s->fec_arr[i]->bitstring);
+ av_freep(&s->fec_arr[i]);
+ }
+ av_freep(&s->fec_arr);
+ }
+ av_freep(&s->rtp_buf);
+
+ return 0;
+}
+
+const URLProtocol ff_fecrtp_protocol = {
+ .name = "fecrtp",
+ .url_open = fecrtp_open,
+ .url_write = fecrtp_write,
+ .url_close = fecrtp_close,
+ .priv_data_size = sizeof(FecrtpContext),
+ .flags = URL_PROTOCOL_FLAG_NETWORK,
+ .priv_data_class = &fecrtp_class,
+};
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index fb6fabdce5..af16313fdb 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -31,6 +31,7 @@ extern const URLProtocol ff_crypto_protocol;
extern const URLProtocol ff_data_protocol;
extern const URLProtocol ff_ffrtmpcrypt_protocol;
extern const URLProtocol ff_ffrtmphttp_protocol;
+extern const URLProtocol ff_fecrtp_protocol;
extern const URLProtocol ff_file_protocol;
extern const URLProtocol ff_ftp_protocol;
extern const URLProtocol ff_gopher_protocol;
diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c
index 7dd6042158..a58472d684 100644
--- a/libavformat/rtpproto.c
+++ b/libavformat/rtpproto.c
@@ -295,7 +295,7 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
av_log(h, AV_LOG_ERROR, "Failed to parse the FEC protocol value\n");
goto fail;
}
- if (strcmp(fec_protocol, "prompeg")) {
+ if (strcmp(fec_protocol, "prompeg") && strcmp(fec_protocol, "fecrtp")) {
av_log(h, AV_LOG_ERROR, "Unsupported FEC protocol %s\n", fec_protocol);
goto fail;
}
--
2.25.1
More information about the ffmpeg-devel
mailing list