diff --git a/docs/FNE Jitter Buffer Configuration.md b/docs/FNE Jitter Buffer Configuration.md index 5c9ac154..f302df4f 100644 --- a/docs/FNE Jitter Buffer Configuration.md +++ b/docs/FNE Jitter Buffer Configuration.md @@ -191,36 +191,6 @@ Suitable for: - Networks with frequent reordering - Deployments with multiple satellite/cellular links -## Monitoring and Tuning - -### Startup Logging - -When the FNE starts, jitter buffer configuration is displayed in the logs: - -``` -I: 2025-12-03 22:07:11.374 Jitter Buffer Enabled: yes -I: 2025-12-03 22:07:11.374 Jitter Buffer Default Max Size: 6 frames -I: 2025-12-03 22:07:11.374 Jitter Buffer Default Max Wait: 50000 microseconds -I: 2025-12-03 22:07:11.374 Jitter Buffer Peer Overrides: 3 peer(s) configured -``` - -### Per-Peer Configuration Logging - -When a peer connects and jitter buffering is enabled, you'll see (with verbose logging): - -``` -I: 2025-12-03 22:10:15.234 PEER 31003 jitter buffer configured (override): enabled=yes, maxSize=8, maxWait=80000 -I: 2025-12-03 22:10:16.456 PEER 31004 jitter buffer configured (default): enabled=yes, maxSize=4, maxWait=40000 -``` - -### Future Monitoring (To Be Implemented) - -Planned monitoring capabilities: -- Per-peer jitter buffer statistics (reordered frames, dropped frames, timeouts) -- InfluxDB metrics for trend analysis -- REST API endpoints for runtime statistics -- Automatic tuning recommendations based on observed behavior - ## Performance Characteristics ### CPU Impact @@ -282,29 +252,6 @@ Based on the adaptive jitter buffer design: - Check FNE logs for peer-specific configuration messages - Enable verbose and debug logging to trace packet flow -## Technical Details - -### RTP Sequence Number Handling - -The jitter buffer correctly handles RTP sequence number wraparound (RFC 3550): -- Sequence numbers are 16-bit unsigned integers (0-65535) -- After 65535, sequence resets to 0 -- Buffer correctly calculates sequence differences across wraparound -- No special configuration needed - -### Thread Safety - -- Each peer's jitter buffer map is protected by a mutex -- Per-stream buffers operate independently -- Safe for concurrent access from multiple worker threads - -### Memory Management - -- Buffered frames use RAII for automatic cleanup -- Timed-out frames are automatically freed -- Stream buffers cleaned up when streams end -- No memory leaks under normal operation - ## Best Practices 1. **Start Disabled**: Begin with jitter buffering disabled and enable only as needed @@ -334,11 +281,3 @@ jitterBuffer: maxSize: <2-8> # Optional, defaults to defaultMaxSize maxWait: <10000-200000> # Optional, defaults to defaultMaxWait ``` - -## Version History - -- **December 2025** - Initial implementation - - Zero-latency fast path - - Per-peer, per-stream adaptive buffering - - Configuration parsing and validation - - Sequence wraparound support diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index a66606b9..c2687f02 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -444,16 +444,16 @@ void FNENetwork::applyJitterBufferConfig(uint32_t peerId, FNEPeerConnection* con if (it != m_peerJitterOverrides.end()) { const JitterBufferConfig& config = it->second; connection->setJitterBufferParams(config.enabled, config.maxSize, config.maxWait); - if (m_verbose) { - LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (override): enabled=%s, maxSize=%u, maxWait=%u", - peerId, config.enabled ? "yes" : "no", config.maxSize, config.maxWait); + if (m_verbose && config.enabled) { + LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (per-peer), maxSize = %u, maxWait = %u", + peerId, config.maxSize, config.maxWait); } } else { // use default settings connection->setJitterBufferParams(m_jitterBufferEnabled, m_jitterMaxSize, m_jitterMaxWait); if (m_verbose && m_jitterBufferEnabled) { - LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (default): enabled=%s, maxSize=%u, maxWait=%u", - peerId, m_jitterBufferEnabled ? "yes" : "no", m_jitterMaxSize, m_jitterMaxWait); + LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (global), maxSize = %u, maxWait = %u", + peerId, m_jitterMaxSize, m_jitterMaxWait); } } } @@ -543,7 +543,7 @@ void FNENetwork::processNetworkTreeDisconnect(uint32_t peerId, uint32_t offendin } else { // is this perhaps a peer connection of ours? if (m_host->m_peerNetworks.size() > 0) { - for (auto peer : m_host->m_peerNetworks) { + for (auto& peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->getPeerId() == peerId) { LogWarning(LOG_STP, "PEER %u, upstream master requested disconnect for our peer connection, duplicate connection dropped", peerId); @@ -590,7 +590,7 @@ void FNENetwork::clock(uint32_t ms) m_peers.unlock(); if (m_forceListUpdate) { - for (auto peer : m_peers) { + for (auto& peer : m_peers) { peerMetadataUpdate(peer.first); } m_forceListUpdate = false; @@ -601,7 +601,7 @@ void FNENetwork::clock(uint32_t ms) // check to see if any peers have been quiet (no ping) longer than allowed std::vector peersToRemove = std::vector(); m_peers.shared_lock(); - for (auto peer : m_peers) { + for (auto& peer : m_peers) { uint32_t id = peer.first; FNEPeerConnection* connection = peer.second; if (connection != nullptr) { @@ -633,7 +633,7 @@ void FNENetwork::clock(uint32_t ms) // send peer updates to neighbor FNE peers if (m_host->m_peerNetworks.size() > 0) { - for (auto peer : m_host->m_peerNetworks) { + for (auto& peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { // perform master tree maintainence tasks if (peer.second->isEnabled() && peer.second->getRemotePeerId() > 0U && @@ -685,7 +685,7 @@ void FNENetwork::clock(uint32_t ms) if (m_updateLookupTimer.isRunning() && m_updateLookupTimer.hasExpired()) { // send network metadata updates to peers m_peers.shared_lock(); - for (auto peer : m_peers) { + for (auto& peer : m_peers) { uint32_t id = peer.first; FNEPeerConnection* connection = peer.second; if (connection != nullptr) { @@ -724,7 +724,7 @@ void FNENetwork::clock(uint32_t ms) if (m_haUpdateTimer.isRunning() && m_haUpdateTimer.hasExpired()) { // send peer updates to replica peers if (m_host->m_peerNetworks.size() > 0) { - for (auto peer : m_host->m_peerNetworks) { + for (auto& peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { std::vector haParams; @@ -801,7 +801,7 @@ void FNENetwork::close() ::memset(buffer, 0x00U, 1U); uint32_t streamId = createStreamId(); - for (auto peer : m_peers) { + for (auto& peer : m_peers) { writePeer(peer.first, m_peerId, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, streamId); } @@ -1799,7 +1799,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } else { // attempt to forward KMM key request to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), @@ -1863,7 +1863,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, @@ -1900,7 +1900,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, @@ -1936,7 +1936,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, @@ -1973,7 +1973,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, @@ -2023,7 +2023,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, @@ -2072,7 +2072,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { + for (auto& peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, @@ -2472,7 +2472,7 @@ void FNENetwork::processInCallCtrl(network::NET_ICC::ENUM command, network::NET_ // send ICC request to any peers connected to us that are neighbor FNEs m_peers.shared_lock(); - for (auto peer : m_peers) { + for (auto& peer : m_peers) { if (peer.second == nullptr) continue; if (peerId != peer.first) { @@ -3153,7 +3153,7 @@ bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::E // are we sending this ICC request upstream? if (toUpstream && systemReq) { if (m_host->m_peerNetworks.size() > 0U) { - for (auto peer : m_host->m_peerNetworks) { + for (auto& peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled()) { peer.second->writeMaster({ NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false, 0U, ssrc); @@ -3414,7 +3414,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin } // remove peers who were sent keys - for (auto peerId : peersToRemove) + for (auto& peerId : peersToRemove) m_peerReplicaKeyQueue.erase(peerId); s_keyQueueMutex.unlock();