mirror of https://github.com/ossrs/srs.git
Upgrade libsrt to v1.5.3. v5.0.183 v6.0.81 (#3808)
fix https://github.com/ossrs/srs/issues/3155 Build srt-1-fit fails with `standard attributes in middle of decl-specifiers` on GCC 12,Arch Linux. See https://github.com/Haivision/srt/releases/tag/v1.5.3pull/3806/head
parent
f9bba0a9b0
commit
c5e067fb0b
@ -0,0 +1,57 @@
|
||||
#
|
||||
# SRT - Secure, Reliable, Transport Copyright (c) 2022 Haivision Systems Inc.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
# Check for C++11 std::put_time().
|
||||
#
|
||||
# Sets:
|
||||
# HAVE_CXX_STD_PUT_TIME
|
||||
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
function(CheckCXXStdPutTime)
|
||||
|
||||
unset(HAVE_CXX_STD_PUT_TIME CACHE)
|
||||
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6
|
||||
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
unset(CMAKE_REQUIRED_LINK_OPTIONS)
|
||||
|
||||
set(CheckCXXStdPutTime_CODE
|
||||
"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
int main(void)
|
||||
{
|
||||
const int result = 0;
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm tm = *std::localtime(&t);
|
||||
std::cout
|
||||
<< std::put_time(&tm, \"%FT%T\")
|
||||
<< std::setfill('0')
|
||||
<< std::setw(6)
|
||||
<< std::endl;
|
||||
return result;
|
||||
}
|
||||
"
|
||||
)
|
||||
|
||||
# NOTE: Should we set -std or use the current compiler configuration.
|
||||
# It seems that the top level build does not track the compiler
|
||||
# in a consistent manner. So Maybe we need this?
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++11")
|
||||
|
||||
# Check that the compiler can build the std::put_time() example:
|
||||
message(STATUS "Checking for C++ 'std::put_time()':")
|
||||
check_cxx_source_compiles(
|
||||
"${CheckCXXStdPutTime_CODE}"
|
||||
HAVE_CXX_STD_PUT_TIME)
|
||||
|
||||
endfunction(CheckCXXStdPutTime)
|
@ -0,0 +1,3 @@
|
||||
## Scripts for building SRT for Android
|
||||
|
||||
See [Building SRT for Android](../../docs/build/build-android.md) for the instructions.
|
@ -0,0 +1,111 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo_help()
|
||||
{
|
||||
echo "Usage: $0 [options...]"
|
||||
echo " -n NDK root path for the build"
|
||||
echo " -a Target API level"
|
||||
echo " -t Space-separated list of target architectures"
|
||||
echo " Android supports the following architectures: armeabi-v7a arm64-v8a x86 x86_64"
|
||||
echo " -e Encryption library to be used. Possible options: openssl (default) mbedtls"
|
||||
echo " -o OpenSSL version. E.g. 1.1.1l"
|
||||
echo " -m Mbed TLS version. E.g. v2.26.0"
|
||||
echo
|
||||
echo "Example: ./build-android -n /home/username/Android/Sdk/ndk/23.0.7599858 -a 28 -t \"arm64-v8a x86_64\""
|
||||
echo
|
||||
}
|
||||
|
||||
# Init optional command line vars
|
||||
NDK_ROOT=""
|
||||
API_LEVEL=28
|
||||
BUILD_TARGETS="armeabi-v7a arm64-v8a x86 x86_64"
|
||||
OPENSSL_VERSION=1.1.1l
|
||||
ENC_LIB=openssl
|
||||
MBEDTLS_VERSION=v2.26.0
|
||||
|
||||
while getopts n:a:t:o:s:e:m: option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
n) NDK_ROOT=${OPTARG};;
|
||||
a) API_LEVEL=${OPTARG};;
|
||||
t) BUILD_TARGETS=${OPTARG};;
|
||||
o) OPENSSL_VERSION=${OPTARG};;
|
||||
s) SRT_VERSION=${OPTARG};;
|
||||
e) ENC_LIB=${OPTARG};;
|
||||
m) MBEDTLS_VERSION=${OPTARG};;
|
||||
*) twentytwo=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
echo_help
|
||||
|
||||
if [ -z "$NDK_ROOT" ] ; then
|
||||
echo "NDK directory not set."
|
||||
exit 128
|
||||
else
|
||||
if [ ! -d "$NDK_ROOT" ]; then
|
||||
echo "NDK directory does not exist: $NDK_ROOT"
|
||||
exit 128
|
||||
fi
|
||||
fi
|
||||
|
||||
SCRIPT_DIR=$(pwd)
|
||||
HOST_TAG='unknown'
|
||||
unamestr=$(uname -s)
|
||||
if [ "$unamestr" = 'Linux' ]; then
|
||||
HOST_TAG='linux-x86_64'
|
||||
elif [ "$unamestr" = 'Darwin' ]; then
|
||||
if [ $(uname -p) = 'arm' ]; then
|
||||
echo "NDK does not currently support ARM64"
|
||||
exit 128
|
||||
else
|
||||
HOST_TAG='darwin-x86_64'
|
||||
fi
|
||||
fi
|
||||
|
||||
# Write files relative to current location
|
||||
BASE_DIR=$(pwd)
|
||||
case "${BASE_DIR}" in
|
||||
*\ * )
|
||||
echo "Your path contains whitespaces, which is not supported by 'make install'."
|
||||
exit 128
|
||||
;;
|
||||
esac
|
||||
cd "${BASE_DIR}"
|
||||
|
||||
if [ $ENC_LIB = 'openssl' ]; then
|
||||
echo "Building OpenSSL $OPENSSL_VERSION"
|
||||
$SCRIPT_DIR/mkssl -n $NDK_ROOT -a $API_LEVEL -t "$BUILD_TARGETS" -o $OPENSSL_VERSION -d $BASE_DIR -h $HOST_TAG
|
||||
elif [ $ENC_LIB = 'mbedtls' ]; then
|
||||
if [ ! -d $BASE_DIR/mbedtls ]; then
|
||||
git clone https://github.com/ARMmbed/mbedtls mbedtls
|
||||
if [ ! -z "$MBEDTLS_VERSION" ]; then
|
||||
git -C $BASE_DIR/mbedtls checkout $MBEDTLS_VERSION
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Unknown encryption library. Possible options: openssl mbedtls"
|
||||
exit 128
|
||||
fi
|
||||
|
||||
# Build working copy of srt repository
|
||||
REPO_DIR="../.."
|
||||
|
||||
for build_target in $BUILD_TARGETS; do
|
||||
LIB_DIR=$BASE_DIR/$build_target/lib
|
||||
JNI_DIR=$BASE_DIR/prebuilt/$build_target
|
||||
|
||||
mkdir -p $JNI_DIR
|
||||
|
||||
if [ $ENC_LIB = 'mbedtls' ]; then
|
||||
$SCRIPT_DIR/mkmbedtls -n $NDK_ROOT -a $API_LEVEL -t $build_target -s $BASE_DIR/mbedtls -i $BASE_DIR/$build_target
|
||||
cp $LIB_DIR/libmbedcrypto.so $JNI_DIR/libmbedcrypto.so
|
||||
cp $LIB_DIR/libmbedtls.so $JNI_DIR/libmbedtls.so
|
||||
cp $LIB_DIR/libmbedx509.so $JNI_DIR/libmbedx509.so
|
||||
fi
|
||||
|
||||
git -C $REPO_DIR clean -fd -e scripts
|
||||
$SCRIPT_DIR/mksrt -n $NDK_ROOT -a $API_LEVEL -t $build_target -e $ENC_LIB -s $REPO_DIR -i $BASE_DIR/$build_target
|
||||
cp $LIB_DIR/libsrt.so $JNI_DIR/libsrt.so
|
||||
done
|
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts s:i:t:n:a: option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
s) SRC_DIR=${OPTARG};;
|
||||
i) INSTALL_DIR=${OPTARG};;
|
||||
t) ARCH_ABI=${OPTARG};;
|
||||
n) NDK_ROOT=${OPTARG};;
|
||||
a) API_LEVEL=${OPTARG};;
|
||||
*) twentytwo=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
BUILD_DIR=/tmp/mbedtls_android_build
|
||||
rm -rf $BUILD_DIR
|
||||
mkdir $BUILD_DIR
|
||||
cd $BUILD_DIR
|
||||
cmake -DENABLE_TESTING=Off -DUSE_SHARED_MBEDTLS_LIBRARY=On \
|
||||
-DCMAKE_PREFIX_PATH=$INSTALL_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DCMAKE_ANDROID_NDK=$NDK_ROOT \
|
||||
-DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=$API_LEVEL -DCMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \
|
||||
-DCMAKE_C_FLAGS="-fPIC" -DCMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo $SRC_DIR
|
||||
cmake --build .
|
||||
cmake --install .
|
@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts s:i:t:n:a:e: option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
s) SRC_DIR=${OPTARG};;
|
||||
i) INSTALL_DIR=${OPTARG};;
|
||||
t) ARCH_ABI=${OPTARG};;
|
||||
n) NDK_ROOT=${OPTARG};;
|
||||
a) API_LEVEL=${OPTARG};;
|
||||
e) ENC_LIB=${OPTARG};;
|
||||
*) twentytwo=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
cd $SRC_DIR
|
||||
./configure --use-enclib=$ENC_LIB \
|
||||
--use-openssl-pc=OFF \
|
||||
--OPENSSL_INCLUDE_DIR=$INSTALL_DIR/include \
|
||||
--OPENSSL_CRYPTO_LIBRARY=$INSTALL_DIR/lib/libcrypto.a --OPENSSL_SSL_LIBRARY=$INSTALL_DIR/lib/libssl.a \
|
||||
--STATIC_MBEDTLS=FALSE \
|
||||
--MBEDTLS_INCLUDE_DIR=$INSTALL_DIR/include --MBEDTLS_INCLUDE_DIRS=$INSTALL_DIR/include \
|
||||
--MBEDTLS_LIBRARIES=$INSTALL_DIR/lib/libmbedtls.so \
|
||||
--CMAKE_PREFIX_PATH=$INSTALL_DIR --CMAKE_INSTALL_PREFIX=$INSTALL_DIR --CMAKE_ANDROID_NDK=$NDK_ROOT \
|
||||
--CMAKE_SYSTEM_NAME=Android --CMAKE_SYSTEM_VERSION=$API_LEVEL --CMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \
|
||||
--CMAKE_C_FLAGS="-fPIC" --CMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \
|
||||
--enable-c++11 --enable-stdcxx-sync \
|
||||
--enable-debug=2 --enable-logging=0 --enable-heavy-logging=0 --enable-apps=0
|
||||
make
|
||||
make install
|
@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts n:o:a:t:d:h: option
|
||||
do
|
||||
case "${option}"
|
||||
in
|
||||
n) ANDROID_NDK=${OPTARG};;
|
||||
o) OPENSSL_VERSION=${OPTARG};;
|
||||
a) API_LEVEL=${OPTARG};;
|
||||
t) BUILD_TARGETS=${OPTARG};;
|
||||
d) OUT_DIR=${OPTARG};;
|
||||
h) HOST_TAG=${OPTARG};;
|
||||
*) twentytwo=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
BUILD_DIR=/tmp/openssl_android_build
|
||||
|
||||
if [ ! -d openssl-${OPENSSL_VERSION} ]
|
||||
then
|
||||
if [ ! -f openssl-${OPENSSL_VERSION}.tar.gz ]
|
||||
then
|
||||
wget https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz || exit 128
|
||||
fi
|
||||
tar xzf openssl-${OPENSSL_VERSION}.tar.gz || exit 128
|
||||
fi
|
||||
|
||||
cd openssl-${OPENSSL_VERSION} || exit 128
|
||||
|
||||
|
||||
##### export ndk directory. Required by openssl-build-scripts #####
|
||||
case ${OPENSSL_VERSION} in
|
||||
1.1.1*)
|
||||
export ANDROID_NDK_HOME=$ANDROID_NDK
|
||||
;;
|
||||
*)
|
||||
export ANDROID_NDK_ROOT=$ANDROID_NDK
|
||||
;;
|
||||
esac
|
||||
|
||||
export PATH=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG/bin:$PATH
|
||||
|
||||
##### build-function #####
|
||||
build_the_thing() {
|
||||
make clean
|
||||
./Configure $SSL_TARGET -D__ANDROID_API__=$API_LEVEL && \
|
||||
make SHLIB_EXT=.so && \
|
||||
make install SHLIB_EXT=.so DESTDIR=$DESTDIR || exit 128
|
||||
}
|
||||
|
||||
##### set variables according to build-tagret #####
|
||||
for build_target in $BUILD_TARGETS
|
||||
do
|
||||
case $build_target in
|
||||
armeabi-v7a)
|
||||
DESTDIR="$BUILD_DIR/armeabi-v7a"
|
||||
SSL_TARGET="android-arm"
|
||||
;;
|
||||
x86)
|
||||
DESTDIR="$BUILD_DIR/x86"
|
||||
SSL_TARGET="android-x86"
|
||||
;;
|
||||
x86_64)
|
||||
DESTDIR="$BUILD_DIR/x86_64"
|
||||
SSL_TARGET="android-x86_64"
|
||||
;;
|
||||
arm64-v8a)
|
||||
DESTDIR="$BUILD_DIR/arm64-v8a"
|
||||
SSL_TARGET="android-arm64"
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -rf $DESTDIR
|
||||
build_the_thing
|
||||
#### copy libraries and includes to output-directory #####
|
||||
mkdir -p $OUT_DIR/$build_target/include
|
||||
cp -R $DESTDIR/usr/local/include/* $OUT_DIR/$build_target/include
|
||||
cp -R $DESTDIR/usr/local/ssl/* $OUT_DIR/$build_target/
|
||||
mkdir -p $OUT_DIR/$build_target/lib
|
||||
cp -R $DESTDIR/usr/local/lib/*.so $OUT_DIR/$build_target/lib
|
||||
cp -R $DESTDIR/usr/local/lib/*.a $OUT_DIR/$build_target/lib
|
||||
done
|
||||
|
||||
echo Success
|
@ -0,0 +1,10 @@
|
||||
/* Copyright © 2023 Steve Lhomme */
|
||||
/* SPDX-License-Identifier: ISC */
|
||||
#include <windows.h>
|
||||
#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600 /* _WIN32_WINNT_VISTA */
|
||||
#error NOPE
|
||||
#endif
|
||||
int main(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,611 +0,0 @@
|
||||
/*
|
||||
* SRT - Secure, Reliable, Transport
|
||||
* Copyright (c) 2018 Haivision Systems Inc.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the
|
||||
above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the University of Illinois
|
||||
nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
/*****************************************************************************
|
||||
written by
|
||||
Yunhong Gu, last updated 05/05/2009
|
||||
modified by
|
||||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef INC_SRT_BUFFER_H
|
||||
#define INC_SRT_BUFFER_H
|
||||
|
||||
#include "udt.h"
|
||||
#include "list.h"
|
||||
#include "queue.h"
|
||||
#include "tsbpd_time.h"
|
||||
#include "utilities.h"
|
||||
|
||||
// The notation used for "circular numbers" in comments:
|
||||
// The "cicrular numbers" are numbers that when increased up to the
|
||||
// maximum become zero, and similarly, when the zero value is decreased,
|
||||
// it turns into the maximum value minus one. This wrapping works the
|
||||
// same for adding and subtracting. Circular numbers cannot be multiplied.
|
||||
|
||||
// Operations done on these numbers are marked with additional % character:
|
||||
// a %> b : a is later than b
|
||||
// a ++% (++%a) : shift a by 1 forward
|
||||
// a +% b : shift a by b
|
||||
// a == b : equality is same as for just numbers
|
||||
|
||||
namespace srt {
|
||||
|
||||
/// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND)
|
||||
class AvgBufSize
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
|
||||
public:
|
||||
AvgBufSize()
|
||||
: m_dBytesCountMAvg(0.0)
|
||||
, m_dCountMAvg(0.0)
|
||||
, m_dTimespanMAvg(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
bool isTimeToUpdate(const time_point& now) const;
|
||||
void update(const time_point& now, int pkts, int bytes, int timespan_ms);
|
||||
|
||||
public:
|
||||
inline double pkts() const { return m_dCountMAvg; }
|
||||
inline double timespan_ms() const { return m_dTimespanMAvg; }
|
||||
inline double bytes() const { return m_dBytesCountMAvg; }
|
||||
|
||||
private:
|
||||
time_point m_tsLastSamplingTime;
|
||||
double m_dBytesCountMAvg;
|
||||
double m_dCountMAvg;
|
||||
double m_dTimespanMAvg;
|
||||
};
|
||||
|
||||
/// The class to estimate source bitrate based on samples submitted to the buffer.
|
||||
/// Is currently only used by the CSndBuffer.
|
||||
class CRateEstimator
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
typedef sync::steady_clock::duration duration;
|
||||
public:
|
||||
CRateEstimator();
|
||||
|
||||
public:
|
||||
uint64_t getInRatePeriod() const { return m_InRatePeriod; }
|
||||
|
||||
/// Retrieve input bitrate in bytes per second
|
||||
int getInputRate() const { return m_iInRateBps; }
|
||||
|
||||
void setInputRateSmpPeriod(int period);
|
||||
|
||||
/// Update input rate calculation.
|
||||
/// @param [in] time current time in microseconds
|
||||
/// @param [in] pkts number of packets newly added to the buffer
|
||||
/// @param [in] bytes number of payload bytes in those newly added packets
|
||||
///
|
||||
/// @return Current size of the data in the sending list.
|
||||
void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0);
|
||||
|
||||
void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); }
|
||||
|
||||
private: // Constants
|
||||
static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms
|
||||
static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms
|
||||
static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload
|
||||
static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE;
|
||||
|
||||
private:
|
||||
int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime
|
||||
int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime
|
||||
time_point m_tsInRateStartTime;
|
||||
uint64_t m_InRatePeriod; // usec
|
||||
int m_iInRateBps; // Input Rate in Bytes/sec
|
||||
};
|
||||
|
||||
class CSndBuffer
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
typedef sync::steady_clock::duration duration;
|
||||
|
||||
public:
|
||||
// XXX There's currently no way to access the socket ID set for
|
||||
// whatever the buffer is currently working for. Required to find
|
||||
// some way to do this, possibly by having a "reverse pointer".
|
||||
// Currently just "unimplemented".
|
||||
std::string CONID() const { return ""; }
|
||||
|
||||
/// @brief CSndBuffer constructor.
|
||||
/// @param size initial number of blocks (each block to store one packet payload).
|
||||
/// @param maxpld maximum packet payload.
|
||||
CSndBuffer(int size = 32, int maxpld = 1500);
|
||||
~CSndBuffer();
|
||||
|
||||
public:
|
||||
/// Insert a user buffer into the sending list.
|
||||
/// For @a w_mctrl the following fields are used:
|
||||
/// INPUT:
|
||||
/// - msgttl: timeout for retransmitting the message, if lost
|
||||
/// - inorder: request to deliver the message in order of sending
|
||||
/// - srctime: local time as a base for packet's timestamp (0 if unused)
|
||||
/// - pktseq: sequence number to be stamped on the packet (-1 if unused)
|
||||
/// - msgno: message number to be stamped on the packet (-1 if unused)
|
||||
/// OUTPUT:
|
||||
/// - srctime: local time stamped on the packet (same as input, if input wasn't 0)
|
||||
/// - pktseq: sequence number to be stamped on the next packet
|
||||
/// - msgno: message number stamped on the packet
|
||||
/// @param [in] data pointer to the user data block.
|
||||
/// @param [in] len size of the block.
|
||||
/// @param [inout] w_mctrl Message control data
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl);
|
||||
|
||||
/// Read a block of data from file and insert it into the sending list.
|
||||
/// @param [in] ifs input file stream.
|
||||
/// @param [in] len size of the block.
|
||||
/// @return actual size of data added from the file.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int addBufferFromFile(std::fstream& ifs, int len);
|
||||
|
||||
/// Find data position to pack a DATA packet from the furthest reading point.
|
||||
/// @param [out] packet the packet to read.
|
||||
/// @param [out] origintime origin time stamp of the message
|
||||
/// @param [in] kflags Odd|Even crypto key flag
|
||||
/// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented.
|
||||
/// @return Actual length of data read.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc);
|
||||
|
||||
/// Peek an information on the next original data packet to send.
|
||||
/// @return origin time stamp of the next packet; epoch start time otherwise.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
time_point peekNextOriginal() const;
|
||||
|
||||
/// Find data position to pack a DATA packet for a retransmission.
|
||||
/// @param [in] offset offset from the last ACK point (backward sequence number difference)
|
||||
/// @param [out] packet the packet to read.
|
||||
/// @param [out] origintime origin time stamp of the message
|
||||
/// @param [out] msglen length of the message
|
||||
/// @return Actual length of data read (return 0 if offset too large, -1 if TTL exceeded).
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int readData(const int offset, CPacket& w_packet, time_point& w_origintime, int& w_msglen);
|
||||
|
||||
/// Get the time of the last retransmission (if any) of the DATA packet.
|
||||
/// @param [in] offset offset from the last ACK point (backward sequence number difference)
|
||||
///
|
||||
/// @return Last time of the last retransmission event for the corresponding DATA packet.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
time_point getPacketRexmitTime(const int offset);
|
||||
|
||||
/// Update the ACK point and may release/unmap/return the user data according to the flag.
|
||||
/// @param [in] offset number of packets acknowledged.
|
||||
int32_t getMsgNoAt(const int offset);
|
||||
|
||||
void ackData(int offset);
|
||||
|
||||
/// Read size of data still in the sending list.
|
||||
/// @return Current size of the data in the sending list.
|
||||
int getCurrBufSize() const;
|
||||
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time);
|
||||
|
||||
void updAvgBufSize(const time_point& time);
|
||||
int getAvgBufSize(int& bytes, int& timespan);
|
||||
int getCurrBufSize(int& bytes, int& timespan);
|
||||
|
||||
/// @brief Get the buffering delay of the oldest message in the buffer.
|
||||
/// @return the delay value.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
duration getBufferingDelay(const time_point& tnow) const;
|
||||
|
||||
uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); }
|
||||
|
||||
/// Retrieve input bitrate in bytes per second
|
||||
int getInputRate() const { return m_rateEstimator.getInputRate(); }
|
||||
|
||||
void resetInputRateSmpPeriod(bool disable = false) { m_rateEstimator.resetInputRateSmpPeriod(disable); }
|
||||
|
||||
const CRateEstimator& getRateEstimator() const { return m_rateEstimator; }
|
||||
|
||||
void setRateEstimator(const CRateEstimator& other) { m_rateEstimator = other; }
|
||||
|
||||
private:
|
||||
void increase();
|
||||
|
||||
private:
|
||||
mutable sync::Mutex m_BufLock; // used to synchronize buffer operation
|
||||
|
||||
struct Block
|
||||
{
|
||||
char* m_pcData; // pointer to the data block
|
||||
int m_iLength; // payload length of the block.
|
||||
|
||||
int32_t m_iMsgNoBitset; // message number
|
||||
int32_t m_iSeqNo; // sequence number for scheduling
|
||||
time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending.
|
||||
time_point m_tsRexmitTime; // packet retransmission time
|
||||
int m_iTTL; // time to live (milliseconds)
|
||||
|
||||
Block* m_pNext; // next block
|
||||
|
||||
int32_t getMsgSeq()
|
||||
{
|
||||
// NOTE: this extracts message ID with regard to REXMIT flag.
|
||||
// This is valid only for message ID that IS GENERATED in this instance,
|
||||
// not provided by the peer. This can be otherwise sent to the peer - it doesn't matter
|
||||
// for the peer that it uses LESS bits to represent the message.
|
||||
return m_iMsgNoBitset & MSGNO_SEQ::mask;
|
||||
}
|
||||
|
||||
} * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock;
|
||||
|
||||
// m_pBlock: The head pointer
|
||||
// m_pFirstBlock: The first block
|
||||
// m_pCurrBlock: The current block
|
||||
// m_pLastBlock: The last block (if first == last, buffer is empty)
|
||||
|
||||
struct Buffer
|
||||
{
|
||||
char* m_pcData; // buffer
|
||||
int m_iSize; // size
|
||||
Buffer* m_pNext; // next buffer
|
||||
} * m_pBuffer; // physical buffer
|
||||
|
||||
int32_t m_iNextMsgNo; // next message number
|
||||
|
||||
int m_iSize; // buffer size (number of packets)
|
||||
const int m_iBlockLen; // maximum length of a block holding packet payload (excluding packet header).
|
||||
int m_iCount; // number of used blocks
|
||||
|
||||
int m_iBytesCount; // number of payload bytes in queue
|
||||
time_point m_tsLastOriginTime;
|
||||
|
||||
AvgBufSize m_mavg;
|
||||
CRateEstimator m_rateEstimator;
|
||||
|
||||
private:
|
||||
CSndBuffer(const CSndBuffer&);
|
||||
CSndBuffer& operator=(const CSndBuffer&);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if (!ENABLE_NEW_RCVBUFFER)
|
||||
|
||||
class CRcvBuffer
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
typedef sync::steady_clock::duration duration;
|
||||
|
||||
public:
|
||||
// XXX There's currently no way to access the socket ID set for
|
||||
// whatever the queue is currently working for. Required to find
|
||||
// some way to do this, possibly by having a "reverse pointer".
|
||||
// Currently just "unimplemented".
|
||||
std::string CONID() const { return ""; }
|
||||
|
||||
static const int DEFAULT_SIZE = 65536;
|
||||
/// Construct the buffer.
|
||||
/// @param [in] queue CUnitQueue that actually holds the units (packets)
|
||||
/// @param [in] bufsize_pkts in units (packets)
|
||||
CRcvBuffer(CUnitQueue* queue, int bufsize_pkts = DEFAULT_SIZE);
|
||||
~CRcvBuffer();
|
||||
|
||||
public:
|
||||
/// Write data into the buffer.
|
||||
/// @param [in] unit pointer to a data unit containing new packet
|
||||
/// @param [in] offset offset from last ACK point.
|
||||
/// @return 0 is success, -1 if data is repeated.
|
||||
int addData(CUnit* unit, int offset);
|
||||
|
||||
/// Read data into a user buffer.
|
||||
/// @param [in] data pointer to user buffer.
|
||||
/// @param [in] len length of user buffer.
|
||||
/// @return size of data read.
|
||||
int readBuffer(char* data, int len);
|
||||
|
||||
/// Read data directly into file.
|
||||
/// @param [in] file C++ file stream.
|
||||
/// @param [in] len expected length of data to write into the file.
|
||||
/// @return size of data read.
|
||||
int readBufferToFile(std::fstream& ofs, int len);
|
||||
|
||||
/// Update the ACK point of the buffer.
|
||||
/// @param [in] len number of units to be acknowledged.
|
||||
/// @return 1 if a user buffer is fulfilled, otherwise 0.
|
||||
int ackData(int len);
|
||||
|
||||
/// Query how many buffer space left for data receiving.
|
||||
/// Actually only acknowledged packets, that are still in the buffer,
|
||||
/// are considered to take buffer space.
|
||||
///
|
||||
/// @return size of available buffer space (including user buffer) for data receiving.
|
||||
/// Not counting unacknowledged packets.
|
||||
int getAvailBufSize() const;
|
||||
|
||||
/// Query how many data has been continuously received (for reading) and ready to play (tsbpdtime < now).
|
||||
/// @return size of valid (continous) data for reading.
|
||||
int getRcvDataSize() const;
|
||||
|
||||
/// Query how many data was received and acknowledged.
|
||||
/// @param [out] bytes bytes
|
||||
/// @param [out] spantime spantime
|
||||
/// @return size in pkts of acked data.
|
||||
int getRcvDataSize(int& bytes, int& spantime);
|
||||
|
||||
/// Query a 1 sec moving average of how many data was received and acknowledged.
|
||||
/// @param [out] bytes bytes
|
||||
/// @param [out] spantime spantime
|
||||
/// @return size in pkts of acked data.
|
||||
int getRcvAvgDataSize(int& bytes, int& spantime);
|
||||
|
||||
/// Query how many data of the receive buffer is acknowledged.
|
||||
/// @param [in] now current time in us.
|
||||
/// @return none.
|
||||
void updRcvAvgDataSize(const time_point& now);
|
||||
|
||||
/// Query the received average payload size.
|
||||
/// @return size (bytes) of payload size
|
||||
unsigned getRcvAvgPayloadSize() const;
|
||||
|
||||
struct ReadingState
|
||||
{
|
||||
time_point tsStart;
|
||||
time_point tsLastAck;
|
||||
time_point tsEnd;
|
||||
int iNumAcknowledged;
|
||||
int iNumUnacknowledged;
|
||||
};
|
||||
|
||||
ReadingState debugGetReadingState() const;
|
||||
|
||||
/// Form a string of the current buffer fullness state.
|
||||
/// number of packets acknowledged, TSBPD readiness, etc.
|
||||
std::string strFullnessState(const time_point& tsNow) const;
|
||||
|
||||
/// Mark the message to be dropped from the message list.
|
||||
/// @param [in] msgno message number.
|
||||
/// @param [in] using_rexmit_flag whether the MSGNO field uses rexmit flag (if not, one more bit is part of the
|
||||
/// msgno value)
|
||||
void dropMsg(int32_t msgno, bool using_rexmit_flag);
|
||||
|
||||
/// read a message.
|
||||
/// @param [out] data buffer to write the message into.
|
||||
/// @param [in] len size of the buffer.
|
||||
/// @return actuall size of data read.
|
||||
int readMsg(char* data, int len);
|
||||
|
||||
#if ENABLE_HEAVY_LOGGING
|
||||
void readMsgHeavyLogging(int p);
|
||||
#endif
|
||||
|
||||
/// read a message.
|
||||
/// @param [out] data buffer to write the message into.
|
||||
/// @param [in] len size of the buffer.
|
||||
/// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay
|
||||
/// @return actuall size of data read.
|
||||
int readMsg(char* data, int len, SRT_MSGCTRL& w_mctrl, int upto);
|
||||
|
||||
/// Query if data is ready to read (tsbpdtime <= now if TsbPD is active).
|
||||
/// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay
|
||||
/// of next packet in recv buffer, ready or not.
|
||||
/// @param [out] curpktseq Sequence number of the packet if there is one ready to play
|
||||
/// @return true if ready to play, false otherwise (tsbpdtime may be !0 in
|
||||
/// both cases).
|
||||
bool isRcvDataReady(time_point& w_tsbpdtime, int32_t& w_curpktseq, int32_t seqdistance);
|
||||
|
||||
#ifdef SRT_DEBUG_TSBPD_OUTJITTER
|
||||
void debugTraceJitter(time_point t);
|
||||
#else
|
||||
void debugTraceJitter(time_point) {}
|
||||
#endif /* SRT_DEBUG_TSBPD_OUTJITTER */
|
||||
|
||||
bool isRcvDataReady();
|
||||
bool isRcvDataAvailable() { return m_iLastAckPos != m_iStartPos; }
|
||||
CPacket* getRcvReadyPacket(int32_t seqdistance);
|
||||
|
||||
/// Set TimeStamp-Based Packet Delivery Rx Mode
|
||||
/// @param [in] timebase localtime base (uSec) of packet time stamps including buffering delay
|
||||
/// @param [in] delay aggreed TsbPD delay
|
||||
void setRcvTsbPdMode(const time_point& timebase, const duration& delay);
|
||||
|
||||
/// Add packet timestamp for drift caclculation and compensation
|
||||
/// @param [in] timestamp packet time stamp
|
||||
/// @param [in] tsPktArrival arrival time of the packet used to extract the drift sample.
|
||||
/// @param [in] rtt RTT sample
|
||||
bool addRcvTsbPdDriftSample(uint32_t timestamp, const time_point& tsPktArrival, int rtt);
|
||||
|
||||
#ifdef SRT_DEBUG_TSBPD_DRIFT
|
||||
void printDriftHistogram(int64_t iDrift);
|
||||
void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg);
|
||||
#endif
|
||||
|
||||
/// Get information on the 1st message in queue.
|
||||
// Parameters (of the 1st packet queue, ready to play or not):
|
||||
/// @param [out] w_tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0
|
||||
/// if none
|
||||
/// @param [out] w_passack true if 1st ready packet is not yet acknowleged (allowed to be delivered to the app)
|
||||
/// @param [out] w_skipseqno SRT_SEQNO_NONE or seq number of 1st unacknowledged pkt ready to play preceeded by
|
||||
/// missing packets.
|
||||
/// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base if exist packet ready-to-play
|
||||
/// and larger than base
|
||||
/// @retval true 1st packet ready to play (tsbpdtime <= now). Not yet acknowledged if passack == true
|
||||
/// @retval false IF tsbpdtime = 0: rcv buffer empty; ELSE:
|
||||
/// IF skipseqno != SRT_SEQNO_NONE, packet ready to play preceeded by missing packets.;
|
||||
/// IF skipseqno == SRT_SEQNO_NONE, no missing packet but 1st not ready to play.
|
||||
bool getRcvFirstMsg(time_point& w_tsbpdtime,
|
||||
bool& w_passack,
|
||||
int32_t& w_skipseqno,
|
||||
int32_t& w_curpktseq,
|
||||
int32_t base_seq = SRT_SEQNO_NONE);
|
||||
|
||||
/// Update the ACK point of the buffer.
|
||||
/// @param [in] len size of data to be skip & acknowledged.
|
||||
void skipData(int len);
|
||||
|
||||
#if ENABLE_HEAVY_LOGGING
|
||||
void reportBufferStats() const; // Heavy logging Debug only
|
||||
#endif
|
||||
bool empty() const
|
||||
{
|
||||
// This will not always return the intended value,
|
||||
// that is, it may return false when the buffer really is
|
||||
// empty - but it will return true then in one of next calls.
|
||||
// This function will be always called again at some point
|
||||
// if it returned false, and on true the connection
|
||||
// is going to be broken - so this behavior is acceptable.
|
||||
return m_iStartPos == m_iLastAckPos;
|
||||
}
|
||||
bool full() const { return m_iStartPos == (m_iLastAckPos + 1) % m_iSize; }
|
||||
int capacity() const { return m_iSize; }
|
||||
|
||||
private:
|
||||
/// This gives up unit at index p. The unit is given back to the
|
||||
/// free unit storage for further assignment for the new incoming
|
||||
/// data.
|
||||
size_t freeUnitAt(size_t p)
|
||||
{
|
||||
CUnit* u = m_pUnit[p];
|
||||
m_pUnit[p] = NULL;
|
||||
size_t rmbytes = u->m_Packet.getLength();
|
||||
m_pUnitQueue->makeUnitFree(u);
|
||||
return rmbytes;
|
||||
}
|
||||
|
||||
/// Adjust receive queue to 1st ready to play message (tsbpdtime < now).
|
||||
/// Parameters (of the 1st packet queue, ready to play or not):
|
||||
/// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if
|
||||
/// none
|
||||
/// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base
|
||||
/// @retval true 1st packet ready to play without discontinuity (no hole)
|
||||
/// @retval false tsbpdtime = 0: no packet ready to play
|
||||
bool getRcvReadyMsg(time_point& w_tsbpdtime, int32_t& w_curpktseq, int upto, int base_seq = SRT_SEQNO_NONE);
|
||||
|
||||
public:
|
||||
/// @brief Get clock drift in microseconds.
|
||||
int64_t getDrift() const { return m_tsbpd.drift(); }
|
||||
|
||||
public:
|
||||
int32_t getTopMsgno() const;
|
||||
|
||||
void getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift);
|
||||
|
||||
void applyGroupTime(const time_point& timebase, bool wrapcheck, uint32_t delay, const duration& udrift);
|
||||
void applyGroupDrift(const time_point& timebase, bool wrapcheck, const duration& udrift);
|
||||
time_point getPktTsbPdTime(uint32_t timestamp);
|
||||
int debugGetSize() const;
|
||||
time_point debugGetDeliveryTime(int offset);
|
||||
|
||||
size_t dropData(int len);
|
||||
|
||||
private:
|
||||
int extractData(char* data, int len, int p, int q, bool passack);
|
||||
bool accessMsg(int& w_p, int& w_q, bool& w_passack, int64_t& w_playtime, int upto);
|
||||
|
||||
/// Describes the state of the first N packets
|
||||
std::string debugTimeState(size_t first_n_pkts) const;
|
||||
|
||||
/// thread safe bytes counter of the Recv & Ack buffer
|
||||
/// @param [in] pkts acked or removed pkts from rcv buffer (used with acked = true)
|
||||
/// @param [in] bytes number of bytes added/delete (if negative) to/from rcv buffer.
|
||||
/// @param [in] acked true when adding new pkt in RcvBuffer; false when acking/removing pkts to/from buffer
|
||||
void countBytes(int pkts, int bytes, bool acked = false);
|
||||
|
||||
private:
|
||||
bool scanMsg(int& w_start, int& w_end, bool& w_passack);
|
||||
|
||||
int shift(int basepos, int shift) const { return (basepos + shift) % m_iSize; }
|
||||
|
||||
/// Simplified versions with ++ and --; avoid using division instruction
|
||||
int shiftFwd(int basepos) const
|
||||
{
|
||||
if (++basepos == m_iSize)
|
||||
return 0;
|
||||
return basepos;
|
||||
}
|
||||
|
||||
int shiftBack(int basepos) const
|
||||
{
|
||||
if (basepos == 0)
|
||||
return m_iSize - 1;
|
||||
return --basepos;
|
||||
}
|
||||
|
||||
private:
|
||||
CUnit** m_pUnit; // Array of pointed units collected in the buffer
|
||||
const int m_iSize; // Size of the internal array of CUnit* items
|
||||
CUnitQueue* m_pUnitQueue; // the shared unit queue
|
||||
|
||||
int m_iStartPos; // HEAD: first packet available for reading
|
||||
int m_iLastAckPos; // the last ACKed position (exclusive), follows the last readable
|
||||
// EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1
|
||||
int m_iMaxPos; // delta between acked-TAIL and reception-TAIL
|
||||
|
||||
int m_iNotch; // the starting read point of the first unit
|
||||
// (this is required for stream reading mode; it's
|
||||
// the position in the first unit in the list
|
||||
// up to which data are already retrieved;
|
||||
// in message reading mode it's unused and always 0)
|
||||
|
||||
sync::Mutex m_BytesCountLock; // used to protect counters operations
|
||||
int m_iBytesCount; // Number of payload bytes in the buffer
|
||||
int m_iAckedPktsCount; // Number of acknowledged pkts in the buffer
|
||||
int m_iAckedBytesCount; // Number of acknowledged payload bytes in the buffer
|
||||
unsigned m_uAvgPayloadSz; // Average payload size for dropped bytes estimation
|
||||
|
||||
CTsbpdTime m_tsbpd;
|
||||
|
||||
AvgBufSize m_mavg;
|
||||
|
||||
private:
|
||||
CRcvBuffer();
|
||||
CRcvBuffer(const CRcvBuffer&);
|
||||
CRcvBuffer& operator=(const CRcvBuffer&);
|
||||
};
|
||||
|
||||
#endif // !ENABLE_NEW_RCVBUFFER
|
||||
|
||||
} // namespace srt
|
||||
|
||||
#endif
|
@ -0,0 +1,730 @@
|
||||
/*
|
||||
* SRT - Secure, Reliable, Transport
|
||||
* Copyright (c) 2018 Haivision Systems Inc.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the
|
||||
above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the University of Illinois
|
||||
nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
/*****************************************************************************
|
||||
written by
|
||||
Yunhong Gu, last updated 03/12/2011
|
||||
modified by
|
||||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "platform_sys.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "buffer_snd.h"
|
||||
#include "packet.h"
|
||||
#include "core.h" // provides some constants
|
||||
#include "logging.h"
|
||||
|
||||
namespace srt {
|
||||
|
||||
using namespace std;
|
||||
using namespace srt_logging;
|
||||
using namespace sync;
|
||||
|
||||
CSndBuffer::CSndBuffer(int size, int maxpld, int authtag)
|
||||
: m_BufLock()
|
||||
, m_pBlock(NULL)
|
||||
, m_pFirstBlock(NULL)
|
||||
, m_pCurrBlock(NULL)
|
||||
, m_pLastBlock(NULL)
|
||||
, m_pBuffer(NULL)
|
||||
, m_iNextMsgNo(1)
|
||||
, m_iSize(size)
|
||||
, m_iBlockLen(maxpld)
|
||||
, m_iAuthTagSize(authtag)
|
||||
, m_iCount(0)
|
||||
, m_iBytesCount(0)
|
||||
{
|
||||
// initial physical buffer of "size"
|
||||
m_pBuffer = new Buffer;
|
||||
m_pBuffer->m_pcData = new char[m_iSize * m_iBlockLen];
|
||||
m_pBuffer->m_iSize = m_iSize;
|
||||
m_pBuffer->m_pNext = NULL;
|
||||
|
||||
// circular linked list for out bound packets
|
||||
m_pBlock = new Block;
|
||||
Block* pb = m_pBlock;
|
||||
char* pc = m_pBuffer->m_pcData;
|
||||
|
||||
for (int i = 0; i < m_iSize; ++i)
|
||||
{
|
||||
pb->m_iMsgNoBitset = 0;
|
||||
pb->m_pcData = pc;
|
||||
pc += m_iBlockLen;
|
||||
|
||||
if (i < m_iSize - 1)
|
||||
{
|
||||
pb->m_pNext = new Block;
|
||||
pb = pb->m_pNext;
|
||||
}
|
||||
}
|
||||
pb->m_pNext = m_pBlock;
|
||||
|
||||
m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock;
|
||||
|
||||
setupMutex(m_BufLock, "Buf");
|
||||
}
|
||||
|
||||
CSndBuffer::~CSndBuffer()
|
||||
{
|
||||
Block* pb = m_pBlock->m_pNext;
|
||||
while (pb != m_pBlock)
|
||||
{
|
||||
Block* temp = pb;
|
||||
pb = pb->m_pNext;
|
||||
delete temp;
|
||||
}
|
||||
delete m_pBlock;
|
||||
|
||||
while (m_pBuffer != NULL)
|
||||
{
|
||||
Buffer* temp = m_pBuffer;
|
||||
m_pBuffer = m_pBuffer->m_pNext;
|
||||
delete[] temp->m_pcData;
|
||||
delete temp;
|
||||
}
|
||||
|
||||
releaseMutex(m_BufLock);
|
||||
}
|
||||
|
||||
void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl)
|
||||
{
|
||||
int32_t& w_msgno = w_mctrl.msgno;
|
||||
int32_t& w_seqno = w_mctrl.pktseq;
|
||||
int64_t& w_srctime = w_mctrl.srctime;
|
||||
const int& ttl = w_mctrl.msgttl;
|
||||
const int iPktLen = getMaxPacketLen();
|
||||
const int iNumBlocks = countNumPacketsRequired(len, iPktLen);
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" << m_iCount << "/" << m_iSize);
|
||||
// Retrieve current time before locking the mutex to be closer to packet submission event.
|
||||
const steady_clock::time_point tnow = steady_clock::now();
|
||||
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
// Dynamically increase sender buffer if there is not enough room.
|
||||
while (iNumBlocks + m_iCount >= m_iSize)
|
||||
{
|
||||
HLOGC(bslog.Debug, log << "addBuffer: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers...");
|
||||
increase();
|
||||
}
|
||||
|
||||
const int32_t inorder = w_mctrl.inorder ? MSGNO_PACKET_INORDER::mask : 0;
|
||||
HLOGC(bslog.Debug,
|
||||
log << CONID() << "addBuffer: adding " << iNumBlocks << " packets (" << len << " bytes) to send, msgno="
|
||||
<< (w_msgno > 0 ? w_msgno : m_iNextMsgNo) << (inorder ? "" : " NOT") << " in order");
|
||||
|
||||
// Calculate origin time (same for all blocks of the message).
|
||||
m_tsLastOriginTime = w_srctime ? time_point() + microseconds_from(w_srctime) : tnow;
|
||||
// Rewrite back the actual value, even if it stays the same, so that the calling facilities can reuse it.
|
||||
// May also be a subject to conversion error, thus the actual value is signalled back.
|
||||
w_srctime = count_microseconds(m_tsLastOriginTime.time_since_epoch());
|
||||
|
||||
// The sequence number passed to this function is the sequence number
|
||||
// that the very first packet from the packet series should get here.
|
||||
// If there's more than one packet, this function must increase it by itself
|
||||
// and then return the accordingly modified sequence number in the reference.
|
||||
|
||||
Block* s = m_pLastBlock;
|
||||
|
||||
if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied
|
||||
{
|
||||
HLOGC(bslog.Debug, log << "addBuffer: using internally managed msgno=" << m_iNextMsgNo);
|
||||
w_msgno = m_iNextMsgNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
HLOGC(bslog.Debug, log << "addBuffer: OVERWRITTEN by msgno supplied by caller: msgno=" << w_msgno);
|
||||
m_iNextMsgNo = w_msgno;
|
||||
}
|
||||
|
||||
for (int i = 0; i < iNumBlocks; ++i)
|
||||
{
|
||||
int pktlen = len - i * iPktLen;
|
||||
if (pktlen > iPktLen)
|
||||
pktlen = iPktLen;
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "addBuffer: %" << w_seqno << " #" << w_msgno << " offset=" << (i * iPktLen)
|
||||
<< " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData);
|
||||
memcpy((s->m_pcData), data + i * iPktLen, pktlen);
|
||||
s->m_iLength = pktlen;
|
||||
|
||||
s->m_iSeqNo = w_seqno;
|
||||
w_seqno = CSeqNo::incseq(w_seqno);
|
||||
|
||||
s->m_iMsgNoBitset = m_iNextMsgNo | inorder;
|
||||
if (i == 0)
|
||||
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST);
|
||||
if (i == iNumBlocks - 1)
|
||||
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST);
|
||||
// NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT.
|
||||
// if i == 0 == size-1, it results with PB_SOLO.
|
||||
// Packets assigned to one message can be:
|
||||
// [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message
|
||||
// [PB_FIRST] [PB_LAST] - 2 packets per message
|
||||
// [PB_SOLO] - 1 packet per message
|
||||
|
||||
s->m_iTTL = ttl;
|
||||
s->m_tsRexmitTime = time_point();
|
||||
s->m_tsOriginTime = m_tsLastOriginTime;
|
||||
|
||||
// Should never happen, as the call to increase() should ensure enough buffers.
|
||||
SRT_ASSERT(s->m_pNext);
|
||||
s = s->m_pNext;
|
||||
}
|
||||
m_pLastBlock = s;
|
||||
|
||||
m_iCount += iNumBlocks;
|
||||
m_iBytesCount += len;
|
||||
|
||||
m_rateEstimator.updateInputRate(m_tsLastOriginTime, iNumBlocks, len);
|
||||
updAvgBufSize(m_tsLastOriginTime);
|
||||
|
||||
// MSGNO_SEQ::mask has a form: 00000011111111...
|
||||
// At least it's known that it's from some index inside til the end (to bit 0).
|
||||
// If this value has been reached in a step of incrementation, it means that the
|
||||
// maximum value has been reached. Casting to int32_t to ensure the same sign
|
||||
// in comparison, although it's far from reaching the sign bit.
|
||||
|
||||
const int nextmsgno = ++MsgNo(m_iNextMsgNo);
|
||||
HLOGC(bslog.Debug, log << "CSndBuffer::addBuffer: updating msgno: #" << m_iNextMsgNo << " -> #" << nextmsgno);
|
||||
m_iNextMsgNo = nextmsgno;
|
||||
}
|
||||
|
||||
int CSndBuffer::addBufferFromFile(fstream& ifs, int len)
|
||||
{
|
||||
const int iPktLen = getMaxPacketLen();
|
||||
const int iNumBlocks = countNumPacketsRequired(len, iPktLen);
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "addBufferFromFile: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << iPktLen
|
||||
<< " buffers for " << len << " bytes");
|
||||
|
||||
// dynamically increase sender buffer
|
||||
while (iNumBlocks + m_iCount >= m_iSize)
|
||||
{
|
||||
HLOGC(bslog.Debug,
|
||||
log << "addBufferFromFile: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers...");
|
||||
increase();
|
||||
}
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len
|
||||
<< " bytes) to send, msgno=" << m_iNextMsgNo);
|
||||
|
||||
Block* s = m_pLastBlock;
|
||||
int total = 0;
|
||||
for (int i = 0; i < iNumBlocks; ++i)
|
||||
{
|
||||
if (ifs.bad() || ifs.fail() || ifs.eof())
|
||||
break;
|
||||
|
||||
int pktlen = len - i * iPktLen;
|
||||
if (pktlen > iPktLen)
|
||||
pktlen = iPktLen;
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen
|
||||
<< " TO BUFFER:" << (void*)s->m_pcData);
|
||||
ifs.read(s->m_pcData, pktlen);
|
||||
if ((pktlen = int(ifs.gcount())) <= 0)
|
||||
break;
|
||||
|
||||
// currently file transfer is only available in streaming mode, message is always in order, ttl = infinite
|
||||
s->m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask;
|
||||
if (i == 0)
|
||||
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST);
|
||||
if (i == iNumBlocks - 1)
|
||||
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST);
|
||||
// NOTE: PB_FIRST | PB_LAST == PB_SOLO.
|
||||
// none of PB_FIRST & PB_LAST == PB_SUBSEQUENT.
|
||||
|
||||
s->m_iLength = pktlen;
|
||||
s->m_iTTL = SRT_MSGTTL_INF;
|
||||
s = s->m_pNext;
|
||||
|
||||
total += pktlen;
|
||||
}
|
||||
m_pLastBlock = s;
|
||||
|
||||
enterCS(m_BufLock);
|
||||
m_iCount += iNumBlocks;
|
||||
m_iBytesCount += total;
|
||||
|
||||
leaveCS(m_BufLock);
|
||||
|
||||
m_iNextMsgNo++;
|
||||
if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask))
|
||||
m_iNextMsgNo = 1;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc)
|
||||
{
|
||||
int readlen = 0;
|
||||
w_seqnoinc = 0;
|
||||
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
while (m_pCurrBlock != m_pLastBlock)
|
||||
{
|
||||
// Make the packet REFLECT the data stored in the buffer.
|
||||
w_packet.m_pcData = m_pCurrBlock->m_pcData;
|
||||
readlen = m_pCurrBlock->m_iLength;
|
||||
w_packet.setLength(readlen, m_iBlockLen);
|
||||
w_packet.m_iSeqNo = m_pCurrBlock->m_iSeqNo;
|
||||
|
||||
// 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0).
|
||||
// 2. The readData() is called to get the original (unique) payload not ever sent yet.
|
||||
// The payload must be encrypted for the first time if the encryption
|
||||
// is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet
|
||||
// header must be set and remembered accordingly (see EncryptionKeySpec).
|
||||
// 3. The next time this packet is read (only for retransmission), the payload is already
|
||||
// encrypted, and the proper flag value is already stored.
|
||||
|
||||
// TODO: Alternatively, encryption could happen before the packet is submitted to the buffer
|
||||
// (before the addBuffer() call), and corresponding flags could be set accordingly.
|
||||
// This may also put an encryption burden on the application thread, rather than the sending thread,
|
||||
// which could be more efficient. Note that packet sequence number must be properly set in that case,
|
||||
// as it is used as a counter for the AES encryption.
|
||||
if (kflgs == -1)
|
||||
{
|
||||
HLOGC(bslog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING.");
|
||||
readlen = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs);
|
||||
}
|
||||
|
||||
Block* p = m_pCurrBlock;
|
||||
w_packet.m_iMsgNo = m_pCurrBlock->m_iMsgNoBitset;
|
||||
w_srctime = m_pCurrBlock->m_tsOriginTime;
|
||||
m_pCurrBlock = m_pCurrBlock->m_pNext;
|
||||
|
||||
if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - w_srctime) > p->m_iTTL))
|
||||
{
|
||||
LOGC(bslog.Warn, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " with TTL=" << p->m_iTTL);
|
||||
// Skip this packet due to TTL expiry.
|
||||
readlen = 0;
|
||||
++w_seqnoinc;
|
||||
continue;
|
||||
}
|
||||
|
||||
HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send");
|
||||
break;
|
||||
}
|
||||
|
||||
return readlen;
|
||||
}
|
||||
|
||||
CSndBuffer::time_point CSndBuffer::peekNextOriginal() const
|
||||
{
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
if (m_pCurrBlock == m_pLastBlock)
|
||||
return time_point();
|
||||
|
||||
return m_pCurrBlock->m_tsOriginTime;
|
||||
}
|
||||
|
||||
int32_t CSndBuffer::getMsgNoAt(const int offset)
|
||||
{
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
|
||||
Block* p = m_pFirstBlock;
|
||||
|
||||
if (p)
|
||||
{
|
||||
HLOGC(bslog.Debug,
|
||||
log << "CSndBuffer::getMsgNoAt: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #"
|
||||
<< p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength));
|
||||
}
|
||||
|
||||
if (offset >= m_iCount)
|
||||
{
|
||||
// Prevent accessing the last "marker" block
|
||||
LOGC(bslog.Error,
|
||||
log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, max offset=" << m_iCount);
|
||||
return SRT_MSGNO_CONTROL;
|
||||
}
|
||||
|
||||
// XXX Suboptimal procedure to keep the blocks identifiable
|
||||
// by sequence number. Consider using some circular buffer.
|
||||
int i;
|
||||
Block* ee SRT_ATR_UNUSED = 0;
|
||||
for (i = 0; i < offset && p; ++i)
|
||||
{
|
||||
ee = p;
|
||||
p = p->m_pNext;
|
||||
}
|
||||
|
||||
if (!p)
|
||||
{
|
||||
LOGC(bslog.Error,
|
||||
log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, stopped at " << i << " with #"
|
||||
<< (ee ? ee->getMsgSeq() : SRT_MSGNO_NONE));
|
||||
return SRT_MSGNO_CONTROL;
|
||||
}
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "CSndBuffer::getMsgNoAt: offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo
|
||||
<< " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength));
|
||||
|
||||
return p->getMsgSeq();
|
||||
}
|
||||
|
||||
int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, int& w_msglen)
|
||||
{
|
||||
int32_t& msgno_bitset = w_packet.m_iMsgNo;
|
||||
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
|
||||
Block* p = m_pFirstBlock;
|
||||
|
||||
// XXX Suboptimal procedure to keep the blocks identifiable
|
||||
// by sequence number. Consider using some circular buffer.
|
||||
for (int i = 0; i < offset && p != m_pLastBlock; ++i)
|
||||
{
|
||||
p = p->m_pNext;
|
||||
}
|
||||
if (p == m_pLastBlock)
|
||||
{
|
||||
LOGC(qslog.Error, log << "CSndBuffer::readData: offset " << offset << " too large!");
|
||||
return 0;
|
||||
}
|
||||
#if ENABLE_HEAVY_LOGGING
|
||||
const int32_t first_seq = p->m_iSeqNo;
|
||||
int32_t last_seq = p->m_iSeqNo;
|
||||
#endif
|
||||
|
||||
// Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale.
|
||||
|
||||
// If so, then inform the caller that it should first take care of the whole
|
||||
// message (all blocks with that message id). Shift the m_pCurrBlock pointer
|
||||
// to the position past the last of them. Then return -1 and set the
|
||||
// msgno_bitset return reference to the message id that should be dropped as
|
||||
// a whole.
|
||||
|
||||
// After taking care of that, the caller should immediately call this function again,
|
||||
// this time possibly in order to find the real data to be sent.
|
||||
|
||||
// if found block is stale
|
||||
// (This is for messages that have declared TTL - messages that fail to be sent
|
||||
// before the TTL defined time comes, will be dropped).
|
||||
|
||||
if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL))
|
||||
{
|
||||
int32_t msgno = p->getMsgSeq();
|
||||
w_msglen = 1;
|
||||
p = p->m_pNext;
|
||||
bool move = false;
|
||||
while (p != m_pLastBlock && msgno == p->getMsgSeq())
|
||||
{
|
||||
#if ENABLE_HEAVY_LOGGING
|
||||
last_seq = p->m_iSeqNo;
|
||||
#endif
|
||||
if (p == m_pCurrBlock)
|
||||
move = true;
|
||||
p = p->m_pNext;
|
||||
if (move)
|
||||
m_pCurrBlock = p;
|
||||
w_msglen++;
|
||||
}
|
||||
|
||||
HLOGC(qslog.Debug,
|
||||
log << "CSndBuffer::readData: due to TTL exceeded, SEQ " << first_seq << " - " << last_seq << ", "
|
||||
<< w_msglen << " packets to drop, msgno=" << msgno);
|
||||
|
||||
// If readData returns -1, then msgno_bitset is understood as a Message ID to drop.
|
||||
// This means that in this case it should be written by the message sequence value only
|
||||
// (not the whole 4-byte bitset written at PH_MSGNO).
|
||||
msgno_bitset = msgno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
w_packet.m_pcData = p->m_pcData;
|
||||
const int readlen = p->m_iLength;
|
||||
w_packet.setLength(readlen, m_iBlockLen);
|
||||
|
||||
// XXX Here the value predicted to be applied to PH_MSGNO field is extracted.
|
||||
// As this function is predicted to extract the data to send as a rexmited packet,
|
||||
// the packet must be in the form ready to send - so, in case of encryption,
|
||||
// encrypted, and with all ENC flags already set. So, the first call to send
|
||||
// the packet originally (the other overload of this function) must set these
|
||||
// flags.
|
||||
w_packet.m_iMsgNo = p->m_iMsgNoBitset;
|
||||
w_srctime = p->m_tsOriginTime;
|
||||
|
||||
// This function is called when packet retransmission is triggered.
|
||||
// Therefore we are setting the rexmit time.
|
||||
p->m_tsRexmitTime = steady_clock::now();
|
||||
|
||||
HLOGC(qslog.Debug,
|
||||
log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.m_iSeqNo
|
||||
<< " size=" << readlen << " to send [REXMIT]");
|
||||
|
||||
return readlen;
|
||||
}
|
||||
|
||||
sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset)
|
||||
{
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
const Block* p = m_pFirstBlock;
|
||||
|
||||
// XXX Suboptimal procedure to keep the blocks identifiable
|
||||
// by sequence number. Consider using some circular buffer.
|
||||
for (int i = 0; i < offset; ++i)
|
||||
{
|
||||
SRT_ASSERT(p);
|
||||
p = p->m_pNext;
|
||||
}
|
||||
|
||||
SRT_ASSERT(p);
|
||||
return p->m_tsRexmitTime;
|
||||
}
|
||||
|
||||
void CSndBuffer::ackData(int offset)
|
||||
{
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
|
||||
bool move = false;
|
||||
for (int i = 0; i < offset; ++i)
|
||||
{
|
||||
m_iBytesCount -= m_pFirstBlock->m_iLength;
|
||||
if (m_pFirstBlock == m_pCurrBlock)
|
||||
move = true;
|
||||
m_pFirstBlock = m_pFirstBlock->m_pNext;
|
||||
}
|
||||
if (move)
|
||||
m_pCurrBlock = m_pFirstBlock;
|
||||
|
||||
m_iCount -= offset;
|
||||
|
||||
updAvgBufSize(steady_clock::now());
|
||||
}
|
||||
|
||||
int CSndBuffer::getCurrBufSize() const
|
||||
{
|
||||
return m_iCount;
|
||||
}
|
||||
|
||||
int CSndBuffer::getMaxPacketLen() const
|
||||
{
|
||||
return m_iBlockLen - m_iAuthTagSize;
|
||||
}
|
||||
|
||||
int CSndBuffer::countNumPacketsRequired(int iPldLen) const
|
||||
{
|
||||
const int iPktLen = getMaxPacketLen();
|
||||
return countNumPacketsRequired(iPldLen, iPktLen);
|
||||
}
|
||||
|
||||
int CSndBuffer::countNumPacketsRequired(int iPldLen, int iPktLen) const
|
||||
{
|
||||
return (iPldLen + iPktLen - 1) / iPktLen;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int round_val(double val)
|
||||
{
|
||||
return static_cast<int>(round(val));
|
||||
}
|
||||
}
|
||||
|
||||
int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp)
|
||||
{
|
||||
ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */
|
||||
|
||||
/* update stats in case there was no add/ack activity lately */
|
||||
updAvgBufSize(steady_clock::now());
|
||||
|
||||
// Average number of packets and timespan could be small,
|
||||
// so rounding is beneficial, while for the number of
|
||||
// bytes in the buffer is a higher value, so rounding can be omitted,
|
||||
// but probably better to round all three values.
|
||||
w_bytes = round_val(m_mavg.bytes());
|
||||
w_tsp = round_val(m_mavg.timespan_ms());
|
||||
return round_val(m_mavg.pkts());
|
||||
}
|
||||
|
||||
void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now)
|
||||
{
|
||||
if (!m_mavg.isTimeToUpdate(now))
|
||||
return;
|
||||
|
||||
int bytes = 0;
|
||||
int timespan_ms = 0;
|
||||
const int pkts = getCurrBufSize((bytes), (timespan_ms));
|
||||
m_mavg.update(now, pkts, bytes, timespan_ms);
|
||||
}
|
||||
|
||||
int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const
|
||||
{
|
||||
w_bytes = m_iBytesCount;
|
||||
/*
|
||||
* Timespan can be less then 1000 us (1 ms) if few packets.
|
||||
* Also, if there is only one pkt in buffer, the time difference will be 0.
|
||||
* Therefore, always add 1 ms if not empty.
|
||||
*/
|
||||
w_timespan = 0 < m_iCount ? (int) count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1 : 0;
|
||||
|
||||
return m_iCount;
|
||||
}
|
||||
|
||||
CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const
|
||||
{
|
||||
ScopedLock lck(m_BufLock);
|
||||
SRT_ASSERT(m_pFirstBlock);
|
||||
if (m_iCount == 0)
|
||||
return duration(0);
|
||||
|
||||
return tnow - m_pFirstBlock->m_tsOriginTime;
|
||||
}
|
||||
|
||||
int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time)
|
||||
{
|
||||
int dpkts = 0;
|
||||
int dbytes = 0;
|
||||
bool move = false;
|
||||
int32_t msgno = 0;
|
||||
|
||||
ScopedLock bufferguard(m_BufLock);
|
||||
for (int i = 0; i < m_iCount && m_pFirstBlock->m_tsOriginTime < too_late_time; ++i)
|
||||
{
|
||||
dpkts++;
|
||||
dbytes += m_pFirstBlock->m_iLength;
|
||||
msgno = m_pFirstBlock->getMsgSeq();
|
||||
|
||||
if (m_pFirstBlock == m_pCurrBlock)
|
||||
move = true;
|
||||
m_pFirstBlock = m_pFirstBlock->m_pNext;
|
||||
}
|
||||
|
||||
if (move)
|
||||
{
|
||||
m_pCurrBlock = m_pFirstBlock;
|
||||
}
|
||||
m_iCount -= dpkts;
|
||||
|
||||
m_iBytesCount -= dbytes;
|
||||
w_bytes = dbytes;
|
||||
|
||||
// We report the increased number towards the last ever seen
|
||||
// by the loop, as this last one is the last received. So remained
|
||||
// (even if "should remain") is the first after the last removed one.
|
||||
w_first_msgno = ++MsgNo(msgno);
|
||||
|
||||
updAvgBufSize(steady_clock::now());
|
||||
|
||||
return (dpkts);
|
||||
}
|
||||
|
||||
void CSndBuffer::increase()
|
||||
{
|
||||
int unitsize = m_pBuffer->m_iSize;
|
||||
|
||||
// new physical buffer
|
||||
Buffer* nbuf = NULL;
|
||||
try
|
||||
{
|
||||
nbuf = new Buffer;
|
||||
nbuf->m_pcData = new char[unitsize * m_iBlockLen];
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
delete nbuf;
|
||||
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
||||
}
|
||||
nbuf->m_iSize = unitsize;
|
||||
nbuf->m_pNext = NULL;
|
||||
|
||||
// insert the buffer at the end of the buffer list
|
||||
Buffer* p = m_pBuffer;
|
||||
while (p->m_pNext != NULL)
|
||||
p = p->m_pNext;
|
||||
p->m_pNext = nbuf;
|
||||
|
||||
// new packet blocks
|
||||
Block* nblk = NULL;
|
||||
try
|
||||
{
|
||||
nblk = new Block;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
delete nblk;
|
||||
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
||||
}
|
||||
Block* pb = nblk;
|
||||
for (int i = 1; i < unitsize; ++i)
|
||||
{
|
||||
pb->m_pNext = new Block;
|
||||
pb = pb->m_pNext;
|
||||
}
|
||||
|
||||
// insert the new blocks onto the existing one
|
||||
pb->m_pNext = m_pLastBlock->m_pNext;
|
||||
m_pLastBlock->m_pNext = nblk;
|
||||
|
||||
pb = nblk;
|
||||
char* pc = nbuf->m_pcData;
|
||||
for (int i = 0; i < unitsize; ++i)
|
||||
{
|
||||
pb->m_pcData = pc;
|
||||
pb = pb->m_pNext;
|
||||
pc += m_iBlockLen;
|
||||
}
|
||||
|
||||
m_iSize += unitsize;
|
||||
|
||||
HLOGC(bslog.Debug,
|
||||
log << "CSndBuffer: BUFFER FULL - adding " << (unitsize * m_iBlockLen) << " bytes spread to " << unitsize
|
||||
<< " blocks"
|
||||
<< " (total size: " << m_iSize << " bytes)");
|
||||
}
|
||||
|
||||
} // namespace srt
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* SRT - Secure, Reliable, Transport
|
||||
* Copyright (c) 2018 Haivision Systems Inc.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the
|
||||
above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the University of Illinois
|
||||
nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
/*****************************************************************************
|
||||
written by
|
||||
Yunhong Gu, last updated 05/05/2009
|
||||
modified by
|
||||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef INC_SRT_BUFFER_SND_H
|
||||
#define INC_SRT_BUFFER_SND_H
|
||||
|
||||
#include "srt.h"
|
||||
#include "packet.h"
|
||||
#include "buffer_tools.h"
|
||||
|
||||
// The notation used for "circular numbers" in comments:
|
||||
// The "cicrular numbers" are numbers that when increased up to the
|
||||
// maximum become zero, and similarly, when the zero value is decreased,
|
||||
// it turns into the maximum value minus one. This wrapping works the
|
||||
// same for adding and subtracting. Circular numbers cannot be multiplied.
|
||||
|
||||
// Operations done on these numbers are marked with additional % character:
|
||||
// a %> b : a is later than b
|
||||
// a ++% (++%a) : shift a by 1 forward
|
||||
// a +% b : shift a by b
|
||||
// a == b : equality is same as for just numbers
|
||||
|
||||
namespace srt {
|
||||
|
||||
class CSndBuffer
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
typedef sync::steady_clock::duration duration;
|
||||
|
||||
public:
|
||||
// XXX There's currently no way to access the socket ID set for
|
||||
// whatever the buffer is currently working for. Required to find
|
||||
// some way to do this, possibly by having a "reverse pointer".
|
||||
// Currently just "unimplemented".
|
||||
std::string CONID() const { return ""; }
|
||||
|
||||
/// @brief CSndBuffer constructor.
|
||||
/// @param size initial number of blocks (each block to store one packet payload).
|
||||
/// @param maxpld maximum packet payload (including auth tag).
|
||||
/// @param authtag auth tag length in bytes (16 for GCM, 0 otherwise).
|
||||
CSndBuffer(int size = 32, int maxpld = 1500, int authtag = 0);
|
||||
~CSndBuffer();
|
||||
|
||||
public:
|
||||
/// Insert a user buffer into the sending list.
|
||||
/// For @a w_mctrl the following fields are used:
|
||||
/// INPUT:
|
||||
/// - msgttl: timeout for retransmitting the message, if lost
|
||||
/// - inorder: request to deliver the message in order of sending
|
||||
/// - srctime: local time as a base for packet's timestamp (0 if unused)
|
||||
/// - pktseq: sequence number to be stamped on the packet (-1 if unused)
|
||||
/// - msgno: message number to be stamped on the packet (-1 if unused)
|
||||
/// OUTPUT:
|
||||
/// - srctime: local time stamped on the packet (same as input, if input wasn't 0)
|
||||
/// - pktseq: sequence number to be stamped on the next packet
|
||||
/// - msgno: message number stamped on the packet
|
||||
/// @param [in] data pointer to the user data block.
|
||||
/// @param [in] len size of the block.
|
||||
/// @param [inout] w_mctrl Message control data
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl);
|
||||
|
||||
/// Read a block of data from file and insert it into the sending list.
|
||||
/// @param [in] ifs input file stream.
|
||||
/// @param [in] len size of the block.
|
||||
/// @return actual size of data added from the file.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int addBufferFromFile(std::fstream& ifs, int len);
|
||||
|
||||
/// Find data position to pack a DATA packet from the furthest reading point.
|
||||
/// @param [out] packet the packet to read.
|
||||
/// @param [out] origintime origin time stamp of the message
|
||||
/// @param [in] kflags Odd|Even crypto key flag
|
||||
/// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented.
|
||||
/// @return Actual length of data read.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc);
|
||||
|
||||
/// Peek an information on the next original data packet to send.
|
||||
/// @return origin time stamp of the next packet; epoch start time otherwise.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
time_point peekNextOriginal() const;
|
||||
|
||||
/// Find data position to pack a DATA packet for a retransmission.
|
||||
/// @param [in] offset offset from the last ACK point (backward sequence number difference)
|
||||
/// @param [out] packet the packet to read.
|
||||
/// @param [out] origintime origin time stamp of the message
|
||||
/// @param [out] msglen length of the message
|
||||
/// @return Actual length of data read (return 0 if offset too large, -1 if TTL exceeded).
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int readData(const int offset, CPacket& w_packet, time_point& w_origintime, int& w_msglen);
|
||||
|
||||
/// Get the time of the last retransmission (if any) of the DATA packet.
|
||||
/// @param [in] offset offset from the last ACK point (backward sequence number difference)
|
||||
///
|
||||
/// @return Last time of the last retransmission event for the corresponding DATA packet.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
time_point getPacketRexmitTime(const int offset);
|
||||
|
||||
/// Update the ACK point and may release/unmap/return the user data according to the flag.
|
||||
/// @param [in] offset number of packets acknowledged.
|
||||
int32_t getMsgNoAt(const int offset);
|
||||
|
||||
void ackData(int offset);
|
||||
|
||||
/// Read size of data still in the sending list.
|
||||
/// @return Current size of the data in the sending list.
|
||||
int getCurrBufSize() const;
|
||||
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time);
|
||||
|
||||
void updAvgBufSize(const time_point& time);
|
||||
int getAvgBufSize(int& bytes, int& timespan);
|
||||
int getCurrBufSize(int& bytes, int& timespan) const;
|
||||
|
||||
|
||||
/// Het maximum payload length per packet.
|
||||
int getMaxPacketLen() const;
|
||||
|
||||
/// @brief Count the number of required packets to store the payload (message).
|
||||
/// @param iPldLen the length of the payload to check.
|
||||
/// @return the number of required data packets.
|
||||
int countNumPacketsRequired(int iPldLen) const;
|
||||
|
||||
/// @brief Count the number of required packets to store the payload (message).
|
||||
/// @param iPldLen the length of the payload to check.
|
||||
/// @param iMaxPktLen the maximum payload length of the packet (the value returned from getMaxPacketLen()).
|
||||
/// @return the number of required data packets.
|
||||
int countNumPacketsRequired(int iPldLen, int iMaxPktLen) const;
|
||||
|
||||
/// @brief Get the buffering delay of the oldest message in the buffer.
|
||||
/// @return the delay value.
|
||||
SRT_ATTR_EXCLUDES(m_BufLock)
|
||||
duration getBufferingDelay(const time_point& tnow) const;
|
||||
|
||||
uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); }
|
||||
|
||||
/// Retrieve input bitrate in bytes per second
|
||||
int getInputRate() const { return m_rateEstimator.getInputRate(); }
|
||||
|
||||
void resetInputRateSmpPeriod(bool disable = false) { m_rateEstimator.resetInputRateSmpPeriod(disable); }
|
||||
|
||||
const CRateEstimator& getRateEstimator() const { return m_rateEstimator; }
|
||||
|
||||
void setRateEstimator(const CRateEstimator& other) { m_rateEstimator = other; }
|
||||
|
||||
private:
|
||||
void increase();
|
||||
|
||||
private:
|
||||
mutable sync::Mutex m_BufLock; // used to synchronize buffer operation
|
||||
|
||||
struct Block
|
||||
{
|
||||
char* m_pcData; // pointer to the data block
|
||||
int m_iLength; // payload length of the block (excluding auth tag).
|
||||
|
||||
int32_t m_iMsgNoBitset; // message number
|
||||
int32_t m_iSeqNo; // sequence number for scheduling
|
||||
time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending.
|
||||
time_point m_tsRexmitTime; // packet retransmission time
|
||||
int m_iTTL; // time to live (milliseconds)
|
||||
|
||||
Block* m_pNext; // next block
|
||||
|
||||
int32_t getMsgSeq()
|
||||
{
|
||||
// NOTE: this extracts message ID with regard to REXMIT flag.
|
||||
// This is valid only for message ID that IS GENERATED in this instance,
|
||||
// not provided by the peer. This can be otherwise sent to the peer - it doesn't matter
|
||||
// for the peer that it uses LESS bits to represent the message.
|
||||
return m_iMsgNoBitset & MSGNO_SEQ::mask;
|
||||
}
|
||||
|
||||
} * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock;
|
||||
|
||||
// m_pBlock: The head pointer
|
||||
// m_pFirstBlock: The first block
|
||||
// m_pCurrBlock: The current block
|
||||
// m_pLastBlock: The last block (if first == last, buffer is empty)
|
||||
|
||||
struct Buffer
|
||||
{
|
||||
char* m_pcData; // buffer
|
||||
int m_iSize; // size
|
||||
Buffer* m_pNext; // next buffer
|
||||
} * m_pBuffer; // physical buffer
|
||||
|
||||
int32_t m_iNextMsgNo; // next message number
|
||||
|
||||
int m_iSize; // buffer size (number of packets)
|
||||
const int m_iBlockLen; // maximum length of a block holding packet payload and AUTH tag (excluding packet header).
|
||||
const int m_iAuthTagSize; // Authentication tag size (if GCM is enabled).
|
||||
int m_iCount; // number of used blocks
|
||||
|
||||
int m_iBytesCount; // number of payload bytes in queue
|
||||
time_point m_tsLastOriginTime;
|
||||
|
||||
AvgBufSize m_mavg;
|
||||
CRateEstimator m_rateEstimator;
|
||||
|
||||
private:
|
||||
CSndBuffer(const CSndBuffer&);
|
||||
CSndBuffer& operator=(const CSndBuffer&);
|
||||
};
|
||||
|
||||
} // namespace srt
|
||||
|
||||
#endif
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* SRT - Secure, Reliable, Transport
|
||||
* Copyright (c) 2018 Haivision Systems Inc.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the
|
||||
above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the University of Illinois
|
||||
nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
/*****************************************************************************
|
||||
written by
|
||||
Yunhong Gu, last updated 03/12/2011
|
||||
modified by
|
||||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "platform_sys.h"
|
||||
#include "buffer_tools.h"
|
||||
#include "packet.h"
|
||||
#include "logger_defs.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace srt {
|
||||
|
||||
using namespace std;
|
||||
using namespace srt_logging;
|
||||
using namespace sync;
|
||||
|
||||
// You can change this value at build config by using "ENFORCE" options.
|
||||
#if !defined(SRT_MAVG_SAMPLING_RATE)
|
||||
#define SRT_MAVG_SAMPLING_RATE 40
|
||||
#endif
|
||||
|
||||
bool AvgBufSize::isTimeToUpdate(const time_point& now) const
|
||||
{
|
||||
const int usMAvgBasePeriod = 1000000; // 1s in microseconds
|
||||
const int us2ms = 1000;
|
||||
const int msMAvgPeriod = (usMAvgBasePeriod / SRT_MAVG_SAMPLING_RATE) / us2ms;
|
||||
const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling
|
||||
return (elapsed_ms >= msMAvgPeriod);
|
||||
}
|
||||
|
||||
void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes, int timespan_ms)
|
||||
{
|
||||
const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling
|
||||
m_tsLastSamplingTime = now;
|
||||
const uint64_t one_second_in_ms = 1000;
|
||||
if (elapsed_ms > one_second_in_ms)
|
||||
{
|
||||
// No sampling in last 1 sec, initialize average
|
||||
m_dCountMAvg = pkts;
|
||||
m_dBytesCountMAvg = bytes;
|
||||
m_dTimespanMAvg = timespan_ms;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// weight last average value between -1 sec and last sampling time (LST)
|
||||
// and new value between last sampling time and now
|
||||
// |elapsed_ms|
|
||||
// +----------------------------------+-------+
|
||||
// -1 LST 0(now)
|
||||
//
|
||||
m_dCountMAvg = avg_iir_w<1000, double>(m_dCountMAvg, pkts, elapsed_ms);
|
||||
m_dBytesCountMAvg = avg_iir_w<1000, double>(m_dBytesCountMAvg, bytes, elapsed_ms);
|
||||
m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms);
|
||||
}
|
||||
|
||||
CRateEstimator::CRateEstimator()
|
||||
: m_iInRatePktsCount(0)
|
||||
, m_iInRateBytesCount(0)
|
||||
, m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start)
|
||||
, m_iInRateBps(INPUTRATE_INITIAL_BYTESPS)
|
||||
{}
|
||||
|
||||
void CRateEstimator::setInputRateSmpPeriod(int period)
|
||||
{
|
||||
m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation
|
||||
}
|
||||
|
||||
void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes)
|
||||
{
|
||||
// no input rate calculation
|
||||
if (m_InRatePeriod == 0)
|
||||
return;
|
||||
|
||||
if (is_zero(m_tsInRateStartTime))
|
||||
{
|
||||
m_tsInRateStartTime = time;
|
||||
return;
|
||||
}
|
||||
else if (time < m_tsInRateStartTime)
|
||||
{
|
||||
// Old packets are being submitted for estimation, e.g. during the backup link activation.
|
||||
return;
|
||||
}
|
||||
|
||||
m_iInRatePktsCount += pkts;
|
||||
m_iInRateBytesCount += bytes;
|
||||
|
||||
// Trigger early update in fast start mode
|
||||
const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS);
|
||||
|
||||
const uint64_t period_us = count_microseconds(time - m_tsInRateStartTime);
|
||||
if (!early_update && period_us <= m_InRatePeriod)
|
||||
return;
|
||||
|
||||
// Required Byte/sec rate (payload + headers)
|
||||
m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE);
|
||||
m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us);
|
||||
HLOGC(bslog.Debug,
|
||||
log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount
|
||||
<< " rate=" << (m_iInRateBps * 8) / 1000 << "kbps interval=" << period_us);
|
||||
m_iInRatePktsCount = 0;
|
||||
m_iInRateBytesCount = 0;
|
||||
m_tsInRateStartTime = time;
|
||||
|
||||
setInputRateSmpPeriod(INPUTRATE_RUNNING_US);
|
||||
}
|
||||
|
||||
CSndRateEstimator::CSndRateEstimator(const time_point& tsNow)
|
||||
: m_tsFirstSampleTime(tsNow)
|
||||
, m_iFirstSampleIdx(0)
|
||||
, m_iCurSampleIdx(0)
|
||||
, m_iRateBps(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CSndRateEstimator::addSample(const time_point& ts, int pkts, size_t bytes)
|
||||
{
|
||||
const int iSampleDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS;
|
||||
const int delta = NUM_PERIODS - iSampleDeltaIdx;
|
||||
|
||||
// TODO: -delta <= NUM_PERIODS, then just reset the state on the estimator.
|
||||
|
||||
if (iSampleDeltaIdx >= 2 * NUM_PERIODS)
|
||||
{
|
||||
// Just reset the estimator and start like if new.
|
||||
for (int i = 0; i < NUM_PERIODS; ++i)
|
||||
{
|
||||
const int idx = incSampleIdx(m_iFirstSampleIdx, i);
|
||||
m_Samples[idx].reset();
|
||||
|
||||
if (idx == m_iCurSampleIdx)
|
||||
break;
|
||||
}
|
||||
|
||||
m_iFirstSampleIdx = 0;
|
||||
m_iCurSampleIdx = 0;
|
||||
m_iRateBps = 0;
|
||||
m_tsFirstSampleTime += milliseconds_from(iSampleDeltaIdx * SAMPLE_DURATION_MS);
|
||||
}
|
||||
else if (iSampleDeltaIdx > NUM_PERIODS)
|
||||
{
|
||||
// In run-time a constant flow of samples is expected. Once all periods are filled (after 1 second of sampling),
|
||||
// the iSampleDeltaIdx should be either (NUM_PERIODS - 1),
|
||||
// or NUM_PERIODS. In the later case it means the start of a new sampling period.
|
||||
int d = delta;
|
||||
while (d < 0)
|
||||
{
|
||||
m_Samples[m_iFirstSampleIdx].reset();
|
||||
m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx);
|
||||
m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS);
|
||||
m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx);
|
||||
++d;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the new sample period has started.
|
||||
const int iNewDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS;
|
||||
if (incSampleIdx(m_iFirstSampleIdx, iNewDeltaIdx) != m_iCurSampleIdx)
|
||||
{
|
||||
// Now there should be some periods (at most last NUM_PERIODS) ready to be summed,
|
||||
// rate estimation updated, after which all the new entry should be added.
|
||||
Sample sum;
|
||||
int iNumPeriods = 0;
|
||||
bool bMetNonEmpty = false;
|
||||
for (int i = 0; i < NUM_PERIODS; ++i)
|
||||
{
|
||||
const int idx = incSampleIdx(m_iFirstSampleIdx, i);
|
||||
const Sample& s = m_Samples[idx];
|
||||
sum += s;
|
||||
if (bMetNonEmpty || !s.empty())
|
||||
{
|
||||
++iNumPeriods;
|
||||
bMetNonEmpty = true;
|
||||
}
|
||||
|
||||
if (idx == m_iCurSampleIdx)
|
||||
break;
|
||||
}
|
||||
|
||||
if (iNumPeriods == 0)
|
||||
{
|
||||
m_iRateBps = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iRateBps = sum.m_iBytesCount * 1000 / (iNumPeriods * SAMPLE_DURATION_MS);
|
||||
}
|
||||
|
||||
HLOGC(bslog.Note,
|
||||
log << "CSndRateEstimator: new rate estimation :" << (m_iRateBps * 8) / 1000 << " kbps. Based on "
|
||||
<< iNumPeriods << " periods, " << sum.m_iPktsCount << " packets, " << sum.m_iBytesCount << " bytes.");
|
||||
|
||||
// Shift one sampling period to start collecting the new one.
|
||||
m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx);
|
||||
m_Samples[m_iCurSampleIdx].reset();
|
||||
|
||||
// If all NUM_SAMPLES are recorded, the first position has to be shifted as well.
|
||||
if (delta <= 0)
|
||||
{
|
||||
m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx);
|
||||
m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS);
|
||||
}
|
||||
}
|
||||
|
||||
m_Samples[m_iCurSampleIdx].m_iBytesCount += bytes;
|
||||
m_Samples[m_iCurSampleIdx].m_iPktsCount += pkts;
|
||||
}
|
||||
|
||||
int CSndRateEstimator::getCurrentRate() const
|
||||
{
|
||||
SRT_ASSERT(m_iCurSampleIdx >= 0 && m_iCurSampleIdx < NUM_PERIODS);
|
||||
return (int) avg_iir<16, unsigned long long>(m_iRateBps, m_Samples[m_iCurSampleIdx].m_iBytesCount * 1000 / SAMPLE_DURATION_MS);
|
||||
}
|
||||
|
||||
int CSndRateEstimator::incSampleIdx(int val, int inc) const
|
||||
{
|
||||
SRT_ASSERT(inc >= 0 && inc <= NUM_PERIODS);
|
||||
val += inc;
|
||||
while (val >= NUM_PERIODS)
|
||||
val -= NUM_PERIODS;
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* SRT - Secure, Reliable, Transport
|
||||
* Copyright (c) 2018 Haivision Systems Inc.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the
|
||||
above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the University of Illinois
|
||||
nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
/*****************************************************************************
|
||||
written by
|
||||
Yunhong Gu, last updated 05/05/2009
|
||||
modified by
|
||||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef INC_SRT_BUFFER_TOOLS_H
|
||||
#define INC_SRT_BUFFER_TOOLS_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace srt
|
||||
{
|
||||
|
||||
/// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND)
|
||||
class AvgBufSize
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
|
||||
public:
|
||||
AvgBufSize()
|
||||
: m_dBytesCountMAvg(0.0)
|
||||
, m_dCountMAvg(0.0)
|
||||
, m_dTimespanMAvg(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
bool isTimeToUpdate(const time_point& now) const;
|
||||
void update(const time_point& now, int pkts, int bytes, int timespan_ms);
|
||||
|
||||
public:
|
||||
inline double pkts() const { return m_dCountMAvg; }
|
||||
inline double timespan_ms() const { return m_dTimespanMAvg; }
|
||||
inline double bytes() const { return m_dBytesCountMAvg; }
|
||||
|
||||
private:
|
||||
time_point m_tsLastSamplingTime;
|
||||
double m_dBytesCountMAvg;
|
||||
double m_dCountMAvg;
|
||||
double m_dTimespanMAvg;
|
||||
};
|
||||
|
||||
/// The class to estimate source bitrate based on samples submitted to the buffer.
|
||||
/// Is currently only used by the CSndBuffer.
|
||||
class CRateEstimator
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
typedef sync::steady_clock::duration duration;
|
||||
public:
|
||||
CRateEstimator();
|
||||
|
||||
public:
|
||||
uint64_t getInRatePeriod() const { return m_InRatePeriod; }
|
||||
|
||||
/// Retrieve input bitrate in bytes per second
|
||||
int getInputRate() const { return m_iInRateBps; }
|
||||
|
||||
void setInputRateSmpPeriod(int period);
|
||||
|
||||
/// Update input rate calculation.
|
||||
/// @param [in] time current time
|
||||
/// @param [in] pkts number of packets newly added to the buffer
|
||||
/// @param [in] bytes number of payload bytes in those newly added packets
|
||||
void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0);
|
||||
|
||||
void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); }
|
||||
|
||||
private: // Constants
|
||||
static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms
|
||||
static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms
|
||||
static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload
|
||||
static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE;
|
||||
|
||||
private:
|
||||
int m_iInRatePktsCount; // number of payload packets added since InRateStartTime.
|
||||
int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime.
|
||||
time_point m_tsInRateStartTime;
|
||||
uint64_t m_InRatePeriod; // usec
|
||||
int m_iInRateBps; // Input Rate in Bytes/sec
|
||||
};
|
||||
|
||||
|
||||
class CSndRateEstimator
|
||||
{
|
||||
typedef sync::steady_clock::time_point time_point;
|
||||
|
||||
public:
|
||||
CSndRateEstimator(const time_point& tsNow);
|
||||
|
||||
/// Add sample.
|
||||
/// @param [in] time sample (sending) time.
|
||||
/// @param [in] pkts number of packets in the sample.
|
||||
/// @param [in] bytes number of payload bytes in the sample.
|
||||
void addSample(const time_point& time, int pkts = 0, size_t bytes = 0);
|
||||
|
||||
/// Retrieve estimated bitrate in bytes per second
|
||||
int getRate() const { return m_iRateBps; }
|
||||
|
||||
/// Retrieve estimated bitrate in bytes per second inluding the current sampling interval.
|
||||
int getCurrentRate() const;
|
||||
|
||||
private:
|
||||
static const int NUM_PERIODS = 10;
|
||||
static const int SAMPLE_DURATION_MS = 100; // 100 ms
|
||||
struct Sample
|
||||
{
|
||||
int m_iPktsCount; // number of payload packets
|
||||
int m_iBytesCount; // number of payload bytes
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_iPktsCount = 0;
|
||||
m_iBytesCount = 0;
|
||||
}
|
||||
|
||||
Sample()
|
||||
: m_iPktsCount(0)
|
||||
, m_iBytesCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
Sample(int iPkts, int iBytes)
|
||||
: m_iPktsCount(iPkts)
|
||||
, m_iBytesCount(iBytes)
|
||||
{
|
||||
}
|
||||
|
||||
Sample operator+(const Sample& other)
|
||||
{
|
||||
return Sample(m_iPktsCount + other.m_iPktsCount, m_iBytesCount + other.m_iBytesCount);
|
||||
}
|
||||
|
||||
Sample& operator+=(const Sample& other)
|
||||
{
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool empty() const { return m_iPktsCount == 0; }
|
||||
};
|
||||
|
||||
int incSampleIdx(int val, int inc = 1) const;
|
||||
|
||||
Sample m_Samples[NUM_PERIODS];
|
||||
|
||||
time_point m_tsFirstSampleTime; //< Start time of the first sameple.
|
||||
int m_iFirstSampleIdx; //< Index of the first sample.
|
||||
int m_iCurSampleIdx; //< Index of the current sample being collected.
|
||||
int m_iRateBps; // Input Rate in Bytes/sec
|
||||
};
|
||||
|
||||
} // namespace srt
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue