roc-streaming / roc-toolkit

Real-time audio streaming over the network.
https://roc-streaming.org
Mozilla Public License 2.0
1.09k stars 213 forks source link

Replace ad-hoc FEC packets with something standard-compliant #48

Closed gavv closed 5 years ago

gavv commented 8 years ago

See also #6 and #13.

I've found two alternative groups of RFCs:

  1. FEC Framework (FECFRAME)

    There is also Raptor(Q) support for FEC Framework:

  2. Generic FEC

Finally, these RFCs are mentioned by both FEC Framework and Generic FEC:

gavv commented 8 years ago

FECFRAME working group

gavv commented 8 years ago

Notes about FECFRAME and Generic FEC

gavv commented 8 years ago

May be interesting: RFC 3095: RObust Header Compression (ROHC)

gavv commented 8 years ago

Notes about FEC Building Block

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.

gavv commented 8 years ago

RFC 6816: LDPC FEC Scheme for FEC Framework

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:

Packet formats suggested in RFC 6816 (adapted to our use-case)

(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)  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

FSSI and SDP

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.

Note about OpenFEC

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).

gavv commented 8 years ago

FECFRAME and RTP

FECFRAME has RTP support (optional and FEC Scheme-dependent).

RTP support in various FEC Schemes for FECFRAME

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 over UDP and RTP

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).

gavv commented 8 years ago

What we need to do

For standard-compliant LDPC FEC Scheme support, we'll start with the following.

Encoder and Decoder

Packet formats:

Packet flow:

baranovmv commented 7 years ago

In order to involve FECFRAME instead of marker bit I see these steps:

baranovmv commented 7 years ago

Due to block-boundaries detection approach was reworked, two tests became invalid:

gavv commented 6 years ago

in sender_receiver::fec_receiver receiver can't work properly because fec footer isn't placed insource packets.

I've reworked those tests.

gavv commented 6 years ago

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
gavv commented 6 years ago

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
gavv commented 6 years ago

Remaining issues

FEC writer:

FEC reader:

Tests:

gavv commented 5 years ago

After finishing this task, we will be able to remove:

gavv commented 5 years ago

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
gavv commented 5 years ago

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.

gavv commented 5 years ago

We also need to remove the "123" payload type.

gavv commented 5 years ago

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.

gavv commented 5 years ago

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:

gavv commented 5 years ago

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.

gavv commented 5 years ago

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.

gavv commented 5 years ago

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).

gavv commented 5 years ago

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).

gavv commented 5 years ago

Regression logs: https://gist.github.com/gavv/04b0ae40140877150f419bb1d02976c5

gavv commented 5 years ago

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.

gavv commented 5 years ago

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.

gavv commented 5 years ago

Since we're now standard-compliant, I'm closing this issue.

The remaining issues were splitted into individual tasks: