-
-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic latency adjustment #688
Comments
@gavv What is the best way you see to convert |
What interface of this component do you presume would fit our needs here? I see it as a single function class like this:
Am I missing something?
|
Since both fec block and packet length are variable, I think we should compute fec block duration on fly. We can teach fec::Reader to do it. In the end of each block, we can compute its duration. However, this duration may be varying even for fixed block size, depending on encoding and packetization. We could compute maximum ever seen duration after last block size change. Or we could compute a moving maximum of last N blocks. Anyway, we'll need to add a pointer to fec::Reader to LatencyMonitor, and if it's non-null, LatencyMonitor should query block duration and pass it to LatencyTuner via LatencyMetrics. Also we'll need to add a reference to IFrameDecoder to fec::Reader and use it to fill duration of repaired packets (no full decoding, just query duration). I think we'll need packet duration during block duration computation. Since we also need to be able to tune latency on sender side, we also need to teach fec::Writer to compute FEC block duration; and teach FeedbackMonitor to write fec block duration into LatencyMetrics passed to LatencyTuner. |
Then I think we can inline it into LatencyTuner. I thought it would have more complicated logic. |
I've updated task description. |
Here's how we do it currently: void Filter::populate_(const packet::PacketPtr& packet) {
if (packet->rtp()->duration == 0) {
packet->rtp()->duration =
(packet::stream_timestamp_t)decoder_.decoded_sample_count(
packet->rtp()->payload.data(), packet->rtp()->payload.size());
}
} |
Wi-Fi + speedtest (no FEC, start from 100ms)@baranovmv Testing your branch on my home wifi, so far works great! Plotting using Streaming from RPi4 to laptop over 5ghz. Running a speed test in background (in web browser) to cause increased jitter. Full logs:
(
Start streaming
Start speed test in background
Stop speed test
|
I can confirm that time of spikes in |
Ethernet (no FEC, start from 40ms)Here's another test. RPi is streaming to laptop connected to laptop via dock station (no hubs/switches/routers). Full logs: Initial latency was 40ms, then it was slowly (during 10 minutes) adjusted as: 40 -> 31 -> 25 -> 53 -> 42 -> 33 ->26. Jitter is pretty stable but has rare spikes that caused those target latency adjustments. There were no drops at all. I'd say that something like 25ms is indeed very suitable target latency for this link. If we'd be able to filter out jitter spikes from calculations, I guess tuner would just stick to it.
|
fe_stableHere is rg -IN fe_stable | ch 0 -1 18:00:14.810 fe_stable=false 18:00:19.802 fe_stable=false 18:00:24.804 fe_stable=false 18:00:29.806 fe_stable=false 18:00:34.809 fe_stable=false 18:00:39.802 fe_stable=false 18:00:44.804 fe_stable=false 18:00:49.807 fe_stable=false 18:00:54.809 fe_stable=false 18:00:59.802 fe_stable=false 18:01:04.804 fe_stable=false 18:01:09.807 fe_stable=false 18:01:14.809 fe_stable=false 18:01:19.802 fe_stable=false 18:01:24.804 fe_stable=false 18:01:29.807 fe_stable=false 18:01:34.809 fe_stable=true 18:01:39.801 fe_stable=true 18:01:44.804 fe_stable=true 18:01:49.806 fe_stable=true 18:01:54.809 fe_stable=true 18:01:59.801 fe_stable=true 18:02:04.803 fe_stable=true 18:02:09.806 fe_stable=true 18:02:14.809 fe_stable=true 18:02:19.801 fe_stable=true 18:02:24.804 fe_stable=true 18:02:29.807 fe_stable=false 18:02:34.809 fe_stable=false 18:02:39.801 fe_stable=false 18:02:44.803 fe_stable=true 18:02:49.806 fe_stable=false 18:02:54.809 fe_stable=false 18:02:59.801 fe_stable=false 18:03:04.803 fe_stable=true 18:03:09.806 fe_stable=true 18:03:14.809 fe_stable=false 18:03:19.801 fe_stable=false 18:03:24.803 fe_stable=false 18:03:29.806 fe_stable=false 18:03:34.809 fe_stable=false 18:03:39.802 fe_stable=false 18:03:44.804 fe_stable=false 18:03:49.806 fe_stable=false 18:03:54.809 fe_stable=false 18:03:59.801 fe_stable=false 18:04:04.803 fe_stable=false 18:04:09.806 fe_stable=false 18:04:14.808 fe_stable=true 18:04:19.800 fe_stable=true 18:04:24.803 fe_stable=true 18:04:29.805 fe_stable=true 18:04:34.808 fe_stable=true 18:04:39.801 fe_stable=true 18:04:44.803 fe_stable=true 18:04:49.805 fe_stable=true 18:04:54.808 fe_stable=true 18:04:59.801 fe_stable=true 18:05:04.803 fe_stable=true 18:05:09.805 fe_stable=true 18:05:14.808 fe_stable=true 18:05:19.800 fe_stable=true 18:05:24.803 fe_stable=true 18:05:29.805 fe_stable=true 18:05:34.808 fe_stable=true 18:05:39.800 fe_stable=true 18:05:44.803 fe_stable=true 18:05:49.805 fe_stable=true 18:05:54.808 fe_stable=true 18:05:59.800 fe_stable=false 18:06:04.803 fe_stable=false 18:06:09.805 fe_stable=false 18:06:14.808 fe_stable=false 18:06:19.801 fe_stable=false 18:06:24.803 fe_stable=false 18:06:29.805 fe_stable=false 18:06:34.808 fe_stable=false 18:06:39.800 fe_stable=false 18:06:44.803 fe_stable=false 18:06:49.805 fe_stable=false 18:06:54.807 fe_stable=true 18:06:59.800 fe_stable=false 18:07:04.802 fe_stable=false 18:07:09.805 fe_stable=false 18:07:14.808 fe_stable=false 18:07:19.800 fe_stable=false 18:07:24.802 fe_stable=true 18:07:29.805 fe_stable=true 18:07:34.807 fe_stable=true 18:07:39.799 fe_stable=true 18:07:44.802 fe_stable=true 18:07:49.804 fe_stable=true 18:07:54.807 fe_stable=true 18:07:59.799 fe_stable=true 18:08:04.802 fe_stable=true 18:08:09.804 fe_stable=true 18:08:14.807 fe_stable=false 18:08:19.800 fe_stable=false 18:08:24.802 fe_stable=false 18:08:29.804 fe_stable=true 18:08:34.807 fe_stable=true 18:08:39.799 fe_stable=false 18:08:44.802 fe_stable=false 18:08:49.805 fe_stable=false 18:08:54.807 fe_stable=true 18:08:59.799 fe_stable=true 18:09:04.801 fe_stable=true 18:09:09.804 fe_stable=false 18:09:14.807 fe_stable=false 18:09:19.799 fe_stable=false 18:09:24.801 fe_stable=true 18:09:29.804 fe_stable=true 18:09:34.807 fe_stable=false 18:09:39.799 fe_stable=false 18:09:44.802 fe_stable=false 18:09:49.804 fe_stable=true 18:09:54.807 fe_stable=true 18:09:59.798 fe_stable=true 18:10:04.801 fe_stable=false 18:10:09.804 fe_stable=false 18:10:14.807 fe_stable=false 18:10:19.798 fe_stable=true 18:10:24.801 fe_stable=false 18:10:29.804 fe_stable=false 18:10:34.807 fe_stable=false 18:10:39.798 fe_stable=true |
RTCP crashWhen I enable RTCP, roc-send crashes when it gots first feedback report from receiver:
Crash: 18:52:23.180 [2408] [dbg] roc_sndio: [backend_map.cpp:20] backend map: initializing: n_backends=2 n_drivers=58 18:52:23.181 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=packet_pool object_size=496 min_slab=8B(1S) max_slab=0B(0S) 18:52:23.181 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=buffer_pool object_size=992 min_slab=8B(1S) max_slab=0B(0S) 18:52:23.181 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=buffer_pool object_size=4136 min_slab=8B(1S) max_slab=0B(0S) 18:52:23.181 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=encoding_pool object_size=240 min_slab=3840B(16S) max_slab=0B(0S) 18:52:23.181 [2409] [dbg] roc_netio: [network_loop.cpp:278] network loop: starting event loop 18:52:23.182 [2410] [dbg] roc_ctl: [control_task_queue.cpp:95] control task queue: starting event loop 18:52:23.182 [2408] [dbg] roc_node: [context.cpp:24] context: initializing 18:52:23.182 [2408] [inf] roc_sndio: [wav_source.cpp:177] wav source: opened input file: path=./long.wav in_bits=16 in_rate=44100 in_ch=2 18:52:23.182 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=slot_pool object_size=1072 min_slab=8B(1S) max_slab=0B(0S) 18:52:23.182 [2408] [dbg] roc_node: [sender.cpp:32] sender node: initializing 18:52:23.182 [2408] [dbg] roc_node: [sender.cpp:73] sender node: configuring audiosrc interface of slot 0 18:52:23.182 [2408] [inf] roc_pipeline: [sender_sink.cpp:72] sender sink: adding slot 18:52:23.183 [2408] [dbg] roc_rtp: [identity.cpp:50] rtp identity: ssrc=1540280261 cname=853129e1-8270-4cf8-aa47-bc0d202891db 18:52:23.183 [2408] [inf] roc_node: [sender.cpp:121] sender node: connecting audiosrc interface of slot 0 to rtp+rs8m://192.168.0.101:20001 18:52:23.183 [2409] [dbg] roc_netio: [udp_port.cpp:129] udp port: : opened port 18:52:23.184 [2408] [inf] roc_node: [sender.cpp:201] sender node: bound audiosrc interface to 0.0.0.0:46121 18:52:23.184 [2409] [dbg] roc_netio: [network_loop.cpp:427] network loop: starting sending packets on port 18:52:23.184 [2408] [dbg] roc_pipeline: [sender_slot.cpp:61] sender slot: adding audiosrc endpoint rtp+rs8m 18:52:23.184 [2408] [dbg] roc_node: [sender.cpp:73] sender node: configuring audiorpr interface of slot 0 18:52:23.184 [2408] [inf] roc_node: [sender.cpp:121] sender node: connecting audiorpr interface of slot 0 to rs8m://192.168.0.101:20002 18:52:23.185 [2408] [dbg] roc_node: [sender.cpp:493] sender node: sharing audiosrc interface port with audiorpr interface 18:52:23.185 [2408] [dbg] roc_pipeline: [sender_slot.cpp:61] sender slot: adding audiorpr endpoint rs8m 18:52:23.185 [2408] [dbg] roc_fec: [openfec_encoder.cpp:28] openfec encoder: initializing: codec=rs m=8 18:52:23.185 [2408] [dbg] roc_fec: [writer.cpp:91] fec writer: update block size: cur_sbl=0 cur_rbl=0 new_sbl=18 new_rbl=10 18:52:23.185 [2408] [dbg] roc_audio: [packetizer.cpp:60] packetizer: initializing: packet_length=5.000ms samples_per_packet=221 payload_size=884 sample_spec= chset=> 18:52:23.185 [2408] [dbg] roc_audio: [latency_tuner.cpp:199] latency tuner: initializing: target_latency=0(0.000ms) min_latency=0(0.000ms) max_latency=0(0.000ms) latency_upper_limit_coef=1.700000 stale_tolerance=0(0.000ms) scaling_interval=0(0.000ms) scaling_tolerance=0.000000 backend=niq profile=intact 18:52:23.185 [2408] [dbg] roc_node: [sender.cpp:73] sender node: configuring audioctl interface of slot 0 18:52:23.185 [2408] [inf] roc_node: [sender.cpp:121] sender node: connecting audioctl interface of slot 0 to rtcp://192.168.0.101:20003 18:52:23.186 [2408] [dbg] roc_node: [sender.cpp:493] sender node: sharing audiosrc interface port with audioctl interface 18:52:23.186 [2408] [dbg] roc_pipeline: [sender_slot.cpp:61] sender slot: adding audioctl endpoint rtcp 18:52:23.186 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=stream_pool object_size=856 min_slab=6848B(8S) max_slab=0B(0S) 18:52:23.186 [2408] [dbg] roc_core: [slab_pool_impl.cpp:61] pool: initializing: name=address_pool object_size=200 min_slab=800B(4S) max_slab=0B(0S) 18:52:23.186 [2408] [dbg] roc_rtcp: [reporter.cpp:69] rtcp reporter: initializing: local_ssrc=1540280261 local_cname="853129e1-8270-4cf8-aa47-bc0d202891db" report_mode=address report_addr=192.168.0.101:20003 timeout=5000.000ms 18:52:23.186 [2408] [dbg] roc_audio: [feedback_monitor.cpp:66] feedback monitor: start gathering feedback 18:52:23.186 [2409] [dbg] roc_netio: [network_loop.cpp:447] network loop: starting receiving packets on port 18:52:23.187 [2408] [dbg] roc_sndio: [pump.cpp:55] pump: starting main loop 18:52:23.187 [2408] [dbg] roc_packet: [router.cpp:103] router: detected new stream: source_id=1540280261 route_flags=[audio] packet_flags=[rtp,fec,audio,prepared,composed] 18:52:23.187 [2408] [dbg] roc_netio: [udp_port.cpp:579] udp port: : recv=0 send=1 send_nb=1 18:52:23.187 [2408] [dbg] roc_rtp: [timestamp_extractor.cpp:82] timestamp extractor: returning mapping: cts:1711983143187832242/sts:1946609469 18:52:23.187 [2408] [dbg] roc_rtcp: [reporter.cpp:1452] rtcp reporter: creating address: remote_addr=192.168.0.101:20003 18:52:23.188 [2408] [dbg] roc_rtcp: [reporter.cpp:1172] rtcp reporter: completed index rebuild: n_streams=0 n_addrs=1 18:52:23.188 [2408] [dbg] roc_rtcp: [communicator.cpp:855] rtcp communicator: generated_pkts=1 processed_pkts=0 proc_errs=0 18:52:23.188 [2408] [dbg] roc_pipeline: [pipeline_loop.cpp:483] pipeline loop: tasks=5 in_place=1.00 in_frame=0.00 preempts=0 sched=0/0 18:52:23.277 [2408] [dbg] roc_packet: [router.cpp:121] router: detected new stream: source_id=none route_flags=[repair] packet_flags=[fec,repair,prepared,composed] 18:52:25.197 [2408] [dbg] roc_rtcp: [reporter.cpp:1399] rtcp reporter: creating stream: ssrc=1469140577 18:52:25.197 [2408] [dbg] roc_rtcp: [reporter.cpp:310] rtcp reporter: updating stream address: ssrc=1469140577 old_addr= new_addr=192.168.0.101:20003 18:52:25.197 [2408] [inf] roc_audio: [feedback_monitor.cpp:81] feedback monitor: got first report from receiver: source=1469140577 |
Wi-Fi + noisy link (FEC, standard conservative settings)Full logs: Settings:
(I think these values can be default in roc-recv). There were no speedtest, however the link itself was noisy since it's evening and everybody around use Wi-Fi / Bluetooth.
So we started from 200ms, jumped to 306ms, then after a big jitter spike to 621ms, then gradually rolled back to 311ms (during 12 minutes). Drops happened only once during that jitter spike. FEC partially recovered those drops.
|
The problem can be solved by using larger FEC blocks on sender, e.g. |
If I use (Jitter is below 200ms in that case). |
I did some more experiments in this setup (streaming from laptop to orange pi connected to 2 different 5ghz access points, in turn connected together via cable). Some observations:
All this is expected, just want to document an actual experiment. BTW even after doing all this I'm still not satisfied with that setup - sometimes burst losses are too long to be covered by a sane FEC block size. I wonder if RTX would help here (I don't know if in this periods ALL packets are dropped or just MANY of them. Also I don't know yet exact reason of losses - physical link or router queues). Also I think compression would help here. |
During some short period, I was constantly getting this panic:
I never restarted sender. I restarted receiver multiple times and got panic each time. After a short time, panic disappears. And it starts reproducing again for a short time after a minute or two. And so on. I suppose it's related to seqnum overflows. First two files in zip are just logs. Second to files also has RTP headers, I added |
The whole combination of factor that enabled the issue are still unclear. The fix is just in simplifying the logic by disabling estimator for the blocks with absent first packet, which should not be a great deal. Apparently the issue could take place during start-up transition, and it basically lead to wrong block length estimation with two times greater value than correct one.
@gavv I'm about to enable We've got
|
- Remove handling of recovered packets, because LinkMeter can never see them. - Extract update_seqnums_() method.
Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated
Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated
Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated
- update help messages - update man pages - add missing options to roc-send - use "auto" instead of "default" for CLI options - cleanup options order
Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated
- update help messages - update man pages - add missing options to roc-send - use "auto" instead of "default" for CLI options - cleanup options order
- remove implementation of IReader, as it's not used - pass IWriter to constructor instead of set_writer() In addition, sample spec is now retrieved from encoding instead of ctor argument. This is needed for link meter created for repaired packets, where sample spec is not known in before.
Bug fixes: - Fix bogus values of max_jitter_overhead and mean_jitter_overhead LatencyConfig refactoring: - Extract LatencyConfig into separate .h/.cpp - Simplify LatencyConfig::deduce_defaults() logic and split it into multiple functions - Allow deduce_defaults() to return error - Validate latency config in deduce_defaults() LatencyTuner refactoring: - Use min/max latency for adaptive tuning range, and latency tolerance for bounds checking - Allow bounds checking even in adaptive mode - Remove validation from constructor (as it's now done in deduce_defaults) - Cleanup logging CLI: - Update roc-recv options parser according to new changes Misc.: - Typos, formatting, naming, method ordering - Increase max log message size to 512
- Remove handling of recovered packets, because LinkMeter can never see them. - Extract update_seqnums_() method.
Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated
- update help messages - update man pages - add missing options to roc-send - use "auto" instead of "default" for CLI options - cleanup options order
- Extract JitterMeter class. - Remove `min_jitter` metric (it is almost always zero or very close to it). - Replace `max_jitter` with `peak_jitter`, which is similar to maximum, but tries to exclude harmless spikes to reduce latency. See comments in JitterMeter for details on the algorithm.
- Extract JitterMeter class. - Remove `min_jitter` metric (it is almost always zero or very close to it). - Replace `max_jitter` with `peak_jitter`, which is similar to maximum, but tries to exclude harmless spikes to reduce latency. See comments in JitterMeter for details on the algorithm.
- Extract JitterMeter class. - Remove `min_jitter` metric (it is almost always zero or very close to it). - Replace `max_jitter` with `peak_jitter`, which is similar to maximum, but tries to exclude harmless spikes to reduce latency. See comments in JitterMeter for details on the algorithm.
- Extract JitterMeter class. - Remove `min_jitter` metric (it is almost always zero or very close to it). - Replace `max_jitter` with `peak_jitter`, which is similar to maximum, but tries to exclude harmless spikes to reduce latency. See comments in JitterMeter for details on the algorithm.
Implementation
Fixes
latency < target - tolerance
orlatency > target + tolerance
latency < min - tolerance
orlatency > max + tolerance
Tests
LinkMeter tests
pipeline tests:
test_receiver_source
roc-toolkit/src/tests/roc_pipeline/test_receiver_source.cpp
Lines 2296 to 2305 in 6eb0a59
metrics_packet_counters
(simulate losses and reorders and check total_packets, lost_packets, ext_first_seqnum, ext_last_seqnum)metrics_jitter
(simulate timestamp jitter and check jitter metric) - moved to Pipeline tests for jitter & dynamic latency #765pipeline tests:
test_loopback_sink_2_source
: cover new metrics (jitter, total_packets, lost_packets - smoke test that all 3 metrics are available on both sender and receiver)roc-toolkit/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp
Lines 350 to 353 in 6eb0a59
roc-toolkit/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp
Lines 381 to 384 in 6eb0a59
public_api
tests: cover new metrics (jitter, total_packets, lost_packets - smoke test that all 3 metrics are available on both sender and receiver)Docs
The text was updated successfully, but these errors were encountered: