You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/src/common/network/AdaptiveJitterBuffer.h

221 lines
7.9 KiB

// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file AdaptiveJitterBuffer.h
* @ingroup network_core
* @file AdaptiveJitterBuffer.cpp
* @ingroup network_core
*/
#if !defined(__ADAPTIVE_JITTER_BUFFER_H__)
#define __ADAPTIVE_JITTER_BUFFER_H__
#include "common/Defines.h"
#include <cstdint>
#include <cstring>
#include <map>
#include <vector>
#include <mutex>
#include <chrono>
namespace network
{
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define DEFAULT_JITTER_MAX_SIZE 4U
#define DEFAULT_JITTER_MAX_WAIT 40000U
#define MIN_JITTER_MAX_SIZE 2U
#define MAX_JITTER_MAX_SIZE 8U
#define MIN_JITTER_MAX_WAIT 10000U
#define MAX_JITTER_MAX_WAIT 200000U
// ---------------------------------------------------------------------------
// Structure Declaration
// ---------------------------------------------------------------------------
/**
* @brief Represents a buffered frame in the jitter buffer.
* @ingroup network_core
*/
struct BufferedFrame {
uint16_t seq; //<! RTP sequence number
uint8_t* data; //<! Frame data
uint32_t length; //<! Frame length
uint64_t timestamp; //<! Reception timestamp (microseconds)
/**
* @brief Initializes a new instance of the BufferedFrame struct.
*/
BufferedFrame() : seq(0U), data(nullptr), length(0U), timestamp(0ULL) { /* stub */ }
/**
* @brief Initializes a new instance of the BufferedFrame struct.
* @param sequence RTP sequence number.
* @param buffer Frame data buffer.
* @param len Frame length.
*/
BufferedFrame(uint16_t sequence, const uint8_t* buffer, uint32_t len) :
seq(sequence),
data(nullptr),
length(len),
timestamp(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count())
{
if (len > 0U && buffer != nullptr) {
data = new uint8_t[len];
::memcpy(data, buffer, len);
}
}
/**
* @brief Finalizes a instance of the BufferedFrame struct.
*/
~BufferedFrame()
{
if (data != nullptr) {
delete[] data;
data = nullptr;
}
}
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief Implements an adaptive jitter buffer for RTP streams.
* @ingroup network_core
*
* This class provides minimal-latency jitter buffering with a zero-latency
* fast path for in-order packets. Out-of-order packets are buffered briefly
* to allow reordering, with adaptive timeout based on observed jitter.
*/
class HOST_SW_API AdaptiveJitterBuffer {
public:
/**
* @brief Initializes a new instance of the AdaptiveJitterBuffer class.
* @param maxBufferSize Maximum number of frames to buffer (default: 4).
* @param maxWaitTime Maximum time to wait for out-of-order frames in microseconds (default: 40000 = 40ms).
*/
AdaptiveJitterBuffer(uint16_t maxBufferSize = 4U, uint32_t maxWaitTime = 40000U);
/**
* @brief Finalizes a instance of the AdaptiveJitterBuffer class.
*/
~AdaptiveJitterBuffer();
/**
* @brief Processes an incoming RTP frame.
* @param seq RTP sequence number.
* @param data Frame data.
* @param length Frame length.
* @param[out] readyFrames Vector of frames ready for delivery (in sequence order).
* @returns bool True if frame was processed successfully, otherwise false.
*
* This method implements a zero-latency fast path for in-order packets.
* Out-of-order packets are buffered and returned when they become sequential.
*/
bool processFrame(uint16_t seq, const uint8_t* data, uint32_t length,
std::vector<BufferedFrame*>& readyFrames);
/**
* @brief Checks for timed-out buffered frames and forces their delivery.
* @param[out] timedOutFrames Vector of frames that have exceeded the wait time.
* @param currentTime Current time in microseconds (0 = use system clock).
*
* This should be called periodically (e.g., every 10-20ms) to ensure
* buffered frames are delivered even if missing packets never arrive.
*/
void checkTimeouts(std::vector<BufferedFrame*>& timedOutFrames,
uint64_t currentTime = 0ULL);
/**
* @brief Resets the jitter buffer state.
* @param clearStats If true, also resets statistics (default: false).
*
* This should be called when a stream ends or restarts.
*/
void reset(bool clearStats = false);
/**
* @brief Gets the current buffer occupancy.
* @returns size_t Number of frames currently buffered.
*/
size_t getBufferSize() const { return m_buffer.size(); }
/**
* @brief Gets the next expected sequence number.
* @returns uint16_t Next expected sequence number.
*/
uint16_t getNextExpectedSeq() const { return m_nextExpectedSeq; }
/**
* @brief Gets statistics about jitter buffer performance.
* @param[out] totalFrames Total frames processed.
* @param[out] reorderedFrames Frames that were out-of-order but successfully reordered.
* @param[out] droppedFrames Frames dropped due to buffer overflow or severe reordering.
* @param[out] timedOutFrames Frames delivered due to timeout (missing packets).
*/
void getStatistics(uint64_t& totalFrames, uint64_t& reorderedFrames,
uint64_t& droppedFrames, uint64_t& timedOutFrames) const;
/**
* @brief Sets the maximum buffer size.
* @param maxBufferSize Maximum number of frames to buffer.
*/
void setMaxBufferSize(uint16_t maxBufferSize) { m_maxBufferSize = maxBufferSize; }
/**
* @brief Sets the maximum wait time for out-of-order frames.
* @param maxWaitTime Maximum wait time in microseconds.
*/
void setMaxWaitTime(uint32_t maxWaitTime) { m_maxWaitTime = maxWaitTime; }
private:
std::map<uint16_t, BufferedFrame*> m_buffer;
mutable std::mutex m_mutex;
uint16_t m_nextExpectedSeq;
uint16_t m_maxBufferSize;
uint32_t m_maxWaitTime;
uint64_t m_totalFrames;
uint64_t m_reorderedFrames;
uint64_t m_droppedFrames;
uint64_t m_timedOutFrames;
bool m_initialized;
/**
* @brief Delivers all sequential frames from the buffer.
* @param[out] readyFrames Vector to append ready frames to.
*
* Internal helper that flushes all frames starting from m_nextExpectedSeq
* until a gap is encountered.
*/
void flushSequentialFrames(std::vector<BufferedFrame*>& readyFrames);
/**
* @brief Calculates sequence number difference handling wraparound.
* @param seq1 First sequence number.
* @param seq2 Second sequence number.
* @returns int32_t Signed difference (seq1 - seq2).
*/
int32_t seqDiff(uint16_t seq1, uint16_t seq2) const;
};
} // namespace network
#endif // __ADAPTIVE_JITTER_BUFFER_H__

Powered by TurnKey Linux.