1 Commits

Author SHA1 Message Date
Cameron Gutman 5cbd555b14 Fix thread sanitizer warnings with C11 atomics 2020-12-06 00:49:10 -06:00
59 changed files with 3479 additions and 14947 deletions
-9
View File
@@ -1,9 +0,0 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "daily"
-79
View File
@@ -1,79 +0,0 @@
---
name: CI
on:
pull_request:
branches:
- master
types:
- opened
- synchronize
- reopened
push:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# TODO: Win32 build cannot find OpenSSL
# - os: windows-latest
# cmake_args: -A Win32
- os: windows-latest
cmake_args: -A x64
- os: windows-11-arm
cmake_args: -A ARM64
- os: macos-latest
build_target: macos
cmake_args: -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl
- os: ubuntu-latest
cc: clang
cxx: clang++
- os: ubuntu-latest
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
cc: clang
cxx: clang++
- os: ubuntu-24.04-arm
cc: gcc
cxx: g++
- os: ubuntu-latest
cc: clang
cxx: clang++
cmake_args: -DUSE_MBEDTLS=ON
prebuild_cmd: sudo apt install -y libmbedtls-dev
- os: ubuntu-latest
cc: gcc
cxx: g++
cmake_args: -DUSE_MBEDTLS=ON
prebuild_cmd: sudo apt install -y libmbedtls-dev
runs-on: ${{ matrix.os }}
name: Build
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Prebuild
if: matrix.prebuild_cmd
run: |
eval "${{ matrix.prebuild_cmd }}"
- name: Build Debug
run: |
mkdir build_debug
cmake ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Debug -B build_debug -S .
cmake --build build_debug --config Debug
- name: Build Release
run: |
mkdir build_release
cmake ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Release -B build_release -S .
cmake --build build_release --config Release
-80
View File
@@ -1,80 +0,0 @@
---
name: "CodeQL"
on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
- cron: '43 20 * * 3'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 60 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language:
- cpp
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'recursive'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
build-mode: none
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
# CodeQL now supports no build mode for C++
# See: https://github.blog/changelog/2025-06-03-codeql-can-be-enabled-at-scale-on-c-c-repositories-in-public-preview-using-build-free-scanning/
if: false
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
-3
View File
@@ -1,9 +1,6 @@
.idea/
.vscode/
limelight-common/ARM/
limelight-common/Debug/
Build/
cmake-*/
**/xcuserdata/
limelight-common/Release/
*.sdf
-3
View File
@@ -1,6 +1,3 @@
[submodule "enet"]
path = enet
url = https://github.com/cgutman/enet.git
[submodule "nanors/deps/simde"]
path = nanors/deps/simde
url = https://github.com/simd-everywhere/simde-no-tests.git
+18 -105
View File
@@ -1,124 +1,37 @@
cmake_minimum_required(VERSION 3.1...4.0)
cmake_minimum_required(VERSION 3.1)
project(moonlight-common-c LANGUAGES C)
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
option(USE_MBEDTLS "Use MbedTLS instead of OpenSSL" OFF)
option(CODE_ANALYSIS "Run code analysis during compilation" OFF)
SET(CMAKE_C_STANDARD 11)
set(CMAKE_POSITION_INDEPENDENT_CODE_BACKUP ${CMAKE_POSITION_INDEPENDENT_CODE})
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_subdirectory(enet)
set(CMAKE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE_BACKUP})
unset(CMAKE_POSITION_INDEPENDENT_CODE_BACKUP)
find_package(OpenSSL 1.0.2 REQUIRED)
aux_source_directory(src SRC_LIST)
# Build shared library by default, but allows user override
if (NOT DEFINED BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS ON)
set(BUILD_SHARED_LIBS_OVERRIDE ON)
endif()
add_library(moonlight-common-c ${SRC_LIST})
if (BUILD_SHARED_LIBS_OVERRIDE)
unset(BUILD_SHARED_LIBS)
unset(BUILD_SHARED_LIBS_OVERRIDE)
endif()
target_link_libraries(moonlight-common-c PRIVATE enet)
aux_source_directory(enet SRC_LIST)
aux_source_directory(reedsolomon SRC_LIST)
if(MSVC)
target_compile_options(moonlight-common-c PRIVATE /W3 /wd4100 /wd4232 /wd5105 /WX)
target_link_libraries(moonlight-common-c PRIVATE ws2_32.lib winmm.lib)
elseif(MINGW)
target_link_libraries(moonlight-common-c PRIVATE -lws2_32 -lwinmm)
add_compile_options(/W4 /wd4100 /wd4232)
add_definitions(-D_CRT_SECURE_NO_WARNINGS=1 -D_CRT_NONSTDC_NO_DEPRECATE=1)
link_libraries(ws2_32.lib qwave.lib winmm.lib)
else()
target_compile_options(moonlight-common-c PRIVATE -Wall -Wextra -Wno-unused-parameter -Werror)
if (CODE_ANALYSIS AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_compile_options(moonlight-common-c PRIVATE -fanalyzer)
endif()
endif()
if (USE_MBEDTLS)
target_compile_definitions(moonlight-common-c PRIVATE USE_MBEDTLS)
find_package(MbedTLS QUIET)
if (MBEDTLS_FOUND)
target_link_libraries(moonlight-common-c PRIVATE ${MBEDCRYPTO_LIBRARY})
target_include_directories(moonlight-common-c SYSTEM PRIVATE ${MBEDTLS_INCLUDE_DIRS})
else()
# For sub project added via CMake
target_link_libraries(moonlight-common-c PRIVATE mbedcrypto)
endif()
else()
find_package(OpenSSL 1.0.2 REQUIRED)
target_link_libraries(moonlight-common-c PRIVATE ${OPENSSL_CRYPTO_LIBRARY})
target_include_directories(moonlight-common-c SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Werror)
endif()
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
target_compile_definitions(moonlight-common-c PRIVATE LC_DEBUG)
add_definitions(-DLC_DEBUG)
else()
target_compile_definitions(moonlight-common-c PRIVATE NDEBUG)
# Avoid false "maybe uninitialized" warning generated by old GCC versions
# when building with -O2
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_compile_options(moonlight-common-c PRIVATE -Wno-maybe-uninitialized)
endif()
add_definitions(-DNDEBUG)
endif()
if (NOT(MSVC OR APPLE))
include(CheckLibraryExists)
CHECK_LIBRARY_EXISTS(rt clock_gettime "" HAVE_CLOCK_GETTIME)
add_definitions(-DHAS_SOCKLEN_T)
if (NOT HAVE_CLOCK_GETTIME)
set(CMAKE_EXTRA_INCLUDE_FILES time.h)
CHECK_FUNCTION_EXISTS(clock_gettime HAVE_CLOCK_GETTIME)
SET(CMAKE_EXTRA_INCLUDE_FILES)
endif()
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/enet/include
${CMAKE_CURRENT_SOURCE_DIR}/reedsolomon
${OPENSSL_INCLUDE_DIR})
foreach(clock CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW)
message(STATUS "Testing whether ${clock} can be used")
CHECK_CXX_SOURCE_COMPILES(
"#define _POSIX_C_SOURCE 200112L
#include <time.h>
int main ()
{
struct timespec ts[1];
clock_gettime (${clock}, ts);
return 0;
}" HAVE_${clock})
if(HAVE_${clock})
message(STATUS "Testing whether ${clock} can be used -- Success")
else()
message(STATUS "Testing whether ${clock} can be used -- Failed")
endif()
endforeach()
link_libraries(${OPENSSL_CRYPTO_LIBRARY})
endif()
target_include_directories(moonlight-common-c SYSTEM PUBLIC src)
target_include_directories(moonlight-common-c SYSTEM PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/nanors
${CMAKE_CURRENT_SOURCE_DIR}/nanors/deps
${CMAKE_CURRENT_SOURCE_DIR}/nanors/deps/obl
)
target_compile_definitions(moonlight-common-c PRIVATE HAS_SOCKLEN_T)
# nanors
if (MSVC)
set_source_files_properties("${CMAKE_SOURCE_DIR}/src/rswrapper.c"
DIRECTORY "${CMAKE_SOURCE_DIR}")
else()
set_source_files_properties("${CMAKE_SOURCE_DIR}/src/rswrapper.c"
DIRECTORY "${CMAKE_SOURCE_DIR}"
PROPERTIES COMPILE_FLAGS "-ftree-vectorize -funroll-loops")
endif()
add_library(moonlight-common-c SHARED ${SRC_LIST})
+37
View File
@@ -0,0 +1,37 @@
clone_depth: 1
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
CMAKE_ARGS: -DCMAKE_SYSTEM_VERSION=10.0.18362.0 -A Win32
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
CMAKE_ARGS: -DCMAKE_SYSTEM_VERSION=10.0.18362.0 -A x64
- APPVEYOR_BUILD_WORKER_IMAGE: macOS
BUILD_TARGET: macos
CMAKE_ARGS: -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
CC: clang
CXX: clang++
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
CC: gcc
CXX: g++
before_build:
- 'git submodule update --init --recursive'
build_script:
- 'mkdir build_debug'
- 'cd build_debug'
- sh: 'cmake $CMAKE_ARGS -DCMAKE_BUILD_TYPE=Debug ..'
- cmd: 'cmake %CMAKE_ARGS% -DCMAKE_BUILD_TYPE=Debug ..'
- sh: 'cmake --build .'
- cmd: 'cmake --build . --config Debug'
- 'cd ..'
- 'mkdir build_release'
- 'cd build_release'
- sh: 'cmake $CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release ..'
- cmd: 'cmake %CMAKE_ARGS% -DCMAKE_BUILD_TYPE=Release ..'
- sh: 'cmake --build .'
- cmd: 'cmake --build . --config Release'
deploy: off
-21
View File
@@ -1,21 +0,0 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/version.h)
if (MBEDTLS_INCLUDE_DIRS AND EXISTS ${MBEDTLS_INCLUDE_DIRS}/mbedtls/version.h)
file(STRINGS "${MBEDTLS_INCLUDE_DIRS}/mbedtls/version.h" MBEDTLS_VERSION_STRING_LINE REGEX "^#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"[0-9.]+\"$")
string(REGEX REPLACE "^#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"([0-9.]+)\"$" "\\1" MBEDTLS_VERSION_STRING "${MBEDTLS_VERSION_STRING_LINE}")
unset(MBEDTLS_VERSION_STRING_LINE)
endif()
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MbedTLS
REQUIRED_VARS MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS
VERSION_VAR MBEDTLS_VERSION_STRING
)
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
+1 -1
Submodule enet updated: c7353c0593...2a788029bf
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Joseph Calderon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-11
View File
@@ -1,11 +0,0 @@
#if defined(__AVX512F__)
#define OBLAS_AVX512
#else
#if defined(__AVX2__)
#define OBLAS_AVX2
#else
#if defined(__SSSE3__)
#define OBLAS_SSE3
#endif
#endif
#endif
File diff suppressed because it is too large Load Diff
-603
View File
@@ -1,603 +0,0 @@
/* these tables were generated with polynomial: 285 */
#ifndef GF2_8_TABLES
#define GF2_8_TABLES
/* clang-format off */
static const uint8_t GF2_8_LOG[] =
{
255, 0, 1, 25, 2, 50, 26,198, 3,223, 51,238, 27,104,199, 75,
4,100,224, 14, 52,141,239,129, 28,193,105,248,200, 8, 76,113,
5,138,101, 47,225, 36, 15, 33, 53,147,142,218,240, 18,130, 69,
29,181,194,125,106, 39,249,185,201,154, 9,120, 77,228,114,166,
6,191,139, 98,102,221, 48,253,226,152, 37,179, 16,145, 34,136,
54,208,148,206,143,150,219,189,241,210, 19, 92,131, 56, 70, 64,
30, 66,182,163,195, 72,126,110,107, 58, 40, 84,250,133,186, 61,
202, 94,155,159, 10, 21,121, 43, 78,212,229,172,115,243,167, 87,
7,112,192,247,140,128, 99, 13,103, 74,222,237, 49,197,254, 24,
227,165,153,119, 38,184,180,124, 17, 68,146,217, 35, 32,137, 46,
55, 63,209, 91,149,188,207,205,144,135,151,178,220,252,190, 97,
242, 86,211,171, 20, 42, 93,158,132, 60, 57, 83, 71,109, 65,162,
31, 45, 67,216,183,123,164,118,196, 23, 73,236,127, 12,111,246,
108,161, 59, 82, 41,157, 85,170,251, 96,134,177,187,204, 62, 90,
203, 89, 95,176,156,169,160, 81, 11,245, 22,235,122,117, 44,215,
79,174,213,233,230,231,173,232,116,214,244,234,168, 80, 88,175,
};
static const uint8_t GF2_8_EXP[] =
{
1, 2, 4, 8, 16, 32, 64,128, 29, 58,116,232,205,135, 19, 38,
76,152, 45, 90,180,117,234,201,143, 3, 6, 12, 24, 48, 96,192,
157, 39, 78,156, 37, 74,148, 53,106,212,181,119,238,193,159, 35,
70,140, 5, 10, 20, 40, 80,160, 93,186,105,210,185,111,222,161,
95,190, 97,194,153, 47, 94,188,101,202,137, 15, 30, 60,120,240,
253,231,211,187,107,214,177,127,254,225,223,163, 91,182,113,226,
217,175, 67,134, 17, 34, 68,136, 13, 26, 52,104,208,189,103,206,
129, 31, 62,124,248,237,199,147, 59,118,236,197,151, 51,102,204,
133, 23, 46, 92,184,109,218,169, 79,158, 33, 66,132, 21, 42, 84,
168, 77,154, 41, 82,164, 85,170, 73,146, 57,114,228,213,183,115,
230,209,191, 99,198,145, 63,126,252,229,215,179,123,246,241,255,
227,219,171, 75,150, 49, 98,196,149, 55,110,220,165, 87,174, 65,
130, 25, 50,100,200,141, 7, 14, 28, 56,112,224,221,167, 83,166,
81,162, 89,178,121,242,249,239,195,155, 43, 86,172, 69,138, 9,
18, 36, 72,144, 61,122,244,245,247,243,251,235,203,139, 11, 22,
44, 88,176,125,250,233,207,131, 27, 54,108,216,173, 71,142, 1,
2, 4, 8, 16, 32, 64,128, 29, 58,116,232,205,135, 19, 38, 76,
152, 45, 90,180,117,234,201,143, 3, 6, 12, 24, 48, 96,192,157,
39, 78,156, 37, 74,148, 53,106,212,181,119,238,193,159, 35, 70,
140, 5, 10, 20, 40, 80,160, 93,186,105,210,185,111,222,161, 95,
190, 97,194,153, 47, 94,188,101,202,137, 15, 30, 60,120,240,253,
231,211,187,107,214,177,127,254,225,223,163, 91,182,113,226,217,
175, 67,134, 17, 34, 68,136, 13, 26, 52,104,208,189,103,206,129,
31, 62,124,248,237,199,147, 59,118,236,197,151, 51,102,204,133,
23, 46, 92,184,109,218,169, 79,158, 33, 66,132, 21, 42, 84,168,
77,154, 41, 82,164, 85,170, 73,146, 57,114,228,213,183,115,230,
209,191, 99,198,145, 63,126,252,229,215,179,123,246,241,255,227,
219,171, 75,150, 49, 98,196,149, 55,110,220,165, 87,174, 65,130,
25, 50,100,200,141, 7, 14, 28, 56,112,224,221,167, 83,166, 81,
162, 89,178,121,242,249,239,195,155, 43, 86,172, 69,138, 9, 18,
36, 72,144, 61,122,244,245,247,243,251,235,203,139, 11, 22, 44,
88,176,125,250,233,207,131, 27, 54,108,216,173, 71,142,};
static const uint8_t GF2_8_INV[] =
{
0, 1,142,244, 71,167,122,186,173,157,221,152, 61,170, 93,150,
216,114,192, 88,224, 62, 76,102,144,222, 85,128,160,131, 75, 42,
108,237, 57, 81, 96, 86, 44,138,112,208, 31, 74, 38,139, 51,110,
72,137,111, 46,164,195, 64, 94, 80, 34,207,169,171, 12, 21,225,
54, 95,248,213,146, 78,166, 4, 48,136, 43, 30, 22,103, 69,147,
56, 35,104,140,129, 26, 37, 97, 19,193,203, 99,151, 14, 55, 65,
36, 87,202, 91,185,196, 23, 77, 82,141,239,179, 32,236, 47, 50,
40,209, 17,217,233,251,218,121,219,119, 6,187,132,205,254,252,
27, 84,161, 29,124,204,228,176, 73, 49, 39, 45, 83,105, 2,245,
24,223, 68, 79,155,188, 15, 92, 11,220,189,148,172, 9,199,162,
28,130,159,198, 52,194, 70, 5,206, 59, 13, 60,156, 8,190,183,
135,229,238,107,235,242,191,175,197,100, 7,123,149,154,174,182,
18, 89,165, 53,101,184,163,158,210,247, 98, 90,133,125,168, 58,
41,113,200,246,249, 67,215,214, 16,115,118,120,153, 10, 25,145,
20, 63,230,240,134,177,226,241,250,116,243,180,109, 33,178,106,
227,231,181,234, 3,143,211,201, 66,212,232,117,127,255,126,253,
};
static const uint8_t GF2_8_SHUF_LO[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
0, 3, 6, 5, 12, 15, 10, 9, 24, 27, 30, 29, 20, 23, 18, 17,
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60,
0, 5, 10, 15, 20, 17, 30, 27, 40, 45, 34, 39, 60, 57, 54, 51,
0, 6, 12, 10, 24, 30, 20, 18, 48, 54, 60, 58, 40, 46, 36, 34,
0, 7, 14, 9, 28, 27, 18, 21, 56, 63, 54, 49, 36, 35, 42, 45,
0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96,104,112,120,
0, 9, 18, 27, 36, 45, 54, 63, 72, 65, 90, 83,108,101,126,119,
0, 10, 20, 30, 40, 34, 60, 54, 80, 90, 68, 78,120,114,108,102,
0, 11, 22, 29, 44, 39, 58, 49, 88, 83, 78, 69,116,127, 98,105,
0, 12, 24, 20, 48, 60, 40, 36, 96,108,120,116, 80, 92, 72, 68,
0, 13, 26, 23, 52, 57, 46, 35,104,101,114,127, 92, 81, 70, 75,
0, 14, 28, 18, 56, 54, 36, 42,112,126,108, 98, 72, 70, 84, 90,
0, 15, 30, 17, 60, 51, 34, 45,120,119,102,105, 68, 75, 90, 85,
0, 16, 32, 48, 64, 80, 96,112,128,144,160,176,192,208,224,240,
0, 17, 34, 51, 68, 85,102,119,136,153,170,187,204,221,238,255,
0, 18, 36, 54, 72, 90,108,126,144,130,180,166,216,202,252,238,
0, 19, 38, 53, 76, 95,106,121,152,139,190,173,212,199,242,225,
0, 20, 40, 60, 80, 68,120,108,160,180,136,156,240,228,216,204,
0, 21, 42, 63, 84, 65,126,107,168,189,130,151,252,233,214,195,
0, 22, 44, 58, 88, 78,116, 98,176,166,156,138,232,254,196,210,
0, 23, 46, 57, 92, 75,114,101,184,175,150,129,228,243,202,221,
0, 24, 48, 40, 96,120, 80, 72,192,216,240,232,160,184,144,136,
0, 25, 50, 43,100,125, 86, 79,200,209,250,227,172,181,158,135,
0, 26, 52, 46,104,114, 92, 70,208,202,228,254,184,162,140,150,
0, 27, 54, 45,108,119, 90, 65,216,195,238,245,180,175,130,153,
0, 28, 56, 36,112,108, 72, 84,224,252,216,196,144,140,168,180,
0, 29, 58, 39,116,105, 78, 83,232,245,210,207,156,129,166,187,
0, 30, 60, 34,120,102, 68, 90,240,238,204,210,136,150,180,170,
0, 31, 62, 33,124, 99, 66, 93,248,231,198,217,132,155,186,165,
0, 32, 64, 96,128,160,192,224, 29, 61, 93,125,157,189,221,253,
0, 33, 66, 99,132,165,198,231, 21, 52, 87,118,145,176,211,242,
0, 34, 68,102,136,170,204,238, 13, 47, 73,107,133,167,193,227,
0, 35, 70,101,140,175,202,233, 5, 38, 67, 96,137,170,207,236,
0, 36, 72,108,144,180,216,252, 61, 25,117, 81,173,137,229,193,
0, 37, 74,111,148,177,222,251, 53, 16,127, 90,161,132,235,206,
0, 38, 76,106,152,190,212,242, 45, 11, 97, 71,181,147,249,223,
0, 39, 78,105,156,187,210,245, 37, 2,107, 76,185,158,247,208,
0, 40, 80,120,160,136,240,216, 93,117, 13, 37,253,213,173,133,
0, 41, 82,123,164,141,246,223, 85,124, 7, 46,241,216,163,138,
0, 42, 84,126,168,130,252,214, 77,103, 25, 51,229,207,177,155,
0, 43, 86,125,172,135,250,209, 69,110, 19, 56,233,194,191,148,
0, 44, 88,116,176,156,232,196,125, 81, 37, 9,205,225,149,185,
0, 45, 90,119,180,153,238,195,117, 88, 47, 2,193,236,155,182,
0, 46, 92,114,184,150,228,202,109, 67, 49, 31,213,251,137,167,
0, 47, 94,113,188,147,226,205,101, 74, 59, 20,217,246,135,168,
0, 48, 96, 80,192,240,160,144,157,173,253,205, 93,109, 61, 13,
0, 49, 98, 83,196,245,166,151,149,164,247,198, 81, 96, 51, 2,
0, 50,100, 86,200,250,172,158,141,191,233,219, 69,119, 33, 19,
0, 51,102, 85,204,255,170,153,133,182,227,208, 73,122, 47, 28,
0, 52,104, 92,208,228,184,140,189,137,213,225,109, 89, 5, 49,
0, 53,106, 95,212,225,190,139,181,128,223,234, 97, 84, 11, 62,
0, 54,108, 90,216,238,180,130,173,155,193,247,117, 67, 25, 47,
0, 55,110, 89,220,235,178,133,165,146,203,252,121, 78, 23, 32,
0, 56,112, 72,224,216,144,168,221,229,173,149, 61, 5, 77,117,
0, 57,114, 75,228,221,150,175,213,236,167,158, 49, 8, 67,122,
0, 58,116, 78,232,210,156,166,205,247,185,131, 37, 31, 81,107,
0, 59,118, 77,236,215,154,161,197,254,179,136, 41, 18, 95,100,
0, 60,120, 68,240,204,136,180,253,193,133,185, 13, 49,117, 73,
0, 61,122, 71,244,201,142,179,245,200,143,178, 1, 60,123, 70,
0, 62,124, 66,248,198,132,186,237,211,145,175, 21, 43,105, 87,
0, 63,126, 65,252,195,130,189,229,218,155,164, 25, 38,103, 88,
0, 64,128,192, 29, 93,157,221, 58,122,186,250, 39,103,167,231,
0, 65,130,195, 25, 88,155,218, 50,115,176,241, 43,106,169,232,
0, 66,132,198, 21, 87,145,211, 42,104,174,236, 63,125,187,249,
0, 67,134,197, 17, 82,151,212, 34, 97,164,231, 51,112,181,246,
0, 68,136,204, 13, 73,133,193, 26, 94,146,214, 23, 83,159,219,
0, 69,138,207, 9, 76,131,198, 18, 87,152,221, 27, 94,145,212,
0, 70,140,202, 5, 67,137,207, 10, 76,134,192, 15, 73,131,197,
0, 71,142,201, 1, 70,143,200, 2, 69,140,203, 3, 68,141,202,
0, 72,144,216, 61,117,173,229,122, 50,234,162, 71, 15,215,159,
0, 73,146,219, 57,112,171,226,114, 59,224,169, 75, 2,217,144,
0, 74,148,222, 53,127,161,235,106, 32,254,180, 95, 21,203,129,
0, 75,150,221, 49,122,167,236, 98, 41,244,191, 83, 24,197,142,
0, 76,152,212, 45, 97,181,249, 90, 22,194,142,119, 59,239,163,
0, 77,154,215, 41,100,179,254, 82, 31,200,133,123, 54,225,172,
0, 78,156,210, 37,107,185,247, 74, 4,214,152,111, 33,243,189,
0, 79,158,209, 33,110,191,240, 66, 13,220,147, 99, 44,253,178,
0, 80,160,240, 93, 13,253,173,186,234, 26, 74,231,183, 71, 23,
0, 81,162,243, 89, 8,251,170,178,227, 16, 65,235,186, 73, 24,
0, 82,164,246, 85, 7,241,163,170,248, 14, 92,255,173, 91, 9,
0, 83,166,245, 81, 2,247,164,162,241, 4, 87,243,160, 85, 6,
0, 84,168,252, 77, 25,229,177,154,206, 50,102,215,131,127, 43,
0, 85,170,255, 73, 28,227,182,146,199, 56,109,219,142,113, 36,
0, 86,172,250, 69, 19,233,191,138,220, 38,112,207,153, 99, 53,
0, 87,174,249, 65, 22,239,184,130,213, 44,123,195,148,109, 58,
0, 88,176,232,125, 37,205,149,250,162, 74, 18,135,223, 55,111,
0, 89,178,235,121, 32,203,146,242,171, 64, 25,139,210, 57, 96,
0, 90,180,238,117, 47,193,155,234,176, 94, 4,159,197, 43,113,
0, 91,182,237,113, 42,199,156,226,185, 84, 15,147,200, 37,126,
0, 92,184,228,109, 49,213,137,218,134, 98, 62,183,235, 15, 83,
0, 93,186,231,105, 52,211,142,210,143,104, 53,187,230, 1, 92,
0, 94,188,226,101, 59,217,135,202,148,118, 40,175,241, 19, 77,
0, 95,190,225, 97, 62,223,128,194,157,124, 35,163,252, 29, 66,
0, 96,192,160,157,253, 93, 61, 39, 71,231,135,186,218,122, 26,
0, 97,194,163,153,248, 91, 58, 47, 78,237,140,182,215,116, 21,
0, 98,196,166,149,247, 81, 51, 55, 85,243,145,162,192,102, 4,
0, 99,198,165,145,242, 87, 52, 63, 92,249,154,174,205,104, 11,
0,100,200,172,141,233, 69, 33, 7, 99,207,171,138,238, 66, 38,
0,101,202,175,137,236, 67, 38, 15,106,197,160,134,227, 76, 41,
0,102,204,170,133,227, 73, 47, 23,113,219,189,146,244, 94, 56,
0,103,206,169,129,230, 79, 40, 31,120,209,182,158,249, 80, 55,
0,104,208,184,189,213,109, 5,103, 15,183,223,218,178, 10, 98,
0,105,210,187,185,208,107, 2,111, 6,189,212,214,191, 4,109,
0,106,212,190,181,223, 97, 11,119, 29,163,201,194,168, 22,124,
0,107,214,189,177,218,103, 12,127, 20,169,194,206,165, 24,115,
0,108,216,180,173,193,117, 25, 71, 43,159,243,234,134, 50, 94,
0,109,218,183,169,196,115, 30, 79, 34,149,248,230,139, 60, 81,
0,110,220,178,165,203,121, 23, 87, 57,139,229,242,156, 46, 64,
0,111,222,177,161,206,127, 16, 95, 48,129,238,254,145, 32, 79,
0,112,224,144,221,173, 61, 77,167,215, 71, 55,122, 10,154,234,
0,113,226,147,217,168, 59, 74,175,222, 77, 60,118, 7,148,229,
0,114,228,150,213,167, 49, 67,183,197, 83, 33, 98, 16,134,244,
0,115,230,149,209,162, 55, 68,191,204, 89, 42,110, 29,136,251,
0,116,232,156,205,185, 37, 81,135,243,111, 27, 74, 62,162,214,
0,117,234,159,201,188, 35, 86,143,250,101, 16, 70, 51,172,217,
0,118,236,154,197,179, 41, 95,151,225,123, 13, 82, 36,190,200,
0,119,238,153,193,182, 47, 88,159,232,113, 6, 94, 41,176,199,
0,120,240,136,253,133, 13,117,231,159, 23,111, 26, 98,234,146,
0,121,242,139,249,128, 11,114,239,150, 29,100, 22,111,228,157,
0,122,244,142,245,143, 1,123,247,141, 3,121, 2,120,246,140,
0,123,246,141,241,138, 7,124,255,132, 9,114, 14,117,248,131,
0,124,248,132,237,145, 21,105,199,187, 63, 67, 42, 86,210,174,
0,125,250,135,233,148, 19,110,207,178, 53, 72, 38, 91,220,161,
0,126,252,130,229,155, 25,103,215,169, 43, 85, 50, 76,206,176,
0,127,254,129,225,158, 31, 96,223,160, 33, 94, 62, 65,192,191,
0,128, 29,157, 58,186, 39,167,116,244,105,233, 78,206, 83,211,
0,129, 31,158, 62,191, 33,160,124,253, 99,226, 66,195, 93,220,
0,130, 25,155, 50,176, 43,169,100,230,125,255, 86,212, 79,205,
0,131, 27,152, 54,181, 45,174,108,239,119,244, 90,217, 65,194,
0,132, 21,145, 42,174, 63,187, 84,208, 65,197,126,250,107,239,
0,133, 23,146, 46,171, 57,188, 92,217, 75,206,114,247,101,224,
0,134, 17,151, 34,164, 51,181, 68,194, 85,211,102,224,119,241,
0,135, 19,148, 38,161, 53,178, 76,203, 95,216,106,237,121,254,
0,136, 13,133, 26,146, 23,159, 52,188, 57,177, 46,166, 35,171,
0,137, 15,134, 30,151, 17,152, 60,181, 51,186, 34,171, 45,164,
0,138, 9,131, 18,152, 27,145, 36,174, 45,167, 54,188, 63,181,
0,139, 11,128, 22,157, 29,150, 44,167, 39,172, 58,177, 49,186,
0,140, 5,137, 10,134, 15,131, 20,152, 17,157, 30,146, 27,151,
0,141, 7,138, 14,131, 9,132, 28,145, 27,150, 18,159, 21,152,
0,142, 1,143, 2,140, 3,141, 4,138, 5,139, 6,136, 7,137,
0,143, 3,140, 6,137, 5,138, 12,131, 15,128, 10,133, 9,134,
0,144, 61,173,122,234, 71,215,244,100,201, 89,142, 30,179, 35,
0,145, 63,174,126,239, 65,208,252,109,195, 82,130, 19,189, 44,
0,146, 57,171,114,224, 75,217,228,118,221, 79,150, 4,175, 61,
0,147, 59,168,118,229, 77,222,236,127,215, 68,154, 9,161, 50,
0,148, 53,161,106,254, 95,203,212, 64,225,117,190, 42,139, 31,
0,149, 55,162,110,251, 89,204,220, 73,235,126,178, 39,133, 16,
0,150, 49,167, 98,244, 83,197,196, 82,245, 99,166, 48,151, 1,
0,151, 51,164,102,241, 85,194,204, 91,255,104,170, 61,153, 14,
0,152, 45,181, 90,194,119,239,180, 44,153, 1,238,118,195, 91,
0,153, 47,182, 94,199,113,232,188, 37,147, 10,226,123,205, 84,
0,154, 41,179, 82,200,123,225,164, 62,141, 23,246,108,223, 69,
0,155, 43,176, 86,205,125,230,172, 55,135, 28,250, 97,209, 74,
0,156, 37,185, 74,214,111,243,148, 8,177, 45,222, 66,251,103,
0,157, 39,186, 78,211,105,244,156, 1,187, 38,210, 79,245,104,
0,158, 33,191, 66,220, 99,253,132, 26,165, 59,198, 88,231,121,
0,159, 35,188, 70,217,101,250,140, 19,175, 48,202, 85,233,118,
0,160, 93,253,186, 26,231, 71,105,201, 52,148,211,115,142, 46,
0,161, 95,254,190, 31,225, 64, 97,192, 62,159,223,126,128, 33,
0,162, 89,251,178, 16,235, 73,121,219, 32,130,203,105,146, 48,
0,163, 91,248,182, 21,237, 78,113,210, 42,137,199,100,156, 63,
0,164, 85,241,170, 14,255, 91, 73,237, 28,184,227, 71,182, 18,
0,165, 87,242,174, 11,249, 92, 65,228, 22,179,239, 74,184, 29,
0,166, 81,247,162, 4,243, 85, 89,255, 8,174,251, 93,170, 12,
0,167, 83,244,166, 1,245, 82, 81,246, 2,165,247, 80,164, 3,
0,168, 77,229,154, 50,215,127, 41,129,100,204,179, 27,254, 86,
0,169, 79,230,158, 55,209,120, 33,136,110,199,191, 22,240, 89,
0,170, 73,227,146, 56,219,113, 57,147,112,218,171, 1,226, 72,
0,171, 75,224,150, 61,221,118, 49,154,122,209,167, 12,236, 71,
0,172, 69,233,138, 38,207, 99, 9,165, 76,224,131, 47,198,106,
0,173, 71,234,142, 35,201,100, 1,172, 70,235,143, 34,200,101,
0,174, 65,239,130, 44,195,109, 25,183, 88,246,155, 53,218,116,
0,175, 67,236,134, 41,197,106, 17,190, 82,253,151, 56,212,123,
0,176,125,205,250, 74,135, 55,233, 89,148, 36, 19,163,110,222,
0,177,127,206,254, 79,129, 48,225, 80,158, 47, 31,174, 96,209,
0,178,121,203,242, 64,139, 57,249, 75,128, 50, 11,185,114,192,
0,179,123,200,246, 69,141, 62,241, 66,138, 57, 7,180,124,207,
0,180,117,193,234, 94,159, 43,201,125,188, 8, 35,151, 86,226,
0,181,119,194,238, 91,153, 44,193,116,182, 3, 47,154, 88,237,
0,182,113,199,226, 84,147, 37,217,111,168, 30, 59,141, 74,252,
0,183,115,196,230, 81,149, 34,209,102,162, 21, 55,128, 68,243,
0,184,109,213,218, 98,183, 15,169, 17,196,124,115,203, 30,166,
0,185,111,214,222,103,177, 8,161, 24,206,119,127,198, 16,169,
0,186,105,211,210,104,187, 1,185, 3,208,106,107,209, 2,184,
0,187,107,208,214,109,189, 6,177, 10,218, 97,103,220, 12,183,
0,188,101,217,202,118,175, 19,137, 53,236, 80, 67,255, 38,154,
0,189,103,218,206,115,169, 20,129, 60,230, 91, 79,242, 40,149,
0,190, 97,223,194,124,163, 29,153, 39,248, 70, 91,229, 58,132,
0,191, 99,220,198,121,165, 26,145, 46,242, 77, 87,232, 52,139,
0,192,157, 93, 39,231,186,122, 78,142,211, 19,105,169,244, 52,
0,193,159, 94, 35,226,188,125, 70,135,217, 24,101,164,250, 59,
0,194,153, 91, 47,237,182,116, 94,156,199, 5,113,179,232, 42,
0,195,155, 88, 43,232,176,115, 86,149,205, 14,125,190,230, 37,
0,196,149, 81, 55,243,162,102,110,170,251, 63, 89,157,204, 8,
0,197,151, 82, 51,246,164, 97,102,163,241, 52, 85,144,194, 7,
0,198,145, 87, 63,249,174,104,126,184,239, 41, 65,135,208, 22,
0,199,147, 84, 59,252,168,111,118,177,229, 34, 77,138,222, 25,
0,200,141, 69, 7,207,138, 66, 14,198,131, 75, 9,193,132, 76,
0,201,143, 70, 3,202,140, 69, 6,207,137, 64, 5,204,138, 67,
0,202,137, 67, 15,197,134, 76, 30,212,151, 93, 17,219,152, 82,
0,203,139, 64, 11,192,128, 75, 22,221,157, 86, 29,214,150, 93,
0,204,133, 73, 23,219,146, 94, 46,226,171,103, 57,245,188,112,
0,205,135, 74, 19,222,148, 89, 38,235,161,108, 53,248,178,127,
0,206,129, 79, 31,209,158, 80, 62,240,191,113, 33,239,160,110,
0,207,131, 76, 27,212,152, 87, 54,249,181,122, 45,226,174, 97,
0,208,189,109,103,183,218, 10,206, 30,115,163,169,121, 20,196,
0,209,191,110, 99,178,220, 13,198, 23,121,168,165,116, 26,203,
0,210,185,107,111,189,214, 4,222, 12,103,181,177, 99, 8,218,
0,211,187,104,107,184,208, 3,214, 5,109,190,189,110, 6,213,
0,212,181, 97,119,163,194, 22,238, 58, 91,143,153, 77, 44,248,
0,213,183, 98,115,166,196, 17,230, 51, 81,132,149, 64, 34,247,
0,214,177,103,127,169,206, 24,254, 40, 79,153,129, 87, 48,230,
0,215,179,100,123,172,200, 31,246, 33, 69,146,141, 90, 62,233,
0,216,173,117, 71,159,234, 50,142, 86, 35,251,201, 17,100,188,
0,217,175,118, 67,154,236, 53,134, 95, 41,240,197, 28,106,179,
0,218,169,115, 79,149,230, 60,158, 68, 55,237,209, 11,120,162,
0,219,171,112, 75,144,224, 59,150, 77, 61,230,221, 6,118,173,
0,220,165,121, 87,139,242, 46,174,114, 11,215,249, 37, 92,128,
0,221,167,122, 83,142,244, 41,166,123, 1,220,245, 40, 82,143,
0,222,161,127, 95,129,254, 32,190, 96, 31,193,225, 63, 64,158,
0,223,163,124, 91,132,248, 39,182,105, 21,202,237, 50, 78,145,
0,224,221, 61,167, 71,122,154, 83,179,142,110,244, 20, 41,201,
0,225,223, 62,163, 66,124,157, 91,186,132,101,248, 25, 39,198,
0,226,217, 59,175, 77,118,148, 67,161,154,120,236, 14, 53,215,
0,227,219, 56,171, 72,112,147, 75,168,144,115,224, 3, 59,216,
0,228,213, 49,183, 83, 98,134,115,151,166, 66,196, 32, 17,245,
0,229,215, 50,179, 86,100,129,123,158,172, 73,200, 45, 31,250,
0,230,209, 55,191, 89,110,136, 99,133,178, 84,220, 58, 13,235,
0,231,211, 52,187, 92,104,143,107,140,184, 95,208, 55, 3,228,
0,232,205, 37,135,111, 74,162, 19,251,222, 54,148,124, 89,177,
0,233,207, 38,131,106, 76,165, 27,242,212, 61,152,113, 87,190,
0,234,201, 35,143,101, 70,172, 3,233,202, 32,140,102, 69,175,
0,235,203, 32,139, 96, 64,171, 11,224,192, 43,128,107, 75,160,
0,236,197, 41,151,123, 82,190, 51,223,246, 26,164, 72, 97,141,
0,237,199, 42,147,126, 84,185, 59,214,252, 17,168, 69,111,130,
0,238,193, 47,159,113, 94,176, 35,205,226, 12,188, 82,125,147,
0,239,195, 44,155,116, 88,183, 43,196,232, 7,176, 95,115,156,
0,240,253, 13,231, 23, 26,234,211, 35, 46,222, 52,196,201, 57,
0,241,255, 14,227, 18, 28,237,219, 42, 36,213, 56,201,199, 54,
0,242,249, 11,239, 29, 22,228,195, 49, 58,200, 44,222,213, 39,
0,243,251, 8,235, 24, 16,227,203, 56, 48,195, 32,211,219, 40,
0,244,245, 1,247, 3, 2,246,243, 7, 6,242, 4,240,241, 5,
0,245,247, 2,243, 6, 4,241,251, 14, 12,249, 8,253,255, 10,
0,246,241, 7,255, 9, 14,248,227, 21, 18,228, 28,234,237, 27,
0,247,243, 4,251, 12, 8,255,235, 28, 24,239, 16,231,227, 20,
0,248,237, 21,199, 63, 42,210,147,107,126,134, 84,172,185, 65,
0,249,239, 22,195, 58, 44,213,155, 98,116,141, 88,161,183, 78,
0,250,233, 19,207, 53, 38,220,131,121,106,144, 76,182,165, 95,
0,251,235, 16,203, 48, 32,219,139,112, 96,155, 64,187,171, 80,
0,252,229, 25,215, 43, 50,206,179, 79, 86,170,100,152,129,125,
0,253,231, 26,211, 46, 52,201,187, 70, 92,161,104,149,143,114,
0,254,225, 31,223, 33, 62,192,163, 93, 66,188,124,130,157, 99,
0,255,227, 28,219, 36, 56,199,171, 84, 72,183,112,143,147,108,
};
static const uint8_t GF2_8_SHUF_HI[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 16, 32, 48, 64, 80, 96,112,128,144,160,176,192,208,224,240,
0, 32, 64, 96,128,160,192,224, 29, 61, 93,125,157,189,221,253,
0, 48, 96, 80,192,240,160,144,157,173,253,205, 93,109, 61, 13,
0, 64,128,192, 29, 93,157,221, 58,122,186,250, 39,103,167,231,
0, 80,160,240, 93, 13,253,173,186,234, 26, 74,231,183, 71, 23,
0, 96,192,160,157,253, 93, 61, 39, 71,231,135,186,218,122, 26,
0,112,224,144,221,173, 61, 77,167,215, 71, 55,122, 10,154,234,
0,128, 29,157, 58,186, 39,167,116,244,105,233, 78,206, 83,211,
0,144, 61,173,122,234, 71,215,244,100,201, 89,142, 30,179, 35,
0,160, 93,253,186, 26,231, 71,105,201, 52,148,211,115,142, 46,
0,176,125,205,250, 74,135, 55,233, 89,148, 36, 19,163,110,222,
0,192,157, 93, 39,231,186,122, 78,142,211, 19,105,169,244, 52,
0,208,189,109,103,183,218, 10,206, 30,115,163,169,121, 20,196,
0,224,221, 61,167, 71,122,154, 83,179,142,110,244, 20, 41,201,
0,240,253, 13,231, 23, 26,234,211, 35, 46,222, 52,196,201, 57,
0, 29, 58, 39,116,105, 78, 83,232,245,210,207,156,129,166,187,
0, 13, 26, 23, 52, 57, 46, 35,104,101,114,127, 92, 81, 70, 75,
0, 61,122, 71,244,201,142,179,245,200,143,178, 1, 60,123, 70,
0, 45, 90,119,180,153,238,195,117, 88, 47, 2,193,236,155,182,
0, 93,186,231,105, 52,211,142,210,143,104, 53,187,230, 1, 92,
0, 77,154,215, 41,100,179,254, 82, 31,200,133,123, 54,225,172,
0,125,250,135,233,148, 19,110,207,178, 53, 72, 38, 91,220,161,
0,109,218,183,169,196,115, 30, 79, 34,149,248,230,139, 60, 81,
0,157, 39,186, 78,211,105,244,156, 1,187, 38,210, 79,245,104,
0,141, 7,138, 14,131, 9,132, 28,145, 27,150, 18,159, 21,152,
0,189,103,218,206,115,169, 20,129, 60,230, 91, 79,242, 40,149,
0,173, 71,234,142, 35,201,100, 1,172, 70,235,143, 34,200,101,
0,221,167,122, 83,142,244, 41,166,123, 1,220,245, 40, 82,143,
0,205,135, 74, 19,222,148, 89, 38,235,161,108, 53,248,178,127,
0,253,231, 26,211, 46, 52,201,187, 70, 92,161,104,149,143,114,
0,237,199, 42,147,126, 84,185, 59,214,252, 17,168, 69,111,130,
0, 58,116, 78,232,210,156,166,205,247,185,131, 37, 31, 81,107,
0, 42, 84,126,168,130,252,214, 77,103, 25, 51,229,207,177,155,
0, 26, 52, 46,104,114, 92, 70,208,202,228,254,184,162,140,150,
0, 10, 20, 30, 40, 34, 60, 54, 80, 90, 68, 78,120,114,108,102,
0,122,244,142,245,143, 1,123,247,141, 3,121, 2,120,246,140,
0,106,212,190,181,223, 97, 11,119, 29,163,201,194,168, 22,124,
0, 90,180,238,117, 47,193,155,234,176, 94, 4,159,197, 43,113,
0, 74,148,222, 53,127,161,235,106, 32,254,180, 95, 21,203,129,
0,186,105,211,210,104,187, 1,185, 3,208,106,107,209, 2,184,
0,170, 73,227,146, 56,219,113, 57,147,112,218,171, 1,226, 72,
0,154, 41,179, 82,200,123,225,164, 62,141, 23,246,108,223, 69,
0,138, 9,131, 18,152, 27,145, 36,174, 45,167, 54,188, 63,181,
0,250,233, 19,207, 53, 38,220,131,121,106,144, 76,182,165, 95,
0,234,201, 35,143,101, 70,172, 3,233,202, 32,140,102, 69,175,
0,218,169,115, 79,149,230, 60,158, 68, 55,237,209, 11,120,162,
0,202,137, 67, 15,197,134, 76, 30,212,151, 93, 17,219,152, 82,
0, 39, 78,105,156,187,210,245, 37, 2,107, 76,185,158,247,208,
0, 55,110, 89,220,235,178,133,165,146,203,252,121, 78, 23, 32,
0, 7, 14, 9, 28, 27, 18, 21, 56, 63, 54, 49, 36, 35, 42, 45,
0, 23, 46, 57, 92, 75,114,101,184,175,150,129,228,243,202,221,
0,103,206,169,129,230, 79, 40, 31,120,209,182,158,249, 80, 55,
0,119,238,153,193,182, 47, 88,159,232,113, 6, 94, 41,176,199,
0, 71,142,201, 1, 70,143,200, 2, 69,140,203, 3, 68,141,202,
0, 87,174,249, 65, 22,239,184,130,213, 44,123,195,148,109, 58,
0,167, 83,244,166, 1,245, 82, 81,246, 2,165,247, 80,164, 3,
0,183,115,196,230, 81,149, 34,209,102,162, 21, 55,128, 68,243,
0,135, 19,148, 38,161, 53,178, 76,203, 95,216,106,237,121,254,
0,151, 51,164,102,241, 85,194,204, 91,255,104,170, 61,153, 14,
0,231,211, 52,187, 92,104,143,107,140,184, 95,208, 55, 3,228,
0,247,243, 4,251, 12, 8,255,235, 28, 24,239, 16,231,227, 20,
0,199,147, 84, 59,252,168,111,118,177,229, 34, 77,138,222, 25,
0,215,179,100,123,172,200, 31,246, 33, 69,146,141, 90, 62,233,
0,116,232,156,205,185, 37, 81,135,243,111, 27, 74, 62,162,214,
0,100,200,172,141,233, 69, 33, 7, 99,207,171,138,238, 66, 38,
0, 84,168,252, 77, 25,229,177,154,206, 50,102,215,131,127, 43,
0, 68,136,204, 13, 73,133,193, 26, 94,146,214, 23, 83,159,219,
0, 52,104, 92,208,228,184,140,189,137,213,225,109, 89, 5, 49,
0, 36, 72,108,144,180,216,252, 61, 25,117, 81,173,137,229,193,
0, 20, 40, 60, 80, 68,120,108,160,180,136,156,240,228,216,204,
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60,
0,244,245, 1,247, 3, 2,246,243, 7, 6,242, 4,240,241, 5,
0,228,213, 49,183, 83, 98,134,115,151,166, 66,196, 32, 17,245,
0,212,181, 97,119,163,194, 22,238, 58, 91,143,153, 77, 44,248,
0,196,149, 81, 55,243,162,102,110,170,251, 63, 89,157,204, 8,
0,180,117,193,234, 94,159, 43,201,125,188, 8, 35,151, 86,226,
0,164, 85,241,170, 14,255, 91, 73,237, 28,184,227, 71,182, 18,
0,148, 53,161,106,254, 95,203,212, 64,225,117,190, 42,139, 31,
0,132, 21,145, 42,174, 63,187, 84,208, 65,197,126,250,107,239,
0,105,210,187,185,208,107, 2,111, 6,189,212,214,191, 4,109,
0,121,242,139,249,128, 11,114,239,150, 29,100, 22,111,228,157,
0, 73,146,219, 57,112,171,226,114, 59,224,169, 75, 2,217,144,
0, 89,178,235,121, 32,203,146,242,171, 64, 25,139,210, 57, 96,
0, 41, 82,123,164,141,246,223, 85,124, 7, 46,241,216,163,138,
0, 57,114, 75,228,221,150,175,213,236,167,158, 49, 8, 67,122,
0, 9, 18, 27, 36, 45, 54, 63, 72, 65, 90, 83,108,101,126,119,
0, 25, 50, 43,100,125, 86, 79,200,209,250,227,172,181,158,135,
0,233,207, 38,131,106, 76,165, 27,242,212, 61,152,113, 87,190,
0,249,239, 22,195, 58, 44,213,155, 98,116,141, 88,161,183, 78,
0,201,143, 70, 3,202,140, 69, 6,207,137, 64, 5,204,138, 67,
0,217,175,118, 67,154,236, 53,134, 95, 41,240,197, 28,106,179,
0,169, 79,230,158, 55,209,120, 33,136,110,199,191, 22,240, 89,
0,185,111,214,222,103,177, 8,161, 24,206,119,127,198, 16,169,
0,137, 15,134, 30,151, 17,152, 60,181, 51,186, 34,171, 45,164,
0,153, 47,182, 94,199,113,232,188, 37,147, 10,226,123,205, 84,
0, 78,156,210, 37,107,185,247, 74, 4,214,152,111, 33,243,189,
0, 94,188,226,101, 59,217,135,202,148,118, 40,175,241, 19, 77,
0,110,220,178,165,203,121, 23, 87, 57,139,229,242,156, 46, 64,
0,126,252,130,229,155, 25,103,215,169, 43, 85, 50, 76,206,176,
0, 14, 28, 18, 56, 54, 36, 42,112,126,108, 98, 72, 70, 84, 90,
0, 30, 60, 34,120,102, 68, 90,240,238,204,210,136,150,180,170,
0, 46, 92,114,184,150,228,202,109, 67, 49, 31,213,251,137,167,
0, 62,124, 66,248,198,132,186,237,211,145,175, 21, 43,105, 87,
0,206,129, 79, 31,209,158, 80, 62,240,191,113, 33,239,160,110,
0,222,161,127, 95,129,254, 32,190, 96, 31,193,225, 63, 64,158,
0,238,193, 47,159,113, 94,176, 35,205,226, 12,188, 82,125,147,
0,254,225, 31,223, 33, 62,192,163, 93, 66,188,124,130,157, 99,
0,142, 1,143, 2,140, 3,141, 4,138, 5,139, 6,136, 7,137,
0,158, 33,191, 66,220, 99,253,132, 26,165, 59,198, 88,231,121,
0,174, 65,239,130, 44,195,109, 25,183, 88,246,155, 53,218,116,
0,190, 97,223,194,124,163, 29,153, 39,248, 70, 91,229, 58,132,
0, 83,166,245, 81, 2,247,164,162,241, 4, 87,243,160, 85, 6,
0, 67,134,197, 17, 82,151,212, 34, 97,164,231, 51,112,181,246,
0,115,230,149,209,162, 55, 68,191,204, 89, 42,110, 29,136,251,
0, 99,198,165,145,242, 87, 52, 63, 92,249,154,174,205,104, 11,
0, 19, 38, 53, 76, 95,106,121,152,139,190,173,212,199,242,225,
0, 3, 6, 5, 12, 15, 10, 9, 24, 27, 30, 29, 20, 23, 18, 17,
0, 51,102, 85,204,255,170,153,133,182,227,208, 73,122, 47, 28,
0, 35, 70,101,140,175,202,233, 5, 38, 67, 96,137,170,207,236,
0,211,187,104,107,184,208, 3,214, 5,109,190,189,110, 6,213,
0,195,155, 88, 43,232,176,115, 86,149,205, 14,125,190,230, 37,
0,243,251, 8,235, 24, 16,227,203, 56, 48,195, 32,211,219, 40,
0,227,219, 56,171, 72,112,147, 75,168,144,115,224, 3, 59,216,
0,147, 59,168,118,229, 77,222,236,127,215, 68,154, 9,161, 50,
0,131, 27,152, 54,181, 45,174,108,239,119,244, 90,217, 65,194,
0,179,123,200,246, 69,141, 62,241, 66,138, 57, 7,180,124,207,
0,163, 91,248,182, 21,237, 78,113,210, 42,137,199,100,156, 63,
0,232,205, 37,135,111, 74,162, 19,251,222, 54,148,124, 89,177,
0,248,237, 21,199, 63, 42,210,147,107,126,134, 84,172,185, 65,
0,200,141, 69, 7,207,138, 66, 14,198,131, 75, 9,193,132, 76,
0,216,173,117, 71,159,234, 50,142, 86, 35,251,201, 17,100,188,
0,168, 77,229,154, 50,215,127, 41,129,100,204,179, 27,254, 86,
0,184,109,213,218, 98,183, 15,169, 17,196,124,115,203, 30,166,
0,136, 13,133, 26,146, 23,159, 52,188, 57,177, 46,166, 35,171,
0,152, 45,181, 90,194,119,239,180, 44,153, 1,238,118,195, 91,
0,104,208,184,189,213,109, 5,103, 15,183,223,218,178, 10, 98,
0,120,240,136,253,133, 13,117,231,159, 23,111, 26, 98,234,146,
0, 72,144,216, 61,117,173,229,122, 50,234,162, 71, 15,215,159,
0, 88,176,232,125, 37,205,149,250,162, 74, 18,135,223, 55,111,
0, 40, 80,120,160,136,240,216, 93,117, 13, 37,253,213,173,133,
0, 56,112, 72,224,216,144,168,221,229,173,149, 61, 5, 77,117,
0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96,104,112,120,
0, 24, 48, 40, 96,120, 80, 72,192,216,240,232,160,184,144,136,
0,245,247, 2,243, 6, 4,241,251, 14, 12,249, 8,253,255, 10,
0,229,215, 50,179, 86,100,129,123,158,172, 73,200, 45, 31,250,
0,213,183, 98,115,166,196, 17,230, 51, 81,132,149, 64, 34,247,
0,197,151, 82, 51,246,164, 97,102,163,241, 52, 85,144,194, 7,
0,181,119,194,238, 91,153, 44,193,116,182, 3, 47,154, 88,237,
0,165, 87,242,174, 11,249, 92, 65,228, 22,179,239, 74,184, 29,
0,149, 55,162,110,251, 89,204,220, 73,235,126,178, 39,133, 16,
0,133, 23,146, 46,171, 57,188, 92,217, 75,206,114,247,101,224,
0,117,234,159,201,188, 35, 86,143,250,101, 16, 70, 51,172,217,
0,101,202,175,137,236, 67, 38, 15,106,197,160,134,227, 76, 41,
0, 85,170,255, 73, 28,227,182,146,199, 56,109,219,142,113, 36,
0, 69,138,207, 9, 76,131,198, 18, 87,152,221, 27, 94,145,212,
0, 53,106, 95,212,225,190,139,181,128,223,234, 97, 84, 11, 62,
0, 37, 74,111,148,177,222,251, 53, 16,127, 90,161,132,235,206,
0, 21, 42, 63, 84, 65,126,107,168,189,130,151,252,233,214,195,
0, 5, 10, 15, 20, 17, 30, 27, 40, 45, 34, 39, 60, 57, 54, 51,
0,210,185,107,111,189,214, 4,222, 12,103,181,177, 99, 8,218,
0,194,153, 91, 47,237,182,116, 94,156,199, 5,113,179,232, 42,
0,242,249, 11,239, 29, 22,228,195, 49, 58,200, 44,222,213, 39,
0,226,217, 59,175, 77,118,148, 67,161,154,120,236, 14, 53,215,
0,146, 57,171,114,224, 75,217,228,118,221, 79,150, 4,175, 61,
0,130, 25,155, 50,176, 43,169,100,230,125,255, 86,212, 79,205,
0,178,121,203,242, 64,139, 57,249, 75,128, 50, 11,185,114,192,
0,162, 89,251,178, 16,235, 73,121,219, 32,130,203,105,146, 48,
0, 82,164,246, 85, 7,241,163,170,248, 14, 92,255,173, 91, 9,
0, 66,132,198, 21, 87,145,211, 42,104,174,236, 63,125,187,249,
0,114,228,150,213,167, 49, 67,183,197, 83, 33, 98, 16,134,244,
0, 98,196,166,149,247, 81, 51, 55, 85,243,145,162,192,102, 4,
0, 18, 36, 54, 72, 90,108,126,144,130,180,166,216,202,252,238,
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
0, 50,100, 86,200,250,172,158,141,191,233,219, 69,119, 33, 19,
0, 34, 68,102,136,170,204,238, 13, 47, 73,107,133,167,193,227,
0,207,131, 76, 27,212,152, 87, 54,249,181,122, 45,226,174, 97,
0,223,163,124, 91,132,248, 39,182,105, 21,202,237, 50, 78,145,
0,239,195, 44,155,116, 88,183, 43,196,232, 7,176, 95,115,156,
0,255,227, 28,219, 36, 56,199,171, 84, 72,183,112,143,147,108,
0,143, 3,140, 6,137, 5,138, 12,131, 15,128, 10,133, 9,134,
0,159, 35,188, 70,217,101,250,140, 19,175, 48,202, 85,233,118,
0,175, 67,236,134, 41,197,106, 17,190, 82,253,151, 56,212,123,
0,191, 99,220,198,121,165, 26,145, 46,242, 77, 87,232, 52,139,
0, 79,158,209, 33,110,191,240, 66, 13,220,147, 99, 44,253,178,
0, 95,190,225, 97, 62,223,128,194,157,124, 35,163,252, 29, 66,
0,111,222,177,161,206,127, 16, 95, 48,129,238,254,145, 32, 79,
0,127,254,129,225,158, 31, 96,223,160, 33, 94, 62, 65,192,191,
0, 15, 30, 17, 60, 51, 34, 45,120,119,102,105, 68, 75, 90, 85,
0, 31, 62, 33,124, 99, 66, 93,248,231,198,217,132,155,186,165,
0, 47, 94,113,188,147,226,205,101, 74, 59, 20,217,246,135,168,
0, 63,126, 65,252,195,130,189,229,218,155,164, 25, 38,103, 88,
0,156, 37,185, 74,214,111,243,148, 8,177, 45,222, 66,251,103,
0,140, 5,137, 10,134, 15,131, 20,152, 17,157, 30,146, 27,151,
0,188,101,217,202,118,175, 19,137, 53,236, 80, 67,255, 38,154,
0,172, 69,233,138, 38,207, 99, 9,165, 76,224,131, 47,198,106,
0,220,165,121, 87,139,242, 46,174,114, 11,215,249, 37, 92,128,
0,204,133, 73, 23,219,146, 94, 46,226,171,103, 57,245,188,112,
0,252,229, 25,215, 43, 50,206,179, 79, 86,170,100,152,129,125,
0,236,197, 41,151,123, 82,190, 51,223,246, 26,164, 72, 97,141,
0, 28, 56, 36,112,108, 72, 84,224,252,216,196,144,140,168,180,
0, 12, 24, 20, 48, 60, 40, 36, 96,108,120,116, 80, 92, 72, 68,
0, 60,120, 68,240,204,136,180,253,193,133,185, 13, 49,117, 73,
0, 44, 88,116,176,156,232,196,125, 81, 37, 9,205,225,149,185,
0, 92,184,228,109, 49,213,137,218,134, 98, 62,183,235, 15, 83,
0, 76,152,212, 45, 97,181,249, 90, 22,194,142,119, 59,239,163,
0,124,248,132,237,145, 21,105,199,187, 63, 67, 42, 86,210,174,
0,108,216,180,173,193,117, 25, 71, 43,159,243,234,134, 50, 94,
0,129, 31,158, 62,191, 33,160,124,253, 99,226, 66,195, 93,220,
0,145, 63,174,126,239, 65,208,252,109,195, 82,130, 19,189, 44,
0,161, 95,254,190, 31,225, 64, 97,192, 62,159,223,126,128, 33,
0,177,127,206,254, 79,129, 48,225, 80,158, 47, 31,174, 96,209,
0,193,159, 94, 35,226,188,125, 70,135,217, 24,101,164,250, 59,
0,209,191,110, 99,178,220, 13,198, 23,121,168,165,116, 26,203,
0,225,223, 62,163, 66,124,157, 91,186,132,101,248, 25, 39,198,
0,241,255, 14,227, 18, 28,237,219, 42, 36,213, 56,201,199, 54,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 17, 34, 51, 68, 85,102,119,136,153,170,187,204,221,238,255,
0, 33, 66, 99,132,165,198,231, 21, 52, 87,118,145,176,211,242,
0, 49, 98, 83,196,245,166,151,149,164,247,198, 81, 96, 51, 2,
0, 65,130,195, 25, 88,155,218, 50,115,176,241, 43,106,169,232,
0, 81,162,243, 89, 8,251,170,178,227, 16, 65,235,186, 73, 24,
0, 97,194,163,153,248, 91, 58, 47, 78,237,140,182,215,116, 21,
0,113,226,147,217,168, 59, 74,175,222, 77, 60,118, 7,148,229,
0,166, 81,247,162, 4,243, 85, 89,255, 8,174,251, 93,170, 12,
0,182,113,199,226, 84,147, 37,217,111,168, 30, 59,141, 74,252,
0,134, 17,151, 34,164, 51,181, 68,194, 85,211,102,224,119,241,
0,150, 49,167, 98,244, 83,197,196, 82,245, 99,166, 48,151, 1,
0,230,209, 55,191, 89,110,136, 99,133,178, 84,220, 58, 13,235,
0,246,241, 7,255, 9, 14,248,227, 21, 18,228, 28,234,237, 27,
0,198,145, 87, 63,249,174,104,126,184,239, 41, 65,135,208, 22,
0,214,177,103,127,169,206, 24,254, 40, 79,153,129, 87, 48,230,
0, 38, 76,106,152,190,212,242, 45, 11, 97, 71,181,147,249,223,
0, 54,108, 90,216,238,180,130,173,155,193,247,117, 67, 25, 47,
0, 6, 12, 10, 24, 30, 20, 18, 48, 54, 60, 58, 40, 46, 36, 34,
0, 22, 44, 58, 88, 78,116, 98,176,166,156,138,232,254,196,210,
0,102,204,170,133,227, 73, 47, 23,113,219,189,146,244, 94, 56,
0,118,236,154,197,179, 41, 95,151,225,123, 13, 82, 36,190,200,
0, 70,140,202, 5, 67,137,207, 10, 76,134,192, 15, 73,131,197,
0, 86,172,250, 69, 19,233,191,138,220, 38,112,207,153, 99, 53,
0,187,107,208,214,109,189, 6,177, 10,218, 97,103,220, 12,183,
0,171, 75,224,150, 61,221,118, 49,154,122,209,167, 12,236, 71,
0,155, 43,176, 86,205,125,230,172, 55,135, 28,250, 97,209, 74,
0,139, 11,128, 22,157, 29,150, 44,167, 39,172, 58,177, 49,186,
0,251,235, 16,203, 48, 32,219,139,112, 96,155, 64,187,171, 80,
0,235,203, 32,139, 96, 64,171, 11,224,192, 43,128,107, 75,160,
0,219,171,112, 75,144,224, 59,150, 77, 61,230,221, 6,118,173,
0,203,139, 64, 11,192,128, 75, 22,221,157, 86, 29,214,150, 93,
0, 59,118, 77,236,215,154,161,197,254,179,136, 41, 18, 95,100,
0, 43, 86,125,172,135,250,209, 69,110, 19, 56,233,194,191,148,
0, 27, 54, 45,108,119, 90, 65,216,195,238,245,180,175,130,153,
0, 11, 22, 29, 44, 39, 58, 49, 88, 83, 78, 69,116,127, 98,105,
0,123,246,141,241,138, 7,124,255,132, 9,114, 14,117,248,131,
0,107,214,189,177,218,103, 12,127, 20,169,194,206,165, 24,115,
0, 91,182,237,113, 42,199,156,226,185, 84, 15,147,200, 37,126,
0, 75,150,221, 49,122,167,236, 98, 41,244,191, 83, 24,197,142,
};
/* clang-format on */
#endif
-282
View File
@@ -1,282 +0,0 @@
// Note about the frequent use of #undef before most defines in this file: These exist to
// prevent a lot of (harmless) redefined macro warnings if this file is re-included multiple
// times in the same build context. Sunshine and Moonlight use this trick to bundle all arch
// builds into the same binary along with runtime detection. An example can be found at
// https://github.com/LizardByte/Sunshine/blob/master/src/rswrapper.c
#include "oblas_lite.h"
#if defined(OBLAS_TINY)
static inline uint8_t gf2_8_mul(uint16_t a, uint16_t b)
{
if (!a || !b) {
return 0;
}
// Perform 8-bit, carry-less multiplication of |a| and |b|.
return GF2_8_EXP[GF2_8_LOG[a] + GF2_8_LOG[b]];
}
static void obl_axpy_ref(u8 *a, u8 *b, u8 u, unsigned k)
{
register u8 *ap = a, *ae = &a[k], *bp = b;
for (; ap != ae; ap++, bp++)
*ap ^= gf2_8_mul(u, *bp);
}
static void obl_scal_ref(u8 *a, u8 *b, u8 u, unsigned k)
{
(void)b;
register u8 *ap = a, *ae = &a[k];
for (; ap != ae; ap++)
*ap = gf2_8_mul(u, *ap);
}
#else
#include "gf2_8_mul_table.h"
static void obl_axpy_ref(u8 *a, u8 *b, u8 u, unsigned k)
{
register const u8 *u_row = &GF2_8_MUL[u << 8];
register u8 *ap = a, *ae = &a[k], *bp = b;
for (; ap != ae; ap++, bp++)
*ap ^= u_row[*bp];
}
static void obl_scal_ref(u8 *a, u8 *b, u8 u, unsigned k)
{
(void)b;
register const u8 *u_row = &GF2_8_MUL[u << 8];
register u8 *ap = a, *ae = &a[k];
for (; ap != ae; ap++)
*ap = u_row[*ap];
}
#endif
void obl_axpyb32_ref(u8 *a, u32 *b, u8 u, unsigned k)
{
for (unsigned idx = 0, p = 0; idx < k; idx += 8 * sizeof(u32), p++) {
u32 tmp = b[p];
while (tmp > 0) {
#ifdef _MSC_VER
unsigned long index = 0;
_BitScanForward(&index, tmp);
unsigned tz = (unsigned int)index;
#else
unsigned tz = __builtin_ctz(tmp);
#endif
tmp = tmp & (tmp - 1);
a[tz + idx] ^= u;
}
}
}
#if defined(OBLAS_AVX512)
#include <immintrin.h>
#undef OBLAS_ALIGN
#define OBLAS_ALIGN 64
#undef OBL_SHUF
#define OBL_SHUF(op, a, b, f) \
do { \
const u8 *u_lo = GF2_8_SHUF_LO + u * 16; \
const u8 *u_hi = GF2_8_SHUF_HI + u * 16; \
const __m512i mask = _mm512_set1_epi8(0x0f); \
const __m128i ulo_128 = _mm_loadu_si128((__m128i *)u_lo); \
const __m128i uhi_128 = _mm_loadu_si128((__m128i *)u_hi); \
const __m512i urow_lo = _mm512_broadcast_i32x4(ulo_128); \
const __m512i urow_hi = _mm512_broadcast_i32x4(uhi_128); \
__m512i *ap = (__m512i *)a, *ae = (__m512i *)(a + k - (k % sizeof(__m512i))), *bp = (__m512i *)b; \
for (; ap < ae; ap++, bp++) { \
__m512i bx = _mm512_loadu_si512(bp); \
__m512i lo = _mm512_and_si512(bx, mask); \
bx = _mm512_srli_epi64(bx, 4); \
__m512i hi = _mm512_and_si512(bx, mask); \
lo = _mm512_shuffle_epi8(urow_lo, lo); \
hi = _mm512_shuffle_epi8(urow_hi, hi); \
_mm512_storeu_si512(ap, f(_mm512_loadu_si512(ap), _mm512_xor_si512(lo, hi))); \
} \
op##_ref((u8 *)ap, (u8 *)bp, u, k % sizeof(__m512i)); \
} while (0)
#undef OBL_SHUF_XOR
#define OBL_SHUF_XOR _mm512_xor_si512
#undef OBL_AXPYB32
#define OBL_AXPYB32(a, b, u, k) \
do { \
__m512i *ap = (__m512i *)a, *ae = (__m512i *)(a + k); \
__m512i scatter = \
_mm512_set_epi32(0x03030303, 0x03030303, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x00000000, 0x00000000, \
0x03030303, 0x03030303, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x00000000, 0x00000000); \
__m512i cmpmask = \
_mm512_set_epi32(0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201, \
0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201); \
__m512i up = _mm512_set1_epi8(u); \
for (unsigned p = 0; ap < ae; p++, ap++) { \
__m512i bcast = _mm512_set1_epi32(b[p]); \
__m512i ret = _mm512_shuffle_epi8(bcast, scatter); \
ret = _mm512_andnot_si512(ret, cmpmask); \
__mmask64 tmp = _mm512_cmpeq_epi8_mask(ret, _mm512_setzero_si512()); \
ret = _mm512_mask_blend_epi8(tmp, _mm512_setzero_si512(), up); \
_mm512_storeu_si512(ap, _mm512_xor_si512(_mm512_loadu_si512(ap), ret)); \
} \
} while (0)
#elif defined(OBLAS_AVX2)
#include <immintrin.h>
#undef OBLAS_ALIGN
#define OBLAS_ALIGN 32
#undef OBL_SHUF
#define OBL_SHUF(op, a, b, f) \
do { \
const u8 *u_lo = GF2_8_SHUF_LO + u * 16; \
const u8 *u_hi = GF2_8_SHUF_HI + u * 16; \
const __m256i mask = _mm256_set1_epi8(0x0f); \
const __m256i urow_lo = _mm256_loadu2_m128i((__m128i *)u_lo, (__m128i *)u_lo); \
const __m256i urow_hi = _mm256_loadu2_m128i((__m128i *)u_hi, (__m128i *)u_hi); \
__m256i *ap = (__m256i *)a, *ae = (__m256i *)(a + k - (k % sizeof(__m256i))), *bp = (__m256i *)b; \
for (; ap < ae; ap++, bp++) { \
__m256i bx = _mm256_loadu_si256(bp); \
__m256i lo = _mm256_and_si256(bx, mask); \
bx = _mm256_srli_epi64(bx, 4); \
__m256i hi = _mm256_and_si256(bx, mask); \
lo = _mm256_shuffle_epi8(urow_lo, lo); \
hi = _mm256_shuffle_epi8(urow_hi, hi); \
_mm256_storeu_si256(ap, f(_mm256_loadu_si256(ap), _mm256_xor_si256(lo, hi))); \
} \
op##_ref((u8 *)ap, (u8 *)bp, u, k % sizeof(__m256i)); \
} while (0)
#undef OBL_SHUF_XOR
#define OBL_SHUF_XOR _mm256_xor_si256
#undef OBL_AXPYB32
#define OBL_AXPYB32(a, b, u, k) \
do { \
__m256i *ap = (__m256i *)a, *ae = (__m256i *)(a + k); \
__m256i scatter = \
_mm256_set_epi32(0x03030303, 0x03030303, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x00000000, 0x00000000); \
__m256i cmpmask = \
_mm256_set_epi32(0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201, 0x80402010, 0x08040201); \
__m256i up = _mm256_set1_epi8(u); \
for (unsigned p = 0; ap < ae; p++, ap++) { \
__m256i bcast = _mm256_set1_epi32(b[p]); \
__m256i ret = _mm256_shuffle_epi8(bcast, scatter); \
ret = _mm256_andnot_si256(ret, cmpmask); \
ret = _mm256_and_si256(_mm256_cmpeq_epi8(ret, _mm256_setzero_si256()), up); \
_mm256_storeu_si256(ap, _mm256_xor_si256(_mm256_loadu_si256(ap), ret)); \
} \
} while (0)
#elif defined(OBLAS_SSE3) || !(defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_AMD64))
#if defined(OBLAS_SSE3)
#include <emmintrin.h>
#include <tmmintrin.h>
#else
#define SIMDE_ENABLE_NATIVE_ALIASES
#include <simde/x86/ssse3.h>
#endif
#undef OBLAS_ALIGN
#define OBLAS_ALIGN 16
#undef OBL_SHUF
#define OBL_SHUF(op, a, b, f) \
do { \
const u8 *u_lo = GF2_8_SHUF_LO + u * 16; \
const u8 *u_hi = GF2_8_SHUF_HI + u * 16; \
const __m128i mask = _mm_set1_epi8(0x0f); \
const __m128i urow_lo = _mm_loadu_si128((__m128i *)u_lo); \
const __m128i urow_hi = _mm_loadu_si128((__m128i *)u_hi); \
__m128i *ap = (__m128i *)a, *ae = (__m128i *)(a + k - (k % sizeof(__m128i))), *bp = (__m128i *)b; \
for (; ap < ae; ap++, bp++) { \
__m128i bx = _mm_loadu_si128(bp); \
__m128i lo = _mm_and_si128(bx, mask); \
bx = _mm_srli_epi64(bx, 4); \
__m128i hi = _mm_and_si128(bx, mask); \
lo = _mm_shuffle_epi8(urow_lo, lo); \
hi = _mm_shuffle_epi8(urow_hi, hi); \
_mm_storeu_si128(ap, f(_mm_loadu_si128(ap), _mm_xor_si128(lo, hi))); \
} \
op##_ref((u8 *)ap, (u8 *)bp, u, k % sizeof(__m128i)); \
} while (0)
#undef OBL_SHUF_XOR
#define OBL_SHUF_XOR _mm_xor_si128
#undef OBL_AXPYB32
#define OBL_AXPYB32(a, b, u, k) \
do { \
__m128i *ap = (__m128i *)a, *ae = (__m128i *)(a + k); \
__m128i scatter_hi = _mm_set_epi32(0x03030303, 0x03030303, 0x02020202, 0x02020202); \
__m128i scatter_lo = _mm_set_epi32(0x01010101, 0x01010101, 0x00000000, 0x00000000); \
__m128i cmpmask = _mm_set_epi32(0x80402010, 0x08040201, 0x80402010, 0x08040201); \
__m128i up = _mm_set1_epi8(u); \
for (unsigned p = 0; ap < ae; p++, ap++) { \
__m128i bcast = _mm_set1_epi32(b[p]); \
__m128i ret_lo = _mm_shuffle_epi8(bcast, scatter_lo); \
__m128i ret_hi = _mm_shuffle_epi8(bcast, scatter_hi); \
ret_lo = _mm_andnot_si128(ret_lo, cmpmask); \
ret_hi = _mm_andnot_si128(ret_hi, cmpmask); \
ret_lo = _mm_and_si128(_mm_cmpeq_epi8(ret_lo, _mm_setzero_si128()), up); \
ret_hi = _mm_and_si128(_mm_cmpeq_epi8(ret_hi, _mm_setzero_si128()), up); \
_mm_storeu_si128(ap, _mm_xor_si128(_mm_loadu_si128(ap), ret_lo)); \
ap++; \
_mm_storeu_si128(ap, _mm_xor_si128(_mm_loadu_si128(ap), ret_hi)); \
} \
} while (0)
#else
#undef OBLAS_ALIGN
#define OBLAS_ALIGN (sizeof(void *))
#undef OBL_SHUF
#define OBL_SHUF(op, a, b, f) \
do { \
op##_ref(a, b, u, k); \
} while (0)
#undef OBL_SHUF_XOR
#define OBL_SHUF_XOR
#undef OBL_AXPYB32
#define OBL_AXPYB32 obl_axpyb32_ref
#endif
#define OBL_NOOP(a, b) (b)
void obl_axpy(u8 *a, u8 *b, u8 u, unsigned k)
{
if (u == 1) {
register u8 *ap = a, *ae = &a[k], *bp = b;
for (; ap < ae; ap++, bp++)
*ap ^= *bp;
} else {
OBL_SHUF(obl_axpy, a, b, OBL_SHUF_XOR);
}
}
void obl_scal(u8 *a, u8 u, unsigned k)
{
OBL_SHUF(obl_scal, a, a, OBL_NOOP);
}
void obl_swap(u8 *a, u8 *b, unsigned k)
{
register u8 *ap = a, *ae = &a[k], *bp = b;
for (; ap < ae; ap++, bp++) {
u8 tmp = *ap;
*ap = *bp;
*bp = tmp;
}
}
void obl_axpyb32(u8 *a, u32 *b, u8 u, unsigned k)
{
OBL_AXPYB32(a, b, u, k);
}
-11
View File
@@ -1,11 +0,0 @@
#include <stdint.h>
#include "gf2_8_tables.h"
typedef uint8_t u8;
typedef uint32_t u32;
void obl_axpy(u8 *a, u8 *b, u8 u, unsigned k);
void obl_scal(u8 *a, u8 u, unsigned k);
void obl_swap(u8 *a, u8 *b, unsigned k);
void obl_axpyb32(u8 *a, u32 *b, u8 u, unsigned k);
-178
View File
@@ -1,178 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "oblas_lite.c"
#include "rs.h"
static void axpy(u8 *a, u8 *b, u8 u, int k)
{
if (u == 0)
return;
if (u == 1) {
register u8 *ap = a, *ae = &a[k], *bp = b;
for (; ap < ae; ap++, bp++)
*ap ^= *bp;
} else {
obl_axpy(a, b, u, k);
}
}
static void scal(u8 *a, u8 u, int k)
{
if (u < 2)
return;
obl_scal(a, u, k);
}
static void gemm(u8 *a, u8 **b, u8 **c, int n, int k, int m)
{
int ci = 0;
for (int row = 0; row < n; row++, ci++) {
u8 *ap = a + (row * k);
memset(c[ci], 0, m);
for (int idx = 0; idx < k; idx++)
axpy(c[ci], b[idx], ap[idx], m);
}
}
static int invert_mat(u8 *src, u8 *wrk, u8 **dst, int V0, int K, int T, u8 *c, u8 *d)
{
int V0b = V0, W = K - V0;
u8 u = 0;
for (int i = 0; i < W; i++) {
int dr = d[i] * K;
for (int j = 0; j < W; j++)
wrk[i * W + j] = src[dr + c[V0 + j]];
}
for (; V0 < K; V0++) {
int dr = d[V0 - V0b] * K;
for (int row = 0; row < V0b; row++) {
u = src[dr + c[row]];
axpy(dst[c[V0]], dst[c[row]], u, T);
}
}
for (int x = 0; x < W; x++) {
u = GF2_8_INV[wrk[x * W + x]];
scal(wrk + x * W + x, u, W);
scal(dst[c[V0b + x]], u, T);
for (int row = x + 1; row < W; row++) {
u = wrk[row * W + x];
axpy(wrk + row * W, wrk + x * W, u, W);
axpy(dst[c[V0b + row]], dst[c[V0b + x]], u, T);
}
}
for (int x = W - 1; x >= 0; x--) {
u8 *from = dst[c[V0b + x]];
for (int row = 0; row < x; row++) {
u = wrk[row * W + x];
axpy(dst[c[V0b + row]], from, u, T);
}
}
return 0;
}
void reed_solomon_init(void)
{
}
reed_solomon *reed_solomon_new_static(void *buf, size_t len, int ds, int ps)
{
reed_solomon *rs = buf;
if ((ds + ps) > DATA_SHARDS_MAX || ds <= 0 || ps <= 0)
return NULL;
if (len < reed_solomon_bufsize(ds, ps))
return NULL;
memset(buf, 0, len);
rs->ds = ds;
rs->ps = ps;
rs->ts = ds + ps;
for (int j = 0; j < rs->ps; j++) {
u8 *row = rs->p + j * rs->ds;
for (int i = 0; i < rs->ds; i++)
row[i] = GF2_8_INV[(rs->ps + i) ^ j];
}
return rs;
}
reed_solomon *reed_solomon_new(int ds, int ps)
{
size_t len = reed_solomon_bufsize(ds, ps);
void *buf = malloc(len);
if (!buf)
return NULL;
if (reed_solomon_new_static(buf, len, ds, ps) == NULL) {
free(buf);
return NULL;
}
return buf;
}
void reed_solomon_release(reed_solomon *rs)
{
if (rs)
free(rs);
}
int reed_solomon_decode(reed_solomon *rs, u8 **data, u8 *marks, int nr_shards, int bs)
{
if (nr_shards < rs->ts)
return -1;
#ifdef _MSC_VER
u8 *erasures = _alloca(rs->ds);
u8 *colperm = _alloca(rs->ds);
u8 *rowperm = _alloca(rs->ds);
#else
u8 erasures[rs->ds], colperm[rs->ds], rowperm[rs->ds];
#endif
u8 *wrk = rs->p + 1 * rs->ps * rs->ds;
u8 gaps = 0;
for (int i = 0; i < rs->ds; i++)
if (marks[i])
erasures[gaps++] = i;
for (int i = 0, j = 0; i < rs->ds - gaps; i++, j++) {
while (marks[j])
j++;
colperm[i] = j;
}
for (int i = 0, j = rs->ds - gaps; i < gaps; i++, j++)
colperm[j] = erasures[i];
int i = 0;
for (int j = rs->ds; i < gaps; i++, j++) {
while (marks[j])
j++;
if (j >= nr_shards)
break;
rowperm[i] = j - rs->ds;
memcpy(data[erasures[i]], data[j], bs);
}
if (i < gaps)
return -1;
invert_mat(rs->p, wrk, data, rs->ds - gaps, rs->ds, bs, colperm, rowperm);
return 0;
}
int reed_solomon_encode(reed_solomon *rs, u8 **shards, int nr_shards, int bs)
{
if (nr_shards < rs->ts)
return -1;
gemm(rs->p, shards, shards + rs->ds, rs->ps, rs->ds, bs);
return 0;
}
-26
View File
@@ -1,26 +0,0 @@
#ifndef __RS_H_
#define __RS_H_
#include <stdint.h>
#define DATA_SHARDS_MAX 255
typedef struct _reed_solomon {
int ds;
int ps;
int ts;
uint8_t p[];
} reed_solomon;
#define reed_solomon_bufsize(ds, ps) (sizeof(reed_solomon) + 2 * (ps) * (ds))
#define reed_solomon_reconstruct reed_solomon_decode
void reed_solomon_init(void);
reed_solomon *reed_solomon_new_static(void *buf, size_t len, int ds, int ps);
reed_solomon *reed_solomon_new(int data_shards, int parity_shards);
void reed_solomon_release(reed_solomon *rs);
int reed_solomon_encode(reed_solomon *rs, uint8_t **shards, int nr_shards, int bs);
int reed_solomon_decode(reed_solomon *rs, uint8_t **shards, uint8_t *marks, int nr_shards, int bs);
#endif
+639
View File
@@ -0,0 +1,639 @@
/*
* fec.c -- forward error correction based on Vandermonde matrices
*
* (C) 1997-98 Luigi Rizzo (luigi@iet.unipi.it)
* (C) 2001 Alain Knaff (alain@knaff.lu)
* (C) 2017 Iwan Timmer (irtimmer@gmail.com)
*
* Portions derived from code by Phil Karn (karn@ka9q.ampr.org),
* Robert Morelos-Zaragoza (robert@spectra.eng.hawaii.edu) and Hari
* Thirumoorthy (harit@spectra.eng.hawaii.edu), Aug 1995
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "rs.h"
#ifdef _MSC_VER
#define NEED_ALLOCA
#define alloca(x) _alloca(x)
#endif
typedef unsigned char gf;
#define GF_BITS 8
#define GF_PP "101110001"
#define GF_SIZE ((1 << GF_BITS) - 1)
#define SWAP(a,b,t) {t tmp; tmp=a; a=b; b=tmp;}
/*
* USE_GF_MULC, GF_MULC0(c) and GF_ADDMULC(x) can be used when multiplying
* many numbers by the same constant. In this case the first
* call sets the constant, and others perform the multiplications.
* A value related to the multiplication is held in a local variable
* declared with USE_GF_MULC . See usage in addmul1().
*/
#define USE_GF_MULC register gf * __gf_mulc_
#define GF_MULC0(c) __gf_mulc_ = &gf_mul_table[(c)<<8]
#define GF_ADDMULC(dst, x) dst ^= __gf_mulc_[x]
#define GF_MULC(dst, x) dst = __gf_mulc_[x]
#define gf_mul(x,y) gf_mul_table[(x<<8)+y]
/*
* To speed up computations, we have tables for logarithm, exponent
* multiplication and inverse of a number.
*/
static gf gf_exp[2*GF_SIZE];
static int gf_log[GF_SIZE + 1];
static gf inverse[GF_SIZE+1];
#ifdef _MSC_VER
static gf __declspec(align (256)) gf_mul_table[(GF_SIZE + 1)*(GF_SIZE + 1)];
#else
static gf gf_mul_table[(GF_SIZE + 1)*(GF_SIZE + 1)] __attribute__((aligned (256)));
#endif
/*
* modnn(x) computes x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1,
* without a slow divide.
*/
static inline gf modnn(int x) {
while (x >= GF_SIZE) {
x -= GF_SIZE;
x = (x >> GF_BITS) + (x & GF_SIZE);
}
return x;
}
static void addmul(gf *dst1, gf *src1, gf c, int sz) {
USE_GF_MULC;
if (c != 0) {
register gf *dst = dst1, *src = src1;
gf *lim = &dst[sz];
GF_MULC0(c);
for (; dst < lim; dst++, src++)
GF_ADDMULC(*dst, *src);
}
}
static void mul(gf *dst1, gf *src1, gf c, int sz) {
USE_GF_MULC;
if (c != 0) {
register gf *dst = dst1, *src = src1;
gf *lim = &dst[sz];
GF_MULC0(c);
for (; dst < lim; dst++, src++)
GF_MULC(*dst , *src);
} else
memset(dst1, 0, c);
}
/* y = a.dot(b) */
static gf* multiply1(gf *a, int ar, int ac, gf *b, int br, int bc) {
gf *new_m, tg;
int r, c, i, ptr = 0;
assert(ac == br);
new_m = (gf*) calloc(1, ar*bc);
if (NULL != new_m) {
/* this multiply is slow */
for (r = 0; r < ar; r++) {
for (c = 0; c < bc; c++) {
tg = 0;
for (i = 0; i < ac; i++)
tg ^= gf_mul(a[r*ac+i], b[i*bc+c]);
new_m[ptr++] = tg;
}
}
}
return new_m;
}
static void init_mul_table(void) {
int i, j;
for (i=0; i< GF_SIZE+1; i++)
for (j=0; j< GF_SIZE+1; j++)
gf_mul_table[(i<<8)+j] = gf_exp[modnn(gf_log[i] + gf_log[j]) ] ;
for (j=0; j< GF_SIZE+1; j++)
gf_mul_table[j] = gf_mul_table[j<<8] = 0;
}
/*
* initialize the data structures used for computations in GF.
*/
static void generate_gf(void) {
int i;
gf mask;
mask = 1;
gf_exp[GF_BITS] = 0;
/*
* first, generate the (polynomial representation of) powers of \alpha,
* which are stored in gf_exp[i] = \alpha ** i .
* At the same time build gf_log[gf_exp[i]] = i .
* The first GF_BITS powers are simply bits shifted to the left.
*/
for (i = 0; i < GF_BITS; i++, mask <<= 1) {
gf_exp[i] = mask;
gf_log[gf_exp[i]] = i;
/*
* If GF_PP[i] == 1 then \alpha ** i occurs in poly-repr
* gf_exp[GF_BITS] = \alpha ** GF_BITS
*/
if (GF_PP[i] == '1')
gf_exp[GF_BITS] ^= mask;
}
/*
* now gf_exp[GF_BITS] = \alpha ** GF_BITS is complete, so can als
* compute its inverse.
*/
gf_log[gf_exp[GF_BITS]] = GF_BITS;
/*
* Poly-repr of \alpha ** (i+1) is given by poly-repr of
* \alpha ** i shifted left one-bit and accounting for any
* \alpha ** GF_BITS term that may occur when poly-repr of
* \alpha ** i is shifted.
*/
mask = 1 << (GF_BITS - 1) ;
for (i = GF_BITS + 1; i < GF_SIZE; i++) {
if (gf_exp[i - 1] >= mask)
gf_exp[i] = gf_exp[GF_BITS] ^ ((gf_exp[i - 1] ^ mask) << 1);
else
gf_exp[i] = gf_exp[i - 1] << 1;
gf_log[gf_exp[i]] = i;
}
/*
* log(0) is not defined, so use a special value
*/
gf_log[0] = GF_SIZE;
/* set the extended gf_exp values for fast multiply */
for (i = 0; i < GF_SIZE; i++)
gf_exp[i + GF_SIZE] = gf_exp[i];
/*
* again special cases. 0 has no inverse. This used to
* be initialized to GF_SIZE, but it should make no difference
* since noone is supposed to read from here.
*/
inverse[0] = 0;
inverse[1] = 1;
for (i=2; i<=GF_SIZE; i++)
inverse[i] = gf_exp[GF_SIZE-gf_log[i]];
}
/*
* invert_mat() takes a matrix and produces its inverse
* k is the size of the matrix.
* (Gauss-Jordan, adapted from Numerical Recipes in C)
* Return non-zero if singular.
*/
static int invert_mat(gf *src, int k) {
gf c, *p;
int irow, icol, row, col, i, ix;
int error = 1;
#ifdef NEED_ALLOCA
int *indxc = alloca(k*sizeof(int));
int *indxr = alloca(k*sizeof(int));
int *ipiv = alloca(k*sizeof(int));
gf *id_row = alloca(k*sizeof(gf));
#else
int indxc[k];
int indxr[k];
int ipiv[k];
gf id_row[k];
#endif
memset(id_row, 0, k*sizeof(gf));
/*
* ipiv marks elements already used as pivots.
*/
for (i = 0; i < k; i++)
ipiv[i] = 0;
for (col = 0; col < k; col++) {
gf *pivot_row;
/*
* Zeroing column 'col', look for a non-zero element.
* First try on the diagonal, if it fails, look elsewhere.
*/
irow = icol = -1;
if (ipiv[col] != 1 && src[col*k + col] != 0) {
irow = col;
icol = col;
goto found_piv;
}
for (row = 0; row < k; row++) {
if (ipiv[row] != 1) {
for (ix = 0; ix < k; ix++) {
if (ipiv[ix] == 0) {
if (src[row*k + ix] != 0) {
irow = row;
icol = ix;
goto found_piv;
}
} else if (ipiv[ix] > 1) {
fprintf(stderr, "singular matrix\n");
goto fail;
}
}
}
}
if (icol == -1) {
fprintf(stderr, "XXX pivot not found!\n");
goto fail ;
}
found_piv:
++(ipiv[icol]);
/*
* swap rows irow and icol, so afterwards the diagonal
* element will be correct. Rarely done, not worth
* optimizing.
*/
if (irow != icol) {
for (ix = 0; ix < k; ix++) {
SWAP(src[irow*k + ix], src[icol*k + ix], gf);
}
}
indxr[col] = irow;
indxc[col] = icol;
pivot_row = &src[icol*k];
c = pivot_row[icol];
if (c == 0) {
fprintf(stderr, "singular matrix 2\n");
goto fail;
} else if (c != 1 ) {
/*
* this is done often , but optimizing is not so
* fruitful, at least in the obvious ways (unrolling)
*/
c = inverse[ c ];
pivot_row[icol] = 1;
for (ix = 0; ix < k; ix++)
pivot_row[ix] = gf_mul(c, pivot_row[ix]);
}
/*
* from all rows, remove multiples of the selected row
* to zero the relevant entry (in fact, the entry is not zero
* because we know it must be zero).
* (Here, if we know that the pivot_row is the identity,
* we can optimize the addmul).
*/
id_row[icol] = 1;
if (memcmp(pivot_row, id_row, k*sizeof(gf)) != 0) {
for (p = src, ix = 0 ; ix < k ; ix++, p += k) {
if (ix != icol) {
c = p[icol];
p[icol] = 0;
addmul(p, pivot_row, c, k);
}
}
}
id_row[icol] = 0;
}
for (col = k-1 ; col >= 0 ; col-- ) {
if (indxr[col] <0 || indxr[col] >= k)
fprintf(stderr, "AARGH, indxr[col] %d\n", indxr[col]);
else if (indxc[col] <0 || indxc[col] >= k)
fprintf(stderr, "AARGH, indxc[col] %d\n", indxc[col]);
else
if (indxr[col] != indxc[col] ) {
for (row = 0 ; row < k ; row++ )
SWAP( src[row*k + indxr[col]], src[row*k + indxc[col]], gf);
}
}
error = 0;
fail:
return error ;
}
/*
* Not check for input params
* */
static gf* sub_matrix(gf* matrix, int rmin, int cmin, int rmax, int cmax, int nrows, int ncols) {
int i, j, ptr = 0;
gf* new_m = (gf*) malloc((rmax-rmin) * (cmax-cmin));
if (NULL != new_m) {
for (i = rmin; i < rmax; i++) {
for (j = cmin; j < cmax; j++) {
new_m[ptr++] = matrix[i*ncols + j];
}
}
}
return new_m;
}
/* copy from golang rs version */
static inline int code_some_shards(gf* matrixRows, gf** inputs, gf** outputs, int dataShards, int outputCount, int byteCount) {
gf* in;
int iRow, c;
for (c = 0; c < dataShards; c++) {
in = inputs[c];
for (iRow = 0; iRow < outputCount; iRow++) {
if (0 == c)
mul(outputs[iRow], in, matrixRows[iRow*dataShards+c], byteCount);
else
addmul(outputs[iRow], in, matrixRows[iRow*dataShards+c], byteCount);
}
}
return 0;
}
void reed_solomon_init(void) {
generate_gf();
init_mul_table();
}
reed_solomon* reed_solomon_new(int data_shards, int parity_shards) {
gf* vm = NULL;
gf* top = NULL;
int err = 0;
reed_solomon* rs = NULL;
do {
rs = malloc(sizeof(reed_solomon));
if (NULL == rs)
return NULL;
rs->data_shards = data_shards;
rs->parity_shards = parity_shards;
rs->shards = (data_shards + parity_shards);
rs->m = NULL;
rs->parity = NULL;
if (rs->shards > DATA_SHARDS_MAX || data_shards <= 0 || parity_shards <= 0) {
err = 1;
break;
}
vm = (gf*)malloc(data_shards * rs->shards);
if (NULL == vm) {
err = 2;
break;
}
int ptr = 0;
for (int row = 0; row < rs->shards; row++) {
for (int col = 0; col < data_shards; col++)
vm[ptr++] = row == col ? 1 : 0;
}
top = sub_matrix(vm, 0, 0, data_shards, data_shards, rs->shards, data_shards);
if (NULL == top) {
err = 3;
break;
}
err = invert_mat(top, data_shards);
assert(0 == err);
rs->m = multiply1(vm, rs->shards, data_shards, top, data_shards, data_shards);
if (NULL == rs->m) {
err = 4;
break;
}
for (int j = 0; j < parity_shards; j++) {
for (int i = 0; i < data_shards; i++)
rs->m[(data_shards + j)*data_shards + i] = inverse[(parity_shards + i) ^ j];
}
rs->parity = sub_matrix(rs->m, data_shards, 0, rs->shards, data_shards, rs->shards, data_shards);
if (NULL == rs->parity) {
err = 5;
break;
}
free(vm);
free(top);
vm = NULL;
top = NULL;
return rs;
} while(0);
fprintf(stderr, "err=%d\n", err);
if (NULL != vm)
free(vm);
if (NULL != top)
free(top);
if (NULL != rs) {
if (NULL != rs->m)
free(rs->m);
if (NULL != rs->parity)
free(rs->parity);
free(rs);
}
return NULL;
}
void reed_solomon_release(reed_solomon* rs) {
if (NULL != rs) {
if (NULL != rs->m)
free(rs->m);
if (NULL != rs->parity)
free(rs->parity);
free(rs);
}
}
/**
* decode one shard
* input:
* rs
* original data_blocks[rs->data_shards][block_size]
* dec_fec_blocks[nr_fec_blocks][block_size]
* fec_block_nos: fec pos number in original fec_blocks
* erased_blocks: erased blocks in original data_blocks
* nr_fec_blocks: the number of erased blocks
* */
static int reed_solomon_decode(reed_solomon* rs, unsigned char **data_blocks, int block_size, unsigned char **dec_fec_blocks, unsigned int *fec_block_nos, unsigned int *erased_blocks, int nr_fec_blocks) {
/* use stack instead of malloc, define a small number of DATA_SHARDS_MAX to save memory */
gf dataDecodeMatrix[DATA_SHARDS_MAX*DATA_SHARDS_MAX];
unsigned char* subShards[DATA_SHARDS_MAX];
unsigned char* outputs[DATA_SHARDS_MAX];
gf* m = rs->m;
int i, j, c, swap, subMatrixRow, dataShards;
/* the erased_blocks should always sorted
* if sorted, nr_fec_blocks times to check it
* if not, sort it here
* */
for (i = 0; i < nr_fec_blocks; i++) {
swap = 0;
for (j = i+1; j < nr_fec_blocks; j++) {
if (erased_blocks[i] > erased_blocks[j]) {
/* the prefix is bigger than the following, swap */
c = erased_blocks[i];
erased_blocks[i] = erased_blocks[j];
erased_blocks[j] = c;
swap = 1;
}
}
if (!swap)
break;
}
j = 0;
subMatrixRow = 0;
dataShards = rs->data_shards;
for (i = 0; i < dataShards; i++) {
if (j < nr_fec_blocks && i == (int)erased_blocks[j])
j++;
else {
/* this row is ok */
for (c = 0; c < dataShards; c++)
dataDecodeMatrix[subMatrixRow*dataShards + c] = m[i*dataShards + c];
subShards[subMatrixRow] = data_blocks[i];
subMatrixRow++;
}
}
for (i = 0; i < nr_fec_blocks && subMatrixRow < dataShards; i++) {
subShards[subMatrixRow] = dec_fec_blocks[i];
j = dataShards + fec_block_nos[i];
for (c = 0; c < dataShards; c++)
dataDecodeMatrix[subMatrixRow*dataShards + c] = m[j*dataShards + c];
subMatrixRow++;
}
if (subMatrixRow < dataShards)
return -1;
invert_mat(dataDecodeMatrix, dataShards);
for (i = 0; i < nr_fec_blocks; i++) {
j = erased_blocks[i];
outputs[i] = data_blocks[j];
memmove(dataDecodeMatrix+i*dataShards, dataDecodeMatrix+j*dataShards, dataShards);
}
return code_some_shards(dataDecodeMatrix, subShards, outputs, dataShards, nr_fec_blocks, block_size);
}
/**
* encode a big size of buffer
* input:
* rs
* nr_shards: assert(0 == nr_shards % rs->shards)
* shards[nr_shards][block_size]
* */
int reed_solomon_encode(reed_solomon* rs, unsigned char** shards, int nr_shards, int block_size) {
unsigned char** data_blocks;
unsigned char** fec_blocks;
int i, ds = rs->data_shards, ps = rs->parity_shards, ss = rs->shards;
i = nr_shards / ss;
data_blocks = shards;
fec_blocks = &shards[(i*ds)];
for (i = 0; i < nr_shards; i += ss) {
code_some_shards(rs->parity, data_blocks, fec_blocks, rs->data_shards, rs->parity_shards, block_size);
data_blocks += ds;
fec_blocks += ps;
}
return 0;
}
/**
* reconstruct a big size of buffer
* input:
* rs
* nr_shards: assert(0 == nr_shards % rs->data_shards)
* shards[nr_shards][block_size]
* marks[nr_shards] marks as errors
* */
int reed_solomon_reconstruct(reed_solomon* rs, unsigned char** shards, unsigned char* marks, int nr_shards, int block_size) {
unsigned char *dec_fec_blocks[DATA_SHARDS_MAX];
unsigned int fec_block_nos[DATA_SHARDS_MAX];
unsigned int erased_blocks[DATA_SHARDS_MAX];
unsigned char* fec_marks;
unsigned char **data_blocks, **fec_blocks;
int i, j, dn, pn, n;
int ds = rs->data_shards;
int ps = rs->parity_shards;
int err = 0;
data_blocks = shards;
n = nr_shards / rs->shards;
fec_marks = marks + n*ds; //after all data, is't fec marks
fec_blocks = shards + n*ds;
for (j = 0; j < n; j++) {
dn = 0;
for (i = 0; i < ds; i++) {
if (marks[i])
erased_blocks[dn++] = i;
}
if (dn > 0) {
pn = 0;
for (i = 0; i < ps && pn < dn; i++) {
if (!fec_marks[i]) {
//got valid fec row
fec_block_nos[pn] = i;
dec_fec_blocks[pn] = fec_blocks[i];
pn++;
}
}
if (dn == pn) {
reed_solomon_decode(rs, data_blocks, block_size, dec_fec_blocks, fec_block_nos, erased_blocks, dn);
} else
err = -1;
}
data_blocks += ds;
marks += ds;
fec_blocks += ps;
fec_marks += ps;
}
return err;
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef __RS_H_
#define __RS_H_
/* use small value to save memory */
#define DATA_SHARDS_MAX 255
typedef struct _reed_solomon {
int data_shards;
int parity_shards;
int shards;
unsigned char* m;
unsigned char* parity;
} reed_solomon;
/**
* MUST initial one time
* */
void reed_solomon_init(void);
reed_solomon* reed_solomon_new(int data_shards, int parity_shards);
void reed_solomon_release(reed_solomon* rs);
/**
* encode a big size of buffer
* input:
* rs
* nr_shards: assert(0 == nr_shards % rs->data_shards)
* shards[nr_shards][block_size]
* */
int reed_solomon_encode(reed_solomon* rs, unsigned char** shards, int nr_shards, int block_size);
/**
* reconstruct a big size of buffer
* input:
* rs
* nr_shards: assert(0 == nr_shards % rs->data_shards)
* shards[nr_shards][block_size]
* marks[nr_shards] marks as errors
* */
int reed_solomon_reconstruct(reed_solomon* rs, unsigned char** shards, unsigned char* marks, int nr_shards, int block_size);
#endif
+142 -237
View File
@@ -1,112 +1,48 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "LinkedBlockingQueue.h"
#include "RtpReorderQueue.h"
static SOCKET rtpSocket = INVALID_SOCKET;
static LINKED_BLOCKING_QUEUE packetQueue;
static RTP_AUDIO_QUEUE rtpAudioQueue;
static RTP_REORDER_QUEUE rtpReorderQueue;
static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread;
static PLT_THREAD decoderThread;
static PPLT_CRYPTO_CONTEXT audioDecryptionCtx;
static uint32_t avRiKeyId;
static unsigned short lastSeq;
static bool pingThreadStarted;
static bool receivedDataFromPeer;
static uint64_t firstReceiveTime;
#ifdef LC_DEBUG
#define INVALID_OPUS_HEADER 0x00
static uint8_t opusHeaderByte;
#endif
#define RTP_PORT 48000
#define MAX_PACKET_SIZE 1400
typedef struct _QUEUE_AUDIO_PACKET_HEADER {
LINKED_BLOCKING_QUEUE_ENTRY lentry;
int size;
} QUEUED_AUDIO_PACKET_HEADER, *PQUEUED_AUDIO_PACKET_HEADER;
// This is much larger than we should typically have buffered, but
// it needs to be. We need a cushion in case our thread gets blocked
// for longer than normal.
#define RTP_RECV_BUFFER (64 * 1024)
typedef struct _QUEUED_AUDIO_PACKET {
QUEUED_AUDIO_PACKET_HEADER header;
// data must remain at the front
char data[MAX_PACKET_SIZE];
int size;
union {
RTP_QUEUE_ENTRY rentry;
LINKED_BLOCKING_QUEUE_ENTRY lentry;
} q;
} QUEUED_AUDIO_PACKET, *PQUEUED_AUDIO_PACKET;
static void AudioPingThreadProc(void* context) {
char legacyPingData[] = { 0x50, 0x49, 0x4E, 0x47 };
LC_SOCKADDR saddr;
LC_ASSERT(AudioPortNumber != 0);
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
SET_PORT(&saddr, AudioPortNumber);
// We do not check for errors here. Socket errors will be handled
// on the read-side in ReceiveThreadProc(). This avoids potential
// issues related to receiving ICMP port unreachable messages due
// to sending a packet prior to the host PC binding to that port.
int pingCount = 0;
while (!PltIsThreadInterrupted(&udpPingThread)) {
if (AudioPingPayload.payload[0] != 0) {
pingCount++;
AudioPingPayload.sequenceNumber = BE32(pingCount);
sendto(rtpSocket, (char*)&AudioPingPayload, sizeof(AudioPingPayload), 0, (struct sockaddr*)&saddr, AddrLen);
}
else {
sendto(rtpSocket, legacyPingData, sizeof(legacyPingData), 0, (struct sockaddr*)&saddr, AddrLen);
}
PltSleepMsInterruptible(&udpPingThread, 500);
}
}
// Initialize the audio stream and start
int initializeAudioStream(void) {
// Initialize the audio stream
void initializeAudioStream(void) {
LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
RtpaInitializeQueue(&rtpAudioQueue);
RtpqInitializeQueue(&rtpReorderQueue, RTPQ_DEFAULT_MAX_SIZE, RTPQ_DEFAULT_QUEUE_TIME);
lastSeq = 0;
receivedDataFromPeer = false;
pingThreadStarted = false;
firstReceiveTime = 0;
audioDecryptionCtx = PltCreateCryptoContext();
#ifdef LC_DEBUG
opusHeaderByte = INVALID_OPUS_HEADER;
#endif
// Copy and byte-swap the AV RI key ID used for the audio encryption IV
memcpy(&avRiKeyId, StreamConfig.remoteInputAesIv, sizeof(avRiKeyId));
avRiKeyId = BE32(avRiKeyId);
return 0;
}
// This is called when the RTSP SETUP message is parsed and the audio port
// number is parsed out of it. Alternatively, it's also called if parsing fails
// and will use the well known audio port instead.
int notifyAudioPortNegotiationComplete(void) {
LC_ASSERT(!pingThreadStarted);
LC_ASSERT(AudioPortNumber != 0);
// For GFE 3.22 compatibility, we must start the audio ping thread before the RTSP handshake.
// It will not reply to our RTSP PLAY request until the audio ping has been received.
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, &LocalAddr, AddrLen, 0, SOCK_QOS_TYPE_AUDIO);
if (rtpSocket == INVALID_SOCKET) {
return LastSocketFail();
}
// We may receive audio before our threads are started, but that's okay. We'll
// drop the first 1 second of audio packets to catch up with the backlog.
int err = PltCreateThread("AudioPing", AudioPingThreadProc, NULL, &udpPingThread);
if (err != 0) {
return err;
}
pingThreadStarted = true;
return 0;
}
static void freePacketList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
@@ -124,128 +60,75 @@ static void freePacketList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
// Tear down the audio stream once we're done with it
void destroyAudioStream(void) {
if (rtpSocket != INVALID_SOCKET) {
if (pingThreadStarted) {
PltInterruptThread(&udpPingThread);
PltJoinThread(&udpPingThread);
freePacketList(LbqDestroyLinkedBlockingQueue(&packetQueue));
RtpqCleanupQueue(&rtpReorderQueue);
}
static void UdpPingThreadProc(void* context) {
// Ping in ASCII
char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
struct sockaddr_in6 saddr;
SOCK_RET err;
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
saddr.sin6_port = htons(RTP_PORT);
// Send PING every second until we get data back then every 5 seconds after that.
while (!PltIsThreadInterrupted(&udpPingThread)) {
err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen);
if (err != sizeof(pingData)) {
Limelog("Audio Ping: sendto() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
}
closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET;
PltSleepMsInterruptible(&udpPingThread, 500);
}
PltDestroyCryptoContext(audioDecryptionCtx);
freePacketList(LbqDestroyLinkedBlockingQueue(&packetQueue));
RtpaCleanupQueue(&rtpAudioQueue);
}
static bool queuePacketToLbq(PQUEUED_AUDIO_PACKET* packet) {
int err;
do {
err = LbqOfferQueueItem(&packetQueue, *packet, &(*packet)->header.lentry);
if (err == LBQ_SUCCESS) {
// The LBQ owns the buffer now
*packet = NULL;
}
else if (err == LBQ_BOUND_EXCEEDED) {
Limelog("Audio packet queue overflow\n");
err = LbqOfferQueueItem(&packetQueue, *packet, &(*packet)->q.lentry);
if (err == LBQ_SUCCESS) {
// The LBQ owns the buffer now
*packet = NULL;
}
else if (err == LBQ_BOUND_EXCEEDED) {
Limelog("Audio packet queue overflow\n");
freePacketList(LbqFlushQueueItems(&packetQueue));
}
else if (err == LBQ_INTERRUPTED) {
return false;
}
// The audio queue is full, so free all existing items and try again
freePacketList(LbqFlushQueueItems(&packetQueue));
}
} while (err == LBQ_BOUND_EXCEEDED);
return err == LBQ_SUCCESS;
return true;
}
static void decodeInputData(PQUEUED_AUDIO_PACKET packet) {
// If the packet size is zero, this is a placeholder for a missing
// packet. Trigger packet loss concealment logic in libopus by
// invoking the decoder with a NULL buffer.
if (packet->header.size == 0) {
AudioCallbacks.decodeAndPlaySample(NULL, 0);
return;
}
PRTP_PACKET rtp;
PRTP_PACKET rtp = (PRTP_PACKET)&packet->data[0];
rtp = (PRTP_PACKET)&packet->data[0];
if (lastSeq != 0 && (unsigned short)(lastSeq + 1) != rtp->sequenceNumber) {
Limelog("Network dropped audio data (expected %d, but received %d)\n", lastSeq + 1, rtp->sequenceNumber);
Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber);
AudioCallbacks.decodeAndPlaySample(NULL, 0);
}
lastSeq = rtp->sequenceNumber;
if (AudioEncryptionEnabled) {
// We must have room for the AES padding which may be written to the buffer
unsigned char decryptedOpusData[ROUND_TO_PKCS7_PADDED_LEN(MAX_PACKET_SIZE)];
unsigned char iv[16] = { 0 };
int dataLength = packet->header.size - sizeof(*rtp);
LC_ASSERT(dataLength <= MAX_PACKET_SIZE);
// The IV is the avkeyid (equivalent to the rikeyid) +
// the RTP sequence number, in big endian.
uint32_t ivSeq = BE32(avRiKeyId + rtp->sequenceNumber);
memcpy(iv, &ivSeq, sizeof(ivSeq));
if (!PltDecryptMessage(audioDecryptionCtx, ALGORITHM_AES_CBC, CIPHER_FLAG_RESET_IV | CIPHER_FLAG_FINISH,
(unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
iv, sizeof(iv),
NULL, 0,
(unsigned char*)(rtp + 1), dataLength,
decryptedOpusData, &dataLength)) {
Limelog("Failed to decrypt audio packet (sequence number: %u)\n", rtp->sequenceNumber);
LC_ASSERT_VT(false);
return;
}
#ifdef LC_DEBUG
if (opusHeaderByte == INVALID_OPUS_HEADER) {
opusHeaderByte = decryptedOpusData[0];
LC_ASSERT_VT(opusHeaderByte != INVALID_OPUS_HEADER);
}
else {
// Opus header should stay constant for the entire stream.
// If it doesn't, it may indicate that the RtpAudioQueue
// incorrectly recovered a data shard or the decryption
// of the audio packet failed. Sunshine violates this for
// surround sound in some cases, so just ignore it.
LC_ASSERT_VT(decryptedOpusData[0] == opusHeaderByte || IS_SUNSHINE());
}
#endif
AudioCallbacks.decodeAndPlaySample((char*)decryptedOpusData, dataLength);
}
else {
#ifdef LC_DEBUG
if (opusHeaderByte == INVALID_OPUS_HEADER) {
opusHeaderByte = ((uint8_t*)(rtp + 1))[0];
LC_ASSERT_VT(opusHeaderByte != INVALID_OPUS_HEADER);
}
else {
// Opus header should stay constant for the entire stream.
// If it doesn't, it may indicate that the RtpAudioQueue
// incorrectly recovered a data shard. Sunshine violates
// this for surround sound in some cases, so just ignore it.
LC_ASSERT_VT(((uint8_t*)(rtp + 1))[0] == opusHeaderByte || IS_SUNSHINE());
}
#endif
AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->header.size - sizeof(*rtp));
}
AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp));
}
static void AudioReceiveThreadProc(void* context) {
static void ReceiveThreadProc(void* context) {
PRTP_PACKET rtp;
PQUEUED_AUDIO_PACKET packet;
int queueStatus;
bool useSelect;
uint32_t packetsToDrop;
int packetsToDrop = 500 / AudioPacketDuration;
int waitingForAudioMs;
packet = NULL;
packetsToDrop = 500 / AudioPacketDuration;
if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) {
// SO_RCVTIMEO failed, so use select() to wait
@@ -267,72 +150,61 @@ static void AudioReceiveThreadProc(void* context) {
}
}
packet->header.size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE, useSelect);
if (packet->header.size < 0) {
packet->size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE, useSelect);
if (packet->size < 0) {
Limelog("Audio Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
break;
}
else if (packet->header.size == 0) {
else if (packet->size == 0) {
// Receive timed out; try again
if (!receivedDataFromPeer) {
waitingForAudioMs += UDP_RECV_POLL_TIMEOUT_MS;
}
else {
// If we hit this path, there are no queued audio packets on the host PC,
// so we don't need to drop anything.
packetsToDrop = 0;
}
// If we hit this path, there are no queued audio packets on the host PC,
// so we don't need to drop anything.
packetsToDrop = 0;
continue;
}
if (packet->header.size < (int)sizeof(RTP_PACKET)) {
if (packet->size < (int)sizeof(RTP_PACKET)) {
// Runt packet
continue;
}
rtp = (PRTP_PACKET)&packet->data[0];
if (rtp->packetType != 97) {
// Not audio
continue;
}
if (!receivedDataFromPeer) {
receivedDataFromPeer = true;
Limelog("Received first audio packet after %d ms\n", waitingForAudioMs);
if (firstReceiveTime != 0) {
// XXX firstReceiveTime is never set here...
// We're already dropping 500ms of audio so this probably doesn't matter
packetsToDrop += (uint32_t)(PltGetMillis() - firstReceiveTime) / AudioPacketDuration;
}
Limelog("Initial audio resync period: %d milliseconds\n", packetsToDrop * AudioPacketDuration);
}
// GFE accumulates audio samples before we are ready to receive them, so
// we will drop the ones that arrived before the receive thread was ready.
// GFE accumulates audio samples before we are ready to receive them,
// so we will drop the first 100 packets to avoid accumulating latency
// by sending audio frames to the player faster than they can be played.
if (packetsToDrop > 0) {
// Only count actual audio data (not FEC) in the packets to drop calculation
if (rtp->packetType == 97) {
packetsToDrop--;
}
packetsToDrop--;
continue;
}
// Convert fields to host byte-order
rtp->sequenceNumber = BE16(rtp->sequenceNumber);
rtp->timestamp = BE32(rtp->timestamp);
rtp->ssrc = BE32(rtp->ssrc);
rtp->sequenceNumber = htons(rtp->sequenceNumber);
rtp->timestamp = htonl(rtp->timestamp);
rtp->ssrc = htonl(rtp->ssrc);
queueStatus = RtpaAddPacket(&rtpAudioQueue, (PRTP_PACKET)&packet->data[0], (uint16_t)packet->header.size);
queueStatus = RtpqAddPacket(&rtpReorderQueue, (PRTP_PACKET)packet, &packet->q.rentry);
if (RTPQ_HANDLE_NOW(queueStatus)) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if (!queuePacketToLbq(&packet)) {
// An exit signal was received
break;
}
else {
// Ownership should have been taken by the LBQ
LC_ASSERT(packet == NULL);
}
}
else {
decodeInputData(packet);
@@ -346,43 +218,33 @@ static void AudioReceiveThreadProc(void* context) {
if (RTPQ_PACKET_READY(queueStatus)) {
// If packets are ready, pull them and send them to the decoder
uint16_t length;
PQUEUED_AUDIO_PACKET queuedPacket;
while ((queuedPacket = (PQUEUED_AUDIO_PACKET)RtpaGetQueuedPacket(&rtpAudioQueue, sizeof(QUEUED_AUDIO_PACKET_HEADER), &length)) != NULL) {
// Populate header data (not preserved in queued packets)
queuedPacket->header.size = length;
while ((packet = (PQUEUED_AUDIO_PACKET)RtpqGetQueuedPacket(&rtpReorderQueue)) != NULL) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if (!queuePacketToLbq(&queuedPacket)) {
if (!queuePacketToLbq(&packet)) {
// An exit signal was received
free(queuedPacket);
break;
}
else {
// Ownership should have been taken by the LBQ
LC_ASSERT(queuedPacket == NULL);
}
}
else {
decodeInputData(queuedPacket);
free(queuedPacket);
decodeInputData(packet);
free(packet);
}
}
// Break on exit
if (queuedPacket != NULL) {
if (packet != NULL) {
break;
}
}
}
}
if (packet != NULL) {
free(packet);
}
}
static void AudioDecoderThreadProc(void* context) {
static void DecoderThreadProc(void* context) {
int err;
PQUEUED_AUDIO_PACKET packet;
@@ -406,18 +268,31 @@ void stopAudioStream(void) {
AudioCallbacks.stop();
PltInterruptThread(&udpPingThread);
PltInterruptThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
// Signal threads waiting on the LBQ
LbqSignalQueueShutdown(&packetQueue);
PltInterruptThread(&decoderThread);
}
PltJoinThread(&udpPingThread);
PltJoinThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&udpPingThread);
PltCloseThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
if (rtpSocket != INVALID_SOCKET) {
closeSocket(rtpSocket);
rtpSocket = INVALID_SOCKET;
}
AudioCallbacks.cleanup();
}
@@ -444,9 +319,16 @@ int startAudioStream(void* audioContext, int arFlags) {
return err;
}
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER);
if (rtpSocket == INVALID_SOCKET) {
err = LastSocketFail();
AudioCallbacks.cleanup();
return err;
}
AudioCallbacks.start();
err = PltCreateThread("AudioRecv", AudioReceiveThreadProc, NULL, &receiveThread);
err = PltCreateThread("AudioRecv", ReceiveThreadProc, NULL, &receiveThread);
if (err != 0) {
AudioCallbacks.stop();
closeSocket(rtpSocket);
@@ -455,17 +337,44 @@ int startAudioStream(void* audioContext, int arFlags) {
}
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
err = PltCreateThread("AudioDec", AudioDecoderThreadProc, NULL, &decoderThread);
err = PltCreateThread("AudioDec", DecoderThreadProc, NULL, &decoderThread);
if (err != 0) {
AudioCallbacks.stop();
PltInterruptThread(&receiveThread);
PltJoinThread(&receiveThread);
PltCloseThread(&receiveThread);
closeSocket(rtpSocket);
AudioCallbacks.cleanup();
return err;
}
}
// Don't start pinging (which will cause GFE to start sending us traffic)
// until everything else is started. Otherwise we could accumulate a
// bunch of audio packets in the socket receive buffer while our audio
// backend is starting up and create audio latency.
err = PltCreateThread("AudioPing", UdpPingThreadProc, NULL, &udpPingThread);
if (err != 0) {
AudioCallbacks.stop();
PltInterruptThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
// Signal threads waiting on the LBQ
LbqSignalQueueShutdown(&packetQueue);
PltInterruptThread(&decoderThread);
}
PltJoinThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&receiveThread);
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
closeSocket(rtpSocket);
AudioCallbacks.cleanup();
return err;
}
return 0;
}
@@ -476,7 +385,3 @@ int LiGetPendingAudioFrames(void) {
int LiGetPendingAudioDuration(void) {
return LiGetPendingAudioFrames() * AudioPacketDuration;
}
const RTP_AUDIO_STATS* LiGetRTPAudioStats(void) {
return &rtpAudioQueue.stats;
}
+16 -35
View File
@@ -10,30 +10,30 @@ void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int le
// Get the long long in the correct byte order
static uint64_t byteSwap64(PBYTE_BUFFER buff, uint64_t l) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return BE64(l);
return HTONLL(l);
}
else {
return LE64(l);
return l;
}
}
// Get the int in the correct byte order
static uint32_t byteSwap32(PBYTE_BUFFER buff, uint32_t i) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return BE32(i);
return htonl(i);
}
else {
return LE32(i);
return i;
}
}
// Get the short in the correct byte order
static uint16_t byteSwap16(PBYTE_BUFFER buff, uint16_t s) {
if (buff->byteOrder == BYTE_ORDER_BIG) {
return BE16(s);
return htons(s);
}
else {
return LE16(s);
return s;
}
}
@@ -47,33 +47,21 @@ bool BbAdvanceBuffer(PBYTE_BUFFER buff, int offset) {
return true;
}
// Rewind the byte buffer back to the starting position
void BbRewindBuffer(PBYTE_BUFFER buff) {
buff->position = 0;
}
// Get a variable number of bytes from the byte buffer (all or nothing though)
bool BbGetBytes(PBYTE_BUFFER buff, uint8_t* data, int length) {
if (buff->position + length > buff->length) {
memset(data, 0, length);
// Get a byte from the byte buffer
bool BbGet8(PBYTE_BUFFER buff, uint8_t* c) {
if (buff->position + sizeof(*c) > buff->length) {
return false;
}
memcpy(data, &buff->buffer[buff->position], length);
buff->position += length;
memcpy(c, &buff->buffer[buff->position], sizeof(*c));
buff->position += sizeof(*c);
return true;
}
// Get a byte from the byte buffer
bool BbGet8(PBYTE_BUFFER buff, uint8_t* c) {
return BbGetBytes(buff, c, sizeof(*c));
}
// Get a short from the byte buffer
bool BbGet16(PBYTE_BUFFER buff, uint16_t* s) {
if (buff->position + sizeof(*s) > buff->length) {
*s = 0;
return false;
}
@@ -88,7 +76,6 @@ bool BbGet16(PBYTE_BUFFER buff, uint16_t* s) {
// Get an int from the byte buffer
bool BbGet32(PBYTE_BUFFER buff, uint32_t* i) {
if (buff->position + sizeof(*i) > buff->length) {
*i = 0;
return false;
}
@@ -103,7 +90,6 @@ bool BbGet32(PBYTE_BUFFER buff, uint32_t* i) {
// Get a long from the byte buffer
bool BbGet64(PBYTE_BUFFER buff, uint64_t* l) {
if (buff->position + sizeof(*l) > buff->length) {
*l = 0;
return false;
}
@@ -157,19 +143,14 @@ bool BbPut16(PBYTE_BUFFER buff, uint16_t s) {
return true;
}
// Put a variable number of bytes into the byte buffer (all or nothing though)
bool BbPutBytes(PBYTE_BUFFER buff, const uint8_t* data, int length) {
if (buff->position + length > buff->length) {
// Put a byte into the buffer
bool BbPut8(PBYTE_BUFFER buff, uint8_t c) {
if (buff->position + sizeof(c) > buff->length) {
return false;
}
memcpy(&buff->buffer[buff->position], data, length);
buff->position += length;
memcpy(&buff->buffer[buff->position], &c, sizeof(c));
buff->position += sizeof(c);
return true;
}
// Put a byte into the buffer
bool BbPut8(PBYTE_BUFFER buff, uint8_t c) {
return BbPutBytes(buff, &c, sizeof(c));
}
+12 -3
View File
@@ -5,6 +5,18 @@
#define BYTE_ORDER_LITTLE 1
#define BYTE_ORDER_BIG 2
#ifndef HTONLL
#define HTONLL(x) \
((((x) & 0xff00000000000000ull) >> 56) \
| (((x) & 0x00ff000000000000ull) >> 40) \
| (((x) & 0x0000ff0000000000ull) >> 24) \
| (((x) & 0x000000ff00000000ull) >> 8) \
| (((x) & 0x00000000ff000000ull) << 8) \
| (((x) & 0x0000000000ff0000ull) << 24) \
| (((x) & 0x000000000000ff00ull) << 40) \
| (((x) & 0x00000000000000ffull) << 56))
#endif
typedef struct _BYTE_BUFFER {
char* buffer;
unsigned int length;
@@ -14,15 +26,12 @@ typedef struct _BYTE_BUFFER {
void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int length, int byteOrder);
bool BbAdvanceBuffer(PBYTE_BUFFER buff, int offset);
void BbRewindBuffer(PBYTE_BUFFER buff);
bool BbGetBytes(PBYTE_BUFFER buff, uint8_t* data, int length);
bool BbGet8(PBYTE_BUFFER buff, uint8_t* c);
bool BbGet16(PBYTE_BUFFER buff, uint16_t* s);
bool BbGet32(PBYTE_BUFFER buff, uint32_t* i);
bool BbGet64(PBYTE_BUFFER buff, uint64_t* l);
bool BbPutBytes(PBYTE_BUFFER buff, const uint8_t* data, int length);
bool BbPut8(PBYTE_BUFFER buff, uint8_t c);
bool BbPut16(PBYTE_BUFFER buff, uint16_t s);
bool BbPut32(PBYTE_BUFFER buff, uint32_t i);
+46 -192
View File
@@ -1,51 +1,39 @@
#include "Limelight-internal.h"
#include "Platform.h"
static int stage = STAGE_NONE;
static ConnListenerConnectionTerminated originalTerminationCallback;
static bool alreadyTerminated;
static atomic_bool alreadyTerminated;
static PLT_THREAD terminationCallbackThread;
static int terminationCallbackErrorCode;
// Common globals
char* RemoteAddrString;
struct sockaddr_storage RemoteAddr;
struct sockaddr_storage LocalAddr;
SOCKADDR_LEN AddrLen;
SOCKADDR_LEN RemoteAddrLen;
int AppVersionQuad[4];
STREAM_CONFIGURATION StreamConfig;
CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
DECODER_RENDERER_CALLBACKS VideoCallbacks;
AUDIO_RENDERER_CALLBACKS AudioCallbacks;
int NegotiatedVideoFormat;
volatile bool ConnectionInterrupted;
atomic_bool ConnectionInterrupted;
bool HighQualitySurroundSupported;
bool HighQualitySurroundEnabled;
OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
int OriginalVideoBitrate;
int AudioPacketDuration;
bool AudioEncryptionEnabled;
bool ReferenceFrameInvalidationSupported;
uint16_t RtspPortNumber;
uint16_t ControlPortNumber;
uint16_t AudioPortNumber;
uint16_t VideoPortNumber;
SS_PING AudioPingPayload;
SS_PING VideoPingPayload;
uint32_t ControlConnectData;
uint32_t SunshineFeatureFlags;
uint32_t EncryptionFeaturesSupported;
uint32_t EncryptionFeaturesRequested;
uint32_t EncryptionFeaturesEnabled;
// Connection stages
static const char* stageNames[STAGE_MAX] = {
"none",
"platform initialization",
"name resolution",
"audio stream initialization",
"RTSP handshake",
"control stream initialization",
"video stream initialization",
"audio stream initialization",
"input stream initialization",
"control stream establishment",
"video stream establishment",
@@ -103,6 +91,12 @@ void LiStopConnection(void) {
stage--;
Limelog("done\n");
}
if (stage == STAGE_AUDIO_STREAM_INIT) {
Limelog("Cleaning up audio stream...");
destroyAudioStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_VIDEO_STREAM_INIT) {
Limelog("Cleaning up video stream...");
destroyVideoStream();
@@ -119,12 +113,6 @@ void LiStopConnection(void) {
// Nothing to do
stage--;
}
if (stage == STAGE_AUDIO_STREAM_INIT) {
Limelog("Cleaning up audio stream...");
destroyAudioStream();
stage--;
Limelog("done\n");
}
if (stage == STAGE_NAME_RESOLUTION) {
// Nothing to do
stage--;
@@ -175,34 +163,8 @@ static void ClInternalConnectionTerminated(int errorCode)
LC_ASSERT(err == 0);
}
// Detach the thread since we never wait on it
PltDetachThread(&terminationCallbackThread);
}
static bool parseRtspPortNumberFromUrl(const char* rtspSessionUrl, uint16_t* port)
{
// If the session URL is not present, we will just use the well known port
if (rtspSessionUrl == NULL) {
return false;
}
// Pick the last colon in the string to match the port number
char* portNumberStart = strrchr(rtspSessionUrl, ':');
if (portNumberStart == NULL) {
return false;
}
// Skip the colon
portNumberStart++;
// Validate the port number
long int rawPort = strtol(portNumberStart, NULL, 10);
if (rawPort <= 0 || rawPort > 65535) {
return false;
}
*port = (uint16_t)rawPort;
return true;
// Close the thread handle since we can never wait on it
PltCloseThread(&terminationCallbackThread);
}
// Starts the connection to the streaming machine
@@ -211,45 +173,11 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
void* audioContext, int arFlags) {
int err;
if (drCallbacks != NULL && (drCallbacks->capabilities & CAPABILITY_PULL_RENDERER) && drCallbacks->submitDecodeUnit) {
Limelog("CAPABILITY_PULL_RENDERER cannot be set with a submitDecodeUnit callback\n");
LC_ASSERT(false);
err = -1;
goto Cleanup;
}
if (drCallbacks != NULL && (drCallbacks->capabilities & CAPABILITY_PULL_RENDERER) && (drCallbacks->capabilities & CAPABILITY_DIRECT_SUBMIT)) {
Limelog("CAPABILITY_PULL_RENDERER and CAPABILITY_DIRECT_SUBMIT cannot be set together\n");
LC_ASSERT(false);
err = -1;
goto Cleanup;
}
if (serverInfo->serverCodecModeSupport == 0) {
Limelog("serverCodecModeSupport field in SERVER_INFORMATION must be set!\n");
LC_ASSERT(false);
err = -1;
goto Cleanup;
}
// Extract the appversion from the supplied string
if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion,
AppVersionQuad) < 0) {
Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion);
err = -1;
goto Cleanup;
}
// Replace missing callbacks with placeholders
fixupMissingCallbacks(&drCallbacks, &arCallbacks, &clCallbacks);
memcpy(&VideoCallbacks, drCallbacks, sizeof(VideoCallbacks));
memcpy(&AudioCallbacks, arCallbacks, sizeof(AudioCallbacks));
#ifdef LC_DEBUG_RECORD_MODE
// Install the pass-through recorder callbacks
setRecorderCallbacks(&VideoCallbacks, &AudioCallbacks);
#endif
// Hook the termination callback so we can avoid issuing a termination callback
// after LiStopConnection() is called.
//
@@ -258,29 +186,10 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
memcpy(&ListenerCallbacks, clCallbacks, sizeof(ListenerCallbacks));
ListenerCallbacks.connectionTerminated = ClInternalConnectionTerminated;
memset(&LocalAddr, 0, sizeof(LocalAddr));
NegotiatedVideoFormat = 0;
memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig));
OriginalVideoBitrate = streamConfig->bitrate;
RemoteAddrString = strdup(serverInfo->address);
// The values in RTSP SETUP will be used to populate these.
VideoPortNumber = 0;
ControlPortNumber = 0;
AudioPortNumber = 0;
// Parse RTSP port number from RTSP session URL
if (!parseRtspPortNumberFromUrl(serverInfo->rtspSessionUrl, &RtspPortNumber)) {
// Use the well known port if parsing fails
RtspPortNumber = 48010;
Limelog("RTSP port: %u (RTSP URL parsing failed)\n", RtspPortNumber);
}
else {
Limelog("RTSP port: %u\n", RtspPortNumber);
}
alreadyTerminated = false;
ConnectionInterrupted = false;
// Validate the audio configuration
if (MAGIC_BYTE_FROM_AUDIO_CONFIG(StreamConfig.audioConfiguration) != 0xCA ||
@@ -309,7 +218,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
}
// Dimensions over 4096 are only supported with HEVC on NVENC
if (!(StreamConfig.supportedVideoFormats & ~VIDEO_FORMAT_MASK_H264) &&
if (!StreamConfig.supportsHevc &&
(StreamConfig.width > 4096 || StreamConfig.height > 4096)) {
Limelog("WARNING: Streaming at resolutions above 4K using H.264 will likely fail! Trying anyway!\n");
}
@@ -317,18 +226,18 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
else if (StreamConfig.width > 8192 || StreamConfig.height > 8192) {
Limelog("WARNING: Streaming at resolutions above 8K will likely fail! Trying anyway!\n");
}
// Reference frame invalidation doesn't seem to work with resolutions much
// higher than 1440p. I haven't figured out a pattern to indicate which
// resolutions will work and which won't, but we can at least exclude
// 4K from RFI to avoid significant persistent artifacts after frame loss.
if (StreamConfig.width == 3840 && StreamConfig.height == 2160 &&
(VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC) &&
!IS_SUNSHINE()) {
Limelog("Disabling reference frame invalidation for 4K streaming with GFE\n");
VideoCallbacks.capabilities &= ~CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
}
// Extract the appversion from the supplied string
if (extractVersionQuadFromString(serverInfo->serverInfoAppVersion,
AppVersionQuad) < 0) {
Limelog("Invalid appversion string: %s\n", serverInfo->serverInfoAppVersion);
err = -1;
goto Cleanup;
}
alreadyTerminated = false;
ConnectionInterrupted = false;
Limelog("Initializing platform...");
ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT);
err = initializePlatform();
@@ -344,52 +253,18 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
Limelog("Resolving host name...");
ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION);
LC_ASSERT(RtspPortNumber != 0);
if (RtspPortNumber != 48010) {
// If we have an alternate RTSP port, use that as our test port. The host probably
// isn't listening on 47989 or 47984 anyway, since they're using alternate ports.
err = resolveHostName(serverInfo->address, AF_UNSPEC, RtspPortNumber, &RemoteAddr, &AddrLen);
if (err != 0) {
// Sleep for a second and try again. It's possible that we've attempt to connect
// before the host has gotten around to listening on the RTSP port. Give it some
// time before retrying.
PltSleepMs(1000);
err = resolveHostName(serverInfo->address, AF_UNSPEC, RtspPortNumber, &RemoteAddr, &AddrLen);
}
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47984, &RemoteAddr, &RemoteAddrLen);
if (err != 0) {
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47989, &RemoteAddr, &RemoteAddrLen);
}
else {
// We use TCP 47984 and 47989 first here because we know those should always be listening
// on hosts using the standard ports.
//
// TCP 48010 is a last resort because:
// a) it's not always listening and there's a race between listen() on the host and our connect()
// b) it's not used at all by certain host versions which perform RTSP over ENet
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47984, &RemoteAddr, &AddrLen);
if (err != 0) {
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47989, &RemoteAddr, &AddrLen);
}
if (err != 0) {
err = resolveHostName(serverInfo->address, AF_UNSPEC, 48010, &RemoteAddr, &AddrLen);
}
if (err != 0) {
err = resolveHostName(serverInfo->address, AF_UNSPEC, 48010, &RemoteAddr, &RemoteAddrLen);
}
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}
// Resolve LocalAddr by RemoteAddr.
{
SOCKADDR_LEN localAddrLen;
err = getLocalAddressByUdpConnect(&RemoteAddr, AddrLen, RtspPortNumber, &LocalAddr, &localAddrLen);
if (err != 0) {
Limelog("failed to resolve local addr: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_NAME_RESOLUTION, err);
goto Cleanup;
}
LC_ASSERT(localAddrLen == AddrLen);
}
stage++;
LC_ASSERT(stage == STAGE_NAME_RESOLUTION);
ListenerCallbacks.stageComplete(STAGE_NAME_RESOLUTION);
@@ -399,47 +274,24 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
// now that we have resolved the target address and impose the video packet
// size cap if required.
if (StreamConfig.streamingRemotely == STREAM_CFG_AUTO) {
bool isNat64 = isNat64SynthesizedAddress(&RemoteAddr);
// It's possible to have a NAT64 prefix on a ULA or other private range,
// so we must exclude NAT64 addresses from our local address checks.
if (!isNat64 && isPrivateNetworkAddress(&RemoteAddr)) {
if (isPrivateNetworkAddress(&RemoteAddr)) {
StreamConfig.streamingRemotely = STREAM_CFG_LOCAL;
}
else {
StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
if (RemoteAddr.ss_family == AF_INET || isNat64) {
// Cap packet size at 1024 for remote IPv4 streaming to avoid fragmentation.
Limelog("Packet size capped at 1024 bytes for remote IPv4 streaming\n");
if (StreamConfig.packetSize > 1024) {
// Cap packet size at 1024 for remote streaming to avoid
// MTU problems and fragmentation.
Limelog("Packet size capped at 1KB for remote streaming\n");
StreamConfig.packetSize = 1024;
}
else {
// IPv6 guarantees a minimum MTU of 1280 before fragmentation, so use a higher
// packet size cap for remote IPv6 streaming (when not using NAT64 which isn't
// end-to-end IPv6 traffic).
Limelog("Packet size capped at 1184 bytes for remote IPv6 streaming\n");
StreamConfig.packetSize = 1184;
}
}
}
Limelog("Initializing audio stream...");
ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_INIT);
err = initializeAudioStream();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_AUDIO_STREAM_INIT, err);
goto Cleanup;
}
stage++;
LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT);
Limelog("done\n");
Limelog("Starting RTSP handshake...");
ListenerCallbacks.stageStarting(STAGE_RTSP_HANDSHAKE);
err = performRtspHandshake(serverInfo);
err = performRtspHandshake();
if (err != 0) {
Limelog("failed: %d\n", err);
ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err);
@@ -471,6 +323,14 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
ListenerCallbacks.stageComplete(STAGE_VIDEO_STREAM_INIT);
Limelog("done\n");
Limelog("Initializing audio stream...");
ListenerCallbacks.stageStarting(STAGE_AUDIO_STREAM_INIT);
initializeAudioStream();
stage++;
LC_ASSERT(stage == STAGE_AUDIO_STREAM_INIT);
ListenerCallbacks.stageComplete(STAGE_AUDIO_STREAM_INIT);
Limelog("done\n");
Limelog("Initializing input stream...");
ListenerCallbacks.stageStarting(STAGE_INPUT_STREAM_INIT);
initializeInputStream();
@@ -546,9 +406,3 @@ Cleanup:
}
return err;
}
const char* LiGetLaunchUrlQueryParameters(void) {
// v0 = Video encryption and control stream encryption v2
// v1 = RTSP encryption
return "&corever=1";
}
+2 -30
View File
@@ -19,8 +19,7 @@ unsigned int LiGetPortFlagsFromStage(int stage)
switch (stage)
{
case STAGE_RTSP_HANDSHAKE:
// GFE 3.22 requires a successful ping on 48000 to complete RTSP handshake
return ML_PORT_FLAG_TCP_48010 | ML_PORT_FLAG_UDP_48010 | ML_PORT_FLAG_UDP_48000;
return ML_PORT_FLAG_TCP_48010 | ML_PORT_FLAG_UDP_48010;
case STAGE_CONTROL_STREAM_START:
return ML_PORT_FLAG_UDP_47999;
@@ -78,33 +77,6 @@ unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex)
}
}
void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* outputBuffer, int outputBufferLength)
{
// Initialize the output buffer to an empty string
outputBuffer[0] = 0;
// If there is no separator specified, use an empty string
if (separator == NULL) {
separator = "";
}
int offset = 0;
for (int i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
if (portFlags & (1U << i)) {
const char* protoStr = LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? "UDP" : "TCP";
offset += snprintf(&outputBuffer[offset], outputBufferLength - offset, "%s%s %u",
offset != 0 ? separator : "",
protoStr,
LiGetPortFromPortFlagIndex(i));
if (outputBufferLength - offset <= 0) {
// snprintf() will return the desired length if the buffer is too small,
// so it is possible for this calculation to be negative.
break;
}
}
}
}
unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags)
{
unsigned int failingPortFlags;
@@ -151,7 +123,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
goto Exit;
}
SET_PORT((LC_SOCKADDR*)&address, LiGetPortFromPortFlagIndex(i));
((struct sockaddr_in6*)&address)->sin6_port = htons(LiGetPortFromPortFlagIndex(i));
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) {
// Initiate an asynchronous connection
err = connect(sockets[i], (struct sockaddr*)&address, address_length);
+215 -1282
View File
File diff suppressed because it is too large Load Diff
+1 -26
View File
@@ -36,11 +36,6 @@ static void fakeClConnectionTerminated(int errorCode) {}
static void fakeClLogMessage(const char* format, ...) {}
static void fakeClRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor) {}
static void fakeClConnectionStatusUpdate(int connectionStatus) {}
static void fakeClSetHdrMode(bool enabled) {}
static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor) {}
static void fakeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {}
static void fakeClSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right) {};
static void fakeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {}
static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
.stageStarting = fakeClStageStarting,
@@ -50,12 +45,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
.connectionTerminated = fakeClConnectionTerminated,
.logMessage = fakeClLogMessage,
.rumble = fakeClRumble,
.connectionStatusUpdate = fakeClConnectionStatusUpdate,
.setHdrMode = fakeClSetHdrMode,
.rumbleTriggers = fakeClRumbleTriggers,
.setMotionEventState = fakeClSetMotionEventState,
.setControllerLED = fakeClSetControllerLED,
.setAdaptiveTriggers = fakeClSetAdaptiveTriggers,
.connectionStatusUpdate = fakeClConnectionStatusUpdate
};
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
@@ -131,20 +121,5 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND
if ((*clCallbacks)->connectionStatusUpdate == NULL) {
(*clCallbacks)->connectionStatusUpdate = fakeClConnectionStatusUpdate;
}
if ((*clCallbacks)->setHdrMode == NULL) {
(*clCallbacks)->setHdrMode = fakeClSetHdrMode;
}
if ((*clCallbacks)->rumbleTriggers == NULL) {
(*clCallbacks)->rumbleTriggers = fakeClRumbleTriggers;
}
if ((*clCallbacks)->setMotionEventState == NULL) {
(*clCallbacks)->setMotionEventState = fakeClSetMotionEventState;
}
if ((*clCallbacks)->setControllerLED == NULL) {
(*clCallbacks)->setControllerLED = fakeClSetControllerLED;
}
if ((*clCallbacks)->setAdaptiveTriggers == NULL) {
(*clCallbacks)->setAdaptiveTriggers = fakeClSetAdaptiveTriggers;
}
}
}
+32 -114
View File
@@ -1,52 +1,44 @@
#pragma once
#include <stdint.h>
#pragma pack(push, 1)
// netfloat is just a little-endian float in byte form
// for network transmission.
typedef uint8_t netfloat[4];
typedef struct _NV_INPUT_HEADER {
uint32_t size; // Size of packet (excluding this field) - Big Endian
uint32_t magic; // Packet type - Little Endian
} NV_INPUT_HEADER, *PNV_INPUT_HEADER;
uint32_t packetType;
} NV_INPUT_HEADER, PNV_INPUT_HEADER;
#define ENABLE_HAPTICS_MAGIC 0x0000000D
#define PACKET_TYPE_HAPTICS 0x06
#define H_MAGIC_A 0x0000000D
#define H_MAGIC_B 0x00000001
typedef struct _NV_HAPTICS_PACKET {
NV_INPUT_HEADER header;
uint16_t enable;
int magicA;
int magicB;
} NV_HAPTICS_PACKET, *PNV_HAPTICS_PACKET;
#define KEY_DOWN_EVENT_MAGIC 0x00000003
#define KEY_UP_EVENT_MAGIC 0x00000004
#define PACKET_TYPE_KEYBOARD 0x0A
typedef struct _NV_KEYBOARD_PACKET {
NV_INPUT_HEADER header;
char flags; // Sunshine extension (always 0 for GFE)
char keyAction;
int zero1;
short keyCode;
char modifiers;
short zero2;
} NV_KEYBOARD_PACKET, *PNV_KEYBOARD_PACKET;
#define UTF8_TEXT_EVENT_MAGIC 0x00000017
#define UTF8_TEXT_EVENT_MAX_COUNT 32
typedef struct _NV_UNICODE_PACKET {
NV_INPUT_HEADER header;
char text[UTF8_TEXT_EVENT_MAX_COUNT];
} NV_UNICODE_PACKET, *PNV_UNICODE_PACKET;
#define MOUSE_MOVE_REL_MAGIC 0x00000006
#define MOUSE_MOVE_REL_MAGIC_GEN5 0x00000007
#define PACKET_TYPE_REL_MOUSE_MOVE 0x08
#define MOUSE_MOVE_REL_MAGIC 0x06
typedef struct _NV_REL_MOUSE_MOVE_PACKET {
NV_INPUT_HEADER header;
int magic;
short deltaX;
short deltaY;
} NV_REL_MOUSE_MOVE_PACKET, *PNV_REL_MOUSE_MOVE_PACKET;
#define MOUSE_MOVE_ABS_MAGIC 0x00000005
#define PACKET_TYPE_ABS_MOUSE_MOVE 0x0e
#define MOUSE_MOVE_ABS_MAGIC 0x05
typedef struct _NV_ABS_MOUSE_MOVE_PACKET {
NV_INPUT_HEADER header;
int magic;
short x;
short y;
@@ -59,19 +51,21 @@ typedef struct _NV_ABS_MOUSE_MOVE_PACKET {
short height;
} NV_ABS_MOUSE_MOVE_PACKET, *PNV_ABS_MOUSE_MOVE_PACKET;
#define MOUSE_BUTTON_DOWN_EVENT_MAGIC_GEN5 0x00000008
#define MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5 0x00000009
#define PACKET_TYPE_MOUSE_BUTTON 0x05
typedef struct _NV_MOUSE_BUTTON_PACKET {
NV_INPUT_HEADER header;
uint8_t button;
char action;
int button;
} NV_MOUSE_BUTTON_PACKET, *PNV_MOUSE_BUTTON_PACKET;
#define CONTROLLER_MAGIC 0x0000000A
#define PACKET_TYPE_CONTROLLER 0x18
#define C_HEADER_A 0x0000000A
#define C_HEADER_B 0x1400
#define C_TAIL_A 0x0000009C
#define C_TAIL_B 0x0055
typedef struct _NV_CONTROLLER_PACKET {
NV_INPUT_HEADER header;
int headerA;
short headerB;
short buttonFlags;
unsigned char leftTrigger;
@@ -84,14 +78,15 @@ typedef struct _NV_CONTROLLER_PACKET {
short tailB;
} NV_CONTROLLER_PACKET, *PNV_CONTROLLER_PACKET;
#define MULTI_CONTROLLER_MAGIC 0x0000000D
#define MULTI_CONTROLLER_MAGIC_GEN5 0x0000000C
#define PACKET_TYPE_MULTI_CONTROLLER 0x1E
#define MC_HEADER_A 0x0000000D
#define MC_HEADER_B 0x001A
#define MC_MID_B 0x0014
#define MC_TAIL_A 0x009C
#define MC_TAIL_A 0x0000009C
#define MC_TAIL_B 0x0055
typedef struct _NV_MULTI_CONTROLLER_PACKET {
NV_INPUT_HEADER header;
int headerA;
short headerB;
short controllerNumber;
short activeGamepadMask;
@@ -103,97 +98,20 @@ typedef struct _NV_MULTI_CONTROLLER_PACKET {
short leftStickY;
short rightStickX;
short rightStickY;
short tailA;
short buttonFlags2; // Sunshine protocol extension (always 0 for GFE)
int tailA;
short tailB;
} NV_MULTI_CONTROLLER_PACKET, *PNV_MULTI_CONTROLLER_PACKET;
#define SCROLL_MAGIC 0x00000009
#define SCROLL_MAGIC_GEN5 0x0000000A
#define PACKET_TYPE_SCROLL 0xA
#define MAGIC_A 0x09
typedef struct _NV_SCROLL_PACKET {
NV_INPUT_HEADER header;
char magicA;
char zero1;
short zero2;
short scrollAmt1;
short scrollAmt2;
short zero3;
} NV_SCROLL_PACKET, *PNV_SCROLL_PACKET;
#define SS_HSCROLL_MAGIC 0x55000001
typedef struct _SS_HSCROLL_PACKET {
NV_INPUT_HEADER header;
short scrollAmount;
} SS_HSCROLL_PACKET, *PSS_HSCROLL_PACKET;
#define SS_TOUCH_MAGIC 0x55000002
typedef struct _SS_TOUCH_PACKET {
NV_INPUT_HEADER header;
uint8_t eventType;
uint8_t zero[1]; // Alignment/reserved
uint16_t rotation;
uint32_t pointerId;
netfloat x;
netfloat y;
netfloat pressureOrDistance;
netfloat contactAreaMajor;
netfloat contactAreaMinor;
} SS_TOUCH_PACKET, *PSS_TOUCH_PACKET;
#define SS_PEN_MAGIC 0x55000003
typedef struct _SS_PEN_PACKET {
NV_INPUT_HEADER header;
uint8_t eventType;
uint8_t toolType;
uint8_t penButtons;
uint8_t zero[1]; // Alignment/reserved
netfloat x;
netfloat y;
netfloat pressureOrDistance;
uint16_t rotation;
uint8_t tilt;
uint8_t zero2[1];
netfloat contactAreaMajor;
netfloat contactAreaMinor;
} SS_PEN_PACKET, *PSS_PEN_PACKET;
#define SS_CONTROLLER_ARRIVAL_MAGIC 0x55000004
typedef struct _SS_CONTROLLER_ARRIVAL_PACKET {
NV_INPUT_HEADER header;
uint8_t controllerNumber;
uint8_t type;
uint16_t capabilities;
uint32_t supportedButtonFlags;
} SS_CONTROLLER_ARRIVAL_PACKET, *PSS_CONTROLLER_ARRIVAL_PACKET;
#define SS_CONTROLLER_TOUCH_MAGIC 0x55000005
typedef struct _SS_CONTROLLER_TOUCH_PACKET {
NV_INPUT_HEADER header;
uint8_t controllerNumber;
uint8_t eventType;
uint8_t zero; // Alignment/reserved
uint8_t touchpadIndex;
uint32_t pointerId;
netfloat x;
netfloat y;
netfloat pressure;
} SS_CONTROLLER_TOUCH_PACKET, *PSS_CONTROLLER_TOUCH_PACKET;
#define SS_CONTROLLER_MOTION_MAGIC 0x55000006
typedef struct _SS_CONTROLLER_MOTION_PACKET {
NV_INPUT_HEADER header;
uint8_t controllerNumber;
uint8_t motionType;
uint8_t zero[2]; // Alignment/reserved
netfloat x;
netfloat y;
netfloat z;
} SS_CONTROLLER_MOTION_PACKET, *PSS_CONTROLLER_MOTION_PACKET;
#define SS_CONTROLLER_BATTERY_MAGIC 0x55000007
typedef struct _SS_CONTROLLER_BATTERY_PACKET {
NV_INPUT_HEADER header;
uint8_t controllerNumber;
uint8_t batteryState;
uint8_t batteryPercentage;
uint8_t zero[1]; // Alignment/reserved
} SS_CONTROLLER_BATTERY_PACKET, *PSS_CONTROLLER_BATTERY_PACKET;
#pragma pack(pop)
+424 -1306
View File
File diff suppressed because it is too large Load Diff
+15 -70
View File
@@ -1,69 +1,31 @@
#pragma once
#include "Platform.h"
#include "Limelight.h"
#include "Platform.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "PlatformCrypto.h"
#include "Video.h"
#include "Input.h"
#include "RtpAudioQueue.h"
#include "RtpVideoQueue.h"
#include "ByteBuffer.h"
#include "RtpFecQueue.h"
#include <enet/enet.h>
// Common globals
extern char* RemoteAddrString;
extern struct sockaddr_storage RemoteAddr;
extern struct sockaddr_storage LocalAddr;
extern SOCKADDR_LEN AddrLen;
extern SOCKADDR_LEN RemoteAddrLen;
extern int AppVersionQuad[4];
extern STREAM_CONFIGURATION StreamConfig;
extern CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
extern DECODER_RENDERER_CALLBACKS VideoCallbacks;
extern AUDIO_RENDERER_CALLBACKS AudioCallbacks;
extern int NegotiatedVideoFormat;
extern volatile bool ConnectionInterrupted;
extern atomic_bool ConnectionInterrupted;
extern bool HighQualitySurroundSupported;
extern bool HighQualitySurroundEnabled;
extern OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
extern OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
extern int OriginalVideoBitrate;
extern int AudioPacketDuration;
extern bool AudioEncryptionEnabled;
extern bool ReferenceFrameInvalidationSupported;
extern uint16_t RtspPortNumber;
extern uint16_t ControlPortNumber;
extern uint16_t AudioPortNumber;
extern uint16_t VideoPortNumber;
extern SS_PING AudioPingPayload;
extern SS_PING VideoPingPayload;
extern uint32_t ControlConnectData;
extern uint32_t SunshineFeatureFlags;
// Encryption flags shared by Sunshine and Moonlight in RTSP
#define SS_ENC_CONTROL_V2 0x01
#define SS_ENC_VIDEO 0x02
#define SS_ENC_AUDIO 0x04
extern uint32_t EncryptionFeaturesSupported;
extern uint32_t EncryptionFeaturesRequested;
extern uint32_t EncryptionFeaturesEnabled;
// ENet channel ID values
#define CTRL_CHANNEL_GENERIC 0x00
#define CTRL_CHANNEL_URGENT 0x01 // IDR, LTR ACK and RFI
#define CTRL_CHANNEL_KEYBOARD 0x02
#define CTRL_CHANNEL_MOUSE 0x03
#define CTRL_CHANNEL_PEN 0x04
#define CTRL_CHANNEL_TOUCH 0x05
#define CTRL_CHANNEL_UTF8 0x06
#define CTRL_CHANNEL_GAMEPAD_BASE 0x10 // 0x10 to 0x1F by controller index
#define CTRL_CHANNEL_SENSOR_BASE 0x20 // 0x20 to 0x2F by controller index
#define CTRL_CHANNEL_COUNT 0x30
#ifndef UINT24_MAX
#define UINT24_MAX 0xFFFFFF
@@ -77,17 +39,6 @@ extern uint32_t EncryptionFeaturesEnabled;
#define isBefore24(x, y) (U24((x) - (y)) > (UINT24_MAX/2))
#define isBefore32(x, y) (U32((x) - (y)) > (UINT32_MAX/2))
#define APP_VERSION_AT_LEAST(a, b, c) \
((AppVersionQuad[0] > (a)) || \
(AppVersionQuad[0] == (a) && AppVersionQuad[1] > (b)) || \
(AppVersionQuad[0] == (a) && AppVersionQuad[1] == (b) && AppVersionQuad[2] >= (c)))
#define IS_SUNSHINE() (AppVersionQuad[3] < 0)
// Client feature flags for x-ml-general.featureFlags SDP attribute
#define ML_FF_FEC_STATUS 0x01 // Client sends SS_FRAME_FEC_STATUS for frame losses
#define ML_FF_SESSION_ID_V1 0x02 // Client supports X-SS-Ping-Payload and X-SS-Connect-Data
#define UDP_RECV_POLL_TIMEOUT_MS 100
// At this value or above, we will request high quality audio unless CAPABILITY_SLOW_OPUS_DECODER
@@ -102,15 +53,12 @@ extern uint32_t EncryptionFeaturesEnabled;
#define MAGIC_BYTE_FROM_AUDIO_CONFIG(x) ((x) & 0xFF)
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs);
int gracefullyDisconnectEnetPeer(ENetHost* host, ENetPeer* peer, enet_uint32 lingerTimeoutMs);
int extractVersionQuadFromString(const char* string, int* quad);
bool isReferenceFrameInvalidationSupportedByDecoder(void);
bool isReferenceFrameInvalidationEnabled(void);
void* extendBuffer(void* ptr, size_t newSize);
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
PCONNECTION_LISTENER_CALLBACKS* clCallbacks);
void setRecorderCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks);
char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length);
@@ -118,31 +66,28 @@ int initializeControlStream(void);
int startControlStream(void);
int stopControlStream(void);
void destroyControlStream(void);
void connectionDetectedFrameLoss(uint32_t startFrame, uint32_t endFrame);
void connectionReceivedCompleteFrame(uint32_t frameIndex, bool frameIsLTR);
void connectionSawFrame(uint32_t frameIndex);
void connectionSendFrameFecStatus(PSS_FRAME_FEC_STATUS fecStatus);
int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t channelId, uint32_t flags, bool moreData);
void flushInputOnControlStream(void);
bool isControlDataInTransit(void);
void requestIdrOnDemand(void);
void connectionDetectedFrameLoss(int startFrame, int endFrame);
void connectionReceivedCompleteFrame(int frameIndex);
void connectionSawFrame(int frameIndex);
void connectionLostPackets(int lastReceivedPacket, int nextReceivedPacket);
int sendInputPacketOnControlStream(unsigned char* data, int length);
int performRtspHandshake(PSERVER_INFORMATION serverInfo);
int performRtspHandshake(void);
void initializeVideoDepacketizer(int pktSize);
void destroyVideoDepacketizer(void);
void queueRtpPacket(PRTPV_QUEUE_ENTRY queueEntry);
void queueRtpPacket(PRTPFEC_QUEUE_ENTRY queueEntry);
void stopVideoDepacketizer(void);
void requestDecoderRefresh(void);
void notifyFrameLost(unsigned int frameNumber, bool speculative);
void initializeVideoStream(void);
void destroyVideoStream(void);
void notifyKeyFrameReceived(void);
int startVideoStream(void* rendererContext, int drFlags);
void submitFrame(PQUEUED_DECODE_UNIT qdu);
void stopVideoStream(void);
int initializeAudioStream(void);
int notifyAudioPortNegotiationComplete(void);
void initializeAudioStream(void);
void destroyAudioStream(void);
int startAudioStream(void* audioContext, int arFlags);
void stopAudioStream(void);
+59 -482
View File
@@ -20,7 +20,7 @@ extern "C" {
#define STREAM_CFG_AUTO 2
// Values for the 'colorSpace' field below.
// Rec. 2020 is not supported with H.264 video streams on GFE hosts.
// Rec. 2020 is only supported with HEVC video streams.
#define COLORSPACE_REC_601 0
#define COLORSPACE_REC_709 1
#define COLORSPACE_REC_2020 2
@@ -29,18 +29,6 @@ extern "C" {
#define COLOR_RANGE_LIMITED 0
#define COLOR_RANGE_FULL 1
// Values for 'encryptionFlags' field below
#define ENCFLG_NONE 0x00000000
#define ENCFLG_AUDIO 0x00000001
#define ENCFLG_VIDEO 0x00000002
#define ENCFLG_ALL 0xFFFFFFFF
// This function returns a string that you SHOULD append to the /launch and /resume
// query parameter string. This is used to enable certain extended functionality
// with Sunshine hosts. The returned string is owned by moonlight-common-c and
// should not be freed by the caller.
const char* LiGetLaunchUrlQueryParameters(void);
typedef struct _STREAM_CONFIGURATION {
// Dimensions in pixels of the desired video stream
int width;
@@ -49,9 +37,7 @@ typedef struct _STREAM_CONFIGURATION {
// FPS of the desired video stream
int fps;
// Bitrate of the desired video stream (audio adds another ~1 Mbps). This
// includes error correction data, so the actual encoder bitrate will be
// about 20% lower when using the standard 20% FEC configuration.
// Bitrate of the desired video stream (audio adds another ~1 Mbps)
int bitrate;
// Max video packet size in bytes (use 1024 if unsure). If STREAM_CFG_AUTO
@@ -69,10 +55,24 @@ typedef struct _STREAM_CONFIGURATION {
// Specifies the channel configuration of the audio stream.
// See AUDIO_CONFIGURATION constants and MAKE_AUDIO_CONFIGURATION() below.
int audioConfiguration;
// Specifies that the client can accept an H.265 video stream
// if the server is able to provide one.
int supportsHevc;
// Specifies the mask of supported video formats.
// See VIDEO_FORMAT constants below.
int supportedVideoFormats;
// Specifies that the client is requesting an HDR H.265 video stream.
//
// This should only be set if:
// 1) The client decoder supports HEVC Main10 profile (supportsHevc must be set too)
// 2) The server has support for HDR as indicated by ServerCodecModeSupport in /serverinfo
// 3) The app supports HDR as indicated by IsHdrSupported in /applist
int enableHdr;
// Specifies the percentage that the specified bitrate will be adjusted
// when an HEVC stream will be delivered. This allows clients to opt to
// reduce bandwidth when HEVC is chosen as the video codec rather than
// (or in addition to) improving image quality.
int hevcBitratePercentageMultiplier;
// If specified, the client's display refresh rate x 100. For example,
// 59.94 Hz would be specified as 5994. This is used by recent versions
@@ -87,14 +87,6 @@ typedef struct _STREAM_CONFIGURATION {
// option (listed above). If not set, the encoder will default to Limited.
int colorRange;
// Specifies the data streams where encryption may be enabled if supported
// by the host PC. Ideally, you would pass ENCFLG_ALL to encrypt everything
// that we support encrypting. However, lower performance hardware may not
// be able to support encrypting heavy stuff like video or audio data, so
// that encryption may be disabled here. Remote input encryption is always
// enabled.
int encryptionFlags;
// AES encryption data for the remote input stream. This must be
// the same as what was passed as rikey and rikeyid
// in /launch and /resume requests.
@@ -106,8 +98,7 @@ typedef struct _STREAM_CONFIGURATION {
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig);
// These identify codec configuration data in the buffer lists
// of frames identified as IDR frames for H.264 and HEVC formats.
// For other codecs, all data is marked as BUFFER_TYPE_PICDATA.
// of frames identified as IDR frames.
#define BUFFER_TYPE_PICDATA 0x00
#define BUFFER_TYPE_SPS 0x01
#define BUFFER_TYPE_PPS 0x02
@@ -123,7 +114,7 @@ typedef struct _LENTRY {
// Size of data in bytes (never <= 0)
int length;
// Buffer type (listed above, only set for H.264 and HEVC formats)
// Buffer type (listed above)
int bufferType;
} LENTRY, *PLENTRY;
@@ -131,13 +122,10 @@ typedef struct _LENTRY {
// previous P-frames.
#define FRAME_TYPE_PFRAME 0x00
// This is a key frame.
//
// For H.264 and HEVC, this means the frame contains SPS, PPS, and VPS (HEVC only) NALUs
// as the first buffers in the list. The I-frame data follows immediately
// Indicates this frame contains SPS, PPS, and VPS (if applicable)
// as the first buffers in the list. Each NALU will appear as a separate
// buffer in the buffer list. The I-frame data follows immediately
// after the codec configuration NALUs.
//
// For other codecs, any configuration data is not split into separate buffers.
#define FRAME_TYPE_IDR 0x01
// A decode unit describes a buffer chain of video data from multiple packets
@@ -148,49 +136,21 @@ typedef struct _DECODE_UNIT {
// Frame type
int frameType;
// Optional host processing latency of the frame, in 1/10 ms units.
// Zero when the host doesn't provide the latency data
// or frame processing latency is not applicable to the current frame
// (happens when the frame is repeated).
uint16_t frameHostProcessingLatency;
// Receive time of first buffer. This value uses an implementation-defined epoch.
// To compute actual latency values, use LiGetMillis() to get a timestamp that
// shares the same epoch as this value.
uint64_t receiveTimeMs;
// Receive time of first buffer in microseconds.
uint64_t receiveTimeUs;
// Time the frame was fully assembled and queued for the video decoder to process.
// This is also approximately the same time as the final packet was received, so
// enqueueTimeUs - receiveTimeUs is the time taken to receive the frame. At the
// time the decode unit is passed to submitDecodeUnit(), the total queue delay
// can be calculated. This value is in microseconds.
uint64_t enqueueTimeUs;
// Presentation time in microseconds with the epoch at the first captured frame.
// Presentation time in milliseconds with the epoch at the first captured frame.
// This can be used to aid frame pacing or to drop old frames that were queued too
// long prior to display.
uint64_t presentationTimeUs;
// Original RTP timestamp in 90kHz units. Useful when using APIs that deal with integer
// time such as Apple's CMTime. To exactly recover the RTP timestamp, use something like
// CMTimeMake((int64_t)du->rtpTimestamp, 90000);
uint32_t rtpTimestamp;
unsigned int presentationTimeMs;
// Length of the entire buffer chain in bytes
int fullLength;
// Head of the buffer chain (never NULL)
PLENTRY bufferList;
// Determines if this frame is SDR or HDR
//
// Note: This is not currently parsed from the actual bitstream, so if your
// client has access to a bitstream parser, prefer that over this field.
bool hdrActive;
// Provides the colorspace of this frame (see COLORSPACE_* defines above)
//
// Note: This is not currently parsed from the actual bitstream, so if your
// client has access to a bitstream parser, prefer that over this field.
uint8_t colorspace;
} DECODE_UNIT, *PDECODE_UNIT;
// Specifies that the audio stream should be encoded in stereo (default)
@@ -220,25 +180,21 @@ typedef struct _DECODE_UNIT {
// The maximum number of channels supported
#define AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT 8
// Passed in StreamConfiguration.supportedVideoFormats to specify supported codecs
// and to DecoderRendererSetup() to specify selected codec.
#define VIDEO_FORMAT_H264 0x0001 // H.264 High Profile
#define VIDEO_FORMAT_H264_HIGH8_444 0x0004 // H.264 High 4:4:4 8-bit Profile
#define VIDEO_FORMAT_H265 0x0100 // HEVC Main Profile
#define VIDEO_FORMAT_H265_MAIN10 0x0200 // HEVC Main10 Profile
#define VIDEO_FORMAT_H265_REXT8_444 0x0400 // HEVC RExt 4:4:4 8-bit Profile
#define VIDEO_FORMAT_H265_REXT10_444 0x0800 // HEVC RExt 4:4:4 10-bit Profile
#define VIDEO_FORMAT_AV1_MAIN8 0x1000 // AV1 Main 8-bit profile
#define VIDEO_FORMAT_AV1_MAIN10 0x2000 // AV1 Main 10-bit profile
#define VIDEO_FORMAT_AV1_HIGH8_444 0x4000 // AV1 High 4:4:4 8-bit profile
#define VIDEO_FORMAT_AV1_HIGH10_444 0x8000 // AV1 High 4:4:4 10-bit profile
// Passed to DecoderRendererSetup to indicate that the following video stream will be
// in H.264 High Profile.
#define VIDEO_FORMAT_H264 0x0001
// Passed to DecoderRendererSetup to indicate that the following video stream will be
// in H.265 Main profile. This will only be passed if supportsHevc is true.
#define VIDEO_FORMAT_H265 0x0100
// Passed to DecoderRendererSetup to indicate that the following video stream will be
// in H.265 Main10 (HDR10) profile. This will only be passed if enableHdr is true.
#define VIDEO_FORMAT_H265_MAIN10 0x0200
// Masks for clients to use to match video codecs without profile-specific details.
#define VIDEO_FORMAT_MASK_H264 0x000F
#define VIDEO_FORMAT_MASK_H265 0x0F00
#define VIDEO_FORMAT_MASK_AV1 0xF000
#define VIDEO_FORMAT_MASK_10BIT 0xAA00
#define VIDEO_FORMAT_MASK_YUV444 0xCC04
#define VIDEO_FORMAT_MASK_H264 0x00FF
#define VIDEO_FORMAT_MASK_H265 0xFF00
// If set in the renderer capabilities field, this flag will cause audio/video data to
// be submitted directly from the receive thread. This should only be specified if the
@@ -266,16 +222,6 @@ typedef struct _DECODE_UNIT {
// buffer size rather than just assuming it will always be 240.
#define CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION 0x10
// This flag opts the renderer into a pull-based model rather than the default push-based
// callback model. The renderer must invoke the new functions (LiWaitForNextVideoFrame(),
// LiCompleteVideoFrame(), and similar) to receive A/V data. Setting this capability while
// also providing a sample callback is not allowed.
#define CAPABILITY_PULL_RENDERER 0x20
// If set in the video renderer capabilities field, this flag specifies that the renderer
// supports reference frame invalidation for AV1 streams. This flag is only valid on video renderers.
#define CAPABILITY_REFERENCE_FRAME_INVALIDATION_AV1 0x40
// If set in the video renderer capabilities field, this macro specifies that the renderer
// supports slicing to increase decoding performance. The parameter specifies the desired
// number of slices per frame. This capability is only valid on video renderers.
@@ -373,10 +319,10 @@ void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks);
#define STAGE_NONE 0
#define STAGE_PLATFORM_INIT 1
#define STAGE_NAME_RESOLUTION 2
#define STAGE_AUDIO_STREAM_INIT 3
#define STAGE_RTSP_HANDSHAKE 4
#define STAGE_CONTROL_STREAM_INIT 5
#define STAGE_VIDEO_STREAM_INIT 6
#define STAGE_RTSP_HANDSHAKE 3
#define STAGE_CONTROL_STREAM_INIT 4
#define STAGE_VIDEO_STREAM_INIT 5
#define STAGE_AUDIO_STREAM_INIT 6
#define STAGE_INPUT_STREAM_INIT 7
#define STAGE_CONTROL_STREAM_START 8
#define STAGE_VIDEO_STREAM_START 9
@@ -423,22 +369,6 @@ typedef void(*ConnListenerConnectionTerminated)(int errorCode);
// an extremely unstable connection or a bitrate that is far too high.
#define ML_ERROR_NO_VIDEO_FRAME -101
// This error is passed to ConnListenerConnectionTerminated() if the stream ends
// very soon after starting due to a graceful termination from the host. Usually
// this seems to happen if DRM protected content is on-screen (pre-GFE 3.22), or
// another issue that prevents the encoder from being able to capture video successfully.
#define ML_ERROR_UNEXPECTED_EARLY_TERMINATION -102
// This error is passed to ConnListenerConnectionTerminated() if the stream ends
// due to a protected content error from the host. This value is supported on GFE 3.22+.
#define ML_ERROR_PROTECTED_CONTENT -103
// This error is passed to ConnListenerConnectionTerminated() if the stream ends
// due a frame conversion error. This is most commonly due to an incompatible
// desktop resolution and streaming resolution with HDR enabled. This value is
// supported on GFE 3.22+.
#define ML_ERROR_FRAME_CONVERSION -104
// This callback is invoked to log debug message
typedef void(*ConnListenerLogMessage)(const char* format, ...);
@@ -456,33 +386,6 @@ typedef void(*ConnListenerRumble)(unsigned short controllerNumber, unsigned shor
#define CONN_STATUS_POOR 1
typedef void(*ConnListenerConnectionStatusUpdate)(int connectionStatus);
// This callback is invoked to notify the client of a change in HDR mode on
// the host. The client will probably want to update the local display mode
// to match the state of HDR on the host. This callback may be invoked even
// if the stream is not using an HDR-capable codec.
typedef void(*ConnListenerSetHdrMode)(bool hdrEnabled);
// This callback is invoked to rumble a gamepad's triggers. For more details,
// see the comment above on ConnListenerRumble().
typedef void(*ConnListenerRumbleTriggers)(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor);
// This callback is invoked to notify the client that the host would like motion
// sensor reports for the specified gamepad (see LiSendControllerMotionEvent())
// at the specified reporting rate (or as close as possible).
//
// If reportRateHz is 0, the host is asking for motion event reporting to stop.
typedef void(*ConnListenerSetMotionEventState)(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz);
// This callback is invoked to notify the client of a change in the dualsense
// adaptive trigger configuration.
#define DS_EFFECT_PAYLOAD_SIZE 10
#define DS_EFFECT_RIGHT_TRIGGER 0x04
#define DS_EFFECT_LEFT_TRIGGER 0x08
typedef void(*ConnListenerSetAdaptiveTriggers)(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right);
// This callback is invoked to set a controller's RGB LED (if present).
typedef void(*ConnListenerSetControllerLED)(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b);
typedef struct _CONNECTION_LISTENER_CALLBACKS {
ConnListenerStageStarting stageStarting;
ConnListenerStageComplete stageComplete;
@@ -492,50 +395,21 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS {
ConnListenerLogMessage logMessage;
ConnListenerRumble rumble;
ConnListenerConnectionStatusUpdate connectionStatusUpdate;
ConnListenerSetHdrMode setHdrMode;
ConnListenerRumbleTriggers rumbleTriggers;
ConnListenerSetMotionEventState setMotionEventState;
ConnListenerSetControllerLED setControllerLED;
ConnListenerSetAdaptiveTriggers setAdaptiveTriggers;
} CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS;
// Use this function to zero the connection callbacks when allocated on the stack or heap
void LiInitializeConnectionCallbacks(PCONNECTION_LISTENER_CALLBACKS clCallbacks);
// ServerCodecModeSupport values
#define SCM_H264 0x00000001
#define SCM_HEVC 0x00000100
#define SCM_HEVC_MAIN10 0x00000200
#define SCM_AV1_MAIN8 0x00010000 // Sunshine extension
#define SCM_AV1_MAIN10 0x00020000 // Sunshine extension
#define SCM_H264_HIGH8_444 0x00040000 // Sunshine extension
#define SCM_HEVC_REXT8_444 0x00080000 // Sunshine extension
#define SCM_HEVC_REXT10_444 0x00100000 // Sunshine extension
#define SCM_AV1_HIGH8_444 0x00200000 // Sunshine extension
#define SCM_AV1_HIGH10_444 0x00400000 // Sunshine extension
// SCM masks to identify various codec capabilities
#define SCM_MASK_H264 (SCM_H264 | SCM_H264_HIGH8_444)
#define SCM_MASK_HEVC (SCM_HEVC | SCM_HEVC_MAIN10 | SCM_HEVC_REXT8_444 | SCM_HEVC_REXT10_444)
#define SCM_MASK_AV1 (SCM_AV1_MAIN8 | SCM_AV1_MAIN10 | SCM_AV1_HIGH8_444 | SCM_AV1_HIGH10_444)
#define SCM_MASK_10BIT (SCM_HEVC_MAIN10 | SCM_HEVC_REXT10_444 | SCM_AV1_MAIN10 | SCM_AV1_HIGH10_444)
#define SCM_MASK_YUV444 (SCM_H264_HIGH8_444 | SCM_HEVC_REXT8_444 | SCM_HEVC_REXT10_444 | SCM_AV1_HIGH8_444 | SCM_AV1_HIGH10_444)
typedef struct _SERVER_INFORMATION {
// Server host name or IP address in text form
const char* address;
// Text inside 'appversion' tag in /serverinfo
const char* serverInfoAppVersion;
// Text inside 'GfeVersion' tag in /serverinfo (if present)
const char* serverInfoGfeVersion;
// Text inside 'sessionUrl0' tag in /resume and /launch (if present)
const char* rtspSessionUrl;
// Specifies the 'ServerCodecModeSupport' from the /serverinfo response.
int serverCodecModeSupport;
} SERVER_INFORMATION, *PSERVER_INFORMATION;
// Use this function to zero the server information when allocated on the stack or heap
@@ -563,12 +437,6 @@ void LiInterruptConnection(void);
// from the integer passed to the ConnListenerStageXXX callbacks
const char* LiGetStageName(int stage);
// This function returns an estimate of the current RTT to the host PC obtained via ENet
// protocol statistics. This function will fail if the current GFE version does not use
// ENet for the control stream (very old versions), or if the ENet peer is not connected.
// This function may only be called between LiStartConnection() and LiStopConnection().
bool LiGetEstimatedRttInfo(uint32_t* estimatedRtt, uint32_t* estimatedRttVariance);
// This function queues a relative mouse move event to be sent to the remote server.
int LiSendMouseMoveEvent(short deltaX, short deltaY);
@@ -577,10 +445,8 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY);
// may not position the mouse correctly.
//
// Absolute mouse motion doesn't work in many games, so this mode should not be the default
// for mice when streaming. It may be desirable as the default touchscreen behavior when
// LiSendTouchEvent() is not supported and the touchscreen is not the primary input method.
// In the latter case, a touchscreen-as-trackpad mode using LiSendMouseMoveEvent() is likely
// to be better for gaming use cases.
// for mice when streaming. It may be desirable as the default touchscreen behavior if the
// touchscreen is not the primary input method.
//
// The x and y values are transformed to host coordinates as if they are from a plane which
// is referenceWidth by referenceHeight in size. This allows you to provide coordinates that
@@ -590,97 +456,6 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY);
// referenceWidth and referenceHeight to your window width and height.
int LiSendMousePositionEvent(short x, short y, short referenceWidth, short referenceHeight);
// This function queues a mouse position update event to be sent to the remote server, so
// all of the limitations of LiSendMousePositionEvent() mentioned above apply here too!
//
// This function behaves like a combination of LiSendMouseMoveEvent() and LiSendMousePositionEvent()
// in that it sends a relative motion event, however it sends this data as an absolute position
// based on the computed position of a virtual client cursor which is "moved" any time that
// LiSendMousePositionEvent() or LiSendMouseMoveAsMousePositionEvent() is called. As a result
// of this internal virtual cursor state, callers must ensure LiSendMousePositionEvent() and
// LiSendMouseMoveAsMousePositionEvent() are not called concurrently!
//
// The big advantage of this function is that it allows callers to avoid mouse acceleration that
// would otherwise affect motion when using LiSendMouseMoveEvent(). The downside is that it has the
// same game compatibility issues as LiSendMousePositionEvent().
//
// This function can be useful when mouse capture is the only feasible way to receive mouse input,
// like on Android or iOS, and the OS cannot provide raw unaccelerated mouse motion when capturing.
// Using this function avoids double-acceleration in cases when the client motion is also accelerated.
int LiSendMouseMoveAsMousePositionEvent(short deltaX, short deltaY, short referenceWidth, short referenceHeight);
// Error return value to indicate that the requested functionality is not supported by the host
#define LI_ERR_UNSUPPORTED -5501
// This function allows multi-touch input to be sent directly to Sunshine hosts. The x and y values
// are normalized device coordinates stretching top-left corner (0.0, 0.0) to bottom-right corner
// (1.0, 1.0) of the video area.
//
// Pointer ID is an opaque ID that must uniquely identify each active touch on screen. It must
// remain constant through any down/up/move/cancel events involved in a single touch interaction.
//
// Rotation is in degrees from vertical in Y dimension (parallel to screen, 0..360). If rotation is
// unknown, pass LI_ROT_UNKNOWN.
//
// Pressure is a 0.0 to 1.0 range value from min to max pressure. Sending a down/move event with
// a pressure of 0.0 indicates the actual pressure is unknown.
//
// For hover events, the pressure value is treated as a 1.0 to 0.0 range of distance from the touch
// surface where 1.0 is the farthest measurable distance and 0.0 is actually touching the display
// (which is invalid for a hover event). Reporting distance 0.0 for a hover event indicates the
// actual distance is unknown.
//
// Contact area is modelled as an ellipse with major and minor axis values in normalized device
// coordinates. If contact area is unknown, report 0.0 for both contact area axis parameters.
// For circular contact areas or if a minor axis value is not available, pass the same value
// for major and minor axes. For APIs or devices, that don't report contact area as an ellipse,
// approximations can be used such as: https://docs.kernel.org/input/multi-touch-protocol.html#event-computation
//
// For hover events, the "contact area" is the size of the hovering finger/tool. If unavailable,
// pass 0.0 for both contact area parameters.
//
// Touches can be cancelled using LI_TOUCH_EVENT_CANCEL or LI_TOUCH_EVENT_CANCEL_ALL. When using
// LI_TOUCH_EVENT_CANCEL, only the pointerId parameter is valid. All other parameters are ignored.
// To cancel all active touches (on focus loss, for example), use LI_TOUCH_EVENT_CANCEL_ALL.
//
// If unsupported by the host, this will return LI_ERR_UNSUPPORTED and the caller should consider
// falling back to other functions to send this input (such as LiSendMousePositionEvent()).
//
// To determine if LiSendTouchEvent() is supported without calling it, call LiGetHostFeatureFlags()
// and check for the LI_FF_PEN_TOUCH_EVENTS flag.
#define LI_TOUCH_EVENT_HOVER 0x00
#define LI_TOUCH_EVENT_DOWN 0x01
#define LI_TOUCH_EVENT_UP 0x02
#define LI_TOUCH_EVENT_MOVE 0x03
#define LI_TOUCH_EVENT_CANCEL 0x04
#define LI_TOUCH_EVENT_BUTTON_ONLY 0x05
#define LI_TOUCH_EVENT_HOVER_LEAVE 0x06
#define LI_TOUCH_EVENT_CANCEL_ALL 0x07
#define LI_ROT_UNKNOWN 0xFFFF
int LiSendTouchEvent(uint8_t eventType, uint32_t pointerId, float x, float y, float pressureOrDistance,
float contactAreaMajor, float contactAreaMinor, uint16_t rotation);
// This function is similar to LiSendTouchEvent() but allows additional parameters relevant for pen
// input, including tilt and buttons. Tilt is in degrees from vertical in Z dimension (perpendicular
// to screen, 0..90). See LiSendTouchEvent() for detailed documentation on other parameters.
//
// x, y, pressure, rotation, contact area, and tilt are ignored for LI_TOUCH_EVENT_BUTTON_ONLY events.
// If one of those changes, send LI_TOUCH_EVENT_MOVE or LI_TOUCH_EVENT_HOVER instead.
//
// To determine if LiSendPenEvent() is supported without calling it, call LiGetHostFeatureFlags()
// and check for the LI_FF_PEN_TOUCH_EVENTS flag.
#define LI_TOOL_TYPE_UNKNOWN 0x00
#define LI_TOOL_TYPE_PEN 0x01
#define LI_TOOL_TYPE_ERASER 0x02
#define LI_PEN_BUTTON_PRIMARY 0x01
#define LI_PEN_BUTTON_SECONDARY 0x02
#define LI_PEN_BUTTON_TERTIARY 0x04
#define LI_TILT_UNKNOWN 0xFF
int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons,
float x, float y, float pressureOrDistance,
float contactAreaMajor, float contactAreaMinor,
uint16_t rotation, uint8_t tilt);
// This function queues a mouse button event to be sent to the remote server.
#define BUTTON_ACTION_PRESS 0x07
#define BUTTON_ACTION_RELEASE 0x08
@@ -692,8 +467,6 @@ int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons,
int LiSendMouseButtonEvent(char action, int button);
// This function queues a keyboard event to be sent to the remote server.
// Key codes are Win32 Virtual Key (VK) codes and interpreted as keys on
// a US English layout.
#define KEY_ACTION_DOWN 0x03
#define KEY_ACTION_UP 0x04
#define MODIFIER_SHIFT 0x01
@@ -702,15 +475,6 @@ int LiSendMouseButtonEvent(char action, int button);
#define MODIFIER_META 0x08
int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers);
// Similar to LiSendKeyboardEvent() but allows the client to inform the host that
// the keycode was not mapped to a standard US English scancode and should be
// interpreted as-is. This is a Sunshine protocol extension.
#define SS_KBE_FLAG_NON_NORMALIZED 0x01
int LiSendKeyboardEvent2(short keyCode, char keyAction, char modifiers, char flags);
// This function queues an UTF-8 encoded text to be sent to the remote server.
int LiSendUtf8TextEvent(const char *text, unsigned int length);
// Button flags
#define A_FLAG 0x1000
#define B_FLAG 0x2000
@@ -728,106 +492,20 @@ int LiSendUtf8TextEvent(const char *text, unsigned int length);
#define RS_CLK_FLAG 0x0080
#define SPECIAL_FLAG 0x0400
// Extended buttons (Sunshine only)
#define PADDLE1_FLAG 0x010000
#define PADDLE2_FLAG 0x020000
#define PADDLE3_FLAG 0x040000
#define PADDLE4_FLAG 0x080000
#define TOUCHPAD_FLAG 0x100000 // Touchpad buttons on Sony controllers
#define MISC_FLAG 0x200000 // Share/Mic/Capture/Mute buttons on various controllers
// This function queues a controller event to be sent to the remote server. It will
// be seen by the computer as the first controller.
int LiSendControllerEvent(int buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
int LiSendControllerEvent(short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY);
// This function queues a controller event to be sent to the remote server. The controllerNumber
// parameter is a zero-based index of which controller this event corresponds to. The largest legal
// controller number is 3 for GFE hosts and 15 for Sunshine hosts. On generation 3 servers (GFE 2.1.x),
// these will be sent as controller 0 regardless of the controllerNumber parameter.
//
// The activeGamepadMask parameter is a bitfield with bits set for each controller present.
// On GFE, activeGamepadMask is limited to a maximum of 4 bits (0xF).
// On Sunshine, it is limited to 16 bits (0xFFFF).
//
// To indicate arrival of a gamepad, you may send an empty event with the controller number
// set to the new controller and the bit of the new controller set in the active gamepad mask.
// However, you should prefer LiSendControllerArrivalEvent() instead of this function for
// that purpose, because it allows the host to make a better choice of emulated controller.
//
// To indicate removal of a gamepad, send an empty event with the controller number set to the
// removed controller and the bit of the removed controller cleared in the active gamepad mask.
// controller number is 3 (for a total of 4 controllers, the Xinput maximum). On generation 3 servers (GFE 2.1.x),
// these will be sent as controller 0 regardless of the controllerNumber parameter. The activeGamepadMask
// parameter is a bitfield with bits set for each controller present up to a maximum of 4 (0xF).
int LiSendMultiControllerEvent(short controllerNumber, short activeGamepadMask,
int buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY);
// This function provides a method of informing the host of the available buttons and capabilities
// on a new controller. This is the recommended approach for indicating the arrival of a new controller.
//
// This can allow the host to make better decisions about what type of controller to emulate and what
// capabilities to advertise to the OS on the virtual controller.
//
// If controller arrival events are unsupported by the host, this will fall back to indicating
// arrival via LiSendMultiControllerEvent().
#define LI_CTYPE_UNKNOWN 0x00
#define LI_CTYPE_XBOX 0x01
#define LI_CTYPE_PS 0x02
#define LI_CTYPE_NINTENDO 0x03
#define LI_CCAP_ANALOG_TRIGGERS 0x01 // Reports values between 0x00 and 0xFF for trigger axes
#define LI_CCAP_RUMBLE 0x02 // Can rumble in response to ConnListenerRumble() callback
#define LI_CCAP_TRIGGER_RUMBLE 0x04 // Can rumble triggers in response to ConnListenerRumbleTriggers() callback
#define LI_CCAP_TOUCHPAD 0x08 // Reports touchpad events via LiSendControllerTouchEvent()
#define LI_CCAP_ACCEL 0x10 // Can report accelerometer events via LiSendControllerMotionEvent()
#define LI_CCAP_GYRO 0x20 // Can report gyroscope events via LiSendControllerMotionEvent()
#define LI_CCAP_BATTERY_STATE 0x40 // Reports battery state via LiSendControllerBatteryEvent()
#define LI_CCAP_RGB_LED 0x80 // Can set RGB LED state via ConnListenerSetControllerLED()
#define LI_CCAP_DUAL_TOUCHPAD 0x100 // Reports touchpad events from 2 separate touchpads
int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepadMask, uint8_t type,
uint32_t supportedButtonFlags, uint16_t capabilities);
// This function is similar to LiSendTouchEvent(), but the touch events are associated with a
// touchpad device present on a game controller instead of a touchscreen.
//
// If unsupported by the host, this will return LI_ERR_UNSUPPORTED and the caller should consider
// using this touch input to simulate trackpad input.
//
// To determine if LiSendControllerTouchEvent() is supported without calling it, call LiGetHostFeatureFlags()
// and check for the LI_FF_CONTROLLER_TOUCH_EVENTS flag.
int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint32_t pointerId, float x, float y, float pressure);
// This function is similar to LiSendControllerTouchEvent(), but it allows the touchpad index to be
// provided for use with controllers that have multiple touchpads (like the Steam Controller).
//
// The only valid touchpad indices are currently 0 (support indicated by LI_CCAP_TOUCHPAD) and 1
// (support indicated by LI_CCAP_DUAL_TOUCHPAD).
int LiSendControllerTouchEvent2(uint8_t controllerNumber, uint8_t eventType, uint8_t touchpadIndex, uint32_t pointerId, float x, float y, float pressure);
// This function allows clients to send controller-associated motion events to a supported host.
//
// For power and performance reasons, motion sensors should not be enabled unless the host has
// explicitly asked for motion event reports via ConnListenerSetMotionEventState().
//
// LI_MOTION_TYPE_ACCEL should report data in m/s^2 (inclusive of gravitational acceleration).
// LI_MOTION_TYPE_GYRO should report data in deg/s.
//
// The x/y/z axis assignments follow SDL's convention documented here:
// https://github.com/libsdl-org/SDL/blob/96720f335002bef62115e39327940df454d78f6c/include/SDL3/SDL_sensor.h#L80-L124
#define LI_MOTION_TYPE_ACCEL 0x01
#define LI_MOTION_TYPE_GYRO 0x02
int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, float x, float y, float z);
// This function allows clients to send controller battery state to a supported host. If the
// host can adjust battery state on the emulated controller, it can use this information to
// make the virtual controller match the physical controller on the client.
#define LI_BATTERY_STATE_UNKNOWN 0x00
#define LI_BATTERY_STATE_NOT_PRESENT 0x01
#define LI_BATTERY_STATE_DISCHARGING 0x02
#define LI_BATTERY_STATE_CHARGING 0x03
#define LI_BATTERY_STATE_NOT_CHARGING 0x04 // Connected to power but not charging
#define LI_BATTERY_STATE_FULL 0x05
#define LI_BATTERY_PERCENTAGE_UNKNOWN 0xFF
int LiSendControllerBatteryEvent(uint8_t controllerNumber, uint8_t batteryState, uint8_t batteryPercentage);
// This function queues a vertical scroll event to the remote server.
// The number of "clicks" is multiplied by WHEEL_DELTA (120) before
// being sent to the PC.
@@ -839,18 +517,9 @@ int LiSendScrollEvent(signed char scrollClicks);
// scrolling (Apple Trackpads, Microsoft Precision Touchpads, etc.).
int LiSendHighResScrollEvent(short scrollAmount);
// These functions send horizontal scroll events to the host which are
// analogous to LiSendScrollEvent() and LiSendHighResScrollEvent().
// This is a Sunshine protocol extension.
int LiSendHScrollEvent(signed char scrollClicks);
int LiSendHighResHScrollEvent(short scrollAmount);
// This function returns a time in microseconds with an implementation-defined epoch.
// It should only ever be compared with the return value from a previous call to itself.
uint64_t LiGetMicroseconds(void);
// This function returns a time in milliseconds with an implementation-defined epoch.
// It should only ever be compared with the return value from a previous call to itself.
// NOTE: This will be populated from gettimeofday() if !HAVE_CLOCK_GETTIME and
// populated from clock_gettime(CLOCK_MONOTONIC) if HAVE_CLOCK_GETTIME.
uint64_t LiGetMillis(void);
// This is a simplistic STUN function that can assist clients in getting the WAN address
@@ -873,36 +542,6 @@ int LiGetPendingAudioFrames(void);
// negotiated audio frame duration.
int LiGetPendingAudioDuration(void);
// Returns a pointer to a struct containing various statistics about the RTP audio stream.
// The data should be considered read-only and must not be modified.
typedef struct _RTP_AUDIO_STATS {
uint32_t packetCountAudio; // total audio packets
uint32_t packetCountFec; // total packets of type FEC
uint32_t packetCountFecRecovered; // a packet was saved
uint32_t packetCountFecFailed; // tried to recover but too much was lost
uint32_t packetCountOOS; // out-of-sequence packets
uint32_t packetCountInvalid; // corrupted packets, etc
uint32_t packetCountFecInvalid; // invalid FEC packet
} RTP_AUDIO_STATS, *PRTP_AUDIO_STATS;
const RTP_AUDIO_STATS* LiGetRTPAudioStats(void);
// Returns a pointer to a struct containing various statistics about the RTP video stream.
// The data should be considered read-only and must not be modified.
// Right now this is mainly used to track total video and FEC packets, as there are
// many video stats already implemented at a higher level in moonlight-qt.
typedef struct _RTP_VIDEO_STATS {
uint32_t packetCountVideo; // total video packets
uint32_t packetCountFec; // total packets of type FEC
uint32_t packetCountFecRecovered; // a packet was saved
uint32_t packetCountFecFailed; // tried to recover but too much was lost
uint32_t packetCountOOS; // out-of-sequence packets
uint32_t packetCountInvalid; // corrupted packets, etc
uint32_t packetCountFecInvalid; // invalid FEC packet
} RTP_VIDEO_STATS, *PRTP_VIDEO_STATS;
const RTP_VIDEO_STATS* LiGetRTPVideoStats(void);
// Port index flags for use with LiGetPortFromPortFlagIndex() and LiGetProtocolFromPortFlagIndex()
#define ML_PORT_INDEX_TCP_47984 0
#define ML_PORT_INDEX_TCP_47989 1
@@ -930,17 +569,12 @@ const RTP_VIDEO_STATS* LiGetRTPVideoStats(void);
unsigned int LiGetPortFlagsFromStage(int stage);
unsigned int LiGetPortFlagsFromTerminationErrorCode(int errorCode);
// Returns the IPPROTO_* value for the specified port index
// Returns the IPPROTO_* value for the specified port index
int LiGetProtocolFromPortFlagIndex(int portFlagIndex);
// Returns the port number for the specified port index
unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex);
// Populates the output buffer with a stringified list of the port flags set in the input argument.
// The second and subsequent entries will be prepended by 'separator' (if provided).
// If the output buffer is too small, the output will be truncated to fit the provided buffer.
void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* outputBuffer, int outputBufferLength);
// This function may be used to test if the local network is blocking Moonlight's ports. It requires
// a test server running on an Internet-reachable host. To perform a test, pass in the DNS hostname
// of the test server, a reference TCP port to ensure the test host is reachable at all (something
@@ -955,63 +589,6 @@ void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* o
#define ML_TEST_RESULT_INCONCLUSIVE 0xFFFFFFFF
unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags);
// This family of functions can be used for pull-based video renderers that opt to manage a decoding/rendering
// thread themselves. After successfully calling the WaitFor/Poll variants that dequeue the video frame, you
// must call LiCompleteVideoFrame() to notify that processing is completed. The same DR_* status values
// from drSubmitDecodeUnit() must be passed to LiCompleteVideoFrame() as the drStatus argument.
//
// In order to safely use these functions, you must set CAPABILITY_PULL_RENDERER on the video decoder.
typedef void* VIDEO_FRAME_HANDLE;
bool LiWaitForNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit);
bool LiPollNextVideoFrame(VIDEO_FRAME_HANDLE* frameHandle, PDECODE_UNIT* decodeUnit);
bool LiPeekNextVideoFrame(PDECODE_UNIT* decodeUnit);
void LiWakeWaitForVideoFrame(void);
void LiCompleteVideoFrame(VIDEO_FRAME_HANDLE handle, int drStatus);
// This function returns the last reported HDR mode from the host PC.
// See ConnListenerSetHdrMode() for more details.
bool LiGetCurrentHostDisplayHdrMode(void);
typedef struct _SS_HDR_METADATA {
// RGB order
struct {
uint16_t x; // Normalized to 50,000
uint16_t y; // Normalized to 50,000
} displayPrimaries[3];
struct {
uint16_t x; // Normalized to 50,000
uint16_t y; // Normalized to 50,000
} whitePoint;
uint16_t maxDisplayLuminance; // Nits
uint16_t minDisplayLuminance; // 1/10000th of a nit
// These are content-specific values which may not be available for all hosts.
uint16_t maxContentLightLevel; // Nits
uint16_t maxFrameAverageLightLevel; // Nits
// These are display-specific values which may not be available for all hosts.
uint16_t maxFullFrameLuminance; // Nits
} SS_HDR_METADATA, *PSS_HDR_METADATA;
// This function populates the provided mastering metadata struct with the HDR metadata
// from the host PC's monitor and content (if available). It is only valid to call this
// function when HDR mode is active on the host. This is a Sunshine protocol extension.
bool LiGetHdrMetadata(PSS_HDR_METADATA metadata);
// This function requests an IDR frame from the host. Typically this is done using DR_NEED_IDR, but clients
// processing frames asynchronously may need to reset their decoder state even after returning DR_OK for
// the prior frame. Rather than wait for a new frame and return DR_NEED_IDR for that one, they can just
// call this API instead. Note that this function does not guarantee that the *next* frame will be an IDR
// frame, just that an IDR frame will arrive soon.
void LiRequestIdrFrame(void);
// This function returns any extended feature flags supported by the host.
#define LI_FF_PEN_TOUCH_EVENTS 0x01 // LiSendTouchEvent()/LiSendPenEvent() supported
#define LI_FF_CONTROLLER_TOUCH_EVENTS 0x02 // LiSendControllerTouchEvent() supported
uint32_t LiGetHostFeatureFlags(void);
#ifdef __cplusplus
}
#endif
+69 -106
View File
@@ -2,10 +2,10 @@
// Destroy the linked blocking queue and associated mutex and event
PLINKED_BLOCKING_QUEUE_ENTRY LbqDestroyLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead) {
LC_ASSERT(queueHead->shutdown || queueHead->draining || queueHead->lifetimeSize == 0);
LC_ASSERT(queueHead->shutdown || queueHead->lifetimeSize == 0);
PltDeleteMutex(&queueHead->mutex);
PltDeleteConditionVariable(&queueHead->cond);
PltCloseEvent(&queueHead->containsDataEvent);
return queueHead->head;
}
@@ -20,15 +20,10 @@ PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead
head = queueHead->head;
// Reinitialize the queue to empty
if (head != NULL) {
queueHead->head = NULL;
queueHead->tail = NULL;
queueHead->currentSize = 0;
}
else {
LC_ASSERT(queueHead->tail == NULL);
LC_ASSERT(queueHead->currentSize == 0);
}
queueHead->head = NULL;
queueHead->tail = NULL;
queueHead->currentSize = 0;
PltClearEvent(&queueHead->containsDataEvent);
PltUnlockMutex(&queueHead->mutex);
@@ -41,14 +36,13 @@ int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeB
memset(queueHead, 0, sizeof(*queueHead));
err = PltCreateMutex(&queueHead->mutex);
err = PltCreateEvent(&queueHead->containsDataEvent);
if (err != 0) {
return err;
}
err = PltCreateConditionVariable(&queueHead->cond, &queueHead->mutex);
err = PltCreateMutex(&queueHead->mutex);
if (err != 0) {
PltDeleteMutex(&queueHead->mutex);
return err;
}
@@ -58,24 +52,8 @@ int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeB
}
void LbqSignalQueueShutdown(PLINKED_BLOCKING_QUEUE queueHead) {
PltLockMutex(&queueHead->mutex);
queueHead->shutdown = true;
PltUnlockMutex(&queueHead->mutex);
PltSignalConditionVariable(&queueHead->cond);
}
void LbqSignalQueueDrain(PLINKED_BLOCKING_QUEUE queueHead) {
PltLockMutex(&queueHead->mutex);
queueHead->draining = true;
PltUnlockMutex(&queueHead->mutex);
PltSignalConditionVariable(&queueHead->cond);
}
void LbqSignalQueueUserWake(PLINKED_BLOCKING_QUEUE queueHead) {
PltLockMutex(&queueHead->mutex);
queueHead->pendingUserWake = true;
PltUnlockMutex(&queueHead->mutex);
PltSignalConditionVariable(&queueHead->cond);
PltSetEvent(&queueHead->containsDataEvent);
}
int LbqGetItemCount(PLINKED_BLOCKING_QUEUE queueHead) {
@@ -83,25 +61,21 @@ int LbqGetItemCount(PLINKED_BLOCKING_QUEUE queueHead) {
}
int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOCKING_QUEUE_ENTRY entry) {
bool wasEmpty;
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
entry->flink = NULL;
entry->data = data;
PltLockMutex(&queueHead->mutex);
if (queueHead->shutdown || queueHead->draining) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
if (queueHead->currentSize == queueHead->sizeBound) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_BOUND_EXCEEDED;
}
wasEmpty = queueHead->head == NULL;
if (wasEmpty) {
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
LC_ASSERT(queueHead->tail == NULL);
queueHead->head = entry;
@@ -121,33 +95,26 @@ int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOC
PltUnlockMutex(&queueHead->mutex);
if (wasEmpty) {
// Only call PltSignalConditionVariable() when transitioning from
// empty -> non-empty to avoid a useless syscall for each new entry.
PltSignalConditionVariable(&queueHead->cond);
}
PltSetEvent(&queueHead->containsDataEvent);
return LBQ_SUCCESS;
}
// This must be synchronized with LbqFlushQueueItems by the caller
int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
PltLockMutex(&queueHead->mutex);
if (queueHead->shutdown) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
if (queueHead->currentSize == 0) {
return LBQ_NO_ELEMENT;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
if (queueHead->draining) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
else {
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
*data = queueHead->head->data;
@@ -159,23 +126,20 @@ int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
PLINKED_BLOCKING_QUEUE_ENTRY entry;
PltLockMutex(&queueHead->mutex);
if (queueHead->shutdown) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
if (queueHead->currentSize == 0) {
return LBQ_NO_ELEMENT;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
if (queueHead->draining) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
else {
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
PltUnlockMutex(&queueHead->mutex);
return LBQ_NO_ELEMENT;
}
entry = queueHead->head;
@@ -184,6 +148,7 @@ int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
queueHead->tail = NULL;
PltClearEvent(&queueHead->containsDataEvent);
}
else {
LC_ASSERT(queueHead->currentSize != 0);
@@ -199,51 +164,49 @@ int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
int LbqWaitForQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
PLINKED_BLOCKING_QUEUE_ENTRY entry;
PltLockMutex(&queueHead->mutex);
// Wait for a waking condition: either data available or rundown
while (queueHead->head == NULL && !queueHead->draining && !queueHead->shutdown && !queueHead->pendingUserWake) {
PltWaitForConditionVariable(&queueHead->cond, &queueHead->mutex);
}
// If we're shutting down, abort immediately, even if there's data available
int err;
if (queueHead->shutdown) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
// If this is a user requested wake, process it now
if (queueHead->pendingUserWake) {
queueHead->pendingUserWake = false;
for (;;) {
err = PltWaitForEvent(&queueHead->containsDataEvent);
if (err != PLT_WAIT_SUCCESS) {
return LBQ_INTERRUPTED;
}
if (queueHead->shutdown) {
return LBQ_INTERRUPTED;
}
PltLockMutex(&queueHead->mutex);
if (queueHead->head == NULL) {
PltClearEvent(&queueHead->containsDataEvent);
PltUnlockMutex(&queueHead->mutex);
continue;
}
entry = queueHead->head;
queueHead->head = entry->flink;
queueHead->currentSize--;
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
queueHead->tail = NULL;
PltClearEvent(&queueHead->containsDataEvent);
}
else {
LC_ASSERT(queueHead->currentSize != 0);
queueHead->head->blink = NULL;
}
*data = entry->data;
PltUnlockMutex(&queueHead->mutex);
return LBQ_USER_WAKE;
break;
}
// If we're draining, only abort if we have no data available
if (queueHead->draining && queueHead->head == NULL) {
PltUnlockMutex(&queueHead->mutex);
return LBQ_INTERRUPTED;
}
// We should have bailed by this point if there was no data
LC_ASSERT(queueHead->head != NULL);
entry = queueHead->head;
queueHead->head = entry->flink;
queueHead->currentSize--;
if (queueHead->head == NULL) {
LC_ASSERT(queueHead->currentSize == 0);
queueHead->tail = NULL;
}
else {
LC_ASSERT(queueHead->currentSize != 0);
queueHead->head->blink = NULL;
}
*data = entry->data;
PltUnlockMutex(&queueHead->mutex);
return LBQ_SUCCESS;
}
+5 -10
View File
@@ -7,7 +7,6 @@
#define LBQ_INTERRUPTED 1
#define LBQ_BOUND_EXCEEDED 2
#define LBQ_NO_ELEMENT 3
#define LBQ_USER_WAKE 4
typedef struct _LINKED_BLOCKING_QUEUE_ENTRY {
struct _LINKED_BLOCKING_QUEUE_ENTRY* flink;
@@ -17,15 +16,13 @@ typedef struct _LINKED_BLOCKING_QUEUE_ENTRY {
typedef struct _LINKED_BLOCKING_QUEUE {
PLT_MUTEX mutex;
PLT_COND cond;
PLT_EVENT containsDataEvent;
int sizeBound;
atomic_int currentSize;
atomic_bool shutdown;
int lifetimeSize;
PLINKED_BLOCKING_QUEUE_ENTRY head;
PLINKED_BLOCKING_QUEUE_ENTRY tail;
int sizeBound;
int currentSize;
int lifetimeSize;
bool shutdown;
bool draining;
bool pendingUserWake;
} LINKED_BLOCKING_QUEUE, *PLINKED_BLOCKING_QUEUE;
int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeBound);
@@ -36,6 +33,4 @@ int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data);
PLINKED_BLOCKING_QUEUE_ENTRY LbqDestroyLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead);
PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead);
void LbqSignalQueueShutdown(PLINKED_BLOCKING_QUEUE queueHead);
void LbqSignalQueueDrain(PLINKED_BLOCKING_QUEUE queueHead);
void LbqSignalQueueUserWake(PLINKED_BLOCKING_QUEUE queueHead);
int LbqGetItemCount(PLINKED_BLOCKING_QUEUE queueHead);
+30 -85
View File
@@ -6,21 +6,16 @@
// multiple times for retransmissions to work correctly. It is meant to be a drop-in
// replacement for enet_host_service(). It also handles cancellation of the connection
// attempt during the wait.
static int serviceEnetHostInternal(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs, bool ignoreInterrupts) {
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
int ret;
// Clear the last socket error to ensure the caller doesn't read a stale error upon a
// failure in non-socket-related processing in enet_host_service()
SetLastSocketError(0);
// We need to call enet_host_service() multiple times to make sure retransmissions happen
for (;;) {
int selectedTimeout = timeoutMs < ENET_INTERNAL_TIMEOUT_MS ? timeoutMs : ENET_INTERNAL_TIMEOUT_MS;
// We want to report an interrupt event if we are able to read data
if (!ignoreInterrupts && ConnectionInterrupted) {
if (ConnectionInterrupted) {
Limelog("ENet wait interrupted\n");
SetLastSocketError(EINTR);
ret = -1;
break;
}
@@ -32,74 +27,39 @@ static int serviceEnetHostInternal(ENetHost* client, ENetEvent* event, enet_uint
timeoutMs -= selectedTimeout;
}
return ret;
}
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
return serviceEnetHostInternal(client, event, timeoutMs, false);
}
// This function performs a graceful disconnect, including lingering until outbound
// traffic is acked (up until the linger timeout elapses).
int gracefullyDisconnectEnetPeer(ENetHost* host, ENetPeer* peer, enet_uint32 lingerTimeoutMs) {
// Check if this peer is currently alive. We won't get another ENET_EVENT_TYPE_DISCONNECT
// event from ENet if the peer is dead. In that case, we'll do an abortive disconnect.
if (peer->state == ENET_PEER_STATE_CONNECTED) {
ENetEvent event;
int err;
// Begin the disconnection process. We'll get ENET_EVENT_TYPE_DISCONNECT once
// the peer acks all outstanding reliable sends.
enet_peer_disconnect_later(peer, 0);
// We must use the internal function which lets us ignore pending interrupts.
while ((err = serviceEnetHostInternal(host, &event, lingerTimeoutMs, true)) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
Limelog("ENet peer acknowledged disconnection\n");
return 0;
default:
LC_ASSERT(false);
break;
}
}
if (err == 0) {
Limelog("Timed out waiting for ENet peer to acknowledge disconnection\n");
int extractVersionQuadFromString(const char* string, int* quad) {
char versionString[128];
char* nextDot;
char* nextNumber;
int i;
strcpy(versionString, string);
nextNumber = versionString;
for (i = 0; i < 4; i++) {
if (i == 3) {
nextDot = strchr(nextNumber, '\0');
}
else {
Limelog("Failed to receive ENet peer disconnection acknowledgement: %d\n", LastSocketFail());
nextDot = strchr(nextNumber, '.');
}
return -1;
}
else {
Limelog("ENet peer is already disconnected\n");
enet_peer_disconnect_now(peer, 0);
return 0;
}
}
int extractVersionQuadFromString(const char* string, int* quad) {
const char* nextNumber = string;
for (int i = 0; i < 4; i++) {
// Parse the next component
quad[i] = (int)strtol(nextNumber, (char**)&nextNumber, 10);
// Skip the dot if we still have version components left.
//
// We continue looping even when we're at the end of the
// input string to ensure all subsequent version components
// are zeroed.
if (*nextNumber != 0) {
nextNumber++;
if (nextDot == NULL) {
return -1;
}
// Cut the string off at the next dot
*nextDot = '\0';
quad[i] = atoi(nextNumber);
// Move on to the next segment
nextNumber = nextDot + 1;
}
return 0;
}
@@ -111,17 +71,10 @@ void* extendBuffer(void* ptr, size_t newSize) {
return newBuf;
}
bool isReferenceFrameInvalidationSupportedByDecoder(void) {
LC_ASSERT(NegotiatedVideoFormat != 0);
return ((NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H264) && (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC)) ||
((NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) && (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC)) ||
((NegotiatedVideoFormat & VIDEO_FORMAT_MASK_AV1) && (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AV1));
}
bool isReferenceFrameInvalidationEnabled(void) {
// RFI must be supported by the server and the client decoder to be used
return ReferenceFrameInvalidationSupported && isReferenceFrameInvalidationSupportedByDecoder();
LC_ASSERT(NegotiatedVideoFormat != 0);
return ((NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H264) && (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC)) ||
((NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) && (VideoCallbacks.capabilities & CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC));
}
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig) {
@@ -147,11 +100,3 @@ void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo) {
uint64_t LiGetMillis(void) {
return PltGetMillis();
}
uint64_t LiGetMicroseconds(void) {
return PltGetMicroseconds();
}
uint32_t LiGetHostFeatureFlags(void) {
return SunshineFeatureFlags;
}
+132 -367
View File
@@ -1,9 +1,10 @@
#define _GNU_SOURCE
#include "Limelight-internal.h"
#if defined(__vita__)
#include <pthread.h>
#include <psp2/kernel/processmgr.h>
#endif
#include "Platform.h"
#include "PlatformThreads.h"
#include "PlatformSockets.h"
#include <enet/enet.h>
// The maximum amount of time before observing an interrupt
// in PltSleepMsInterruptible().
@@ -13,12 +14,14 @@ struct thread_context {
ThreadEntry entry;
void* context;
const char* name;
#if defined(__vita__)
PLT_THREAD* thread;
#endif
};
static int activeThreads = 0;
static int activeMutexes = 0;
static int activeEvents = 0;
static int activeCondVars = 0;
#if defined(LC_WINDOWS)
@@ -35,10 +38,12 @@ typedef struct tagTHREADNAME_INFO
typedef HRESULT (WINAPI *SetThreadDescription_t)(HANDLE, PCWSTR);
void setThreadNameWin32(const char* name) {
HMODULE hKernel32;
SetThreadDescription_t setThreadDescriptionFunc;
// This function is only supported on Windows 10 RS1 and later
setThreadDescriptionFunc = (SetThreadDescription_t)GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetThreadDescription");
hKernel32 = LoadLibraryA("kernel32.dll");
setThreadDescriptionFunc = (SetThreadDescription_t)GetProcAddress(hKernel32, "SetThreadDescription");
if (setThreadDescriptionFunc != NULL) {
WCHAR nameW[16];
size_t chars;
@@ -46,6 +51,7 @@ void setThreadNameWin32(const char* name) {
mbstowcs_s(&chars, nameW, ARRAYSIZE(nameW), name, _TRUNCATE);
setThreadDescriptionFunc(GetCurrentThread(), nameW);
}
FreeLibrary(hKernel32);
#ifdef _MSC_VER
// This method works on legacy OSes and older tools not updated to use SetThreadDescription yet,
@@ -68,9 +74,9 @@ void setThreadNameWin32(const char* name) {
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
struct thread_context* ctx = (struct thread_context*)lpParameter;
#elif defined(__WIIU__)
int ThreadProc(int argc, const char** argv) {
struct thread_context* ctx = (struct thread_context*)argv;
#elif defined(__vita__)
int ThreadProc(SceSize args, void *argp) {
struct thread_context* ctx = (struct thread_context*)argp;
#else
void* ThreadProc(void* context) {
struct thread_context* ctx = (struct thread_context*)context;
@@ -78,17 +84,19 @@ void* ThreadProc(void* context) {
#if defined(LC_WINDOWS)
setThreadNameWin32(ctx->name);
#elif defined(__linux__) || defined(__FreeBSD__)
#elif defined(__linux__)
pthread_setname_np(pthread_self(), ctx->name);
#elif defined(LC_DARWIN)
pthread_setname_np(ctx->name);
#endif
ctx->entry(ctx->context);
#if defined(__vita__)
ctx->thread->alive = false;
#else
free(ctx);
#endif
#if defined(LC_WINDOWS) || defined(__vita__) || defined(__WIIU__) || defined(__3DS__)
#if defined(LC_WINDOWS) || defined(__vita__)
return 0;
#else
return NULL;
@@ -97,10 +105,9 @@ void* ThreadProc(void* context) {
void PltSleepMs(int ms) {
#if defined(LC_WINDOWS)
SleepEx(ms, FALSE);
#elif defined(__3DS__)
s64 nsecs = ms * 1000000;
svcSleepThread(nsecs);
WaitForSingleObjectEx(GetCurrentThread(), ms, FALSE);
#elif defined(__vita__)
sceKernelDelayThread(ms * 1000);
#else
useconds_t usecs = ms * 1000;
usleep(usecs);
@@ -117,11 +124,15 @@ void PltSleepMsInterruptible(PLT_THREAD* thread, int ms) {
int PltCreateMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
InitializeSRWLock(mutex);
#elif defined(__WIIU__)
OSFastMutex_Init(mutex, "");
#elif defined(__3DS__)
LightLock_Init(mutex);
*mutex = CreateMutexEx(NULL, NULL, 0, MUTEX_ALL_ACCESS);
if (!*mutex) {
return -1;
}
#elif defined(__vita__)
*mutex = sceKernelCreateMutex("", 0, 0, NULL);
if (*mutex < 0) {
return -1;
}
#else
int err = pthread_mutex_init(mutex, NULL);
if (err != 0) {
@@ -133,12 +144,11 @@ int PltCreateMutex(PLT_MUTEX* mutex) {
}
void PltDeleteMutex(PLT_MUTEX* mutex) {
LC_ASSERT(activeMutexes > 0);
activeMutexes--;
#if defined(LC_WINDOWS)
// No-op to destroy a SRWLOCK
#elif defined(__WIIU__) || defined(__3DS__)
CloseHandle(*mutex);
#elif defined(__vita__)
sceKernelDeleteMutex(*mutex);
#else
pthread_mutex_destroy(mutex);
#endif
@@ -146,11 +156,13 @@ void PltDeleteMutex(PLT_MUTEX* mutex) {
void PltLockMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
AcquireSRWLockExclusive(mutex);
#elif defined(__WIIU__)
OSFastMutex_Lock(mutex);
#elif defined(__3DS__)
LightLock_Lock(mutex);
int err;
err = WaitForSingleObjectEx(*mutex, INFINITE, FALSE);
if (err != WAIT_OBJECT_0) {
LC_ASSERT(false);
}
#elif defined(__vita__)
sceKernelLockMutex(*mutex, 1, NULL);
#else
pthread_mutex_lock(mutex);
#endif
@@ -158,47 +170,35 @@ void PltLockMutex(PLT_MUTEX* mutex) {
void PltUnlockMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
ReleaseSRWLockExclusive(mutex);
#elif defined(__WIIU__)
OSFastMutex_Unlock(mutex);
#elif defined(__3DS__)
LightLock_Unlock(mutex);
ReleaseMutex(*mutex);
#elif defined(__vita__)
sceKernelUnlockMutex(*mutex, 1);
#else
pthread_mutex_unlock(mutex);
#endif
}
void PltJoinThread(PLT_THREAD* thread) {
LC_ASSERT(activeThreads > 0);
activeThreads--;
LC_ASSERT(thread->cancelled);
#if defined(LC_WINDOWS)
WaitForSingleObjectEx(thread->handle, INFINITE, FALSE);
CloseHandle(thread->handle);
#elif defined(__WIIU__)
OSJoinThread(&thread->thread, NULL);
#elif defined(__3DS__)
threadJoin(thread->thread, U64_MAX);
threadFree(thread->thread);
#elif defined(__vita__)
while(thread->alive) {
PltSleepMs(10);
}
if (thread->context != NULL)
free(thread->context);
#else
pthread_join(thread->thread, NULL);
#endif
}
void PltDetachThread(PLT_THREAD* thread) {
LC_ASSERT(activeThreads > 0);
void PltCloseThread(PLT_THREAD* thread) {
activeThreads--;
#if defined(LC_WINDOWS)
// According MSDN:
// "Closing a thread handle does not terminate the associated thread or remove the thread object."
CloseHandle(thread->handle);
#elif defined(__WIIU__)
OSDetachThread(&thread->thread);
#elif defined(__3DS__)
threadDetach(thread->thread);
#else
pthread_detach(thread->thread);
#elif defined(__vita__)
sceKernelDeleteThread(thread->handle);
#endif
}
@@ -210,12 +210,6 @@ void PltInterruptThread(PLT_THREAD* thread) {
thread->cancelled = true;
}
#ifdef __WIIU__
static void thread_deallocator(OSThread *thread, void *stack) {
free(stack);
}
#endif
int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THREAD* thread) {
struct thread_context* ctx;
@@ -227,7 +221,7 @@ int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THRE
ctx->entry = entry;
ctx->context = context;
ctx->name = name;
thread->cancelled = false;
#if defined(LC_WINDOWS)
@@ -238,67 +232,25 @@ int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THRE
return -1;
}
}
#elif defined(__WIIU__)
memset(&thread->thread, 0, sizeof(thread->thread));
// Allocate stack
const int stack_size = 4 * 1024 * 1024;
uint8_t* stack = (uint8_t*)memalign(16, stack_size);
if (stack == NULL) {
free(ctx);
return -1;
}
// Create thread
if (!OSCreateThread(&thread->thread,
ThreadProc,
0, (char*)ctx,
stack + stack_size, stack_size,
0x10, OS_THREAD_ATTRIB_AFFINITY_ANY))
#elif defined(__vita__)
{
free(ctx);
free(stack);
return -1;
}
OSSetThreadName(&thread->thread, name);
OSSetThreadDeallocator(&thread->thread, thread_deallocator);
OSResumeThread(&thread->thread);
#elif defined(__3DS__)
{
size_t stack_size = 0x40000;
s32 priority = 0x30;
svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);
thread->thread = threadCreate(ThreadProc,
ctx,
stack_size,
priority,
-1,
false);
if (thread->thread == NULL) {
thread->alive = true;
thread->context = ctx;
ctx->thread = thread;
thread->handle = sceKernelCreateThread(name, ThreadProc, 0, 0x40000, 0, 0, NULL);
if (thread->handle < 0) {
free(ctx);
return -1;
}
sceKernelStartThread(thread->handle, sizeof(struct thread_context), ctx);
}
#else
{
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef __vita__
pthread_attr_setstacksize(&attr, 0x100000);
#endif
ctx->name = name;
int err = pthread_create(&thread->thread, &attr, ThreadProc, ctx);
pthread_attr_destroy(&attr);
int err = pthread_create(&thread->thread, NULL, ThreadProc, ctx);
if (err != 0) {
free(ctx);
return err;
}
}
#endif
@@ -313,14 +265,20 @@ int PltCreateEvent(PLT_EVENT* event) {
if (!*event) {
return -1;
}
#elif defined(__vita__)
event->mutex = sceKernelCreateMutex("", 0, 0, NULL);
if (event->mutex < 0) {
return -1;
}
event->cond = sceKernelCreateCond("", 0, event->mutex, NULL);
if (event->cond < 0) {
sceKernelDeleteMutex(event->mutex);
return -1;
}
event->signalled = false;
#else
if (PltCreateMutex(&event->mutex) < 0) {
return -1;
}
if (PltCreateConditionVariable(&event->cond, &event->mutex) < 0) {
PltDeleteMutex(&event->mutex);
return -1;
}
pthread_mutex_init(&event->mutex, NULL);
pthread_cond_init(&event->cond, NULL);
event->signalled = false;
#endif
activeEvents++;
@@ -328,24 +286,31 @@ int PltCreateEvent(PLT_EVENT* event) {
}
void PltCloseEvent(PLT_EVENT* event) {
LC_ASSERT(activeEvents > 0);
activeEvents--;
#if defined(LC_WINDOWS)
CloseHandle(*event);
#elif defined(__vita__)
sceKernelDeleteCond(event->cond);
sceKernelDeleteMutex(event->mutex);
#else
PltDeleteConditionVariable(&event->cond);
PltDeleteMutex(&event->mutex);
pthread_mutex_destroy(&event->mutex);
pthread_cond_destroy(&event->cond);
#endif
}
void PltSetEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
SetEvent(*event);
#else
PltLockMutex(&event->mutex);
#elif defined(__vita__)
sceKernelLockMutex(event->mutex, 1, NULL);
event->signalled = true;
PltUnlockMutex(&event->mutex);
PltSignalConditionVariable(&event->cond);
sceKernelUnlockMutex(event->mutex, 1);
sceKernelSignalCondAll(event->cond);
#else
pthread_mutex_lock(&event->mutex);
event->signalled = true;
pthread_mutex_unlock(&event->mutex);
pthread_cond_broadcast(&event->cond);
#endif
}
@@ -357,262 +322,63 @@ void PltClearEvent(PLT_EVENT* event) {
#endif
}
void PltWaitForEvent(PLT_EVENT* event) {
int PltWaitForEvent(PLT_EVENT* event) {
#if defined(LC_WINDOWS)
WaitForSingleObjectEx(*event, INFINITE, FALSE);
#else
PltLockMutex(&event->mutex);
while (!event->signalled) {
PltWaitForConditionVariable(&event->cond, &event->mutex);
DWORD error;
error = WaitForSingleObjectEx(*event, INFINITE, FALSE);
if (error == WAIT_OBJECT_0) {
return PLT_WAIT_SUCCESS;
}
PltUnlockMutex(&event->mutex);
#endif
}
int PltCreateConditionVariable(PLT_COND* cond, PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
InitializeConditionVariable(cond);
#elif defined(__WIIU__)
OSFastCond_Init(cond, "");
#elif defined(__3DS__)
CondVar_Init(cond);
#else
pthread_cond_init(cond, NULL);
#endif
activeCondVars++;
return 0;
}
void PltDeleteConditionVariable(PLT_COND* cond) {
LC_ASSERT(activeCondVars > 0);
activeCondVars--;
#if defined(LC_WINDOWS)
// No-op to delete a CONDITION_VARIABLE
#elif defined(__WIIU__)
// No-op to delete an OSFastCondition
#elif defined(__3DS__)
// No-op to delete CondVar
#else
pthread_cond_destroy(cond);
#endif
}
void PltSignalConditionVariable(PLT_COND* cond) {
#if defined(LC_WINDOWS)
WakeConditionVariable(cond);
#elif defined(__WIIU__)
OSFastCond_Signal(cond);
#elif defined(__3DS__)
CondVar_Signal(cond);
#else
pthread_cond_signal(cond);
#endif
}
void PltWaitForConditionVariable(PLT_COND* cond, PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
SleepConditionVariableSRW(cond, mutex, INFINITE, 0);
#elif defined(__WIIU__)
OSFastCond_Wait(cond, mutex);
#elif defined(__3DS__)
CondVar_Wait(cond, mutex);
#else
pthread_cond_wait(cond, mutex);
#endif
}
//// Begin timing functions
// These functions return a number of microseconds or milliseconds since an opaque start time.
static bool ticks_started = false;
#if defined(LC_WINDOWS)
static LARGE_INTEGER start_ticks;
static LARGE_INTEGER ticks_per_second;
void PltTicksInit(void) {
if (ticks_started) {
return;
}
ticks_started = true;
QueryPerformanceFrequency(&ticks_per_second);
QueryPerformanceCounter(&start_ticks);
}
uint64_t PltGetMicroseconds(void) {
if (!ticks_started) {
PltTicksInit();
}
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (uint64_t)(((now.QuadPart - start_ticks.QuadPart) * 1000000) / ticks_per_second.QuadPart);
}
#elif defined(LC_DARWIN)
static uint64_t start_ns;
void PltTicksInit(void) {
if (ticks_started) {
return;
}
ticks_started = true;
start_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
}
uint64_t PltGetMicroseconds(void) {
if (!ticks_started) {
PltTicksInit();
}
const uint64_t now_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
return (now_ns - start_ns) / 1000;
}
#elif defined(__vita__)
static uint64_t start;
void PltTicksInit(void) {
if (ticks_started) {
return;
}
ticks_started = true;
start = sceKernelGetProcessTimeWide();
}
uint64_t PltGetMicroseconds(void) {
if (!ticks_started) {
PltTicksInit();
}
uint64_t now = sceKernelGetProcessTimeWide();
return (uint64_t)(now - start);
}
#elif defined(__3DS__)
static uint64_t start;
void PltTicksInit(void) {
if (ticks_started) {
return;
}
ticks_started = true;
start = svcGetSystemTick();
}
uint64_t PltGetMicroseconds(void) {
if (!ticks_started) {
PltTicksInit();
}
uint64_t elapsed = svcGetSystemTick() - start;
return elapsed * 1000 / CPU_TICKS_PER_MSEC;
}
#else
/* Use CLOCK_MONOTONIC_RAW, if available, which is not subject to adjustment by NTP */
#ifdef HAVE_CLOCK_GETTIME
static struct timespec start_ts;
# ifdef CLOCK_MONOTONIC_RAW
# define PLT_MONOTONIC_CLOCK CLOCK_MONOTONIC_RAW
# else
# define PLT_MONOTONIC_CLOCK CLOCK_MONOTONIC
# endif
#endif
static bool has_monotonic_time = false;
static struct timeval start_tv;
void PltTicksInit(void) {
if (ticks_started) {
return;
}
ticks_started = true;
#ifdef HAVE_CLOCK_GETTIME
if (clock_gettime(PLT_MONOTONIC_CLOCK, &start_ts) == 0) {
has_monotonic_time = true;
} else
#endif
{
gettimeofday(&start_tv, NULL);
}
}
uint64_t PltGetMicroseconds(void) {
if (!ticks_started) {
PltTicksInit();
}
if (has_monotonic_time) {
#ifdef HAVE_CLOCK_GETTIME
struct timespec now;
clock_gettime(PLT_MONOTONIC_CLOCK, &now);
return (uint64_t)(((int64_t)(now.tv_sec - start_ts.tv_sec) * 1000000) + ((now.tv_nsec - start_ts.tv_nsec) / 1000));
#else
else {
LC_ASSERT(false);
return 0;
#endif
} else {
struct timeval now;
gettimeofday(&now, NULL);
return (uint64_t)(((int64_t)(now.tv_sec - start_tv.tv_sec) * 1000000) + (now.tv_usec - start_tv.tv_usec));
return -1;
}
}
#elif defined(__vita__)
sceKernelLockMutex(event->mutex, 1, NULL);
while (!event->signalled) {
sceKernelWaitCond(event->cond, NULL);
}
sceKernelUnlockMutex(event->mutex, 1);
return PLT_WAIT_SUCCESS;
#else
pthread_mutex_lock(&event->mutex);
while (!event->signalled) {
pthread_cond_wait(&event->cond, &event->mutex);
}
pthread_mutex_unlock(&event->mutex);
return PLT_WAIT_SUCCESS;
#endif
}
uint64_t PltGetMillis(void) {
return PltGetMicroseconds() / 1000;
}
//// End timing functions
bool PltSafeStrcpy(char* dest, size_t dest_size, const char* src) {
LC_ASSERT(dest_size > 0);
#ifdef LC_DEBUG
// In debug builds, do the same little trick that MSVC
// does to ensure the entire buffer is writable.
memset(dest, 0xFE, dest_size);
#endif
#ifdef _MSC_VER
// strncpy_s() with _TRUNCATE does what we need for MSVC.
// We use this rather than strcpy_s() because we don't want
// the invalid parameter handler invoked upon failure.
if (strncpy_s(dest, dest_size, src, _TRUNCATE) != 0) {
LC_ASSERT(false);
dest[0] = 0;
return false;
}
#if defined(LC_WINDOWS)
return GetTickCount64();
#elif HAVE_CLOCK_GETTIME
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
return (tv.tv_sec * 1000) + (tv.tv_nsec / 1000000);
#else
// Check length of the source and destination strings before
// the strcpy() call. Set destination to an empty string if
// the source string doesn't fit in the destination.
if (strlen(src) >= dest_size) {
LC_ASSERT(false);
dest[0] = 0;
return false;
}
struct timeval tv;
strcpy(dest, src);
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
#endif
return true;
}
int initializePlatform(void) {
int err;
PltTicksInit();
err = initializePlatformSockets();
if (err != 0) {
return err;
}
err = enet_initialize();
if (err != 0) {
return err;
@@ -620,18 +386,17 @@ int initializePlatform(void) {
enterLowLatencyMode();
return 0;
return 0;
}
void cleanupPlatform(void) {
exitLowLatencyMode();
cleanupPlatformSockets();
enet_deinitialize();
LC_ASSERT(activeThreads == 0);
LC_ASSERT(activeMutexes == 0);
LC_ASSERT(activeEvents == 0);
LC_ASSERT(activeCondVars == 0);
}
+3 -100
View File
@@ -1,49 +1,20 @@
#pragma once
#ifdef _WIN32
// Prevent bogus definitions of error codes
// that are incompatible with Winsock errors.
#define _CRT_NO_POSIX_ERROR_CODES
// Ignore CRT warnings about POSIX names
#define _CRT_NONSTDC_NO_DEPRECATE 1
#endif
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdatomic.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <Windows.h>
#include <Winsock2.h>
#include <ws2tcpip.h>
#elif defined(__APPLE__)
#include <mach/mach_time.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#elif defined(__vita__)
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <psp2/kernel/threadmgr.h>
#elif defined(__WIIU__)
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <malloc.h>
#include <coreinit/thread.h>
#include <coreinit/fastmutex.h>
#include <coreinit/fastcondition.h>
#include <fcntl.h>
#elif defined(__3DS__)
#include <3ds.h>
#include <fcntl.h>
#else
#include <unistd.h>
#include <pthread.h>
@@ -51,7 +22,6 @@
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#endif
#ifdef _WIN32
@@ -63,19 +33,6 @@
# endif
#endif
#ifdef LC_WINDOWS
// Windows doesn't have strtok_r() but it has the same
// function named strtok_s().
#define strtok_r strtok_s
# if defined(WINAPI_FAMILY) && WINAPI_FAMILY==WINAPI_FAMILY_APP
# define LC_UWP
# else
# define LC_WINDOWS_DESKTOP
#endif
#endif
#include <stdio.h>
#include "Limelight.h"
@@ -105,61 +62,7 @@
#define LC_ASSERT(x) assert(x)
#endif
// If we're fuzzing, we don't want to enable asserts that can be affected by
// bad input from the remote host. LC_ASSERT_VT() is used for assertions that
// check data that comes from the host. These checks are enabled for normal
// debug builds, since they indicate an error in Moonlight or on the host.
// These are disabled when fuzzing, since the traffic is intentionally invalid.
#ifdef LC_FUZZING
#define LC_ASSERT_VT(x)
#else
#define LC_ASSERT_VT(x) LC_ASSERT(x)
#endif
#ifdef _MSC_VER
#pragma intrinsic(_byteswap_ushort)
#define BSWAP16(x) _byteswap_ushort(x)
#pragma intrinsic(_byteswap_ulong)
#define BSWAP32(x) _byteswap_ulong(x)
#pragma intrinsic(_byteswap_uint64)
#define BSWAP64(x) _byteswap_uint64(x)
#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
#define BSWAP16(x) __builtin_bswap16(x)
#define BSWAP32(x) __builtin_bswap32(x)
#define BSWAP64(x) __builtin_bswap64(x)
#elif defined(__has_builtin) && __has_builtin(__builtin_bswap16)
#define BSWAP16(x) __builtin_bswap16(x)
#define BSWAP32(x) __builtin_bswap32(x)
#define BSWAP64(x) __builtin_bswap64(x)
#else
#error Please define your platform byteswap macros!
#endif
#if (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || defined(__BIG_ENDIAN__)
#define LE16(x) BSWAP16(x)
#define LE32(x) BSWAP32(x)
#define LE64(x) BSWAP64(x)
#define BE16(x) (x)
#define BE32(x) (x)
#define BE64(x) (x)
#define IS_LITTLE_ENDIAN() (false)
#else
#define LE16(x) (x)
#define LE32(x) (x)
#define LE64(x) (x)
#define BE16(x) BSWAP16(x)
#define BE32(x) BSWAP32(x)
#define BE64(x) BSWAP64(x)
#define IS_LITTLE_ENDIAN() (true)
#endif
int initializePlatform(void);
void cleanupPlatform(void);
bool PltSafeStrcpy(char* dest, size_t dest_size, const char* src);
void PltTicksInit(void);
uint64_t PltGetMicroseconds(void);
uint64_t PltGetMillis(void);
-470
View File
@@ -1,470 +0,0 @@
#include "Limelight-internal.h"
#ifdef USE_MBEDTLS
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/version.h>
mbedtls_entropy_context EntropyContext;
mbedtls_ctr_drbg_context CtrDrbgContext;
bool RandomStateInitialized = false;
#if MBEDTLS_VERSION_MAJOR > 2 || (MBEDTLS_VERSION_MAJOR == 2 && MBEDTLS_VERSION_MINOR >= 25)
#define USE_MBEDTLS_CRYPTO_EXT
#endif
#else
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif
static int addPkcs7PaddingInPlace(unsigned char* plaintext, int plaintextLen) {
int paddedLength = ROUND_TO_PKCS7_PADDED_LEN(plaintextLen);
unsigned char paddingByte = (unsigned char)(16 - (plaintextLen % 16));
memset(&plaintext[plaintextLen], paddingByte, paddedLength - plaintextLen);
return paddedLength;
}
// When CIPHER_FLAG_PAD_TO_BLOCK_SIZE is used, inputData buffer must be allocated such that
// the buffer length is at least ROUND_TO_PKCS7_PADDED_LEN(inputDataLength) and inputData
// buffer may be modified!
// For GCM, the IV can change from message to message without CIPHER_FLAG_RESET_IV.
// CIPHER_FLAG_RESET_IV is only required for GCM when the IV length changes.
// Changing the key between encrypt/decrypt calls on a single context is not supported.
bool PltEncryptMessage(PPLT_CRYPTO_CONTEXT ctx, int algorithm, int flags,
unsigned char* key, int keyLength,
unsigned char* iv, int ivLength,
unsigned char* tag, int tagLength,
unsigned char* inputData, int inputDataLength,
unsigned char* outputData, int* outputDataLength) {
#ifdef USE_MBEDTLS
mbedtls_cipher_mode_t cipherMode;
size_t outLength;
switch (algorithm) {
case ALGORITHM_AES_CBC:
LC_ASSERT(tag == NULL);
LC_ASSERT(tagLength == 0);
cipherMode = MBEDTLS_MODE_CBC;
break;
case ALGORITHM_AES_GCM:
LC_ASSERT(tag != NULL);
LC_ASSERT(tagLength > 0);
cipherMode = MBEDTLS_MODE_GCM;
break;
default:
LC_ASSERT(false);
return false;
}
if (!ctx->initialized) {
if (mbedtls_cipher_setup(&ctx->ctx, mbedtls_cipher_info_from_values(MBEDTLS_CIPHER_ID_AES, keyLength * 8, cipherMode)) != 0) {
return false;
}
if (mbedtls_cipher_setkey(&ctx->ctx, key, keyLength * 8, MBEDTLS_ENCRYPT) != 0) {
return false;
}
ctx->initialized = true;
}
if (tag != NULL) {
#ifdef USE_MBEDTLS_CRYPTO_EXT
// In mbedTLS, tag is always after ciphertext, while we need to put tag BEFORE ciphertext here
// To avoid frequent heap allocation, we will use some evil tricks...
// We only support 16 bytes sized tag
LC_ASSERT(tagLength == 16);
// Assume outputData is right after tag
LC_ASSERT(outputData == tag + tagLength);
#ifndef LC_DEBUG
if (tagLength != 16 || outputData != tag + tagLength) {
return false;
}
#endif
size_t encryptedLength = 0;
unsigned char * encryptedData = tag;
size_t encryptedCapacity = inputDataLength + tagLength;
if (mbedtls_cipher_auth_encrypt_ext(&ctx->ctx, iv, ivLength, NULL, 0, inputData, inputDataLength, encryptedData,
encryptedCapacity, &encryptedLength, tagLength) != 0) {
return false;
}
outLength = encryptedLength - tagLength;
unsigned char tagTemp[16];
// Copy the tag to temp buffer
memcpy(tagTemp, encryptedData + outLength, tagLength);
// Move ciphertext to the end
memmove(encryptedData + tagLength, encryptedData, outLength);
// Copy back tag
memcpy(encryptedData, tagTemp, tagLength);
#else
if (mbedtls_cipher_auth_encrypt(&ctx->ctx, iv, ivLength, NULL, 0, inputData, inputDataLength, outputData, &outLength, tag, tagLength) != 0) {
return false;
}
#endif
}
else {
if (flags & CIPHER_FLAG_RESET_IV) {
if (mbedtls_cipher_set_iv(&ctx->ctx, iv, ivLength) != 0) {
return false;
}
mbedtls_cipher_reset(&ctx->ctx);
}
if (flags & CIPHER_FLAG_PAD_TO_BLOCK_SIZE) {
inputDataLength = addPkcs7PaddingInPlace(inputData, inputDataLength);
}
if (mbedtls_cipher_update(&ctx->ctx, inputData, inputDataLength, outputData, &outLength) != 0) {
return false;
}
if (flags & CIPHER_FLAG_FINISH) {
size_t finishLength;
if (mbedtls_cipher_finish(&ctx->ctx, &outputData[outLength], &finishLength) != 0) {
return false;
}
outLength += finishLength;
}
}
*outputDataLength = outLength;
return true;
#else
LC_ASSERT(keyLength == 16);
if (algorithm == ALGORITHM_AES_GCM) {
LC_ASSERT(tag != NULL);
LC_ASSERT(tagLength > 0);
if (!ctx->initialized || (flags & CIPHER_FLAG_RESET_IV)) {
// Perform a full initialization. This codepath also allows
// us to change the IV length if required.
if (EVP_EncryptInit_ex(ctx->ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
return false;
}
if (EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_IVLEN, ivLength, NULL) != 1) {
return false;
}
if (EVP_EncryptInit_ex(ctx->ctx, NULL, NULL, key, iv) != 1) {
return false;
}
ctx->initialized = true;
}
else {
// Calling with cipher == NULL results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(ctx->ctx, NULL, NULL, NULL, iv) != 1) {
return false;
}
}
}
else if (algorithm == ALGORITHM_AES_CBC) {
LC_ASSERT(tag == NULL);
LC_ASSERT(tagLength == 0);
if (!ctx->initialized) {
// Perform a full initialization
if (EVP_EncryptInit_ex(ctx->ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
return false;
}
ctx->initialized = true;
}
else if (flags & CIPHER_FLAG_RESET_IV) {
// Calling with cipher == NULL results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(ctx->ctx, NULL, NULL, NULL, iv) != 1) {
return false;
}
}
if (flags & CIPHER_FLAG_PAD_TO_BLOCK_SIZE) {
inputDataLength = addPkcs7PaddingInPlace(inputData, inputDataLength);
}
}
else {
LC_ASSERT(false);
return false;
}
if (EVP_EncryptUpdate(ctx->ctx, outputData, outputDataLength, inputData, inputDataLength) != 1) {
return false;
}
if (algorithm == ALGORITHM_AES_GCM) {
int len;
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if (EVP_EncryptFinal_ex(ctx->ctx, outputData, &len) != 1) {
return false;
}
LC_ASSERT(len == 0);
if (EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_GET_TAG, tagLength, tag) != 1) {
return false;
}
}
else if (flags & CIPHER_FLAG_FINISH) {
int len;
if (EVP_EncryptFinal_ex(ctx->ctx, &outputData[*outputDataLength], &len) != 1) {
return false;
}
*outputDataLength += len;
}
return true;
#endif
}
// When CBC is used, outputData buffer must be allocated such that the buffer length is
// at least ROUND_TO_PKCS7_PADDED_LEN(inputDataLength) to allow room for PKCS7 padding.
// For GCM, the IV can change from message to message without CIPHER_FLAG_RESET_IV.
// CIPHER_FLAG_RESET_IV is only required for GCM when the IV length changes.
// Changing the key between encrypt/decrypt calls on a single context is not supported.
bool PltDecryptMessage(PPLT_CRYPTO_CONTEXT ctx, int algorithm, int flags,
unsigned char* key, int keyLength,
unsigned char* iv, int ivLength,
unsigned char* tag, int tagLength,
unsigned char* inputData, int inputDataLength,
unsigned char* outputData, int* outputDataLength) {
#ifdef USE_MBEDTLS
mbedtls_cipher_mode_t cipherMode;
size_t outLength;
switch (algorithm) {
case ALGORITHM_AES_CBC:
LC_ASSERT(tag == NULL);
LC_ASSERT(tagLength == 0);
cipherMode = MBEDTLS_MODE_CBC;
break;
case ALGORITHM_AES_GCM:
LC_ASSERT(tag != NULL);
LC_ASSERT(tagLength > 0);
cipherMode = MBEDTLS_MODE_GCM;
break;
default:
LC_ASSERT(false);
return false;
}
if (!ctx->initialized) {
if (mbedtls_cipher_setup(&ctx->ctx, mbedtls_cipher_info_from_values(MBEDTLS_CIPHER_ID_AES, keyLength * 8, cipherMode)) != 0) {
return false;
}
if (mbedtls_cipher_setkey(&ctx->ctx, key, keyLength * 8, MBEDTLS_DECRYPT) != 0) {
return false;
}
ctx->initialized = true;
}
if (tag != NULL) {
#ifdef USE_MBEDTLS_CRYPTO_EXT
// We only support 16 bytes sized tag
LC_ASSERT(tagLength == 16);
// Assume inputData is right after tag
LC_ASSERT(inputData == tag + tagLength);
#ifndef LC_DEBUG
if (tagLength != 16 || inputData != tag + tagLength) {
return false;
}
#endif
unsigned char * encryptedData = tag;
size_t encryptedDataLen = inputDataLength + tagLength;
unsigned char tagTemp[16];
// Copy the tag to temp buffer
memcpy(tagTemp, encryptedData, tagLength);
// Move ciphertext to the beginning
memmove(encryptedData, encryptedData + tagLength, inputDataLength);
// Copy back tag to the end
memcpy(encryptedData + inputDataLength, tagTemp, tagLength);
if (mbedtls_cipher_auth_decrypt_ext(&ctx->ctx, iv, ivLength, NULL, 0, encryptedData, encryptedDataLen,
outputData, inputDataLength, &outLength, tagLength) != 0) {
return false;
}
#else
if (mbedtls_cipher_auth_decrypt(&ctx->ctx, iv, ivLength, NULL, 0, inputData, inputDataLength, outputData, &outLength, tag, tagLength) != 0) {
return false;
}
#endif
}
else {
if (flags & CIPHER_FLAG_RESET_IV) {
if (mbedtls_cipher_set_iv(&ctx->ctx, iv, ivLength) != 0) {
return false;
}
mbedtls_cipher_reset(&ctx->ctx);
}
if (mbedtls_cipher_update(&ctx->ctx, inputData, inputDataLength, outputData, &outLength) != 0) {
return false;
}
if (flags & CIPHER_FLAG_FINISH) {
size_t finishLength;
if (mbedtls_cipher_finish(&ctx->ctx, &outputData[outLength], &finishLength) != 0) {
return false;
}
outLength += finishLength;
}
}
*outputDataLength = outLength;
return true;
#else
LC_ASSERT(keyLength == 16);
if (algorithm == ALGORITHM_AES_GCM) {
LC_ASSERT(tag != NULL);
LC_ASSERT(tagLength > 0);
if (!ctx->initialized || (flags & CIPHER_FLAG_RESET_IV)) {
// Perform a full initialization. This codepath also allows
// us to change the IV length if required.
if (EVP_DecryptInit_ex(ctx->ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
return false;
}
if (EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_IVLEN, ivLength, NULL) != 1) {
return false;
}
if (EVP_DecryptInit_ex(ctx->ctx, NULL, NULL, key, iv) != 1) {
return false;
}
ctx->initialized = true;
}
else {
// Calling with cipher == NULL results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_DecryptInit_ex(ctx->ctx, NULL, NULL, NULL, iv) != 1) {
return false;
}
}
}
else if (algorithm == ALGORITHM_AES_CBC) {
LC_ASSERT(tag == NULL);
LC_ASSERT(tagLength == 0);
if (!ctx->initialized) {
// Perform a full initialization
if (EVP_DecryptInit_ex(ctx->ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
return false;
}
ctx->initialized = true;
}
else if (flags & CIPHER_FLAG_RESET_IV) {
// Calling with cipher == NULL results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_DecryptInit_ex(ctx->ctx, NULL, NULL, NULL, iv) != 1) {
return false;
}
}
}
else {
LC_ASSERT(false);
return false;
}
if (EVP_DecryptUpdate(ctx->ctx, outputData, outputDataLength, inputData, inputDataLength) != 1) {
return false;
}
if (algorithm == ALGORITHM_AES_GCM) {
int len;
// Set the GCM tag before calling EVP_DecryptFinal_ex()
if (EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_TAG, tagLength, tag) != 1) {
return false;
}
// GCM will never have additional plaintext here, but we need to call it to
// ensure that the GCM authentication tag is correct for this data.
if (EVP_DecryptFinal_ex(ctx->ctx, outputData, &len) != 1) {
return false;
}
LC_ASSERT(len == 0);
}
else if (flags & CIPHER_FLAG_FINISH) {
int len;
if (EVP_DecryptFinal_ex(ctx->ctx, &outputData[*outputDataLength], &len) != 1) {
return false;
}
*outputDataLength += len;
}
return true;
#endif
}
PPLT_CRYPTO_CONTEXT PltCreateCryptoContext(void) {
PPLT_CRYPTO_CONTEXT ctx = malloc(sizeof(*ctx));
if (!ctx) {
return NULL;
}
ctx->initialized = false;
#ifdef USE_MBEDTLS
mbedtls_cipher_init(&ctx->ctx);
#else
ctx->ctx = EVP_CIPHER_CTX_new();
if (!ctx->ctx) {
free(ctx);
return NULL;
}
#endif
return ctx;
}
void PltDestroyCryptoContext(PPLT_CRYPTO_CONTEXT ctx) {
#ifdef USE_MBEDTLS
mbedtls_cipher_free(&ctx->ctx);
#else
EVP_CIPHER_CTX_free(ctx->ctx);
#endif
free(ctx);
}
void PltGenerateRandomData(unsigned char* data, int length) {
#ifdef USE_MBEDTLS
// FIXME: This is not thread safe...
if (!RandomStateInitialized) {
mbedtls_entropy_init(&EntropyContext);
mbedtls_ctr_drbg_init(&CtrDrbgContext);
if (mbedtls_ctr_drbg_seed(&CtrDrbgContext, mbedtls_entropy_func, &EntropyContext, NULL, 0) != 0) {
// Nothing we can really do here...
Limelog("Seeding MbedTLS random number generator failed!\n");
LC_ASSERT(false);
return;
}
RandomStateInitialized = true;
}
mbedtls_ctr_drbg_random(&CtrDrbgContext, data, length);
#else
RAND_bytes(data, length);
#endif
}
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include <stdbool.h>
#ifdef USE_MBEDTLS
#include <mbedtls/cipher.h>
#else
// Hide the real OpenSSL definition from other code
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
#endif
typedef struct _PLT_CRYPTO_CONTEXT {
#ifdef USE_MBEDTLS
mbedtls_cipher_context_t ctx;
bool initialized;
#else
EVP_CIPHER_CTX* ctx;
bool initialized;
#endif
} PLT_CRYPTO_CONTEXT, *PPLT_CRYPTO_CONTEXT;
#define ROUND_TO_PKCS7_PADDED_LEN(x) ((((x) + 15) / 16) * 16)
PPLT_CRYPTO_CONTEXT PltCreateCryptoContext(void);
void PltDestroyCryptoContext(PPLT_CRYPTO_CONTEXT ctx);
#define ALGORITHM_AES_CBC 1
#define ALGORITHM_AES_GCM 2
#define CIPHER_FLAG_RESET_IV 0x01
#define CIPHER_FLAG_FINISH 0x02
#define CIPHER_FLAG_PAD_TO_BLOCK_SIZE 0x04
bool PltEncryptMessage(PPLT_CRYPTO_CONTEXT ctx, int algorithm, int flags,
unsigned char* key, int keyLength,
unsigned char* iv, int ivLength,
unsigned char* tag, int tagLength,
unsigned char* inputData, int inputDataLength,
unsigned char* outputData, int* outputDataLength);
bool PltDecryptMessage(PPLT_CRYPTO_CONTEXT ctx, int algorithm, int flags,
unsigned char* key, int keyLength,
unsigned char* iv, int ivLength,
unsigned char* tag, int tagLength,
unsigned char* inputData, int inputDataLength,
unsigned char* outputData, int* outputDataLength);
void PltGenerateRandomData(unsigned char* data, int length);
+119 -466
View File
@@ -1,3 +1,4 @@
#include "PlatformSockets.h"
#include "Limelight-internal.h"
#define TEST_PORT_TIMEOUT_SEC 3
@@ -5,61 +6,38 @@
#define RCV_BUFFER_SIZE_MIN 32767
#define RCV_BUFFER_SIZE_STEP 16384
#if defined(__vita__)
#define TCPv4_MSS 512
#else
#define TCPv4_MSS 536
#endif
#define TCPv6_MSS 1220
#if defined(LC_WINDOWS)
#ifndef SIO_UDP_CONNRESET
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
#endif
static HMODULE WlanApiLibraryHandle;
static HANDLE WlanHandle;
#if defined(LC_WINDOWS_DESKTOP)
DWORD (WINAPI *pfnWlanOpenHandle)(DWORD dwClientVersion, PVOID pReserved, PDWORD pdwNegotiatedVersion, PHANDLE phClientHandle);
DWORD (WINAPI *pfnWlanCloseHandle)(HANDLE hClientHandle, PVOID pReserved);
DWORD (WINAPI *pfnWlanEnumInterfaces)(HANDLE hClientHandle, PVOID pReserved, PWLAN_INTERFACE_INFO_LIST *ppInterfaceList);
VOID (WINAPI *pfnWlanFreeMemory)(PVOID pMemory);
DWORD (WINAPI *pfnWlanSetInterface)(HANDLE hClientHandle, CONST GUID *pInterfaceGuid, WLAN_INTF_OPCODE OpCode, DWORD dwDataSize, CONST PVOID pData, PVOID pReserved);
#endif
#ifndef WLAN_API_MAKE_VERSION
#define WLAN_API_MAKE_VERSION(_major, _minor) (((DWORD)(_minor)) << 16 | (_major))
#endif
#endif
#ifdef __3DS__
in_port_t n3ds_udp_port = 47998;
static const int n3ds_max_buf_size = 0x20000;
#endif
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string, size_t stringLength)
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string)
{
char addrstr[URLSAFESTRING_LEN];
char addrstr[INET6_ADDRSTRLEN];
#ifdef AF_INET6
if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
inet_ntop(addr->ss_family, &sin6->sin6_addr, addrstr, sizeof(addrstr));
// IPv6 addresses need to be enclosed in brackets for URLs
snprintf(string, stringLength, "[%s]", addrstr);
sprintf(string, "[%s]", addrstr);
}
else
#endif
{
else {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
inet_ntop(addr->ss_family, &sin->sin_addr, addrstr, sizeof(addrstr));
// IPv4 addresses are returned without changes
snprintf(string, stringLength, "%s", addrstr);
sprintf(string, "%s", addrstr);
}
}
@@ -71,16 +49,9 @@ void shutdownTcpSocket(SOCKET s) {
int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs) {
#if defined(LC_WINDOWS)
// Windows says that SO_RCVTIMEO puts the socket into an indeterminate state
// when a timeout occurs. MSDN doesn't go into it any more than that, but it
// seems likely that they are referring to the inability to know whether a
// cancelled request consumed some data or not (very relevant for stream-based
// protocols like TCP). Since our sockets are UDP which is already unreliable,
// losing some data in a very rare case is fine, especially because we get to
// halve the number of syscalls per packet by avoiding select().
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeoutMs, sizeof(timeoutMs));
#elif defined(__WIIU__) || defined(__3DS__)
// timeouts aren't supported on Wii U or 3DS
// Windows says that SO_RCVTIMEO puts the socket
// into an indeterminate state, so we won't use
// it for non-fatal socket operations.
return -1;
#else
struct timeval val;
@@ -92,8 +63,22 @@ int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs) {
#endif
}
int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs) {
void setRecvTimeout(SOCKET s, int timeoutSec) {
#if defined(LC_WINDOWS)
int val = timeoutSec * 1000;
#else
struct timeval val;
val.tv_sec = timeoutSec;
val.tv_usec = 0;
#endif
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&val, sizeof(val)) < 0) {
Limelog("setsockopt(SO_RCVTIMEO) failed: %d\n", (int)LastSocketError());
}
}
int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs) {
#if defined(LC_WINDOWS) || defined(__vita__)
// We could have used WSAPoll() but it has some nasty bugs
// https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
//
@@ -118,10 +103,19 @@ int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs) {
if (pollFds[i].events & POLLOUT) {
FD_SET(pollFds[i].fd, &writeFds);
#ifdef LC_WINDOWS
// Windows signals failed connections as an exception,
// while Linux signals them as writeable.
FD_SET(pollFds[i].fd, &exceptFds);
#endif
}
#ifndef LC_WINDOWS
// nfds is unused on Windows
if (pollFds[i].fd >= nfds) {
nfds = pollFds[i].fd + 1;
}
#endif
}
tv.tv_sec = timeoutMs / 1000;
@@ -147,40 +141,15 @@ int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs) {
}
}
return err;
#elif defined(__3DS__)
int err;
u64 poll_start = osGetTime();
for (u64 i = poll_start; (i - poll_start) < timeoutMs; i = osGetTime()) {
err = poll(pollFds, pollFdsCount, 0); // This is running for 14ms
if (err) {
break;
}
svcSleepThread(1000);
}
return err;
#else
return poll(pollFds, pollFdsCount, timeoutMs);
#endif
}
bool isSocketReadable(SOCKET s) {
struct pollfd pfd;
int err;
pfd.fd = s;
pfd.events = POLLIN;
err = pollSockets(&pfd, 1, 0);
if (err <= 0) {
return false;
}
return true;
}
int recvUdpSocket(SOCKET s, char* buffer, int size, bool useSelect) {
int err;
do {
if (useSelect) {
struct pollfd pfd;
@@ -205,14 +174,7 @@ int recvUdpSocket(SOCKET s, char* buffer, int size, bool useSelect) {
if (err < 0 &&
(LastSocketError() == EWOULDBLOCK ||
LastSocketError() == EINTR ||
LastSocketError() == EAGAIN ||
#if defined(LC_WINDOWS)
// This error is specific to overlapped I/O which isn't even
// possible to perform with recvfrom(). It seems to randomly
// be returned instead of WSAETIMEDOUT on certain systems.
LastSocketError() == WSA_IO_PENDING ||
#endif
LastSocketError() == ETIMEDOUT)) {
LastSocketError() == EAGAIN)) {
// Return 0 for timeout
return 0;
}
@@ -238,90 +200,24 @@ void closeSocket(SOCKET s) {
#endif
}
// These set "safe" host or link-local QoS options that we can unconditionally
// set without having to worry about routers blockholing the traffic.
static void setSocketQos(SOCKET s, int socketQosType) {
#ifdef SO_NET_SERVICE_TYPE
int value;
switch (socketQosType) {
case SOCK_QOS_TYPE_BEST_EFFORT:
value = NET_SERVICE_TYPE_BE;
break;
case SOCK_QOS_TYPE_AUDIO:
value = NET_SERVICE_TYPE_VO;
break;
case SOCK_QOS_TYPE_VIDEO:
value = NET_SERVICE_TYPE_VI;
break;
default:
Limelog("Unknown QoS type: %d\n", socketQosType);
return;
}
// iOS/macOS
if (setsockopt(s, SOL_SOCKET, SO_NET_SERVICE_TYPE, (char*)&value, sizeof(value)) < 0) {
Limelog("setsockopt(SO_NET_SERVICE_TYPE, %d) failed: %d\n", value, (int)LastSocketError());
}
#endif
#ifdef SO_PRIORITY
int value;
switch (socketQosType) {
case SOCK_QOS_TYPE_BEST_EFFORT:
value = 0;
break;
case SOCK_QOS_TYPE_AUDIO:
value = 6;
break;
case SOCK_QOS_TYPE_VIDEO:
value = 5;
break;
default:
Limelog("Unknown QoS type: %d\n", socketQosType);
return;
}
// Linux
if (setsockopt(s, SOL_SOCKET, SO_PRIORITY, (char*)&value, sizeof(value)) < 0) {
Limelog("setsockopt(SO_PRIORITY, %d) failed: %d\n", value, (int)LastSocketError());
}
#endif
}
SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCKADDR_LEN addrLen, int bufferSize, int socketQosType) {
SOCKET bindUdpSocket(int addrfamily, int bufferSize) {
SOCKET s;
LC_SOCKADDR bindAddr;
struct sockaddr_storage addr;
int err;
s = createSocket(addressFamily, SOCK_DGRAM, IPPROTO_UDP, false);
LC_ASSERT(addrfamily == AF_INET || addrfamily == AF_INET6);
s = createSocket(addrfamily, SOCK_DGRAM, IPPROTO_UDP, false);
if (s == INVALID_SOCKET) {
return INVALID_SOCKET;
}
// Use localAddr to bind if it was provided
if (localAddr && localAddr->ss_family != 0) {
memcpy(&bindAddr, localAddr, addrLen);
SET_PORT(&bindAddr, 0);
}
else {
// Otherwise wildcard bind to the specified address family
memset(&bindAddr, 0, sizeof(bindAddr));
SET_FAMILY(&bindAddr, addressFamily);
#ifdef AF_INET6
LC_ASSERT(addressFamily == AF_INET || addressFamily == AF_INET6);
addrLen = (addressFamily == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6));
#else
LC_ASSERT(addressFamily == AF_INET);
addrLen = sizeof(struct sockaddr_in);
#endif
}
#ifdef __3DS__
// binding to wildcard port is broken on the 3DS, so we need to define a port manually
struct sockaddr_in *n3ds_addr = &bindAddr;
n3ds_addr->sin_port = htons(n3ds_udp_port++);
#endif
if (bind(s, (struct sockaddr*) &bindAddr, addrLen) == SOCKET_ERROR) {
memset(&addr, 0, sizeof(addr));
addr.ss_family = addrfamily;
if (bind(s, (struct sockaddr*) &addr,
addrfamily == AF_INET ?
sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6)) == SOCKET_ERROR) {
err = LastSocketError();
Limelog("bind() failed: %d\n", err);
closeSocket(s);
@@ -329,97 +225,56 @@ SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCK
return INVALID_SOCKET;
}
#if defined(LC_DARWIN)
#ifdef LC_DARWIN
{
// Disable SIGPIPE on iOS
int val = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val));
}
#elif defined(LC_WINDOWS)
{
// Disable WSAECONNRESET for UDP sockets on Windows
BOOL val = FALSE;
DWORD bytesReturned = 0;
if (WSAIoctl(s, SIO_UDP_CONNRESET, &val, sizeof(val), NULL, 0, &bytesReturned, NULL, NULL) != 0) {
Limelog("WSAIoctl(SIO_UDP_CONNRESET) failed: %d\n", LastSocketError());
}
}
#elif defined(__WIIU__)
{
// Enable usage of userbuffers on Wii U
int val = 1;
setsockopt(s, SOL_SOCKET, SO_RUSRBUF, &val, sizeof(val));
}
#endif
// Enable QOS for the socket (best effort)
if (socketQosType != SOCK_QOS_TYPE_BEST_EFFORT) {
setSocketQos(s, socketQosType);
}
#ifdef __3DS__
if (bufferSize == 0 || bufferSize > n3ds_max_buf_size)
bufferSize = n3ds_max_buf_size;
#endif
if (bufferSize != 0) {
// We start at the requested recv buffer value and step down until we find
// a value that the OS will accept.
for (;;) {
err = setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(bufferSize));
if (err == 0) {
// Successfully set a buffer size
break;
}
else if (bufferSize <= RCV_BUFFER_SIZE_MIN) {
// Failed to set a buffer size within the allowable range
Limelog("Set rcv buffer size failed: %d\n", LastSocketError());
break;
}
else if (bufferSize - RCV_BUFFER_SIZE_STEP <= RCV_BUFFER_SIZE_MIN) {
// Last shot - we're trying the minimum
bufferSize = RCV_BUFFER_SIZE_MIN;
}
else {
// Lower the requested size by another step
bufferSize -= RCV_BUFFER_SIZE_STEP;
}
}
#if defined(LC_DEBUG)
// We start at the requested recv buffer value and step down until we find
// a value that the OS will accept.
for (;;) {
err = setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(bufferSize));
if (err == 0) {
Limelog("Selected receive buffer size: %d\n", bufferSize);
// Successfully set a buffer size
break;
}
else if (bufferSize <= RCV_BUFFER_SIZE_MIN) {
// Failed to set a buffer size within the allowable range
break;
}
else if (bufferSize - RCV_BUFFER_SIZE_STEP <= RCV_BUFFER_SIZE_MIN) {
// Last shot - we're trying the minimum
bufferSize = RCV_BUFFER_SIZE_MIN;
}
else {
Limelog("Unable to set receive buffer size: %d\n", LastSocketError());
// Lower the requested size by another step
bufferSize -= RCV_BUFFER_SIZE_STEP;
}
{
SOCKADDR_LEN len = sizeof(bufferSize);
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, &len) == 0) {
Limelog("Actual receive buffer size: %d\n", bufferSize);
}
}
#endif
}
#if defined(LC_DEBUG)
if (err == 0) {
Limelog("Selected receive buffer size: %d\n", bufferSize);
}
else {
Limelog("Unable to set receive buffer size: %d\n", LastSocketError());
}
#endif
return s;
}
int setSocketNonBlocking(SOCKET s, bool enabled) {
#if defined(__vita__) || defined(__HAIKU__)
int val = enabled ? 1 : 0;
#if defined(__vita__)
return setsockopt(s, SOL_SOCKET, SO_NONBLOCK, (char*)&val, sizeof(val));
#elif defined(O_NONBLOCK)
return fcntl(s, F_SETFL, (enabled ? O_NONBLOCK : 0) | (fcntl(s, F_GETFL) & ~O_NONBLOCK));
#elif defined(FIONBIO)
#ifdef LC_WINDOWS
u_long val = enabled ? 1 : 0;
#else
int val = enabled ? 1 : 0;
#endif
return ioctlsocket(s, FIONBIO, &val);
#else
#error Please define your platform non-blocking sockets API!
return SOCKET_ERROR;
#endif
}
@@ -449,7 +304,7 @@ SOCKET createSocket(int addressFamily, int socketType, int protocol, bool nonBlo
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec) {
SOCKET s;
LC_SOCKADDR addr;
struct sockaddr_in6 addr;
struct pollfd pfd;
int err;
int val;
@@ -504,7 +359,7 @@ SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen,
// Start connection
memcpy(&addr, dstaddr, addrlen);
SET_PORT(&addr, port);
addr.sin6_port = htons(port);
err = connect(s, (struct sockaddr*) &addr, addrlen);
if (err < 0) {
err = (int)LastSocketError();
@@ -512,7 +367,7 @@ SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen,
goto Exit;
}
}
// Wait for the connection to complete or the timeout to elapse
pfd.fd = s;
pfd.events = POLLOUT;
@@ -532,17 +387,6 @@ SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen,
SetLastSocketError(ETIMEDOUT);
return INVALID_SOCKET;
}
#ifdef __3DS__ //SO_ERROR is unreliable on 3DS
else {
char test_buffer[1];
err = (int)recv(s, test_buffer, 1, MSG_PEEK);
if (err < 0 &&
(LastSocketError() == EWOULDBLOCK ||
LastSocketError() == EAGAIN)) {
err = 0;
}
}
#else
else {
// The socket was signalled
SOCKADDR_LEN len = sizeof(err);
@@ -552,11 +396,10 @@ SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen,
err = (err != 0) ? err : LastSocketFail();
}
}
#endif
// Disable non-blocking I/O now that the connection is established
setSocketNonBlocking(s, false);
Exit:
if (err != 0) {
Limelog("connect() failed: %d\n", err);
@@ -568,40 +411,6 @@ Exit:
return s;
}
int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort,
struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen) {
SOCKET udpSocket;
LC_SOCKADDR connAddr;
LC_ASSERT(targetPort != 0);
udpSocket = createSocket(targetAddr->ss_family, SOCK_DGRAM, IPPROTO_UDP, false);
if (udpSocket == INVALID_SOCKET) {
return LastSocketError();
}
memcpy(&connAddr, targetAddr, targetAddrLen);
SET_PORT(&connAddr, RtspPortNumber);
if (connect(udpSocket, (struct sockaddr*)&connAddr, targetAddrLen) < 0) {
int err = LastSocketError();
Limelog("UDP connect() failed: %d\n", err);
closeSocket(udpSocket);
return err;
}
*localAddrLen = sizeof(*localAddr);
if (getsockname(udpSocket, (struct sockaddr*)localAddr, localAddrLen) < 0) {
int err = LastSocketError();
Limelog("getsockname() failed: %d\n", err);
closeSocket(udpSocket);
return err;
}
closeSocket(udpSocket);
return 0;
}
// See TCP_MAXSEG note in connectTcpSocket() above for more information.
// TCP_NODELAY must be enabled on the socket for this function to work!
int sendMtuSafe(SOCKET s, char* buffer, int size) {
@@ -634,43 +443,10 @@ int enableNoDelay(SOCKET s) {
return 0;
}
static bool isPrivateNetworkAddressV4(struct sockaddr_in* address, bool matchCGN)
{
unsigned int addr;
memcpy(&addr, &address->sin_addr, sizeof(addr));
addr = htonl(addr);
// 10.0.0.0/8
if ((addr & 0xFF000000) == 0x0A000000) {
return true;
}
// 172.16.0.0/12
else if ((addr & 0xFFF00000) == 0xAC100000) {
return true;
}
// 192.168.0.0/16
else if ((addr & 0xFFFF0000) == 0xC0A80000) {
return true;
}
// 169.254.0.0/16
else if ((addr & 0xFFFF0000) == 0xA9FE0000) {
return true;
}
// 100.64.0.0/10
else if (matchCGN && (addr & 0xFFC00000) == 0x64400000) {
return true;
}
else {
return false;
}
}
int resolveHostName(const char* host, int family, int tcpTestPort, struct sockaddr_storage* addr, SOCKADDR_LEN* addrLen)
{
struct addrinfo hints, *res, *currentAddr;
int err;
bool needsFallbackV4 = false;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
@@ -686,35 +462,14 @@ int resolveHostName(const char* host, int family, int tcpTestPort, struct sockad
Limelog("getaddrinfo(%s) returned success without addresses\n", host);
return -1;
}
#ifdef AF_INET6
{
struct sockaddr_in sin4;
memset(&sin4, 0, sizeof(sin4));
sin4.sin_family = AF_INET;
// As a workaround for broken 464XLAT on iOS where the CLAT synthesizes IPv6
// addresses for on-link IPv4 destinations on other interfaces, we will try
// again with an IPv4-only resolution if we detect that getaddrinfo() resolved
// a private IPv4 address to a single IPv6 address and that address didn't work.
needsFallbackV4 =
family == AF_UNSPEC &&
res->ai_family == AF_INET6 &&
res->ai_next == NULL &&
inet_pton(AF_INET, host, &sin4.sin_addr) == 1 &&
isPrivateNetworkAddressV4(&sin4, true);
}
#endif
for (currentAddr = res; currentAddr != NULL; currentAddr = currentAddr->ai_next) {
// Use the test port to ensure this address is working if:
// a) We have multiple addresses
// b) The caller asked us to test even with a single address
// c) We got an IPv6 address synthesized from an IPv4 address
if (tcpTestPort != 0 && (res->ai_next != NULL || (tcpTestPort & TCP_PORT_FLAG_ALWAYS_TEST) || needsFallbackV4)) {
if (tcpTestPort != 0 && (res->ai_next != NULL || (tcpTestPort & TCP_PORT_FLAG_ALWAYS_TEST))) {
SOCKET testSocket = connectTcpSocket((struct sockaddr_storage*)currentAddr->ai_addr,
(SOCKADDR_LEN)currentAddr->ai_addrlen,
currentAddr->ai_addrlen,
tcpTestPort & TCP_PORT_MASK,
TEST_PORT_TIMEOUT_SEC);
if (testSocket == INVALID_SOCKET) {
@@ -725,46 +480,58 @@ int resolveHostName(const char* host, int family, int tcpTestPort, struct sockad
closeSocket(testSocket);
}
}
memcpy(addr, currentAddr->ai_addr, currentAddr->ai_addrlen);
*addrLen = (SOCKADDR_LEN)currentAddr->ai_addrlen;
*addrLen = currentAddr->ai_addrlen;
freeaddrinfo(res);
return 0;
}
Limelog("No working addresses found for host: %s\n", host);
freeaddrinfo(res);
if (needsFallbackV4) {
// Fallback to IPv4-only if we didn't find a working address (see comment above)
return resolveHostName(host, AF_INET, tcpTestPort, addr, addrLen);
}
else {
Limelog("No working addresses found for host: %s\n", host);
return -1;
}
return -1;
}
#ifdef AF_INET6
bool isInSubnetV6(struct sockaddr_in6* sin6, unsigned char* subnet, int prefixLength) {
int i;
for (i = 0; i < prefixLength; i++) {
unsigned char mask = 1 << (i % 8);
if ((sin6->sin6_addr.s6_addr[i / 8] & mask) != (subnet[i / 8] & mask)) {
return false;
}
}
return true;
}
#endif
bool isPrivateNetworkAddress(struct sockaddr_storage* address) {
// We only count IPv4 addresses as possibly private for now
if (address->ss_family == AF_INET) {
return isPrivateNetworkAddressV4((struct sockaddr_in*)address, false);
unsigned int addr;
memcpy(&addr, &((struct sockaddr_in*)address)->sin_addr, sizeof(addr));
addr = htonl(addr);
// 10.0.0.0/8
if ((addr & 0xFF000000) == 0x0A000000) {
return true;
}
// 172.16.0.0/12
else if ((addr & 0xFFF00000) == 0xAC100000) {
return true;
}
// 192.168.0.0/16
else if ((addr & 0xFFFF0000) == 0xC0A80000) {
return true;
}
// 169.254.0.0/16
else if ((addr & 0xFFFF0000) == 0xA9FE0000) {
return true;
}
}
#ifdef AF_INET6
else if (address->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)address;
static unsigned char linkLocalPrefix[] = {0xfe, 0x80};
@@ -784,127 +551,13 @@ bool isPrivateNetworkAddress(struct sockaddr_storage* address) {
return true;
}
}
#endif
return false;
}
bool isNat64SynthesizedAddress(struct sockaddr_storage* address) {
#ifdef AF_INET6
if (address->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)address;
struct addrinfo hints, *res, *currentAddr;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo("ipv4only.arpa.", NULL, &hints, &res);
if (err != 0) {
Limelog("Client is not running in NAT64 environment (%d)\n", err);
return false;
}
else if (res == NULL) {
Limelog("getaddrinfo(ipv4only.arpa.) returned success without addresses\n");
return false;
}
for (currentAddr = res; currentAddr != NULL; currentAddr = currentAddr->ai_next) {
struct sockaddr_in6* candidate6 = (struct sockaddr_in6*)currentAddr->ai_addr;
static const unsigned char wellKnownAddresses[2][4] = {
{ 0xC0, 0x00, 0x00, 0xAA }, // 192.0.0.170
{ 0xC0, 0x00, 0x00, 0xAB }, // 192.0.0.171
};
if (candidate6->sin6_family != AF_INET6) {
// This shouldn't be possible but check anyway
continue;
}
for (int i = 0; i < 2; i++) {
int foundCount = 0;
int prefixLen = 0;
int suffixStart = 0;
// Search for each well-known IPv4 address at all locations specified by
// https://datatracker.ietf.org/doc/html/rfc6052#section-2.2
if (memcmp(&candidate6->sin6_addr.s6_addr[4], wellKnownAddresses[i], 4) == 0) {
foundCount++;
prefixLen = 4;
suffixStart = 9;
}
if (memcmp(&candidate6->sin6_addr.s6_addr[5], &wellKnownAddresses[i][0], 3) == 0 &&
memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][3], 1) == 0) {
foundCount++;
prefixLen = 5;
suffixStart = 10;
}
if (memcmp(&candidate6->sin6_addr.s6_addr[6], &wellKnownAddresses[i][0], 2) == 0 &&
memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][2], 2) == 0) {
foundCount++;
prefixLen = 6;
suffixStart = 11;
}
if (memcmp(&candidate6->sin6_addr.s6_addr[7], &wellKnownAddresses[i][0], 1) == 0 &&
memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i][1], 3) == 0) {
foundCount++;
prefixLen = 7;
suffixStart = 12;
}
if (memcmp(&candidate6->sin6_addr.s6_addr[9], &wellKnownAddresses[i], 4) == 0) {
foundCount++;
prefixLen = 8;
suffixStart = 13;
}
if (memcmp(&candidate6->sin6_addr.s6_addr[12], &wellKnownAddresses[i], 4) == 0) {
foundCount++;
prefixLen = 12;
suffixStart = 16;
}
// We must find the well-known address exactly once. If we find it zero or multiple
// times, we must try the second well-known address or other AAAA records.
if (foundCount != 1) {
continue;
}
// We have a valid NAT64 address identified, so we know we're running in an NAT64 environment.
//
// Now we must check to see if the address we resolved for the remote host actually falls
// within the NAT64 range to see if we must restrict ourselves to the IPv4 MTU.
if (memcmp(&sin6->sin6_addr.s6_addr[0], &candidate6->sin6_addr.s6_addr[0], prefixLen) == 0 &&
(suffixStart == 16 || memcmp(&sin6->sin6_addr.s6_addr[suffixStart],
&candidate6->sin6_addr.s6_addr[suffixStart],
16 - suffixStart) == 0)) {
freeaddrinfo(res);
return true;
}
else {
// This one didn't match, so let's break out of the loop and try the next AAAA record.
break;
}
}
}
freeaddrinfo(res);
return false;
}
#endif
return false;
}
// Enable platform-specific low latency options (best-effort)
void enterLowLatencyMode(void) {
#if defined(LC_WINDOWS_DESKTOP)
#if defined(LC_WINDOWS)
DWORD negotiatedVersion;
PWLAN_INTERFACE_INFO_LIST wlanInterfaceList;
DWORD i;
@@ -919,11 +572,11 @@ void enterLowLatencyMode(void) {
return;
}
pfnWlanOpenHandle = (void*)GetProcAddress(WlanApiLibraryHandle, "WlanOpenHandle");
pfnWlanCloseHandle = (void*)GetProcAddress(WlanApiLibraryHandle, "WlanCloseHandle");
pfnWlanFreeMemory = (void*)GetProcAddress(WlanApiLibraryHandle, "WlanFreeMemory");
pfnWlanEnumInterfaces = (void*)GetProcAddress(WlanApiLibraryHandle, "WlanEnumInterfaces");
pfnWlanSetInterface = (void*)GetProcAddress(WlanApiLibraryHandle, "WlanSetInterface");
pfnWlanOpenHandle = GetProcAddress(WlanApiLibraryHandle, "WlanOpenHandle");
pfnWlanCloseHandle = GetProcAddress(WlanApiLibraryHandle, "WlanCloseHandle");
pfnWlanFreeMemory = GetProcAddress(WlanApiLibraryHandle, "WlanFreeMemory");
pfnWlanEnumInterfaces = GetProcAddress(WlanApiLibraryHandle, "WlanEnumInterfaces");
pfnWlanSetInterface = GetProcAddress(WlanApiLibraryHandle, "WlanSetInterface");
if (pfnWlanOpenHandle == NULL || pfnWlanCloseHandle == NULL ||
pfnWlanFreeMemory == NULL || pfnWlanEnumInterfaces == NULL || pfnWlanSetInterface == NULL) {
@@ -978,7 +631,7 @@ void enterLowLatencyMode(void) {
}
void exitLowLatencyMode(void) {
#if defined(LC_WINDOWS_DESKTOP)
#if defined(LC_WINDOWS)
// Closing our WLAN client handle will undo our optimizations
if (WlanHandle != NULL) {
pfnWlanCloseHandle(WlanHandle, NULL);
@@ -1007,7 +660,7 @@ int initializePlatformSockets(void) {
#if defined(LC_WINDOWS)
WSADATA data;
return WSAStartup(MAKEWORD(2, 0), &data);
#elif defined(__vita__) || defined(__WIIU__) || defined(__3DS__)
#elif defined(__vita__)
return 0; // already initialized
#elif defined(LC_POSIX) && !defined(LC_CHROME)
// Disable SIGPIPE signals to avoid us getting
+34 -63
View File
@@ -2,71 +2,46 @@
#include "Limelight.h"
#include "Platform.h"
#ifdef __3DS__
#include <netinet/in.h>
#ifdef AF_INET6
#undef AF_INET6
#endif
extern in_port_t n3ds_udp_port;
#endif
#ifdef __vita__
#ifdef AF_INET6
#undef AF_INET6
#endif
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Windows.h>
#include <wlanapi.h>
#ifndef __MINGW32__
#include <timeapi.h>
#else
#include <mmsystem.h>
#endif
#define SetLastSocketError(x) WSASetLastError(x)
#define LastSocketError() WSAGetLastError()
#define SHUT_RDWR SD_BOTH
#ifdef EAGAIN
#undef EAGAIN
#endif
#define EAGAIN WSAEWOULDBLOCK
#ifdef EINTR
#undef EINTR
#endif
#define EINTR WSAEINTR
// errno.h will include incompatible definitions of these
// values compared to what Winsock uses, so we must undef
// them to ensure the correct value is used.
#ifdef EWOULDBLOCK
#undef EWOULDBLOCK
#endif
#define EWOULDBLOCK WSAEWOULDBLOCK
#ifdef EAGAIN
#undef EAGAIN
#endif
#define EAGAIN WSAEWOULDBLOCK
#ifdef EINPROGRESS
#undef EINPROGRESS
#endif
#define EINPROGRESS WSAEINPROGRESS
#ifdef EINTR
#undef EINTR
#endif
#define EINTR WSAEINTR
#ifdef ETIMEDOUT
#undef ETIMEDOUT
#endif
#define ETIMEDOUT WSAETIMEDOUT
#ifdef ECONNREFUSED
#undef ECONNREFUSED
#endif
#define ECONNREFUSED WSAECONNREFUSED
#ifdef EMSGSIZE
#undef EMSGSIZE
#endif
#define EMSGSIZE WSAEMSGSIZE
typedef int SOCK_RET;
typedef int SOCKADDR_LEN;
@@ -81,7 +56,24 @@ typedef int SOCKADDR_LEN;
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#if defined(__vita__)
#define POLLIN 0x0001
#define POLLPRI 0x0002
#define POLLOUT 0x0004
#define POLLERR 0x0008
#define POLLRDNORM 0x0040
#define POLLWRNORM POLLOUT
#define POLLRDBAND 0x0080
#define POLLWRBAND 0x0100
struct pollfd {
int fd;
short events;
short revents;
};
#else
#include <poll.h>
#endif
#define ioctlsocket ioctl
#define LastSocketError() errno
@@ -94,46 +86,25 @@ typedef ssize_t SOCK_RET;
typedef socklen_t SOCKADDR_LEN;
#endif
#ifdef AF_INET6
typedef struct sockaddr_in6 LC_SOCKADDR;
#define SET_FAMILY(addr, family) ((addr)->sin6_family = (family))
#define SET_PORT(addr, port) ((addr)->sin6_port = htons(port))
#else
typedef struct sockaddr_in LC_SOCKADDR;
#define SET_FAMILY(addr, family) ((addr)->sin_family = (family))
#define SET_PORT(addr, port) ((addr)->sin_port = htons(port))
#endif
#define LastSocketFail() ((LastSocketError() != 0) ? LastSocketError() : -1)
#ifdef AF_INET6
// IPv6 addresses have 2 extra characters for URL escaping
#define URLSAFESTRING_LEN (INET6_ADDRSTRLEN+2)
#else
#define URLSAFESTRING_LEN INET_ADDRSTRLEN
#endif
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string, size_t stringLength);
#define SOCK_QOS_TYPE_BEST_EFFORT 0
#define SOCK_QOS_TYPE_AUDIO 1
#define SOCK_QOS_TYPE_VIDEO 2
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string);
SOCKET createSocket(int addressFamily, int socketType, int protocol, bool nonBlocking);
SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen, unsigned short port, int timeoutSec);
int getLocalAddressByUdpConnect(const struct sockaddr_storage* targetAddr, SOCKADDR_LEN targetAddrLen, unsigned short targetPort,
struct sockaddr_storage* localAddr, SOCKADDR_LEN* localAddrLen);
int sendMtuSafe(SOCKET s, char* buffer, int size);
SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCKADDR_LEN addrLen, int bufferSize, int socketQosType);
SOCKET bindUdpSocket(int addrfamily, int bufferSize);
int enableNoDelay(SOCKET s);
int setSocketNonBlocking(SOCKET s, bool enabled);
int recvUdpSocket(SOCKET s, char* buffer, int size, bool useSelect);
void shutdownTcpSocket(SOCKET s);
int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs);
void setRecvTimeout(SOCKET s, int timeoutSec);
void closeSocket(SOCKET s);
bool isPrivateNetworkAddress(struct sockaddr_storage* address);
bool isNat64SynthesizedAddress(struct sockaddr_storage* address);
int pollSockets(struct pollfd* pollFds, int pollFdsCount, int timeoutMs);
bool isSocketReadable(SOCKET s);
#define TCP_PORT_MASK 0xFFFF
#define TCP_PORT_FLAG_ALWAYS_TEST 0x10000
+26 -33
View File
@@ -6,68 +6,61 @@
typedef void(*ThreadEntry)(void* context);
#if defined(LC_WINDOWS)
typedef SRWLOCK PLT_MUTEX;
typedef CONDITION_VARIABLE PLT_COND;
typedef HANDLE PLT_MUTEX;
typedef HANDLE PLT_EVENT;
typedef struct _PLT_THREAD {
HANDLE handle;
bool cancelled;
atomic_bool cancelled;
} PLT_THREAD;
#elif defined(__WIIU__)
typedef OSFastMutex PLT_MUTEX;
typedef OSFastCondition PLT_COND;
#elif defined(__vita__)
typedef int PLT_MUTEX;
typedef struct _PLT_EVENT {
int mutex;
int cond;
atomic_bool signalled;
} PLT_EVENT;
typedef struct _PLT_THREAD {
OSThread thread;
int cancelled;
} PLT_THREAD;
#elif defined(__3DS__)
typedef LightLock PLT_MUTEX;
typedef CondVar PLT_COND;
typedef struct _PLT_THREAD {
Thread thread;
bool cancelled;
int handle;
atomic_bool cancelled;
void *context;
bool alive;
} PLT_THREAD;
#elif defined (LC_POSIX)
typedef pthread_mutex_t PLT_MUTEX;
typedef pthread_cond_t PLT_COND;
typedef struct _PLT_EVENT {
pthread_mutex_t mutex;
pthread_cond_t cond;
atomic_bool signalled;
} PLT_EVENT;
typedef struct _PLT_THREAD {
pthread_t thread;
bool cancelled;
atomic_bool cancelled;
} PLT_THREAD;
#else
#error Unsupported platform
#endif
#ifdef LC_WINDOWS
typedef HANDLE PLT_EVENT;
#else
typedef struct _PLT_EVENT {
PLT_MUTEX mutex;
PLT_COND cond;
bool signalled;
} PLT_EVENT;
#endif
int PltCreateMutex(PLT_MUTEX* mutex);
void PltDeleteMutex(PLT_MUTEX* mutex);
void PltLockMutex(PLT_MUTEX* mutex);
void PltUnlockMutex(PLT_MUTEX* mutex);
int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THREAD* thread);
void PltCloseThread(PLT_THREAD* thread);
void PltInterruptThread(PLT_THREAD* thread);
bool PltIsThreadInterrupted(PLT_THREAD* thread);
void PltJoinThread(PLT_THREAD* thread);
void PltDetachThread(PLT_THREAD* thread);
int PltCreateEvent(PLT_EVENT* event);
void PltCloseEvent(PLT_EVENT* event);
void PltSetEvent(PLT_EVENT* event);
void PltClearEvent(PLT_EVENT* event);
void PltWaitForEvent(PLT_EVENT* event);
int PltWaitForEvent(PLT_EVENT* event);
int PltCreateConditionVariable(PLT_COND* cond, PLT_MUTEX* mutex);
void PltDeleteConditionVariable(PLT_COND* cond);
void PltSignalConditionVariable(PLT_COND* cond);
void PltWaitForConditionVariable(PLT_COND* cond, PLT_MUTEX* mutex);
void PltRunThreadProc(void);
#define PLT_WAIT_SUCCESS 0
#define PLT_WAIT_INTERRUPTED 1
void PltSleepMs(int ms);
void PltSleepMsInterruptible(PLT_THREAD* thread, int ms);
-102
View File
@@ -1,102 +0,0 @@
#ifdef _WIN32
// Don't warn for fopen() usage
#define _CRT_SECURE_NO_WARNINGS 1
#endif
#include "Limelight-internal.h"
static FILE* videoFile;
static FILE* audioFile;
static DECODER_RENDERER_CALLBACKS realDrCallbacks;
static AUDIO_RENDERER_CALLBACKS realArCallbacks;
static int recDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
{
const char* path = context;
if (path != NULL) {
videoFile = fopen(path, "wb");
if (videoFile == NULL) {
return -1;
}
}
else {
Limelog("Video recording will not be enabled - file path not specified in drContext!\n");
}
return realDrCallbacks.setup(videoFormat, width, height, redrawRate, NULL, drFlags);
}
static void recDrCleanup(void)
{
if (videoFile != NULL) {
fclose(videoFile);
videoFile = NULL;
}
realDrCallbacks.cleanup();
}
static int recDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
{
if (videoFile != NULL) {
PLENTRY entry = decodeUnit->bufferList;
while (entry != NULL) {
fwrite(entry->data, 1, entry->length, videoFile);
entry = entry->next;
}
}
return realDrCallbacks.submitDecodeUnit(decodeUnit);
}
static int recArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags)
{
const char* path = context;
if (path != NULL) {
audioFile = fopen(path, "wb");
if (audioFile == NULL) {
return -1;
}
}
else {
Limelog("Audio recording will not be enabled - file path not specified in arContext!\n");
}
return realArCallbacks.init(audioConfiguration, opusConfig, NULL, arFlags);
}
static void recArCleanup(void)
{
if (audioFile != NULL) {
fclose(audioFile);
audioFile = NULL;
}
realArCallbacks.cleanup();
}
static void recArDecodeAndPlaySample(char* sampleData, int sampleLength)
{
if (audioFile != NULL) {
fwrite(sampleData, 1, sampleLength, audioFile);
}
realArCallbacks.decodeAndPlaySample(sampleData, sampleLength);
}
void setRecorderCallbacks(PDECODER_RENDERER_CALLBACKS drCallbacks, PAUDIO_RENDERER_CALLBACKS arCallbacks)
{
realDrCallbacks = *drCallbacks;
realArCallbacks = *arCallbacks;
drCallbacks->setup = recDrSetup;
drCallbacks->cleanup = recDrCleanup;
drCallbacks->submitDecodeUnit = recDrSubmitDecodeUnit;
arCallbacks->init = recArInit;
arCallbacks->cleanup = recArCleanup;
arCallbacks->decodeAndPlaySample = recArDecodeAndPlaySample;
}
-729
View File
@@ -1,729 +0,0 @@
#include "Limelight-internal.h"
#if defined(LC_DEBUG) && !defined(LC_FUZZING)
// This enables FEC validation mode with a synthetic drop
// and recovered packet checks vs the original input. It
// is on by default for debug builds.
//
// NB: Unlike the video FEC feature of the same name, this
// is much more restrictive in terms of when the validation
// runs. Due to the logic to immediately return in-order
// data packets, it requires non-consecutive data packets to
// trigger the call to completeFecBlock(). Missing or OOO
// packets will do the job.
#define FEC_VALIDATION_MODE
#define FEC_VERBOSE
#endif
#define RTP_PAYLOAD_TYPE_AUDIO 97
#define RTP_PAYLOAD_TYPE_FEC 127
void RtpaInitializeQueue(PRTP_AUDIO_QUEUE queue) {
memset(queue, 0, sizeof(*queue));
// We will start in the synchronizing state, where we wait for the first
// full FEC block before reporting losses, out of order packets, etc.
queue->synchronizing = true;
// Older versions of GFE violate some invariants that our FEC code requires, so we turn it off for
// anything older than GFE 3.19 just to be safe. GFE seems to have changed to the "modern" behavior
// between GFE 3.18 and 3.19.
//
// In the case of GFE 3.13, it does send FEC packets but it requires very special handling because:
// a) data and FEC shards may vary in size
// b) FEC blocks can start on boundaries that are not multiples of RTPA_DATA_SHARDS
//
// It doesn't seem worth it to sink a bunch of hours into figure out how to properly handle audio FEC
// for a 3 year old version of GFE that almost nobody uses. Instead, we'll just disable the FEC queue
// entirely and pass all audio data straight to the decoder.
//
if (!APP_VERSION_AT_LEAST(7, 1, 415)) {
Limelog("Audio FEC has been disabled due to an incompatibility with your host's old software.\n");
Limelog("Audio quality may suffer on unreliable network connections due to lack of FEC!\n");
queue->incompatibleServer = true;
}
reed_solomon_init();
// The number of data and parity shards is constant, so we can reuse
// the same RS matrices for all traffic.
queue->rs = reed_solomon_new(RTPA_DATA_SHARDS, RTPA_FEC_SHARDS);
// For unknown reasons, the RS parity matrix computed by our RS implementation
// doesn't match the one Nvidia uses for audio data. I'm not exactly sure why,
// but we can simply replace it with the matrix generated by OpenFEC which
// works correctly. This is possible because the data and FEC shard count is
// constant and known in advance.
const unsigned char parity[] = { 0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c };
memcpy(queue->rs->p, parity, sizeof(parity));
}
static void validateFecBlockState(PRTP_AUDIO_QUEUE queue) {
#ifdef LC_DEBUG
PRTPA_FEC_BLOCK lastBlock = queue->blockHead;
// The next sequence number must not be less than the oldest BSN unless we're still synchronizing with the source
LC_ASSERT(!isBefore16(queue->nextRtpSequenceNumber, queue->oldestRtpBaseSequenceNumber) || queue->synchronizing);
if (lastBlock == NULL) {
return;
}
uint16_t lastSeqNum = lastBlock->fecHeader.baseSequenceNumber;
uint32_t lastTs = lastBlock->fecHeader.baseTimestamp;
// The head should not have a previous entry
LC_ASSERT(lastBlock->prev == NULL);
// The next sequence number must not exceed the first FEC block (otherwise it should have been dequeued and freed)
LC_ASSERT(isBefore16(queue->nextRtpSequenceNumber, queue->blockHead->fecHeader.baseSequenceNumber + RTPA_DATA_SHARDS));
// The first FEC block should not be before the oldest BSN (or we will drop packets that belong in that FEC block).
LC_ASSERT(!isBefore16(queue->blockHead->fecHeader.baseSequenceNumber, queue->oldestRtpBaseSequenceNumber));
PRTPA_FEC_BLOCK block = lastBlock->next;
while (block != NULL) {
// Ensure the list is sorted correctly
LC_ASSERT(isBefore16(lastSeqNum, block->fecHeader.baseSequenceNumber));
LC_ASSERT_VT(isBefore32(lastTs, block->fecHeader.baseTimestamp));
// Ensure entry invariants are satisfied
LC_ASSERT_VT(block->blockSize == lastBlock->blockSize);
LC_ASSERT_VT(block->fecHeader.payloadType == lastBlock->fecHeader.payloadType);
LC_ASSERT_VT(block->fecHeader.ssrc == lastBlock->fecHeader.ssrc);
// Ensure the list itself is consistent
LC_ASSERT(block->prev == lastBlock);
LC_ASSERT(block->next != NULL || queue->blockTail == block);
lastBlock = block;
block = block->next;
}
#endif
}
static PRTPA_FEC_BLOCK allocateFecBlock(PRTP_AUDIO_QUEUE queue, uint16_t blockSize) {
PRTPA_FEC_BLOCK block = queue->freeBlockHead;
if (block != NULL) {
LC_ASSERT(queue->freeBlockCount > 0);
// If the block size matches, we're good to go
if (block->blockSize == blockSize) {
// Advance the free block list to the next entry
queue->freeBlockHead = block->next;
queue->freeBlockCount--;
// Return the new block
return block;
}
else {
// The block size didn't match. This should never happen with GFE
// because it uses constant sized data shards, but Sunshine can
// trigger this condition. If it does happen, let's free the cached
// entry so we can populate the cache with correctly sized blocks.
queue->freeBlockHead = block->next;
queue->freeBlockCount--;
// Free the existing block
free(block);
}
}
else {
LC_ASSERT(queue->freeBlockCount == 0);
}
// We either didn't have any free entries or the block
// size didn't match, so allocate a new FEC block now.
uint16_t dataPacketSize = blockSize + sizeof(RTP_PACKET);
return malloc(sizeof(*block) + (RTPA_DATA_SHARDS * dataPacketSize) + (RTPA_FEC_SHARDS * blockSize));
}
static void freeFecBlockHead(PRTP_AUDIO_QUEUE queue) {
PRTPA_FEC_BLOCK blockHead = queue->blockHead;
queue->blockHead = queue->blockHead->next;
if (queue->blockHead != NULL) {
queue->blockHead->prev = NULL;
}
else {
LC_ASSERT(queue->blockTail == blockHead);
queue->blockTail = NULL;
}
queue->oldestRtpBaseSequenceNumber = blockHead->fecHeader.baseSequenceNumber + RTPA_DATA_SHARDS;
// Once we complete an FEC block (successfully or not), we're synchronized with the source
queue->synchronizing = false;
validateFecBlockState(queue);
if (queue->freeBlockCount >= RTPA_CACHED_FEC_BLOCK_LIMIT) {
// Too many entries cached, so just free this one
free(blockHead);
}
else {
// Place this entry at the head of the free list for better cache behavior
blockHead->next = queue->freeBlockHead;
queue->freeBlockHead = blockHead;
queue->freeBlockCount++;
}
}
void RtpaCleanupQueue(PRTP_AUDIO_QUEUE queue) {
while (queue->blockHead != NULL) {
PRTPA_FEC_BLOCK block = queue->blockHead;
queue->blockHead = block->next;
free(block);
}
queue->blockTail = NULL;
while (queue->freeBlockHead != NULL) {
PRTPA_FEC_BLOCK block = queue->freeBlockHead;
queue->freeBlockHead = block->next;
queue->freeBlockCount--;
free(block);
}
LC_ASSERT(queue->freeBlockCount == 0);
reed_solomon_release(queue->rs);
queue->rs = NULL;
}
static PRTPA_FEC_BLOCK getFecBlockForRtpPacket(PRTP_AUDIO_QUEUE queue, PRTP_PACKET packet, uint16_t length) {
uint32_t fecBlockSsrc;
uint16_t fecBlockBaseSeqNum;
uint32_t fecBlockBaseTs;
uint16_t blockSize;
uint8_t fecBlockPayloadType;
validateFecBlockState(queue);
if (packet->packetType == RTP_PAYLOAD_TYPE_AUDIO) {
if (length < sizeof(RTP_PACKET)) {
queue->stats.packetCountInvalid++;
Limelog("RTP audio data packet too small: %u\n", length);
LC_ASSERT_VT(false);
return NULL;
}
queue->stats.packetCountAudio++;
// Remember if we've received out-of-sequence packets lately. We can use
// this knowledge to more quickly give up on FEC blocks.
if (!queue->synchronizing && isBefore16(packet->sequenceNumber, queue->oldestRtpBaseSequenceNumber)) {
queue->lastOosSequenceNumber = packet->sequenceNumber;
queue->stats.packetCountOOS++;
if (!queue->receivedOosData) {
Limelog("Leaving fast audio recovery mode after OOS audio data (%u < %u)\n",
packet->sequenceNumber, queue->oldestRtpBaseSequenceNumber);
queue->receivedOosData = true;
}
}
// This condition looks odd, but it's just a simple way to check if we've gone
// more than 32767 packets without an OOS packet.
else if (queue->receivedOosData && isBefore16(queue->oldestRtpBaseSequenceNumber, queue->lastOosSequenceNumber)) {
Limelog("Entering fast audio recovery mode after sequenced audio data\n");
queue->receivedOosData = false;
}
// This is a data packet, so we will need to synthesize an FEC header
fecBlockPayloadType = packet->packetType;
fecBlockBaseSeqNum = (packet->sequenceNumber / RTPA_DATA_SHARDS) * RTPA_DATA_SHARDS;
fecBlockBaseTs = packet->timestamp - ((packet->sequenceNumber - fecBlockBaseSeqNum) * AudioPacketDuration);
fecBlockSsrc = packet->ssrc;
blockSize = length - sizeof(RTP_PACKET);
}
else if (packet->packetType == RTP_PAYLOAD_TYPE_FEC) {
PAUDIO_FEC_HEADER fecHeader = (PAUDIO_FEC_HEADER)(packet + 1);
if (length < sizeof(RTP_PACKET) + sizeof(AUDIO_FEC_HEADER)) {
queue->stats.packetCountFecInvalid++;
Limelog("RTP audio FEC packet too small: %u\n", length);
LC_ASSERT_VT(false);
return NULL;
}
queue->stats.packetCountFec++;
// This is an FEC packet, so we can just copy (and byteswap) the FEC header
fecBlockPayloadType = fecHeader->payloadType;
fecBlockBaseSeqNum = BE16(fecHeader->baseSequenceNumber);
fecBlockBaseTs = BE32(fecHeader->baseTimestamp);
fecBlockSsrc = BE32(fecHeader->ssrc);
// Ensure the FEC shard index is valid to prevent OOB access
// later during recovery.
if (fecHeader->fecShardIndex >= RTPA_FEC_SHARDS) {
queue->stats.packetCountFecInvalid++;
Limelog("Too many audio FEC shards: %u\n", fecHeader->fecShardIndex);
LC_ASSERT_VT(false);
return NULL;
}
if (fecBlockBaseSeqNum % RTPA_DATA_SHARDS != 0) {
// The FEC blocks must start on a RTPA_DATA_SHARDS boundary for our queuing logic to work. This isn't
// the case for older versions of GeForce Experience (at least 3.13). Disable the FEC logic if this
// invariant is validated.
queue->stats.packetCountFecInvalid++;
Limelog("Invalid FEC block base sequence number (got %u, expected %u)\n",
fecBlockBaseSeqNum, (fecBlockBaseSeqNum / RTPA_DATA_SHARDS) * RTPA_DATA_SHARDS);
Limelog("Audio FEC has been disabled due to an incompatibility with your host's old software!\n");
LC_ASSERT_VT(fecBlockBaseSeqNum % RTPA_DATA_SHARDS == 0);
queue->incompatibleServer = true;
return NULL;
}
blockSize = length - sizeof(RTP_PACKET) - sizeof(AUDIO_FEC_HEADER);
}
else {
Limelog("Invalid RTP audio payload type: %u\n", packet->packetType);
LC_ASSERT_VT(false);
return NULL;
}
// Synchronize the nextRtpSequenceNumber and oldestRtpBaseSequenceNumber values
// when the connection begins. Start on the next FEC block boundary, so we can
// be sure we aren't starting in the middle (which will lead to a spurious audio
// data block recovery warning on connection start if we miss more than 2 packets).
if (queue->synchronizing && queue->oldestRtpBaseSequenceNumber == 0) {
queue->nextRtpSequenceNumber = queue->oldestRtpBaseSequenceNumber = fecBlockBaseSeqNum + RTPA_DATA_SHARDS;
return NULL;
}
// Drop packets from FEC blocks that have already been completed
if (isBefore16(fecBlockBaseSeqNum, queue->oldestRtpBaseSequenceNumber)) {
return NULL;
}
// Look for an existing FEC block
PRTPA_FEC_BLOCK existingBlock = queue->blockHead;
while (existingBlock != NULL) {
if (existingBlock->fecHeader.baseSequenceNumber == fecBlockBaseSeqNum) {
// The FEC header data should match for all packets
LC_ASSERT_VT(existingBlock->fecHeader.payloadType == fecBlockPayloadType);
LC_ASSERT_VT(existingBlock->fecHeader.baseTimestamp == fecBlockBaseTs);
LC_ASSERT_VT(existingBlock->fecHeader.ssrc == fecBlockSsrc);
// The block size must match in order to safely copy shards into it
if (existingBlock->blockSize != blockSize) {
// This can happen with older versions of GeForce Experience (3.13) and Sunshine that don't use a
// constant size for audio packets.
queue->stats.packetCountFecInvalid++;
Limelog("Audio block size mismatch (got %u, expected %u)\n", blockSize, existingBlock->blockSize);
Limelog("Audio FEC has been disabled due to an incompatibility with your host's old software!\n");
LC_ASSERT_VT(existingBlock->blockSize == blockSize);
queue->incompatibleServer = true;
return NULL;
}
// If the block is completed, don't return it
return existingBlock->fullyReassembled ? NULL : existingBlock;
}
else if (isBefore16(fecBlockBaseSeqNum, existingBlock->fecHeader.baseSequenceNumber)) {
// The new block goes right before this one
break;
}
existingBlock = existingBlock->next;
}
// We didn't find an existing FEC block, so we'll have to allocate one
uint16_t dataPacketSize = blockSize + sizeof(RTP_PACKET);
PRTPA_FEC_BLOCK block = allocateFecBlock(queue, blockSize);
if (block == NULL) {
return NULL;
}
memset(block, 0, sizeof(*block));
block->queueTimeUs = PltGetMicroseconds();
block->blockSize = blockSize;
memset(block->marks, 1, sizeof(block->marks));
// Set up the FEC header
block->fecHeader.payloadType = fecBlockPayloadType;
block->fecHeader.baseSequenceNumber = fecBlockBaseSeqNum;
block->fecHeader.baseTimestamp = fecBlockBaseTs;
block->fecHeader.ssrc = fecBlockSsrc;
// Set up packet buffers pointing into the slab we allocated
uint8_t* data = (uint8_t*)(block + 1);
for (int i = 0; i < RTPA_DATA_SHARDS; i++) {
block->dataPackets[i] = (PRTP_PACKET)data;
data += dataPacketSize;
}
for (int i = 0; i < RTPA_FEC_SHARDS; i++) {
block->fecPackets[i] = data;
data += blockSize;
}
// Place this block into the list in order
if (existingBlock != NULL) {
// This new block comes right before existingBlock
PRTPA_FEC_BLOCK prevBlock = existingBlock->prev;
existingBlock->prev = block;
if (prevBlock == NULL) {
LC_ASSERT(queue->blockHead == existingBlock);
queue->blockHead = block;
}
else {
prevBlock->next = block;
}
block->prev = prevBlock;
block->next = existingBlock;
}
else {
// This block goes at the tail of the list
block->prev = queue->blockTail;
if (queue->blockTail != NULL) {
queue->blockTail->next = block;
}
queue->blockTail = block;
if (queue->blockHead == NULL) {
queue->blockHead = block;
}
}
validateFecBlockState(queue);
return block;
}
static bool completeFecBlock(PRTP_AUDIO_QUEUE queue, PRTPA_FEC_BLOCK block) {
uint8_t* shards[RTPA_TOTAL_SHARDS];
// If we don't have enough shards, we can't do anything.
// FEC validation mode requires one additional shard.
#ifdef FEC_VALIDATION_MODE
if (block->dataShardsReceived + block->fecShardsReceived < RTPA_DATA_SHARDS + 1) {
#else
if (block->dataShardsReceived + block->fecShardsReceived < RTPA_DATA_SHARDS) {
#endif
return false;
}
// If we have all data shards, don't bother with any recovery
// unless we're in FEC validation mode
LC_ASSERT(block->dataShardsReceived <= RTPA_DATA_SHARDS);
#ifndef FEC_VALIDATION_MODE
if (block->dataShardsReceived == RTPA_DATA_SHARDS) {
return true;
}
#endif
// We have recovery to do. Let's build the array.
for (int i = 0; i < RTPA_DATA_SHARDS; i++) {
shards[i] = (uint8_t*)(block->dataPackets[i] + 1);
}
for (int i = 0; i < RTPA_FEC_SHARDS; i++) {
shards[RTPA_DATA_SHARDS + i] = block->fecPackets[i];
}
#ifdef FEC_VALIDATION_MODE
unsigned int dropIndex;
// Choose a successfully received packet to drop
do {
dropIndex = rand() % RTPA_DATA_SHARDS;
} while (block->marks[dropIndex]);
// Copy the original data to validate later
PRTP_PACKET droppedRtpPacket = malloc(sizeof(RTP_PACKET) + block->blockSize);
memcpy(droppedRtpPacket, block->dataPackets[dropIndex], sizeof(RTP_PACKET) + block->blockSize);
// Fake the drop by setting the mark bit and zeroing the "missing" packet
block->marks[dropIndex] = 1;
memset(block->dataPackets[dropIndex], 0, sizeof(RTP_PACKET) + block->blockSize);
#endif
int res = reed_solomon_decode(queue->rs, shards, block->marks, RTPA_TOTAL_SHARDS, block->blockSize);
if (res != 0) {
// We should always have enough data to recover the entire block since we checked above.
LC_ASSERT(res == 0);
return false;
}
// We will need to recover the RTP packet using the FEC header
for (int i = 0; i < RTPA_DATA_SHARDS; i++) {
if (block->marks[i]) {
block->dataPackets[i]->header = 0x80; // RTPv2
block->dataPackets[i]->packetType = block->fecHeader.payloadType;
block->dataPackets[i]->sequenceNumber = block->fecHeader.baseSequenceNumber + i;
block->dataPackets[i]->timestamp = block->fecHeader.baseTimestamp + (i * AudioPacketDuration);
block->dataPackets[i]->ssrc = block->fecHeader.ssrc;
block->marks[i] = 0;
}
}
if (block->dataShardsReceived != RTPA_DATA_SHARDS) {
queue->stats.packetCountFecRecovered += RTPA_DATA_SHARDS - block->dataShardsReceived;
#ifdef FEC_VERBOSE
Limelog("Recovered %d audio data shards from block %d\n",
RTPA_DATA_SHARDS - block->dataShardsReceived,
block->fecHeader.baseSequenceNumber);
#endif
}
#ifdef FEC_VALIDATION_MODE
// Check the RTP header values
LC_ASSERT_VT(block->dataPackets[dropIndex]->header == droppedRtpPacket->header);
LC_ASSERT_VT(block->dataPackets[dropIndex]->packetType == droppedRtpPacket->packetType);
LC_ASSERT_VT(block->dataPackets[dropIndex]->sequenceNumber == droppedRtpPacket->sequenceNumber);
LC_ASSERT_VT(block->dataPackets[dropIndex]->timestamp == droppedRtpPacket->timestamp);
LC_ASSERT_VT(block->dataPackets[dropIndex]->ssrc == droppedRtpPacket->ssrc);
// Check the data itself - use memcmp() and only loop if an error is detected
if (memcmp(block->dataPackets[dropIndex] + 1, droppedRtpPacket + 1, block->blockSize)) {
unsigned char* actualData = (unsigned char*)(block->dataPackets[dropIndex] + 1);
unsigned char* expectedData = (unsigned char*)(droppedRtpPacket + 1);
int recoveryErrors = 0;
for (int j = 0; j < block->blockSize; j++) {
if (actualData[j] != expectedData[j]) {
Limelog("Recovery error at %d: expected 0x%02x, actual 0x%02x\n",
j, expectedData[j], actualData[j]);
recoveryErrors++;
}
}
LC_ASSERT_VT(recoveryErrors == 0);
}
free(droppedRtpPacket);
#endif
return true;
}
static bool queueHasPacketReady(PRTP_AUDIO_QUEUE queue) {
validateFecBlockState(queue);
return queue->blockHead != NULL &&
((queue->blockHead->marks[queue->blockHead->nextDataPacketIndex] == 0 &&
queue->blockHead->fecHeader.baseSequenceNumber + queue->blockHead->nextDataPacketIndex == queue->nextRtpSequenceNumber)
|| queue->blockHead->allowDiscontinuity);
}
static void handleMissingPackets(PRTP_AUDIO_QUEUE queue) {
// Nothing to do for an empty queue
if (queue->blockHead == NULL) {
return;
}
// If the packet we're waiting on precedes our earliest FEC block, a previous FEC block was completely lost.
// We should resynchronize immediately by advancing the queue state to play our oldest block next.
//
// NB: We do NOT want to set allowDiscontinuity here, because that will result in playing back the entire
// FEC block immediately but we've only received a single packet from that block. Worse still, when the
// remaining packets from this block arrive, they will trigger the OOS detection and kick us out of fast
// audio recovery mode.
if (isBefore16(queue->nextRtpSequenceNumber, queue->blockHead->fecHeader.baseSequenceNumber)) {
queue->nextRtpSequenceNumber = queue->blockHead->fecHeader.baseSequenceNumber;
queue->oldestRtpBaseSequenceNumber = queue->blockHead->fecHeader.baseSequenceNumber;
return;
}
// If we reach this point, we know the next packet resides in the first FEC block we're
// currently waiting on. In that case, we want to wait at least until we have a second FEC
// block to give up on the first one. If we don't have a second block now, just keep waiting.
LC_ASSERT_VT(isBefore16(queue->nextRtpSequenceNumber, queue->blockHead->fecHeader.baseSequenceNumber + RTPA_DATA_SHARDS));
if (queue->blockHead == queue->blockTail) {
return;
}
// At this point, we know we've got a second FEC block queued up waiting on the first one to complete.
// If we've never seen OOS data from this host, we'll assume the first one is lost and skip forward.
// If we have seen OOS data, we'll wait for a little while longer to see if OOS packets arrive before giving up.
if (!queue->receivedOosData || PltGetMicroseconds() - queue->blockHead->queueTimeUs > (uint64_t)(AudioPacketDuration * RTPA_DATA_SHARDS) + (RTPQ_OOS_WAIT_TIME_MS * 1000)) {
LC_ASSERT(!isBefore16(queue->nextRtpSequenceNumber, queue->blockHead->fecHeader.baseSequenceNumber));
queue->stats.packetCountFecFailed++;
Limelog("Unable to recover audio data block %u to %u (%u+%u=%u received < %u needed)\n",
queue->blockHead->fecHeader.baseSequenceNumber,
queue->blockHead->fecHeader.baseSequenceNumber + RTPA_DATA_SHARDS - 1,
queue->blockHead->dataShardsReceived,
queue->blockHead->fecShardsReceived,
queue->blockHead->dataShardsReceived + queue->blockHead->fecShardsReceived,
RTPA_DATA_SHARDS);
// Return all available audio data even if there are discontinuities
queue->blockHead->allowDiscontinuity = true;
LC_ASSERT(queueHasPacketReady(queue));
}
}
int RtpaAddPacket(PRTP_AUDIO_QUEUE queue, PRTP_PACKET packet, uint16_t length) {
if (queue->incompatibleServer) {
// Just feed audio data straight through to the decoder. We lose handling of out-of-order
// and duplicated packets in this mode, but it shouldn't be a problem for the very small
// portion of users that are running an ancient GFE or Sunshine version.
if (packet->packetType == RTP_PAYLOAD_TYPE_AUDIO) {
return RTPQ_RET_HANDLE_NOW;
}
else {
return 0;
}
}
PRTPA_FEC_BLOCK fecBlock = getFecBlockForRtpPacket(queue, packet, length);
if (fecBlock == NULL) {
// Reject the packet
return 0;
}
if (packet->packetType == RTP_PAYLOAD_TYPE_AUDIO) {
uint16_t pos = packet->sequenceNumber - fecBlock->fecHeader.baseSequenceNumber;
// This is validated in getFecBlockForRtpPacket()
LC_ASSERT(pos < RTPA_DATA_SHARDS);
if (fecBlock->marks[pos]) {
// If there was a missing data shard, copy the RTP header and packet data into it
memcpy(fecBlock->dataPackets[pos], packet, length);
fecBlock->marks[pos] = 0;
fecBlock->dataShardsReceived++;
}
else {
// This is a duplicate packet - reject it
return 0;
}
// This is the common case - an in-order receive of the next data shard.
// We handle this quickly by telling the caller to immediately consume it.
if (packet->sequenceNumber == queue->nextRtpSequenceNumber) {
queue->nextRtpSequenceNumber = packet->sequenceNumber + 1;
// We are going to return this entry, so update the FEC block
// state to indicate that the caller has already received it.
fecBlock->nextDataPacketIndex++;
// If we've returned all packets in this FEC block, free it.
if (queue->nextRtpSequenceNumber == U16(fecBlock->fecHeader.baseSequenceNumber + RTPA_DATA_SHARDS)) {
LC_ASSERT(fecBlock == queue->blockHead);
LC_ASSERT(fecBlock->nextDataPacketIndex == RTPA_DATA_SHARDS);
freeFecBlockHead(queue);
}
else {
validateFecBlockState(queue);
}
return RTPQ_RET_HANDLE_NOW;
}
}
else if (packet->packetType == RTP_PAYLOAD_TYPE_FEC) {
PAUDIO_FEC_HEADER fecHeader = (PAUDIO_FEC_HEADER)(packet + 1);
// This is validated in getFecBlockForRtpPacket()
LC_ASSERT(fecHeader->fecShardIndex < RTPA_FEC_SHARDS);
if (fecBlock->marks[RTPA_DATA_SHARDS + fecHeader->fecShardIndex]) {
// If there was a missing FEC shard, copy just the FEC data into it
memcpy(fecBlock->fecPackets[fecHeader->fecShardIndex], fecHeader + 1, length - sizeof(RTP_PACKET) - sizeof(AUDIO_FEC_HEADER));
fecBlock->marks[RTPA_DATA_SHARDS + fecHeader->fecShardIndex] = 0;
fecBlock->fecShardsReceived++;
}
else {
// This is a duplicate packet - reject it
return 0;
}
}
else {
// getFecBlockForRtpPacket() would have already failed
LC_ASSERT(false);
return 0;
}
// Try to complete the FEC block via data shards or data+FEC shards
LC_ASSERT(fecBlock == queue->blockHead || queue->blockHead != queue->blockTail);
if (completeFecBlock(queue, fecBlock)) {
// We completed a FEC block
fecBlock->fullyReassembled = true;
}
// If we still have nothing ready, see if we should skip the missing packets.
if (!queueHasPacketReady(queue)) {
handleMissingPackets(queue);
}
return queueHasPacketReady(queue) ? RTPQ_RET_PACKET_READY : 0;
}
PRTP_PACKET RtpaGetQueuedPacket(PRTP_AUDIO_QUEUE queue, uint16_t customHeaderLength, uint16_t* length) {
validateFecBlockState(queue);
// If we're returning audio data even with discontinuities, we'll fill in blank entries
// for packets that were lost and could not be recovered.
if (queue->blockHead != NULL && queue->blockHead->allowDiscontinuity) {
PRTPA_FEC_BLOCK nextBlock = queue->blockHead;
PRTP_PACKET lostPacket;
LC_ASSERT(nextBlock->fecHeader.baseSequenceNumber + nextBlock->nextDataPacketIndex == queue->nextRtpSequenceNumber);
if (nextBlock->marks[nextBlock->nextDataPacketIndex]) {
// This packet is missing. Return an empty entry to let the caller
// know to perform packet loss concealment for this frame.
lostPacket = malloc(customHeaderLength);
if (lostPacket == NULL) {
return NULL;
}
// Lost packet placeholder entries have no associated data
*length = 0;
// Move on to the next data shard
nextBlock->nextDataPacketIndex++;
queue->nextRtpSequenceNumber++;
}
else {
lostPacket = NULL;
LC_ASSERT(queueHasPacketReady(queue));
}
// If we've read everything from this FEC block, remove and free it
if (nextBlock->nextDataPacketIndex == RTPA_DATA_SHARDS) {
freeFecBlockHead(queue);
}
else {
validateFecBlockState(queue);
}
if (lostPacket != NULL) {
return lostPacket;
}
}
// Return the next RTP sequence number by indexing into the most recent FEC block
if (queueHasPacketReady(queue)) {
PRTPA_FEC_BLOCK nextBlock = queue->blockHead;
PRTP_PACKET packet = malloc(customHeaderLength + sizeof(RTP_PACKET) + nextBlock->blockSize);
if (packet == NULL) {
return NULL;
}
*length = nextBlock->blockSize + sizeof(RTP_PACKET);
memcpy((uint8_t*)packet + customHeaderLength, nextBlock->dataPackets[nextBlock->nextDataPacketIndex], *length);
nextBlock->nextDataPacketIndex++;
queue->nextRtpSequenceNumber++;
// If we've read everything from this FEC block, remove and free it
if (nextBlock->nextDataPacketIndex == RTPA_DATA_SHARDS) {
freeFecBlockHead(queue);
}
else {
validateFecBlockState(queue);
}
return packet;
}
return NULL;
}
-88
View File
@@ -1,88 +0,0 @@
#pragma once
#include "Video.h"
#include "rswrapper.h"
typedef struct _reed_solomon {
int ds;
int ps;
int ts;
uint8_t p[];
} reed_solomon;
// Maximum time to wait for an OOS data/FEC shard
// after the entire FEC block should have been received
#define RTPQ_OOS_WAIT_TIME_MS 10
#define RTPA_DATA_SHARDS 4
#define RTPA_FEC_SHARDS 2
#define RTPA_TOTAL_SHARDS (RTPA_DATA_SHARDS + RTPA_FEC_SHARDS)
// Maximum number of FEC block entries to cache
#define RTPA_CACHED_FEC_BLOCK_LIMIT 4
typedef struct _AUDIO_FEC_HEADER {
uint8_t fecShardIndex;
uint8_t payloadType;
uint16_t baseSequenceNumber;
uint32_t baseTimestamp;
uint32_t ssrc;
} AUDIO_FEC_HEADER, *PAUDIO_FEC_HEADER;
typedef struct _RTPA_FEC_BLOCK {
struct _RTPA_FEC_BLOCK* prev;
struct _RTPA_FEC_BLOCK* next;
PRTP_PACKET dataPackets[RTPA_DATA_SHARDS];
uint8_t* fecPackets[RTPA_FEC_SHARDS];
uint8_t marks[RTPA_TOTAL_SHARDS];
AUDIO_FEC_HEADER fecHeader;
uint64_t queueTimeUs;
uint8_t dataShardsReceived;
uint8_t fecShardsReceived;
bool fullyReassembled;
// Used when dequeuing data from FEC blocks for the caller
uint8_t nextDataPacketIndex;
bool allowDiscontinuity;
uint16_t blockSize;
// Data for shards comes here
} RTPA_FEC_BLOCK, *PRTPA_FEC_BLOCK;
typedef struct _RTP_AUDIO_QUEUE {
PRTPA_FEC_BLOCK blockHead;
PRTPA_FEC_BLOCK blockTail;
reed_solomon* rs;
PRTPA_FEC_BLOCK freeBlockHead;
uint16_t freeBlockCount;
uint16_t nextRtpSequenceNumber;
uint16_t oldestRtpBaseSequenceNumber;
uint16_t lastOosSequenceNumber;
bool receivedOosData;
bool synchronizing;
bool incompatibleServer;
RTP_AUDIO_STATS stats;
} RTP_AUDIO_QUEUE, *PRTP_AUDIO_QUEUE;
#define RTPQ_RET_PACKET_CONSUMED 0x1
#define RTPQ_RET_PACKET_READY 0x2
#define RTPQ_RET_HANDLE_NOW 0x4
#define RTPQ_PACKET_CONSUMED(x) ((x) & RTPQ_RET_PACKET_CONSUMED)
#define RTPQ_PACKET_READY(x) ((x) & RTPQ_RET_PACKET_READY)
#define RTPQ_HANDLE_NOW(x) ((x) == RTPQ_RET_HANDLE_NOW)
void RtpaInitializeQueue(PRTP_AUDIO_QUEUE queue);
void RtpaCleanupQueue(PRTP_AUDIO_QUEUE queue);
int RtpaAddPacket(PRTP_AUDIO_QUEUE queue, PRTP_PACKET packet, uint16_t length);
PRTP_PACKET RtpaGetQueuedPacket(PRTP_AUDIO_QUEUE queue, uint16_t customHeaderLength, uint16_t* length);
+493
View File
@@ -0,0 +1,493 @@
#include "Limelight-internal.h"
#include "RtpFecQueue.h"
#include "rs.h"
#ifdef LC_DEBUG
// This enables FEC validation mode with a synthetic drop
// and recovered packet checks vs the original input. It
// is on by default for debug builds.
#define FEC_VALIDATION_MODE
#endif
void RtpfInitializeQueue(PRTP_FEC_QUEUE queue) {
reed_solomon_init();
memset(queue, 0, sizeof(*queue));
queue->currentFrameNumber = UINT16_MAX;
}
void RtpfCleanupQueue(PRTP_FEC_QUEUE queue) {
while (queue->bufferHead != NULL) {
PRTPFEC_QUEUE_ENTRY entry = queue->bufferHead;
queue->bufferHead = entry->next;
free(entry->packet);
}
}
// newEntry is contained within the packet buffer so we free the whole entry by freeing entry->packet
static bool queuePacket(PRTP_FEC_QUEUE queue, PRTPFEC_QUEUE_ENTRY newEntry, bool head, PRTP_PACKET packet, int length, bool isParity) {
PRTPFEC_QUEUE_ENTRY entry;
LC_ASSERT(!isBefore16(packet->sequenceNumber, queue->nextContiguousSequenceNumber));
// If the packet is in order, we can take the fast path and avoid having
// to loop through the whole list. If we get an out of order or missing
// packet, the fast path will stop working and we'll use the loop instead.
if (packet->sequenceNumber == queue->nextContiguousSequenceNumber) {
queue->nextContiguousSequenceNumber = U16(packet->sequenceNumber + 1);
}
else {
// Check for duplicates
entry = queue->bufferHead;
while (entry != NULL) {
if (entry->packet->sequenceNumber == packet->sequenceNumber) {
return false;
}
entry = entry->next;
}
}
newEntry->packet = packet;
newEntry->length = length;
newEntry->isParity = isParity;
newEntry->prev = NULL;
newEntry->next = NULL;
// 90 KHz video clock
newEntry->presentationTimeMs = packet->timestamp / 90;
if (queue->bufferHead == NULL) {
LC_ASSERT(queue->bufferSize == 0);
queue->bufferHead = queue->bufferTail = newEntry;
}
else if (head) {
LC_ASSERT(queue->bufferSize > 0);
PRTPFEC_QUEUE_ENTRY oldHead = queue->bufferHead;
newEntry->next = oldHead;
LC_ASSERT(oldHead->prev == NULL);
oldHead->prev = newEntry;
queue->bufferHead = newEntry;
}
else {
LC_ASSERT(queue->bufferSize > 0);
PRTPFEC_QUEUE_ENTRY oldTail = queue->bufferTail;
newEntry->prev = oldTail;
LC_ASSERT(oldTail->next == NULL);
oldTail->next = newEntry;
queue->bufferTail = newEntry;
}
queue->bufferSize++;
return true;
}
#define PACKET_RECOVERY_FAILURE() \
ret = -1; \
Limelog("FEC recovery returned corrupt packet %d" \
" (frame %d)", rtpPacket->sequenceNumber, \
queue->currentFrameNumber); \
free(packets[i]); \
continue
// Returns 0 if the frame is completely constructed
static int reconstructFrame(PRTP_FEC_QUEUE queue) {
unsigned int totalPackets = U16(queue->bufferHighestSequenceNumber - queue->bufferLowestSequenceNumber) + 1;
int ret;
#ifdef FEC_VALIDATION_MODE
// We'll need an extra packet to run in FEC validation mode, because we will
// be "dropping" one below and recovering it using parity. However, some frames
// are so large that FEC is disabled entirely, so don't wait for parity on those.
if (queue->bufferSize < queue->bufferDataPackets + (queue->fecPercentage ? 1 : 0)) {
#else
if (queue->bufferSize < queue->bufferDataPackets) {
#endif
// Not enough data to recover yet
return -1;
}
#ifdef FEC_VALIDATION_MODE
// If FEC is disabled or unsupported for this frame, we must bail early here.
if ((queue->fecPercentage == 0 || AppVersionQuad[0] < 5) &&
queue->receivedBufferDataPackets == queue->bufferDataPackets) {
#else
if (queue->receivedBufferDataPackets == queue->bufferDataPackets) {
#endif
// We've received a full frame with no need for FEC.
return 0;
}
if (AppVersionQuad[0] < 5) {
// Our FEC recovery code doesn't work properly until Gen 5
Limelog("FEC recovery not supported on Gen %d servers\n",
AppVersionQuad[0]);
return -1;
}
reed_solomon* rs = NULL;
unsigned char** packets = calloc(totalPackets, sizeof(unsigned char*));
unsigned char* marks = calloc(totalPackets, sizeof(unsigned char));
if (packets == NULL || marks == NULL) {
ret = -2;
goto cleanup;
}
rs = reed_solomon_new(queue->bufferDataPackets, queue->bufferParityPackets);
// This could happen in an OOM condition, but it could also mean the FEC data
// that we fed to reed_solomon_new() is bogus, so we'll assert to get a better look.
LC_ASSERT(rs != NULL);
if (rs == NULL) {
ret = -3;
goto cleanup;
}
memset(marks, 1, sizeof(char) * (totalPackets));
int receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
int packetBufferSize = receiveSize + sizeof(RTPFEC_QUEUE_ENTRY);
#ifdef FEC_VALIDATION_MODE
// Choose a packet to drop
unsigned int dropIndex = rand() % queue->bufferDataPackets;
PRTP_PACKET droppedRtpPacket = NULL;
int droppedRtpPacketLength = 0;
#endif
PRTPFEC_QUEUE_ENTRY entry = queue->bufferHead;
while (entry != NULL) {
unsigned int index = U16(entry->packet->sequenceNumber - queue->bufferLowestSequenceNumber);
#ifdef FEC_VALIDATION_MODE
if (index == dropIndex) {
// If this was the drop choice, remember the original contents
// and "drop" it.
droppedRtpPacket = entry->packet;
droppedRtpPacketLength = entry->length;
entry = entry->next;
continue;
}
#endif
packets[index] = (unsigned char*) entry->packet;
marks[index] = 0;
//Set padding to zero
if (entry->length < receiveSize) {
memset(&packets[index][entry->length], 0, receiveSize - entry->length);
}
entry = entry->next;
}
unsigned int i;
for (i = 0; i < totalPackets; i++) {
if (marks[i]) {
packets[i] = malloc(packetBufferSize);
if (packets[i] == NULL) {
ret = -4;
goto cleanup_packets;
}
}
}
ret = reed_solomon_reconstruct(rs, packets, marks, totalPackets, receiveSize);
// We should always provide enough parity to recover the missing data successfully.
// If this fails, something is probably wrong with our FEC state.
LC_ASSERT(ret == 0);
cleanup_packets:
for (i = 0; i < totalPackets; i++) {
if (marks[i]) {
// Only submit frame data, not FEC packets
if (ret == 0 && i < queue->bufferDataPackets) {
PRTPFEC_QUEUE_ENTRY queueEntry = (PRTPFEC_QUEUE_ENTRY)&packets[i][receiveSize];
PRTP_PACKET rtpPacket = (PRTP_PACKET) packets[i];
rtpPacket->sequenceNumber = U16(i + queue->bufferLowestSequenceNumber);
rtpPacket->header = queue->bufferHead->packet->header;
rtpPacket->timestamp = queue->bufferHead->packet->timestamp;
rtpPacket->ssrc = queue->bufferHead->packet->ssrc;
int dataOffset = sizeof(*rtpPacket);
if (rtpPacket->header & FLAG_EXTENSION) {
dataOffset += 4; // 2 additional fields
}
PNV_VIDEO_PACKET nvPacket = (PNV_VIDEO_PACKET)(((char*)rtpPacket) + dataOffset);
nvPacket->frameIndex = queue->currentFrameNumber;
#ifdef FEC_VALIDATION_MODE
if (i == dropIndex && droppedRtpPacket != NULL) {
// Check the packet contents if this was our known drop
PNV_VIDEO_PACKET droppedNvPacket = (PNV_VIDEO_PACKET)(((char*)droppedRtpPacket) + dataOffset);
int droppedDataLength = droppedRtpPacketLength - dataOffset - sizeof(*nvPacket);
int recoveredDataLength = StreamConfig.packetSize - sizeof(*nvPacket);
int j;
int recoveryErrors = 0;
LC_ASSERT(droppedDataLength <= recoveredDataLength);
LC_ASSERT(droppedDataLength == recoveredDataLength || (nvPacket->flags & FLAG_EOF));
// Check all NV_VIDEO_PACKET fields except fecInfo which differs in the recovered packet
LC_ASSERT(nvPacket->flags == droppedNvPacket->flags);
LC_ASSERT(nvPacket->frameIndex == droppedNvPacket->frameIndex);
LC_ASSERT(nvPacket->streamPacketIndex == droppedNvPacket->streamPacketIndex);
// TODO: Investigate assertion failure here with GFE 3.20.4. The remaining fields and
// video data are still recovered successfully, so this doesn't seem critical.
//LC_ASSERT(memcmp(nvPacket->reserved, droppedNvPacket->reserved, sizeof(nvPacket->reserved)) == 0);
// Check the data itself - use memcmp() and only loop if an error is detected
if (memcmp(nvPacket + 1, droppedNvPacket + 1, droppedDataLength)) {
unsigned char* actualData = (unsigned char*)(nvPacket + 1);
unsigned char* expectedData = (unsigned char*)(droppedNvPacket + 1);
for (j = 0; j < droppedDataLength; j++) {
if (actualData[j] != expectedData[j]) {
Limelog("Recovery error at %d: expected 0x%02x, actual 0x%02x\n",
j, expectedData[j], actualData[j]);
recoveryErrors++;
}
}
}
// If this packet is at the end of the frame, the remaining data should be zeros.
for (j = droppedDataLength; j < recoveredDataLength; j++) {
unsigned char* actualData = (unsigned char*)(nvPacket + 1);
if (actualData[j] != 0) {
Limelog("Recovery error at %d: expected 0x00, actual 0x%02x\n",
j, actualData[j]);
recoveryErrors++;
}
}
LC_ASSERT(recoveryErrors == 0);
// This drop was fake, so we don't want to actually submit it to the depacketizer.
// It will get confused because it's already seen this packet before.
free(packets[i]);
continue;
}
#endif
// Do some rudamentary checks to see that the recovered packet is sane.
// In some cases (4K 30 FPS 80 Mbps), we seem to get some odd failures
// here in rare cases where FEC recovery is required. I'm unsure if it
// is our bug, NVIDIA's, or something else, but we don't want the corrupt
// packet to by ingested by our depacketizer (or worse, the decoder).
if (i == 0 && !(nvPacket->flags & FLAG_SOF)) {
PACKET_RECOVERY_FAILURE();
}
if (i == queue->bufferDataPackets - 1 && !(nvPacket->flags & FLAG_EOF)) {
PACKET_RECOVERY_FAILURE();
}
if (i > 0 && i < queue->bufferDataPackets - 1 && !(nvPacket->flags & FLAG_CONTAINS_PIC_DATA)) {
PACKET_RECOVERY_FAILURE();
}
if (nvPacket->flags & ~(FLAG_SOF | FLAG_EOF | FLAG_CONTAINS_PIC_DATA)) {
PACKET_RECOVERY_FAILURE();
}
// FEC recovered frames may have extra zero padding at the end. This is
// fine per H.264 Annex B which states trailing zero bytes must be
// discarded by decoders. It's not safe to strip all zero padding because
// it may be a legitimate part of the H.264 bytestream.
LC_ASSERT(isBefore16(rtpPacket->sequenceNumber, queue->bufferFirstParitySequenceNumber));
queuePacket(queue, queueEntry, false, rtpPacket, StreamConfig.packetSize + dataOffset, false);
} else if (packets[i] != NULL) {
free(packets[i]);
}
}
}
cleanup:
reed_solomon_release(rs);
if (packets != NULL)
free(packets);
if (marks != NULL)
free(marks);
return ret;
}
static void removeEntry(PRTP_FEC_QUEUE queue, PRTPFEC_QUEUE_ENTRY entry) {
LC_ASSERT(entry != NULL);
LC_ASSERT(queue->bufferSize > 0);
LC_ASSERT(queue->bufferHead != NULL);
LC_ASSERT(queue->bufferTail != NULL);
if (queue->bufferHead == entry) {
queue->bufferHead = entry->next;
}
if (queue->bufferTail == entry) {
queue->bufferTail = entry->prev;
}
if (entry->prev != NULL) {
entry->prev->next = entry->next;
}
if (entry->next != NULL) {
entry->next->prev = entry->prev;
}
queue->bufferSize--;
}
static void submitCompletedFrame(PRTP_FEC_QUEUE queue) {
unsigned int nextSeqNum = queue->bufferLowestSequenceNumber;
while (queue->bufferSize > 0) {
PRTPFEC_QUEUE_ENTRY entry = queue->bufferHead;
unsigned int lowestRtpSequenceNumber = entry->packet->sequenceNumber;
while (entry != NULL) {
// We should never encounter a packet that's lower than our next seq num
LC_ASSERT(!isBefore16(entry->packet->sequenceNumber, nextSeqNum));
// Never return parity packets
if (entry->isParity) {
PRTPFEC_QUEUE_ENTRY parityEntry = entry;
// Skip this entry
entry = parityEntry->next;
// Remove this entry
removeEntry(queue, parityEntry);
// Free the entry and packet
free(parityEntry->packet);
continue;
}
// Check for the next packet in sequence. This will be O(1) for non-reordered packet streams.
if (entry->packet->sequenceNumber == nextSeqNum) {
removeEntry(queue, entry);
entry->prev = entry->next = NULL;
// To avoid having to sample the system time for each packet, we cheat
// and use the first packet's receive time for all packets. This ends up
// actually being better for the measurements that the depacketizer does,
// since it properly handles out of order packets.
LC_ASSERT(queue->bufferFirstRecvTimeMs != 0);
entry->receiveTimeMs = queue->bufferFirstRecvTimeMs;
// Submit this packet for decoding. It will own freeing the entry now.
queueRtpPacket(entry);
break;
}
else if (isBefore16(entry->packet->sequenceNumber, lowestRtpSequenceNumber)) {
lowestRtpSequenceNumber = entry->packet->sequenceNumber;
}
entry = entry->next;
}
if (entry == NULL) {
// Start at the lowest we found last enumeration
nextSeqNum = lowestRtpSequenceNumber;
}
else {
// We found this packet so move on to the next one in sequence
nextSeqNum = U16(nextSeqNum + 1);
}
}
}
int RtpfAddPacket(PRTP_FEC_QUEUE queue, PRTP_PACKET packet, int length, PRTPFEC_QUEUE_ENTRY packetEntry) {
if (isBefore16(packet->sequenceNumber, queue->nextContiguousSequenceNumber)) {
// Reject packets behind our current buffer window
return RTPF_RET_REJECTED;
}
// FLAG_EXTENSION is required for all supported versions of GFE.
LC_ASSERT(packet->header & FLAG_EXTENSION);
int dataOffset = sizeof(*packet);
if (packet->header & FLAG_EXTENSION) {
dataOffset += 4; // 2 additional fields
}
PNV_VIDEO_PACKET nvPacket = (PNV_VIDEO_PACKET)(((char*)packet) + dataOffset);
if (isBefore16(nvPacket->frameIndex, queue->currentFrameNumber)) {
// Reject frames behind our current frame number
return RTPF_RET_REJECTED;
}
int fecIndex = (nvPacket->fecInfo & 0x3FF000) >> 12;
// Reinitialize the queue if it's empty after a frame delivery or
// if we can't finish a frame before receiving the next one.
if (queue->bufferSize == 0 || queue->currentFrameNumber != nvPacket->frameIndex) {
if (queue->currentFrameNumber != nvPacket->frameIndex && queue->bufferSize != 0) {
Limelog("Unrecoverable frame %d: %d+%d=%d received < %d needed\n",
queue->currentFrameNumber, queue->receivedBufferDataPackets,
queue->bufferSize - queue->receivedBufferDataPackets,
queue->bufferSize,
queue->bufferDataPackets);
}
queue->currentFrameNumber = nvPacket->frameIndex;
// Discard any unsubmitted buffers from the previous frame
while (queue->bufferHead != NULL) {
PRTPFEC_QUEUE_ENTRY entry = queue->bufferHead;
queue->bufferHead = entry->next;
free(entry->packet);
}
queue->bufferTail = NULL;
queue->bufferSize = 0;
queue->bufferFirstRecvTimeMs = PltGetMillis();
queue->bufferLowestSequenceNumber = U16(packet->sequenceNumber - fecIndex);
queue->nextContiguousSequenceNumber = queue->bufferLowestSequenceNumber;
queue->receivedBufferDataPackets = 0;
queue->bufferDataPackets = (nvPacket->fecInfo & 0xFFC00000) >> 22;
queue->fecPercentage = (nvPacket->fecInfo & 0xFF0) >> 4;
queue->bufferParityPackets = (queue->bufferDataPackets * queue->fecPercentage + 99) / 100;
queue->bufferFirstParitySequenceNumber = U16(queue->bufferLowestSequenceNumber + queue->bufferDataPackets);
queue->bufferHighestSequenceNumber = U16(queue->bufferFirstParitySequenceNumber + queue->bufferParityPackets - 1);
} else if (isBefore16(queue->bufferHighestSequenceNumber, packet->sequenceNumber)) {
// In rare cases, we get extra parity packets. It's rare enough that it's probably
// not worth handling, so we'll just drop them.
return RTPF_RET_REJECTED;
}
LC_ASSERT(!queue->fecPercentage || U16(packet->sequenceNumber - fecIndex) == queue->bufferLowestSequenceNumber);
LC_ASSERT((nvPacket->fecInfo & 0xFF0) >> 4 == queue->fecPercentage);
LC_ASSERT((nvPacket->fecInfo & 0xFFC00000) >> 22 == queue->bufferDataPackets);
LC_ASSERT((nvPacket->flags & FLAG_EOF) || length - dataOffset == StreamConfig.packetSize);
if (!queuePacket(queue, packetEntry, false, packet, length, !isBefore16(packet->sequenceNumber, queue->bufferFirstParitySequenceNumber))) {
return RTPF_RET_REJECTED;
}
else {
if (isBefore16(packet->sequenceNumber, queue->bufferFirstParitySequenceNumber)) {
queue->receivedBufferDataPackets++;
}
// Try to submit this frame. If we haven't received enough packets,
// this will fail and we'll keep waiting.
if (reconstructFrame(queue) == 0) {
// Submit the frame data to the depacketizer
submitCompletedFrame(queue);
// submitCompletedFrame() should have consumed all data
LC_ASSERT(queue->bufferHead == NULL);
LC_ASSERT(queue->bufferTail == NULL);
LC_ASSERT(queue->bufferSize == 0);
// Ignore any more packets for this frame
queue->currentFrameNumber++;
}
return RTPF_RET_QUEUED;
}
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "Video.h"
typedef struct _RTPFEC_QUEUE_ENTRY {
PRTP_PACKET packet;
int length;
bool isParity;
uint64_t receiveTimeMs;
uint32_t presentationTimeMs;
struct _RTPFEC_QUEUE_ENTRY* next;
struct _RTPFEC_QUEUE_ENTRY* prev;
} RTPFEC_QUEUE_ENTRY, *PRTPFEC_QUEUE_ENTRY;
typedef struct _RTP_FEC_QUEUE {
PRTPFEC_QUEUE_ENTRY bufferHead;
PRTPFEC_QUEUE_ENTRY bufferTail;
uint64_t bufferFirstRecvTimeMs;
uint32_t bufferSize;
uint32_t bufferLowestSequenceNumber;
uint32_t bufferHighestSequenceNumber;
uint32_t bufferFirstParitySequenceNumber;
uint32_t bufferDataPackets;
uint32_t bufferParityPackets;
uint32_t receivedBufferDataPackets;
uint32_t fecPercentage;
uint32_t nextContiguousSequenceNumber;
uint32_t currentFrameNumber;
} RTP_FEC_QUEUE, *PRTP_FEC_QUEUE;
#define RTPF_RET_QUEUED 0
#define RTPF_RET_REJECTED 1
void RtpfInitializeQueue(PRTP_FEC_QUEUE queue);
void RtpfCleanupQueue(PRTP_FEC_QUEUE queue);
int RtpfAddPacket(PRTP_FEC_QUEUE queue, PRTP_PACKET packet, int length, PRTPFEC_QUEUE_ENTRY packetEntry);
void RtpfSubmitQueuedPackets(PRTP_FEC_QUEUE queue);
+256
View File
@@ -0,0 +1,256 @@
#include "Limelight-internal.h"
#include "RtpReorderQueue.h"
void RtpqInitializeQueue(PRTP_REORDER_QUEUE queue, int maxSize, int maxQueueTimeMs) {
memset(queue, 0, sizeof(*queue));
queue->maxSize = maxSize;
queue->maxQueueTimeMs = maxQueueTimeMs;
queue->nextRtpSequenceNumber = UINT16_MAX;
queue->oldestQueuedTimeMs = UINT64_MAX;
}
void RtpqCleanupQueue(PRTP_REORDER_QUEUE queue) {
while (queue->queueHead != NULL) {
PRTP_QUEUE_ENTRY entry = queue->queueHead;
queue->queueHead = entry->next;
free(entry->packet);
}
}
// newEntry is contained within the packet buffer so we free the whole entry by freeing entry->packet
static bool queuePacket(PRTP_REORDER_QUEUE queue, PRTP_QUEUE_ENTRY newEntry, bool head, PRTP_PACKET packet) {
PRTP_QUEUE_ENTRY entry;
LC_ASSERT(!isBefore16(packet->sequenceNumber, queue->nextRtpSequenceNumber));
// Don't queue duplicates
entry = queue->queueHead;
while (entry != NULL) {
if (entry->packet->sequenceNumber == packet->sequenceNumber) {
return false;
}
entry = entry->next;
}
newEntry->packet = packet;
newEntry->queueTimeMs = PltGetMillis();
newEntry->prev = NULL;
newEntry->next = NULL;
if (queue->oldestQueuedTimeMs == UINT64_MAX) {
queue->oldestQueuedTimeMs = newEntry->queueTimeMs;
}
if (queue->queueHead == NULL) {
LC_ASSERT(queue->queueSize == 0);
queue->queueHead = queue->queueTail = newEntry;
}
else if (head) {
LC_ASSERT(queue->queueSize > 0);
PRTP_QUEUE_ENTRY oldHead = queue->queueHead;
newEntry->next = oldHead;
LC_ASSERT(oldHead->prev == NULL);
oldHead->prev = newEntry;
queue->queueHead = newEntry;
}
else {
LC_ASSERT(queue->queueSize > 0);
PRTP_QUEUE_ENTRY oldTail = queue->queueTail;
newEntry->prev = oldTail;
LC_ASSERT(oldTail->next == NULL);
oldTail->next = newEntry;
queue->queueTail = newEntry;
}
queue->queueSize++;
return true;
}
static void updateOldestQueued(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY entry;
queue->oldestQueuedTimeMs = UINT64_MAX;
entry = queue->queueHead;
while (entry != NULL) {
if (entry->queueTimeMs < queue->oldestQueuedTimeMs) {
queue->oldestQueuedTimeMs = entry->queueTimeMs;
}
entry = entry->next;
}
}
static PRTP_QUEUE_ENTRY getEntryByLowestSeq(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY lowestSeqEntry, entry;
lowestSeqEntry = queue->queueHead;
entry = queue->queueHead;
while (entry != NULL) {
if (isBefore16(entry->packet->sequenceNumber, lowestSeqEntry->packet->sequenceNumber)) {
lowestSeqEntry = entry;
}
entry = entry->next;
}
// Remember the updated lowest sequence number
if (lowestSeqEntry != NULL) {
queue->nextRtpSequenceNumber = lowestSeqEntry->packet->sequenceNumber;
}
return lowestSeqEntry;
}
static void removeEntry(PRTP_REORDER_QUEUE queue, PRTP_QUEUE_ENTRY entry) {
LC_ASSERT(entry != NULL);
LC_ASSERT(queue->queueSize > 0);
LC_ASSERT(queue->queueHead != NULL);
LC_ASSERT(queue->queueTail != NULL);
if (queue->queueHead == entry) {
queue->queueHead = entry->next;
}
if (queue->queueTail == entry) {
queue->queueTail = entry->prev;
}
if (entry->prev != NULL) {
entry->prev->next = entry->next;
}
if (entry->next != NULL) {
entry->next->prev = entry->prev;
}
queue->queueSize--;
}
static PRTP_QUEUE_ENTRY enforceQueueConstraints(PRTP_REORDER_QUEUE queue) {
bool dequeuePacket = false;
// Empty queue is fine
if (queue->queueHead == NULL) {
return NULL;
}
// Check that the queue's time constraint is satisfied
if (PltGetMillis() - queue->oldestQueuedTimeMs > queue->maxQueueTimeMs) {
Limelog("Returning RTP packet queued for too long\n");
dequeuePacket = true;
}
// Check that the queue's size constraint is satisfied. We subtract one
// because this is validating that the queue will meet constraints _after_
// the current packet is enqueued.
if (!dequeuePacket && queue->queueSize == queue->maxSize - 1) {
Limelog("Returning RTP packet after queue overgrowth\n");
dequeuePacket = true;
}
if (dequeuePacket) {
// Return the lowest seq queued
return getEntryByLowestSeq(queue);
}
else {
return NULL;
}
}
int RtpqAddPacket(PRTP_REORDER_QUEUE queue, PRTP_PACKET packet, PRTP_QUEUE_ENTRY packetEntry) {
if (queue->nextRtpSequenceNumber != UINT16_MAX &&
isBefore16(packet->sequenceNumber, queue->nextRtpSequenceNumber)) {
// Reject packets behind our current sequence number
return 0;
}
if (queue->queueHead == NULL) {
// Return immediately for an exact match with an empty queue
if (queue->nextRtpSequenceNumber == UINT16_MAX ||
packet->sequenceNumber == queue->nextRtpSequenceNumber) {
queue->nextRtpSequenceNumber = packet->sequenceNumber + 1;
return RTPQ_RET_HANDLE_NOW;
}
else {
// Queue is empty currently so we'll put this packet on there
if (!queuePacket(queue, packetEntry, false, packet)) {
return 0;
}
else {
return RTPQ_RET_PACKET_CONSUMED;
}
}
}
else {
PRTP_QUEUE_ENTRY lowestEntry;
// Validate that the queue remains within our contraints
// and get the lowest element
lowestEntry = enforceQueueConstraints(queue);
// If the queue is now empty after validating queue constraints,
// this packet can be returned immediately
if (lowestEntry == NULL && queue->queueHead == NULL) {
queue->nextRtpSequenceNumber = packet->sequenceNumber + 1;
return RTPQ_RET_HANDLE_NOW;
}
else if (lowestEntry != NULL && queue->nextRtpSequenceNumber != UINT16_MAX &&
isBefore16(packet->sequenceNumber, queue->nextRtpSequenceNumber)) {
// The queue constraints were enforced and a new lowest entry was
// made available for retrieval. This packet was behind the new lowest
// so it will not be consumed by the queue.
return RTPQ_RET_PACKET_READY;
}
// Queue has data inside, so we need to see where this packet fits
if (packet->sequenceNumber == queue->nextRtpSequenceNumber) {
// It fits in a hole where we need a packet, now we have some ready
if (!queuePacket(queue, packetEntry, false, packet)) {
return 0;
}
else {
return RTPQ_RET_PACKET_READY | RTPQ_RET_PACKET_CONSUMED;
}
}
else {
if (!queuePacket(queue, packetEntry, false, packet)) {
return 0;
}
else {
// Constraint validation may have changed the oldest packet to one that
// matches the next sequence number
return RTPQ_RET_PACKET_CONSUMED | ((lowestEntry != NULL) ? RTPQ_RET_PACKET_READY : 0);
}
}
}
}
PRTP_PACKET RtpqGetQueuedPacket(PRTP_REORDER_QUEUE queue) {
PRTP_QUEUE_ENTRY queuedEntry, entry;
// Find the next queued packet
queuedEntry = NULL;
entry = queue->queueHead;
while (entry != NULL) {
if (entry->packet->sequenceNumber == queue->nextRtpSequenceNumber) {
queue->nextRtpSequenceNumber++;
queuedEntry = entry;
removeEntry(queue, entry);
break;
}
entry = entry->next;
}
// Bail if we found nothing
if (queuedEntry == NULL) {
// Update the oldest queued packet time
updateOldestQueued(queue);
return NULL;
}
// We don't update the oldest queued entry here, because we know
// the caller will call again until it receives null
return queuedEntry->packet;
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include "Video.h"
#define RTPQ_DEFAULT_MAX_SIZE 16
#define RTPQ_DEFAULT_QUEUE_TIME 40
typedef struct _RTP_QUEUE_ENTRY {
PRTP_PACKET packet;
uint64_t queueTimeMs;
struct _RTP_QUEUE_ENTRY* next;
struct _RTP_QUEUE_ENTRY* prev;
} RTP_QUEUE_ENTRY, *PRTP_QUEUE_ENTRY;
typedef struct _RTP_REORDER_QUEUE {
int maxSize;
uint32_t maxQueueTimeMs;
PRTP_QUEUE_ENTRY queueHead;
PRTP_QUEUE_ENTRY queueTail;
int queueSize;
unsigned short nextRtpSequenceNumber;
uint64_t oldestQueuedTimeMs;
} RTP_REORDER_QUEUE, *PRTP_REORDER_QUEUE;
#define RTPQ_RET_PACKET_CONSUMED 0x1
#define RTPQ_RET_PACKET_READY 0x2
#define RTPQ_RET_HANDLE_NOW 0x4
#define RTPQ_PACKET_CONSUMED(x) ((x) & RTPQ_RET_PACKET_CONSUMED)
#define RTPQ_PACKET_READY(x) ((x) & RTPQ_RET_PACKET_READY)
#define RTPQ_HANDLE_NOW(x) ((x) == RTPQ_RET_HANDLE_NOW)
void RtpqInitializeQueue(PRTP_REORDER_QUEUE queue, int maxSize, int maxQueueTimeMs);
void RtpqCleanupQueue(PRTP_REORDER_QUEUE queue);
int RtpqAddPacket(PRTP_REORDER_QUEUE queue, PRTP_PACKET packet, PRTP_QUEUE_ENTRY packetEntry);
PRTP_PACKET RtpqGetQueuedPacket(PRTP_REORDER_QUEUE queue);
-805
View File
@@ -1,805 +0,0 @@
#include "Limelight-internal.h"
#include "rswrapper.h"
#if defined(LC_DEBUG) && !defined(LC_FUZZING)
// This enables FEC validation mode with a synthetic drop
// and recovered packet checks vs the original input. It
// is on by default for debug builds.
#define FEC_VALIDATION_MODE
#define FEC_VERBOSE
#endif
// Don't try speculative RFI for 5 minutes after seeing
// an out of order packet or incorrect prediction
#define SPECULATIVE_RFI_COOLDOWN_PERIOD_US 300000000
// RTP packets use a 90 KHz presentation timestamp clock
#define PTS_DIVISOR 90
void RtpvInitializeQueue(PRTP_VIDEO_QUEUE queue) {
reed_solomon_init();
memset(queue, 0, sizeof(*queue));
queue->currentFrameNumber = 1;
queue->multiFecCapable = APP_VERSION_AT_LEAST(7, 1, 431);
}
static void purgeListEntries(PRTPV_QUEUE_LIST list) {
while (list->head != NULL) {
PRTPV_QUEUE_ENTRY entry = list->head;
list->head = entry->next;
free(entry->packet);
}
list->tail = NULL;
list->count = 0;
}
void RtpvCleanupQueue(PRTP_VIDEO_QUEUE queue) {
purgeListEntries(&queue->pendingFecBlockList);
purgeListEntries(&queue->completedFecBlockList);
}
static void insertEntryIntoList(PRTPV_QUEUE_LIST list, PRTPV_QUEUE_ENTRY entry) {
LC_ASSERT(entry->prev == NULL);
LC_ASSERT(entry->next == NULL);
if (list->head == NULL) {
LC_ASSERT(list->count == 0);
LC_ASSERT(list->tail == NULL);
list->head = list->tail = entry;
}
else {
LC_ASSERT(list->count != 0);
PRTPV_QUEUE_ENTRY oldTail = list->tail;
entry->prev = oldTail;
LC_ASSERT(oldTail->next == NULL);
oldTail->next = entry;
list->tail = entry;
}
list->count++;
}
static void removeEntryFromList(PRTPV_QUEUE_LIST list, PRTPV_QUEUE_ENTRY entry) {
LC_ASSERT(entry != NULL);
LC_ASSERT(list->count != 0);
LC_ASSERT(list->head != NULL);
LC_ASSERT(list->tail != NULL);
if (list->head == entry) {
list->head = entry->next;
}
if (list->tail == entry) {
list->tail = entry->prev;
}
if (entry->prev != NULL) {
LC_ASSERT(entry->prev->next == entry);
entry->prev->next = entry->next;
}
if (entry->next != NULL) {
LC_ASSERT(entry->next->prev == entry);
entry->next->prev = entry->prev;
}
entry->next = NULL;
entry->prev = NULL;
list->count--;
}
static void reportFinalFrameFecStatus(PRTP_VIDEO_QUEUE queue) {
SS_FRAME_FEC_STATUS fecStatus;
fecStatus.frameIndex = BE32(queue->currentFrameNumber);
fecStatus.highestReceivedSequenceNumber = BE16(queue->receivedHighestSequenceNumber);
fecStatus.nextContiguousSequenceNumber = BE16(queue->nextContiguousSequenceNumber);
fecStatus.missingPacketsBeforeHighestReceived = BE16(queue->missingPackets);
fecStatus.totalDataPackets = BE16(queue->bufferDataPackets);
fecStatus.totalParityPackets = BE16(queue->bufferParityPackets);
fecStatus.receivedDataPackets = BE16(queue->receivedDataPackets);
fecStatus.receivedParityPackets = BE16(queue->receivedParityPackets);
fecStatus.fecPercentage = (uint8_t)queue->fecPercentage;
fecStatus.multiFecBlockIndex = (uint8_t)queue->multiFecCurrentBlockNumber;
fecStatus.multiFecBlockCount = (uint8_t)(queue->multiFecLastBlockNumber + 1);
connectionSendFrameFecStatus(&fecStatus);
}
// newEntry is contained within the packet buffer so we free the whole entry by freeing entry->packet
static bool queuePacket(PRTP_VIDEO_QUEUE queue, PRTPV_QUEUE_ENTRY newEntry, PRTP_PACKET packet, int length, bool isParity, bool isFecRecovery) {
PRTPV_QUEUE_ENTRY entry;
bool outOfSequence;
LC_ASSERT(!(isFecRecovery && isParity));
LC_ASSERT(!isBefore16(packet->sequenceNumber, queue->nextContiguousSequenceNumber));
// If the packet is in order, we can take the fast path and avoid having
// to loop through the whole list. If we get an out of order or missing
// packet, the fast path will stop working and we'll use the loop instead.
//
// NB: It's not enough to just check next contiguous sequence number because
// it's possible that we hit the OOS path earlier which doesn't update the
// next contiguous sequence number. If that happens, we need to use the slow
// path for this entire frame to avoid possibly mishandling a duplicate packet.
if (queue->useFastQueuePath && packet->sequenceNumber == queue->nextContiguousSequenceNumber) {
queue->nextContiguousSequenceNumber = U16(packet->sequenceNumber + 1);
outOfSequence = false;
}
else {
outOfSequence = false;
// Check for duplicates
entry = queue->pendingFecBlockList.head;
while (entry != NULL) {
if (packet->sequenceNumber == entry->packet->sequenceNumber) {
return false;
}
else if (isBefore16(packet->sequenceNumber, entry->packet->sequenceNumber)) {
outOfSequence = true;
}
entry = entry->next;
}
// If we make it here, we cannot use the fast queue path for this frame because
// we're about to queue a non-duplicate packet out of order. This will not update
// nextContiguousSequenceNumber which the fast path relies on.
queue->useFastQueuePath = false;
}
newEntry->packet = packet;
newEntry->length = length;
newEntry->isParity = isParity;
newEntry->prev = NULL;
newEntry->next = NULL;
newEntry->presentationTimeUs = ((uint64_t)packet->timestamp * 1000) / PTS_DIVISOR;
newEntry->rtpTimestamp = packet->timestamp;
// FEC recovery packets are synthesized by us, so don't use them to determine OOS data
if (!isFecRecovery) {
if (outOfSequence) {
// This packet was received after a higher sequence number packet, so note that we
// received an out of order packet to disable our speculative RFI recovery logic.
queue->lastOosFramePresentationTimestamp = newEntry->presentationTimeUs;
if (!queue->receivedOosData) {
Limelog("Leaving speculative RFI mode after OOS video data at frame %u\n",
queue->currentFrameNumber);
queue->receivedOosData = true;
}
}
else if (queue->receivedOosData && newEntry->presentationTimeUs > queue->lastOosFramePresentationTimestamp + SPECULATIVE_RFI_COOLDOWN_PERIOD_US) {
Limelog("Entering speculative RFI mode after sequenced video data at frame %u\n",
queue->currentFrameNumber);
queue->receivedOosData = false;
}
}
insertEntryIntoList(&queue->pendingFecBlockList, newEntry);
return true;
}
#define PACKET_RECOVERY_FAILURE() \
ret = -1; \
Limelog("FEC recovery returned corrupt packet %d" \
" (frame %d)", rtpPacket->sequenceNumber, \
queue->currentFrameNumber); \
free(packets[i]); \
continue
// Returns 0 if the frame is completely constructed
static int reconstructFrame(PRTP_VIDEO_QUEUE queue) {
unsigned int totalPackets = queue->bufferDataPackets + queue->bufferParityPackets;
unsigned int neededPackets = queue->bufferDataPackets;
int ret;
LC_ASSERT(totalPackets == U16(queue->bufferHighestSequenceNumber - queue->bufferLowestSequenceNumber) + 1U);
#ifdef FEC_VALIDATION_MODE
// We'll need an extra packet to run in FEC validation mode, because we will
// be "dropping" one below and recovering it using parity. However, some frames
// are so large that FEC is disabled entirely, so don't wait for parity on those.
neededPackets += queue->fecPercentage ? 1 : 0;
#endif
LC_ASSERT(totalPackets - neededPackets <= queue->bufferParityPackets);
if (queue->pendingFecBlockList.count < neededPackets) {
// If we've never received OOS data from this host, we can predict whether this frame will be recoverable
// based on the packets we've received (or not) so far. If the number of missing shards exceeds the total
// needed shards, there is no hope of recovering the data. The only way we could recover this frame is by
// receiving OOS data, which is unlikely because we've not seen any recently from this host.
if (!queue->reportedLostFrame && !queue->receivedOosData) {
// NB: We use totalPackets - neededPackets instead of just bufferParityPackets here because we require
// one extra parity shard for recovery if we're in FEC validation mode.
if (queue->missingPackets > totalPackets - neededPackets) {
notifyFrameLost(queue->currentFrameNumber, true);
queue->reportedLostFrame = true;
}
else {
// Assert that there are enough remaining packets to possibly recover this frame.
LC_ASSERT(neededPackets - queue->pendingFecBlockList.count <= U16(queue->bufferHighestSequenceNumber - queue->receivedHighestSequenceNumber));
}
}
// Not enough data to recover yet
return -1;
}
// If we make it here and reported a lost frame, we lied to the host. This can happen if we happen to get
// unlucky and this particular frame happens to be the one with OOS data, but it should almost never happen.
LC_ASSERT(queue->missingPackets <= queue->bufferParityPackets);
LC_ASSERT(!queue->reportedLostFrame || queue->receivedOosData);
if (queue->reportedLostFrame && !queue->receivedOosData) {
// If it turns out that we lied to the host, stop further speculative RFI requests for a while.
queue->receivedOosData = true;
queue->lastOosFramePresentationTimestamp = queue->pendingFecBlockList.head->presentationTimeUs;
Limelog("Leaving speculative RFI mode due to incorrect loss prediction of frame %u\n", queue->currentFrameNumber);
}
#ifdef FEC_VALIDATION_MODE
// If FEC is disabled or unsupported for this frame, we must bail early here.
if ((queue->fecPercentage == 0 || AppVersionQuad[0] < 5) &&
queue->receivedDataPackets == queue->bufferDataPackets) {
#else
if (queue->receivedDataPackets == queue->bufferDataPackets) {
#endif
// We've received a full frame with no need for FEC.
return 0;
}
if (AppVersionQuad[0] < 5) {
// Our FEC recovery code doesn't work properly until Gen 5
Limelog("FEC recovery not supported on Gen %d servers\n",
AppVersionQuad[0]);
return -1;
}
reed_solomon* rs = NULL;
unsigned char** packets = calloc(totalPackets, sizeof(unsigned char*));
unsigned char* marks = calloc(totalPackets, sizeof(unsigned char));
if (packets == NULL || marks == NULL) {
ret = -2;
goto cleanup;
}
rs = reed_solomon_new(queue->bufferDataPackets, queue->bufferParityPackets);
// This could happen in an OOM condition, but it could also mean the FEC data
// that we fed to reed_solomon_new() is bogus, so we'll assert to get a better look.
LC_ASSERT(rs != NULL);
if (rs == NULL) {
ret = -3;
goto cleanup;
}
memset(marks, 1, sizeof(char) * (totalPackets));
int receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
int packetBufferSize = receiveSize + sizeof(RTPV_QUEUE_ENTRY);
#ifdef FEC_VALIDATION_MODE
// Choose a packet to drop
unsigned int dropIndex = rand() % queue->bufferDataPackets;
PRTP_PACKET droppedRtpPacket = NULL;
int droppedRtpPacketLength = 0;
#endif
PRTPV_QUEUE_ENTRY entry = queue->pendingFecBlockList.head;
while (entry != NULL) {
unsigned int index = U16(entry->packet->sequenceNumber - queue->bufferLowestSequenceNumber);
#ifdef FEC_VALIDATION_MODE
if (index == dropIndex) {
// If this was the drop choice, remember the original contents
// and "drop" it.
droppedRtpPacket = entry->packet;
droppedRtpPacketLength = entry->length;
entry = entry->next;
continue;
}
#endif
// We should never have duplicate packets enqueued
LC_ASSERT(packets[index] == NULL);
LC_ASSERT(marks[index] != 0);
packets[index] = (unsigned char*) entry->packet;
marks[index] = 0;
//Set padding to zero
if (entry->length < receiveSize) {
memset(&packets[index][entry->length], 0, receiveSize - entry->length);
}
entry = entry->next;
}
unsigned int i;
for (i = 0; i < totalPackets; i++) {
if (marks[i]) {
packets[i] = malloc(packetBufferSize);
if (packets[i] == NULL) {
ret = -4;
goto cleanup_packets;
}
}
}
ret = reed_solomon_decode(rs, packets, marks, totalPackets, receiveSize);
// We should always provide enough parity to recover the missing data successfully.
// If this fails, something is probably wrong with our FEC state.
LC_ASSERT(ret == 0);
if (queue->bufferDataPackets != queue->receivedDataPackets) {
#ifdef FEC_VERBOSE
Limelog("Recovered %d video data shards from frame %d\n",
queue->bufferDataPackets - queue->receivedDataPackets,
queue->currentFrameNumber);
#endif
// Report the final FEC status if we needed to perform a recovery
reportFinalFrameFecStatus(queue);
}
cleanup_packets:
for (i = 0; i < totalPackets; i++) {
if (marks[i]) {
// Only submit frame data, not FEC packets
if (ret == 0 && i < queue->bufferDataPackets) {
PRTPV_QUEUE_ENTRY queueEntry = (PRTPV_QUEUE_ENTRY)&packets[i][receiveSize];
PRTP_PACKET rtpPacket = (PRTP_PACKET) packets[i];
rtpPacket->sequenceNumber = U16(i + queue->bufferLowestSequenceNumber);
rtpPacket->header = queue->pendingFecBlockList.head->packet->header;
rtpPacket->timestamp = queue->pendingFecBlockList.head->packet->timestamp;
rtpPacket->ssrc = queue->pendingFecBlockList.head->packet->ssrc;
int dataOffset = sizeof(*rtpPacket);
if (rtpPacket->header & FLAG_EXTENSION) {
dataOffset += 4; // 2 additional fields
}
PNV_VIDEO_PACKET nvPacket = (PNV_VIDEO_PACKET)(((char*)rtpPacket) + dataOffset);
nvPacket->frameIndex = queue->currentFrameNumber;
nvPacket->multiFecBlocks =
((queue->multiFecLastBlockNumber << 2) | queue->multiFecCurrentBlockNumber) << 4;
// TODO: nvPacket->multiFecFlags?
#ifdef FEC_VALIDATION_MODE
if (i == dropIndex && droppedRtpPacket != NULL) {
// Check the packet contents if this was our known drop
PNV_VIDEO_PACKET droppedNvPacket = (PNV_VIDEO_PACKET)(((char*)droppedRtpPacket) + dataOffset);
int droppedDataLength = droppedRtpPacketLength - dataOffset - sizeof(*nvPacket);
int recoveredDataLength = StreamConfig.packetSize - sizeof(*nvPacket);
int j;
int recoveryErrors = 0;
LC_ASSERT_VT(droppedDataLength <= recoveredDataLength);
LC_ASSERT_VT(droppedDataLength == recoveredDataLength || (nvPacket->flags & FLAG_EOF));
// Check all NV_VIDEO_PACKET fields except FEC stuff which differs in the recovered packet
LC_ASSERT_VT(nvPacket->flags == droppedNvPacket->flags);
LC_ASSERT_VT(nvPacket->extraFlags == droppedNvPacket->extraFlags);
LC_ASSERT_VT(nvPacket->frameIndex == droppedNvPacket->frameIndex);
LC_ASSERT_VT(nvPacket->streamPacketIndex == droppedNvPacket->streamPacketIndex);
LC_ASSERT_VT(!queue->multiFecCapable || nvPacket->multiFecBlocks == droppedNvPacket->multiFecBlocks);
// Check the data itself - use memcmp() and only loop if an error is detected
if (memcmp(nvPacket + 1, droppedNvPacket + 1, droppedDataLength)) {
unsigned char* actualData = (unsigned char*)(nvPacket + 1);
unsigned char* expectedData = (unsigned char*)(droppedNvPacket + 1);
for (j = 0; j < droppedDataLength; j++) {
if (actualData[j] != expectedData[j]) {
Limelog("Recovery error at %d: expected 0x%02x, actual 0x%02x\n",
j, expectedData[j], actualData[j]);
recoveryErrors++;
}
}
}
// If this packet is at the end of the frame, the remaining data should be zeros.
for (j = droppedDataLength; j < recoveredDataLength; j++) {
unsigned char* actualData = (unsigned char*)(nvPacket + 1);
if (actualData[j] != 0) {
Limelog("Recovery error at %d: expected 0x00, actual 0x%02x\n",
j, actualData[j]);
recoveryErrors++;
}
}
LC_ASSERT_VT(recoveryErrors == 0);
// This drop was fake, so we don't want to actually submit it to the depacketizer.
// It will get confused because it's already seen this packet before.
free(packets[i]);
continue;
}
#endif
// Do some rudamentary checks to see that the recovered packet is sane.
// In some cases (4K 30 FPS 80 Mbps), we seem to get some odd failures
// here in rare cases where FEC recovery is required. I'm unsure if it
// is our bug, NVIDIA's, or something else, but we don't want the corrupt
// packet to by ingested by our depacketizer (or worse, the decoder).
if (i == 0 && !(nvPacket->flags & FLAG_SOF)) {
PACKET_RECOVERY_FAILURE();
}
if (i == queue->bufferDataPackets - 1 && !(nvPacket->flags & FLAG_EOF)) {
PACKET_RECOVERY_FAILURE();
}
if (i > 0 && i < queue->bufferDataPackets - 1 && !(nvPacket->flags & FLAG_CONTAINS_PIC_DATA)) {
PACKET_RECOVERY_FAILURE();
}
if (nvPacket->flags & ~(FLAG_SOF | FLAG_EOF | FLAG_CONTAINS_PIC_DATA)) {
PACKET_RECOVERY_FAILURE();
}
// FEC recovered frames may have extra zero padding at the end. This is
// fine per H.264 Annex B which states trailing zero bytes must be
// discarded by decoders. It's not safe to strip all zero padding because
// it may be a legitimate part of the H.264 bytestream.
LC_ASSERT(isBefore16(rtpPacket->sequenceNumber, queue->bufferFirstParitySequenceNumber));
queuePacket(queue, queueEntry, rtpPacket, StreamConfig.packetSize + dataOffset, false, true);
} else if (packets[i] != NULL) {
free(packets[i]);
}
}
}
cleanup:
reed_solomon_release(rs);
if (packets != NULL)
free(packets);
if (marks != NULL)
free(marks);
return ret;
}
static void stageCompleteFecBlock(PRTP_VIDEO_QUEUE queue) {
unsigned int nextSeqNum = queue->bufferLowestSequenceNumber;
while (queue->pendingFecBlockList.count > 0) {
PRTPV_QUEUE_ENTRY entry = queue->pendingFecBlockList.head;
unsigned int lowestRtpSequenceNumber = entry->packet->sequenceNumber;
do {
// We should never encounter a packet that's lower than our next seq num
LC_ASSERT(!isBefore16(entry->packet->sequenceNumber, nextSeqNum));
// Never return parity packets
if (entry->isParity) {
PRTPV_QUEUE_ENTRY parityEntry = entry;
// Skip this entry
entry = parityEntry->next;
// Remove this entry
removeEntryFromList(&queue->pendingFecBlockList, parityEntry);
// Free the entry and packet
free(parityEntry->packet);
continue;
}
// Check for the next packet in sequence. This will be O(1) for non-reordered packet streams.
if (entry->packet->sequenceNumber == nextSeqNum) {
removeEntryFromList(&queue->pendingFecBlockList, entry);
// To avoid having to sample the system time for each packet, we cheat
// and use the first packet's receive time for all packets. This ends up
// actually being better for the measurements that the depacketizer does,
// since it properly handles out of order packets.
LC_ASSERT(queue->bufferFirstRecvTimeUs != 0);
entry->receiveTimeUs = queue->bufferFirstRecvTimeUs;
// Move this packet to the completed FEC block list
insertEntryIntoList(&queue->completedFecBlockList, entry);
break;
}
else if (isBefore16(entry->packet->sequenceNumber, lowestRtpSequenceNumber)) {
lowestRtpSequenceNumber = entry->packet->sequenceNumber;
}
entry = entry->next;
} while (entry != NULL);
if (entry == NULL) {
// Start at the lowest we found last enumeration
nextSeqNum = lowestRtpSequenceNumber;
}
else {
// We found this packet so move on to the next one in sequence
nextSeqNum = U16(nextSeqNum + 1);
}
}
}
static void submitCompletedFrame(PRTP_VIDEO_QUEUE queue) {
while (queue->completedFecBlockList.count > 0) {
PRTPV_QUEUE_ENTRY entry = queue->completedFecBlockList.head;
// Parity packets should have been removed by stageCompleteFecBlock()
LC_ASSERT(!entry->isParity);
// Submit this packet for decoding. It will own freeing the entry now.
removeEntryFromList(&queue->completedFecBlockList, entry);
queueRtpPacket(entry);
}
}
uint32_t RtpvGetCurrentFrameNumber(PRTP_VIDEO_QUEUE queue) {
return queue->currentFrameNumber;
}
int RtpvAddPacket(PRTP_VIDEO_QUEUE queue, PRTP_PACKET packet, int length, PRTPV_QUEUE_ENTRY packetEntry) {
if (isBefore16(packet->sequenceNumber, queue->nextContiguousSequenceNumber)) {
// Reject packets behind our current buffer window
return RTPF_RET_REJECTED;
}
// FLAG_EXTENSION is required for all supported versions of GFE.
LC_ASSERT_VT(packet->header & FLAG_EXTENSION);
int dataOffset = sizeof(*packet);
if (packet->header & FLAG_EXTENSION) {
dataOffset += 4; // 2 additional fields
}
if (length < dataOffset + (int)sizeof(NV_VIDEO_PACKET)) {
// Reject packets that are too small to fit a NV_VIDEO_PACKET header
return RTPF_RET_REJECTED;
}
PNV_VIDEO_PACKET nvPacket = (PNV_VIDEO_PACKET)(((char*)packet) + dataOffset);
nvPacket->streamPacketIndex = LE32(nvPacket->streamPacketIndex);
nvPacket->frameIndex = LE32(nvPacket->frameIndex);
nvPacket->fecInfo = LE32(nvPacket->fecInfo);
// For legacy servers, we'll fixup the reserved data so that it looks like
// it's a single FEC frame from a multi-FEC capable server. This allows us
// to make our parsing logic simpler.
if (!queue->multiFecCapable) {
nvPacket->multiFecFlags = 0x10;
nvPacket->multiFecBlocks = 0x00;
}
#ifndef LC_FUZZING
if (isBefore16(nvPacket->frameIndex, queue->currentFrameNumber)) {
// Reject frames behind our current frame number
return RTPF_RET_REJECTED;
}
#endif
uint32_t fecIndex = (nvPacket->fecInfo & 0x3FF000) >> 12;
uint8_t fecCurrentBlockNumber = (nvPacket->multiFecBlocks >> 4) & 0x3;
if (nvPacket->frameIndex == queue->currentFrameNumber && fecCurrentBlockNumber < queue->multiFecCurrentBlockNumber) {
// Reject FEC blocks behind our current block number
return RTPF_RET_REJECTED;
}
// Reinitialize the queue if it's empty after a frame delivery or
// if we can't finish a frame before receiving the next one.
if (queue->pendingFecBlockList.count == 0 || queue->currentFrameNumber != nvPacket->frameIndex ||
queue->multiFecCurrentBlockNumber != fecCurrentBlockNumber) {
if (queue->pendingFecBlockList.count != 0) {
// Report the final status of the FEC queue before dropping this frame
reportFinalFrameFecStatus(queue);
if (queue->multiFecLastBlockNumber != 0) {
Limelog("Unrecoverable frame %d (block %d of %d): %d+%d=%d received < %d needed\n",
queue->currentFrameNumber, queue->multiFecCurrentBlockNumber+1,
queue->multiFecLastBlockNumber+1,
queue->receivedDataPackets,
queue->receivedParityPackets,
queue->pendingFecBlockList.count,
queue->bufferDataPackets);
// If we just missed a block of this frame rather than the whole thing,
// we must manually advance the queue to the next frame. Parsing this
// frame further is not possible.
if (queue->currentFrameNumber == nvPacket->frameIndex) {
// Discard any unsubmitted buffers from the previous frame
purgeListEntries(&queue->pendingFecBlockList);
purgeListEntries(&queue->completedFecBlockList);
// Notify the host of the loss of this frame
if (!queue->reportedLostFrame) {
notifyFrameLost(queue->currentFrameNumber, false);
queue->reportedLostFrame = true;
}
queue->currentFrameNumber++;
queue->multiFecCurrentBlockNumber = 0;
return RTPF_RET_REJECTED;
}
}
else {
Limelog("Unrecoverable frame %d: %d+%d=%d received < %d needed\n",
queue->currentFrameNumber, queue->receivedDataPackets,
queue->receivedParityPackets,
queue->pendingFecBlockList.count,
queue->bufferDataPackets);
}
}
// We must either start on the current FEC block number for the current frame,
// or block 0 of a new frame.
uint8_t expectedFecBlockNumber = (queue->currentFrameNumber == nvPacket->frameIndex ? queue->multiFecCurrentBlockNumber : 0);
if (fecCurrentBlockNumber != expectedFecBlockNumber) {
// Report the final status of the FEC queue before dropping this frame
reportFinalFrameFecStatus(queue);
Limelog("Unrecoverable frame %d: lost FEC blocks %d to %d\n",
nvPacket->frameIndex,
expectedFecBlockNumber + 1,
fecCurrentBlockNumber);
// Discard any unsubmitted buffers from the previous frame
purgeListEntries(&queue->pendingFecBlockList);
purgeListEntries(&queue->completedFecBlockList);
// Notify the host of the loss of this frame
if (!queue->reportedLostFrame) {
notifyFrameLost(queue->currentFrameNumber, false);
queue->reportedLostFrame = true;
}
// We dropped a block of this frame, so we must skip to the next one.
queue->currentFrameNumber = nvPacket->frameIndex + 1;
queue->multiFecCurrentBlockNumber = 0;
return RTPF_RET_REJECTED;
}
// Discard any pending buffers from the previous FEC block
purgeListEntries(&queue->pendingFecBlockList);
// Discard any completed FEC blocks from the previous frame
if (queue->currentFrameNumber != nvPacket->frameIndex) {
purgeListEntries(&queue->completedFecBlockList);
}
// If the frame numbers are not contiguous, the network dropped an entire frame.
// The check here looks weird, but that's because we increment the frame number
// after successfully processing a frame.
if (queue->currentFrameNumber != nvPacket->frameIndex) {
LC_ASSERT_VT(queue->currentFrameNumber < nvPacket->frameIndex);
// If the frame immediately preceding this one was lost, we may have already
// reported it using our speculative RFI logic. Don't report it again.
if (queue->currentFrameNumber + 1 != nvPacket->frameIndex || !queue->reportedLostFrame) {
// NB: We only have to notify for the most recent lost frame, since
// the depacketizer will report the RFI range starting at the last
// frame it saw.
notifyFrameLost(nvPacket->frameIndex - 1, false);
}
}
queue->currentFrameNumber = nvPacket->frameIndex;
// Tell the control stream logic about this frame, even if we don't end up
// being able to reconstruct a full frame from it.
connectionSawFrame(queue->currentFrameNumber);
queue->bufferFirstRecvTimeUs = PltGetMicroseconds();
queue->bufferLowestSequenceNumber = U16(packet->sequenceNumber - fecIndex);
queue->nextContiguousSequenceNumber = queue->bufferLowestSequenceNumber;
queue->receivedDataPackets = 0;
queue->receivedParityPackets = 0;
queue->receivedHighestSequenceNumber = 0;
queue->missingPackets = 0;
queue->useFastQueuePath = true;
queue->reportedLostFrame = false;
queue->bufferDataPackets = (nvPacket->fecInfo & 0xFFC00000) >> 22;
queue->fecPercentage = (nvPacket->fecInfo & 0xFF0) >> 4;
queue->bufferParityPackets = (queue->bufferDataPackets * queue->fecPercentage + 99) / 100;
queue->bufferFirstParitySequenceNumber = U16(queue->bufferLowestSequenceNumber + queue->bufferDataPackets);
queue->bufferHighestSequenceNumber = U16(queue->bufferFirstParitySequenceNumber + queue->bufferParityPackets - 1);
queue->multiFecCurrentBlockNumber = fecCurrentBlockNumber;
queue->multiFecLastBlockNumber = (nvPacket->multiFecBlocks >> 6) & 0x3;
queue->stats.packetCountVideo += queue->bufferDataPackets;
queue->stats.packetCountFec += queue->bufferParityPackets;
}
// Reject packets above our FEC queue valid sequence number range
if (isBefore16(queue->bufferHighestSequenceNumber, packet->sequenceNumber)) {
return RTPF_RET_REJECTED;
}
LC_ASSERT_VT(!queue->fecPercentage || U16(packet->sequenceNumber - fecIndex) == queue->bufferLowestSequenceNumber);
LC_ASSERT_VT((nvPacket->fecInfo & 0xFF0) >> 4 == queue->fecPercentage);
LC_ASSERT_VT((nvPacket->fecInfo & 0xFFC00000) >> 22 == queue->bufferDataPackets);
// Verify that the legacy non-multi-FEC compatibility code works
LC_ASSERT_VT(queue->multiFecCapable || fecCurrentBlockNumber == 0);
LC_ASSERT_VT(queue->multiFecCapable || queue->multiFecLastBlockNumber == 0);
// Multi-block FEC details must remain the same within a single frame
LC_ASSERT_VT(fecCurrentBlockNumber == queue->multiFecCurrentBlockNumber);
LC_ASSERT_VT(((nvPacket->multiFecBlocks >> 6) & 0x3) == queue->multiFecLastBlockNumber);
LC_ASSERT_VT((nvPacket->flags & FLAG_EOF) || length - dataOffset == StreamConfig.packetSize);
if (!queuePacket(queue, packetEntry, packet, length, !isBefore16(packet->sequenceNumber, queue->bufferFirstParitySequenceNumber), false)) {
return RTPF_RET_REJECTED;
}
else {
// Update total missing packet count
if (queue->pendingFecBlockList.count == 1) {
// Initialize counts and highest seqnum on the first packet
LC_ASSERT(queue->missingPackets == 0);
LC_ASSERT(queue->receivedHighestSequenceNumber == 0);
queue->missingPackets += U16(packet->sequenceNumber - queue->bufferLowestSequenceNumber);
queue->receivedHighestSequenceNumber = packet->sequenceNumber;
}
else if (isBefore16(queue->receivedHighestSequenceNumber, packet->sequenceNumber)) {
// If we receive a packet above the highest sequence number,
// adjust our missing packets count based on that new sequence number.
queue->missingPackets += U16(packet->sequenceNumber - queue->receivedHighestSequenceNumber - 1);
queue->receivedHighestSequenceNumber = packet->sequenceNumber;
}
else {
// If we receive a packet behind the highest sequence number, but
// queuePacket() accepted it, we must have received a missing packet.
LC_ASSERT(queue->missingPackets > 0);
queue->missingPackets--;
}
// We explicitly assert less-than because we know we received at least one packet (this one)
LC_ASSERT(queue->missingPackets < queue->bufferDataPackets + queue->bufferParityPackets);
if (isBefore16(packet->sequenceNumber, queue->bufferFirstParitySequenceNumber)) {
queue->receivedDataPackets++;
LC_ASSERT(queue->receivedDataPackets <= queue->bufferDataPackets);
}
else {
queue->receivedParityPackets++;
LC_ASSERT(queue->receivedParityPackets <= queue->bufferParityPackets);
}
// Try to submit this frame. If we haven't received enough packets,
// this will fail and we'll keep waiting.
if (reconstructFrame(queue) == 0) {
// Stage the complete FEC block for use once reassembly is complete
stageCompleteFecBlock(queue);
// stageCompleteFecBlock() should have consumed all pending FEC data
LC_ASSERT(queue->pendingFecBlockList.head == NULL);
LC_ASSERT(queue->pendingFecBlockList.tail == NULL);
LC_ASSERT(queue->pendingFecBlockList.count == 0);
// If we're not yet at the last FEC block for this frame, move on to the next block.
// Otherwise, the frame is complete and we can move on to the next frame.
if (queue->multiFecCurrentBlockNumber < queue->multiFecLastBlockNumber) {
// Move on to the next FEC block for this frame
queue->multiFecCurrentBlockNumber++;
}
else {
// Submit all FEC blocks to the depacketizer
submitCompletedFrame(queue);
// submitCompletedFrame() should have consumed all completed FEC data
LC_ASSERT(queue->completedFecBlockList.head == NULL);
LC_ASSERT(queue->completedFecBlockList.tail == NULL);
LC_ASSERT(queue->completedFecBlockList.count == 0);
// Continue to the next frame
queue->currentFrameNumber++;
queue->multiFecCurrentBlockNumber = 0;
}
}
return RTPF_RET_QUEUED;
}
}
-60
View File
@@ -1,60 +0,0 @@
#pragma once
#include "Video.h"
typedef struct _RTPV_QUEUE_ENTRY {
struct _RTPV_QUEUE_ENTRY* next;
struct _RTPV_QUEUE_ENTRY* prev;
PRTP_PACKET packet;
uint64_t receiveTimeUs;
uint64_t presentationTimeUs;
uint32_t rtpTimestamp;
int length;
bool isParity;
} RTPV_QUEUE_ENTRY, *PRTPV_QUEUE_ENTRY;
typedef struct _RTPV_QUEUE_LIST {
PRTPV_QUEUE_ENTRY head;
PRTPV_QUEUE_ENTRY tail;
uint32_t count;
} RTPV_QUEUE_LIST, *PRTPV_QUEUE_LIST;
typedef struct _RTP_VIDEO_QUEUE {
RTPV_QUEUE_LIST pendingFecBlockList;
RTPV_QUEUE_LIST completedFecBlockList;
uint64_t bufferFirstRecvTimeUs;
uint32_t bufferLowestSequenceNumber;
uint32_t bufferHighestSequenceNumber;
uint32_t bufferFirstParitySequenceNumber;
uint32_t bufferDataPackets;
uint32_t bufferParityPackets;
uint32_t receivedDataPackets;
uint32_t receivedParityPackets;
uint32_t receivedHighestSequenceNumber;
uint32_t fecPercentage;
uint32_t nextContiguousSequenceNumber;
uint32_t missingPackets; // # of holes behind receivedHighestSequenceNumber
bool useFastQueuePath;
bool reportedLostFrame;
uint32_t currentFrameNumber;
bool multiFecCapable;
uint8_t multiFecCurrentBlockNumber;
uint8_t multiFecLastBlockNumber;
uint64_t lastOosFramePresentationTimestamp;
bool receivedOosData;
RTP_VIDEO_STATS stats; // the above values are short-lived, this tracks stats for the life of the queue
} RTP_VIDEO_QUEUE, *PRTP_VIDEO_QUEUE;
#define RTPF_RET_QUEUED 0
#define RTPF_RET_REJECTED 1
void RtpvInitializeQueue(PRTP_VIDEO_QUEUE queue);
void RtpvCleanupQueue(PRTP_VIDEO_QUEUE queue);
int RtpvAddPacket(PRTP_VIDEO_QUEUE queue, PRTP_PACKET packet, int length, PRTPV_QUEUE_ENTRY packetEntry);
uint32_t RtpvGetCurrentFrameNumber(PRTP_VIDEO_QUEUE queue);
void RtpvSubmitQueuedPackets(PRTP_VIDEO_QUEUE queue);
+94 -627
View File
File diff suppressed because it is too large Load Diff
+32 -70
View File
@@ -1,4 +1,3 @@
#include "Platform.h"
#include "Rtsp.h"
// Check if String s begins with the given prefix
@@ -27,7 +26,7 @@ static int getMessageLength(PRTSP_MESSAGE msg) {
// Add length of response-specific strings
else {
char statusCodeStr[16];
snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
count += strlen(statusCodeStr);
count += strlen(msg->message.response.statusString);
// two spaces and \r\n
@@ -63,7 +62,6 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
char flag;
bool messageEnded = false;
char* strtokCtx = NULL;
char* payload = NULL;
char* opt = NULL;
int statusCode = 0;
@@ -90,7 +88,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
messageBuffer[length] = 0;
// Get the first token of the message
token = strtok_r(messageBuffer, delim, &strtokCtx);
token = strtok(messageBuffer, delim);
if (token == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
@@ -103,15 +101,15 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
protocol = token;
// Get the status code
token = strtok_r(NULL, delim, &strtokCtx);
token = strtok(NULL, delim);
statusCode = atoi(token);
if (token == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
statusCode = atoi(token);
// Get the status string
statusStr = strtok_r(NULL, end, &strtokCtx);
statusStr = strtok(NULL, end);
if (statusStr == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
@@ -126,12 +124,12 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
else {
flag = TYPE_REQUEST;
command = token;
target = strtok_r(NULL, delim, &strtokCtx);
target = strtok(NULL, delim);
if (target == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
}
protocol = strtok_r(NULL, delim, &strtokCtx);
protocol = strtok(NULL, delim);
if (protocol == NULL) {
exitCode = RTSP_ERROR_MALFORMED;
goto ExitFailure;
@@ -146,7 +144,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
// Parse remaining options
while (token != NULL)
{
token = strtok_r(NULL, typeFlag == TOKEN_OPTION ? optDelim : end, &strtokCtx);
token = strtok(NULL, typeFlag == TOKEN_OPTION ? optDelim : end);
if (token != NULL) {
if (typeFlag == TOKEN_OPTION) {
opt = token;
@@ -161,7 +159,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
}
newOpt->flags = 0;
newOpt->option = opt;
newOpt->content = token + 1; // Skip the protocol defined blank space
newOpt->content = token;
newOpt->next = NULL;
insertOption(&options, newOpt);
@@ -176,16 +174,6 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
break;
}
else if (startsWith(endCheck, "\n\r") && endCheck[2] == '\0') {
// Previous `if` statement already handle situation when two bytes are missing.
// This is the workaround for the same problem, but for only one byte missing.
// Sometimes on android emulators last byte or two bytes are missing.
// This is link to this bug in Goggle Issue Tracker:
// https://issuetracker.google.com/issues/150758736?pli=1
messageEnded = true;
break;
}
else if (startsWith(endCheck, "\n\r\n")) {
// We've encountered the end of the message - mark it thus
messageEnded = true;
@@ -320,22 +308,9 @@ void freeOptionList(POPTION_ITEM optionsHead) {
}
}
bool appendString(char* dest, int* destOffset, int* destRemainingLength, char* source) {
int ret = snprintf(&dest[*destOffset], *destRemainingLength, "%s", source);
if (ret < 0 || ret >= *destRemainingLength) {
LC_ASSERT(false);
return false;
}
*destOffset += ret;
*destRemainingLength -= ret;
return true;
}
// Serialize the message struct into a string containing the RTSP message
char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
int size = getMessageLength(msg);
int offset = 0;
char* serializedMessage;
POPTION_ITEM current = msg->options;
char statusCodeStr[16];
@@ -347,53 +322,44 @@ char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
if (msg->type == TYPE_REQUEST) {
// command [space]
if (!appendString(serializedMessage, &offset, &size, msg->message.request.command) || !appendString(serializedMessage, &offset, &size, " ")) {
goto fail;
}
strcpy(serializedMessage, msg->message.request.command);
strcat(serializedMessage, " ");
// target [space]
if (!appendString(serializedMessage, &offset, &size, msg->message.request.target) || !appendString(serializedMessage, &offset, &size, " ")) {
goto fail;
}
strcat(serializedMessage, msg->message.request.target);
strcat(serializedMessage, " ");
// protocol \r\n
if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
goto fail;
}
strcat(serializedMessage, msg->protocol);
strcat(serializedMessage, "\r\n");
}
else {
// protocol [space]
if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, " ")) {
goto fail;
}
strcpy(serializedMessage, msg->protocol);
strcat(serializedMessage, " ");
// status code [space]
snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
if (!appendString(serializedMessage, &offset, &size, statusCodeStr) || !appendString(serializedMessage, &offset, &size, " ")) {
goto fail;
}
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
strcat(serializedMessage, statusCodeStr);
strcat(serializedMessage, " ");
// status str\r\n
if (!appendString(serializedMessage, &offset, &size, msg->message.response.statusString) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
goto fail;
}
strcat(serializedMessage, msg->message.response.statusString);
strcat(serializedMessage, "\r\n");
}
// option content\r\n
while (current != NULL) {
if (!appendString(serializedMessage, &offset, &size, current->option) || !appendString(serializedMessage, &offset, &size, ": ")) {
goto fail;
}
if (!appendString(serializedMessage, &offset, &size, current->content) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
goto fail;
}
strcat(serializedMessage, current->option);
strcat(serializedMessage, ": ");
strcat(serializedMessage, current->content);
strcat(serializedMessage, "\r\n");
current = current->next;
}
// Final \r\n
if (!appendString(serializedMessage, &offset, &size, "\r\n")) {
goto fail;
}
strcat(serializedMessage, "\r\n");
// payload
if (msg->payload != NULL) {
if (msg->payloadLength > size) {
goto fail;
}
int offset;
// Find end of the RTSP message header
for (offset = 0; serializedMessage[offset] != 0; offset++);
// Add the payload after
memcpy(&serializedMessage[offset], msg->payload, msg->payloadLength);
@@ -401,14 +367,10 @@ char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
*serializedLength = offset + msg->payloadLength;
}
else {
*serializedLength = offset;
*serializedLength = (int)strlen(serializedMessage);
}
return serializedMessage;
fail:
free(serializedMessage);
return NULL;
}
// Free everything in a msg struct
+89 -248
View File
@@ -35,50 +35,21 @@ static int getSerializedAttributeListSize(PSDP_OPTION head) {
currentEntry = currentEntry->next;
}
// Add one for the null terminator
return (int)size + 1;
return (int)size;
}
// Populate the serialized attribute list into a string
static int fillSerializedAttributeList(char* buffer, size_t length, PSDP_OPTION head) {
static int fillSerializedAttributeList(char* buffer, PSDP_OPTION head) {
PSDP_OPTION currentEntry = head;
int offset = 0;
while (currentEntry != NULL) {
int ret = snprintf(&buffer[offset], length, "a=%s:", currentEntry->name);
if (ret > 0 && (size_t)ret < length) {
offset += ret;
length -= ret;
}
else {
LC_ASSERT(false);
break;
}
if ((size_t)currentEntry->payloadLen < length) {
memcpy(&buffer[offset], currentEntry->payload, currentEntry->payloadLen);
offset += currentEntry->payloadLen;
length -= currentEntry->payloadLen;
}
else {
LC_ASSERT(false);
break;
}
ret = snprintf(&buffer[offset], length, " \r\n");
if (ret > 0 && (size_t)ret < length) {
offset += ret;
length -= ret;
}
else {
LC_ASSERT(false);
break;
}
offset += sprintf(&buffer[offset], "a=%s:", currentEntry->name);
memcpy(&buffer[offset], currentEntry->payload, currentEntry->payloadLen);
offset += currentEntry->payloadLen;
offset += sprintf(&buffer[offset], " \r\n");
currentEntry = currentEntry->next;
}
// We should have only space for the null terminator left over
LC_ASSERT(length == 1);
return offset;
}
@@ -91,13 +62,9 @@ static int addAttributeBinary(PSDP_OPTION* head, char* name, const void* payload
return -1;
}
if (!PltSafeStrcpy(option->name, sizeof(option->name), name)) {
free(option);
return -1;
}
option->next = NULL;
option->payloadLen = payloadLen;
strcpy(option->name, name);
option->payload = (void*)(option + 1);
memcpy(option->payload, payload, payloadLen);
@@ -166,83 +133,25 @@ static int addGen4Options(PSDP_OPTION* head, char* addrStr) {
char payloadStr[92];
int err = 0;
LC_ASSERT(RtspPortNumber != 0);
snprintf(payloadStr, sizeof(payloadStr), "rtsp://%s:%u", addrStr, RtspPortNumber);
sprintf(payloadStr, "rtsp://%s:48010", addrStr);
err |= addAttributeString(head, "x-nv-general.serverAddress", payloadStr);
return err;
}
#define NVFF_BASE 0x07
#define NVFF_AUDIO_ENCRYPTION 0x20
#define NVFF_RI_ENCRYPTION 0x80
static int addGen5Options(PSDP_OPTION* head) {
int err = 0;
char payloadStr[32];
// This must be initialized to false already
LC_ASSERT(!AudioEncryptionEnabled);
if (APP_VERSION_AT_LEAST(7, 1, 431)) {
unsigned int featureFlags;
// RI encryption is always enabled
featureFlags = NVFF_BASE | NVFF_RI_ENCRYPTION;
// Enable audio encryption if the client opted in or the host required it
if ((StreamConfig.encryptionFlags & ENCFLG_AUDIO) || (EncryptionFeaturesEnabled & SS_ENC_AUDIO)) {
featureFlags |= NVFF_AUDIO_ENCRYPTION;
AudioEncryptionEnabled = true;
}
snprintf(payloadStr, sizeof(payloadStr), "%u", featureFlags);
err |= addAttributeString(head, "x-nv-general.featureFlags", payloadStr);
// Ask for the encrypted control protocol to ensure remote input will be encrypted.
// This used to be done via separate RI encryption, but now it is all or nothing.
err |= addAttributeString(head, "x-nv-general.useReliableUdp", "13");
// Require at least 2 FEC packets for small frames. If a frame has fewer data shards
// than would generate 2 FEC shards, it will increase the FEC percentage for that frame
// above the set value (even going as high as 200% FEC to generate 2 FEC shards from a
// 1 data shard frame).
err |= addAttributeString(head, "x-nv-vqos[0].fec.minRequiredFecPackets", "2");
// BLL-FEC appears to adjust dynamically based on the loss rate and instantaneous bitrate
// of each frame, however we can't dynamically control it from our side yet. As a result,
// the effective FEC amount is significantly lower (single digit percentages for many
// large frames) and the result is worse performance during packet loss. Disabling BLL-FEC
// results in GFE 3.26 falling back to the legacy FEC method as we would like.
err |= addAttributeString(head, "x-nv-vqos[0].bllFec.enable", "0");
}
else {
// We want to use the new ENet connections for control and input
err |= addAttributeString(head, "x-nv-general.useReliableUdp", "1");
err |= addAttributeString(head, "x-nv-ri.useControlChannel", "1");
// When streaming 4K, lower FEC levels to reduce stream overhead
if (StreamConfig.width >= 3840 && StreamConfig.height >= 2160) {
err |= addAttributeString(head, "x-nv-vqos[0].fec.repairPercent", "5");
}
else {
err |= addAttributeString(head, "x-nv-vqos[0].fec.repairPercent", "20");
}
}
// We want to use the new ENet connections for control and input
err |= addAttributeString(head, "x-nv-general.useReliableUdp", "1");
err |= addAttributeString(head, "x-nv-ri.useControlChannel", "1");
if (APP_VERSION_AT_LEAST(7, 1, 446) && (StreamConfig.width < 720 || StreamConfig.height < 540)) {
// We enable DRC with a static DRC table for very low resoutions on GFE 3.26 to work around
// a bug that causes nvstreamer.exe to crash due to failing to populate a list of valid resolutions.
//
// Despite the fact that the DRC table doesn't include our target streaming resolution, we still
// seem to stream at the target resolution, presumably because we don't send control data to tell
// the host otherwise.
err |= addAttributeString(head, "x-nv-vqos[0].drc.enable", "1");
err |= addAttributeString(head, "x-nv-vqos[0].drc.tableType", "2");
}
else {
// Disable dynamic resolution switching
err |= addAttributeString(head, "x-nv-vqos[0].drc.enable", "0");
// Disable dynamic resolution switching
err |= addAttributeString(head, "x-nv-vqos[0].drc.enable", "0");
// When streaming 4K, lower FEC levels to reduce stream overhead
if (StreamConfig.width >= 3840 && StreamConfig.height >= 2160) {
err |= addAttributeString(head, "x-nv-vqos[0].fec.repairPercent", "5");
}
// Recovery mode can cause the FEC percentage to change mid-frame, which
@@ -258,7 +167,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
int audioChannelCount;
int audioChannelMask;
int err;
int adjustedBitrate;
int bitrate;
// This must have been resolved to either local or remote by now
LC_ASSERT(StreamConfig.streamingRemotely != STREAM_CFG_AUTO);
@@ -266,66 +175,15 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
optionHead = NULL;
err = 0;
if (IS_SUNSHINE()) {
// Send client feature flags to Sunshine hosts
uint32_t moonlightFeatureFlags = ML_FF_FEC_STATUS | ML_FF_SESSION_ID_V1;
snprintf(payloadStr, sizeof(payloadStr), "%u", moonlightFeatureFlags);
err |= addAttributeString(&optionHead, "x-ml-general.featureFlags", payloadStr);
// New-style control stream encryption is low overhead, so we enable it any time it is supported
if (EncryptionFeaturesSupported & SS_ENC_CONTROL_V2) {
EncryptionFeaturesEnabled |= SS_ENC_CONTROL_V2;
}
// If video encryption is supported by the host and desired by the client, use it
if ((EncryptionFeaturesSupported & SS_ENC_VIDEO) && (StreamConfig.encryptionFlags & ENCFLG_VIDEO)) {
EncryptionFeaturesEnabled |= SS_ENC_VIDEO;
}
else if ((EncryptionFeaturesRequested & SS_ENC_VIDEO) && !(StreamConfig.encryptionFlags & ENCFLG_VIDEO)) {
// If video encryption is explicitly requested by the host but *not* by the client,
// we'll encrypt anyway (since we are capable of doing so) and print a warning.
Limelog("Enabling video encryption by host request despite client opt-out. Performance may suffer!");
EncryptionFeaturesEnabled |= SS_ENC_VIDEO;
}
// If audio encryption is supported by the host and desired by the client, use it
if ((EncryptionFeaturesSupported & SS_ENC_AUDIO) && (StreamConfig.encryptionFlags & ENCFLG_AUDIO)) {
EncryptionFeaturesEnabled |= SS_ENC_AUDIO;
}
else if ((EncryptionFeaturesRequested & SS_ENC_AUDIO) && !(StreamConfig.encryptionFlags & ENCFLG_AUDIO)) {
// If audio encryption is explicitly requested by the host but *not* by the client,
// we'll encrypt anyway (since we are capable of doing so) and print a warning.
Limelog("Enabling audio encryption by host request despite client opt-out. Audio quality may suffer!");
EncryptionFeaturesEnabled |= SS_ENC_AUDIO;
}
snprintf(payloadStr, sizeof(payloadStr), "%u", EncryptionFeaturesEnabled);
err |= addAttributeString(&optionHead, "x-ss-general.encryptionEnabled", payloadStr);
// Enable YUV444 if requested
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_YUV444) {
err |= addAttributeString(&optionHead, "x-ss-video[0].chromaSamplingType", "1");
}
else {
err |= addAttributeString(&optionHead, "x-ss-video[0].chromaSamplingType", "0");
}
}
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.width);
sprintf(payloadStr, "%d", StreamConfig.width);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr);
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.height);
sprintf(payloadStr, "%d", StreamConfig.height);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportHt", payloadStr);
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.fps);
sprintf(payloadStr, "%d", StreamConfig.fps);
err |= addAttributeString(&optionHead, "x-nv-video[0].maxFPS", payloadStr);
// Adjust the video packet size to account for encryption overhead
if (EncryptionFeaturesEnabled & SS_ENC_VIDEO) {
LC_ASSERT(StreamConfig.packetSize % 16 == 0);
StreamConfig.packetSize -= sizeof(ENC_VIDEO_HEADER);
LC_ASSERT(StreamConfig.packetSize % 16 == 0);
}
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.packetSize);
sprintf(payloadStr, "%d", StreamConfig.packetSize);
err |= addAttributeString(&optionHead, "x-nv-video[0].packetSize", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-video[0].rateControlMode", "4");
@@ -333,43 +191,44 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
err |= addAttributeString(&optionHead, "x-nv-video[0].timeoutLengthMs", "7000");
err |= addAttributeString(&optionHead, "x-nv-video[0].framesWithInvalidRefThreshold", "0");
// 20% of the video bitrate will added to the user-specified bitrate for FEC
adjustedBitrate = (int)(StreamConfig.bitrate * 0.80);
// Use more strict bitrate logic when streaming remotely. The theory here is that remote
// streaming is much more bandwidth sensitive. Someone might select 5 Mbps because that's
// really all they have, so we need to be careful not to exceed the cap, even counting
// things like audio and control data.
if (StreamConfig.streamingRemotely == STREAM_CFG_REMOTE) {
// 20% of the video bitrate will added to the user-specified bitrate for FEC
bitrate = (int)(OriginalVideoBitrate * 0.80);
// Subtract 500 Kbps to leave room for audio and control. On remote streams,
// GFE will use 96Kbps stereo audio. For local streams, it will choose 512Kbps.
if (adjustedBitrate > 500) {
adjustedBitrate -= 500;
if (bitrate > 500) {
bitrate -= 500;
}
}
else {
bitrate = StreamConfig.bitrate;
}
// If the calculated bitrate (with the HEVC multiplier in effect) is less than this,
// use the lower of the two bitrate values.
bitrate = StreamConfig.bitrate < bitrate ? StreamConfig.bitrate : bitrate;
// GFE currently imposes a limit of 100 Mbps for the video bitrate. It will automatically
// impose that on maximumBitrateKbps but not on initialBitrateKbps. We will impose the cap
// ourselves so initialBitrateKbps does not exceed maximumBitrateKbps.
adjustedBitrate = adjustedBitrate > 100000 ? 100000 : adjustedBitrate;
bitrate = bitrate > 100000 ? 100000 : bitrate;
// We don't support dynamic bitrate scaling properly (it tends to bounce between min and max and never
// settle on the optimal bitrate if it's somewhere in the middle), so we'll just latch the bitrate
// to the requested value.
if (AppVersionQuad[0] >= 5) {
snprintf(payloadStr, sizeof(payloadStr), "%d", adjustedBitrate);
sprintf(payloadStr, "%d", bitrate);
err |= addAttributeString(&optionHead, "x-nv-video[0].initialBitrateKbps", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-video[0].initialPeakBitrateKbps", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrateKbps", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrateKbps", payloadStr);
// Send the configured bitrate to Sunshine hosts, so they can adjust for dynamic FEC percentage
if (IS_SUNSHINE()) {
snprintf(payloadStr, sizeof(payloadStr), "%u", StreamConfig.bitrate);
err |= addAttributeString(&optionHead, "x-ml-video.configuredBitrateKbps", payloadStr);
}
}
else {
if (StreamConfig.streamingRemotely == STREAM_CFG_REMOTE) {
@@ -377,7 +236,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
err |= addAttributeString(&optionHead, "x-nv-video[0].peakBitrate", "4");
}
snprintf(payloadStr, sizeof(payloadStr), "%d", adjustedBitrate);
sprintf(payloadStr, "%d", bitrate);
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrate", payloadStr);
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrate", payloadStr);
}
@@ -427,17 +286,26 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
// If not using slicing, we request 1 slice per frame
slicesPerFrame = 1;
}
snprintf(payloadStr, sizeof(payloadStr), "%d", slicesPerFrame);
sprintf(payloadStr, "%d", slicesPerFrame);
err |= addAttributeString(&optionHead, "x-nv-video[0].videoEncoderSlicesPerFrame", payloadStr);
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_AV1) {
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "2");
}
else if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) {
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) {
err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "1");
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "1");
if (!APP_VERSION_AT_LEAST(7, 1, 408)) {
if (AppVersionQuad[0] >= 7) {
// Enable HDR if requested
if (StreamConfig.enableHdr) {
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "1");
}
else {
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "0");
}
}
if (AppVersionQuad[0] < 7 ||
(AppVersionQuad[0] == 7 && AppVersionQuad[1] < 1) ||
(AppVersionQuad[0] == 7 && AppVersionQuad[1] == 1 && AppVersionQuad[2] < 408)) {
// This disables split frame encode on GFE 3.10 which seems to produce broken
// HEVC output at 1080p60 (full of artifacts even on the SHIELD itself, go figure).
// It now appears to work fine on GFE 3.14.1.
@@ -446,24 +314,23 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
}
}
else {
err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "0");
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "0");
}
if (AppVersionQuad[0] >= 7) {
// Enable HDR if requested
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_10BIT) {
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "1");
}
else {
if (AppVersionQuad[0] >= 7) {
// HDR is not supported on H.264
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "0");
}
// If the decoder supports reference frame invalidation, that indicates it also supports
// the maximum number of reference frames allowed by the codec. Even if we can't use RFI
// due to lack of host support, we can still allow the host to pick a number of reference
// frames greater than 1 to improve encoding efficiency.
if (isReferenceFrameInvalidationSupportedByDecoder()) {
// We shouldn't be able to reach this path with enableHdr set. If we did, that means
// the server or client doesn't support HEVC and the client didn't do the correct checks
// before requesting HDR streaming.
LC_ASSERT(!StreamConfig.enableHdr);
}
if (AppVersionQuad[0] >= 7) {
if (isReferenceFrameInvalidationEnabled()) {
err |= addAttributeString(&optionHead, "x-nv-video[0].maxNumReferenceFrames", "0");
}
else {
@@ -473,13 +340,13 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
err |= addAttributeString(&optionHead, "x-nv-video[0].maxNumReferenceFrames", "1");
}
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.clientRefreshRateX100);
sprintf(payloadStr, "%d", StreamConfig.clientRefreshRateX100);
err |= addAttributeString(&optionHead, "x-nv-video[0].clientRefreshRateX100", payloadStr);
}
snprintf(payloadStr, sizeof(payloadStr), "%d", audioChannelCount);
sprintf(payloadStr, "%d", audioChannelCount);
err |= addAttributeString(&optionHead, "x-nv-audio.surround.numChannels", payloadStr);
snprintf(payloadStr, sizeof(payloadStr), "%d", audioChannelMask);
sprintf(payloadStr, "%d", audioChannelMask);
err |= addAttributeString(&optionHead, "x-nv-audio.surround.channelMask", payloadStr);
if (audioChannelCount > 2) {
err |= addAttributeString(&optionHead, "x-nv-audio.surround.enable", "1");
@@ -490,7 +357,8 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
}
if (AppVersionQuad[0] >= 7) {
if (StreamConfig.bitrate >= HIGH_AUDIO_BITRATE_THRESHOLD && audioChannelCount > 2 &&
// Decide to use HQ audio based on the original video bitrate, not the HEVC-adjusted value
if (OriginalVideoBitrate >= HIGH_AUDIO_BITRATE_THRESHOLD && audioChannelCount > 2 &&
HighQualitySurroundSupported && (AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) == 0) {
// Enable high quality mode for surround sound
err |= addAttributeString(&optionHead, "x-nv-audio.surround.AudioQuality", "1");
@@ -506,10 +374,13 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
err |= addAttributeString(&optionHead, "x-nv-audio.surround.AudioQuality", "0");
HighQualitySurroundEnabled = false;
if ((AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) ||
((AudioCallbacks.capabilities & CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION) != 0 &&
StreamConfig.bitrate < LOW_AUDIO_BITRATE_TRESHOLD)) {
// Use 10 ms packets for slow devices and networks to balance latency and bandwidth usage
if ((AudioCallbacks.capabilities & CAPABILITY_SLOW_OPUS_DECODER) != 0) {
// Use 20 ms packets for slow decoders to save CPU time
AudioPacketDuration = 20;
}
else if ((AudioCallbacks.capabilities & CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION) != 0 &&
OriginalVideoBitrate < LOW_AUDIO_BITRATE_TRESHOLD) {
// Use 10 ms packets for slow networks to balance latency and bandwidth usage
AudioPacketDuration = 10;
}
else {
@@ -518,7 +389,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
}
}
snprintf(payloadStr, sizeof(payloadStr), "%d", AudioPacketDuration);
sprintf(payloadStr, "%d", AudioPacketDuration);
err |= addAttributeString(&optionHead, "x-nv-aqos.packetDuration", payloadStr);
}
else {
@@ -530,7 +401,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
}
if (AppVersionQuad[0] >= 7) {
snprintf(payloadStr, sizeof(payloadStr), "%d", (StreamConfig.colorSpace << 1) | StreamConfig.colorRange);
sprintf(payloadStr, "%d", (StreamConfig.colorSpace << 1) | StreamConfig.colorRange);
err |= addAttributeString(&optionHead, "x-nv-video[0].encoderCscMode", payloadStr);
}
@@ -543,8 +414,8 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
}
// Populate the SDP header with required information
static int fillSdpHeader(char* buffer, size_t length, int rtspClientVersion, char*urlSafeAddr) {
return snprintf(buffer, length,
static int fillSdpHeader(char* buffer, int rtspClientVersion, char*urlSafeAddr) {
return sprintf(buffer,
"v=0\r\n"
"o=android 0 %d IN %s %s\r\n"
"s=NVIDIA Streaming Client\r\n",
@@ -554,67 +425,37 @@ static int fillSdpHeader(char* buffer, size_t length, int rtspClientVersion, cha
}
// Populate the SDP tail with required information
static int fillSdpTail(char* buffer, size_t length) {
LC_ASSERT(VideoPortNumber != 0);
return snprintf(buffer, length,
static int fillSdpTail(char* buffer) {
return sprintf(buffer,
"t=0 0\r\n"
"m=video %d \r\n",
AppVersionQuad[0] < 4 ? 47996 : VideoPortNumber);
AppVersionQuad[0] < 4 ? 47996 : 47998);
}
// Get the SDP attributes for the stream config
char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length) {
PSDP_OPTION attributeList;
int attributeListSize;
int offset, written;
int offset;
char* payload;
char urlSafeAddr[URLSAFESTRING_LEN];
addrToUrlSafeString(&RemoteAddr, urlSafeAddr, sizeof(urlSafeAddr));
addrToUrlSafeString(&RemoteAddr, urlSafeAddr);
attributeList = getAttributesList(urlSafeAddr);
if (attributeList == NULL) {
return NULL;
}
attributeListSize = getSerializedAttributeListSize(attributeList);
payload = malloc(MAX_SDP_HEADER_LEN + MAX_SDP_TAIL_LEN + attributeListSize);
payload = malloc(MAX_SDP_HEADER_LEN + MAX_SDP_TAIL_LEN +
getSerializedAttributeListSize(attributeList));
if (payload == NULL) {
freeAttributeList(attributeList);
return NULL;
}
offset = 0;
written = fillSdpHeader(payload, MAX_SDP_HEADER_LEN, rtspClientVersion, urlSafeAddr);
if (written < 0 || written >= MAX_SDP_HEADER_LEN) {
LC_ASSERT(false);
free(payload);
freeAttributeList(attributeList);
return NULL;
}
else {
offset += written;
}
written = fillSerializedAttributeList(&payload[offset], attributeListSize, attributeList);
if (written < 0 || written >= attributeListSize) {
LC_ASSERT(false);
free(payload);
freeAttributeList(attributeList);
return NULL;
}
else {
offset += written;
}
written = fillSdpTail(&payload[offset], MAX_SDP_TAIL_LEN);
if (written < 0 || written >= MAX_SDP_TAIL_LEN) {
LC_ASSERT(false);
free(payload);
freeAttributeList(attributeList);
return NULL;
}
else {
offset += written;
}
offset = fillSdpHeader(payload, rtspClientVersion, urlSafeAddr);
offset += fillSerializedAttributeList(&payload[offset], attributeList);
offset += fillSdpTail(&payload[offset]);
freeAttributeList(attributeList);
*length = offset;
+6 -4
View File
@@ -1,5 +1,7 @@
#include "Limelight-internal.h"
#include <openssl/rand.h>
#define STUN_RECV_TIMEOUT_SEC 3
#define STUN_MESSAGE_BINDING_REQUEST 0x0001
@@ -65,7 +67,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
snprintf(stunPortStr, sizeof(stunPortStr), "%u", stunPort);
sprintf(stunPortStr, "%u", stunPort);
err = getaddrinfo(stunServer, stunPortStr, &hints, &stunAddrs);
if (err != 0 || stunAddrs == NULL) {
Limelog("Failed to resolve STUN server: %d\n", err);
@@ -73,7 +75,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
goto Exit;
}
sock = bindUdpSocket(hints.ai_family, NULL, 0, 0, SOCK_QOS_TYPE_BEST_EFFORT);
sock = bindUdpSocket(hints.ai_family, 2048);
if (sock == INVALID_SOCKET) {
err = LastSocketFail();
Limelog("Failed to connect to STUN server: %d\n", err);
@@ -83,7 +85,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
reqMsg.messageType = htons(STUN_MESSAGE_BINDING_REQUEST);
reqMsg.messageLength = 0;
reqMsg.magicCookie = htonl(STUN_MESSAGE_COOKIE);
PltGenerateRandomData(reqMsg.transactionId, sizeof(reqMsg.transactionId));
RAND_bytes(reqMsg.transactionId, sizeof(reqMsg.transactionId));
bytesRead = SOCKET_ERROR;
for (i = 0; i < STUN_RECV_TIMEOUT_SEC * 1000 / UDP_RECV_POLL_TIMEOUT_MS && bytesRead <= 0; i++) {
@@ -93,7 +95,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
// Send a request to each resolved address but stop if we get a response
for (current = stunAddrs; current != NULL && bytesRead <= 0; current = current->ai_next) {
err = (int)sendto(sock, (char *)&reqMsg, sizeof(reqMsg), 0, current->ai_addr, (SOCKADDR_LEN)current->ai_addrlen);
err = (int)sendto(sock, (char *)&reqMsg, sizeof(reqMsg), 0, current->ai_addr, current->ai_addrlen);
if (err == SOCKET_ERROR) {
err = LastSocketFail();
Limelog("Failed to send STUN binding request: %d\n", err);
+13 -61
View File
@@ -7,31 +7,21 @@ typedef struct _QUEUED_DECODE_UNIT {
LINKED_BLOCKING_QUEUE_ENTRY entry;
} QUEUED_DECODE_UNIT, *PQUEUED_DECODE_UNIT;
#pragma pack(push, 1)
void completeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu, int drStatus);
bool getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu);
// The encrypted video header must be a multiple
// of 16 bytes in size to ensure the block size
// for FEC stays a multiple of 16 too.
typedef struct _ENC_VIDEO_HEADER {
uint8_t iv[12];
uint32_t frameNumber;
uint8_t tag[16];
} ENC_VIDEO_HEADER, *PENC_VIDEO_HEADER;
#pragma pack(push, 1)
#define FLAG_CONTAINS_PIC_DATA 0x1
#define FLAG_EOF 0x2
#define FLAG_SOF 0x4
#define NV_VIDEO_PACKET_EXTRA_FLAG_LTR_FRAME 0x1
typedef struct _NV_VIDEO_PACKET {
uint32_t streamPacketIndex;
uint32_t frameIndex;
uint8_t flags;
uint8_t extraFlags;
uint8_t multiFecFlags;
uint8_t multiFecBlocks;
uint32_t fecInfo;
unsigned int streamPacketIndex;
unsigned int frameIndex;
char flags;
char reserved[3];
int fecInfo;
} NV_VIDEO_PACKET, *PNV_VIDEO_PACKET;
#define FLAG_EXTENSION 0x10
@@ -40,49 +30,11 @@ typedef struct _NV_VIDEO_PACKET {
#define MAX_RTP_HEADER_SIZE 16
typedef struct _RTP_PACKET {
uint8_t header;
uint8_t packetType;
uint16_t sequenceNumber;
uint32_t timestamp;
uint32_t ssrc;
char header;
char packetType;
unsigned short sequenceNumber;
unsigned int timestamp;
unsigned int ssrc;
} RTP_PACKET, *PRTP_PACKET;
// Fields are big-endian
typedef struct _SS_PING {
char payload[16];
uint32_t sequenceNumber;
} SS_PING, *PSS_PING;
// Fields are big-endian
#define SS_FRAME_FEC_PTYPE 0x5502
typedef struct _SS_FRAME_FEC_STATUS {
uint32_t frameIndex;
uint16_t highestReceivedSequenceNumber;
uint16_t nextContiguousSequenceNumber;
uint16_t missingPacketsBeforeHighestReceived;
uint16_t totalDataPackets;
uint16_t totalParityPackets;
uint16_t receivedDataPackets;
uint16_t receivedParityPackets;
uint8_t fecPercentage;
uint8_t multiFecBlockIndex;
uint8_t multiFecBlockCount;
} SS_FRAME_FEC_STATUS, *PSS_FRAME_FEC_STATUS;
// Fields are little-endian
#define SS_LTR_FRAME_ACK_PTYPE 0x0350
typedef struct _SS_LTR_FRAME_ACK {
uint32_t frameIndex;
uint32_t reserved;
} SS_LTR_FRAME_ACK, *PSS_LTR_FRAME_ACK;
// Fields are little-endian
#define SS_RFI_REQUEST_PTYPE 0x0301
typedef struct _SS_RFI_REQUEST {
uint32_t firstFrameIndex;
uint32_t reserved1;
uint32_t lastFrameIndex;
uint32_t reserved2[3];
} SS_RFI_REQUEST, *PSS_RFI_REQUEST;
#pragma pack(pop)
+226 -720
View File
File diff suppressed because it is too large Load Diff
+71 -145
View File
@@ -1,44 +1,39 @@
#include "Limelight-internal.h"
#include "PlatformSockets.h"
#include "PlatformThreads.h"
#include "RtpFecQueue.h"
#define FIRST_FRAME_MAX 1500
#define FIRST_FRAME_TIMEOUT_SEC 10
#define RTP_PORT 47998
#define FIRST_FRAME_PORT 47996
static RTP_VIDEO_QUEUE rtpQueue;
#define RTP_RECV_BUFFER (512 * 1024)
static RTP_FEC_QUEUE rtpQueue;
static SOCKET rtpSocket = INVALID_SOCKET;
static SOCKET firstFrameSocket = INVALID_SOCKET;
static PPLT_CRYPTO_CONTEXT decryptionCtx;
static PLT_THREAD udpPingThread;
static PLT_THREAD receiveThread;
static PLT_THREAD decoderThread;
static bool receivedDataFromPeer;
static uint64_t firstDataTimeMs;
static bool receivedFullFrame;
static atomic_bool receivedFullFrame;
// We can't request an IDR frame until the depacketizer knows
// that a packet was lost. This timeout bounds the time that
// the RTP queue will wait for missing/reordered packets.
#define RTP_QUEUE_DELAY 10
// This is the desired number of video packets that can be
// stored in the socket's receive buffer. 2048 is chosen
// because it should be large enough for all reasonable
// frame sizes (probably 2 or 3 frames) without using too
// much kernel memory with larger packet sizes. It also
// can smooth over transient pauses in network traffic
// and subsequent packet/frame bursts that follow.
#define RTP_RECV_PACKETS_BUFFERED 2048
// Initialize the video stream
void initializeVideoStream(void) {
initializeVideoDepacketizer(StreamConfig.packetSize);
RtpvInitializeQueue(&rtpQueue);
decryptionCtx = PltCreateCryptoContext();
RtpfInitializeQueue(&rtpQueue); //TODO RTP_QUEUE_DELAY
receivedDataFromPeer = false;
firstDataTimeMs = 0;
receivedFullFrame = false;
@@ -46,35 +41,25 @@ void initializeVideoStream(void) {
// Clean up the video stream
void destroyVideoStream(void) {
PltDestroyCryptoContext(decryptionCtx);
destroyVideoDepacketizer();
RtpvCleanupQueue(&rtpQueue);
RtpfCleanupQueue(&rtpQueue);
}
// UDP Ping proc
static void VideoPingThreadProc(void* context) {
char legacyPingData[] = { 0x50, 0x49, 0x4E, 0x47 };
LC_SOCKADDR saddr;
LC_ASSERT(VideoPortNumber != 0);
static void UdpPingThreadProc(void* context) {
char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
struct sockaddr_in6 saddr;
SOCK_RET err;
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
SET_PORT(&saddr, VideoPortNumber);
saddr.sin6_port = htons(RTP_PORT);
// We do not check for errors here. Socket errors will be handled
// on the read-side in ReceiveThreadProc(). This avoids potential
// issues related to receiving ICMP port unreachable messages due
// to sending a packet prior to the host PC binding to that port.
int pingCount = 0;
while (!PltIsThreadInterrupted(&udpPingThread)) {
if (VideoPingPayload.payload[0] != 0) {
pingCount++;
VideoPingPayload.sequenceNumber = BE32(pingCount);
sendto(rtpSocket, (char*)&VideoPingPayload, sizeof(VideoPingPayload), 0, (struct sockaddr*)&saddr, AddrLen);
}
else {
sendto(rtpSocket, legacyPingData, sizeof(legacyPingData), 0, (struct sockaddr*)&saddr, AddrLen);
err = sendto(rtpSocket, pingData, sizeof(pingData), 0, (struct sockaddr*)&saddr, RemoteAddrLen);
if (err != sizeof(pingData)) {
Limelog("Video Ping: send() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return;
}
PltSleepMsInterruptible(&udpPingThread, 500);
@@ -82,21 +67,16 @@ static void VideoPingThreadProc(void* context) {
}
// Receive thread proc
static void VideoReceiveThreadProc(void* context) {
static void ReceiveThreadProc(void* context) {
int err;
int bufferSize, receiveSize, decryptedSize, minSize;
int bufferSize, receiveSize;
char* buffer;
char* encryptedBuffer;
int queueStatus;
bool useSelect;
int waitingForVideoMs;
bool encrypted;
encrypted = !!(EncryptionFeaturesEnabled & SS_ENC_VIDEO);
decryptedSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
minSize = sizeof(RTP_PACKET) + ((EncryptionFeaturesEnabled & SS_ENC_VIDEO) ? sizeof(ENC_VIDEO_HEADER) : 0);
receiveSize = decryptedSize + ((EncryptionFeaturesEnabled & SS_ENC_VIDEO) ? sizeof(ENC_VIDEO_HEADER) : 0);
bufferSize = decryptedSize + sizeof(RTPV_QUEUE_ENTRY);
receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
bufferSize = receiveSize + sizeof(RTPFEC_QUEUE_ENTRY);
buffer = NULL;
if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) {
@@ -108,19 +88,6 @@ static void VideoReceiveThreadProc(void* context) {
useSelect = false;
}
// Allocate a staging buffer to use for each received packet
if (encrypted) {
encryptedBuffer = (char*)malloc(receiveSize);
if (encryptedBuffer == NULL) {
Limelog("Video Receive: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1);
return;
}
}
else {
encryptedBuffer = NULL;
}
waitingForVideoMs = 0;
while (!PltIsThreadInterrupted(&receiveThread)) {
PRTP_PACKET packet;
@@ -130,14 +97,11 @@ static void VideoReceiveThreadProc(void* context) {
if (buffer == NULL) {
Limelog("Video Receive: malloc() failed\n");
ListenerCallbacks.connectionTerminated(-1);
break;
return;
}
}
err = recvUdpSocket(rtpSocket,
encrypted ? encryptedBuffer : buffer,
receiveSize,
useSelect);
err = recvUdpSocket(rtpSocket, buffer, receiveSize, useSelect);
if (err < 0) {
Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
@@ -154,7 +118,7 @@ static void VideoReceiveThreadProc(void* context) {
break;
}
}
// Receive timed out; try again
continue;
}
@@ -166,68 +130,23 @@ static void VideoReceiveThreadProc(void* context) {
firstDataTimeMs = PltGetMillis();
}
#ifndef LC_FUZZING
if (!receivedFullFrame) {
if (PltGetMillis() - firstDataTimeMs >= FIRST_FRAME_TIMEOUT_SEC * 1000) {
uint64_t now = PltGetMillis();
if (now - firstDataTimeMs >= FIRST_FRAME_TIMEOUT_SEC * 1000) {
Limelog("Terminating connection due to lack of a successful video frame\n");
ListenerCallbacks.connectionTerminated(ML_ERROR_NO_VIDEO_FRAME);
break;
}
}
#endif
if (err < minSize) {
// Runt packet
continue;
}
// Decrypt the packet into the buffer if encryption is enabled
if (encrypted) {
PENC_VIDEO_HEADER encHeader = (PENC_VIDEO_HEADER)encryptedBuffer;
// If this frame is below our current frame number, discard it before decryption
// to save CPU cycles decrypting FEC shards for a frame we already reassembled.
//
// Since this is happening _before_ decryption, this packet is not trusted yet.
// It's imperative that we do not mutate any state based on this packet until
// after it has been decrypted successfully!
//
// It's possible for an attacker to inject a fake packet that has any value of
// header fields they want, however this provides them no benefit because we will
// simply drop said packet here (if it's below the current frame number) or it
// will pass this check and be dropped during decryption (if contents is tampered)
// or after decryption in the RTP queue (if it's a replay of a previous authentic
// packet from the host).
//
// In short, an attacker spoofing this value via MITM or sending malicious values
// impersonating the host from off-link doesn't gain them anything. If they have
// a true MITM, they can DoS our connection by just dropping all our traffic, so
// tampering with packets to fail this check doesn't accomplish anything they
// couldn't already do. If they're not on-link, we just throw their malicious
// traffic away (as mentioned in the paragraph above) and continue accepting
// legitmate video traffic.
if (encHeader->frameNumber && LE32(encHeader->frameNumber) < RtpvGetCurrentFrameNumber(&rtpQueue)) {
continue;
}
if (!PltDecryptMessage(decryptionCtx, ALGORITHM_AES_GCM, 0,
(unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
encHeader->iv, sizeof(encHeader->iv),
encHeader->tag, sizeof(encHeader->tag),
((unsigned char*)(encHeader + 1)), err - sizeof(ENC_VIDEO_HEADER), // The ciphertext is after the header
(unsigned char*)buffer, &err)) {
Limelog("Failed to decrypt video packet!\n");
continue;
}
}
// Convert fields to host byte-order
packet = (PRTP_PACKET)&buffer[0];
packet->sequenceNumber = BE16(packet->sequenceNumber);
packet->timestamp = BE32(packet->timestamp);
packet->ssrc = BE32(packet->ssrc);
packet->sequenceNumber = htons(packet->sequenceNumber);
packet->timestamp = htonl(packet->timestamp);
packet->ssrc = htonl(packet->ssrc);
queueStatus = RtpvAddPacket(&rtpQueue, packet, err, (PRTPV_QUEUE_ENTRY)&buffer[decryptedSize]);
queueStatus = RtpfAddPacket(&rtpQueue, packet, err, (PRTPFEC_QUEUE_ENTRY)&buffer[receiveSize]);
if (queueStatus == RTPF_RET_QUEUED) {
// The queue owns the buffer
@@ -238,28 +157,26 @@ static void VideoReceiveThreadProc(void* context) {
if (buffer != NULL) {
free(buffer);
}
if (encryptedBuffer != NULL) {
free(encryptedBuffer);
}
}
void notifyKeyFrameReceived(void) {
void submitFrame(PQUEUED_DECODE_UNIT qdu) {
// Pass the frame to the decoder
int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit);
completeQueuedDecodeUnit(qdu, ret);
// Remember that we got a full frame successfully
receivedFullFrame = true;
}
// Decoder thread proc
static void VideoDecoderThreadProc(void* context) {
static void DecoderThreadProc(void* context) {
PQUEUED_DECODE_UNIT qdu;
while (!PltIsThreadInterrupted(&decoderThread)) {
VIDEO_FRAME_HANDLE frameHandle;
PDECODE_UNIT decodeUnit;
if (!LiWaitForNextVideoFrame(&frameHandle, &decodeUnit)) {
if (!getNextQueuedDecodeUnit(&qdu)) {
return;
}
LiCompleteVideoFrame(frameHandle, VideoCallbacks.submitDecodeUnit(decodeUnit));
submitFrame(qdu);
}
}
@@ -284,10 +201,10 @@ void stopVideoStream(void) {
// Wake up client code that may be waiting on the decode unit queue
stopVideoDepacketizer();
PltInterruptThread(&udpPingThread);
PltInterruptThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltInterruptThread(&decoderThread);
}
@@ -297,10 +214,16 @@ void stopVideoStream(void) {
PltJoinThread(&udpPingThread);
PltJoinThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&udpPingThread);
PltCloseThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
if (firstFrameSocket != INVALID_SOCKET) {
closeSocket(firstFrameSocket);
firstFrameSocket = INVALID_SOCKET;
@@ -328,9 +251,7 @@ int startVideoStream(void* rendererContext, int drFlags) {
return err;
}
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, &LocalAddr, AddrLen,
RTP_RECV_PACKETS_BUFFERED * (StreamConfig.packetSize + MAX_RTP_HEADER_SIZE),
SOCK_QOS_TYPE_VIDEO);
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER);
if (rtpSocket == INVALID_SOCKET) {
VideoCallbacks.cleanup();
return LastSocketError();
@@ -338,7 +259,7 @@ int startVideoStream(void* rendererContext, int drFlags) {
VideoCallbacks.start();
err = PltCreateThread("VideoRecv", VideoReceiveThreadProc, NULL, &receiveThread);
err = PltCreateThread("VideoRecv", ReceiveThreadProc, NULL, &receiveThread);
if (err != 0) {
VideoCallbacks.stop();
closeSocket(rtpSocket);
@@ -346,12 +267,13 @@ int startVideoStream(void* rendererContext, int drFlags) {
return err;
}
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
err = PltCreateThread("VideoDec", VideoDecoderThreadProc, NULL, &decoderThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
err = PltCreateThread("VideoDec", DecoderThreadProc, NULL, &decoderThread);
if (err != 0) {
VideoCallbacks.stop();
PltInterruptThread(&receiveThread);
PltJoinThread(&receiveThread);
PltCloseThread(&receiveThread);
closeSocket(rtpSocket);
VideoCallbacks.cleanup();
return err;
@@ -360,19 +282,23 @@ int startVideoStream(void* rendererContext, int drFlags) {
if (AppVersionQuad[0] == 3) {
// Connect this socket to open port 47998 for our ping thread
firstFrameSocket = connectTcpSocket(&RemoteAddr, AddrLen,
firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC);
if (firstFrameSocket == INVALID_SOCKET) {
VideoCallbacks.stop();
stopVideoDepacketizer();
PltInterruptThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltInterruptThread(&decoderThread);
}
PltJoinThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
closeSocket(rtpSocket);
VideoCallbacks.cleanup();
return LastSocketError();
@@ -381,18 +307,22 @@ int startVideoStream(void* rendererContext, int drFlags) {
// Start pinging before reading the first frame so GFE knows where
// to send UDP data
err = PltCreateThread("VideoPing", VideoPingThreadProc, NULL, &udpPingThread);
err = PltCreateThread("VideoPing", UdpPingThreadProc, NULL, &udpPingThread);
if (err != 0) {
VideoCallbacks.stop();
stopVideoDepacketizer();
PltInterruptThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltInterruptThread(&decoderThread);
}
PltJoinThread(&receiveThread);
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltJoinThread(&decoderThread);
}
PltCloseThread(&receiveThread);
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
PltCloseThread(&decoderThread);
}
closeSocket(rtpSocket);
if (firstFrameSocket != INVALID_SOCKET) {
closeSocket(firstFrameSocket);
@@ -413,7 +343,3 @@ int startVideoStream(void* rendererContext, int drFlags) {
return 0;
}
const RTP_VIDEO_STATS* LiGetRTPVideoStats(void) {
return &rtpQueue.stats;
}
-191
View File
@@ -1,191 +0,0 @@
/**
* @file src/rswrapper.c
* @brief Wrappers for nanors vectorization with different ISA options
*/
// _FORTIY_SOURCE can cause some versions of GCC to try to inline
// memset() with incompatible target options when compiling rs.c
#ifdef _FORTIFY_SOURCE
#undef _FORTIFY_SOURCE
#endif
// The assert() function is decorated with __cold on macOS which
// is incompatible with Clang's target multiversioning feature
#ifndef NDEBUG
#define NDEBUG
#endif
#define DECORATE_FUNC_I(a, b) a##b
#define DECORATE_FUNC(a, b) DECORATE_FUNC_I(a, b)
// Append an ISA suffix to the public RS API
#define reed_solomon_init DECORATE_FUNC(reed_solomon_init, ISA_SUFFIX)
#define reed_solomon_new DECORATE_FUNC(reed_solomon_new, ISA_SUFFIX)
#define reed_solomon_new_static DECORATE_FUNC(reed_solomon_new_static, ISA_SUFFIX)
#define reed_solomon_release DECORATE_FUNC(reed_solomon_release, ISA_SUFFIX)
#define reed_solomon_decode DECORATE_FUNC(reed_solomon_decode, ISA_SUFFIX)
#define reed_solomon_encode DECORATE_FUNC(reed_solomon_encode, ISA_SUFFIX)
// Append an ISA suffix to internal functions to prevent multiple definition errors
#define obl_axpy_ref DECORATE_FUNC(obl_axpy_ref, ISA_SUFFIX)
#define obl_scal_ref DECORATE_FUNC(obl_scal_ref, ISA_SUFFIX)
#define obl_axpyb32_ref DECORATE_FUNC(obl_axpyb32_ref, ISA_SUFFIX)
#define obl_axpy DECORATE_FUNC(obl_axpy, ISA_SUFFIX)
#define obl_scal DECORATE_FUNC(obl_scal, ISA_SUFFIX)
#define obl_swap DECORATE_FUNC(obl_swap, ISA_SUFFIX)
#define obl_axpyb32 DECORATE_FUNC(obl_axpyb32, ISA_SUFFIX)
#define axpy DECORATE_FUNC(axpy, ISA_SUFFIX)
#define scal DECORATE_FUNC(scal, ISA_SUFFIX)
#define gemm DECORATE_FUNC(gemm, ISA_SUFFIX)
#define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX)
#if defined(__x86_64__) || defined(__i386__) || (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)))
// Compile a variant for SSSE3
#if defined(__clang__)
#pragma clang attribute push(__attribute__((target("ssse3"))), apply_to = function)
#elif __GNUC__
#pragma GCC push_options
#pragma GCC target("ssse3")
#endif
#define ISA_SUFFIX _ssse3
#define OBLAS_SSE3
#include "../nanors/rs.c"
#undef OBLAS_SSE3
#undef ISA_SUFFIX
#if defined(__clang__)
#pragma clang attribute pop
#elif __GNUC__
#pragma GCC pop_options
#endif
// Compile a variant for AVX2
#if defined(__clang__)
#pragma clang attribute push(__attribute__((target("avx2"))), apply_to = function)
#elif __GNUC__
#pragma GCC push_options
#pragma GCC target("avx2")
#endif
#define ISA_SUFFIX _avx2
#define OBLAS_AVX2
#include "../nanors/rs.c"
#undef OBLAS_AVX2
#undef ISA_SUFFIX
#if defined(__clang__)
#pragma clang attribute pop
#elif __GNUC__
#pragma GCC pop_options
#endif
// Compile a variant for AVX512BW
#if defined(__clang__)
#pragma clang attribute push(__attribute__((target("avx512f,avx512bw"))), apply_to = function)
#elif __GNUC__
#pragma GCC push_options
#pragma GCC target("avx512f,avx512bw")
#endif
#define ISA_SUFFIX _avx512
#define OBLAS_AVX512
#include "../nanors/rs.c"
#undef OBLAS_AVX512
#undef ISA_SUFFIX
#if defined(__clang__)
#pragma clang attribute pop
#elif __GNUC__
#pragma GCC pop_options
#endif
#endif
// Compile a default variant
#define ISA_SUFFIX _def
#include "../nanors/deps/obl/autoshim.h"
#include "../nanors/rs.c"
#undef ISA_SUFFIX
#undef reed_solomon_init
#undef reed_solomon_new
#undef reed_solomon_new_static
#undef reed_solomon_release
#undef reed_solomon_decode
#undef reed_solomon_encode
#include "rswrapper.h"
reed_solomon_new_t reed_solomon_new_fn;
reed_solomon_release_t reed_solomon_release_fn;
reed_solomon_encode_t reed_solomon_encode_fn;
reed_solomon_decode_t reed_solomon_decode_fn;
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
#if defined(_M_AMD64)
// For some reason this is needed to avoid a "C1189 No target architecture" error from winnt.h
# define _AMD64_
#endif
#include <processthreadsapi.h>
BOOL _msc_supports_ssse3(void) { return IsProcessorFeaturePresent(PF_SSSE3_INSTRUCTIONS_AVAILABLE); }
BOOL _msc_supports_avx2(void) { return IsProcessorFeaturePresent(PF_AVX2_INSTRUCTIONS_AVAILABLE); }
BOOL _msc_supports_avx512f(void) { return IsProcessorFeaturePresent(PF_AVX512F_INSTRUCTIONS_AVAILABLE); }
#endif
/**
* @brief This initializes the RS function pointers to the best vectorized version available.
* @details The streaming code will directly invoke these function pointers during encoding.
*/
void reed_solomon_init(void) {
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
// Visual Studio
if (_msc_supports_avx512f()) {
reed_solomon_new_fn = reed_solomon_new_avx512;
reed_solomon_release_fn = reed_solomon_release_avx512;
reed_solomon_encode_fn = reed_solomon_encode_avx512;
reed_solomon_decode_fn = reed_solomon_decode_avx512;
reed_solomon_init_avx512();
} else if (_msc_supports_avx2()) {
reed_solomon_new_fn = reed_solomon_new_avx2;
reed_solomon_release_fn = reed_solomon_release_avx2;
reed_solomon_encode_fn = reed_solomon_encode_avx2;
reed_solomon_decode_fn = reed_solomon_decode_avx2;
reed_solomon_init_avx2();
} else if (_msc_supports_ssse3()) {
reed_solomon_new_fn = reed_solomon_new_ssse3;
reed_solomon_release_fn = reed_solomon_release_ssse3;
reed_solomon_encode_fn = reed_solomon_encode_ssse3;
reed_solomon_decode_fn = reed_solomon_decode_ssse3;
reed_solomon_init_ssse3();
} else
#elif defined(__x86_64__)
// gcc & clang
if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw")) {
reed_solomon_new_fn = reed_solomon_new_avx512;
reed_solomon_release_fn = reed_solomon_release_avx512;
reed_solomon_encode_fn = reed_solomon_encode_avx512;
reed_solomon_decode_fn = reed_solomon_decode_avx512;
reed_solomon_init_avx512();
} else if (__builtin_cpu_supports("avx2")) {
reed_solomon_new_fn = reed_solomon_new_avx2;
reed_solomon_release_fn = reed_solomon_release_avx2;
reed_solomon_encode_fn = reed_solomon_encode_avx2;
reed_solomon_decode_fn = reed_solomon_decode_avx2;
reed_solomon_init_avx2();
} else if (__builtin_cpu_supports("ssse3")) {
reed_solomon_new_fn = reed_solomon_new_ssse3;
reed_solomon_release_fn = reed_solomon_release_ssse3;
reed_solomon_encode_fn = reed_solomon_encode_ssse3;
reed_solomon_decode_fn = reed_solomon_decode_ssse3;
reed_solomon_init_ssse3();
} else
#endif
//
{
reed_solomon_new_fn = reed_solomon_new_def;
reed_solomon_release_fn = reed_solomon_release_def;
reed_solomon_encode_fn = reed_solomon_encode_def;
reed_solomon_decode_fn = reed_solomon_decode_def;
reed_solomon_init_def();
}
}
-32
View File
@@ -1,32 +0,0 @@
/**
* @file src/rswrapper.h
* @brief Wrappers for nanors vectorization
* @details This is a drop-in replacement for nanors rs.h
*/
#pragma once
// standard includes
#include <stdint.h>
typedef struct _reed_solomon reed_solomon;
typedef reed_solomon *(*reed_solomon_new_t)(int data_shards, int parity_shards);
typedef void (*reed_solomon_release_t)(reed_solomon *rs);
typedef int (*reed_solomon_encode_t)(reed_solomon *rs, uint8_t **shards, int nr_shards, int bs);
typedef int (*reed_solomon_decode_t)(reed_solomon *rs, uint8_t **shards, uint8_t *marks, int nr_shards, int bs);
extern reed_solomon_new_t reed_solomon_new_fn;
extern reed_solomon_release_t reed_solomon_release_fn;
extern reed_solomon_encode_t reed_solomon_encode_fn;
extern reed_solomon_decode_t reed_solomon_decode_fn;
#define reed_solomon_new reed_solomon_new_fn
#define reed_solomon_release reed_solomon_release_fn
#define reed_solomon_encode reed_solomon_encode_fn
#define reed_solomon_decode reed_solomon_decode_fn
/**
* @brief This initializes the RS function pointers to the best vectorized version available.
* @details The streaming code will directly invoke these function pointers during encoding.
*/
void reed_solomon_init(void);