diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4061ca5e73..b1c02143f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ on: - main jobs: clang-format-check: - runs-on: macos-12 + runs-on: macos-13 steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install clang-format run: | brew install clang-format @@ -22,111 +22,79 @@ jobs: - name: Run clang format check run: | bash scripts/check-clang.sh - mac-os-build-clang: - runs-on: macos-12 - env: - CC: /usr/bin/clang - CXX: /usr/bin/clang++ - AWS_KVS_LOG_LEVEL: 2 - permissions: - id-token: write - contents: read - steps: - - name: Clone repository - uses: actions/checkout@v3 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} - aws-region: ${{ secrets.AWS_REGION }} - - name: Build repository - run: | - mkdir build && cd build - cmake .. -DBUILD_TEST=TRUE -DCOMPILER_WARNINGS=TRUE - make - - name: Run tests - run: | - cd build - ./tst/webrtc_client_test - mac-os-build-gcc: - runs-on: macos-12 + mac-tests: + strategy: + matrix: + os: + - name: Mac Intel + runner: macos-13 + - name: Mac Apple Silicon + runner: macos-15 + compiler: [ gcc, clang ] + config: + - name: Shared OpenSSL + cmake_flags: "-DBUILD_TEST=ON" + - name: Static OpenSSL + cmake_flags: "-DBUILD_TEST=ON -DBUILD_STATIC_LIBS=TRUE" + - name: Shared MbedTLS + cmake_flags: "-DBUILD_TEST=ON -DUSE_OPENSSL=OFF -DUSE_MBEDTLS=ON" + - name: Static MbedTLS + cmake_flags: "-DBUILD_TEST=ON -DBUILD_STATIC_LIBS=TRUE -DUSE_OPENSSL=OFF -DUSE_MBEDTLS=ON" + fail-fast: false + env: - CC: gcc - CXX: g++ AWS_KVS_LOG_LEVEL: 2 permissions: id-token: write contents: read + + runs-on: ${{ matrix.os.runner }} + name: ${{ matrix.os.name }}, ${{ matrix.compiler }}, ${{ matrix.config.name }} + steps: - - name: Clone repository - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 + - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} - - name: Build repository + + - name: Install GStreamer run: | - mkdir build && cd build - cmake .. -DBUILD_TEST=TRUE -DCOMPILER_WARNINGS=TRUE - make - - name: Run tests - run: | + brew install gstreamer + + - name: Configure and build ${{ matrix.config.name }} + run: | + if [[ "${{ matrix.compiler }}" == "gcc" ]]; then + export CC="gcc-14" + export CXX="g++-14" + else + export CC="clang" + export CXX="clang++" + fi + echo "Using $CC and $CXX" + + mkdir -p build cd build - ./tst/webrtc_client_test - mac-os-m1-build-clang: - runs-on: macos-13-xlarge - env: - AWS_KVS_LOG_LEVEL: 2 - PKG_CONFIG_PATH: /usr/local/opt/pkgconfig - permissions: - id-token: write - contents: read - steps: - - name: Clone repository - uses: actions/checkout@v3 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} - aws-region: ${{ secrets.AWS_REGION }} - - name: Install dependencies - run: | - brew install gstreamer glib - - name: Build repository - run: | - brew unlink openssl - mkdir build && cd build - sh -c 'cmake .. -DBUILD_TEST=TRUE -DCMAKE_C_COMPILER=$(brew --prefix llvm@15)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@15)/bin/clang++' - make + + if [[ "${{ matrix.compiler }}" == "gcc" ]]; then + # Skip building the tests for Mac with GCC + # https://github.com/awslabs/aws-crt-cpp/issues/605 + cmake .. ${{ matrix.config.cmake_flags }} -DENABLE_AWS_SDK_IN_TESTS=OFF + else + cmake .. ${{ matrix.config.cmake_flags }} + fi + make -j$(sysctl -n hw.ncpu) + shell: bash + - name: Run tests - run: | - cd build - ./tst/webrtc_client_test - static-build-mac: - runs-on: macos-12 - env: - AWS_KVS_LOG_LEVEL: 2 - permissions: - id-token: write - contents: read - steps: - - name: Clone repository - uses: actions/checkout@v3 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} - aws-region: ${{ secrets.AWS_REGION }} - - name: Build repository run: | - mkdir build && cd build - cmake .. -DBUILD_STATIC_LIBS=TRUE -DBUILD_TEST=TRUE - make - - name: Run tests - run: | cd build ./tst/webrtc_client_test + shell: bash + address-sanitizer: runs-on: ubuntu-20.04 env: @@ -140,9 +108,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -160,7 +128,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test undefined-behavior-sanitizer: @@ -175,9 +143,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -195,7 +163,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test # memory-sanitizer: @@ -206,7 +174,7 @@ jobs: # AWS_KVS_LOG_LEVEL: 2 # steps: # - name: Clone repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Install dependencies # run: | # sudo apt clean && sudo apt update @@ -231,9 +199,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -250,7 +218,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test linux-gcc-4_4: @@ -263,9 +231,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -288,7 +256,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test static-build-linux: @@ -304,7 +272,7 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: | apk update @@ -324,9 +292,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -348,7 +316,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test mbedtls-ubuntu-gcc-11: @@ -360,9 +328,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -382,7 +350,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test @@ -396,9 +364,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -429,9 +397,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -448,7 +416,7 @@ jobs: make ulimit -c unlimited -S - name: Run tests - run: | + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test sample-check: @@ -512,9 +480,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -530,7 +498,37 @@ jobs: cmake .. -DBUILD_TEST=TRUE make - name: Run tests - run: | + run: | + cd build + timeout --signal=SIGABRT 60m ./tst/webrtc_client_test + ubuntu-os-build-stats-calc-control: + runs-on: ubuntu-20.04 + env: + AWS_KVS_LOG_LEVEL: 2 + permissions: + id-token: write + contents: read + steps: + - name: Clone repository + uses: actions/checkout@v4 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Install dependencies + run: | + sudo apt clean && sudo apt update + sudo apt-get -y install libcurl4-openssl-dev + - name: Build repository + run: | + # TODO: Remove the following line. This is only a workaround for enabling IPv6, https://github.com/travis-ci/travis-ci/issues/8891. + sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' + mkdir build && cd build + cmake .. -DBUILD_TEST=TRUE -DENABLE_STATS_CALCULATION_CONTROL=TRUE + make + - name: Run tests + run: | cd build timeout --signal=SIGABRT 60m ./tst/webrtc_client_test windows-msvc-openssl: @@ -542,9 +540,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -592,7 +590,7 @@ jobs: # AWS_KVS_LOG_LEVEL: 7 # steps: # - name: Clone repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Move cloned repo # shell: powershell # run: | @@ -625,7 +623,7 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' @@ -643,7 +641,7 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' @@ -661,10 +659,113 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi binutils-arm-linux-gnueabi - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' mkdir build && cd build cmake .. -DBUILD_OPENSSL=TRUE -DBUILD_OPENSSL_PLATFORM=linux-generic32 -DBUILD_LIBSRTP_HOST_PLATFORM=x86_64-unknown-linux-gnu -DBUILD_LIBSRTP_DESTINATION_PLATFORM=arm-unknown-linux-uclibcgnueabi make + + stack-size-check: + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Check StackSize with Invalid Value + run: | + mkdir build && cd build + set +e # Allow the script to continue even if cmake fails (expected) + cmake .. -DKVS_STACK_SIZE=65536asdfsadf + cmake_exit_code=$? + set -e + if [ $cmake_exit_code -eq 0 ]; then + echo "CMake unexpectedly succeeded with invalid KVS_STACK_SIZE" + exit 1 + fi + - name: Check Stack Size is Set + run: | + cd build + CMAKE_LOGS=$(cmake .. -DKVS_STACK_SIZE=65536 -DBUILD_SAMPLE=OFF 2>&1) + if ! echo "$CMAKE_LOGS" | grep -q "Building with default stack size: 65536 bytes"; then + echo "Stack size was not sent to PIC properly. See the logs below:" + echo "$CMAKE_LOGS" + exit 1 + fi + + valgrind-check: + runs-on: ubuntu-latest + env: + AWS_KVS_LOG_LEVEL: 7 + permissions: + id-token: write + contents: read + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: Install deps + run: | + sudo apt clean && sudo apt update + sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ jammy main' + sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ jammy universe' + sudo apt-get -q update + sudo apt-get -y install libcurl4-openssl-dev valgrind + - name: Build repository + run: | + mkdir build && cd build + cmake .. -DUSE_OPENSSL=OFF -DUSE_MBEDTLS=ON + make + ulimit -c unlimited -S + - name: Run tests + run: | + cd build + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-master.txt ./samples/kvsWebrtcClientMaster demo-channel-gh-actions & + PID_MASTER=$! + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-viewer.txt ./samples/kvsWebrtcClientViewer demo-channel-gh-actions & + PID_VIEWER=$! + + # Wait for processes to run initially + sleep 30 # Wait 30s + + # Send SIGINT (2) to both processes + kill -2 $PID_VIEWER + sleep 30 + + kill -2 $PID_MASTER + + # Start a background task to enforce a timeout for graceful shutdown + ( + sleep 10 + kill -9 $PID_MASTER 2>/dev/null + kill -9 $PID_VIEWER 2>/dev/null + ) & + + wait $PID_MASTER + EXIT_STATUS_MASTER=$? + wait $PID_VIEWER + EXIT_STATUS_VIEWER=$? + + # Check exit statuses to determine if the interrupt was successful + if [ $EXIT_STATUS_MASTER -ne 0 ] || [ $EXIT_STATUS_VIEWER -ne 0 ]; then + echo "Process did not exit gracefully." + echo "Master exit code: $EXIT_STATUS_MASTER" + echo "Viewer exit code: $EXIT_STATUS_VIEWER" + exit 1 + else + + echo "Processes exited successfully." + fi + + # Check for memory leaks in Valgrind output files + if grep "All heap blocks were freed -- no leaks are possible" valgrind-master.txt && grep "All heap blocks were freed -- no leaks are possible" valgrind-viewer.txt; then + echo "No memory leaks detected." + else + echo "Memory leaks detected." + fi diff --git a/.github/workflows/pr-desc-lint.yml b/.github/workflows/pr-desc-lint.yml index 73c32d71c3..ee2c429ff1 100644 --- a/.github/workflows/pr-desc-lint.yml +++ b/.github/workflows/pr-desc-lint.yml @@ -23,34 +23,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - pr_description=$(gh pr view https://github.com/${GITHUB_REPOSITORY}/pull/${{ github.event.pull_request.number }} --json body -q ".body") - error_occurred=0 + pr_link="https://github.com/${GITHUB_REPOSITORY}/pull/${{ github.event.pull_request.number }}" + + echo "$pr_link" + + pr_description=$(gh pr view $pr_link --json body -q ".body") + + if [ -z "$pr_description" ]; then + echo "Failed to fetch the PR description" + exit 1 + fi + # Define minimum character count for each section - MIN_CHARS=25 + MIN_CHARS=10 # Extract contents - # Extract contents - what_changed=$(echo "$pr_description" | sed -n -e '/\*What was changed?\*/,/\*/p' | sed '$d' | sed '1d') - why_changed=$(echo "$pr_description" | sed -n -e '/\*Why was it changed?\*/,/\*/p' | sed '$d' | sed '1d') - how_changed=$(echo "$pr_description" | sed -n -e '/\*How was it changed?\*/,/\*/p' | sed '$d' | sed '1d') - testing_done=$(echo "$pr_description" | sed -n -e '/\*What testing was done for the changes?\*/,/\*/p' | sed '$d' | sed '1d') - + what_changed=$(echo "$pr_description" | sed -n -e '/\*What was changed?\*/,/\*Why was it changed?\*/p' | sed '$d' | sed '1d') + why_changed=$(echo "$pr_description" | sed -n -e '/\*Why was it changed?\*/,/\*How was it changed?\*/p' | sed '$d' | sed '1d') + how_changed=$(echo "$pr_description" | sed -n -e '/\*How was it changed?\*/,/\*What testing was done for the changes?\*/p' | sed '$d' | sed '1d') + testing_done=$(echo "$pr_description" | sed -n -e '/\*What testing was done for the changes?\*/,/By submitting this pull request/p' | sed '$d' | sed '1d') + + error_occurred=0 if [[ ${#what_changed} -lt $MIN_CHARS ]]; then - echo "PR description for what changed section is either missing or too short." + echo "PR description for what changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${what_changed}" error_occurred=1 fi if [[ ${#why_changed} -lt $MIN_CHARS ]]; then - echo "PR description for why it changed section is either missing or too short." + echo "PR description for why it changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${why_changed}" error_occurred=1 fi if [[ ${#how_changed} -lt $MIN_CHARS ]]; then - echo "PR description for how was it changed section is either missing or too short." + echo "PR description for how was it changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${how_changed}" error_occurred=1 fi if [[ ${#testing_done} -lt $MIN_CHARS ]]; then - echo "PR description for testing section are either missing or too short." + echo "PR description for testing section are either missing or too short. Required: ${MIN_CHARS}, Current: ${testing_done}" error_occurred=1 fi if [[ $error_occurred -eq 1 ]]; then - exit 1 + exit 1 fi diff --git a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt index d8117701c1..8905b07e3a 100644 --- a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt +++ b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt @@ -6,7 +6,7 @@ include(ExternalProject) ExternalProject_Add(libkvsCommonLws-download GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-producer-c.git - GIT_TAG v1.5.3 + GIT_TAG v1.5.4 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build LIST_SEPARATOR | CMAKE_ARGS @@ -19,6 +19,7 @@ ExternalProject_Add(libkvsCommonLws-download -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DUSE_OPENSSL=${USE_OPENSSL} -DUSE_MBEDTLS=${USE_MBEDTLS} + -DKVS_DEFAULT_STACK_SIZE=${KVS_DEFAULT_STACK_SIZE} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -DBUILD_STATIC=${BUILD_STATIC} BUILD_ALWAYS TRUE diff --git a/CMakeLists.txt b/CMakeLists.txt index 38b7e2ac16..e11d5dc6cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include(CheckIncludeFiles) include(CheckFunctionExists) # The version MUST be updated before every release -project(KinesisVideoWebRTCClient VERSION 1.11.0 LANGUAGES C) +project(KinesisVideoWebRTCClient VERSION 1.12.0 LANGUAGES C) # User Flags option(ADD_MUCLIBC "Add -muclibc c flag" OFF) @@ -21,6 +21,7 @@ option(ENABLE_DATA_CHANNEL "Enable support for data channel" ON) option(ENABLE_KVS_THREADPOOL "Enable support for KVS thread pool in signaling" OFF) option(INSTRUMENTED_ALLOCATORS "Enable memory instrumentation" OFF) option(ENABLE_AWS_SDK_IN_TESTS "Enable support for compiling AWS SDKs for tests" ON) +option(ENABLE_STATS_CALCULATION_CONTROL "Enable support for runtime control of ice agent stat calculations." OFF) # Developer Flags option(BUILD_TEST "Build the testing tree." OFF) @@ -53,6 +54,18 @@ add_definitions(-DSDK_VERSION=\"${GIT_COMMIT_HASH}\") add_definitions(-DVERSION_STRING=\"${PROJECT_VERSION}\") add_definitions(-DDETECTED_GIT_HASH) +if(NOT KVS_STACK_SIZE OR KVS_STACK_SIZE STREQUAL "") + message(STATUS "Stack size is system default stack size") +else() + string(REGEX MATCH "^[0-9]+$" IS_KVS_STACK_SIZE_A_NUMBER "${KVS_STACK_SIZE}") + if(IS_KVS_STACK_SIZE_A_NUMBER) + message(STATUS "Setting default stack size to provided value: ${KVS_STACK_SIZE} bytes") + set(KVS_DEFAULT_STACK_SIZE ${KVS_STACK_SIZE}) + else() + message(FATAL_ERROR "KVS_STACK_SIZE must be a numeric value, but got: ${KVS_STACK_SIZE}") + endif() +endif() + if(NOT OPEN_SRC_INSTALL_PREFIX OR OPEN_SRC_INSTALL_PREFIX STREQUAL "") set(OPEN_SRC_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/open-source" CACHE PATH "Libraries will be downloaded and built in this directory.") else() @@ -115,6 +128,10 @@ if (ENABLE_KVS_THREADPOOL) add_definitions(-DENABLE_KVS_THREADPOOL) endif() +if (ENABLE_STATS_CALCULATION_CONTROL) + add_definitions(-DENABLE_STATS_CALCULATION_CONTROL) +endif() + if(USE_OPENSSL) add_definitions(-DKVS_USE_OPENSSL) elseif(USE_MBEDTLS) @@ -254,6 +271,7 @@ set(BUILD_ARGS -DBUILD_STATIC=${BUILD_STATIC_LIBS} -DUSE_OPENSSL=${USE_OPENSSL} -DUSE_MBEDTLS=${USE_MBEDTLS} + -DKVS_DEFAULT_STACK_SIZE=${KVS_DEFAULT_STACK_SIZE} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}) build_dependency(kvsCommonLws ${BUILD_ARGS}) @@ -386,6 +404,7 @@ endif() file(GLOB WEBRTC_SIGNALING_CLIENT_SOURCE_FILES "src/source/Signaling/*.c") +message(STATUS "OPEN_SRC_INSTALL_PREFIX: ${OPEN_SRC_INSTALL_PREFIX}") include_directories(${OPEN_SRC_INCLUDE_DIRS}) include_directories(${OPEN_SRC_INSTALL_PREFIX}/include) include_directories(${KINESIS_VIDEO_WEBRTC_CLIENT_SRC}/src/include) diff --git a/README.md b/README.md index 7527802653..4e3d673387 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,9 @@ Please refer to the release notes in [Releases](https://github.com/awslabs/amazo ### Download To download run the following command: -`git clone --recursive https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c.git` +```shell +git clone https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c.git --single-branch -b main kvs-webrtc-sdk +``` You will also need to install `pkg-config` and `CMake` and a build environment @@ -65,9 +67,11 @@ You will also need to install `pkg-config` and `CMake` and a build environment Create a build directory in the newly checked out repository, and execute CMake from it. -`mkdir -p amazon-kinesis-video-streams-webrtc-sdk-c/build; cd amazon-kinesis-video-streams-webrtc-sdk-c/build; cmake .. ` +```shell +mkdir -p kvs-webrtc-sdk/build; cd kvs-webrtc-sdk/build; cmake .. +``` -We have provided an example of using GStreamer to capture/encode video, and then send via this library. This is only build if `pkg-config` finds +We have provided an example of using GStreamer to capture/encode video, and then send via this library. This is only built if `pkg-config` finds GStreamer is installed on your system. On Ubuntu and Raspberry Pi OS you can get the libraries by running @@ -136,7 +140,7 @@ If you wish to cross-compile `CC` and `CXX` are respected when building the libr If `-DBUILD_STATIC_LIBS=TRUE` then all dependencies and KVS WebRTC libraries will be built as static libraries. #### CMake Arguments -You can pass the following options to `cmake ..`. +You can pass the following options to `cmake ..`: * `-DBUILD_SAMPLE` -- Build the sample executables. ON by default. * `-DIOT_CORE_ENABLE_CREDENTIALS` -- Build the sample applications using IoT credentials. OFF by default. @@ -157,6 +161,10 @@ You can pass the following options to `cmake ..`. * `-DLINK_PROFILER` -- Link with gperftools (available profiler options are listed [here](https://github.com/gperftools/gperftools)) * `-DPKG_CONFIG_EXECUTABLE` -- Set pkg config path. This might be required to find gstreamer's pkg config specifically on Windows. * `-DENABLE_KVS_THREADPOOL` -- Enable the KVS threadpool which is off by default. +* `-DENABLE_STATS_CALCULATION_CONTROL` -- Enable the runtime control of ICE agent stats calculations. + +These options get propagated to [PIC](https://github.com/awslabs/amazon-kinesis-video-streams-pic): +* `-DKVS_STACK_SIZE` -- Default stack size for threads created using THREAD_CREATE(), in bytes. To clean up the `open-source` and `build` folders from previous build, use `cmake --build . --target clean` from the `build` folder @@ -369,6 +377,43 @@ If using the WebRTC SDK Test Page, set the following values using the same AWS c Then choose Start Viewer to start live video streaming of the sample H264/Opus frames. +## Memory optimization switches + +Starting with v1.11.0, the SDK provides some knobs to optimize memory usage to tailor to platform needs and resources + +### Controlling RTP rolling buffer capacity + +The SDK maintains an RTP rolling buffer to hold the RTP packets. This is useful to respond to NACKs and even in case of JitterBuffer. The rolling buffer size is controlled by 3 parameters: +1. MTU: This is set to a default of 1200 bytes +2. Buffer duration: This is the amount of time of media that you would like the rolling buffer to accommodate before it is overwritten due to buffer overflow. By default, the SDK sets this to 3 seconds +3. Highest expected bitrate: This is the expected bitrate of the media in question. The typical bitrates could vary based on resolution and codec. By default, the SDK sets this to 5 mibps for video and 1 mibps for audio + +The rolling buffer capacity is calculated as follows: +``` +Capacity = Buffer duration * highest expected bitrate (in bips) / 8 / MTU + +With buffer duration = 1 second, Highest expected bitrate = 5 mibps and MTU 1200 bytes, capacity = 546 RTP packets +``` + +The rolling buffer size can be configured per transceiver using the `configureTransceiverRollingBuffer` API. Make sure to use the API after the addTransceiver call to ensure the `RtcMediaStreamTrack` and `KvsRtpTransceiver` objects are created. By default, the rolling buffer duration is set to 3 sec and bitrate is set to 5mibps for video and 1mibps for audio. + +The rolling buffer config parameters are as follows: +``` +rollingBufferDurationSec = , must be more than 100ms and less than 10 seconds +rollingBufferBitratebps = , must be more than 100kibits/sec and less than 240 mibps +``` + +For example, if we want to set duration to 200ms and bitrate to 150kibps: +```c +PRtcRtpTransceiver pVideoRtcRtpTransceiver; +RtcMediaStreamTrack videoTrack; +videoTrack.kind = MEDIA_STREAM_TRACK_KIND_VIDEO; +videoTrack.codec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; +CHK_STATUS(configureTransceiverRollingBuffer(pVideoRtcRtpTransceiver, &videoTrack, 0.2, 150 * 1024)); +``` +By setting these up, applications can have better control over the amount of memory that the application consumes. However, note, if the allocation is too small and the network bad leading to multiple nacks, it can lead to choppy media / dropped frames. Hence, care must be taken while deciding on the values to ensure the parameters satisfy necessary performance requirements. +For more information, check the sample to see how these values are set up. + ## Setup IoT * To use IoT certificate to authenticate with KVS signaling, please refer to [Controlling Access to Kinesis Video Streams Resources Using AWS IoT](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iot.html) for provisioning details. * A sample IAM policy for the IoT role looks like below, policy can be modified based on your permission requirement. @@ -545,31 +590,8 @@ By default, the threadpool starts with 3 threads that it will increase up to the 1. `export AWS_KVS_WEBRTC_THREADPOOL_MIN_THREADS=` 2. `export AWS_KVS_WEBRTC_THREADPOOL_MAX_THREADS=` -### Set up TWCC -TWCC is a mechanism in WebRTC designed to enhance the performance and reliability of real-time communication over the Internet. TWCC addresses the challenges of network congestion by providing detailed feedback on the transport of packets across the network, enabling adaptive bitrate control and optimization of -media streams in real-time. This feedback mechanism is crucial for maintaining high-quality audio and video communication, as it allows senders to adjust their transmission strategies based on comprehensive information about packet losses, delays, and jitter experienced across the entire transport path. -The importance of TWCC in WebRTC lies in its ability to ensure efficient use of available network bandwidth while minimizing the negative impacts of network congestion. By monitoring the delivery of packets across the network, TWCC helps identify bottlenecks and adjust the media transmission rates accordingly. -This dynamic approach to congestion control is essential for preventing degradation in call quality, such as pixelation, stuttering, or drops in audio and video streams, especially in environments with fluctuating network conditions. To learn more about TWCC, you can refer to the [RFC draft](https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01) - -In order to enable TWCC usage in the SDK, 2 things need to be set up: - -1. Set the `disableSenderSideBandwidthEstimation` to FALSE. In our samples, the value is set using `enableTwcc` flag in `pSampleConfiguration` - -```c -pSampleConfiguration->enableTwcc = TRUE; // to enable TWCC -pSampleConfiguration->enableTwcc = FALSE; // to disable TWCC -configuration.kvsRtcConfiguration.disableSenderSideBandwidthEstimation = !pSampleConfiguration->enableTwcc; -``` - -2. Set the callback that will have the business logic to modify the bitrate based on packet loss information. The callback can be set using `peerConnectionOnSenderBandwidthEstimation()`. - -```c -CHK_STATUS(peerConnectionOnSenderBandwidthEstimation(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, - sampleSenderBandwidthEstimationHandler)); -``` - -By default, our SDK enables TWCC listener. The SDK has a sample implementation to integrate TWCC into the Gstreamer pipeline via the `sampleSenderBandwidthEstimationHandler` callback. To get more details, look for this specific callback. - +### Thread stack sizes +The default thread stack size in the KVS WebRTC SDK is determined by the system's default configuration. Developers can modify the stack size for all threads created using the `THREAD_CREATE()` macro by specifying the desired value through the `-DKVS_STACK_SIZE` CMake flag. Additionally, stack sizes for individual threads can be customized using the `THREAD_CREATE_WITH_PARAMS()` macro. Notable stack sizes that may need to be changed for your specific application will be the ConnectionListener Receiver thread and the media sender threads. ### Setting ICE related timeouts @@ -589,6 +611,20 @@ Let us look into when each of these could be changed: 3. `iceConnectionCheckTimeout`: It is useful to increase this timeout in unstable/slow network where the packet exchange takes time and hence the binding request/response. Essentially, increasing it will allow atleast one candidate pair to be tried for nomination by the other peer. 4. `iceConnectionCheckPollingInterval`: This value is set to a default of 50 ms per [spec](https://datatracker.ietf.org/doc/html/rfc8445#section-14.2). Changing this would change the frequency of connectivity checks and essentially, the ICE state machine transitions. Decreasing the value could help in faster connection establishment in a reliable high performant network setting with good system resources. Increasing the value could help in reducing the network load, however, the connection establishment could slow down. Unless there is a strong reasoning, it is **NOT** recommended to deviate from spec/default. +### Enable ICE agent stats + +The SDK calculates 4 different stats: +1. ICE server stats - stats for ICE servers the SDK is using +2. [Local candidate stats](https://www.w3.org/TR/webrtc-stats/#dom-rtcstatstype-local-candidate) - stats for the selected local candidate +3. [Remote candidate stats](https://www.w3.org/TR/webrtc-stats/#dom-rtcstatstype-remote-candidate) - stats for the selected remote candidate +4. [Candidate pair stats](https://www.w3.org/TR/webrtc-stats/#dom-rtcstatstype-candidate-pair) - stats for the selected candidate pair + +For more information on these stats, refer to [AWS Docs](https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-reference.html) + +The SDK enables generating these stats by default. To control whether the SDK calculates these stats, the ENABLE_STATS_CALCULATION_CONTROL CMake option must be set, enabling the use of the following field: +`configuration.kvsRtcConfiguration.enableIceStats = FALSE`. +Disabling these stats may lead to reductions in memory use. + ## Documentation All Public APIs are documented in our [Include.h](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/blob/main/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h), we also generate a [Doxygen](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/) each commit for easier navigation. diff --git a/samples/Common.c b/samples/Common.c index 35bc2c3b80..b371f30779 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -51,8 +51,8 @@ VOID onConnectionStateChange(UINT64 customData, RTC_PEER_CONNECTION_STATE newSta CHK_STATUS(peerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->peerConnectionMetrics)); CHK_STATUS(iceAgentGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->iceMetrics)); - if (STATUS_FAILED(retStatus = logSelectedIceCandidatesInformation(pSampleStreamingSession))) { - DLOGW("Failed to get information about selected Ice candidates: 0x%08x", retStatus); + if (pSampleConfiguration->enableIceStats) { + CHK_LOG_ERR(logSelectedIceCandidatesInformation(pSampleStreamingSession)); } break; case RTC_PEER_CONNECTION_STATE_FAILED: @@ -114,21 +114,21 @@ STATUS logSelectedIceCandidatesInformation(PSampleStreamingSession pSampleStream CHK(pSampleStreamingSession != NULL, STATUS_NULL_ARG); rtcMetrics.requestedTypeOfStats = RTC_STATS_TYPE_LOCAL_CANDIDATE; CHK_STATUS(rtcPeerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, NULL, &rtcMetrics)); - DLOGD("Local Candidate IP Address: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.address); - DLOGD("Local Candidate type: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.candidateType); - DLOGD("Local Candidate port: %d", rtcMetrics.rtcStatsObject.localIceCandidateStats.port); - DLOGD("Local Candidate priority: %d", rtcMetrics.rtcStatsObject.localIceCandidateStats.priority); - DLOGD("Local Candidate transport protocol: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.protocol); - DLOGD("Local Candidate relay protocol: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.relayProtocol); - DLOGD("Local Candidate Ice server source: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.url); + DLOGI("Local Candidate IP Address: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.address); + DLOGI("Local Candidate type: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.candidateType); + DLOGI("Local Candidate port: %d", rtcMetrics.rtcStatsObject.localIceCandidateStats.port); + DLOGI("Local Candidate priority: %d", rtcMetrics.rtcStatsObject.localIceCandidateStats.priority); + DLOGI("Local Candidate transport protocol: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.protocol); + DLOGI("Local Candidate relay protocol: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.relayProtocol); + DLOGI("Local Candidate Ice server source: %s", rtcMetrics.rtcStatsObject.localIceCandidateStats.url); rtcMetrics.requestedTypeOfStats = RTC_STATS_TYPE_REMOTE_CANDIDATE; CHK_STATUS(rtcPeerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, NULL, &rtcMetrics)); - DLOGD("Remote Candidate IP Address: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.address); - DLOGD("Remote Candidate type: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.candidateType); - DLOGD("Remote Candidate port: %d", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.port); - DLOGD("Remote Candidate priority: %d", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.priority); - DLOGD("Remote Candidate transport protocol: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.protocol); + DLOGI("Remote Candidate IP Address: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.address); + DLOGI("Remote Candidate type: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.candidateType); + DLOGI("Remote Candidate port: %d", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.port); + DLOGI("Remote Candidate priority: %d", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.priority); + DLOGI("Remote Candidate transport protocol: %s", rtcMetrics.rtcStatsObject.remoteIceCandidateStats.protocol); CleanUp: LEAVES(); return retStatus; @@ -365,6 +365,10 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP // Set the ICE mode explicitly configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_ALL; +#ifdef ENABLE_STATS_CALCULATION_CONTROL + configuration.kvsRtcConfiguration.enableIceStats = pSampleConfiguration->enableIceStats; +#endif + // Set the STUN server PCHAR pKinesisVideoStunUrlPostFix = KINESIS_VIDEO_STUN_URL_POSTFIX; // If region is in CN, add CN region uri postfix @@ -501,6 +505,10 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P pSampleStreamingSession->twccMetadata.updateLock = MUTEX_CREATE(TRUE); } + // Flag to enable/disable SDK calculations of selected ice server, local, remote and candidate pair stats. + // Note: enableIceStats only has an effect if compiler flag ENABLE_STATS_CALCULATION_CONTROL is defined. + pSampleConfiguration->enableIceStats = FALSE; + CHK_STATUS(initializePeerConnection(pSampleConfiguration, &pSampleStreamingSession->pPeerConnection)); CHK_STATUS(peerConnectionOnIceCandidate(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, onIceCandidateHandler)); CHK_STATUS( @@ -524,6 +532,10 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P CHK_STATUS(addTransceiver(pSampleStreamingSession->pPeerConnection, &videoTrack, &videoRtpTransceiverInit, &pSampleStreamingSession->pVideoRtcRtpTransceiver)); + CHK_STATUS(configureTransceiverRollingBuffer(pSampleStreamingSession->pVideoRtcRtpTransceiver, &videoTrack, + pSampleConfiguration->videoRollingBufferDurationSec, + pSampleConfiguration->videoRollingBufferBitratebps)); + CHK_STATUS(transceiverOnBandwidthEstimation(pSampleStreamingSession->pVideoRtcRtpTransceiver, (UINT64) pSampleStreamingSession, sampleBandwidthEstimationHandler)); @@ -536,6 +548,10 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P CHK_STATUS(addTransceiver(pSampleStreamingSession->pPeerConnection, &audioTrack, &audioRtpTransceiverInit, &pSampleStreamingSession->pAudioRtcRtpTransceiver)); + CHK_STATUS(configureTransceiverRollingBuffer(pSampleStreamingSession->pAudioRtcRtpTransceiver, &audioTrack, + pSampleConfiguration->audioRollingBufferDurationSec, + pSampleConfiguration->audioRollingBufferBitratebps)); + CHK_STATUS(transceiverOnBandwidthEstimation(pSampleStreamingSession->pAudioRtcRtpTransceiver, (UINT64) pSampleStreamingSession, sampleBandwidthEstimationHandler)); // twcc bandwidth estimation @@ -1208,9 +1224,8 @@ STATUS freeSampleConfiguration(PSampleConfiguration* ppSampleConfiguration) } for (i = 0; i < pSampleConfiguration->streamingSessionCount; ++i) { - retStatus = gatherIceServerStats(pSampleConfiguration->sampleStreamingSessionList[i]); - if (STATUS_FAILED(retStatus)) { - DLOGW("Failed to ICE Server Stats for streaming session %d: %08x", i, retStatus); + if (pSampleConfiguration->enableIceStats) { + CHK_LOG_ERR(gatherIceServerStats(pSampleConfiguration->sampleStreamingSessionList[i])); } freeSampleStreamingSession(&pSampleConfiguration->sampleStreamingSessionList[i]); } @@ -1542,7 +1557,7 @@ STATUS signalingMessageReceived(UINT64 customData, PReceivedSignalingMessage pRe MUTEX_UNLOCK(pSampleConfiguration->sampleConfigurationObjLock); locked = FALSE; - if (startStats && + if (pSampleConfiguration->enableIceStats && startStats && STATUS_FAILED(retStatus = timerQueueAddTimer(pSampleConfiguration->timerQueueHandle, SAMPLE_STATS_DURATION, SAMPLE_STATS_DURATION, getIceCandidatePairStatsCallback, (UINT64) pSampleConfiguration, &pSampleConfiguration->iceCandidatePairStatsTimerId))) { diff --git a/samples/Samples.h b/samples/Samples.h index f99de6224d..2e1324d310 100644 --- a/samples/Samples.h +++ b/samples/Samples.h @@ -128,6 +128,10 @@ typedef struct { SIGNALING_CLIENT_HANDLE signalingClientHandle; RTC_CODEC audioCodec; RTC_CODEC videoCodec; + DOUBLE videoRollingBufferDurationSec; + DOUBLE videoRollingBufferBitratebps; + DOUBLE audioRollingBufferDurationSec; + DOUBLE audioRollingBufferBitratebps; PBYTE pAudioFrameBuffer; UINT32 audioBufferSize; PBYTE pVideoFrameBuffer; @@ -171,6 +175,7 @@ typedef struct { PCHAR rtspUri; UINT32 logLevel; BOOL enableTwcc; + BOOL enableIceStats; } SampleConfiguration, *PSampleConfiguration; typedef struct { diff --git a/samples/kvsWebRTCClientMaster.c b/samples/kvsWebRTCClientMaster.c index d034431d05..b2ddbd214f 100644 --- a/samples/kvsWebRTCClientMaster.c +++ b/samples/kvsWebRTCClientMaster.c @@ -50,6 +50,21 @@ INT32 main(INT32 argc, CHAR* argv[]) pSampleConfiguration->audioCodec = audioCodec; pSampleConfiguration->videoCodec = videoCodec; + // Configure the RTP rolling buffer sizes for the set of sample frames + // to be smaller than the default settings (plus some extra for padding). + if (pSampleConfiguration->videoCodec == RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE) { + pSampleConfiguration->videoRollingBufferDurationSec = 3; + pSampleConfiguration->videoRollingBufferBitratebps = 1.4 * 1024 * 1024; + } else if (pSampleConfiguration->videoCodec == RTC_CODEC_H265) { + pSampleConfiguration->videoRollingBufferDurationSec = 3; + pSampleConfiguration->videoRollingBufferBitratebps = 462 * 1024; + } + + if (pSampleConfiguration->audioCodec == RTC_CODEC_OPUS) { + pSampleConfiguration->audioRollingBufferDurationSec = 3; + pSampleConfiguration->audioRollingBufferBitratebps = 512 * 1024; + } + if (argc > 2 && STRNCMP(argv[2], "1", 2) == 0) { pSampleConfiguration->channelInfo.useMediaStorage = TRUE; } diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index db7ec16d68..e51f44d2bd 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -460,11 +460,6 @@ extern "C" { */ #define MAX_SIGNALING_ENDPOINT_URI_LEN 512 -/** - * Maximum allowed ICE URI length - */ -#define MAX_ICE_CONFIG_URI_LEN 256 - /** * Maximum allowed correlation ID length */ @@ -836,7 +831,6 @@ typedef enum { RTC_CODEC_ALAW = 5, //!< ALAW audio codec RTC_CODEC_UNKNOWN = 6, RTC_CODEC_H265 = 7, //!< H265 video codec - // RTC_CODEC_MAX **MUST** be the last enum in the list **ALWAYS** and not assigned a value RTC_CODEC_MAX //!< Placeholder for max number of supported codecs } RTC_CODEC; @@ -1173,7 +1167,7 @@ typedef struct { UINT16 maximumTransmissionUnit; //!< Controls the size of the largest packet the WebRTC SDK will send //!< Some networks may drop packets if they exceed a certain size, and is useful in those conditions. //!< A smaller MTU will incur higher bandwidth usage however since more packets will be generated with - //!< smaller payloads. If unset DEFAULT_MTU_SIZE will be used + //!< smaller payloads. If unset DEFAULT_MTU_SIZE_BYTES will be used UINT32 iceLocalCandidateGatheringTimeout; //!< Maximum time ice will wait for gathering STUN and RELAY candidates. Once //!< it's reached, ice will proceed with whatever candidate it current has. Use default value if 0. @@ -1205,6 +1199,10 @@ typedef struct { BOOL disableSenderSideBandwidthEstimation; //!< Disable TWCC feedback based sender bandwidth estimation, enabled by default. //!< You want to set this to TRUE if you are on a very stable connection and want to save 1.2MB of //!< memory +#ifdef ENABLE_STATS_CALCULATION_CONTROL + BOOL enableIceStats; //!< Control whether ICE agent stats are to be calculated. ENABLE_STATS_CALCULATION_CONTROL compiler flag must be defined + //!< to use this member, else stats are enabled by default. +#endif } KvsRtcConfiguration, *PKvsRtcConfiguration; /** @@ -1611,6 +1609,19 @@ typedef struct { * @{ */ +/** + * @brief Set up rolling buffer configuration - max duration of media to store (sec) and expected max bitrate (bips) of the encoded media + * + * + * @param[in] PRtcRtpTransceiver IN/Initialized and configured RtcRtpTransceiver + * @param[in] PRtcMediaStreamTrack IN/Initialized media stream track information + * @param[in] DOUBLE IN/Rolling buffer duration in seconds + * @param[in] DOUBLE IN/Rolling buffer bitrate in bits/second + * + * @return STATUS code of the execution. STATUS_SUCCESS on success + */ +PUBLIC_API STATUS configureTransceiverRollingBuffer(PRtcRtpTransceiver, PRtcMediaStreamTrack, DOUBLE, DOUBLE); + /** * @brief Initialize a RtcPeerConnection with the provided Configuration * diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h index 72fffb67ba..59be39768c 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h @@ -24,6 +24,11 @@ extern "C" { */ #define MAX_CANDIDATE_ID_LENGTH 9U +/** + * Maximum allowed ICE URI length + */ +#define MAX_ICE_CONFIG_URI_LEN 127 + /** * Maximum allowed relay protocol length */ @@ -52,7 +57,7 @@ extern "C" { /** * Maximum allowed maximum protocol length (allowed values: tcp, udp) */ -#define MAX_PROTOCOL_LENGTH 8U +#define MAX_PROTOCOL_LENGTH 7U /** * Maximum allowed length of IP address string @@ -65,6 +70,12 @@ extern "C" { #define MAX_STATS_STRING_LENGTH 255U /*!@} */ +/** + * Maximum length of candidate type (host, srflx, relay, prflx, unknown) + */ +#define MAX_CANDIDATE_TYPE_LENGTH 7U +/*!@} */ + /** * @brief DOMString type is used to store strings of size 256 bytes (inclusive of '\0' character * @@ -233,14 +244,14 @@ typedef struct { * Reference: https://www.w3.org/TR/webrtc-stats/#ice-server-dict* */ typedef struct { - DOMString url; //!< STUN/TURN server URL - DOMString protocol; //!< Valid values: UDP, TCP - UINT32 iceServerIndex; //!< Ice server index to get stats from. Not available in spec! Needs to be - //!< populated by the application to get specific server stats - INT32 port; //!< Port number used by client - UINT64 totalRequestsSent; //!< Total amount of requests that have been sent to the server - UINT64 totalResponsesReceived; //!< Total number of responses received from the server - UINT64 totalRoundTripTime; //!< Sum of RTTs of all the requests for which response has been received + CHAR url[MAX_ICE_CONFIG_URI_LEN + 1]; //!< STUN/TURN server URL + CHAR protocol[MAX_PROTOCOL_LENGTH + 1]; //!< Valid values: UDP, TCP + UINT32 iceServerIndex; //!< Ice server index to get stats from. Not available in spec! Needs to be + //!< populated by the application to get specific server stats + INT32 port; //!< Port number used by client + UINT64 totalRequestsSent; //!< Total amount of requests that have been sent to the server + UINT64 totalResponsesReceived; //!< Total number of responses received from the server + UINT64 totalRoundTripTime; //!< Sum of RTTs of all the requests for which response has been received } RtcIceServerStats, *PRtcIceServerStats; /** @@ -267,15 +278,14 @@ typedef struct { */ typedef struct { - DOMString url; //!< For local candidates this is the URL of the ICE server from which the candidate was obtained - DOMString transportId; //!< Not used currently. ID of object that was inspected for RTCTransportStats - CHAR address[IP_ADDR_STR_LENGTH + 1]; //!< IPv4 or IPv6 address of the candidate - DOMString protocol; //!< Valid values: UDP, TCP - DOMString relayProtocol; //!< Protocol used by endpoint to communicate with TURN server. (Only for local candidate) - //!< Valid values: UDP, TCP, TLS - INT32 priority; //!< Computed using the formula in https://tools.ietf.org/html/rfc5245#section-15.1 - INT32 port; //!< Port number of the candidate - DOMString candidateType; //!< Type of local/remote ICE candidate + DOMString url; //!< For local candidates this is the URL of the ICE server from which the candidate was obtained + CHAR address[IP_ADDR_STR_LENGTH + 1]; //!< IPv4 or IPv6 address of the candidate + CHAR protocol[MAX_PROTOCOL_LENGTH + 1]; //!< Valid values: UDP, TCP + CHAR relayProtocol[MAX_PROTOCOL_LENGTH + 1]; //!< Protocol used by endpoint to communicate with TURN server. (Only for local candidate) + //!< Valid values: UDP, TCP, TLS + INT32 priority; //!< Computed using the formula in https://tools.ietf.org/html/rfc5245#section-15.1 + INT32 port; //!< Port number of the candidate + CHAR candidateType[MAX_CANDIDATE_TYPE_LENGTH + 1]; //!< Type of local/remote ICE candidate } RtcIceCandidateStats, *PRtcIceCandidateStats; /** diff --git a/src/source/Crypto/Dtls_mbedtls.c b/src/source/Crypto/Dtls_mbedtls.c index 29dca1a8c6..ac59402f67 100644 --- a/src/source/Crypto/Dtls_mbedtls.c +++ b/src/source/Crypto/Dtls_mbedtls.c @@ -31,7 +31,7 @@ STATUS createDtlsSession(PDtlsSessionCallbacks pDtlsSessionCallbacks, TIMER_QUEU mbedtls_ctr_drbg_set_prediction_resistance(&pDtlsSession->ctrDrbg, MBEDTLS_CTR_DRBG_PR_ON); CHK(mbedtls_ctr_drbg_seed(&pDtlsSession->ctrDrbg, mbedtls_entropy_func, &pDtlsSession->entropy, NULL, 0) == 0, STATUS_CREATE_SSL_FAILED); - CHK_STATUS(createIOBuffer(DEFAULT_MTU_SIZE, &pDtlsSession->pReadBuffer)); + CHK_STATUS(createIOBuffer(DEFAULT_MTU_SIZE_BYTES, &pDtlsSession->pReadBuffer)); pDtlsSession->timerQueueHandle = timerQueueHandle; pDtlsSession->timerId = MAX_UINT32; pDtlsSession->sslLock = MUTEX_CREATE(TRUE); @@ -290,7 +290,7 @@ STATUS dtlsSessionStart(PDtlsSession pDtlsSession, BOOL isServer) mbedtls_ssl_conf_export_keys_ext_cb(&pDtlsSession->sslCtxConfig, dtlsSessionKeyDerivationCallback, pDtlsSession); CHK(mbedtls_ssl_setup(&pDtlsSession->sslCtx, &pDtlsSession->sslCtxConfig) == 0, STATUS_SSL_CTX_CREATION_FAILED); - mbedtls_ssl_set_mtu(&pDtlsSession->sslCtx, DEFAULT_MTU_SIZE); + mbedtls_ssl_set_mtu(&pDtlsSession->sslCtx, DEFAULT_MTU_SIZE_BYTES); mbedtls_ssl_set_bio(&pDtlsSession->sslCtx, pDtlsSession, dtlsSessionSendCallback, dtlsSessionReceiveCallback, NULL); mbedtls_ssl_set_timer_cb(&pDtlsSession->sslCtx, &pDtlsSession->transmissionTimer, dtlsSessionSetTimerCallback, dtlsSessionGetTimerCallback); diff --git a/src/source/Crypto/Tls_mbedtls.c b/src/source/Crypto/Tls_mbedtls.c index 3b17c1ea7f..ea4c1ea40a 100644 --- a/src/source/Crypto/Tls_mbedtls.c +++ b/src/source/Crypto/Tls_mbedtls.c @@ -15,7 +15,7 @@ STATUS createTlsSession(PTlsSessionCallbacks pCallbacks, PTlsSession* ppTlsSessi pTlsSession = (PTlsSession) MEMCALLOC(1, SIZEOF(TlsSession)); CHK(pTlsSession != NULL, STATUS_NOT_ENOUGH_MEMORY); - CHK_STATUS(createIOBuffer(DEFAULT_MTU_SIZE, &pTlsSession->pReadBuffer)); + CHK_STATUS(createIOBuffer(DEFAULT_MTU_SIZE_BYTES, &pTlsSession->pReadBuffer)); pTlsSession->callbacks = *pCallbacks; pTlsSession->state = TLS_SESSION_STATE_NEW; @@ -117,7 +117,7 @@ STATUS tlsSessionStart(PTlsSession pTlsSession, BOOL isServer) mbedtls_ssl_conf_authmode(&pTlsSession->sslCtxConfig, MBEDTLS_SSL_VERIFY_REQUIRED); mbedtls_ssl_conf_rng(&pTlsSession->sslCtxConfig, mbedtls_ctr_drbg_random, &pTlsSession->ctrDrbg); CHK(mbedtls_ssl_setup(&pTlsSession->sslCtx, &pTlsSession->sslCtxConfig) == 0, STATUS_SSL_CTX_CREATION_FAILED); - mbedtls_ssl_set_mtu(&pTlsSession->sslCtx, DEFAULT_MTU_SIZE); + mbedtls_ssl_set_mtu(&pTlsSession->sslCtx, DEFAULT_MTU_SIZE_BYTES); mbedtls_ssl_set_bio(&pTlsSession->sslCtx, pTlsSession, tlsSessionSendCallback, tlsSessionReceiveCallback, NULL); /* init and send handshake */ diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 0c86fb7d40..a9d3a066a8 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -30,6 +30,7 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge PIceAgent pIceAgent = NULL; UINT32 i; UINT64 startTimeInMacro = 0; + BOOL doStatCalcs = TRUE; CHK(ppIceAgent != NULL && username != NULL && password != NULL && pConnectionListener != NULL, STATUS_NULL_ARG); CHK(STRNLEN(username, MAX_ICE_CONFIG_USER_NAME_LEN + 1) <= MAX_ICE_CONFIG_USER_NAME_LEN && @@ -40,7 +41,6 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge CHK(NULL != (pIceAgent = (PIceAgent) MEMCALLOC(1, SIZEOF(IceAgent))), STATUS_NOT_ENOUGH_MEMORY); STRNCPY(pIceAgent->localUsername, username, MAX_ICE_CONFIG_USER_NAME_LEN); STRNCPY(pIceAgent->localPassword, password, MAX_ICE_CONFIG_CREDENTIAL_LEN); - ATOMIC_STORE_BOOL(&pIceAgent->remoteCredentialReceived, FALSE); ATOMIC_STORE_BOOL(&pIceAgent->agentStartGathering, FALSE); ATOMIC_STORE_BOOL(&pIceAgent->stopGathering, FALSE); @@ -83,6 +83,12 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge pIceAgent->relayCandidateCount = 0; +// Ice agent stats calculations are on by default. +// Runtime control for turning stats calculations on/off can be activated with this compiler flag. +#ifdef ENABLE_STATS_CALCULATION_CONTROL + doStatCalcs = pIceAgent->kvsRtcConfiguration.enableIceStats; +#endif + CHK_STATUS(doubleListCreate(&pIceAgent->localCandidates)); CHK_STATUS(doubleListCreate(&pIceAgent->remoteCandidates)); CHK_STATUS(doubleListCreate(&pIceAgent->iceCandidatePairs)); @@ -90,7 +96,7 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge // Pre-allocate stun packets - // no other attribtues needed: https://tools.ietf.org/html/rfc8445#section-11 + // no other attributes needed: https://tools.ietf.org/html/rfc8445#section-11 CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_BINDING_INDICATION, NULL, &pIceAgent->pBindingIndication)); CHK_STATUS(hashTableCreateWithParams(ICE_HASH_TABLE_BUCKET_COUNT, ICE_HASH_TABLE_BUCKET_LENGTH, &pIceAgent->requestTimestampDiagnostics)); @@ -107,18 +113,22 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge (PCHAR) pRtcConfiguration->iceServers[i].username, (PCHAR) pRtcConfiguration->iceServers[i].credential), pIceAgent->iceAgentProfileDiagnostics.iceServerParsingTime[i], "ICE server parsing"); if (STATUS_SUCCEEDED(retStatus)) { - pIceAgent->rtcIceServerDiagnostics[i].port = (INT32) getInt16(pIceAgent->iceServers[i].ipAddress.port); - switch (pIceAgent->iceServers[pIceAgent->iceServersCount].transport) { - case KVS_SOCKET_PROTOCOL_UDP: - STRCPY(pIceAgent->rtcIceServerDiagnostics[i].protocol, ICE_TRANSPORT_TYPE_UDP); - break; - case KVS_SOCKET_PROTOCOL_TCP: - STRCPY(pIceAgent->rtcIceServerDiagnostics[i].protocol, ICE_TRANSPORT_TYPE_TCP); - break; - default: - MEMSET(pIceAgent->rtcIceServerDiagnostics[i].protocol, 0, SIZEOF(pIceAgent->rtcIceServerDiagnostics[i].protocol)); + if (doStatCalcs) { + CHK(NULL != (pIceAgent->pRtcIceServerDiagnostics[i] = (PRtcIceServerDiagnostics) MEMCALLOC(1, SIZEOF(RtcIceServerDiagnostics))), + STATUS_NOT_ENOUGH_MEMORY); + pIceAgent->pRtcIceServerDiagnostics[i]->port = (INT32) getInt16(pIceAgent->iceServers[i].ipAddress.port); + switch (pIceAgent->iceServers[pIceAgent->iceServersCount].transport) { + case KVS_SOCKET_PROTOCOL_UDP: + STRCPY(pIceAgent->pRtcIceServerDiagnostics[i]->protocol, ICE_TRANSPORT_TYPE_UDP); + break; + case KVS_SOCKET_PROTOCOL_TCP: + STRCPY(pIceAgent->pRtcIceServerDiagnostics[i]->protocol, ICE_TRANSPORT_TYPE_TCP); + break; + default: + MEMSET(pIceAgent->pRtcIceServerDiagnostics[i]->protocol, 0, SIZEOF(pIceAgent->pRtcIceServerDiagnostics[i]->protocol)); + } + STRCPY(pIceAgent->pRtcIceServerDiagnostics[i]->url, pRtcConfiguration->iceServers[i].urls); } - STRCPY(pIceAgent->rtcIceServerDiagnostics[i].url, pRtcConfiguration->iceServers[i].urls); pIceAgent->iceServersCount++; } else { DLOGE("Failed to parse ICE servers"); @@ -126,6 +136,16 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge } } + if (doStatCalcs) { + CHK(NULL != + (pIceAgent->pRtcSelectedRemoteIceCandidateDiagnostics = + (PRtcIceCandidateDiagnostics) MEMCALLOC(1, SIZEOF(RtcIceCandidateDiagnostics))), + STATUS_NOT_ENOUGH_MEMORY); + CHK(NULL != + (pIceAgent->pRtcSelectedLocalIceCandidateDiagnostics = + (PRtcIceCandidateDiagnostics) MEMCALLOC(1, SIZEOF(RtcIceCandidateDiagnostics))), + STATUS_NOT_ENOUGH_MEMORY); + } CleanUp: if (STATUS_FAILED(retStatus) && pIceAgent != NULL) { @@ -154,6 +174,7 @@ STATUS freeIceAgent(PIceAgent* ppIceAgent) PIceAgent pIceAgent = NULL; PDoubleListNode pCurNode = NULL; UINT64 data; + UINT32 i; PIceCandidatePair pIceCandidatePair = NULL; PIceCandidate pIceCandidate = NULL; @@ -163,8 +184,6 @@ STATUS freeIceAgent(PIceAgent* ppIceAgent) pIceAgent = *ppIceAgent; - hashTableFree(pIceAgent->requestTimestampDiagnostics); - if (pIceAgent->localCandidates != NULL) { CHK_STATUS(doubleListGetHeadNode(pIceAgent->localCandidates, &pCurNode)); while (pCurNode != NULL) { @@ -254,6 +273,12 @@ STATUS freeIceAgent(PIceAgent* ppIceAgent) freeTransactionIdStore(&pIceAgent->pStunBindingRequestTransactionIdStore); } + CHK_LOG_ERR(hashTableFree(pIceAgent->requestTimestampDiagnostics)); + SAFE_MEMFREE(pIceAgent->pRtcSelectedLocalIceCandidateDiagnostics); + SAFE_MEMFREE(pIceAgent->pRtcSelectedRemoteIceCandidateDiagnostics); + for (i = 0; i < MAX_ICE_SERVERS_COUNT; i++) { + SAFE_MEMFREE(pIceAgent->pRtcIceServerDiagnostics[i]); + } MEMFREE(pIceAgent); *ppIceAgent = NULL; @@ -696,13 +721,15 @@ STATUS iceAgentSendPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen) CleanUp: if (STATUS_SUCCEEDED(retStatus) && pIceAgent->pDataSendingIceCandidatePair != NULL) { - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.packetsDiscardedOnSend += packetsDiscarded; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.bytesDiscardedOnSend += bytesDiscarded; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.state = pIceAgent->pDataSendingIceCandidatePair->state; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.lastPacketSentTimestamp = - pIceAgent->pDataSendingIceCandidatePair->lastDataSentTime; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.bytesSent += bytesSent; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.packetsSent += packetsSent; + if (pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->packetsDiscardedOnSend += packetsDiscarded; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->bytesDiscardedOnSend += bytesDiscarded; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->state = pIceAgent->pDataSendingIceCandidatePair->state; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->lastPacketSentTimestamp = + pIceAgent->pDataSendingIceCandidatePair->lastDataSentTime; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->bytesSent += bytesSent; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->packetsSent += packetsSent; + } } if (locked) { @@ -1056,9 +1083,17 @@ STATUS createIceCandidatePairs(PIceAgent pIceAgent, PIceCandidate pIceCandidate, PIceCandidatePair pIceCandidatePair = NULL; BOOL freeObjOnFailure = TRUE; PIceCandidate pCurrentIceCandidate = NULL; + BOOL doStatCalcs = TRUE; CHK(pIceAgent != NULL && pIceCandidate != NULL, STATUS_NULL_ARG); - CHK_WARN(pIceCandidate->state == ICE_CANDIDATE_STATE_VALID, retStatus, "New ice candidate need to be valid to form pairs"); + CHK_WARN(pIceCandidate->state == ICE_CANDIDATE_STATE_VALID, retStatus, + "New ice candidate need to be valid to form pairs"); // Ice agent stats calculations are on by default. + +// Ice agent stats calculations are on by default. +// Runtime control for turning stats calculations on/off can be activated with this compiler flag. +#ifdef ENABLE_STATS_CALCULATION_CONTROL + doStatCalcs = pIceAgent->kvsRtcConfiguration.enableIceStats; +#endif // if pIceCandidate is a remote candidate, then form pairs with every single valid local candidate. Otherwise, // form pairs with every single valid remote candidate @@ -1076,6 +1111,13 @@ STATUS createIceCandidatePairs(PIceAgent pIceAgent, PIceCandidate pIceCandidate, pIceCandidatePair = (PIceCandidatePair) MEMCALLOC(1, SIZEOF(IceCandidatePair)); CHK(pIceCandidatePair != NULL, STATUS_NOT_ENOUGH_MEMORY); + if (doStatCalcs) { + CHK(NULL != + (pIceCandidatePair->pRtcIceCandidatePairDiagnostics = + (PRtcIceCandidatePairDiagnostics) MEMCALLOC(1, SIZEOF(RtcIceCandidatePairDiagnostics))), + STATUS_NOT_ENOUGH_MEMORY); + } + if (isRemoteCandidate) { // Since we pick local candidate list pIceCandidatePair->local = pCurrentIceCandidate; @@ -1094,19 +1136,23 @@ STATUS createIceCandidatePairs(PIceAgent pIceAgent, PIceCandidate pIceCandidate, CHK_STATUS(hashTableCreateWithParams(ICE_HASH_TABLE_BUCKET_COUNT, ICE_HASH_TABLE_BUCKET_LENGTH, &pIceCandidatePair->requestSentTime)); pIceCandidatePair->lastDataSentTime = 0; - STRNCPY(pIceCandidatePair->rtcIceCandidatePairDiagnostics.localCandidateId, pIceCandidatePair->local->id, - ARRAY_SIZE(pIceCandidatePair->rtcIceCandidatePairDiagnostics.localCandidateId)); - STRNCPY(pIceCandidatePair->rtcIceCandidatePairDiagnostics.remoteCandidateId, pIceCandidatePair->remote->id, - ARRAY_SIZE(pIceCandidatePair->rtcIceCandidatePairDiagnostics.remoteCandidateId)); - pIceCandidatePair->rtcIceCandidatePairDiagnostics.state = pIceCandidatePair->state; - pIceCandidatePair->rtcIceCandidatePairDiagnostics.nominated = pIceCandidatePair->nominated; - pIceCandidatePair->rtcIceCandidatePairDiagnostics.lastPacketSentTimestamp = pIceCandidatePair->lastDataSentTime; pIceCandidatePair->firstStunRequest = TRUE; pIceCandidatePair->priority = computeCandidatePairPriority(pIceCandidatePair, pIceAgent->isControlling); - pIceCandidatePair->rtcIceCandidatePairDiagnostics.totalRoundTripTime = 0.0; - pIceCandidatePair->rtcIceCandidatePairDiagnostics.currentRoundTripTime = 0.0; - // Set data sending ICE candidate pair stats - NULLABLE_SET_EMPTY(pIceCandidatePair->rtcIceCandidatePairDiagnostics.circuitBreakerTriggerCount); + + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + STRNCPY(pIceCandidatePair->pRtcIceCandidatePairDiagnostics->localCandidateId, pIceCandidatePair->local->id, + ARRAY_SIZE(pIceCandidatePair->pRtcIceCandidatePairDiagnostics->localCandidateId)); + STRNCPY(pIceCandidatePair->pRtcIceCandidatePairDiagnostics->remoteCandidateId, pIceCandidatePair->remote->id, + ARRAY_SIZE(pIceCandidatePair->pRtcIceCandidatePairDiagnostics->remoteCandidateId)); + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->state = pIceCandidatePair->state; + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->nominated = pIceCandidatePair->nominated; + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->lastPacketSentTimestamp = pIceCandidatePair->lastDataSentTime; + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->totalRoundTripTime = 0.0; + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->currentRoundTripTime = 0.0; + // Set data sending ICE candidate pair stats + NULLABLE_SET_EMPTY(pIceCandidatePair->pRtcIceCandidatePairDiagnostics->circuitBreakerTriggerCount); + } + CHK_STATUS(insertIceCandidatePair(pIceAgent->iceCandidatePairs, pIceCandidatePair)); freeObjOnFailure = FALSE; } @@ -1137,6 +1183,7 @@ STATUS freeIceCandidatePair(PIceCandidatePair* ppIceCandidatePair) CHK_LOG_ERR(freeTransactionIdStore(&pIceCandidatePair->pTransactionIdStore)); CHK_LOG_ERR(hashTableFree(pIceCandidatePair->requestSentTime)); + SAFE_MEMFREE(pIceCandidatePair->pRtcIceCandidatePairDiagnostics); SAFE_MEMFREE(pIceCandidatePair); CleanUp: @@ -1280,16 +1327,19 @@ STATUS iceCandidatePairCheckConnection(PStunPacket pStunBindingRequest, PIceAgen CHK_STATUS(hashTableUpsert(pIceCandidatePair->requestSentTime, checkSum, GETTIME())); CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); - if (pIceCandidatePair->local->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED) { - pIceAgent->rtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex].totalRequestsSent++; + if (pIceCandidatePair->local->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED && + pIceAgent->pRtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex] != NULL) { + pIceAgent->pRtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex]->totalRequestsSent++; } CHK_STATUS(iceAgentSendStunPacket(pStunBindingRequest, (PBYTE) pIceAgent->remotePassword, (UINT32) STRLEN(pIceAgent->remotePassword) * SIZEOF(CHAR), pIceAgent, pIceCandidatePair->local, &pIceCandidatePair->remote->ipAddress)); - pIceCandidatePair->rtcIceCandidatePairDiagnostics.lastRequestTimestamp = GETTIME(); - pIceCandidatePair->rtcIceCandidatePairDiagnostics.requestsSent++; + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->lastRequestTimestamp = GETTIME(); + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->requestsSent++; + } CleanUp: CHK_LOG_ERR(retStatus); @@ -1334,7 +1384,9 @@ STATUS iceAgentSendStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 pa &pIceCandidatePair)); if (pIceCandidatePair != NULL && pIceCandidatePair == pIceAgent->pDataSendingIceCandidatePair && pIceAgent->pDataSendingIceCandidatePair->firstStunRequest) { - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.firstRequestTimestamp = GETTIME(); + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->firstRequestTimestamp = GETTIME(); + } pIceAgent->pDataSendingIceCandidatePair->firstStunRequest = FALSE; } } @@ -1414,8 +1466,10 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent) transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId); checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)); CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddress)); - pIceAgent->rtcIceServerDiagnostics[pCandidate->iceServerIndex].totalRequestsSent++; - CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); + if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) { + pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++; + CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); + } } break; @@ -1966,10 +2020,11 @@ STATUS updateCandidateStats(PIceAgent pIceAgent, BOOL isRemote) STATUS retStatus = STATUS_SUCCESS; CHK(pIceAgent != NULL && pIceAgent->pDataSendingIceCandidatePair != NULL, STATUS_NULL_ARG); PIceCandidate pIceCandidate = pIceAgent->pDataSendingIceCandidatePair->remote; - PRtcIceCandidateDiagnostics pRtcIceCandidateDiagnostics = &pIceAgent->rtcSelectedRemoteIceCandidateDiagnostics; + PRtcIceCandidateDiagnostics pRtcIceCandidateDiagnostics = pIceAgent->pRtcSelectedRemoteIceCandidateDiagnostics; if (!isRemote) { pIceCandidate = pIceAgent->pDataSendingIceCandidatePair->local; - pRtcIceCandidateDiagnostics = &pIceAgent->rtcSelectedLocalIceCandidateDiagnostics; + pRtcIceCandidateDiagnostics = pIceAgent->pRtcSelectedLocalIceCandidateDiagnostics; + CHK(pRtcIceCandidateDiagnostics != NULL, STATUS_SUCCESS); STRNCPY(pRtcIceCandidateDiagnostics->url, STATS_NOT_APPLICABLE_STR, ARRAY_SIZE(pRtcIceCandidateDiagnostics->url)); // URL and relay protocol are populated only for local candidate by spec. // If candidate type is host, there is no URL and is set to N/A @@ -1988,13 +2043,14 @@ STATUS updateCandidateStats(PIceAgent pIceAgent, BOOL isRemote) break; case KVS_SOCKET_PROTOCOL_TCP: STRNCPY(pRtcIceCandidateDiagnostics->relayProtocol, ICE_TRANSPORT_TYPE_TCP, - ARRAY_SIZE(pIceAgent->rtcSelectedLocalIceCandidateDiagnostics.relayProtocol)); + ARRAY_SIZE(pIceAgent->pRtcSelectedLocalIceCandidateDiagnostics->relayProtocol)); break; default: MEMSET(pRtcIceCandidateDiagnostics->relayProtocol, 0, SIZEOF(pRtcIceCandidateDiagnostics->relayProtocol)); } } } + CHK(pRtcIceCandidateDiagnostics != NULL, STATUS_SUCCESS); getIpAddrStr(&pIceCandidate->ipAddress, pRtcIceCandidateDiagnostics->address, ARRAY_SIZE(pRtcIceCandidateDiagnostics->address)); pRtcIceCandidateDiagnostics->port = (UINT16) getInt16(pIceCandidate->ipAddress.port); pRtcIceCandidateDiagnostics->priority = pIceCandidate->priority; @@ -2002,8 +2058,8 @@ STATUS updateCandidateStats(PIceAgent pIceAgent, BOOL isRemote) ARRAY_SIZE(pRtcIceCandidateDiagnostics->candidateType)); STRNCPY(pRtcIceCandidateDiagnostics->protocol, ICE_TRANSPORT_TYPE_UDP, ARRAY_SIZE(pRtcIceCandidateDiagnostics->protocol)); - if (pIceCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED) { - STRNCPY(pRtcIceCandidateDiagnostics->protocol, pIceAgent->rtcIceServerDiagnostics[pIceCandidate->iceServerIndex].protocol, + if (pIceCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED && pIceAgent->pRtcIceServerDiagnostics[pIceCandidate->iceServerIndex] != NULL) { + STRNCPY(pRtcIceCandidateDiagnostics->protocol, pIceAgent->pRtcIceServerDiagnostics[pIceCandidate->iceServerIndex]->protocol, ARRAY_SIZE(pRtcIceCandidateDiagnostics->protocol)); } CleanUp: @@ -2068,10 +2124,6 @@ STATUS iceAgentConnectedStateSetup(PIceAgent pIceAgent) if (pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED && pIceCandidatePair->nominated) { pIceAgent->pDataSendingIceCandidatePair = pIceCandidatePair; - retStatus = updateSelectedLocalRemoteCandidateStats(pIceAgent); - if (STATUS_FAILED(retStatus)) { - DLOGW("Failed to update candidate stats with status code 0x%08x", retStatus); - } break; } } @@ -2165,7 +2217,9 @@ STATUS iceAgentReadyStateSetup(PIceAgent pIceAgent) pCurNode = pCurNode->pNext; if (pIceCandidatePair->nominated && pIceCandidatePair->state == ICE_CANDIDATE_PAIR_STATE_SUCCEEDED) { pNominatedAndValidCandidatePair = pIceCandidatePair; - pNominatedAndValidCandidatePair->rtcIceCandidatePairDiagnostics.nominated = pIceCandidatePair->nominated; + if (pNominatedAndValidCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pNominatedAndValidCandidatePair->pRtcIceCandidatePairDiagnostics->nominated = pIceCandidatePair->nominated; + } break; } } @@ -2183,6 +2237,9 @@ STATUS iceAgentReadyStateSetup(PIceAgent pIceAgent) iceAgentGetCandidateTypeStr(pIceAgent->pDataSendingIceCandidatePair->remote->iceCandidateType), pIceAgent->pDataSendingIceCandidatePair->roundTripTime / HUNDREDS_OF_NANOS_IN_A_MILLISECOND, pIceAgent->pDataSendingIceCandidatePair->local->priority, pIceAgent->pDataSendingIceCandidatePair->priority); + + CHK_LOG_ERR(updateSelectedLocalRemoteCandidateStats(pIceAgent)); + /* no state timeout for ready state */ pIceAgent->stateEndTime = INVALID_TIMESTAMP_VALUE; @@ -2372,10 +2429,12 @@ STATUS incomingDataHandler(UINT64 customData, PSocketConnection pSocketConnectio pIceAgent->pDataSendingIceCandidatePair->remote->ipAddress.family == pSrc->family && MEMCMP(pIceAgent->pDataSendingIceCandidatePair->remote->ipAddress.address, pSrc->address, addrLen) == 0 && (pIceAgent->pDataSendingIceCandidatePair->remote->ipAddress.port == pSrc->port)) { - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.lastPacketReceivedTimestamp = GETTIME(); - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.bytesReceived += bufferLen; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics - .packetsReceived++; // Since every byte buffer translates to a single RTP packet + if (pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->lastPacketReceivedTimestamp = GETTIME(); + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->bytesReceived += bufferLen; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics + ->packetsReceived++; // Since every byte buffer translates to a single RTP packet + } } } else { if (ATOMIC_LOAD_BOOL(&pIceAgent->processStun)) { @@ -2491,7 +2550,7 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS if (!pIceCandidatePair->nominated) { CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_USE_CANDIDATE, &pStunAttr)); if (pStunAttr != NULL) { - DLOGD("received candidate with USE_CANDIDATE flag, local candidate type %s(%s:%s).", + DLOGI("received candidate with USE_CANDIDATE flag, local candidate type %s(%s:%s).", iceAgentGetCandidateTypeStr(pIceCandidatePair->local->iceCandidateType), pIceCandidatePair->local->id, pIceCandidatePair->remote->id); pIceCandidatePair->nominated = TRUE; @@ -2505,9 +2564,11 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS } if (pIceCandidatePair == pIceAgent->pDataSendingIceCandidatePair) { - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.requestsReceived += connectivityCheckRequestsReceived; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.responsesSent += connectivityCheckResponsesSent; - pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics.nominated = pIceCandidatePair->nominated; + if (pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->requestsReceived += connectivityCheckRequestsReceived; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->responsesSent += connectivityCheckResponsesSent; + pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics->nominated = pIceCandidatePair->nominated; + } } else { DLOGD("going to change the data sending ice candidate pair."); } @@ -2522,14 +2583,16 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS CHK_WARN(pIceCandidate != NULL, retStatus, "Local candidate with socket %d not found. Dropping STUN binding success response", pSocketConnection->localSocket); - // Update round trip time for serial reflexive candidate - pIceAgent->rtcIceServerDiagnostics[pIceCandidate->iceServerIndex].totalResponsesReceived++; - // Transaction ID count be same for candidates coming from same interface, which means there would only - // be one entry. It is not necessary to update a return sttaus since it is not indicative of a failure - if ((hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime)) == STATUS_SUCCESS) { - pIceAgent->rtcIceServerDiagnostics[pIceCandidate->iceServerIndex].totalRoundTripTime += GETTIME() - requestSentTime; - CHK_STATUS(hashTableRemove(pIceAgent->requestTimestampDiagnostics, checkSum)); - hashTableGetCount(pIceAgent->requestTimestampDiagnostics, &count); + if (pIceAgent->pRtcIceServerDiagnostics[pIceCandidate->iceServerIndex] != NULL) { + // Update round trip time for server reflexive candidate + pIceAgent->pRtcIceServerDiagnostics[pIceCandidate->iceServerIndex]->totalResponsesReceived++; + // Transaction ID count be same for candidates coming from same interface, which means there would only + // be one entry. It is not necessary to update a return sttaus since it is not indicative of a failure + if (STATUS_SUCCEEDED(hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime))) { + pIceAgent->pRtcIceServerDiagnostics[pIceCandidate->iceServerIndex]->totalRoundTripTime += GETTIME() - requestSentTime; + CHK_STATUS(hashTableRemove(pIceAgent->requestTimestampDiagnostics, checkSum)); + CHK_STATUS(hashTableGetCount(pIceAgent->requestTimestampDiagnostics, &count)); + } } CHK_STATUS(deserializeStunPacket(pBuffer, bufferLen, NULL, 0, &pStunPacket)); @@ -2557,8 +2620,10 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS DLOGD("Pair binding response! %s %s", pIceCandidatePair->local->id, pIceCandidatePair->remote->id); if (hashTableGet(pIceCandidatePair->requestSentTime, checkSum, &requestSentTime) == STATUS_SUCCESS) { pIceCandidatePair->roundTripTime = GETTIME() - requestSentTime; - pIceCandidatePair->rtcIceCandidatePairDiagnostics.currentRoundTripTime = - (DOUBLE) (pIceCandidatePair->roundTripTime) / HUNDREDS_OF_NANOS_IN_A_SECOND; + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->currentRoundTripTime = + (DOUBLE) (pIceCandidatePair->roundTripTime) / HUNDREDS_OF_NANOS_IN_A_SECOND; + } } else { DLOGW("Unable to fetch request Timestamp from the hash table. No update to RTT for the pair (error code: 0x%08x)", retStatus); } @@ -2567,11 +2632,14 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS // Update round trip time and responses received only for relay candidates. if (pIceCandidatePair->local->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED) { - pIceAgent->rtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex].totalResponsesReceived++; - retStatus = hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime); - if (hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime) == STATUS_SUCCESS) { - pIceAgent->rtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex].totalRoundTripTime += GETTIME() - requestSentTime; - CHK_STATUS(hashTableRemove(pIceAgent->requestTimestampDiagnostics, checkSum)); + if (pIceAgent->pRtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex] != NULL) { + pIceAgent->pRtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex]->totalResponsesReceived++; + retStatus = hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime); + if (hashTableGet(pIceAgent->requestTimestampDiagnostics, checkSum, &requestSentTime) == STATUS_SUCCESS) { + pIceAgent->pRtcIceServerDiagnostics[pIceCandidatePair->local->iceServerIndex]->totalRoundTripTime += + GETTIME() - requestSentTime; + CHK_STATUS(hashTableRemove(pIceAgent->requestTimestampDiagnostics, checkSum)); + } } } CHK_STATUS(deserializeStunPacket(pBuffer, bufferLen, (PBYTE) pIceAgent->remotePassword, @@ -2603,17 +2671,20 @@ STATUS handleStunPacket(PIceAgent pIceAgent, PBYTE pBuffer, UINT32 bufferLen, PS pIceCandidatePair->roundTripTime = GETTIME() - requestSentTime; DLOGD("Ice candidate pair %s_%s is connected. Round trip time: %" PRIu64 "ms", pIceCandidatePair->local->id, pIceCandidatePair->remote->id, pIceCandidatePair->roundTripTime / HUNDREDS_OF_NANOS_IN_A_MILLISECOND); - pIceCandidatePair->rtcIceCandidatePairDiagnostics.totalRoundTripTime += - (DOUBLE) (pIceCandidatePair->roundTripTime) / HUNDREDS_OF_NANOS_IN_A_SECOND; - + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->totalRoundTripTime += + (DOUBLE) (pIceCandidatePair->roundTripTime) / HUNDREDS_OF_NANOS_IN_A_SECOND; + } CHK_STATUS(hashTableRemove(pIceCandidatePair->requestSentTime, checkSum)); } else { DLOGW("Unable to fetch request Timestamp from the hash table. No update to RTT for the pair (error code: 0x%08x)", retStatus); } } - pIceCandidatePair->rtcIceCandidatePairDiagnostics.responsesReceived += connectivityCheckResponsesReceived; - pIceCandidatePair->rtcIceCandidatePairDiagnostics.lastResponseTimestamp = GETTIME(); + if (pIceCandidatePair->pRtcIceCandidatePairDiagnostics != NULL) { + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->responsesReceived += connectivityCheckResponsesReceived; + pIceCandidatePair->pRtcIceCandidatePairDiagnostics->lastResponseTimestamp = GETTIME(); + } break; case STUN_PACKET_TYPE_BINDING_INDICATION: diff --git a/src/source/Ice/IceAgent.h b/src/source/Ice/IceAgent.h index a069ecb1aa..8ad364192d 100644 --- a/src/source/Ice/IceAgent.h +++ b/src/source/Ice/IceAgent.h @@ -80,24 +80,23 @@ typedef struct __IceAgent* PIceAgent; * Internal structure tracking ICE server parameters for diagnostics and metrics/stats */ typedef struct { - CHAR url[MAX_STATS_STRING_LENGTH + 1]; //!< STUN/TURN server URL - CHAR protocol[MAX_STATS_STRING_LENGTH + 1]; //!< Valid values: UDP, TCP - INT32 port; //!< Port number used by client - UINT64 totalRequestsSent; //!< Total amount of requests that have been sent to the server - UINT64 totalResponsesReceived; //!< Total number of responses received from the server - UINT64 totalRoundTripTime; //!< Sum of RTTs of all the requests for which response has been received + CHAR url[MAX_ICE_CONFIG_URI_LEN + 1]; //!< STUN/TURN server URL + CHAR protocol[MAX_PROTOCOL_LENGTH + 1]; //!< Valid values: UDP, TCP + INT32 port; //!< Port number used by client + UINT64 totalRequestsSent; //!< Total amount of requests that have been sent to the server + UINT64 totalResponsesReceived; //!< Total number of responses received from the server + UINT64 totalRoundTripTime; //!< Sum of RTTs of all the requests for which response has been received } RtcIceServerDiagnostics, *PRtcIceServerDiagnostics; typedef struct { - DOMString url; //!< For local candidates this is the URL of the ICE server from which the candidate was obtained - DOMString transportId[MAX_STATS_STRING_LENGTH + 1]; //!< ID of object that was inspected for RTCTransportStats - CHAR address[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; //!< IPv4 or IPv6 address of the candidate - DOMString protocol; //!< Valid values: UDP, TCP - DOMString relayProtocol; //!< Protocol used by endpoint to communicate with TURN server. - //!< Valid values: UDP, TCP, TLS - INT32 priority; //!< Computed using the formula in https://tools.ietf.org/html/rfc5245#section-15.1 - INT32 port; //!< Port number of the candidate - DOMString candidateType; //!< Type of local/remote ICE candidate + CHAR url[MAX_ICE_CONFIG_URI_LEN + 1]; //!< For local candidates this is the URL of the ICE server from which the candidate was obtained + CHAR address[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; //!< IPv4 or IPv6 address of the candidate + CHAR protocol[MAX_PROTOCOL_LENGTH + 1]; //!< Valid values: UDP, TCP + CHAR relayProtocol[MAX_PROTOCOL_LENGTH + 1]; //!< Protocol used by endpoint to communicate with TURN server. + //!< Valid values: UDP, TCP, TLS + INT32 priority; //!< Computed using the formula in https://tools.ietf.org/html/rfc5245#section-15.1 + INT32 port; //!< Port number of the candidate + CHAR candidateType[MAX_CANDIDATE_TYPE_LENGTH + 1]; //!< Type of local/remote ICE candidate } RtcIceCandidateDiagnostics, *PRtcIceCandidateDiagnostics; typedef struct { @@ -183,7 +182,7 @@ typedef struct { PHashTable requestSentTime; UINT64 roundTripTime; UINT64 responsesReceived; - RtcIceCandidatePairDiagnostics rtcIceCandidatePairDiagnostics; + PRtcIceCandidatePairDiagnostics pRtcIceCandidatePairDiagnostics; } IceCandidatePair, *PIceCandidatePair; typedef struct { @@ -213,9 +212,9 @@ struct __IceAgent { CHAR remotePassword[MAX_ICE_CONFIG_CREDENTIAL_LEN + 1]; CHAR combinedUserName[(MAX_ICE_CONFIG_USER_NAME_LEN + 1) << 1]; //!< the combination of remote user name and local user name. - RtcIceServerDiagnostics rtcIceServerDiagnostics[MAX_ICE_SERVERS_COUNT]; - RtcIceCandidateDiagnostics rtcSelectedLocalIceCandidateDiagnostics; - RtcIceCandidateDiagnostics rtcSelectedRemoteIceCandidateDiagnostics; + PRtcIceServerDiagnostics pRtcIceServerDiagnostics[MAX_ICE_SERVERS_COUNT]; + PRtcIceCandidateDiagnostics pRtcSelectedLocalIceCandidateDiagnostics; + PRtcIceCandidateDiagnostics pRtcSelectedRemoteIceCandidateDiagnostics; IceAgentProfileDiagnostics iceAgentProfileDiagnostics; PHashTable requestTimestampDiagnostics; diff --git a/src/source/Metrics/Metrics.c b/src/source/Metrics/Metrics.c index f39c3e0292..93a8cb030e 100644 --- a/src/source/Metrics/Metrics.c +++ b/src/source/Metrics/Metrics.c @@ -9,41 +9,47 @@ STATUS getIceCandidatePairStats(PRtcPeerConnection pRtcPeerConnection, PRtcIceCa STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; PIceAgent pIceAgent = NULL; + CHK((pRtcPeerConnection != NULL || pRtcIceCandidatePairStats != NULL), STATUS_NULL_ARG); pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; MUTEX_LOCK(pIceAgent->lock); locked = TRUE; +#ifdef ENABLE_STATS_CALCULATION_CONTROL + CHK_WARN(pIceAgent->kvsRtcConfiguration.enableIceStats, STATUS_INVALID_OPERATION, "ICE stats not enabled"); +#endif CHK(pIceAgent->pDataSendingIceCandidatePair != NULL, STATUS_SUCCESS); - PRtcIceCandidatePairDiagnostics pRtcIceCandidatePairDiagnostics = &pIceAgent->pDataSendingIceCandidatePair->rtcIceCandidatePairDiagnostics; - STRCPY(pRtcIceCandidatePairStats->localCandidateId, pRtcIceCandidatePairDiagnostics->localCandidateId); - STRCPY(pRtcIceCandidatePairStats->remoteCandidateId, pRtcIceCandidatePairDiagnostics->remoteCandidateId); - pRtcIceCandidatePairStats->state = pRtcIceCandidatePairDiagnostics->state; - pRtcIceCandidatePairStats->nominated = pRtcIceCandidatePairDiagnostics->nominated; + PRtcIceCandidatePairDiagnostics pRtcIceCandidatePairDiagnostics = pIceAgent->pDataSendingIceCandidatePair->pRtcIceCandidatePairDiagnostics; + if (pRtcIceCandidatePairDiagnostics != NULL) { + STRCPY(pRtcIceCandidatePairStats->localCandidateId, pRtcIceCandidatePairDiagnostics->localCandidateId); + STRCPY(pRtcIceCandidatePairStats->remoteCandidateId, pRtcIceCandidatePairDiagnostics->remoteCandidateId); + pRtcIceCandidatePairStats->state = pRtcIceCandidatePairDiagnostics->state; + pRtcIceCandidatePairStats->nominated = pRtcIceCandidatePairDiagnostics->nominated; - // Note: circuitBreakerTriggerCount This is set to NULL currently - pRtcIceCandidatePairStats->circuitBreakerTriggerCount = pRtcIceCandidatePairDiagnostics->circuitBreakerTriggerCount; + // Note: circuitBreakerTriggerCount This is set to NULL currently + pRtcIceCandidatePairStats->circuitBreakerTriggerCount = pRtcIceCandidatePairDiagnostics->circuitBreakerTriggerCount; - pRtcIceCandidatePairStats->packetsDiscardedOnSend = pRtcIceCandidatePairDiagnostics->packetsDiscardedOnSend; - pRtcIceCandidatePairStats->packetsSent = pRtcIceCandidatePairDiagnostics->packetsSent; - pRtcIceCandidatePairStats->packetsReceived = pRtcIceCandidatePairDiagnostics->packetsReceived; + pRtcIceCandidatePairStats->packetsDiscardedOnSend = pRtcIceCandidatePairDiagnostics->packetsDiscardedOnSend; + pRtcIceCandidatePairStats->packetsSent = pRtcIceCandidatePairDiagnostics->packetsSent; + pRtcIceCandidatePairStats->packetsReceived = pRtcIceCandidatePairDiagnostics->packetsReceived; - pRtcIceCandidatePairStats->bytesDiscardedOnSend = pRtcIceCandidatePairDiagnostics->bytesDiscardedOnSend; - pRtcIceCandidatePairStats->bytesSent = pRtcIceCandidatePairDiagnostics->bytesSent; - pRtcIceCandidatePairStats->bytesReceived = pRtcIceCandidatePairDiagnostics->bytesReceived; + pRtcIceCandidatePairStats->bytesDiscardedOnSend = pRtcIceCandidatePairDiagnostics->bytesDiscardedOnSend; + pRtcIceCandidatePairStats->bytesSent = pRtcIceCandidatePairDiagnostics->bytesSent; + pRtcIceCandidatePairStats->bytesReceived = pRtcIceCandidatePairDiagnostics->bytesReceived; - pRtcIceCandidatePairStats->lastPacketSentTimestamp = pRtcIceCandidatePairDiagnostics->lastPacketSentTimestamp; - pRtcIceCandidatePairStats->lastPacketReceivedTimestamp = pRtcIceCandidatePairDiagnostics->lastPacketReceivedTimestamp; - pRtcIceCandidatePairStats->lastRequestTimestamp = pRtcIceCandidatePairDiagnostics->lastRequestTimestamp; - pRtcIceCandidatePairStats->firstRequestTimestamp = pRtcIceCandidatePairDiagnostics->firstRequestTimestamp; - pRtcIceCandidatePairStats->lastResponseTimestamp = pRtcIceCandidatePairDiagnostics->lastResponseTimestamp; + pRtcIceCandidatePairStats->lastPacketSentTimestamp = pRtcIceCandidatePairDiagnostics->lastPacketSentTimestamp; + pRtcIceCandidatePairStats->lastPacketReceivedTimestamp = pRtcIceCandidatePairDiagnostics->lastPacketReceivedTimestamp; + pRtcIceCandidatePairStats->lastRequestTimestamp = pRtcIceCandidatePairDiagnostics->lastRequestTimestamp; + pRtcIceCandidatePairStats->firstRequestTimestamp = pRtcIceCandidatePairDiagnostics->firstRequestTimestamp; + pRtcIceCandidatePairStats->lastResponseTimestamp = pRtcIceCandidatePairDiagnostics->lastResponseTimestamp; - pRtcIceCandidatePairStats->totalRoundTripTime = pRtcIceCandidatePairDiagnostics->totalRoundTripTime; - pRtcIceCandidatePairStats->currentRoundTripTime = pRtcIceCandidatePairDiagnostics->currentRoundTripTime; + pRtcIceCandidatePairStats->totalRoundTripTime = pRtcIceCandidatePairDiagnostics->totalRoundTripTime; + pRtcIceCandidatePairStats->currentRoundTripTime = pRtcIceCandidatePairDiagnostics->currentRoundTripTime; - pRtcIceCandidatePairStats->requestsReceived = pRtcIceCandidatePairDiagnostics->requestsReceived; - pRtcIceCandidatePairStats->requestsSent = pRtcIceCandidatePairDiagnostics->requestsSent; - pRtcIceCandidatePairStats->responsesReceived = pRtcIceCandidatePairDiagnostics->responsesReceived; - pRtcIceCandidatePairStats->responsesSent = pRtcIceCandidatePairDiagnostics->responsesSent; + pRtcIceCandidatePairStats->requestsReceived = pRtcIceCandidatePairDiagnostics->requestsReceived; + pRtcIceCandidatePairStats->requestsSent = pRtcIceCandidatePairDiagnostics->requestsSent; + pRtcIceCandidatePairStats->responsesReceived = pRtcIceCandidatePairDiagnostics->responsesReceived; + pRtcIceCandidatePairStats->responsesSent = pRtcIceCandidatePairDiagnostics->responsesSent; + } CleanUp: if (locked) { MUTEX_UNLOCK(pIceAgent->lock); @@ -55,21 +61,28 @@ STATUS getIceCandidateStats(PRtcPeerConnection pRtcPeerConnection, BOOL isRemote { STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; - PIceAgent pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; + PIceAgent pIceAgent = NULL; + PRtcIceCandidateDiagnostics pRtcIceCandidateDiagnostics = NULL; CHK((pRtcPeerConnection != NULL || pRtcIceCandidateStats != NULL), STATUS_NULL_ARG); + pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; MUTEX_LOCK(pIceAgent->lock); locked = TRUE; - PRtcIceCandidateDiagnostics pRtcIceCandidateDiagnostics = &pIceAgent->rtcSelectedRemoteIceCandidateDiagnostics; - if (!isRemote) { - pRtcIceCandidateDiagnostics = &pIceAgent->rtcSelectedLocalIceCandidateDiagnostics; - STRCPY(pRtcIceCandidateStats->relayProtocol, pRtcIceCandidateDiagnostics->relayProtocol); - STRCPY(pRtcIceCandidateStats->url, pRtcIceCandidateDiagnostics->url); +#ifdef ENABLE_STATS_CALCULATION_CONTROL + CHK_WARN(pIceAgent->kvsRtcConfiguration.enableIceStats, STATUS_INVALID_OPERATION, "ICE stats not enabled"); +#endif + pRtcIceCandidateDiagnostics = pIceAgent->pRtcSelectedRemoteIceCandidateDiagnostics; + if (pRtcIceCandidateDiagnostics != NULL) { + if (!isRemote) { + pRtcIceCandidateDiagnostics = pIceAgent->pRtcSelectedLocalIceCandidateDiagnostics; + STRCPY(pRtcIceCandidateStats->relayProtocol, pRtcIceCandidateDiagnostics->relayProtocol); + STRCPY(pRtcIceCandidateStats->url, pRtcIceCandidateDiagnostics->url); + } + STRCPY(pRtcIceCandidateStats->address, pRtcIceCandidateDiagnostics->address); + STRCPY(pRtcIceCandidateStats->candidateType, pRtcIceCandidateDiagnostics->candidateType); + pRtcIceCandidateStats->port = pRtcIceCandidateDiagnostics->port; + pRtcIceCandidateStats->priority = pRtcIceCandidateDiagnostics->priority; + STRCPY(pRtcIceCandidateStats->protocol, pRtcIceCandidateDiagnostics->protocol); } - STRCPY(pRtcIceCandidateStats->address, pRtcIceCandidateDiagnostics->address); - STRCPY(pRtcIceCandidateStats->candidateType, pRtcIceCandidateDiagnostics->candidateType); - pRtcIceCandidateStats->port = pRtcIceCandidateDiagnostics->port; - pRtcIceCandidateStats->priority = pRtcIceCandidateDiagnostics->priority; - STRCPY(pRtcIceCandidateStats->protocol, pRtcIceCandidateDiagnostics->protocol); CleanUp: if (locked) { MUTEX_UNLOCK(pIceAgent->lock); @@ -83,16 +96,24 @@ STATUS getIceServerStats(PRtcPeerConnection pRtcPeerConnection, PRtcIceServerSta BOOL locked = FALSE; PIceAgent pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; CHK((pRtcPeerConnection != NULL && pRtcIceServerStats != NULL), STATUS_NULL_ARG); - CHK(pRtcIceServerStats->iceServerIndex < pIceAgent->iceServersCount, STATUS_ICE_SERVER_INDEX_INVALID); MUTEX_LOCK(pIceAgent->lock); locked = TRUE; - pRtcIceServerStats->port = pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].port; - STRCPY(pRtcIceServerStats->protocol, pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].protocol); - STRCPY(pRtcIceServerStats->url, pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].url); - pRtcIceServerStats->totalRequestsSent = pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].totalRequestsSent; - pRtcIceServerStats->totalResponsesReceived = pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].totalResponsesReceived; - pRtcIceServerStats->totalRoundTripTime = pIceAgent->rtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex].totalRoundTripTime; + +#ifdef ENABLE_STATS_CALCULATION_CONTROL + CHK_WARN(pIceAgent->kvsRtcConfiguration.enableIceStats, STATUS_INVALID_OPERATION, "ICE stats not enabled"); +#endif + + CHK(pRtcIceServerStats->iceServerIndex < pIceAgent->iceServersCount, STATUS_ICE_SERVER_INDEX_INVALID); + + if (pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex] != NULL) { + pRtcIceServerStats->port = pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->port; + STRCPY(pRtcIceServerStats->protocol, pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->protocol); + STRCPY(pRtcIceServerStats->url, pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->url); + pRtcIceServerStats->totalRequestsSent = pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->totalRequestsSent; + pRtcIceServerStats->totalResponsesReceived = pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->totalResponsesReceived; + pRtcIceServerStats->totalRoundTripTime = pIceAgent->pRtcIceServerDiagnostics[pRtcIceServerStats->iceServerIndex]->totalRoundTripTime; + } CleanUp: if (locked) { MUTEX_UNLOCK(pIceAgent->lock); diff --git a/src/source/PeerConnection/PeerConnection.c b/src/source/PeerConnection/PeerConnection.c index 08c06790c4..d25101cb38 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -7,18 +7,23 @@ static volatile ATOMIC_BOOL gKvsWebRtcInitialized = (SIZE_T) FALSE; // Function to get access to the Singleton instance PWebRtcClientContext getWebRtcClientInstance() { + ENTERS(); static WebRtcClientContext w = {.pStunIpAddrCtx = NULL, .stunCtxlock = INVALID_MUTEX_VALUE, .contextRefCnt = 0, .isContextInitialized = FALSE}; ATOMIC_INCREMENT(&w.contextRefCnt); + LEAVES(); return &w; } VOID releaseHoldOnInstance(PWebRtcClientContext pWebRtcClientContext) { + ENTERS(); ATOMIC_DECREMENT(&pWebRtcClientContext->contextRefCnt); + LEAVES(); } STATUS createWebRtcClientInstance() { + ENTERS(); PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; @@ -41,11 +46,15 @@ STATUS createWebRtcClientInstance() MUTEX_UNLOCK(pWebRtcClientContext->stunCtxlock); } releaseHoldOnInstance(pWebRtcClientContext); + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS allocateSrtp(PKvsPeerConnection pKvsPeerConnection) { + ENTERS(); DtlsKeyingMaterial dtlsKeyingMaterial; STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; @@ -67,17 +76,16 @@ STATUS allocateSrtp(PKvsPeerConnection pKvsPeerConnection) if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->pSrtpSessionLock); } + CHK_LOG_ERR(retStatus); - if (STATUS_FAILED(retStatus)) { - DLOGW("dtlsSessionPopulateKeyingMaterial failed with 0x%08x", retStatus); - } - + LEAVES(); return retStatus; } #ifdef ENABLE_DATA_CHANNEL STATUS allocateSctpSortDataChannelsDataCallback(UINT64 customData, PHashEntry pHashEntry) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PAllocateSctpSortDataChannelsData data = (PAllocateSctpSortDataChannelsData) customData; PKvsDataChannel pKvsDataChannel = (PKvsDataChannel) pHashEntry->value; @@ -90,11 +98,15 @@ STATUS allocateSctpSortDataChannelsDataCallback(UINT64 customData, PHashEntry pH data->currentDataChannelId += 2; CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS allocateSctp(PKvsPeerConnection pKvsPeerConnection) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; SctpSessionCallbacks sctpSessionCallbacks; AllocateSctpSortDataChannelsData data; @@ -145,12 +157,16 @@ STATUS allocateSctp(PKvsPeerConnection pKvsPeerConnection) } CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } #endif VOID onInboundPacket(UINT64 customData, PBYTE buff, UINT32 buffLen) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; BOOL isDtlsConnected = FALSE; @@ -212,12 +228,14 @@ VOID onInboundPacket(UINT64 customData, PBYTE buff, UINT32 buffLen) } CleanUp: - CHK_LOG_ERR(retStatus); + + LEAVES(); } STATUS sendPacketToRtpReceiver(PKvsPeerConnection pKvsPeerConnection, PBYTE pBuffer, UINT32 bufferLen) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PDoubleListNode pCurNode = NULL; PKvsRtpTransceiver pTransceiver; @@ -301,11 +319,15 @@ STATUS sendPacketToRtpReceiver(PKvsPeerConnection pKvsPeerConnection, PBYTE pBuf freeRtpPacket(&pRtpPacket); CHK_LOG_ERR(retStatus); } + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS changePeerConnectionState(PKvsPeerConnection pKvsPeerConnection, RTC_PEER_CONNECTION_STATE newState) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; RtcOnConnectionStateChange onConnectionStateChange = NULL; @@ -347,17 +369,18 @@ STATUS changePeerConnectionState(PKvsPeerConnection pKvsPeerConnection, RTC_PEER } CleanUp: - if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } - CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS onFrameReadyFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, UINT32 frameSize) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsRtpTransceiver pTransceiver = (PKvsRtpTransceiver) customData; PRtpPacket pPacket = NULL; @@ -410,11 +433,14 @@ STATUS onFrameReadyFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, U CleanUp: CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS onFrameDroppedFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, UINT32 timestamp) { + ENTERS(); UNUSED_PARAM(endIndex); STATUS retStatus = STATUS_SUCCESS; UINT64 hashValue = 0; @@ -439,22 +465,29 @@ STATUS onFrameDroppedFunc(UINT64 customData, UINT16 startIndex, UINT16 endIndex, MUTEX_UNLOCK(pTransceiver->statsLock); CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } PVOID dtlsSessionStartThread(PVOID args) { + ENTERS(); PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) args; if (pKvsPeerConnection != NULL) { dtlsSessionHandshakeInThread(pKvsPeerConnection->pDtlsSession, pKvsPeerConnection->dtlsIsServer); } else { DLOGE("Peer connection object NULL, cannot start DTLS handshake"); } + + LEAVES(); return NULL; } VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; RTC_PEER_CONNECTION_STATE newConnectionState = RTC_PEER_CONNECTION_STATE_NEW; @@ -518,12 +551,14 @@ VOID onIceConnectionStateChange(UINT64 customData, UINT64 connectionState) CHK_STATUS(changePeerConnectionState(pKvsPeerConnection, newConnectionState)); CleanUp: - CHK_LOG_ERR(retStatus); + + LEAVES(); } VOID onNewIceLocalCandidate(UINT64 customData, PCHAR candidateSdpStr) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; BOOL locked = FALSE; @@ -548,16 +583,18 @@ VOID onNewIceLocalCandidate(UINT64 customData, PCHAR candidateSdpStr) pKvsPeerConnection->onIceCandidate(pKvsPeerConnection->onIceCandidateCustomData, pIceCandidateStr); CleanUp: - CHK_LOG_ERR(retStatus); if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } + + LEAVES(); } VOID onSctpSessionOutboundPacket(UINT64 customData, PBYTE pPacket, UINT32 packetLen) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = NULL; if (customData == 0) { @@ -568,13 +605,14 @@ VOID onSctpSessionOutboundPacket(UINT64 customData, PBYTE pPacket, UINT32 packet CHK_STATUS(dtlsSessionPutApplicationData(pKvsPeerConnection->pDtlsSession, pPacket, packetLen)); CleanUp: - if (STATUS_FAILED(retStatus)) { - DLOGW("onSctpSessionOutboundPacket failed with 0x%08x", retStatus); - } + CHK_LOG_ERR(retStatus); + + LEAVES(); } VOID onSctpSessionDataChannelMessage(UINT64 customData, UINT32 channelId, BOOL isBinary, PBYTE pMessage, UINT32 pMessageLen) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; PKvsDataChannel pKvsDataChannel = NULL; @@ -598,13 +636,14 @@ VOID onSctpSessionDataChannelMessage(UINT64 customData, UINT32 channelId, BOOL i pKvsDataChannel->onMessage(pKvsDataChannel->onMessageCustomData, &pKvsDataChannel->dataChannel, isBinary, pMessage, pMessageLen); CleanUp: - if (STATUS_FAILED(retStatus)) { - DLOGW("onSctpSessionDataChannelMessage failed with 0x%08x", retStatus); - } + CHK_LOG_ERR(retStatus); + + LEAVES(); } VOID onSctpSessionDataChannelOpen(UINT64 customData, UINT32 channelId, PBYTE pName, UINT32 nameLen) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) customData; PKvsDataChannel pKvsDataChannel = NULL; @@ -627,12 +666,14 @@ VOID onSctpSessionDataChannelOpen(UINT64 customData, UINT32 channelId, PBYTE pNa pKvsPeerConnection->onDataChannel(pKvsPeerConnection->onDataChannelCustomData, &(pKvsDataChannel->dataChannel)); CleanUp: - CHK_LOG_ERR(retStatus); + + LEAVES(); } VOID onDtlsOutboundPacket(UINT64 customData, PBYTE pBuffer, UINT32 bufferLen) { + ENTERS(); PKvsPeerConnection pKvsPeerConnection = NULL; if (customData == 0) { return; @@ -640,10 +681,12 @@ VOID onDtlsOutboundPacket(UINT64 customData, PBYTE pBuffer, UINT32 bufferLen) pKvsPeerConnection = (PKvsPeerConnection) customData; iceAgentSendPacket(pKvsPeerConnection->pIceAgent, pBuffer, bufferLen); + LEAVES(); } VOID onDtlsStateChange(UINT64 customData, RTC_DTLS_TRANSPORT_STATE newDtlsState) { + ENTERS(); PKvsPeerConnection pKvsPeerConnection = NULL; if (customData == 0) { return; @@ -662,6 +705,7 @@ VOID onDtlsStateChange(UINT64 customData, RTC_DTLS_TRANSPORT_STATE newDtlsState) /* explicit ignore */ break; } + LEAVES(); } /* Generate a printable string that does not @@ -680,6 +724,7 @@ STATUS generateJSONSafeString(PCHAR pDst, UINT32 len) } CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -687,6 +732,7 @@ STATUS generateJSONSafeString(PCHAR pDst, UINT32 len) STATUS rtcpReportsCallback(UINT32 timerId, UINT64 currentTime, UINT64 customData) { + ENTERS(); UNUSED_PARAM(timerId); STATUS retStatus = STATUS_SUCCESS; BOOL ready = FALSE; @@ -747,12 +793,15 @@ STATUS rtcpReportsCallback(UINT32 timerId, UINT64 currentTime, UINT64 customData CleanUp: CHK_LOG_ERR(retStatus); SAFE_MEMFREE(rawPacket); + + LEAVES(); return retStatus; } // Not thread safe STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) { + ENTERS(); INT32 errCode; STATUS retStatus = STATUS_SUCCESS; struct addrinfo *rp, *res; @@ -778,11 +827,15 @@ STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) if (!resolved) { retStatus = STATUS_RESOLVE_HOSTNAME_FAILED; } + + CHK_LOG_ERR(retStatus); + LEAVES(); return retStatus; } STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PKvsIpAddress pIpAddr) { + ENTERS(); UNUSED_PARAM(customData); STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; @@ -821,11 +874,15 @@ STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PKvsIpAddress pIpAddr) } DLOGD("Exiting from stun server IP callback"); releaseHoldOnInstance(pWebRtcClientContext); + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } PVOID resolveStunIceServerIp(PVOID args) { + ENTERS(); UNUSED_PARAM(args); PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); BOOL locked = FALSE; @@ -876,6 +933,8 @@ PVOID resolveStunIceServerIp(PVOID args) } releaseHoldOnInstance(pWebRtcClientContext); DLOGD("Exiting from stun server IP resolution thread"); + + LEAVES(); return NULL; } @@ -902,6 +961,7 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection CHK_STATUS(timerQueueCreate(&pKvsPeerConnection->timerQueueHandle)); pKvsPeerConnection->peerConnection.version = PEER_CONNECTION_CURRENT_VERSION; + CHK_STATUS(generateJSONSafeString(pKvsPeerConnection->localIceUfrag, LOCAL_ICE_UFRAG_LEN)); CHK_STATUS(generateJSONSafeString(pKvsPeerConnection->localIcePwd, LOCAL_ICE_PWD_LEN)); CHK_STATUS(generateJSONSafeString(pKvsPeerConnection->localCNAME, LOCAL_CNAME_LEN)); @@ -924,7 +984,7 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection pKvsPeerConnection->peerConnectionObjLock = MUTEX_CREATE(FALSE); pKvsPeerConnection->connectionState = RTC_PEER_CONNECTION_STATE_NONE; pKvsPeerConnection->MTU = pConfiguration->kvsRtcConfiguration.maximumTransmissionUnit == 0 - ? DEFAULT_MTU_SIZE + ? DEFAULT_MTU_SIZE_BYTES : pConfiguration->kvsRtcConfiguration.maximumTransmissionUnit; ATOMIC_STORE_BOOL(&pKvsPeerConnection->sctpIsEnabled, FALSE); @@ -945,6 +1005,9 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection if (!pConfiguration->kvsRtcConfiguration.disableSenderSideBandwidthEstimation) { pKvsPeerConnection->twccLock = MUTEX_CREATE(TRUE); pKvsPeerConnection->pTwccManager = (PTwccManager) MEMCALLOC(1, SIZEOF(TwccManager)); + CHK(pKvsPeerConnection->pTwccManager != NULL, STATUS_NOT_ENOUGH_MEMORY); + CHK_STATUS(hashTableCreateWithParams(TWCC_HASH_TABLE_BUCKET_COUNT, TWCC_HASH_TABLE_BUCKET_LENGTH, + &pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable)); } *ppPeerConnection = (PRtcPeerConnection) pKvsPeerConnection; @@ -966,8 +1029,11 @@ STATUS createPeerConnection(PRtcConfiguration pConfiguration, PRtcPeerConnection STATUS freeHashEntry(UINT64 customData, PHashEntry pHashEntry) { + ENTERS(); UNUSED_PARAM(customData); MEMFREE((PVOID) pHashEntry->value); + + LEAVES(); return STATUS_SUCCESS; } @@ -978,10 +1044,12 @@ STATUS freePeerConnection(PRtcPeerConnection* ppPeerConnection) { ENTERS(); STATUS retStatus = STATUS_SUCCESS; - PKvsPeerConnection pKvsPeerConnection; + PKvsPeerConnection pKvsPeerConnection = NULL; PDoubleListNode pCurNode = NULL; UINT64 item = 0; UINT64 startTime; + UINT32 twccHashTableCount = 0; + BOOL twccLocked = FALSE; CHK(ppPeerConnection != NULL, STATUS_NULL_ARG); @@ -1050,17 +1118,34 @@ STATUS freePeerConnection(PRtcPeerConnection* ppPeerConnection) } if (pKvsPeerConnection->pTwccManager != NULL) { - if (IS_VALID_MUTEX_VALUE(pKvsPeerConnection->twccLock)) { - MUTEX_FREE(pKvsPeerConnection->twccLock); + MUTEX_LOCK(pKvsPeerConnection->twccLock); + twccLocked = TRUE; + + if (STATUS_SUCCEEDED(hashTableGetCount(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, &twccHashTableCount))) { + DLOGI("Number of TWCC info packets in memory: %d", twccHashTableCount); } - // twccManager.twccPackets contains sequence numbers of packets (as opposed to pointers to actual packets) - // we should not deallocate items but we do need to clear the queue - CHK_LOG_ERR(stackQueueClear(&pKvsPeerConnection->pTwccManager->twccPackets, FALSE)); + + CHK_LOG_ERR(hashTableIterateEntries(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, 0, freeHashEntry)); + CHK_LOG_ERR(hashTableFree(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable)); + SAFE_MEMFREE(pKvsPeerConnection->pTwccManager); } + // Incase the `RemoteSessionDescription` has not already been freed. + SAFE_MEMFREE(pKvsPeerConnection->pRemoteSessionDescription); + + if (IS_VALID_MUTEX_VALUE(pKvsPeerConnection->twccLock)) { + if (twccLocked) { + MUTEX_UNLOCK(pKvsPeerConnection->twccLock); + twccLocked = FALSE; + } + MUTEX_FREE(pKvsPeerConnection->twccLock); + pKvsPeerConnection->twccLock = INVALID_MUTEX_VALUE; + } + PROFILE_WITH_START_TIME_OBJ(startTime, pKvsPeerConnection->peerConnectionDiagnostics.freePeerConnectionTime, "Free peer connection"); SAFE_MEMFREE(*ppPeerConnection); + CleanUp: LEAVES(); @@ -1083,10 +1168,10 @@ STATUS peerConnectionOnIceCandidate(PRtcPeerConnection pRtcPeerConnection, UINT6 pKvsPeerConnection->onIceCandidateCustomData = customData; CleanUp: - if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1108,10 +1193,10 @@ STATUS peerConnectionOnDataChannel(PRtcPeerConnection pRtcPeerConnection, UINT64 pKvsPeerConnection->onDataChannelCustomData = customData; CleanUp: - if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1134,10 +1219,10 @@ STATUS peerConnectionOnConnectionStateChange(PRtcPeerConnection pRtcPeerConnecti pKvsPeerConnection->onConnectionStateChangeCustomData = customData; CleanUp: - if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1160,10 +1245,10 @@ STATUS peerConnectionOnSenderBandwidthEstimation(PRtcPeerConnection pRtcPeerConn pKvsPeerConnection->onSenderBandwidthEstimationCustomData = customData; CleanUp: - if (locked) { MUTEX_UNLOCK(pKvsPeerConnection->peerConnectionObjLock); } + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1187,15 +1272,15 @@ STATUS peerConnectionGetLocalDescription(PRtcPeerConnection pRtcPeerConnection, pRtcSessionDescriptionInit->type = SDP_TYPE_ANSWER; } - CHK_STATUS(populateSessionDescription(pKvsPeerConnection, &(pKvsPeerConnection->remoteSessionDescription), pSessionDescription)); + CHK_STATUS(populateSessionDescription(pKvsPeerConnection, pKvsPeerConnection->pRemoteSessionDescription, pSessionDescription)); CHK_STATUS(serializeSessionDescription(pSessionDescription, NULL, &serializeLen)); CHK(serializeLen <= MAX_SESSION_DESCRIPTION_INIT_SDP_LEN, STATUS_NOT_ENOUGH_MEMORY); CHK_STATUS(serializeSessionDescription(pSessionDescription, pRtcSessionDescriptionInit->sdp, &serializeLen)); CleanUp: - SAFE_MEMFREE(pSessionDescription); + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1211,11 +1296,12 @@ STATUS peerConnectionGetCurrentLocalDescription(PRtcPeerConnection pRtcPeerConne CHK(pRtcPeerConnection != NULL && pRtcSessionDescriptionInit != NULL, STATUS_NULL_ARG); // do nothing if remote session description hasn't been received - CHK(pKvsPeerConnection->remoteSessionDescription.sessionName[0] != '\0', retStatus); + CHK(pKvsPeerConnection->pRemoteSessionDescription != NULL, STATUS_SUCCESS); - CHK(NULL != (pSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription))), STATUS_NOT_ENOUGH_MEMORY); + pSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription)); + CHK(pSessionDescription != NULL, STATUS_NOT_ENOUGH_MEMORY); - CHK_STATUS(populateSessionDescription(pKvsPeerConnection, &(pKvsPeerConnection->remoteSessionDescription), pSessionDescription)); + CHK_STATUS(populateSessionDescription(pKvsPeerConnection, pKvsPeerConnection->pRemoteSessionDescription, pSessionDescription)); CHK_STATUS(serializeSessionDescription(pSessionDescription, NULL, &serializeLen)); CHK(serializeLen <= MAX_SESSION_DESCRIPTION_INIT_SDP_LEN, STATUS_NOT_ENOUGH_MEMORY); @@ -1223,6 +1309,7 @@ STATUS peerConnectionGetCurrentLocalDescription(PRtcPeerConnection pRtcPeerConne CHK_STATUS(serializeSessionDescription(pSessionDescription, pRtcSessionDescriptionInit->sdp, &serializeLen)); CleanUp: + CHK_LOG_ERR(retStatus); SAFE_MEMFREE(pSessionDescription); @@ -1236,13 +1323,18 @@ STATUS peerConnectionGetCurrentLocalDescription(PRtcPeerConnection pRtcPeerConne */ UINT32 parseExtId(PCHAR extmapValue) { + ENTERS(); UINT32 extid = 0; if (extmapValue == NULL && STRCHR(extmapValue, ' ') == NULL) { + LEAVES(); return 0; } if (STATUS_FAILED(STRTOUI32(extmapValue, STRCHR(extmapValue, ' '), 10, &extid))) { + LEAVES(); return 0; } + + LEAVES(); return extid; } @@ -1252,14 +1344,18 @@ STATUS setRemoteDescription(PRtcPeerConnection pPeerConnection, PRtcSessionDescr STATUS retStatus = STATUS_SUCCESS; PCHAR remoteIceUfrag = NULL, remoteIcePwd = NULL; UINT32 i, j; + PSessionDescription pSessionDescription; - CHK(pPeerConnection != NULL, STATUS_NULL_ARG); PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; - PSessionDescription pSessionDescription = &pKvsPeerConnection->remoteSessionDescription; - CHK(pSessionDescriptionInit != NULL, STATUS_NULL_ARG); + CHK(pPeerConnection != NULL && pSessionDescriptionInit != NULL, STATUS_NULL_ARG); + + // In master mode, this should be freed once `createAnswer` is invoked for the session. + // In viewer mode, this should be freed once `setRemoteDescription` is completed for the session. + pKvsPeerConnection->pRemoteSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription)); + pSessionDescription = pKvsPeerConnection->pRemoteSessionDescription; + CHK(pSessionDescription != NULL, STATUS_NOT_ENOUGH_MEMORY); - MEMSET(pSessionDescription, 0x00, SIZEOF(SessionDescription)); pKvsPeerConnection->dtlsIsServer = FALSE; /* Assume cant trickle at first */ NULLABLE_SET_VALUE(pKvsPeerConnection->canTrickleIce, FALSE); @@ -1347,6 +1443,10 @@ STATUS setRemoteDescription(PRtcPeerConnection pPeerConnection, PRtcSessionDescr } CleanUp: + if (pKvsPeerConnection != NULL && pKvsPeerConnection->isOffer) { + SAFE_MEMFREE(pKvsPeerConnection->pRemoteSessionDescription); + } + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1364,7 +1464,9 @@ STATUS createOffer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIni CHK(pKvsPeerConnection != NULL && pSessionDescriptionInit != NULL, STATUS_NULL_ARG); // SessionDescription is large enough structure to not define on the stack and use heap memory - CHK(NULL != (pSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription))), STATUS_NOT_ENOUGH_MEMORY); + pSessionDescription = (PSessionDescription) MEMCALLOC(1, SIZEOF(SessionDescription)); + CHK(pSessionDescription != NULL, STATUS_NOT_ENOUGH_MEMORY); + pSessionDescriptionInit->type = SDP_TYPE_OFFER; pKvsPeerConnection->isOffer = TRUE; if (pSessionDescriptionInit->useTrickleIce) { @@ -1376,7 +1478,7 @@ STATUS createOffer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIni #endif CHK_STATUS(setPayloadTypesForOffer(pKvsPeerConnection->pCodecTable)); - CHK_STATUS(populateSessionDescription(pKvsPeerConnection, &(pKvsPeerConnection->remoteSessionDescription), pSessionDescription)); + CHK_STATUS(populateSessionDescription(pKvsPeerConnection, pKvsPeerConnection->pRemoteSessionDescription, pSessionDescription)); CHK_STATUS(serializeSessionDescription(pSessionDescription, NULL, &serializeLen)); CHK(serializeLen <= MAX_SESSION_DESCRIPTION_INIT_SDP_LEN, STATUS_NOT_ENOUGH_MEMORY); CHK_STATUS(serializeSessionDescription(pSessionDescription, pSessionDescriptionInit->sdp, &serializeLen)); @@ -1385,8 +1487,8 @@ STATUS createOffer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIni DLOGD("LOCAL_SDP:%s", pSessionDescriptionInit->sdp); } CleanUp: - SAFE_MEMFREE(pSessionDescription); + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1400,7 +1502,7 @@ STATUS createAnswer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIn PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; CHK(pKvsPeerConnection != NULL && pSessionDescriptionInit != NULL, STATUS_NULL_ARG); - CHK(pKvsPeerConnection->remoteSessionDescription.sessionName[0] != '\0', STATUS_PEERCONNECTION_CREATE_ANSWER_WITHOUT_REMOTE_DESCRIPTION); + CHK(pKvsPeerConnection->pRemoteSessionDescription != NULL, STATUS_PEERCONNECTION_CREATE_ANSWER_WITHOUT_REMOTE_DESCRIPTION); pSessionDescriptionInit->type = SDP_TYPE_ANSWER; pKvsPeerConnection->isOffer = FALSE; @@ -1410,7 +1512,12 @@ STATUS createAnswer(PRtcPeerConnection pPeerConnection, PRtcSessionDescriptionIn if (NULL != GETENV(DEBUG_LOG_SDP)) { DLOGD("LOCAL_SDP:%s", pSessionDescriptionInit->sdp); } + + // Once answer is created, remote SDP is not needed anymore. We also clear only if + // answer SDP is successfully created + SAFE_MEMFREE(pKvsPeerConnection->pRemoteSessionDescription); CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1427,11 +1534,25 @@ STATUS setLocalDescription(PRtcPeerConnection pPeerConnection, PRtcSessionDescri CHK_STATUS(iceAgentStartGathering(pKvsPeerConnection->pIceAgent)); CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; } +STATUS configureTransceiverRollingBuffer(PRtcRtpTransceiver pRtcRtpTransceiver, PRtcMediaStreamTrack pRtcMediaStreamTrack, + DOUBLE rollingBufferDurationSec, DOUBLE rollingBufferBitratebps) +{ + STATUS retStatus = STATUS_SUCCESS; + PKvsRtpTransceiver pKvsRtpTransceiver = (PKvsRtpTransceiver) pRtcRtpTransceiver; + CHK_WARN(pKvsRtpTransceiver != NULL || pRtcMediaStreamTrack != NULL, STATUS_NULL_ARG, + "Transceiver is not created. This needs to be invoked after addTransceiver is invoked"); + + CHK_STATUS(setUpRollingBufferConfigInternal(pKvsRtpTransceiver, pRtcMediaStreamTrack, rollingBufferDurationSec, rollingBufferBitratebps)); +CleanUp: + return retStatus; +} + STATUS addTransceiver(PRtcPeerConnection pPeerConnection, PRtcMediaStreamTrack pRtcMediaStreamTrack, PRtcRtpTransceiverInit pRtcRtpTransceiverInit, PRtcRtpTransceiver* ppRtcRtpTransceiver) { @@ -1446,12 +1567,13 @@ STATUS addTransceiver(PRtcPeerConnection pPeerConnection, PRtcMediaStreamTrack p UINT32 ssrc = (UINT32) RAND(), rtxSsrc = (UINT32) RAND(); RTC_RTP_TRANSCEIVER_DIRECTION direction = RTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; RtcMediaStreamTrack videoTrack; + + CHK(pKvsPeerConnection != NULL, STATUS_NULL_ARG); + if (pRtcRtpTransceiverInit != NULL) { direction = pRtcRtpTransceiverInit->direction; } - CHK(pKvsPeerConnection != NULL, STATUS_NULL_ARG); - if (direction == RTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY && pRtcMediaStreamTrack == NULL) { MEMSET(&videoTrack, 0x00, SIZEOF(RtcMediaStreamTrack)); videoTrack.kind = MEDIA_STREAM_TRACK_KIND_VIDEO; @@ -1459,6 +1581,8 @@ STATUS addTransceiver(PRtcPeerConnection pPeerConnection, PRtcMediaStreamTrack p STRCPY(videoTrack.streamId, "myKvsVideoStream"); STRCPY(videoTrack.trackId, "myVideoTrack"); pRtcMediaStreamTrack = &videoTrack; + // rollingBufferDurationSec will be DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS + // rollingBufferBitratebps will be DEFAULT_EXPECTED_VIDEO_BIT_RATE } switch (pRtcMediaStreamTrack->codec) { @@ -1510,15 +1634,16 @@ STATUS addTransceiver(PRtcPeerConnection pPeerConnection, PRtcMediaStreamTrack p pKvsRtpTransceiver = NULL; CleanUp: - if (pJitterBuffer != NULL) { - freeJitterBuffer(&pJitterBuffer); + CHK_LOG_ERR(freeJitterBuffer(&pJitterBuffer)); } if (pKvsRtpTransceiver != NULL) { - freeKvsRtpTransceiver(&pKvsRtpTransceiver); + CHK_LOG_ERR(freeKvsRtpTransceiver(&pKvsRtpTransceiver)); } + CHK_LOG_ERR(retStatus); + LEAVES(); return retStatus; } @@ -1547,6 +1672,7 @@ STATUS addSupportedCodec(PRtcPeerConnection pPeerConnection, RTC_CODEC rtcCodec) CHK_STATUS(hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, 0)); } CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1563,6 +1689,7 @@ STATUS addIceCandidate(PRtcPeerConnection pPeerConnection, PCHAR pIceCandidate) iceAgentAddRemoteCandidate(pKvsPeerConnection->pIceAgent, pIceCandidate); CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1584,7 +1711,6 @@ STATUS restartIce(PRtcPeerConnection pPeerConnection) CHK_STATUS(iceAgentRestart(pKvsPeerConnection->pIceAgent, pKvsPeerConnection->localIceUfrag, pKvsPeerConnection->localIcePwd)); CleanUp: - CHK_LOG_ERR(retStatus); LEAVES(); @@ -1603,7 +1729,6 @@ STATUS closePeerConnection(PRtcPeerConnection pPeerConnection) PROFILE_WITH_START_TIME_OBJ(startTime, pKvsPeerConnection->peerConnectionDiagnostics.closePeerConnectionTime, "Close peer connection"); CleanUp: - CHK_LOG_ERR(retStatus); LEAVES(); @@ -1612,6 +1737,7 @@ STATUS closePeerConnection(PRtcPeerConnection pPeerConnection) NullableBool canTrickleIceCandidates(PRtcPeerConnection pPeerConnection) { + ENTERS(); NullableBool canTrickle = {FALSE, FALSE}; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; STATUS retStatus = STATUS_SUCCESS; @@ -1622,8 +1748,9 @@ NullableBool canTrickleIceCandidates(PRtcPeerConnection pPeerConnection) } CleanUp: - CHK_LOG_ERR(retStatus); + + LEAVES(); return canTrickle; } @@ -1656,6 +1783,7 @@ STATUS initKvsWebRtc(VOID) ATOMIC_STORE_BOOL(&gKvsWebRtcInitialized, TRUE); CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -1663,6 +1791,7 @@ STATUS initKvsWebRtc(VOID) STATUS cleanupWebRtcClientInstance() { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; // Stun object cleanup @@ -1700,6 +1829,9 @@ STATUS cleanupWebRtcClientInstance() DLOGI("Destroyed WebRtc client context"); CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } @@ -1723,52 +1855,99 @@ STATUS deinitKvsWebRtc(VOID) #endif ATOMIC_STORE_BOOL(&gKvsWebRtcInitialized, FALSE); CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; } -STATUS twccManagerOnPacketSent(PKvsPeerConnection pc, PRtpPacket pRtpPacket) +// Not thread safe. Ensure this function is invoked in a guarded section +static STATUS twccRollingWindowDeletion(PKvsPeerConnection pKvsPeerConnection, PRtpPacket pRtpPacket, UINT16 endingSeqNum) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + UINT16 updatedSeqNum = 0; + PTwccRtpPacketInfo tempTwccRtpPktInfo = NULL; + UINT64 ageOfOldest = 0, firstRtpTime = 0; + UINT64 twccPacketValue = 0; + BOOL isCheckComplete = FALSE; + + CHK(pKvsPeerConnection != NULL && pRtpPacket != NULL && pKvsPeerConnection->pTwccManager != NULL, STATUS_NULL_ARG); + + updatedSeqNum = pKvsPeerConnection->pTwccManager->firstSeqNumInRollingWindow; + do { + // If the seqNum is not present in the hash table, it is ok. We move on to the next + if (STATUS_SUCCEEDED(hashTableGet(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, updatedSeqNum, &twccPacketValue))) { + tempTwccRtpPktInfo = (PTwccRtpPacketInfo) twccPacketValue; + if (tempTwccRtpPktInfo != NULL) { + firstRtpTime = tempTwccRtpPktInfo->localTimeKvs; + // Would be the case if the timestamps are not monotonically increasing. + if (pRtpPacket->sentTime >= firstRtpTime) { + ageOfOldest = pRtpPacket->sentTime - firstRtpTime; + if (ageOfOldest > TWCC_ESTIMATOR_TIME_WINDOW) { + // If the seqNum is not present in the hash table, move on. However, this case should not happen + // given this function is holding the lock and tempTwccRtpPktInfo is populated because it exists + if (STATUS_SUCCEEDED(hashTableRemove(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, updatedSeqNum))) { + SAFE_MEMFREE(tempTwccRtpPktInfo); + } + updatedSeqNum++; + } else { + isCheckComplete = TRUE; + } + } else { + // Move to the next seqNum to check if we can remove the next one atleast + DLOGV("Non-monotonic timestamp detected for RTP packet seqNum %d [ts: %" PRIu64 ". Current RTP packets' ts: %" PRIu64, + updatedSeqNum, firstRtpTime, pRtpPacket->sentTime); + updatedSeqNum++; + } + } else { + CHK_STATUS(hashTableRemove(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, updatedSeqNum)); + updatedSeqNum++; + } + } else { + updatedSeqNum++; + } + // reset before next iteration + tempTwccRtpPktInfo = NULL; + } while (!isCheckComplete && updatedSeqNum != (endingSeqNum + 1)); + + // Update regardless. The loop checks until current RTP packets seq number irrespective of the failure + pKvsPeerConnection->pTwccManager->firstSeqNumInRollingWindow = updatedSeqNum; +CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); + return retStatus; +} + +STATUS twccManagerOnPacketSent(PKvsPeerConnection pKvsPeerConnection, PRtpPacket pRtpPacket) { ENTERS(); STATUS retStatus = STATUS_SUCCESS; BOOL locked = FALSE; - UINT64 sn = 0; - UINT16 seqNum; - BOOL isEmpty = FALSE; - INT64 firstTimeKvs, lastLocalTimeKvs, ageOfOldest; - CHK(pc != NULL && pRtpPacket != NULL, STATUS_NULL_ARG); - CHK(pc->onSenderBandwidthEstimation != NULL && pc->pTwccManager != NULL, STATUS_SUCCESS); + UINT16 seqNum = 0; + PTwccRtpPacketInfo pTwccRtpPktInfo = NULL; + + CHK(pKvsPeerConnection != NULL && pRtpPacket != NULL, STATUS_NULL_ARG); + CHK(pKvsPeerConnection->onSenderBandwidthEstimation != NULL && pKvsPeerConnection->pTwccManager != NULL, STATUS_SUCCESS); CHK(TWCC_EXT_PROFILE == pRtpPacket->header.extensionProfile, STATUS_SUCCESS); - MUTEX_LOCK(pc->twccLock); + MUTEX_LOCK(pKvsPeerConnection->twccLock); locked = TRUE; + CHK((pTwccRtpPktInfo = MEMCALLOC(1, SIZEOF(TwccRtpPacketInfo))) != NULL, STATUS_NOT_ENOUGH_MEMORY); + + pTwccRtpPktInfo->packetSize = pRtpPacket->payloadLength; + pTwccRtpPktInfo->localTimeKvs = pRtpPacket->sentTime; + pTwccRtpPktInfo->remoteTimeKvs = TWCC_PACKET_LOST_TIME; seqNum = TWCC_SEQNUM(pRtpPacket->header.extensionPayload); - CHK_STATUS(stackQueueEnqueue(&pc->pTwccManager->twccPackets, seqNum)); - pc->pTwccManager->twccPacketBySeqNum[seqNum].seqNum = seqNum; - pc->pTwccManager->twccPacketBySeqNum[seqNum].packetSize = pRtpPacket->payloadLength; - pc->pTwccManager->twccPacketBySeqNum[seqNum].localTimeKvs = pRtpPacket->sentTime; - pc->pTwccManager->twccPacketBySeqNum[seqNum].remoteTimeKvs = TWCC_PACKET_LOST_TIME; - pc->pTwccManager->lastLocalTimeKvs = pRtpPacket->sentTime; - - // cleanup queue until it contains up to 2 seconds of sent packets - do { - CHK_STATUS(stackQueuePeek(&pc->pTwccManager->twccPackets, &sn)); - firstTimeKvs = pc->pTwccManager->twccPacketBySeqNum[(UINT16) sn].localTimeKvs; - lastLocalTimeKvs = pRtpPacket->sentTime; - ageOfOldest = lastLocalTimeKvs - firstTimeKvs; - if (ageOfOldest > TWCC_ESTIMATOR_TIME_WINDOW) { - CHK_STATUS(stackQueueDequeue(&pc->pTwccManager->twccPackets, &sn)); - CHK_STATUS(stackQueueIsEmpty(&pc->pTwccManager->twccPackets, &isEmpty)); - } else { - break; - } - } while (!isEmpty); + CHK_STATUS(hashTableUpsert(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, seqNum, (UINT64) pTwccRtpPktInfo)); + // Ensure twccRollingWindowDeletion is run in a guarded section + CHK_STATUS(twccRollingWindowDeletion(pKvsPeerConnection, pRtpPacket, seqNum)); CleanUp: if (locked) { - MUTEX_UNLOCK(pc->twccLock); + MUTEX_UNLOCK(pKvsPeerConnection->twccLock); } CHK_LOG_ERR(retStatus); @@ -1778,6 +1957,7 @@ STATUS twccManagerOnPacketSent(PKvsPeerConnection pc, PRtpPacket pRtpPacket) STATUS peerConnectionGetMetrics(PRtcPeerConnection pPeerConnection, PPeerConnectionMetrics pPeerConnectionMetrics) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); @@ -1805,11 +1985,15 @@ STATUS peerConnectionGetMetrics(PRtcPeerConnection pPeerConnection, PPeerConnect pPeerConnectionMetrics->peerConnectionStats.freePeerConnectionTime = pKvsPeerConnection->peerConnectionDiagnostics.freePeerConnectionTime; CleanUp: releaseHoldOnInstance(pWebRtcClientContext); + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } STATUS iceAgentGetMetrics(PRtcPeerConnection pPeerConnection, PKvsIceAgentMetrics pKvsIceAgentMetrics) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; PKvsPeerConnection pKvsPeerConnection = (PKvsPeerConnection) pPeerConnection; CHK(pKvsPeerConnection != NULL && pKvsIceAgentMetrics != NULL, STATUS_NULL_ARG); @@ -1820,5 +2004,8 @@ STATUS iceAgentGetMetrics(PRtcPeerConnection pPeerConnection, PKvsIceAgentMetric } CHK_STATUS(getIceAgentStats(pKvsPeerConnection->pIceAgent, pKvsIceAgentMetrics)); CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); return retStatus; } diff --git a/src/source/PeerConnection/PeerConnection.h b/src/source/PeerConnection/PeerConnection.h index 8db3674caa..7311ec9794 100644 --- a/src/source/PeerConnection/PeerConnection.h +++ b/src/source/PeerConnection/PeerConnection.h @@ -31,6 +31,8 @@ extern "C" { #define CODEC_HASH_TABLE_BUCKET_LENGTH 2 #define RTX_HASH_TABLE_BUCKET_COUNT 50 #define RTX_HASH_TABLE_BUCKET_LENGTH 2 +#define TWCC_HASH_TABLE_BUCKET_COUNT 100 +#define TWCC_HASH_TABLE_BUCKET_LENGTH 2 #define DATA_CHANNEL_HASH_TABLE_BUCKET_COUNT 200 #define DATA_CHANNEL_HASH_TABLE_BUCKET_LENGTH 2 @@ -47,17 +49,16 @@ typedef enum { } RTX_CODEC; typedef struct { - UINT16 seqNum; - UINT16 packetSize; UINT64 localTimeKvs; UINT64 remoteTimeKvs; -} TwccPacket, *PTwccPacket; + UINT32 packetSize; +} TwccRtpPacketInfo, *PTwccRtpPacketInfo; typedef struct { - StackQueue twccPackets; - TwccPacket twccPacketBySeqNum[65536]; // twccPacketBySeqNum takes about 1.2MB of RAM but provides great cache locality - UINT64 lastLocalTimeKvs; - UINT16 lastReportedSeqNum; + PHashTable pTwccRtpPktInfosHashTable; // Hash table of [seqNum, PTwccPacket] + UINT16 firstSeqNumInRollingWindow; // To monitor the last deleted packet in the rolling window + UINT16 lastReportedSeqNum; // To monitor the last packet's seqNum in the TWCC response + UINT16 prevReportedBaseSeqNum; // To monitor the base seqNum in the TWCC response } TwccManager, *PTwccManager; typedef struct { @@ -70,7 +71,7 @@ typedef struct { typedef struct { RtcPeerConnection peerConnection; - // UINT32 padding padding makes transportWideSequenceNumber 64bit aligned + // UINT32 padding makes transportWideSequenceNumber 64bit aligned // we put atomics at the top of structs because customers application could set the packing to 0 // in which case any atomic operations would result in bus errors if there is a misalignment // for more see https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/pull/987#discussion_r534432907 @@ -86,7 +87,7 @@ typedef struct { PSctpSession pSctpSession; - SessionDescription remoteSessionDescription; + PSessionDescription pRemoteSessionDescription; PDoubleList pTransceivers; PDoubleList pFakeTransceivers; PDoubleList pAnswerTransceivers; @@ -105,6 +106,8 @@ typedef struct { MUTEX peerConnectionObjLock; + // If the local session description is an SDP offer. + // (TRUE = viewer mode, FALSE = master mode) BOOL isOffer; TIMER_QUEUE_HANDLE timerQueueHandle; @@ -181,6 +184,7 @@ VOID onSctpSessionDataChannelOpen(UINT64, UINT32, PBYTE, UINT32); STATUS sendPacketToRtpReceiver(PKvsPeerConnection, PBYTE, UINT32); STATUS changePeerConnectionState(PKvsPeerConnection, RTC_PEER_CONNECTION_STATE); STATUS twccManagerOnPacketSent(PKvsPeerConnection, PRtpPacket); +UINT32 parseExtId(PCHAR); // visible for testing only VOID onIceConnectionStateChange(UINT64, UINT64); diff --git a/src/source/PeerConnection/Rtcp.c b/src/source/PeerConnection/Rtcp.c index 0279299467..f4c8147c49 100644 --- a/src/source/PeerConnection/Rtcp.c +++ b/src/source/PeerConnection/Rtcp.c @@ -185,11 +185,13 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) UINT32 statuses; UINT32 i; UINT64 referenceTime; + PTwccRtpPacketInfo pTwccPacket = NULL; + UINT64 twccPktValue = 0; CHK(pTwccManager != NULL && pRtcpPacket != NULL, STATUS_NULL_ARG); baseSeqNum = getUnalignedInt16BigEndian(pRtcpPacket->payload + 8); + pTwccManager->prevReportedBaseSeqNum = baseSeqNum; packetStatusCount = TWCC_PACKET_STATUS_COUNT(pRtcpPacket->payload); - referenceTime = (pRtcpPacket->payload[12] << 16) | (pRtcpPacket->payload[13] << 8) | (pRtcpPacket->payload[14] & 0xff); referenceTime = KVS_CONVERT_TIMESCALE(referenceTime * 64, MILLISECONDS_PER_SECOND, HUNDREDS_OF_NANOS_IN_A_SECOND); // TODO: handle lost twcc report packets @@ -205,7 +207,6 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) } chunkOffset += TWCC_FB_PACKETCHUNK_SIZE; } - recvOffset = chunkOffset; chunkOffset = 16; packetSeqNum = baseSeqNum; @@ -227,7 +228,14 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) break; case TWCC_STATUS_SYMBOL_NOTRECEIVED: DLOGS("runLength packetSeqNum %u not received %lu", packetSeqNum, referenceTime); - pTwccManager->twccPacketBySeqNum[packetSeqNum].remoteTimeKvs = TWCC_PACKET_LOST_TIME; + // If it does not exist it means the packet was already visited + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + pTwccPacket->remoteTimeKvs = TWCC_PACKET_LOST_TIME; + CHK_STATUS(hashTableUpsert(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, (UINT64) pTwccPacket)); + } + } pTwccManager->lastReportedSeqNum = packetSeqNum; break; default: @@ -236,11 +244,21 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) if (recvDelta != MIN_INT16) { referenceTime += KVS_CONVERT_TIMESCALE(recvDelta, TWCC_TICKS_PER_SECOND, HUNDREDS_OF_NANOS_IN_A_SECOND); DLOGS("runLength packetSeqNum %u received %lu", packetSeqNum, referenceTime); - pTwccManager->twccPacketBySeqNum[packetSeqNum].remoteTimeKvs = referenceTime; + + // If it does not exist it means the packet was already visited + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + pTwccPacket->remoteTimeKvs = referenceTime; + CHK_STATUS(hashTableUpsert(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, (UINT64) pTwccPacket)); + } + } pTwccManager->lastReportedSeqNum = packetSeqNum; } packetSeqNum++; packetsRemaining--; + // Reset to NULL before next iteration + pTwccPacket = NULL; } } else { statuses = MIN(TWCC_STATUSVECTOR_COUNT(packetChunk), packetsRemaining); @@ -259,7 +277,14 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) break; case TWCC_STATUS_SYMBOL_NOTRECEIVED: DLOGS("statusVector packetSeqNum %u not received %lu", packetSeqNum, referenceTime); - pTwccManager->twccPacketBySeqNum[packetSeqNum].remoteTimeKvs = TWCC_PACKET_LOST_TIME; + // If it does not exist it means the packet was already visited + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + pTwccPacket->remoteTimeKvs = TWCC_PACKET_LOST_TIME; + CHK_STATUS(hashTableUpsert(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, (UINT64) pTwccPacket)); + } + } pTwccManager->lastReportedSeqNum = packetSeqNum; break; default: @@ -268,15 +293,104 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) if (recvDelta != MIN_INT16) { referenceTime += KVS_CONVERT_TIMESCALE(recvDelta, TWCC_TICKS_PER_SECOND, HUNDREDS_OF_NANOS_IN_A_SECOND); DLOGS("statusVector packetSeqNum %u received %lu", packetSeqNum, referenceTime); - pTwccManager->twccPacketBySeqNum[packetSeqNum].remoteTimeKvs = referenceTime; + // If it does not exist it means the packet was already visited + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + pTwccPacket->remoteTimeKvs = referenceTime; + CHK_STATUS(hashTableUpsert(pTwccManager->pTwccRtpPktInfosHashTable, packetSeqNum, (UINT64) pTwccPacket)); + } + } pTwccManager->lastReportedSeqNum = packetSeqNum; } packetSeqNum++; packetsRemaining--; + // Reset to NULL before next iteration + pTwccPacket = NULL; } } chunkOffset += TWCC_FB_PACKETCHUNK_SIZE; } + DLOGV("Checking seqNum %d to %d of TWCC reports", baseSeqNum, pTwccManager->lastReportedSeqNum); +CleanUp: + CHK_LOG_ERR(retStatus); + return retStatus; +} + +STATUS updateTwccHashTable(PTwccManager pTwccManager, PINT64 duration, PUINT64 receivedBytes, PUINT64 receivedPackets, PUINT64 sentBytes, + PUINT64 sentPackets) +{ + STATUS retStatus = STATUS_SUCCESS; + UINT64 localStartTimeKvs, localEndTimeKvs = 0; + UINT16 baseSeqNum = 0; + BOOL localStartTimeRecorded = FALSE; + UINT64 twccPktValue = 0; + PTwccRtpPacketInfo pTwccPacket = NULL; + UINT16 seqNum = 0; + + CHK(pTwccManager != NULL && duration != NULL && receivedBytes != NULL && receivedPackets != NULL && sentBytes != NULL && sentPackets != NULL, + STATUS_NULL_ARG); + + *duration = 0; + *receivedBytes = 0; + *receivedPackets = 0; + *sentBytes = 0; + *sentPackets = 0; + + baseSeqNum = pTwccManager->prevReportedBaseSeqNum; + + // Use != instead to cover the case where the group of sequence numbers being checked + // are trending towards MAX_UINT16 and rolling over to 0+, example range [65534, 10] + // We also check for twcc->lastReportedSeqNum + 1 to include the last seq number in the + // report. Without this, we do not check for the seqNum that could cause it to not be cleared + // from memory + for (seqNum = baseSeqNum; seqNum != (pTwccManager->lastReportedSeqNum + 1); seqNum++) { + if (!localStartTimeRecorded) { + // This could happen if the prev packet was deleted as part of rolling window or if there + // is an overlap of RTP packet statuses between TWCC packets. This could also fail if it is + // the first ever packet (seqNum 0) + if (hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, seqNum - 1, &twccPktValue) == STATUS_HASH_KEY_NOT_PRESENT) { + localStartTimeKvs = TWCC_PACKET_UNITIALIZED_TIME; + } else { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + localStartTimeKvs = pTwccPacket->localTimeKvs; + localStartTimeRecorded = TRUE; + } + } + if (localStartTimeKvs == TWCC_PACKET_UNITIALIZED_TIME) { + // time not yet set. If prev seqNum was deleted + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, seqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + localStartTimeKvs = pTwccPacket->localTimeKvs; + localStartTimeRecorded = TRUE; + } + } + } + } + + // The time it would not succeed is if there is an overlap in the RTP packet status between the TWCC + // packets + if (STATUS_SUCCEEDED(hashTableGet(pTwccManager->pTwccRtpPktInfosHashTable, seqNum, &twccPktValue))) { + pTwccPacket = (PTwccRtpPacketInfo) twccPktValue; + if (pTwccPacket != NULL) { + localEndTimeKvs = pTwccPacket->localTimeKvs; + *duration = localEndTimeKvs - localStartTimeKvs; + *sentBytes += pTwccPacket->packetSize; + (*sentPackets)++; + if (pTwccPacket->remoteTimeKvs != TWCC_PACKET_LOST_TIME) { + *receivedBytes += pTwccPacket->packetSize; + (*receivedPackets)++; + if (STATUS_SUCCEEDED(hashTableRemove(pTwccManager->pTwccRtpPktInfosHashTable, seqNum))) { + SAFE_MEMFREE(pTwccPacket); + } + } + } else { + CHK_STATUS(hashTableRemove(pTwccManager->pTwccRtpPktInfosHashTable, seqNum)); + } + } + } CleanUp: CHK_LOG_ERR(retStatus); @@ -286,46 +400,21 @@ STATUS parseRtcpTwccPacket(PRtcpPacket pRtcpPacket, PTwccManager pTwccManager) STATUS onRtcpTwccPacket(PRtcpPacket pRtcpPacket, PKvsPeerConnection pKvsPeerConnection) { STATUS retStatus = STATUS_SUCCESS; - PTwccManager twcc; + PTwccManager pTwccManager = NULL; BOOL locked = FALSE; - BOOL empty = TRUE; - UINT64 sn = 0; - INT64 ageOfOldestPacket; - UINT64 localStartTimeKvs, localEndTimeKvs; UINT64 sentBytes = 0, receivedBytes = 0; UINT64 sentPackets = 0, receivedPackets = 0; INT64 duration = 0; - UINT16 seqNum; - PTwccPacket twccPacket; CHK(pKvsPeerConnection != NULL && pRtcpPacket != NULL, STATUS_NULL_ARG); - CHK(pKvsPeerConnection->onSenderBandwidthEstimation != NULL && pKvsPeerConnection->pTwccManager != NULL, STATUS_SUCCESS); + CHK(pKvsPeerConnection->pTwccManager != NULL && pKvsPeerConnection->onSenderBandwidthEstimation != NULL, STATUS_SUCCESS); MUTEX_LOCK(pKvsPeerConnection->twccLock); locked = TRUE; - twcc = pKvsPeerConnection->pTwccManager; - CHK_STATUS(parseRtcpTwccPacket(pRtcpPacket, twcc)); - CHK_STATUS(stackQueueIsEmpty(&twcc->twccPackets, &empty)); - CHK(!empty, STATUS_SUCCESS); - CHK_STATUS(stackQueuePeek(&twcc->twccPackets, &sn)); - ageOfOldestPacket = twcc->lastLocalTimeKvs - twcc->twccPacketBySeqNum[(UINT16) sn].localTimeKvs; - CHK(ageOfOldestPacket > TWCC_ESTIMATOR_TIME_WINDOW / 2, STATUS_SUCCESS); - localStartTimeKvs = twcc->twccPacketBySeqNum[(UINT16) (sn - 1)].localTimeKvs; - if (localStartTimeKvs == TWCC_PACKET_UNITIALIZED_TIME) { - // time not yet set (only happens for first rtp packet) - localStartTimeKvs = twcc->twccPacketBySeqNum[(UINT16) sn].localTimeKvs; - } - for (seqNum = sn; seqNum != twcc->lastReportedSeqNum; seqNum++) { - twccPacket = &twcc->twccPacketBySeqNum[seqNum]; - localEndTimeKvs = twccPacket->localTimeKvs; - duration = localEndTimeKvs - localStartTimeKvs; - sentBytes += twccPacket->packetSize; - sentPackets++; - if (twccPacket->remoteTimeKvs != TWCC_PACKET_LOST_TIME) { - receivedBytes += twccPacket->packetSize; - receivedPackets++; - } - } + pTwccManager = pKvsPeerConnection->pTwccManager; + CHK_STATUS(parseRtcpTwccPacket(pRtcpPacket, pTwccManager)); + + updateTwccHashTable(pTwccManager, &duration, &receivedBytes, &receivedPackets, &sentBytes, &sentPackets); if (duration > 0) { MUTEX_UNLOCK(pKvsPeerConnection->twccLock); diff --git a/src/source/PeerConnection/Rtcp.h b/src/source/PeerConnection/Rtcp.h index 229cebbe3f..ae2e505e02 100644 --- a/src/source/PeerConnection/Rtcp.h +++ b/src/source/PeerConnection/Rtcp.h @@ -12,6 +12,7 @@ STATUS onRtcpRembPacket(PRtcpPacket, PKvsPeerConnection); STATUS onRtcpPLIPacket(PRtcpPacket, PKvsPeerConnection); STATUS parseRtcpTwccPacket(PRtcpPacket, PTwccManager); STATUS onRtcpTwccPacket(PRtcpPacket, PKvsPeerConnection); +STATUS updateTwccHashTable(PTwccManager, PINT64, PUINT64, PUINT64, PUINT64, PUINT64); // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 // Deltas are represented as multiples of 250us: diff --git a/src/source/PeerConnection/Rtp.c b/src/source/PeerConnection/Rtp.c index 434a5564bf..a098140fc6 100644 --- a/src/source/PeerConnection/Rtp.c +++ b/src/source/PeerConnection/Rtp.c @@ -49,12 +49,87 @@ STATUS createKvsRtpTransceiver(RTC_RTP_TRANSCEIVER_DIRECTION direction, PKvsPeer return retStatus; } +STATUS setUpRollingBufferConfigInternal(PKvsRtpTransceiver pKvsRtpTransceiver, PRtcMediaStreamTrack pRtcMediaStreamTrack, + DOUBLE rollingBufferDurationSec, DOUBLE rollingBufferBitratebps) +{ + STATUS retStatus = STATUS_SUCCESS; + CHK_ERR(pKvsRtpTransceiver != NULL || pRtcMediaStreamTrack != NULL, STATUS_NULL_ARG, + "Media track and transceiver not set. Make sure to set up transceiver with addTransceiver()"); + + // Do not attempt to alloc for a new RollingBufferConfig if one is still not freed. + if (pKvsRtpTransceiver->pRollingBufferConfig == NULL) { + pKvsRtpTransceiver->pRollingBufferConfig = (PRollingBufferConfig) MEMCALLOC(1, SIZEOF(RollingBufferConfig)); + CHK(pKvsRtpTransceiver->pRollingBufferConfig != NULL, STATUS_NOT_ENOUGH_MEMORY); + } + + // Validate configured buffer duration is within acceptable range, else set to default duration. + if (rollingBufferDurationSec >= MIN_ROLLING_BUFFER_DURATION_IN_SECONDS && rollingBufferDurationSec <= MAX_ROLLING_BUFFER_DURATION_IN_SECONDS) { + DLOGI("Rolling buffer duration set to %lf seconds.", rollingBufferDurationSec); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec = rollingBufferDurationSec; + } else if (rollingBufferDurationSec != 0) { + DLOGW("Rolling buffer duration does not fit range (%lf sec - %lf sec). Setting to default %lf sec", MIN_ROLLING_BUFFER_DURATION_IN_SECONDS, + MAX_ROLLING_BUFFER_DURATION_IN_SECONDS, DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec = DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS; + } else if (rollingBufferDurationSec == 0) { + DLOGI("Setting to default buffer duration of %lf sec", DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec = DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS; + } + + // Validate configured expected bitrate is within acceptable range, else set to default bitrate. + if (rollingBufferBitratebps >= MIN_EXPECTED_BIT_RATE && rollingBufferBitratebps <= MAX_EXPECTED_BIT_RATE) { + if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_VIDEO) { + DLOGI("Rolling buffer expected bitrate set to %lf bps for video.", rollingBufferBitratebps); + } else if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_AUDIO) { + DLOGI("Rolling buffer expected bitrate set to %lf bps for audio.", rollingBufferBitratebps); + } else { + DLOGI("Rolling buffer expected bitrate set to %lf bps for unkown codec.", rollingBufferBitratebps); + } + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = rollingBufferBitratebps; + } else if (rollingBufferBitratebps != 0) { + if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_VIDEO) { + DLOGW("Rolling buffer bitrate does not fit range (%lf bps - %lf bps) for video. Setting to default %lf bps.", MIN_EXPECTED_BIT_RATE, + MAX_EXPECTED_BIT_RATE, DEFAULT_EXPECTED_VIDEO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_VIDEO_BIT_RATE; + } else if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_AUDIO) { + DLOGW("Rolling buffer bitrate does not fit range (%lf bps - %lf bps) for audio. Setting to default %lf bps.", MIN_EXPECTED_BIT_RATE, + MAX_EXPECTED_BIT_RATE, DEFAULT_EXPECTED_AUDIO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_AUDIO_BIT_RATE; + } else { + DLOGW("Rolling buffer bitrate does not fit range (%lf bps - %lf bps) for unknown codec. Setting to default %lf bps.", + MIN_EXPECTED_BIT_RATE, MAX_EXPECTED_BIT_RATE, DEFAULT_EXPECTED_VIDEO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_VIDEO_BIT_RATE; + } + } else if (rollingBufferBitratebps == 0) { + if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_VIDEO) { + DLOGI("Setting to default rolling buffer bitrate of %lf bps for video.", DEFAULT_EXPECTED_VIDEO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_VIDEO_BIT_RATE; + } else if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_AUDIO) { + DLOGI("Setting to default rolling buffer bitrate of %lf bps for audio.", DEFAULT_EXPECTED_AUDIO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_AUDIO_BIT_RATE; + } else { + DLOGI("Setting to default rolling buffer bitrate of %lf bps for unknown codec.", DEFAULT_EXPECTED_VIDEO_BIT_RATE); + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps = DEFAULT_EXPECTED_VIDEO_BIT_RATE; + } + } + +CleanUp: + CHK_LOG_ERR(retStatus); + + return retStatus; +} + STATUS freeTransceiver(PRtcRtpTransceiver* pRtcRtpTransceiver) { UNUSED_PARAM(pRtcRtpTransceiver); return STATUS_NOT_IMPLEMENTED; } +STATUS freeRollingBufferConfig(PRollingBufferConfig pRollingBufferConfig) +{ + SAFE_MEMFREE(pRollingBufferConfig); + return STATUS_SUCCESS; +} + STATUS freeKvsRtpTransceiver(PKvsRtpTransceiver* ppKvsRtpTransceiver) { STATUS retStatus = STATUS_SUCCESS; @@ -76,6 +151,9 @@ STATUS freeKvsRtpTransceiver(PKvsRtpTransceiver* ppKvsRtpTransceiver) if (pKvsRtpTransceiver->sender.retransmitter != NULL) { freeRetransmitter(&pKvsRtpTransceiver->sender.retransmitter); } + + freeRollingBufferConfig(pKvsRtpTransceiver->pRollingBufferConfig); + MUTEX_FREE(pKvsRtpTransceiver->statsLock); SAFE_MEMFREE(pKvsRtpTransceiver->peerFrameBuffer); diff --git a/src/source/PeerConnection/Rtp.h b/src/source/PeerConnection/Rtp.h index d9104d73eb..f45c0be8aa 100644 --- a/src/source/PeerConnection/Rtp.h +++ b/src/source/PeerConnection/Rtp.h @@ -9,13 +9,18 @@ extern "C" { // Default MTU comes from libwebrtc // https://groups.google.com/forum/#!topic/discuss-webrtc/gH5ysR3SoZI -#define DEFAULT_MTU_SIZE 1200 -#define DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS 3 -#define HIGHEST_EXPECTED_BIT_RATE (10 * 1024 * 1024) +#define DEFAULT_MTU_SIZE_BYTES 1200 +#define DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS (DOUBLE) 3 +#define DEFAULT_EXPECTED_VIDEO_BIT_RATE (DOUBLE)(10 * 1024 * 1024) +#define DEFAULT_EXPECTED_AUDIO_BIT_RATE (DOUBLE)(10 * 1024 * 1024) #define DEFAULT_SEQ_NUM_BUFFER_SIZE 1000 #define DEFAULT_VALID_INDEX_BUFFER_SIZE 1000 #define DEFAULT_PEER_FRAME_BUFFER_SIZE (5 * 1024) #define SRTP_AUTH_TAG_OVERHEAD 10 +#define MIN_ROLLING_BUFFER_DURATION_IN_SECONDS (DOUBLE) 0.1 +#define MIN_EXPECTED_BIT_RATE (DOUBLE)(102.4 * 1024) // Considering 1Kib = 1024 bits +#define MAX_ROLLING_BUFFER_DURATION_IN_SECONDS (DOUBLE) 10 +#define MAX_EXPECTED_BIT_RATE (DOUBLE)(240 * 1024 * 1024) // Considering 1Kib = 1024 bits // https://www.w3.org/TR/webrtc-stats/#dom-rtcoutboundrtpstreamstats-huge // Huge frames, by definition, are frames that have an encoded size at least 2.5 times the average size of the frames. @@ -43,6 +48,12 @@ typedef struct { } RtcRtpSender, *PRtcRtpSender; +typedef struct { + DOUBLE rollingBufferDurationSec; //!< Maximum duration of media that needs to be buffered (in seconds). The lowest allowed is 0.1 seconds (100ms) + DOUBLE rollingBufferBitratebps; //!< Maximum expected bitrate of media (In bits/second). It is used to determine the buffer capacity. The lowest + //!< allowed is 100 Kbps +} RollingBufferConfig, *PRollingBufferConfig; + typedef struct { RtcRtpTransceiver transceiver; RtcRtpSender sender; @@ -52,6 +63,8 @@ typedef struct { UINT32 jitterBufferSsrc; PJitterBuffer pJitterBuffer; + PRollingBufferConfig pRollingBufferConfig; + UINT64 onFrameCustomData; RtcOnFrame onFrame; @@ -84,6 +97,9 @@ STATUS writeRtpPacket(PKvsPeerConnection pKvsPeerConnection, PRtpPacket pRtpPack STATUS hasTransceiverWithSsrc(PKvsPeerConnection pKvsPeerConnection, UINT32 ssrc); STATUS findTransceiverBySsrc(PKvsPeerConnection pKvsPeerConnection, PKvsRtpTransceiver* ppTransceiver, UINT32 ssrc); +STATUS setUpRollingBufferConfigInternal(PKvsRtpTransceiver, PRtcMediaStreamTrack, DOUBLE, DOUBLE); +STATUS freeRollingBufferConfig(PRollingBufferConfig); + #ifdef __cplusplus } #endif diff --git a/src/source/PeerConnection/SessionDescription.c b/src/source/PeerConnection/SessionDescription.c index 9df02023f6..eeda6923f3 100644 --- a/src/source/PeerConnection/SessionDescription.c +++ b/src/source/PeerConnection/SessionDescription.c @@ -146,6 +146,8 @@ STATUS setPayloadTypesForOffer(PHashTable codecTable) /* * Populate map with PayloadTypes for codecs a KvsPeerConnection has enabled. + * + * Precondition: !pKvsPeerConnection->isOffer */ STATUS setPayloadTypesFromOffer(PHashTable codecTable, PHashTable rtxTable, PSessionDescription pSessionDescription) { @@ -163,6 +165,8 @@ STATUS setPayloadTypesFromOffer(PHashTable codecTable, PHashTable rtxTable, PSes PCHAR fmtp; UINT64 fmtpScore, bestFmtpScore; + CHK(codecTable != NULL && rtxTable != NULL && pSessionDescription != NULL, STATUS_NULL_ARG); + for (currentMedia = 0; currentMedia < pSessionDescription->mediaCount; currentMedia++) { pMediaDescription = &(pSessionDescription->mediaDescriptions[currentMedia]); aptFmtpValCount = 0; @@ -276,6 +280,7 @@ STATUS setPayloadTypesFromOffer(PHashTable codecTable, PHashTable rtxTable, PSes } CleanUp: + CHK_LOG_ERR(retStatus); LEAVES(); return retStatus; @@ -310,9 +315,23 @@ STATUS setTransceiverPayloadTypes(PHashTable codecTable, PHashTable rtxTable, PD } } - CHK_STATUS(createRtpRollingBuffer(DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS * HIGHEST_EXPECTED_BIT_RATE / 8 / DEFAULT_MTU_SIZE, - &pKvsRtpTransceiver->sender.packetBuffer)); - CHK_STATUS(createRetransmitter(DEFAULT_SEQ_NUM_BUFFER_SIZE, DEFAULT_VALID_INDEX_BUFFER_SIZE, &pKvsRtpTransceiver->sender.retransmitter)); + if (pKvsRtpTransceiver != NULL) { + if (pKvsRtpTransceiver->pRollingBufferConfig == NULL) { + // Passing in 0,0. The default values will be set up since application has not set up rolling buffer config with the + // configureTransceiverRollingBuffer() call + DLOGI("Rolling buffer config not set up for tranceiver. Setting defaults."); + CHK_STATUS(setUpRollingBufferConfigInternal(pKvsRtpTransceiver, &pKvsRtpTransceiver->sender.track, 0, 0)); + } + + DLOGI("Rolling buffer params: %lf sec, %lf bps", pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps); + UINT64 rollingBufferCapacity = (UINT64) (pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec * + pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps / 8 / DEFAULT_MTU_SIZE_BYTES); + + DLOGI("The rolling buffer is configured to store %" PRIu64 " packets", rollingBufferCapacity); + CHK_STATUS(createRtpRollingBuffer(rollingBufferCapacity, &pKvsRtpTransceiver->sender.packetBuffer)); + CHK_STATUS(createRetransmitter(DEFAULT_SEQ_NUM_BUFFER_SIZE, DEFAULT_VALID_INDEX_BUFFER_SIZE, &pKvsRtpTransceiver->sender.retransmitter)); + } } CleanUp: @@ -323,29 +342,37 @@ STATUS setTransceiverPayloadTypes(PHashTable codecTable, PHashTable rtxTable, PD PCHAR fmtpForPayloadType(UINT64 payloadType, PSessionDescription pSessionDescription) { + ENTERS(); UINT32 currentMedia, currentAttribute; PSdpMediaDescription pMediaDescription = NULL; CHAR payloadStr[MAX_SDP_ATTRIBUTE_VALUE_LENGTH]; INT32 amountWritten = 0; + STATUS retStatus = STATUS_SUCCESS; + PCHAR retVal = NULL; + + CHK(pSessionDescription != NULL, STATUS_NULL_ARG); MEMSET(payloadStr, 0x00, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); amountWritten = SNPRINTF(payloadStr, SIZEOF(payloadStr), "%" PRId64, payloadType); - if (amountWritten < 0) { - DLOGE("Internal error: Full payload type for fmtp could not be written"); - } else { - for (currentMedia = 0; currentMedia < pSessionDescription->mediaCount; currentMedia++) { - pMediaDescription = &(pSessionDescription->mediaDescriptions[currentMedia]); - for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount; currentAttribute++) { - if (STRCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeName, "fmtp") == 0 && - STRNCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeValue, payloadStr, STRLEN(payloadStr)) == 0) { - return pMediaDescription->sdpAttributes[currentAttribute].attributeValue + STRLEN(payloadStr) + 1; - } + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full payload type for fmtp could not be written"); + + for (currentMedia = 0; currentMedia < pSessionDescription->mediaCount; currentMedia++) { + pMediaDescription = &(pSessionDescription->mediaDescriptions[currentMedia]); + for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount; currentAttribute++) { + if (STRCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeName, "fmtp") == 0 && + STRNCMP(pMediaDescription->sdpAttributes[currentAttribute].attributeValue, payloadStr, STRLEN(payloadStr)) == 0) { + retVal = pMediaDescription->sdpAttributes[currentAttribute].attributeValue + STRLEN(payloadStr) + 1; + CHK(FALSE, STATUS_SUCCESS); // Break loop and go to CleanUp label } } } - return NULL; +CleanUp: + CHK_LOG_ERR(retStatus); + + LEAVES(); + return retVal; } /* @@ -416,21 +443,26 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp BOOL containRtx = FALSE; BOOL directionFound = FALSE; UINT32 i, remoteAttributeCount, attributeCount = 0; - PRtcMediaStreamTrack pRtcMediaStreamTrack = &(pKvsRtpTransceiver->sender.track); PSdpMediaDescription pSdpMediaDescriptionRemote; PCHAR currentFmtp = NULL, rtpMapValue = NULL; CHAR remoteSdpAttributeValue[MAX_SDP_ATTRIBUTE_VALUE_LENGTH]; INT32 amountWritten = 0; + CHK(pKvsRtpTransceiver != NULL, STATUS_NULL_ARG); + CHK(pKvsPeerConnection->isOffer || pRemoteSessionDescription != NULL, STATUS_NULL_ARG); + + PRtcMediaStreamTrack pRtcMediaStreamTrack = &(pKvsRtpTransceiver->sender.track); + MEMSET(remoteSdpAttributeValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); if (pRtcMediaStreamTrack->codec == RTC_CODEC_UNKNOWN && pUnknownCodecPayloadTypesTable != NULL) { CHK_STATUS(hashTableGet(pUnknownCodecPayloadTypesTable, unknownCodecHashTableKey, &payloadType)); } else { CHK_STATUS(hashTableGet(pKvsPeerConnection->pCodecTable, pRtcMediaStreamTrack->codec, &payloadType)); - currentFmtp = fmtpForPayloadType(payloadType, &(pKvsPeerConnection->remoteSessionDescription)); + if (!pKvsPeerConnection->isOffer) { + currentFmtp = fmtpForPayloadType(payloadType, pRemoteSessionDescription); + } } - if (pRtcMediaStreamTrack->kind == MEDIA_STREAM_TRACK_KIND_VIDEO) { if (pRtcMediaStreamTrack->codec == RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE) { retStatus = hashTableGet(pKvsPeerConnection->pRtxTable, RTC_RTX_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE, @@ -573,11 +605,14 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "mid"); - // check all session attribute lines to see if a line with mid is present. If it is present, copy its content and break - for (i = 0; i < pRemoteSessionDescription->mediaDescriptions[mediaSectionId].mediaAttributesCount; i++) { - if (STRCMP(pRemoteSessionDescription->mediaDescriptions[mediaSectionId].sdpAttributes[i].attributeName, MID_KEY) == 0) { - STRCPY(remoteSdpAttributeValue, pRemoteSessionDescription->mediaDescriptions[mediaSectionId].sdpAttributes[i].attributeValue); - break; + + if (!pKvsPeerConnection->isOffer) { + // check all session attribute lines to see if a line with mid is present. If it is present, copy its content and break + for (i = 0; i < pRemoteSessionDescription->mediaDescriptions[mediaSectionId].mediaAttributesCount; i++) { + if (STRCMP(pRemoteSessionDescription->mediaDescriptions[mediaSectionId].sdpAttributes[i].attributeName, MID_KEY) == 0) { + STRCPY(remoteSdpAttributeValue, pRemoteSessionDescription->mediaDescriptions[mediaSectionId].sdpAttributes[i].attributeValue); + break; + } } } @@ -638,7 +673,6 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-mux"); attributeCount++; - if (mediaSectionId != 0) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-rsize"); attributeCount++; @@ -659,19 +693,12 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack", payloadType); CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtcp-fb nack value could not be written"); - amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, - SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack pli", payloadType); - CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtcp-fb nack-pli value could not be written"); attributeCount++; STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack", payloadType); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack pli", payloadType); - attributeCount++; - - STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack", payloadType); - SPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, "%" PRId64 " nack pli", payloadType); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack pli", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H264 rtcp-fb nack-pli value could not be written"); attributeCount++; // TODO: If level asymmetry is allowed, consider sending back DEFAULT_H264_FMTP instead of the received fmtp value. @@ -767,6 +794,9 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack", payloadType); CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H265 rtcp-fb nack value could not be written"); + attributeCount++; + + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtcp-fb"); amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " nack pli", payloadType); CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full H265 rtcp-fb nack-pli value could not be written"); @@ -958,12 +988,11 @@ STATUS populateSessionDescriptionMedia(PKvsPeerConnection pKvsPeerConnection, PS PCHAR pDtlsRole = NULL; PHashTable pUnknownCodecPayloadTypesTable = NULL, pUnknownCodecRtpmapTable = NULL; UINT32 unknownCodecHashTableKey = 0; + UINT32 unknownHashTableBucketCount = 0; CHK_STATUS(dtlsSessionGetLocalCertificateFingerprint(pKvsPeerConnection->pDtlsSession, certificateFingerprint, CERTIFICATE_FINGERPRINT_LENGTH)); - if (pKvsPeerConnection->isOffer) { pDtlsRole = DTLS_ROLE_ACTPASS; - CHK_STATUS(doubleListGetHeadNode(pKvsPeerConnection->pTransceivers, &pCurNode)); while (pCurNode != NULL) { CHK_STATUS(doubleListGetNodeData(pCurNode, &data)); @@ -971,7 +1000,6 @@ STATUS populateSessionDescriptionMedia(PKvsPeerConnection pKvsPeerConnection, PS pKvsRtpTransceiver = (PKvsRtpTransceiver) data; if (pKvsRtpTransceiver != NULL) { CHK(pLocalSessionDescription->mediaCount < MAX_SDP_SESSION_MEDIA_COUNT, STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT); - // If generating answer, need to check if Local Description is present in remote -- if not, we don't need to create a local // description for it or else our Answer will have an extra m-line, for offer the local is the offer itself, don't care about remote CHK_STATUS(populateSingleMediaSection( @@ -982,14 +1010,17 @@ STATUS populateSessionDescriptionMedia(PKvsPeerConnection pKvsPeerConnection, PS } } else { pDtlsRole = DTLS_ROLE_ACTIVE; - CHK_STATUS(hashTableCreate(&pUnknownCodecPayloadTypesTable)); - CHK_STATUS(hashTableCreate(&pUnknownCodecRtpmapTable)); + unknownHashTableBucketCount = + pRemoteSessionDescription->mediaCount < MIN_HASH_BUCKET_COUNT ? MIN_HASH_BUCKET_COUNT : pRemoteSessionDescription->mediaCount; + CHK_STATUS(hashTableCreateWithParams(unknownHashTableBucketCount, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, + &pUnknownCodecPayloadTypesTable)); + CHK_STATUS( + hashTableCreateWithParams(unknownHashTableBucketCount, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, &pUnknownCodecRtpmapTable)); // this function creates a list of transceivers corresponding to each m-line and adds it answerTransceivers // if an m-line does not have a corresponding transceiver created by the user, we create a fake transceiver CHK_STATUS(findTransceiversByRemoteDescription(pKvsPeerConnection, pRemoteSessionDescription, pUnknownCodecPayloadTypesTable, pUnknownCodecRtpmapTable)); - // pAnswerTransceivers contains transceivers created by the user as well as fake transceivers CHK_STATUS(doubleListGetHeadNode(pKvsPeerConnection->pAnswerTransceivers, &pCurNode)); while (pCurNode != NULL) { @@ -1056,12 +1087,13 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio UINT32 i, sizeRemaining; INT32 charsCopied; - CHK(pKvsPeerConnection != NULL && pLocalSessionDescription != NULL && pRemoteSessionDescription != NULL, STATUS_NULL_ARG); + CHK(pKvsPeerConnection != NULL && pLocalSessionDescription != NULL, STATUS_NULL_ARG); + CHK(pKvsPeerConnection->isOffer || pRemoteSessionDescription != NULL, STATUS_NULL_ARG); + CHK_STATUS(populateSessionDescriptionMedia(pKvsPeerConnection, pRemoteSessionDescription, pLocalSessionDescription)); MEMSET(bundleValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); MEMSET(wmsValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); MEMSET(remoteSdpAttributeValue, 0, MAX_SDP_ATTRIBUTE_VALUE_LENGTH); - STRCPY(pLocalSessionDescription->sdpOrigin.userName, "-"); pLocalSessionDescription->sdpOrigin.sessionId = RAND(); pLocalSessionDescription->sdpOrigin.sessionVersion = 2; @@ -1078,7 +1110,6 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio STRCPY(pLocalSessionDescription->sdpAttributes[0].attributeName, "group"); STRCPY(pLocalSessionDescription->sdpAttributes[0].attributeValue, BUNDLE_KEY); pLocalSessionDescription->sessionAttributesCount++; - if (pKvsPeerConnection->canTrickleIce.value) { STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeName, "ice-options"); STRCPY(pLocalSessionDescription->sdpAttributes[pLocalSessionDescription->sessionAttributesCount].attributeValue, "trickle"); @@ -1086,10 +1117,12 @@ STATUS populateSessionDescription(PKvsPeerConnection pKvsPeerConnection, PSessio } // check all session attribute lines to see if a line with BUNDLE is present. If it is present, copy its content and break - for (i = 0; i < pRemoteSessionDescription->sessionAttributesCount; i++) { - if (STRSTR(pRemoteSessionDescription->sdpAttributes[i].attributeValue, BUNDLE_KEY) != NULL) { - STRCPY(remoteSdpAttributeValue, pRemoteSessionDescription->sdpAttributes[i].attributeValue + ARRAY_SIZE(BUNDLE_KEY) - 1); - break; + if (!pKvsPeerConnection->isOffer) { + for (i = 0; i < pRemoteSessionDescription->sessionAttributesCount; i++) { + if (STRSTR(pRemoteSessionDescription->sdpAttributes[i].attributeValue, BUNDLE_KEY) != NULL) { + STRCPY(remoteSdpAttributeValue, pRemoteSessionDescription->sdpAttributes[i].attributeValue + ARRAY_SIZE(BUNDLE_KEY) - 1); + break; + } } } @@ -1184,18 +1217,21 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection PRtcMediaStreamTrack pRtcMediaStreamTrack; RtcMediaStreamTrack track; - CHK_STATUS(hashTableCreate(&pSeenTransceivers)); // to be used by findCodecInTransceivers + // pSeenTranceivers is populated only with codec types supported by the SDK. And if already populated, it is not added again. + // Hence, it is sufficient if the hash table count is set to minimum or required transceivers + UINT32 seenTranceiversHashBucketCnt = RTC_CODEC_MAX < MIN_HASH_BUCKET_COUNT ? MIN_HASH_BUCKET_COUNT : RTC_CODEC_MAX; + CHK_STATUS(hashTableCreateWithParams(seenTranceiversHashBucketCnt, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, &pSeenTransceivers)); // sample m-lines // m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126 // m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 117 35 36 114 115 116 62 118 // this loop iterates over all the m-lines + for (currentMedia = 0; currentMedia < pRemoteSessionDescription->mediaCount; currentMedia++) { pMediaDescription = &(pRemoteSessionDescription->mediaDescriptions[currentMedia]); foundMediaSectionWithCodec = FALSE; count = 0; MEMSET(firstCodec, 0x00, MAX_PAYLOAD_TYPE_LENGTH); - // Scan the media section name for any codecs we support // sample attributeValue=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126 attributeValue = pMediaDescription->mediaName; @@ -1244,7 +1280,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection attributeValue = end + 1; } } while (end != NULL && !foundMediaSectionWithCodec); - // get the first payload type from codecs in case we need to use it to generate a fake transceiver to respond to an m-line // if we don't have a user-created one corresponding to an m-line // we can respond to an m-line by including any one codec the offer had @@ -1261,7 +1296,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount && !foundMediaSectionWithCodec; currentAttribute++) { attributeValue = pMediaDescription->sdpAttributes[currentAttribute].attributeValue; rtcCodec = RTC_CODEC_UNKNOWN; - // check for supported codec in rtpmap values only if an a-line contains the keyword "rtpmap" to save string comparisons if (STRNCMP(RTPMAP_VALUE, pMediaDescription->sdpAttributes[currentAttribute].attributeName, 6) == 0) { if (STRSTR(attributeValue, H264_VALUE) != NULL) { @@ -1324,7 +1358,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection CHK_STATUS(doubleListInsertItemTail(pKvsPeerConnection->pAnswerTransceivers, (UINT64) pKvsRtpFakeTransceiver)); CHK_STATUS(STRTOUI32(firstCodec, firstCodec + tokenLen, 10, &codec)); - // Insert (int)(firstCodec) and rtpMapValue into the hashtables with the same key so they can be retrieved later during serialization CHK_STATUS(hashTableContains(pUnknownCodecPayloadTypesTable, (UINT64) codec, &containsPayloadType)); CHK_STATUS(hashTableContains(pUnknownCodecRtpmapTable, (UINT64) rtpMapValue, &containsRtpMap)); @@ -1339,7 +1372,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection } CleanUp: - CHK_STATUS(hashTableFree(pSeenTransceivers)); CHK_LOG_ERR(retStatus); diff --git a/src/source/PeerConnection/SessionDescription.h b/src/source/PeerConnection/SessionDescription.h index ce9a81d5f9..a7e5ac4903 100644 --- a/src/source/PeerConnection/SessionDescription.h +++ b/src/source/PeerConnection/SessionDescription.h @@ -81,7 +81,9 @@ extern "C" { // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 #define TWCC_SDP_ATTR "transport-cc" -#define TWCC_EXT_URL "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" +#define TWCC_EXT_URL (PCHAR) "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + +#define CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH 2 STATUS setPayloadTypesFromOffer(PHashTable, PHashTable, PSessionDescription); STATUS setPayloadTypesForOffer(PHashTable); diff --git a/src/source/Sdp/Deserialize.c b/src/source/Sdp/Deserialize.c index c67d684140..2fbf731239 100644 --- a/src/source/Sdp/Deserialize.c +++ b/src/source/Sdp/Deserialize.c @@ -5,7 +5,7 @@ STATUS parseMediaName(PSessionDescription pSessionDescription, PCHAR pch, UINT32 { ENTERS(); STATUS retStatus = STATUS_SUCCESS; - CHK(pSessionDescription->mediaCount < MAX_SDP_SESSION_MEDIA_COUNT, STATUS_BUFFER_TOO_SMALL); + CHK(pSessionDescription->mediaCount < MAX_SDP_SESSION_MEDIA_COUNT, STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT); STRNCPY(pSessionDescription->mediaDescriptions[pSessionDescription->mediaCount].mediaName, (pch + SDP_ATTRIBUTE_LENGTH), MIN(MAX_SDP_MEDIA_NAME_LENGTH, lineLen - SDP_ATTRIBUTE_LENGTH)); diff --git a/tst/DtlsFunctionalityTest.cpp b/tst/DtlsFunctionalityTest.cpp index 32faca8ab7..f0e647abd3 100644 --- a/tst/DtlsFunctionalityTest.cpp +++ b/tst/DtlsFunctionalityTest.cpp @@ -131,8 +131,8 @@ TEST_F(DtlsFunctionalityTest, putApplicationDataWithVariedSizes) PBYTE pData = NULL; INT32 dataSizes[] = { 4, // very small packet - DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu - DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + DEFAULT_MTU_SIZE_BYTES - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE_BYTES + 200, // big packet and bigger than even a jumbo frame }; EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); @@ -161,8 +161,8 @@ TEST_F(DtlsFunctionalityTest, processPacketWithVariedSizes) PBYTE pData = NULL; INT32 dataSizes[] = { 4, // very small packet - DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu - DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + DEFAULT_MTU_SIZE_BYTES - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE_BYTES + 200, // big packet and bigger than even a jumbo frame }; INT32 readDataSize; @@ -193,8 +193,8 @@ TEST_F(DtlsFunctionalityTest, putApplicationDataWithVariedSizesInThread) PBYTE pData = NULL; INT32 dataSizes[] = { 4, // very small packet - DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu - DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + DEFAULT_MTU_SIZE_BYTES - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE_BYTES + 200, // big packet and bigger than even a jumbo frame }; EXPECT_EQ(STATUS_SUCCESS, timerQueueCreate(&timerQueueHandle)); @@ -223,8 +223,8 @@ TEST_F(DtlsFunctionalityTest, processPacketWithVariedSizesInThread) PBYTE pData = NULL; INT32 dataSizes[] = { 4, // very small packet - DEFAULT_MTU_SIZE - 200, // small packet but should be still under mtu - DEFAULT_MTU_SIZE + 200, // big packet and bigger than even a jumbo frame + DEFAULT_MTU_SIZE_BYTES - 200, // small packet but should be still under mtu + DEFAULT_MTU_SIZE_BYTES + 200, // big packet and bigger than even a jumbo frame }; INT32 readDataSize; diff --git a/tst/MetricsApiTest.cpp b/tst/MetricsApiTest.cpp index 7484d2db94..656d45a68c 100644 --- a/tst/MetricsApiTest.cpp +++ b/tst/MetricsApiTest.cpp @@ -21,6 +21,9 @@ TEST_F(MetricsApiTest, webRtcGetMetrics) EXPECT_EQ(STATUS_NULL_ARG, rtcPeerConnectionGetMetrics(NULL, NULL, &rtcMetrics)); MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); +#ifdef ENABLE_STATS_CALCULATION_CONTROL + configuration.kvsRtcConfiguration.enableIceStats = TRUE; +#endif ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); @@ -49,6 +52,9 @@ TEST_F(MetricsApiTest, webRtcIceServerGetMetrics) rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 5; MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); +#ifdef ENABLE_STATS_CALCULATION_CONTROL + configuration.kvsRtcConfiguration.enableIceStats = TRUE; +#endif STRNCPY(configuration.iceServers[0].urls, (PCHAR) "stun:stun.kinesisvideo.us-west-2.amazonaws.com:443", MAX_ICE_CONFIG_URI_LEN); STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); @@ -56,7 +62,6 @@ TEST_F(MetricsApiTest, webRtcIceServerGetMetrics) STRNCPY(configuration.iceServers[1].urls, (PCHAR) "turns:54.202.170.151:443?transport=tcp", MAX_ICE_CONFIG_URI_LEN); STRNCPY(configuration.iceServers[1].credential, (PCHAR) "username", MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[1].username, (PCHAR) "password", MAX_ICE_CONFIG_USER_NAME_LEN); - ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); EXPECT_EQ(STATUS_ICE_SERVER_INDEX_INVALID, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); @@ -86,10 +91,122 @@ TEST_F(MetricsApiTest, webRtcIceCandidateGetMetrics) RtcStats rtcIceMetrics; MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); +#ifdef ENABLE_STATS_CALCULATION_CONTROL + configuration.kvsRtcConfiguration.enableIceStats = TRUE; +#endif STRNCPY(configuration.iceServers[0].urls, (PCHAR) "stun:stun.kinesisvideo.us-west-2.amazonaws.com:443", MAX_ICE_CONFIG_URI_LEN); STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + + pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; + + if(pIceAgent == NULL) { + DLOGI("ICE Agent null"); + } else { + DLOGI("ICE agent not null"); + } + IceCandidate localCandidate; + IceCandidate remoteCandidate; + IceCandidatePair iceCandidatePair; + MEMSET(&localCandidate, 0x00, SIZEOF(IceCandidate)); + localCandidate.state = ICE_CANDIDATE_STATE_VALID; + localCandidate.ipAddress.family = KVS_IP_FAMILY_TYPE_IPV4; + localCandidate.iceCandidateType = ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; + localCandidate.isRemote = FALSE; + localCandidate.ipAddress.address[0] = 0x7f; + localCandidate.ipAddress.address[1] = 0x00; + localCandidate.ipAddress.address[2] = 0x00; + localCandidate.ipAddress.address[3] = 0x01; + localCandidate.ipAddress.port = getInt16(1234); + localCandidate.priority = 1; + + MEMSET(&remoteCandidate, 0x00, SIZEOF(IceCandidate)); + remoteCandidate.state = ICE_CANDIDATE_STATE_VALID; + remoteCandidate.ipAddress.family = KVS_IP_FAMILY_TYPE_IPV4; + remoteCandidate.iceCandidateType = ICE_CANDIDATE_TYPE_HOST; + remoteCandidate.isRemote = FALSE; + remoteCandidate.ipAddress.address[0] = 0x0a; + remoteCandidate.ipAddress.address[1] = 0x01; + remoteCandidate.ipAddress.address[2] = 0xff; + remoteCandidate.ipAddress.address[3] = 0xff; + remoteCandidate.ipAddress.port = getInt16(1111); + remoteCandidate.priority = 3; + + iceCandidatePair.local = &localCandidate; + iceCandidatePair.remote = &remoteCandidate; + pIceAgent->pDataSendingIceCandidatePair = &iceCandidatePair; + + EXPECT_EQ(STATUS_SUCCESS, updateSelectedLocalRemoteCandidateStats(pIceAgent)); + + rtcIceMetrics.requestedTypeOfStats = RTC_STATS_TYPE_LOCAL_CANDIDATE; + EXPECT_EQ(STATUS_SUCCESS, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + + EXPECT_EQ(1234, rtcIceMetrics.rtcStatsObject.localIceCandidateStats.port); + EXPECT_EQ(1, rtcIceMetrics.rtcStatsObject.localIceCandidateStats.priority); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "127.0.0.1", rtcIceMetrics.rtcStatsObject.localIceCandidateStats.address); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "srflx", rtcIceMetrics.rtcStatsObject.localIceCandidateStats.candidateType); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "", rtcIceMetrics.rtcStatsObject.localIceCandidateStats.protocol); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "stun.kinesisvideo.us-west-2.amazonaws.com", rtcIceMetrics.rtcStatsObject.localIceCandidateStats.url); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "", rtcIceMetrics.rtcStatsObject.localIceCandidateStats.relayProtocol); + + rtcIceMetrics.requestedTypeOfStats = RTC_STATS_TYPE_REMOTE_CANDIDATE; + EXPECT_EQ(STATUS_SUCCESS, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + EXPECT_EQ(1111, rtcIceMetrics.rtcStatsObject.remoteIceCandidateStats.port); + EXPECT_EQ(3, rtcIceMetrics.rtcStatsObject.remoteIceCandidateStats.priority); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "10.1.255.255", rtcIceMetrics.rtcStatsObject.remoteIceCandidateStats.address); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "host", rtcIceMetrics.rtcStatsObject.remoteIceCandidateStats.candidateType); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "", rtcIceMetrics.rtcStatsObject.remoteIceCandidateStats.protocol); + + EXPECT_EQ(STATUS_SUCCESS, closePeerConnection(pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); +} + +#ifdef ENABLE_STATS_CALCULATION_CONTROL + +TEST_F(MetricsApiTest, webRtcIceServerGetMetrics_IceStatsControlOn_Disabled) +{ + RtcConfiguration configuration; + PRtcPeerConnection pRtcPeerConnection; + RtcStats rtcIceMetrics; + rtcIceMetrics.requestedTypeOfStats = RTC_STATS_TYPE_ICE_SERVER; // Supplying a type that is unavailable + rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 5; + + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + MEMSET(&rtcIceMetrics.rtcStatsObject.iceServerStats, 0x00, SIZEOF(RtcIceServerStats)); + STRNCPY(configuration.iceServers[0].urls, (PCHAR) "stun:stun.kinesisvideo.us-west-2.amazonaws.com:443", MAX_ICE_CONFIG_URI_LEN); + STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); + + STRNCPY(configuration.iceServers[1].urls, (PCHAR) "turns:54.202.170.151:443?transport=tcp", MAX_ICE_CONFIG_URI_LEN); + STRNCPY(configuration.iceServers[1].credential, (PCHAR) "username", MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(configuration.iceServers[1].username, (PCHAR) "password", MAX_ICE_CONFIG_USER_NAME_LEN); + configuration.kvsRtcConfiguration.enableIceStats = FALSE; + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + + rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 1; + EXPECT_EQ(STATUS_INVALID_OPERATION, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + EXPECT_EQ(0, rtcIceMetrics.rtcStatsObject.iceServerStats.port); + EXPECT_EQ('\0', rtcIceMetrics.rtcStatsObject.iceServerStats.url[0]); + EXPECT_EQ('\0', rtcIceMetrics.rtcStatsObject.iceServerStats.protocol[0]); + + EXPECT_EQ(STATUS_SUCCESS, closePeerConnection(pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); +} + +TEST_F(MetricsApiTest, webRtcIceCandidateGetMetrics_IceStatsControlOn_Enabled) +{ + RtcConfiguration configuration; + PRtcPeerConnection pRtcPeerConnection = NULL; + PIceAgent pIceAgent; + RtcStats rtcIceMetrics; + + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + STRNCPY(configuration.iceServers[0].urls, (PCHAR) "stun:stun.kinesisvideo.us-west-2.amazonaws.com:443", MAX_ICE_CONFIG_URI_LEN); + STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); + configuration.kvsRtcConfiguration.enableIceStats = TRUE; ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); pIceAgent = ((PKvsPeerConnection) pRtcPeerConnection)->pIceAgent; @@ -156,6 +273,46 @@ TEST_F(MetricsApiTest, webRtcIceCandidateGetMetrics) EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); } +TEST_F(MetricsApiTest, webRtcIceServerGetMetrics_IceStatsControlOn_Enabled) +{ + RtcConfiguration configuration; + PRtcPeerConnection pRtcPeerConnection; + RtcStats rtcIceMetrics; + rtcIceMetrics.requestedTypeOfStats = RTC_STATS_TYPE_ICE_SERVER; // Supplying a type that is unavailable + rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 5; + + MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + STRNCPY(configuration.iceServers[0].urls, (PCHAR) "stun:stun.kinesisvideo.us-west-2.amazonaws.com:443", MAX_ICE_CONFIG_URI_LEN); + STRNCPY(configuration.iceServers[0].credential, (PCHAR) "", MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(configuration.iceServers[0].username, (PCHAR) "", MAX_ICE_CONFIG_USER_NAME_LEN); + + STRNCPY(configuration.iceServers[1].urls, (PCHAR) "turns:54.202.170.151:443?transport=tcp", MAX_ICE_CONFIG_URI_LEN); + STRNCPY(configuration.iceServers[1].credential, (PCHAR) "username", MAX_ICE_CONFIG_CREDENTIAL_LEN); + STRNCPY(configuration.iceServers[1].username, (PCHAR) "password", MAX_ICE_CONFIG_USER_NAME_LEN); + configuration.kvsRtcConfiguration.enableIceStats = TRUE; + ASSERT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + + EXPECT_EQ(STATUS_ICE_SERVER_INDEX_INVALID, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + + rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 0; + EXPECT_EQ(STATUS_SUCCESS, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + + EXPECT_EQ(443, rtcIceMetrics.rtcStatsObject.iceServerStats.port); + EXPECT_PRED_FORMAT2(testing::IsSubstring, configuration.iceServers[0].urls, rtcIceMetrics.rtcStatsObject.iceServerStats.url); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "", rtcIceMetrics.rtcStatsObject.iceServerStats.protocol); + + rtcIceMetrics.rtcStatsObject.iceServerStats.iceServerIndex = 1; + EXPECT_EQ(STATUS_SUCCESS, rtcPeerConnectionGetMetrics(pRtcPeerConnection, NULL, &rtcIceMetrics)); + EXPECT_EQ(443, rtcIceMetrics.rtcStatsObject.iceServerStats.port); + EXPECT_PRED_FORMAT2(testing::IsSubstring, configuration.iceServers[1].urls, rtcIceMetrics.rtcStatsObject.iceServerStats.url); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "tcp", rtcIceMetrics.rtcStatsObject.iceServerStats.protocol); + + EXPECT_EQ(STATUS_SUCCESS, closePeerConnection(pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); +} + +#endif + } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/RtcpFunctionalityTest.cpp b/tst/RtcpFunctionalityTest.cpp index 00d76ad659..3e87cc4437 100644 --- a/tst/RtcpFunctionalityTest.cpp +++ b/tst/RtcpFunctionalityTest.cpp @@ -153,7 +153,7 @@ TEST_F(RtcpFunctionalityTest, onRtcpPacketCompoundNack) BYTE validRtcpPacket[] = {0x81, 0xcd, 0x00, 0x03, 0x2c, 0xd1, 0xa0, 0xde, 0x00, 0x00, 0xab, 0xe0, 0x00, 0x00, 0x00, 0x00}; initTransceiver(44000); ASSERT_EQ(STATUS_SUCCESS, - createRtpRollingBuffer(DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS * HIGHEST_EXPECTED_BIT_RATE / 8 / DEFAULT_MTU_SIZE, + createRtpRollingBuffer(DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS * DEFAULT_EXPECTED_VIDEO_BIT_RATE / 8 / DEFAULT_MTU_SIZE_BYTES, &pKvsRtpTransceiver->sender.packetBuffer)); ASSERT_EQ(STATUS_SUCCESS, createRetransmitter(DEFAULT_SEQ_NUM_BUFFER_SIZE, DEFAULT_VALID_INDEX_BUFFER_SIZE, &pKvsRtpTransceiver->sender.retransmitter)); @@ -295,27 +295,65 @@ TEST_F(RtcpFunctionalityTest, onpli) freePeerConnection(&pRtcPeerConnection); } +static void testBwHandler(UINT64 customData, UINT32 txBytes, UINT32 rxBytes, UINT32 txPacketsCnt, UINT32 rxPacketsCnt, + UINT64 duration) { + UNUSED_PARAM(customData); + UNUSED_PARAM(txBytes); + UNUSED_PARAM(rxBytes); + UNUSED_PARAM(txPacketsCnt); + UNUSED_PARAM(rxPacketsCnt); + UNUSED_PARAM(duration); + return; +} + static void parseTwcc(const std::string& hex, const uint32_t expectedReceived, const uint32_t expectedNotReceived) { + PRtcPeerConnection pRtcPeerConnection = nullptr; + PKvsPeerConnection pKvsPeerConnection; BYTE payload[256] = {0}; UINT32 payloadLen = 256; hexDecode(const_cast(hex.data()), hex.size(), payload, &payloadLen); RtcpPacket rtcpPacket{}; + RtpPacket rtpPacket{}; + RtcConfiguration config{}; + UINT64 value; + UINT16 twsn; + UINT16 i = 0; + UINT32 extpayload, received = 0, lost = 0; + rtcpPacket.header.packetLength = payloadLen / 4; rtcpPacket.payload = payload; rtcpPacket.payloadLength = payloadLen; - TwccManager twcc{}; - - ASSERT_EQ(STATUS_SUCCESS, parseRtcpTwccPacket(&rtcpPacket, &twcc)); - ASSERT_EQ(STATUS_SUCCESS, stackQueueClear(&twcc.twccPackets, FALSE)); - - uint32_t received = 0; - uint32_t lost = 0; - for (const auto& packet : twcc.twccPacketBySeqNum) { - if (packet.remoteTimeKvs == TWCC_PACKET_LOST_TIME) { - lost++; - } else if (packet.remoteTimeKvs != TWCC_PACKET_UNITIALIZED_TIME) { - received++; + + + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + pKvsPeerConnection = reinterpret_cast(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnSenderBandwidthEstimation(pRtcPeerConnection, 0, + testBwHandler)); + + UINT16 baseSeqNum = getUnalignedInt16BigEndian(rtcpPacket.payload + 8); + UINT16 pktCount = TWCC_PACKET_STATUS_COUNT(rtcpPacket.payload); + + for(i = baseSeqNum; i < baseSeqNum + pktCount; i++) { + rtpPacket.header.extension = TRUE; + rtpPacket.header.extensionProfile = TWCC_EXT_PROFILE; + rtpPacket.header.extensionLength = SIZEOF(UINT32); + twsn = i; + extpayload = TWCC_PAYLOAD(parseExtId(TWCC_EXT_URL), twsn); + rtpPacket.header.extensionPayload = (PBYTE) &extpayload; + EXPECT_EQ(STATUS_SUCCESS, twccManagerOnPacketSent(pKvsPeerConnection, &rtpPacket)); + } + + EXPECT_EQ(STATUS_SUCCESS, parseRtcpTwccPacket(&rtcpPacket, pKvsPeerConnection->pTwccManager)); + + for(i = 0; i < MAX_UINT16; i++) { + if(STATUS_SUCCEEDED(hashTableGet(pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable, i, &value))) { + PTwccRtpPacketInfo tempTwccRtpPktInfo = (PTwccRtpPacketInfo) value; + if(tempTwccRtpPktInfo->remoteTimeKvs == TWCC_PACKET_LOST_TIME) { + lost++; + } else if (tempTwccRtpPktInfo->remoteTimeKvs != TWCC_PACKET_UNITIALIZED_TIME) { + received++; + } } } @@ -323,9 +361,10 @@ static void parseTwcc(const std::string& hex, const uint32_t expectedReceived, c EXPECT_EQ(expectedReceived + expectedNotReceived, TWCC_PACKET_STATUS_COUNT(rtcpPacket.payload)); EXPECT_EQ(expectedReceived, received); EXPECT_EQ(expectedNotReceived, lost); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); } -TEST_F(RtcpFunctionalityTest, twcc3) +TEST_F(RtcpFunctionalityTest, twccParsePacketTest) { parseTwcc("", 0, 0); parseTwcc("4487A9E754B3E6FD01810001147A75A62001C801", 1, 0); @@ -365,6 +404,73 @@ TEST_F(RtcpFunctionalityTest, twcc3) parseTwcc("4487A9E754B3E6FD04B60036147CAA852024C002D999D6407800000000000000000000000000040000000000000000", 43, 11); parseTwcc("4487A9E754B3E6FD040200E4147C9F81202700B7E6649000000000000000000004000000000008000018000000001", 43, 185); } + +TEST_F(RtcpFunctionalityTest, updateTwccHashTableTest) +{ + PRtcPeerConnection pRtcPeerConnection = NULL; + PKvsPeerConnection pKvsPeerConnection = NULL; + RtcConfiguration config{}; + UINT64 receivedBytes = 0, receivedPackets = 0, sentBytes = 0, sentPackets = 0; + INT64 duration = 0; + PTwccRtpPacketInfo pTwccRtpPacketInfo = NULL; + PHashTable pTwccRtpPktInfosHashTable = NULL; + UINT16 hashTableInsertionCount = 0; + UINT16 lowerBound = UINT16_MAX - 3; + UINT16 upperBound = 3; + UINT16 i = 0; + + // Initialize structs and members. + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + pKvsPeerConnection = reinterpret_cast(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, peerConnectionOnSenderBandwidthEstimation(pRtcPeerConnection, 0, testBwHandler)); + + // Grab the hash table. + pTwccRtpPktInfosHashTable = pKvsPeerConnection->pTwccManager->pTwccRtpPktInfosHashTable; + + pKvsPeerConnection->pTwccManager->prevReportedBaseSeqNum = lowerBound; + pKvsPeerConnection->pTwccManager->lastReportedSeqNum = upperBound + 10; + + // Breakup the packet indexes to be across the max int overflow. + for (i = lowerBound; i <= UINT16_MAX && i != 0 ; i++) + { + pTwccRtpPacketInfo = (PTwccRtpPacketInfo) MEMCALLOC(1, SIZEOF(TwccRtpPacketInfo)); + EXPECT_EQ(STATUS_SUCCESS, hashTableUpsert(pTwccRtpPktInfosHashTable, i, (UINT64) pTwccRtpPacketInfo)); + hashTableInsertionCount++; + } + for (i = 0; i < upperBound; i++) + { + pTwccRtpPacketInfo = (PTwccRtpPacketInfo) MEMCALLOC(1, SIZEOF(TwccRtpPacketInfo)); + EXPECT_EQ(STATUS_SUCCESS, hashTableUpsert(pTwccRtpPktInfosHashTable, i, (UINT64) pTwccRtpPacketInfo)); + hashTableInsertionCount++; + } + + // Add at a non-monotonically-increased index. + pTwccRtpPacketInfo = (PTwccRtpPacketInfo) MEMCALLOC(1, SIZEOF(TwccRtpPacketInfo)); + EXPECT_EQ(STATUS_SUCCESS, hashTableUpsert(pTwccRtpPktInfosHashTable, upperBound + 10, (UINT64) pTwccRtpPacketInfo)); + hashTableInsertionCount++; + + // Validate hash table size after and before updating (onRtcpTwccPacket case). + EXPECT_EQ(hashTableInsertionCount, pTwccRtpPktInfosHashTable->itemCount); + EXPECT_EQ(STATUS_SUCCESS, updateTwccHashTable(pKvsPeerConnection->pTwccManager, &duration, &receivedBytes, &receivedPackets, &sentBytes, &sentPackets)); + EXPECT_EQ(0, pTwccRtpPktInfosHashTable->itemCount); + + hashTableInsertionCount = 0; + pTwccRtpPacketInfo = NULL; + for (i = 0; i <= upperBound; i++) + { + EXPECT_EQ(STATUS_SUCCESS, hashTableUpsert(pTwccRtpPktInfosHashTable, i, (UINT64) pTwccRtpPacketInfo)); + hashTableInsertionCount++; + } + EXPECT_EQ(hashTableInsertionCount, pTwccRtpPktInfosHashTable->itemCount); + EXPECT_EQ(STATUS_SUCCESS, updateTwccHashTable(pKvsPeerConnection->pTwccManager, &duration, &receivedBytes, &receivedPackets, &sentBytes, &sentPackets)); + EXPECT_EQ(0, pTwccRtpPktInfosHashTable->itemCount); + + MUTEX_LOCK(pKvsPeerConnection->twccLock); + MUTEX_UNLOCK(pKvsPeerConnection->twccLock); + + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); +} + } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/RtpFunctionalityTest.cpp b/tst/RtpFunctionalityTest.cpp index f9272c11c3..6fc9c950b2 100644 --- a/tst/RtpFunctionalityTest.cpp +++ b/tst/RtpFunctionalityTest.cpp @@ -113,7 +113,7 @@ TEST_F(RtpFunctionalityTest, marshallUnmarshallH264Data) // First call for payload size and sub payload length size EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH264(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + createPayloadForH264(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, &payloadArray.payloadSubLenSize)); if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { @@ -133,7 +133,7 @@ TEST_F(RtpFunctionalityTest, marshallUnmarshallH264Data) // Second call with actual buffer to fill in data EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH264(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + createPayloadForH264(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); EXPECT_LT(0, payloadArray.payloadSubLenSize); @@ -192,7 +192,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameH264Frame) // First call for payload size and sub payload length size EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH264(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + createPayloadForH264(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, &payloadArray.payloadSubLenSize)); if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { @@ -212,7 +212,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameH264Frame) // Second call with actual buffer to fill in data EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH264(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + createPayloadForH264(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); EXPECT_LT(0, payloadArray.payloadSubLenSize); @@ -289,7 +289,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameH265Frame) // First call for payload size and sub payload length size EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH265(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + createPayloadForH265(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, &payloadArray.payloadSubLenSize)); if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { @@ -309,7 +309,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameH265Frame) // Second call with actual buffer to fill in data EXPECT_EQ(STATUS_SUCCESS, - createPayloadForH265(DEFAULT_MTU_SIZE, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + createPayloadForH265(DEFAULT_MTU_SIZE_BYTES, (PBYTE) payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); EXPECT_LT(0, payloadArray.payloadSubLenSize); @@ -375,7 +375,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameOpusFrame) // First call for payload size and sub payload length size EXPECT_EQ(STATUS_SUCCESS, - createPayloadForOpus(DEFAULT_MTU_SIZE, (PBYTE) &payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + createPayloadForOpus(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, &payloadArray.payloadSubLenSize)); if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { @@ -395,7 +395,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameOpusFrame) // Second call with actual buffer to fill in data EXPECT_EQ(STATUS_SUCCESS, - createPayloadForOpus(DEFAULT_MTU_SIZE, (PBYTE) &payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + createPayloadForOpus(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); EXPECT_EQ(1, payloadArray.payloadSubLenSize); @@ -430,7 +430,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameShortG711Frame) // First call for payload size and sub payload length size EXPECT_EQ(STATUS_SUCCESS, - createPayloadForG711(DEFAULT_MTU_SIZE, (PBYTE) &payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + createPayloadForG711(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, &payloadArray.payloadSubLenSize)); if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { @@ -450,7 +450,7 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameShortG711Frame) // Second call with actual buffer to fill in data EXPECT_EQ(STATUS_SUCCESS, - createPayloadForG711(DEFAULT_MTU_SIZE, (PBYTE) &payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + createPayloadForG711(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); EXPECT_EQ(1, payloadArray.payloadSubLenSize); diff --git a/tst/RtpRollingBufferFunctionalityTest.cpp b/tst/RtpRollingBufferFunctionalityTest.cpp index b9cabca2ff..19a9f2de97 100644 --- a/tst/RtpRollingBufferFunctionalityTest.cpp +++ b/tst/RtpRollingBufferFunctionalityTest.cpp @@ -155,6 +155,110 @@ TEST_F(RtpRollingBufferFunctionalityTest, getIndexForSeqListReturnCorrectIndexsW EXPECT_EQ(STATUS_SUCCESS, freeRtpPacket(&pRtpPacket)); } +TEST_F(RtpRollingBufferFunctionalityTest, testRollingBufferParams) +{ + RtcConfiguration config{}; + PKvsRtpTransceiver pKvsRtpTransceiver = nullptr; + PRtcPeerConnection pRtcPeerConnection = nullptr; + PRtcRtpTransceiver pRtcRtpTransceiver = nullptr; + RtcMediaStreamTrack videoTrack{}; + + videoTrack.codec = RTC_CODEC_VP8; + videoTrack.kind = MEDIA_STREAM_TRACK_KIND_VIDEO; + + // Testing with 0,0 + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, 0, 0), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, DEFAULT_EXPECTED_VIDEO_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with invalid value for rolling buffer duration and valid rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, 0.001, 4 * 1024 * 1024), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, 4 * 1024 * 1024); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with valid value for rolling buffer duration and invalid rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, 0.2, 102.39 * 1024), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, 0.2); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, DEFAULT_EXPECTED_VIDEO_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with invalid value for rolling buffer duration and invalid rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, 0.0000001, 102.39 * 1024), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, DEFAULT_EXPECTED_VIDEO_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + + // Testing with max value for rolling buffer duration and max rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, MAX_ROLLING_BUFFER_DURATION_IN_SECONDS, MAX_EXPECTED_BIT_RATE), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, MAX_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, MAX_EXPECTED_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with max value for rolling buffer duration and min rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, MAX_ROLLING_BUFFER_DURATION_IN_SECONDS, MIN_EXPECTED_BIT_RATE), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, MAX_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, MIN_EXPECTED_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + + // Testing with min value for rolling buffer duration and max rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, MIN_ROLLING_BUFFER_DURATION_IN_SECONDS, MAX_EXPECTED_BIT_RATE), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, MIN_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, MAX_EXPECTED_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with min value for rolling buffer duration and min rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, MIN_ROLLING_BUFFER_DURATION_IN_SECONDS, MIN_EXPECTED_BIT_RATE), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, MIN_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, MIN_EXPECTED_BIT_RATE); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + + // Testing with valid value for rolling buffer duration and valid rolling buffer bitrate + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&config, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, ::addTransceiver(pRtcPeerConnection, &videoTrack, nullptr, &pRtcRtpTransceiver)); + pKvsRtpTransceiver = reinterpret_cast(pRtcRtpTransceiver); + EXPECT_TRUE(pKvsRtpTransceiver->pRollingBufferConfig == nullptr); + EXPECT_EQ(configureTransceiverRollingBuffer(pRtcRtpTransceiver, &videoTrack, 0.2, 9.2 * 1024 * 1024), STATUS_SUCCESS); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferDurationSec, 0.2); + EXPECT_EQ(pKvsRtpTransceiver->pRollingBufferConfig->rollingBufferBitratebps, 9.2 * 1024 * 1024); + EXPECT_EQ(freePeerConnection(&pRtcPeerConnection), STATUS_SUCCESS); + +} } // namespace webrtcclient } // namespace video } // namespace kinesis diff --git a/tst/SdpApiTest.cpp b/tst/SdpApiTest.cpp index dbb5547629..774511bf9f 100644 --- a/tst/SdpApiTest.cpp +++ b/tst/SdpApiTest.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "WebRTCClientTestFixture.h" @@ -11,6 +12,11 @@ namespace video { namespace webrtcclient { class SdpApiTest : public WebRtcClientTestBase { + public: + const std::string m_rtcp_h264_nack_line = "a=rtcp-fb:" + std::to_string(DEFAULT_PAYLOAD_H264) + " nack" + SDP_LINE_SEPARATOR; + const std::string m_rtcp_h264_nack_pli_line = "a=rtcp-fb:" + std::to_string(DEFAULT_PAYLOAD_H264) + " nack pli" + SDP_LINE_SEPARATOR; + const std::string m_rtcp_h265_nack_line = "a=rtcp-fb:" + std::to_string(DEFAULT_PAYLOAD_H265) + " nack" + SDP_LINE_SEPARATOR; + const std::string m_rtcp_h265_nack_pli_line = "a=rtcp-fb:" + std::to_string(DEFAULT_PAYLOAD_H265) + " nack pli" + SDP_LINE_SEPARATOR; }; /* @@ -248,6 +254,7 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_NoRtxType) PHashTable pRtxTable; PDoubleList pTransceivers; KvsRtpTransceiver transceiver; + MEMSET(&transceiver, 0x00, SIZEOF(KvsRtpTransceiver)); transceiver.sender.track.codec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; transceiver.transceiver.direction = RTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; transceiver.sender.packetBuffer = NULL; @@ -258,11 +265,15 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_NoRtxType) EXPECT_EQ(STATUS_SUCCESS, doubleListCreate(&pTransceivers)); EXPECT_EQ(STATUS_SUCCESS, doubleListInsertItemHead(pTransceivers, (UINT64)(&transceiver))); EXPECT_EQ(STATUS_SUCCESS, setTransceiverPayloadTypes(pCodecTable, pRtxTable, pTransceivers)); + EXPECT_EQ(transceiver.pRollingBufferConfig->rollingBufferDurationSec, DEFAULT_ROLLING_BUFFER_DURATION_IN_SECONDS); + EXPECT_EQ(transceiver.pRollingBufferConfig->rollingBufferBitratebps, DEFAULT_EXPECTED_VIDEO_BIT_RATE); EXPECT_EQ(1, transceiver.sender.payloadType); EXPECT_NE((PRtpRollingBuffer) NULL, transceiver.sender.packetBuffer); EXPECT_NE((PRetransmitter) NULL, transceiver.sender.retransmitter); hashTableFree(pCodecTable); hashTableFree(pRtxTable); + + freeRollingBufferConfig(transceiver.pRollingBufferConfig); freeRtpRollingBuffer(&transceiver.sender.packetBuffer); freeRetransmitter(&transceiver.sender.retransmitter); doubleListFree(pTransceivers); @@ -274,6 +285,7 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_HasRtxType) PHashTable pRtxTable; PDoubleList pTransceivers; KvsRtpTransceiver transceiver; + MEMSET(&transceiver, 0x00, SIZEOF(KvsRtpTransceiver)); transceiver.sender.track.codec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; transceiver.transceiver.direction = RTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; transceiver.sender.packetBuffer = NULL; @@ -291,6 +303,7 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_HasRtxType) EXPECT_NE((PRetransmitter) NULL, transceiver.sender.retransmitter); hashTableFree(pCodecTable); hashTableFree(pRtxTable); + freeRollingBufferConfig(transceiver.pRollingBufferConfig); freeRtpRollingBuffer(&transceiver.sender.packetBuffer); freeRetransmitter(&transceiver.sender.retransmitter); doubleListFree(pTransceivers); @@ -302,6 +315,7 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_HasRtxType_H265) PHashTable pRtxTable; PDoubleList pTransceivers; KvsRtpTransceiver transceiver; + MEMSET(&transceiver, 0x00, SIZEOF(KvsRtpTransceiver)); transceiver.sender.track.codec = RTC_CODEC_H265; transceiver.transceiver.direction = RTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; transceiver.sender.packetBuffer = NULL; @@ -319,6 +333,7 @@ TEST_F(SdpApiTest, setTransceiverPayloadTypes_HasRtxType_H265) EXPECT_NE((PRetransmitter) NULL, transceiver.sender.retransmitter); hashTableFree(pCodecTable); hashTableFree(pRtxTable); + freeRollingBufferConfig(transceiver.pRollingBufferConfig); freeRtpRollingBuffer(&transceiver.sender.packetBuffer); freeRetransmitter(&transceiver.sender.retransmitter); doubleListFree(pTransceivers); @@ -351,6 +366,15 @@ TEST_F(SdpApiTest, populateSingleMediaSection_TestTxSendRecv) EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sessionDescriptionInit)); EXPECT_PRED_FORMAT2(testing::IsSubstring, "sendrecv", sessionDescriptionInit.sdp); + std::string offerSdp(sessionDescriptionInit.sdp); + + // check nack and nack pli lines + std::string::size_type posPliOnly = offerSdp.find(m_rtcp_h264_nack_line); + std::string::size_type posPliNack = offerSdp.find(m_rtcp_h264_nack_pli_line); + EXPECT_NE(posPliOnly, posPliNack); + EXPECT_NE(posPliOnly, std::string::npos); + EXPECT_NE(posPliNack, std::string::npos); + closePeerConnection(offerPc); freePeerConnection(&offerPc); } @@ -421,6 +445,15 @@ TEST_F(SdpApiTest, populateSingleMediaSection_TestTxSendOnly) EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sessionDescriptionInit)); EXPECT_PRED_FORMAT2(testing::IsSubstring, "sendonly", sessionDescriptionInit.sdp); + std::string offerSdp(sessionDescriptionInit.sdp); + + // check nack and nack pli lines + std::string::size_type posPliOnly = offerSdp.find(m_rtcp_h264_nack_line); + std::string::size_type posPliNack = offerSdp.find(m_rtcp_h264_nack_pli_line); + EXPECT_NE(posPliOnly, posPliNack); + EXPECT_NE(posPliOnly, std::string::npos); + EXPECT_NE(posPliNack, std::string::npos); + closePeerConnection(offerPc); freePeerConnection(&offerPc); } @@ -452,6 +485,15 @@ TEST_F(SdpApiTest, populateSingleMediaSection_TestTxSendOnly_H265) EXPECT_EQ(STATUS_SUCCESS, createOffer(offerPc, &sessionDescriptionInit)); EXPECT_PRED_FORMAT2(testing::IsSubstring, "sendonly", sessionDescriptionInit.sdp); + std::string offerSdp(sessionDescriptionInit.sdp); + + // check nack and nack pli lines + std::string::size_type posPliOnly = offerSdp.find(m_rtcp_h265_nack_line); + std::string::size_type posPliNack = offerSdp.find(m_rtcp_h265_nack_pli_line); + EXPECT_NE(posPliOnly, posPliNack); + EXPECT_NE(posPliOnly, std::string::npos); + EXPECT_NE(posPliNack, std::string::npos); + closePeerConnection(offerPc); freePeerConnection(&offerPc); } @@ -497,7 +539,6 @@ TEST_F(SdpApiTest, populateSingleMediaSection_TestTxRecvOnlyStreamNull) // Create peer connection EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); - PRtcRtpTransceiver pTransceiver; RtcRtpTransceiverInit rtcRtpTransceiverInit; rtcRtpTransceiverInit.direction = RTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; @@ -545,6 +586,7 @@ a=rtpmap:102 H264/90000 MEMSET(&rtcConfiguration, 0x00, SIZEOF(RtcConfiguration)); MEMSET(&rtcMediaStreamTrack, 0x00, SIZEOF(RtcMediaStreamTrack)); MEMSET(&rtcSessionDescriptionInit, 0x00, SIZEOF(RtcSessionDescriptionInit)); + MEMSET(&rtcRtpTransceiverInit, 0x00, SIZEOF(RtcRtpTransceiverInit)); EXPECT_EQ(createPeerConnection(&rtcConfiguration, &pRtcPeerConnection), STATUS_SUCCESS); EXPECT_EQ(addSupportedCodec(pRtcPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE), STATUS_SUCCESS); @@ -647,7 +689,7 @@ a=group:BUNDLE 0 1 2 3 SessionDescription sessionDescription; MEMSET(&sessionDescription, 0x00, SIZEOF(SessionDescription)); // as log as Sdp.h MAX_SDP_SESSION_MEDIA_COUNT 5 this should fail instead of overwriting memory - EXPECT_EQ(STATUS_BUFFER_TOO_SMALL, deserializeSessionDescription(&sessionDescription, (PCHAR) sdp)); + EXPECT_EQ(STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT, deserializeSessionDescription(&sessionDescription, (PCHAR) sdp)); }); } @@ -760,6 +802,196 @@ a=group:BUNDLE audio video data }); } +// Test out unknown codec, unknown rtpmap and seen tranceiver hash map for correct sizing +TEST_F(SdpApiTest, fakeTransceiverTest) +{ + auto offerBase = std::string(R"(v=0 +o=- 2414510623331460048 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 1 +a=extmap-allow-mixed +a=msid-semantic: WMS 2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84)"); + + auto audioSdp = std::string(R"(m=audio 9 UDP/TLS/RTP/SAVPF 111 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 87:E6:EC:59:93:76:9F:42:7D:15:17:F6:8F:C4:29:AB:EA:3F:28:B6:DF:F8:14:2F:96:62:2F:16:98:F5:76:E5 +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=ssrc:331864867 cname:jyxeGEm09Qe6m8dq +a=ssrc:331864867 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 + )"); + + auto videoSdp = std::string(R"(m=video 9 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 37:C4:5C:9C:C9:DA:56:22:47:1F:8C:93:E1:A1:51:A8:15:94:78:1D:89:26:69:44:65:6C:C3:83:96:10:32:43 +a=setup:actpass +a=mid:1 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 VP8/90000 +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=96 +a=rtpmap:102 H264/90000 +a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f +a=rtpmap:103 rtx/90000 +a=fmtp:103 apt=102 +a=rtpmap:104 H264/90000 +a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f +a=rtpmap:105 rtx/90000 +a=fmtp:105 apt=104 +a=ssrc-group:FID 2039979579 916070044 +a=ssrc:2039979579 cname:jyxeGEm09Qe6m8dq +a=ssrc:2039979579 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=ssrc:916070044 cname:jyxeGEm09Qe6m8dq +a=ssrc:916070044 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 + )"); + + auto unsupportedVideoSdp = std::string(R"(m=video 9 UDP/TLS/RTP/SAVPF 200 230 250 270 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 37:C4:5C:9C:C9:DA:56:22:47:1F:8C:93:E1:A1:51:A8:15:94:78:1D:89:26:69:44:65:6C:C3:83:96:10:32:43 +a=setup:actpass +a=mid:1 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:200 abc/90000 +a=rtpmap:230 abc/90000 +a=fmtp:230 apt=0 +a=rtpmap:250 xyz/90000 +a=rtpmap:270 xyz/90000 +a=fmtp:270 apt=100 +a=ssrc:916070099 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 + )"); + + auto unsupportedAudioSdp = std::string(R"(m=audio 9 UDP/TLS/RTP/SAVPF 500 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 87:E6:EC:59:93:76:9F:42:7D:15:17:F6:8F:C4:29:AB:EA:3F:28:B6:DF:F8:14:2F:96:62:2F:16:98:F5:76:E5 +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=ssrc:331864867 cname:jyxeGEm09Qe6m8dq +a=ssrc:331864867 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 + )"); + + offerBase += "\n"; + offerBase += audioSdp; + offerBase += "\n"; + offerBase += videoSdp; + offerBase += "\n"; + offerBase += unsupportedVideoSdp; + offerBase += "\n"; + offerBase += unsupportedVideoSdp; + offerBase += "\n"; + offerBase += unsupportedAudioSdp; + offerBase += "\n"; + + assertLFAndCRLF((PCHAR) offerBase.c_str(), offerBase.size(), [](PCHAR sdp) { + RtcConfiguration configuration{}; + PRtcPeerConnection pRtcPeerConnection = nullptr; + RtcMediaStreamTrack track1{}; + RtcMediaStreamTrack track2{}; + PRtcRtpTransceiver transceiver1 = nullptr; + PRtcRtpTransceiver transceiver2 = nullptr; + RtcSessionDescriptionInit offerSdp{}; + RtcSessionDescriptionInit answerSdp{}; + + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + + track1.kind = MEDIA_STREAM_TRACK_KIND_AUDIO; + track1.codec = RTC_CODEC_OPUS; + STRNCPY(track1.streamId, "audioStream1", MAX_MEDIA_STREAM_ID_LEN); + STRNCPY(track1.trackId, "audioTrack1", MAX_MEDIA_STREAM_TRACK_ID_LEN); + + track2.kind = MEDIA_STREAM_TRACK_KIND_AUDIO; + track2.codec = RTC_CODEC_OPUS; + STRNCPY(track2.streamId, "videoStream1", MAX_MEDIA_STREAM_ID_LEN); + STRNCPY(track2.trackId, "videoTrack1", MAX_MEDIA_STREAM_TRACK_ID_LEN); + + offerSdp.type = SDP_TYPE_OFFER; + STRNCPY(offerSdp.sdp, (PCHAR) sdp, MAX_SESSION_DESCRIPTION_INIT_SDP_LEN); + + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, addSupportedCodec(pRtcPeerConnection, RTC_CODEC_OPUS)); + EXPECT_EQ(STATUS_SUCCESS, addSupportedCodec(pRtcPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE)); + + EXPECT_EQ(STATUS_SUCCESS, addTransceiver(pRtcPeerConnection, &track1, nullptr, &transceiver1)); + EXPECT_EQ(STATUS_SUCCESS, addTransceiver(pRtcPeerConnection, &track2, nullptr, &transceiver2)); + + EXPECT_EQ(STATUS_SUCCESS, setRemoteDescription(pRtcPeerConnection, &offerSdp)); + EXPECT_EQ(STATUS_SUCCESS, createAnswer(pRtcPeerConnection, &answerSdp)); + + std::regex pattern("m=[^\\s]*"); + std::string answerSdpString = std::string(answerSdp.sdp); + auto words_begin = std::sregex_iterator(answerSdpString.begin(), answerSdpString.end(), pattern); + auto words_end = std::sregex_iterator(); + + int count = std::distance(words_begin, words_end); + EXPECT_EQ(count, 5); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "fakeStream", answerSdp.sdp); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "fakeTrack", answerSdp.sdp); + closePeerConnection(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); + }); + + offerBase += unsupportedAudioSdp; + offerBase += "\n"; + + assertLFAndCRLF((PCHAR) offerBase.c_str(), offerBase.size(), [](PCHAR sdp) { + RtcConfiguration configuration{}; + PRtcPeerConnection pRtcPeerConnection = nullptr; + RtcSessionDescriptionInit offerSdp{}; + + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + + offerSdp.type = SDP_TYPE_OFFER; + STRNCPY(offerSdp.sdp, (PCHAR) sdp, MAX_SESSION_DESCRIPTION_INIT_SDP_LEN); + + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + + EXPECT_EQ(STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT, setRemoteDescription(pRtcPeerConnection, &offerSdp)); + closePeerConnection(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); + }); +} + + // if offer (remote) contains video m-line only then answer (local) should contain video m-line only // even if local side has other transceivers, i.e. audio TEST_F(SdpApiTest, offerMediaMultipleDirections_validateAnswerCorrectMatchingDirections) @@ -1036,6 +1268,7 @@ a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01 MEMSET(&rtcConfiguration, 0x00, SIZEOF(RtcConfiguration)); MEMSET(&rtcMediaStreamTrack, 0x00, SIZEOF(RtcMediaStreamTrack)); MEMSET(&rtcSessionDescriptionInit, 0x00, SIZEOF(RtcSessionDescriptionInit)); + MEMSET(&rtcRtpTransceiverInit, 0x00, SIZEOF(RtcRtpTransceiverInit)); EXPECT_EQ(createPeerConnection(&rtcConfiguration, &pRtcPeerConnection), STATUS_SUCCESS); EXPECT_EQ(addSupportedCodec(pRtcPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE), STATUS_SUCCESS); @@ -1511,6 +1744,7 @@ TEST_P(SdpApiTest_SdpMatch, populateSingleMediaSection_TestH264Fmtp) MEMSET(&rtcConfiguration, 0x00, SIZEOF(RtcConfiguration)); MEMSET(&track1, 0x00, SIZEOF(RtcMediaStreamTrack)); MEMSET(&rtcSessionDescriptionInit, 0x00, SIZEOF(RtcSessionDescriptionInit)); + MEMSET(&rtcRtpTransceiverInit, 0x00, SIZEOF(RtcRtpTransceiverInit)); EXPECT_EQ(createPeerConnection(&rtcConfiguration, &pRtcPeerConnection), STATUS_SUCCESS); EXPECT_EQ(addSupportedCodec(pRtcPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE), STATUS_SUCCESS);