Closed gavv closed 5 years ago
May be interesting: RFC 3095: RObust Header Compression (ROHC)
RFC 6363: FECFRAME is inspired by RFC 5052: FEC Building Block:
This document describes how to use Forward Error Correction (FEC) codes to efficiently provide and/or augment reliability for bulk data transfer over IP multicast.
If I understand correctly, FECFRAME is analogous to FEC Building Block in many aspects, but it is different.
In other words, it re-uses concepts and definitions from FEC Building Block, but an application that implements FECFRAME is not expected to be aware of FEC Building Block.
See related notes in FECFRAME:
The FEC Building Block, defined in [RFC5052], provides a framework for the definition of Content Delivery Protocols (CDPs) for object delivery (including, primarily, file delivery) that make use of separately defined FEC schemes.
[...]
This document defines a framework for the definition of CDPs that provide for FEC protection for arbitrary packet flows over unreliable transports such as UDP. As such, this document complements the FEC Building Block of [RFC5052], by providing for the case of arbitrary packet flows over unreliable transport, the same kind of framework as that document provides for object delivery.
[...]
FEC schemes designed for use with this framework must fulfill a number of requirements defined in this document. These requirements are different from those defined in [RFC5052] for FEC schemes for object delivery. However, there is a great deal of commonality, and FEC schemes defined for object delivery may be easily adapted for use with the framework defined in this document.
RFC 5775: ALC and RFC 6726: FLUTE are related to FEC Building Block, so it seems that if we're implementing FECFRAME, we don't need to bother about them.
Extracted from Section 5.1.1.
FEC Scheme-Specific Information (FSSI)
RFC 6816 requires the following parameters to be communicated from sender to receiver:
Source FEC Payload ID
The RFC also requires the following parameters to be included within every source packet:
Repair FEC Payload ID
And within every repair packet:
(in case when RTP is not used for repair packets)
The RFC suggests implementation to append a 6-byte footer (Source FEC Payload ID) to every source packet and 8-byte header (Repair FEC Payload ID) to every repair packet.
Source packet
+--------------------------------+
| IP Header |
+--------------------------------+
| UDP Header |
+--------------------------------+
| RTP packet (with RTP header) |
+--------------------------------+
| Explicit Source FEC Payload ID |
+--------------------------------+
Explicit Source FEC Payload ID:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Block Number (SBN) | Encoding Symbol ID (ESI) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Block Length (k) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Repair packet
+------------------------------------------+
| IP Header |
+------------------------------------------+
| UDP Header |
+------------------------------------------+
| Repair FEC Payload ID |
+------------------------------------------+
| Repair Symbol (blob returned by OpenFEC) |
+------------------------------------------+
Repair FEC Payload ID:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Block Number (SBN) | Encoding Symbol ID (ESI) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Block Length (k) | Number Encoding Symbols (n) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
FEC Scheme-Specific Information (FSSI) may be communicated over SDP.
However, we have no SDP support yet, so we can start with making those parameters configurable through command line. See #6.
OpenFEC encoder and decoder work with symbols, not packets. Those symbols don't include Source FEC Payload ID and Repair FEC Payload ID, and it's up to the applications (i.e., us) to communicate them within packets.
See also client/server example in OpenFEC sources. They add additional header with block sizes to every packet (though not compliant with any RFC, as mentioned in comments).
FECFRAME has RTP support (optional and FEC Scheme-dependent).
Source packets
With any FEC Scheme, source packets may use RTP. In this case, FEC Scheme protects both RTP header and body.
Repair packets
FEC Scheme may or may not allow encapsulating repair packets into RTP. However, Repair FEC Payload ID header is anyway always included into RTP payload before FEC repair symbols.
Explicit vs. Derived Source FEC Payload ID
FEC Scheme may require to append Source FEC Payload ID footer to the RTP packet. This breaks backward-compatibility with receivers that are not aware of FECFRAME.
Alternatively, FEC Scheme may allow to send unmodified RTP source packets and define rules how receiver can derive (compute) Source FEC Payload ID from RTP header.
RTP payload format
If FEC Scheme allows RTP for repair packets, it may define RTP payload format for this purpose. If it's not defined, we should:
There are several benefits of using RTP for repair packets:
However I don't like the idea of inventing custom non-standard RTP encapsulation.
FEC Scheme | Source packets | Repair packets |
---|---|---|
1-D Interleaved Parity (XOR) | Generic FEC | Generic FEC |
Reed-Solomon | Explicit Source FEC Payload ID footer required | RTP allowed, but payload format not defined |
LDPC | Explicit Source FEC Payload ID footer required | RTP allowed, but payload format not defined |
Raptor(Q) for Arbitrary Packet Flows | Explicit Source FEC Payload ID footer required | RTP allowed, but payload format not defined |
Raptor(Q) for Single Sequenced Flow | unmodified RTP packets are sent; Source FEC Payload ID is derived from RTP header | RTP payload format is defined in RFC 6682 |
Thus, all FEC Schemes except Raptor(Q) for Single Sequenced Flow requires to append additional footer to source packets and don't define RTP payload format for repair packets.
Also note that different FEC Schemes define different formats for Source FEC Payload ID and Repair Payload ID.
LDPC repair packets may be sent over UDP or over RTP. I didn't find any RFC with RTP payload format for LDPC repair packets over RTP.
However, we may invent our own non-standard format (using RFC 6682 as an example):
It makes sense to do it and support both modes for LDPC repair packets (UDP and RTP), because while the first one is standardized, the second one provides some additional benefits related to (de)multiplexing and encryption (see above).
For standard-compliant LDPC FEC Scheme support, we'll start with the following.
Encoder and Decoder
Packet formats:
Packet flow:
fec::Encoder
(composer for source packets is optional)In order to involve FECFRAME instead of marker bit I see these steps:
Due to block-boundaries detection approach was reworked, two tests became invalid:
in sender_receiver::fec_receiver receiver can't work properly because fec footer isn't placed insource packets.
I've reworked those tests.
Receiver panics if it gets an unexpected fec block size:
$ ./roc-send -vv -s 127.0.0.1:10001 -r 127.0.0.1:10002 -i ~/stash/loituma.wav --nbsrc 20 --nbrpr 10
$ ./roc-recv -vv -s :10001 -r :10002 --nbsrc 10 --nbrpr 5
[info] roc_sndio: initializing sox
[debug] roc_sndio: player: opening: name=(null) type=(null)
[debug] roc_sndio: driver waveaudio is not supported
[debug] roc_sndio: driver coreaudio is not supported
[debug] roc_sndio: selecting default driver pulseaudio
[debug] roc_sndio: detected defaults: name=default type=pulseaudio
[info] roc_sndio: player: name=default type=pulseaudio
[info] roc_sndio: player: bits=32 out_rate=48000 in_rate=0 ch=2 is_file=0
[info] roc_netio: udp receiver: opened port 0.0.0.0:10001
[info] roc_netio: udp receiver: opened port 0.0.0.0:10002
[debug] roc_netio: transceiver: starting event loop
[debug] roc_sndio: player: starting thread
[info] roc_pipeline: receiver: creating session
[debug] roc_packet: delayed reader: initializing: delay=8640
[debug] roc_fec: of decoder: initializing Reed-Solomon decoder
[debug] roc_audio: resampler: initializing window
[debug] roc_audio: latency monitor: initializing: target_latency=8640 in_rate=44100 out_rate=48000
[debug] roc_packet: router: detected new stream: source=2136340578 flags=0x8u
[debug] roc_audio: depacketizer: ts=128 loss_ratio=0.00000
[debug] roc_packet: router: detected new stream: source=1039193552 flags=0x10u
[debug] roc_packet: delayed reader: initial queue: delay=8640 queue=9280 packets=29
[debug] roc_packet: delayed reader: trimmed queue: delay=8640 queue=8640 packets=27
[debug] roc_fec: fec reader: repair queue: dropped=10
[debug] roc_audio: depacketizer: got first packet: zero_samples=7808
[debug] roc_audio: latency monitor: latency=8000 target=8640 fe=1.00000 trim_fe=1.00000 adj_fe=0.91875
[debug] roc_fec: fec reader: got first packet in a block, start decoding: n_packets_before=18 blk_sn=63330
src/modules/roc_core/array.h:66: error: roc_panic() called
PANIC: roc_fec: array: subscript out of range (index = 10, size = 10)
Traceback (most recent call last):
# /lib64/libc.so.6
# /lib64/libpthread.so.0
# roc::core::Thread::thread_runner_(void*)
# roc::sndio::Player::run()
# roc::sndio::Player::loop_()
# roc::pipeline::Receiver::read(roc::audio::Frame&)
# roc::audio::Mixer::read(roc::audio::Frame&)
# roc::audio::Resampler::read(roc::audio::Frame&)
# roc::audio::Resampler::renew_window_()
# roc::audio::Depacketizer::read(roc::audio::Frame&)
# roc::audio::Depacketizer::read_frame_(roc::audio::Frame&)
# roc::audio::Depacketizer::read_samples_(float*, float*)
# roc::audio::Depacketizer::update_packet_()
# roc::audio::Depacketizer::read_packet_()
# roc::packet::Watchdog::read()
# roc::rtp::Validator::read()
# roc::fec::Reader::read()
# roc::fec::Reader::read_()
# roc::fec::Reader::get_next_packet_()
# roc::fec::Reader::update_packets_()
# roc::fec::Reader::update_source_packets_()
# roc::core::Array<roc::core::SharedPtr<roc::packet::Packet, roc::core::RefCntOwnership> >::operator[](unsigned long)
# roc::core::panic(char const*, char const*, int, char const*, ...)
# roc::core::print_backtrace()
zsh: abort ./roc-recv -vv -s :10001 -r :10002 --nbsrc 10 --nbrpr 5
I've also seen another panic on RPi, but can't reproduce it anymore. Probably also related to mismatching fec block size, but don't know for sure.
$ ./roc-recv -vv -s :10001 -r :10002 --latency 10000 -t alsa
[info] roc_sndio: initializing sox
[debug] roc_sndio: player: opening: name=(null) type=alsa
[debug] roc_sndio: detected defaults: name=default type=alsa
[info] roc_sndio: player: name=default type=alsa
[debug] roc_sndio: [sox] alsa.c: select_format: trying #7
[info] roc_sndio: [sox] alsa.c: selecting format 7: S32_LE (Signed 32 bit Little Endian)
[info] roc_sndio: player: bits=32 out_rate=48000 in_rate=0 ch=2 is_file=0
[info] roc_netio: udp receiver: opened port 0.0.0.0:10001
[info] roc_netio: udp receiver: opened port 0.0.0.0:10002
[debug] roc_netio: transceiver: starting event loop
[debug] roc_sndio: player: starting thread
[info] roc_pipeline: receiver: creating session
[debug] roc_packet: delayed reader: initializing: delay=10000
[debug] roc_fec: of decoder: initializing Reed-Solomon decoder
[debug] roc_audio: resampler: initializing window
[debug] roc_audio: latency monitor: initializing: target_latency=10000 in_rate=44100 out_rate=48000
[debug] roc_packet: router: detected new stream: source=971390119 flags=0x8u
[debug] roc_audio: depacketizer: ts=128 loss_ratio=0.00000
[debug] roc_packet: router: detected new stream: source=1195497134 flags=0x10u
[debug] roc_packet: delayed reader: initial queue: delay=10000 queue=12800 packets=40
[debug] roc_packet: delayed reader: trimmed queue: delay=10000 queue=10240 packets=32
[debug] roc_fec: fec reader: repair queue: dropped=5
[debug] roc_audio: depacketizer: got first packet: zero_samples=11648
[debug] roc_fec: fec reader: got first packet in a block, start decoding: n_packets_before=2 blk_sn=10418
src/modules/roc_fec/reader.cpp:304: error: roc_panic() called
PANIC: roc_fec: fec->blknum != cur_block_sn_
No backtrace available
Aborted
FEC writer:
FEC reader:
Tests:
After finishing this task, we will be able to remove:
packet_length, fec_code, fec_block_source_packets, fec_block_repair_packets from API; these parameters will be detected dynamically when a session is started (#181);
marker bit usage.
One more panic to be fixed:
I: [alsa-sink-USB Audio] roc_pipeline: receiver: creating session
D: [alsa-sink-USB Audio] roc_packet: delayed reader: initializing: delay=10240
D: [alsa-sink-USB Audio] roc_fec: of decoder: initializing Reed-Solomon decoder
D: [alsa-sink-USB Audio] roc_audio: watchdog: initializing: max_blank_duration=88200 max_drops_duration=88200 drop_detection_window=14700
D: [alsa-sink-USB Audio] roc_audio: resampler: initializing: window_interp=128 window_size=32 frame_size=640 channels_num=2
D: [alsa-sink-USB Audio] roc_audio: resampler reader: initializing window
D: [alsa-sink-USB Audio] roc_audio: latency monitor: initializing: target_latency=10240 in_rate=44100 out_rate=44100
D: [alsa-sink-USB Audio] roc_packet: router: detected new stream: source=1620416338 flags=0x8u
D: [alsa-sink-USB Audio] roc_audio: depacketizer: ts=320 loss_ratio=0.00000
D: [alsa-sink-USB Audio] sink.c: Next volume change in 100877 usec
D: [alsa-sink-USB Audio] sink.c: Next volume change in 80725 usec
D: [alsa-sink-USB Audio] roc_packet: router: detected new stream: source=561046944 flags=0x10u
D: [alsa-sink-USB Audio] sink.c: Volume change to 55142 at 151681750 was written 97 usec late
D: [alsa-sink-USB Audio] roc_audio: watchdog: status: bbbbbbbbbbbbbbbbbbbb
D: [alsa-sink-USB Audio] roc_packet: delayed reader: initial queue: delay=10240 queue=10815 packets=35
D: [alsa-sink-USB Audio] roc_packet: delayed reader: trimmed queue: delay=10240 queue=10506 packets=34
D: [alsa-sink-USB Audio] roc_fec: fec reader: repair queue: dropped=10
D: [alsa-sink-USB Audio] roc_audio: depacketizer: got first packet: zero_samples=9600
D: [alsa-sink-USB Audio] roc_audio: latency monitor: latency=10153 target=10240 fe=1.00000 trim_fe=1.00000 adj_fe=1.00000
D: [alsa-sink-USB Audio] roc_audio: watchdog: status: bbbbbbbbbb..........
D: [alsa-sink-USB Audio] roc_fec: fec reader: got first packet in a block, start decoding: n_packets_before=16 blk_sn=6485
src/modules/roc_fec/target_openfec/roc_fec/of_decoder.cpp:106: error: roc_panic()
ERROR: roc_fec: of decoder: invalid payload size: size=1248, expected=1292
Oh, we already don't rely on the marker bit, but we still set it and it's also mentioned in comments. We should cleanup these usages.
We also need to remove the "123" payload type.
It seems that just removing these two lines from fec writer:
rtp.marker = (n == 0);
rtp.payload_type = 123;
doesn't break anything, so both of them are not really used already.
As per https://github.com/roc-project/roc/issues/48#issuecomment-172121898, it seems we should remove RTP headers from repair packets. Currently we add RTP header to repair packets but there is no RFC specifying how to fill it and we even don't initialize all fields and actually are producing an incorrect RTP sequence.
To accomplish this we should:
It seems there is a regression in recent commits. FEC reader now constantly reports some drops though I hear no glitches. Probably it's just a logging issue.
On 3e4d49a1aedabcb17699a36edc402d2eac6ea0ff:
[debug] roc_fec: fec reader: repair queue: dropped=10
[debug] roc_audio: depacketizer: got first packet: zero_samples=8960
[debug] roc_fec: fec reader: got first packet in a block, start decoding: n_packets_before=5 blk_sn=7476
[debug] roc_audio: watchdog: status: bbbbbbbb............
[debug] roc_audio: latency monitor: latency=11290 target=8820 fe=1.00000 trim_fe=1.00000 adj_fe=1.00000
[debug] roc_audio: latency monitor: latency=12198 target=8820 fe=1.00001 trim_fe=1.00001 adj_fe=1.00001
[debug] roc_audio: latency monitor: latency=10600 target=8820 fe=1.00135 trim_fe=1.00135 adj_fe=1.00135
[debug] roc_audio: latency monitor: latency=10713 target=8820 fe=1.00125 trim_fe=1.00125 adj_fe=1.00125
[debug] roc_audio: depacketizer: ts=1917440826 loss_ratio=0.00000
[debug] roc_audio: latency monitor: latency=9292 target=8820 fe=1.00104 trim_fe=1.00104 adj_fe=1.00104
On 7d33b7430bc7c30a5a3cdfec68d3c114c2ee568f (3 commits later):
[debug] roc_fec: fec reader: repair queue: dropped=10
[debug] roc_fec: fec reader: got first packet in a block, start decoding: n_packets_before=0 sn=55532 sbn=55532
[debug] roc_audio: depacketizer: got first packet: zero_samples=8960
[debug] roc_audio: watchdog: status: bbbbbbbb............
[debug] roc_audio: latency monitor: latency=11908 target=8820 fe=1.00000 trim_fe=1.00000 adj_fe=1.00000
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/6/30 .................... ....xxxxxx
[debug] roc_audio: latency monitor: latency=10344 target=8820 fe=1.00008 trim_fe=1.00008 adj_fe=1.00008
[debug] roc_fec: of decoder: repaired 0/5/30 .................... .....xxxxx
[debug] roc_fec: of decoder: repaired 0/12/30 ..................XX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/13/30 .................XXX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/13/30 .................XXX xxxxxxxxxx
[debug] roc_audio: latency monitor: latency=7676 target=8820 fe=1.00154 trim_fe=1.00154 adj_fe=1.00154
[debug] roc_fec: of decoder: repaired 0/3/30 .................... .......xxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/12/30 ..................XX xxxxxxxxxx
[debug] roc_audio: latency monitor: latency=12115 target=8820 fe=1.00179 trim_fe=1.00179 adj_fe=1.00179
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/14/30 ................XXXX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/15/30 ...............XXXXX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/14/30 ................XXXX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/1/30 .................... .........x
[debug] roc_fec: of decoder: repaired 0/18/30 ............XXXXXXXX xxxxxxxxxx
[debug] roc_audio: depacketizer: ts=1912044891 loss_ratio=0.00000
[debug] roc_audio: latency monitor: latency=8211 target=8820 fe=1.00154 trim_fe=1.00154 adj_fe=1.00154
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/11/30 ...................X xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/12/30 ..................XX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/12/30 ..................XX xxxxxxxxxx
[debug] roc_fec: of decoder: repaired 0/10/30 .................... xxxxxxxxxx
Reproduced on my new 64-bit Orange Pi.
I can reproduce this regression on my Orange Pi Lite2 with 200ms latency. I can't reproduce it on the same board but with 2s latency, neither can I reproduce it on my x86_64 laptop with 200ms latency.
There is one more related task. Currently, the receiver is configured to handle a single FEC scheme which is used for every session. Instead, we should allow to bind multiple ports employing different FEC schemes and automatically select the appropriate scheme when creating a new session, depending on which port we got its packets (#181).
After removing FEC configuration from the receiver, we should improve the roc-recv command-line interface to allow the user to bind multiple ports with different protocols, e.g. two Reed-Solomon ports, two LDPC ports, and one bare RTP port (#181).
Regression logs: https://gist.github.com/gavv/04b0ae40140877150f419bb1d02976c5
Ash on my head!
This "problem" is reproduced only when the roc-recv is new (after the last PR) but roc-send is old (before the PR). Recent versions of roc-send work well.
So it's not a regression and there is no bug, everything works as expected. I'm merging dshil/feature/48 branch into develop.
I've also pushed a few commits that fill the N (block size) field of LDPC repair packets and remove RTP headers from all repair packets.
Now our packet format should be fully standard compliant. The only remaining issue is to handle block size dynamically on the receiver.
Since we're now standard-compliant, I'm closing this issue.
The remaining issues were splitted into individual tasks:
See also #6 and #13.
I've found two alternative groups of RFCs:
FEC Framework (FECFRAME)
RFC 6363: FEC Framework
Supports RTP and externally defined FEC schemes.
RFC 6816: LDPC FEC Scheme for FEC Framework
LDPC-Staircase from OpenFEC implements this scheme.
RFC 5170: LDPC FEC
RFC 6816 inherits Section 6: FEC Scheme Specification and Section 5.7: PRNG from this RFC.
There is also Raptor(Q) support for FEC Framework:
Finally, these RFCs are mentioned by both FEC Framework and Generic FEC: