Compare commits
456 Commits
safestring
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2600beaf13 | |||
| 7b026e77be | |||
| 7022b337a9 | |||
| 62687809b1 | |||
| 3fa9191330 | |||
| b187204769 | |||
| 3872285d8c | |||
| a063522db4 | |||
| 5551d29ba2 | |||
| de364b6ecd | |||
| 1d0e91d91a | |||
| b6b95b68b7 | |||
| 6250fa29ee | |||
| 07c32c80f9 | |||
| 305993b013 | |||
| 2a5a1f3e8a | |||
| 435bc6a5a4 | |||
| 3a377e7d7b | |||
| 0586f3d65f | |||
| b126e481a1 | |||
| 20c05eda6a | |||
| 2d984f4b0d | |||
| e59a5f5b53 | |||
| 1c86405fd0 | |||
| e356b2cfde | |||
| fdd026518c | |||
| a3ebaaf015 | |||
| 82ee2d6590 | |||
| 5f2280183c | |||
| 0975a8684f | |||
| ab6e21ff5b | |||
| c86e0537d1 | |||
| 29f298ec88 | |||
| 1176ca6409 | |||
| 34074c9ec0 | |||
| 262537959f | |||
| 58902e342f | |||
| 84f37631c2 | |||
| 22a190bdd5 | |||
| f2f85efa33 | |||
| e95feaf495 | |||
| d3d3e6cf01 | |||
| 12e603eceb | |||
| 04a2f1131c | |||
| dff1690fe1 | |||
| 0fa805d973 | |||
| 8599b6042a | |||
| e49637d153 | |||
| 907110c4ec | |||
| c245fe599d | |||
| 5e75d4e1f1 | |||
| a3e57feadd | |||
| eb21561541 | |||
| 48d7f1ace1 | |||
| ba1fc33672 | |||
| 046c231b33 | |||
| 3d99869c0c | |||
| 8af4562af6 | |||
| 59f7f62b62 | |||
| 9686f6942f | |||
| 9545dd7603 | |||
| ec171fd7ca | |||
| cbd0ec1b25 | |||
| 92abf6e11d | |||
| dc71a16bae | |||
| 7ab34e709a | |||
| 955f13a18d | |||
| 35f730fedd | |||
| 0f3fa30f62 | |||
| 3acba578b1 | |||
| c0e3dc64a4 | |||
| 68153174bc | |||
| fb2b103e51 | |||
| e8113f0e66 | |||
| a517f7cbca | |||
| c104a97fa0 | |||
| 2597b5e779 | |||
| 7f99bebc72 | |||
| 298f356acb | |||
| 06f18be4bf | |||
| b74b6e883c | |||
| 6083a75d1b | |||
| 3430ee2c3a | |||
| b6bbb4fb26 | |||
| 15b55a441b | |||
| 723cac034b | |||
| 6e9ed871bc | |||
| 50d8dcb072 | |||
| 3aae4cdc59 | |||
| 3ed3ba6253 | |||
| 5de4a5b85a | |||
| bbf15af837 | |||
| 05c3f9c754 | |||
| c86f49ee7f | |||
| c9e5660b8c | |||
| 615b5e2bba | |||
| 8d30079033 | |||
| 47fa51034a | |||
| f78f2135fa | |||
| 5e844aad08 | |||
| 620b4be477 | |||
| 574ad6e676 | |||
| 836bc6611f | |||
| 8d73a9f8b7 | |||
| d828868bc8 | |||
| da7967632a | |||
| 426745fa24 | |||
| d457fbb487 | |||
| 7580f3f8e3 | |||
| 1351f382aa | |||
| 24750d4b74 | |||
| 02b7742f4d | |||
| 116267a245 | |||
| b2497a3918 | |||
| 8b84d17c8d | |||
| d055599608 | |||
| 162c581754 | |||
| 1be56269a3 | |||
| 7a6d12fc4e | |||
| c1744de069 | |||
| a258b7e12b | |||
| c3e9aea843 | |||
| 20130e210b | |||
| b2528faa02 | |||
| 7f665babf9 | |||
| 515bea6fb4 | |||
| c13f4a323f | |||
| ec6713fd80 | |||
| 171858c2e7 | |||
| 315d0bbfa3 | |||
| 5a19e52921 | |||
| 91b4186b8a | |||
| 2bb026c763 | |||
| dc62a6f88e | |||
| e05ee6dfc8 | |||
| 78e06eb613 | |||
| 8b0dbf7158 | |||
| e0d83f61c2 | |||
| 8bca948b61 | |||
| a0b29fe3dc | |||
| 325518b47a | |||
| 0f17b4d0c5 | |||
| f2cea4d6b0 | |||
| 6d039a646b | |||
| 92cc6f96d4 | |||
| 77c5d5c282 | |||
| 70a2e305bc | |||
| 6f4f2607b3 | |||
| d8b2b04bb2 | |||
| 27428e655b | |||
| 9b5fbff7ba | |||
| 2ac25bebaa | |||
| 2d0badde9a | |||
| dc803bcd33 | |||
| a3b28eb4d7 | |||
| 49fef03830 | |||
| 9205a87002 | |||
| 659202c3e5 | |||
| ed7d72c07d | |||
| 190b08ecf4 | |||
| 953971c9a3 | |||
| f3b7edbd11 | |||
| 377d37503f | |||
| 53c29f11ba | |||
| befc9805ab | |||
| c8828d586c | |||
| 7608e8e69d | |||
| 0095141e08 | |||
| 7d59c6e14e | |||
| 54e1556c40 | |||
| 9ad56cdd8e | |||
| effba1a16f | |||
| 59a506c15a | |||
| 1125dc3dba | |||
| e36bde4acc | |||
| c0792168f5 | |||
| a0f8c060c0 | |||
| c5dc45e144 | |||
| 28d63b11dd | |||
| f8899f724d | |||
| 8c447137d6 | |||
| 44c8b95400 | |||
| c8aac7f71c | |||
| de0efa861a | |||
| 5cbb6f210d | |||
| 812ec0e2b7 | |||
| 9bf09d681e | |||
| 689450954c | |||
| 372eb94ed0 | |||
| 7eea7a7971 | |||
| 329c55d52f | |||
| cd35abbae7 | |||
| ce98d4fb2f | |||
| 4a48024dc8 | |||
| 7970925fe4 | |||
| 284840bde7 | |||
| 169078d0a9 | |||
| c9426a6a71 | |||
| 02f12e4448 | |||
| b77072d399 | |||
| d3cb8131d1 | |||
| 7e089435c7 | |||
| 55a6d58225 | |||
| 207f981fd0 | |||
| 82105f2f8f | |||
| c88b1bcf8e | |||
| 54d46ca80f | |||
| 830187e4da | |||
| 95e3e26d12 | |||
| 8186687093 | |||
| cf0a0ced90 | |||
| be1a48e856 | |||
| fb12361b3f | |||
| dc186082a7 | |||
| 5e14dbc15e | |||
| 07beb0f0a5 | |||
| 08bb69ff7c | |||
| 4dbb445f8b | |||
| e1a7fe84e0 | |||
| 79b5ef0e1e | |||
| c9a5cea93e | |||
| 22adeb3902 | |||
| 071e595766 | |||
| ef9ad529a4 | |||
| 9da6329496 | |||
| 5c7a5ce129 | |||
| bc00d957d3 | |||
| 8169a31ecc | |||
| 0a87fd023d | |||
| 4840e431f4 | |||
| 1e582e3e58 | |||
| 549058d9d1 | |||
| 56d6b7ff5a | |||
| 50c0a51b10 | |||
| 7d9df5b731 | |||
| 6418c631ba | |||
| 21998586d1 | |||
| 7ba4eea417 | |||
| a2f68835f9 | |||
| af1dbfe505 | |||
| b282c7e603 | |||
| 6d767a8494 | |||
| 9e52b70edf | |||
| 05a925b801 | |||
| 5f92ecafe7 | |||
| 44668bd256 | |||
| 73f1ec64a8 | |||
| 947d1b5aef | |||
| 9d25ebec40 | |||
| 7d1a081fd0 | |||
| f2dd7888f7 | |||
| e951302927 | |||
| 9091238861 | |||
| 5e1be51b84 | |||
| 9240090983 | |||
| 502f799a73 | |||
| bfade1d795 | |||
| 72f4f9e7b1 | |||
| dbfbc91971 | |||
| a4870f619b | |||
| 4c62b5d23a | |||
| 3593dea585 | |||
| 795e3f644a | |||
| 3ae777f973 | |||
| 54825845e7 | |||
| 50603ac16e | |||
| e453a4d548 | |||
| 5a2911ffe4 | |||
| ec420615a1 | |||
| ef9c5a65cf | |||
| c72f30da2e | |||
| bf22101c7d | |||
| d247873ade | |||
| e62dc56047 | |||
| bc458b8848 | |||
| abc7acb5e4 | |||
| c6d68d9e87 | |||
| 4a4a23c7c4 | |||
| cfe75eb569 | |||
| 921b59c467 | |||
| f2e45695b2 | |||
| 6001ece0b8 | |||
| ac1355b922 | |||
| 257029f80d | |||
| 94d439e5c3 | |||
| 6a6328a355 | |||
| 8c55c086d5 | |||
| 5ed9a6508a | |||
| 5b2cf1b8f7 | |||
| a290ec032b | |||
| 8cac195fcf | |||
| f3e37944fc | |||
| 74377a061b | |||
| ceafe7897c | |||
| 5846a9d6aa | |||
| 8abc371fb4 | |||
| b2c39883bf | |||
| d14f62c26a | |||
| fa892c5334 | |||
| 5820cc2048 | |||
| 3b9d8a3176 | |||
| cdda221d64 | |||
| 5ec8ee7cbf | |||
| 0cd3fcf1be | |||
| 46887c0447 | |||
| f05be47ed8 | |||
| 56ccd99cc7 | |||
| 2660a05084 | |||
| 1b642fec73 | |||
| 52250b4815 | |||
| c00f4e15ae | |||
| 7c346c3104 | |||
| 5e3aa93479 | |||
| 75999a6e07 | |||
| eff97414bf | |||
| c680a60710 | |||
| fed1a899aa | |||
| b58930c687 | |||
| 68c784445c | |||
| b2d4b2c61f | |||
| 9999156f26 | |||
| ac9c4e82ac | |||
| a13eeddacf | |||
| f01103af23 | |||
| fb9aab0e57 | |||
| 71a267fd28 | |||
| 61b4fc1fe7 | |||
| ccec4e8475 | |||
| b471edbb80 | |||
| c64ba99654 | |||
| 7b838dd692 | |||
| ca7a6e7bbe | |||
| 2228e4812d | |||
| 5a71a4c092 | |||
| 4e7b1e3c37 | |||
| e5b39af6a5 | |||
| 0c66a50e2d | |||
| 509a17dbc3 | |||
| a8aed6b344 | |||
| ef33aaa3c8 | |||
| e9fd544ff4 | |||
| e3d4f4e91f | |||
| ab51acb712 | |||
| d0adb044cf | |||
| 565b170e00 | |||
| 9361c325bb | |||
| 08e4a47fc2 | |||
| dd3675db63 | |||
| da7db59414 | |||
| fde6b05618 | |||
| 683208ddc8 | |||
| 3dff15b8c4 | |||
| 4aa82b0a9f | |||
| 89918324ce | |||
| 122ce4a568 | |||
| d9ea208dea | |||
| 1376c62e5f | |||
| 59484ea066 | |||
| 46cea5011d | |||
| 4723f8ba7c | |||
| d4e22bb4a6 | |||
| daee5db7bc | |||
| c057075eac | |||
| ed9301f3f8 | |||
| 431e188b07 | |||
| 71c9ff0d91 | |||
| edf1838708 | |||
| 5feb3b6f90 | |||
| 50c0648de2 | |||
| 97216e1704 | |||
| 43eb36e17a | |||
| 387ff48a65 | |||
| 1b3c14a792 | |||
| 132833deeb | |||
| a1a150c300 | |||
| cd62147cdf | |||
| 7743899251 | |||
| 5d09d43b08 | |||
| ccaca624f3 | |||
| 65047ac0e2 | |||
| b0737b882d | |||
| 179970a0d5 | |||
| bc1b5a1b2f | |||
| a000f9f8b8 | |||
| 8f371343cd | |||
| da68e64d9b | |||
| 77ed77b93b | |||
| 252a50bb75 | |||
| 7549243f40 | |||
| 9c92c12fea | |||
| 3ae03998a2 | |||
| 068f7aa9d9 | |||
| bce9f82844 | |||
| fe205d838d | |||
| 83b1b17f87 | |||
| c1befbe2a8 | |||
| 33c4e98152 | |||
| 7174caf5f1 | |||
| 9a5dbcf31c | |||
| b33e9fbcde | |||
| ca4019c09f | |||
| 8dab1ee300 | |||
| ae92f15b0a | |||
| 13041e0323 | |||
| 8354c403f4 | |||
| 29d2cc6d5b | |||
| d7549cd953 | |||
| db81f1e512 | |||
| 55cf1f8d30 | |||
| 625ec431eb | |||
| 3abd3af4b2 | |||
| b24a71ab0b | |||
| 3adacb876d | |||
| 221af82950 | |||
| 76bba517b8 | |||
| 23a86b0455 | |||
| 3979dbd082 | |||
| bced126fdb | |||
| 873fc6f837 | |||
| 4304e597d8 | |||
| efaeade7a6 | |||
| 98d7ceecf7 | |||
| d62ee951a0 | |||
| 5782246b30 | |||
| 31433fc5ee | |||
| 83431e557d | |||
| 0a00163f0b | |||
| cb11b76682 | |||
| 6dca9c6754 | |||
| 9841a6e34d | |||
| 731d52d020 | |||
| 12f0f3d6d7 | |||
| f0dbee171b | |||
| d0c3513504 | |||
| 2c13835f32 | |||
| c0c200e72c | |||
| 9b194cc700 | |||
| 9a706fd78c | |||
| 415d97aa4d | |||
| a51cc972d3 | |||
| b528867466 | |||
| 8b1fbc770e | |||
| a64af8d8cf | |||
| 021fe902d9 | |||
| fd950b6452 | |||
| 3aa2463856 | |||
| 97ca7b099f | |||
| cca2ba9aab | |||
| ce546b12b0 | |||
| 6dd3f9e7bc | |||
| 941ffef2ca | |||
| 8dc304bcd3 | |||
| 3fddfc5557 | |||
| ac6630ef59 | |||
| 0ead0df2a1 | |||
| 333382ae74 |
@@ -0,0 +1,9 @@
|
||||
# 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"
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
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}}"
|
||||
@@ -1,6 +1,9 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
limelight-common/ARM/
|
||||
limelight-common/Debug/
|
||||
Build/
|
||||
cmake-*/
|
||||
**/xcuserdata/
|
||||
limelight-common/Release/
|
||||
*.sdf
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[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
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.1...4.0)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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})
|
||||
endif()
|
||||
|
||||
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
|
||||
target_compile_definitions(moonlight-common-c PRIVATE LC_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()
|
||||
endif()
|
||||
|
||||
if (NOT(MSVC OR APPLE))
|
||||
include(CheckLibraryExists)
|
||||
CHECK_LIBRARY_EXISTS(rt clock_gettime "" HAVE_CLOCK_GETTIME)
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,21 @@
|
||||
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: 2a788029bf...c7353c0593
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
@@ -0,0 +1,11 @@
|
||||
#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
@@ -0,0 +1,603 @@
|
||||
/* 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
|
||||
@@ -0,0 +1,282 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#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);
|
||||
Submodule
+1
Submodule nanors/deps/simde added at 595b743dce
+178
@@ -0,0 +1,178 @@
|
||||
#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
@@ -0,0 +1,26 @@
|
||||
#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
|
||||
@@ -1,641 +0,0 @@
|
||||
/*
|
||||
* 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, nos, nshards;
|
||||
|
||||
/* 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;
|
||||
nos = 0;
|
||||
nshards = 0;
|
||||
dataShards = rs->data_shards;
|
||||
for (i = 0; i < dataShards; i++) {
|
||||
if (j < nr_fec_blocks && i == 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;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#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
|
||||
|
||||
+238
-143
@@ -1,48 +1,112 @@
|
||||
#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_REORDER_QUEUE rtpReorderQueue;
|
||||
static RTP_AUDIO_QUEUE rtpAudioQueue;
|
||||
|
||||
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;
|
||||
|
||||
#define RTP_PORT 48000
|
||||
#ifdef LC_DEBUG
|
||||
#define INVALID_OPUS_HEADER 0x00
|
||||
static uint8_t opusHeaderByte;
|
||||
#endif
|
||||
|
||||
#define MAX_PACKET_SIZE 1400
|
||||
|
||||
// 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 _QUEUE_AUDIO_PACKET_HEADER {
|
||||
LINKED_BLOCKING_QUEUE_ENTRY lentry;
|
||||
int size;
|
||||
} QUEUED_AUDIO_PACKET_HEADER, *PQUEUED_AUDIO_PACKET_HEADER;
|
||||
|
||||
typedef struct _QUEUED_AUDIO_PACKET {
|
||||
// data must remain at the front
|
||||
QUEUED_AUDIO_PACKET_HEADER header;
|
||||
char data[MAX_PACKET_SIZE];
|
||||
|
||||
int size;
|
||||
union {
|
||||
RTP_QUEUE_ENTRY rentry;
|
||||
LINKED_BLOCKING_QUEUE_ENTRY lentry;
|
||||
} q;
|
||||
} QUEUED_AUDIO_PACKET, *PQUEUED_AUDIO_PACKET;
|
||||
|
||||
// Initialize the audio stream
|
||||
void initializeAudioStream(void) {
|
||||
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) {
|
||||
LbqInitializeLinkedBlockingQueue(&packetQueue, 30);
|
||||
RtpqInitializeQueue(&rtpReorderQueue, RTPQ_DEFAULT_MAX_SIZE, RTPQ_DEFAULT_QUEUE_TIME);
|
||||
RtpaInitializeQueue(&rtpAudioQueue);
|
||||
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) {
|
||||
@@ -60,75 +124,128 @@ static void freePacketList(PLINKED_BLOCKING_QUEUE_ENTRY entry) {
|
||||
|
||||
// Tear down the audio stream once we're done with it
|
||||
void destroyAudioStream(void) {
|
||||
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;
|
||||
if (rtpSocket != INVALID_SOCKET) {
|
||||
if (pingThreadStarted) {
|
||||
PltInterruptThread(&udpPingThread);
|
||||
PltJoinThread(&udpPingThread);
|
||||
}
|
||||
|
||||
PltSleepMsInterruptible(&udpPingThread, 500);
|
||||
closeSocket(rtpSocket);
|
||||
rtpSocket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
PltDestroyCryptoContext(audioDecryptionCtx);
|
||||
freePacketList(LbqDestroyLinkedBlockingQueue(&packetQueue));
|
||||
RtpaCleanupQueue(&rtpAudioQueue);
|
||||
}
|
||||
|
||||
static bool queuePacketToLbq(PQUEUED_AUDIO_PACKET* packet) {
|
||||
int err;
|
||||
|
||||
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;
|
||||
}
|
||||
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");
|
||||
|
||||
return true;
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void decodeInputData(PQUEUED_AUDIO_PACKET packet) {
|
||||
PRTP_PACKET rtp;
|
||||
|
||||
rtp = (PRTP_PACKET)&packet->data[0];
|
||||
if (lastSeq != 0 && (unsigned short)(lastSeq + 1) != rtp->sequenceNumber) {
|
||||
Limelog("Received OOS audio data (expected %d, but got %d)\n", lastSeq + 1, rtp->sequenceNumber);
|
||||
|
||||
// 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)&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);
|
||||
}
|
||||
|
||||
lastSeq = rtp->sequenceNumber;
|
||||
|
||||
AudioCallbacks.decodeAndPlaySample((char*)(rtp + 1), packet->size - sizeof(*rtp));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
static void ReceiveThreadProc(void* context) {
|
||||
static void AudioReceiveThreadProc(void* context) {
|
||||
PRTP_PACKET rtp;
|
||||
PQUEUED_AUDIO_PACKET packet;
|
||||
int queueStatus;
|
||||
bool useSelect;
|
||||
int packetsToDrop = 500 / AudioPacketDuration;
|
||||
uint32_t packetsToDrop;
|
||||
int waitingForAudioMs;
|
||||
|
||||
packet = NULL;
|
||||
packetsToDrop = 500 / AudioPacketDuration;
|
||||
|
||||
if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) {
|
||||
// SO_RCVTIMEO failed, so use select() to wait
|
||||
@@ -150,61 +267,72 @@ static void ReceiveThreadProc(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
packet->size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE, useSelect);
|
||||
if (packet->size < 0) {
|
||||
packet->header.size = recvUdpSocket(rtpSocket, &packet->data[0], MAX_PACKET_SIZE, useSelect);
|
||||
if (packet->header.size < 0) {
|
||||
Limelog("Audio Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
|
||||
ListenerCallbacks.connectionTerminated(LastSocketFail());
|
||||
break;
|
||||
}
|
||||
else if (packet->size == 0) {
|
||||
else if (packet->header.size == 0) {
|
||||
// Receive timed out; try again
|
||||
|
||||
|
||||
if (!receivedDataFromPeer) {
|
||||
waitingForAudioMs += UDP_RECV_POLL_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packet->size < sizeof(RTP_PACKET)) {
|
||||
if (packet->header.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 first 100 packets to avoid accumulating latency
|
||||
// by sending audio frames to the player faster than they can be played.
|
||||
// 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.
|
||||
if (packetsToDrop > 0) {
|
||||
packetsToDrop--;
|
||||
// Only count actual audio data (not FEC) in the packets to drop calculation
|
||||
if (rtp->packetType == 97) {
|
||||
packetsToDrop--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert fields to host byte-order
|
||||
rtp->sequenceNumber = htons(rtp->sequenceNumber);
|
||||
rtp->timestamp = htonl(rtp->timestamp);
|
||||
rtp->ssrc = htonl(rtp->ssrc);
|
||||
rtp->sequenceNumber = BE16(rtp->sequenceNumber);
|
||||
rtp->timestamp = BE32(rtp->timestamp);
|
||||
rtp->ssrc = BE32(rtp->ssrc);
|
||||
|
||||
queueStatus = RtpqAddPacket(&rtpReorderQueue, (PRTP_PACKET)packet, &packet->q.rentry);
|
||||
queueStatus = RtpaAddPacket(&rtpAudioQueue, (PRTP_PACKET)&packet->data[0], (uint16_t)packet->header.size);
|
||||
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);
|
||||
@@ -218,33 +346,43 @@ static void ReceiveThreadProc(void* context) {
|
||||
|
||||
if (RTPQ_PACKET_READY(queueStatus)) {
|
||||
// If packets are ready, pull them and send them to the decoder
|
||||
while ((packet = (PQUEUED_AUDIO_PACKET)RtpqGetQueuedPacket(&rtpReorderQueue)) != NULL) {
|
||||
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;
|
||||
|
||||
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if (!queuePacketToLbq(&packet)) {
|
||||
if (!queuePacketToLbq(&queuedPacket)) {
|
||||
// An exit signal was received
|
||||
free(queuedPacket);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Ownership should have been taken by the LBQ
|
||||
LC_ASSERT(queuedPacket == NULL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
decodeInputData(packet);
|
||||
free(packet);
|
||||
decodeInputData(queuedPacket);
|
||||
free(queuedPacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Break on exit
|
||||
if (packet != NULL) {
|
||||
if (queuedPacket != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (packet != NULL) {
|
||||
free(packet);
|
||||
}
|
||||
}
|
||||
|
||||
static void DecoderThreadProc(void* context) {
|
||||
static void AudioDecoderThreadProc(void* context) {
|
||||
int err;
|
||||
PQUEUED_AUDIO_PACKET packet;
|
||||
|
||||
@@ -268,31 +406,18 @@ 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();
|
||||
}
|
||||
|
||||
@@ -319,16 +444,9 @@ 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", ReceiveThreadProc, NULL, &receiveThread);
|
||||
err = PltCreateThread("AudioRecv", AudioReceiveThreadProc, NULL, &receiveThread);
|
||||
if (err != 0) {
|
||||
AudioCallbacks.stop();
|
||||
closeSocket(rtpSocket);
|
||||
@@ -337,44 +455,17 @@ int startAudioStream(void* audioContext, int arFlags) {
|
||||
}
|
||||
|
||||
if ((AudioCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
err = PltCreateThread("AudioDec", DecoderThreadProc, NULL, &decoderThread);
|
||||
err = PltCreateThread("AudioDec", AudioDecoderThreadProc, 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;
|
||||
}
|
||||
|
||||
@@ -385,3 +476,7 @@ int LiGetPendingAudioFrames(void) {
|
||||
int LiGetPendingAudioDuration(void) {
|
||||
return LiGetPendingAudioFrames() * AudioPacketDuration;
|
||||
}
|
||||
|
||||
const RTP_AUDIO_STATS* LiGetRTPAudioStats(void) {
|
||||
return &rtpAudioQueue.stats;
|
||||
}
|
||||
|
||||
+50
-31
@@ -8,32 +8,32 @@ void BbInitializeWrappedBuffer(PBYTE_BUFFER buff, char* data, int offset, int le
|
||||
}
|
||||
|
||||
// Get the long long in the correct byte order
|
||||
static long long byteSwapLongLong(PBYTE_BUFFER buff, long long l) {
|
||||
static uint64_t byteSwap64(PBYTE_BUFFER buff, uint64_t l) {
|
||||
if (buff->byteOrder == BYTE_ORDER_BIG) {
|
||||
return HTONLL(l);
|
||||
return BE64(l);
|
||||
}
|
||||
else {
|
||||
return l;
|
||||
return LE64(l);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the int in the correct byte order
|
||||
static int byteSwapInt(PBYTE_BUFFER buff, int i) {
|
||||
static uint32_t byteSwap32(PBYTE_BUFFER buff, uint32_t i) {
|
||||
if (buff->byteOrder == BYTE_ORDER_BIG) {
|
||||
return htonl(i);
|
||||
return BE32(i);
|
||||
}
|
||||
else {
|
||||
return i;
|
||||
return LE32(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the short in the correct byte order
|
||||
static int byteSwapShort(PBYTE_BUFFER buff, short s) {
|
||||
static uint16_t byteSwap16(PBYTE_BUFFER buff, uint16_t s) {
|
||||
if (buff->byteOrder == BYTE_ORDER_BIG) {
|
||||
return htons(s);
|
||||
return BE16(s);
|
||||
}
|
||||
else {
|
||||
return s;
|
||||
return LE16(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,67 +47,81 @@ bool BbAdvanceBuffer(PBYTE_BUFFER buff, int offset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get a byte from the byte buffer
|
||||
bool BbGet(PBYTE_BUFFER buff, char* c) {
|
||||
if (buff->position + sizeof(*c) > buff->length) {
|
||||
// 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);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(c, &buff->buffer[buff->position], sizeof(*c));
|
||||
buff->position += sizeof(*c);
|
||||
memcpy(data, &buff->buffer[buff->position], length);
|
||||
buff->position += length;
|
||||
|
||||
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 BbGetShort(PBYTE_BUFFER buff, short* s) {
|
||||
bool BbGet16(PBYTE_BUFFER buff, uint16_t* s) {
|
||||
if (buff->position + sizeof(*s) > buff->length) {
|
||||
*s = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(s, &buff->buffer[buff->position], sizeof(*s));
|
||||
buff->position += sizeof(*s);
|
||||
|
||||
*s = byteSwapShort(buff, *s);
|
||||
*s = byteSwap16(buff, *s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get an int from the byte buffer
|
||||
bool BbGetInt(PBYTE_BUFFER buff, int* i) {
|
||||
bool BbGet32(PBYTE_BUFFER buff, uint32_t* i) {
|
||||
if (buff->position + sizeof(*i) > buff->length) {
|
||||
*i = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(i, &buff->buffer[buff->position], sizeof(*i));
|
||||
buff->position += sizeof(*i);
|
||||
|
||||
*i = byteSwapInt(buff, *i);
|
||||
*i = byteSwap32(buff, *i);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get a long from the byte buffer
|
||||
bool BbGetLong(PBYTE_BUFFER buff, long long* l) {
|
||||
bool BbGet64(PBYTE_BUFFER buff, uint64_t* l) {
|
||||
if (buff->position + sizeof(*l) > buff->length) {
|
||||
*l = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(l, &buff->buffer[buff->position], sizeof(*l));
|
||||
buff->position += sizeof(*l);
|
||||
|
||||
*l = byteSwapLongLong(buff, *l);
|
||||
*l = byteSwap64(buff, *l);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put an int into the byte buffer
|
||||
bool BbPutInt(PBYTE_BUFFER buff, int i) {
|
||||
bool BbPut32(PBYTE_BUFFER buff, uint32_t i) {
|
||||
if (buff->position + sizeof(i) > buff->length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i = byteSwapInt(buff, i);
|
||||
i = byteSwap32(buff, i);
|
||||
|
||||
memcpy(&buff->buffer[buff->position], &i, sizeof(i));
|
||||
buff->position += sizeof(i);
|
||||
@@ -116,12 +130,12 @@ bool BbPutInt(PBYTE_BUFFER buff, int i) {
|
||||
}
|
||||
|
||||
// Put a long into the byte buffer
|
||||
bool BbPutLong(PBYTE_BUFFER buff, long long l) {
|
||||
bool BbPut64(PBYTE_BUFFER buff, uint64_t l) {
|
||||
if (buff->position + sizeof(l) > buff->length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
l = byteSwapLongLong(buff, l);
|
||||
l = byteSwap64(buff, l);
|
||||
|
||||
memcpy(&buff->buffer[buff->position], &l, sizeof(l));
|
||||
buff->position += sizeof(l);
|
||||
@@ -130,12 +144,12 @@ bool BbPutLong(PBYTE_BUFFER buff, long long l) {
|
||||
}
|
||||
|
||||
// Put a short into the byte buffer
|
||||
bool BbPutShort(PBYTE_BUFFER buff, short s) {
|
||||
bool BbPut16(PBYTE_BUFFER buff, uint16_t s) {
|
||||
if (buff->position + sizeof(s) > buff->length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s = byteSwapShort(buff, s);
|
||||
s = byteSwap16(buff, s);
|
||||
|
||||
memcpy(&buff->buffer[buff->position], &s, sizeof(s));
|
||||
buff->position += sizeof(s);
|
||||
@@ -143,14 +157,19 @@ bool BbPutShort(PBYTE_BUFFER buff, short s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put a byte into the buffer
|
||||
bool BbPut(PBYTE_BUFFER buff, char c) {
|
||||
if (buff->position + sizeof(c) > buff->length) {
|
||||
// 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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&buff->buffer[buff->position], &c, sizeof(c));
|
||||
buff->position += sizeof(c);
|
||||
memcpy(&buff->buffer[buff->position], data, length);
|
||||
buff->position += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put a byte into the buffer
|
||||
bool BbPut8(PBYTE_BUFFER buff, uint8_t c) {
|
||||
return BbPutBytes(buff, &c, sizeof(c));
|
||||
}
|
||||
|
||||
+11
-20
@@ -5,18 +5,6 @@
|
||||
#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;
|
||||
@@ -26,13 +14,16 @@ 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 BbGet(PBYTE_BUFFER buff, char* c);
|
||||
bool BbGetShort(PBYTE_BUFFER buff, short* s);
|
||||
bool BbGetInt(PBYTE_BUFFER buff, int* i);
|
||||
bool BbGetLong(PBYTE_BUFFER buff, long long* l);
|
||||
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 BbPutInt(PBYTE_BUFFER buff, int i);
|
||||
bool BbPutShort(PBYTE_BUFFER buff, short s);
|
||||
bool BbPut(PBYTE_BUFFER buff, char c);
|
||||
bool BbPutLong(PBYTE_BUFFER buff, long long 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);
|
||||
bool BbPut64(PBYTE_BUFFER buff, uint64_t l);
|
||||
|
||||
+190
-44
@@ -1,5 +1,4 @@
|
||||
#include "Limelight-internal.h"
|
||||
#include "Platform.h"
|
||||
|
||||
static int stage = STAGE_NONE;
|
||||
static ConnListenerConnectionTerminated originalTerminationCallback;
|
||||
@@ -10,7 +9,8 @@ static int terminationCallbackErrorCode;
|
||||
// Common globals
|
||||
char* RemoteAddrString;
|
||||
struct sockaddr_storage RemoteAddr;
|
||||
SOCKADDR_LEN RemoteAddrLen;
|
||||
struct sockaddr_storage LocalAddr;
|
||||
SOCKADDR_LEN AddrLen;
|
||||
int AppVersionQuad[4];
|
||||
STREAM_CONFIGURATION StreamConfig;
|
||||
CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
|
||||
@@ -22,18 +22,30 @@ 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",
|
||||
@@ -91,12 +103,6 @@ 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();
|
||||
@@ -113,6 +119,12 @@ 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--;
|
||||
@@ -163,8 +175,34 @@ static void ClInternalConnectionTerminated(int errorCode)
|
||||
LC_ASSERT(err == 0);
|
||||
}
|
||||
|
||||
// Close the thread handle since we can never wait on it
|
||||
PltCloseThread(&terminationCallbackThread);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Starts the connection to the streaming machine
|
||||
@@ -173,11 +211,45 @@ 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.
|
||||
//
|
||||
@@ -186,10 +258,29 @@ 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 ||
|
||||
@@ -218,7 +309,7 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
|
||||
}
|
||||
|
||||
// Dimensions over 4096 are only supported with HEVC on NVENC
|
||||
if (!StreamConfig.supportsHevc &&
|
||||
if (!(StreamConfig.supportedVideoFormats & ~VIDEO_FORMAT_MASK_H264) &&
|
||||
(StreamConfig.width > 4096 || StreamConfig.height > 4096)) {
|
||||
Limelog("WARNING: Streaming at resolutions above 4K using H.264 will likely fail! Trying anyway!\n");
|
||||
}
|
||||
@@ -226,18 +317,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");
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
alreadyTerminated = false;
|
||||
ConnectionInterrupted = false;
|
||||
|
||||
|
||||
Limelog("Initializing platform...");
|
||||
ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT);
|
||||
err = initializePlatform();
|
||||
@@ -253,18 +344,52 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
|
||||
|
||||
Limelog("Resolving host name...");
|
||||
ListenerCallbacks.stageStarting(STAGE_NAME_RESOLUTION);
|
||||
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47984, &RemoteAddr, &RemoteAddrLen);
|
||||
if (err != 0) {
|
||||
err = resolveHostName(serverInfo->address, AF_UNSPEC, 47989, &RemoteAddr, &RemoteAddrLen);
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (err != 0) {
|
||||
err = resolveHostName(serverInfo->address, AF_UNSPEC, 48010, &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) {
|
||||
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);
|
||||
@@ -274,24 +399,47 @@ 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) {
|
||||
if (isPrivateNetworkAddress(&RemoteAddr)) {
|
||||
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)) {
|
||||
StreamConfig.streamingRemotely = STREAM_CFG_LOCAL;
|
||||
}
|
||||
else {
|
||||
StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
|
||||
|
||||
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");
|
||||
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");
|
||||
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();
|
||||
err = performRtspHandshake(serverInfo);
|
||||
if (err != 0) {
|
||||
Limelog("failed: %d\n", err);
|
||||
ListenerCallbacks.stageFailed(STAGE_RTSP_HANDSHAKE, err);
|
||||
@@ -323,14 +471,6 @@ 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();
|
||||
@@ -406,3 +546,9 @@ Cleanup:
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
const char* LiGetLaunchUrlQueryParameters(void) {
|
||||
// v0 = Video encryption and control stream encryption v2
|
||||
// v1 = RTSP encryption
|
||||
return "&corever=1";
|
||||
}
|
||||
|
||||
+39
-11
@@ -19,7 +19,8 @@ unsigned int LiGetPortFlagsFromStage(int stage)
|
||||
switch (stage)
|
||||
{
|
||||
case STAGE_RTSP_HANDSHAKE:
|
||||
return ML_PORT_FLAG_TCP_48010 | ML_PORT_FLAG_UDP_48010;
|
||||
// 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;
|
||||
|
||||
case STAGE_CONTROL_STREAM_START:
|
||||
return ML_PORT_FLAG_UDP_47999;
|
||||
@@ -77,6 +78,33 @@ 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;
|
||||
@@ -111,7 +139,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
}
|
||||
|
||||
for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
|
||||
if (testPortFlags & (1 << i)) {
|
||||
if (testPortFlags & (1U << i)) {
|
||||
sockets[i] = createSocket(address.ss_family,
|
||||
LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM,
|
||||
LiGetProtocolFromPortFlagIndex(i),
|
||||
@@ -123,7 +151,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
((struct sockaddr_in6*)&address)->sin6_port = htons(LiGetPortFromPortFlagIndex(i));
|
||||
SET_PORT((LC_SOCKADDR*)&address, LiGetPortFromPortFlagIndex(i));
|
||||
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) {
|
||||
// Initiate an asynchronous connection
|
||||
err = connect(sockets[i], (struct sockaddr*)&address, address_length);
|
||||
@@ -133,7 +161,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
Limelog("Failed to start async connect to TCP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
|
||||
|
||||
// Mask off this bit so we don't try to include it in pollSockets() below
|
||||
testPortFlags &= ~(1 << i);
|
||||
testPortFlags &= ~(1U << i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +177,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
Limelog("Failed to send test packet to UDP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
|
||||
|
||||
// Mask off this bit so we don't try to include it in pollSockets() below
|
||||
testPortFlags &= ~(1 << i);
|
||||
testPortFlags &= ~(1U << i);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -170,7 +198,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
|
||||
// Fill out our FD sets
|
||||
for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
|
||||
if (testPortFlags & (1 << i)) {
|
||||
if (testPortFlags & (1U << i)) {
|
||||
pfds[nfds].fd = sockets[i];
|
||||
|
||||
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP) {
|
||||
@@ -211,7 +239,7 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
// This socket was signalled. Figure out what port it was.
|
||||
for (portIndex = 0; portIndex < PORT_FLAGS_MAX_COUNT; portIndex++) {
|
||||
if (sockets[portIndex] == pfds[i].fd) {
|
||||
LC_ASSERT(testPortFlags & (1 << portIndex));
|
||||
LC_ASSERT(testPortFlags & (1U << portIndex));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -225,13 +253,13 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
// a packet from the test server, or it could be because we
|
||||
// received an ICMP error which will be given to us from
|
||||
// recvfrom().
|
||||
testPortFlags &= ~(1 << portIndex);
|
||||
testPortFlags &= ~(1U << portIndex);
|
||||
|
||||
// Check if the socket can be successfully read now
|
||||
err = recvfrom(sockets[portIndex], buf, sizeof(buf), 0, NULL, NULL);
|
||||
if (err >= 0) {
|
||||
// The UDP test was a success.
|
||||
failingPortFlags &= ~(1 << portIndex);
|
||||
failingPortFlags &= ~(1U << portIndex);
|
||||
|
||||
Limelog("UDP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
|
||||
}
|
||||
@@ -250,10 +278,10 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
|
||||
}
|
||||
|
||||
// The TCP test has completed for this port
|
||||
testPortFlags &= ~(1 << portIndex);
|
||||
testPortFlags &= ~(1U << portIndex);
|
||||
if (err == 0) {
|
||||
// The TCP test was a success
|
||||
failingPortFlags &= ~(1 << portIndex);
|
||||
failingPortFlags &= ~(1U << portIndex);
|
||||
|
||||
Limelog("TCP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
|
||||
}
|
||||
|
||||
+1282
-215
File diff suppressed because it is too large
Load Diff
+26
-1
@@ -36,6 +36,11 @@ 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,
|
||||
@@ -45,7 +50,12 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = {
|
||||
.connectionTerminated = fakeClConnectionTerminated,
|
||||
.logMessage = fakeClLogMessage,
|
||||
.rumble = fakeClRumble,
|
||||
.connectionStatusUpdate = fakeClConnectionStatusUpdate
|
||||
.connectionStatusUpdate = fakeClConnectionStatusUpdate,
|
||||
.setHdrMode = fakeClSetHdrMode,
|
||||
.rumbleTriggers = fakeClRumbleTriggers,
|
||||
.setMotionEventState = fakeClSetMotionEventState,
|
||||
.setControllerLED = fakeClSetControllerLED,
|
||||
.setAdaptiveTriggers = fakeClSetAdaptiveTriggers,
|
||||
};
|
||||
|
||||
void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks,
|
||||
@@ -121,5 +131,20 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+115
-33
@@ -1,44 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct _NV_INPUT_HEADER {
|
||||
int packetType;
|
||||
} NV_INPUT_HEADER, PNV_INPUT_HEADER;
|
||||
// netfloat is just a little-endian float in byte form
|
||||
// for network transmission.
|
||||
typedef uint8_t netfloat[4];
|
||||
|
||||
#define PACKET_TYPE_HAPTICS 0x06
|
||||
#define H_MAGIC_A 0x0000000D
|
||||
#define H_MAGIC_B 0x00000001
|
||||
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;
|
||||
|
||||
#define ENABLE_HAPTICS_MAGIC 0x0000000D
|
||||
typedef struct _NV_HAPTICS_PACKET {
|
||||
NV_INPUT_HEADER header;
|
||||
int magicA;
|
||||
int magicB;
|
||||
uint16_t enable;
|
||||
} NV_HAPTICS_PACKET, *PNV_HAPTICS_PACKET;
|
||||
|
||||
#define PACKET_TYPE_KEYBOARD 0x0A
|
||||
#define KEY_DOWN_EVENT_MAGIC 0x00000003
|
||||
#define KEY_UP_EVENT_MAGIC 0x00000004
|
||||
typedef struct _NV_KEYBOARD_PACKET {
|
||||
NV_INPUT_HEADER header;
|
||||
char keyAction;
|
||||
int zero1;
|
||||
char flags; // Sunshine extension (always 0 for GFE)
|
||||
short keyCode;
|
||||
char modifiers;
|
||||
short zero2;
|
||||
} NV_KEYBOARD_PACKET, *PNV_KEYBOARD_PACKET;
|
||||
|
||||
#define PACKET_TYPE_REL_MOUSE_MOVE 0x08
|
||||
#define MOUSE_MOVE_REL_MAGIC 0x06
|
||||
#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
|
||||
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 PACKET_TYPE_ABS_MOUSE_MOVE 0x0e
|
||||
#define MOUSE_MOVE_ABS_MAGIC 0x05
|
||||
#define MOUSE_MOVE_ABS_MAGIC 0x00000005
|
||||
typedef struct _NV_ABS_MOUSE_MOVE_PACKET {
|
||||
NV_INPUT_HEADER header;
|
||||
int magic;
|
||||
|
||||
short x;
|
||||
short y;
|
||||
@@ -51,21 +59,19 @@ typedef struct _NV_ABS_MOUSE_MOVE_PACKET {
|
||||
short height;
|
||||
} NV_ABS_MOUSE_MOVE_PACKET, *PNV_ABS_MOUSE_MOVE_PACKET;
|
||||
|
||||
#define PACKET_TYPE_MOUSE_BUTTON 0x05
|
||||
#define MOUSE_BUTTON_DOWN_EVENT_MAGIC_GEN5 0x00000008
|
||||
#define MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5 0x00000009
|
||||
typedef struct _NV_MOUSE_BUTTON_PACKET {
|
||||
NV_INPUT_HEADER header;
|
||||
char action;
|
||||
int button;
|
||||
uint8_t button;
|
||||
} NV_MOUSE_BUTTON_PACKET, *PNV_MOUSE_BUTTON_PACKET;
|
||||
|
||||
#define PACKET_TYPE_CONTROLLER 0x18
|
||||
#define C_HEADER_A 0x0000000A
|
||||
#define CONTROLLER_MAGIC 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;
|
||||
@@ -78,15 +84,14 @@ typedef struct _NV_CONTROLLER_PACKET {
|
||||
short tailB;
|
||||
} NV_CONTROLLER_PACKET, *PNV_CONTROLLER_PACKET;
|
||||
|
||||
#define PACKET_TYPE_MULTI_CONTROLLER 0x1E
|
||||
#define MC_HEADER_A 0x0000000D
|
||||
#define MULTI_CONTROLLER_MAGIC 0x0000000D
|
||||
#define MULTI_CONTROLLER_MAGIC_GEN5 0x0000000C
|
||||
#define MC_HEADER_B 0x001A
|
||||
#define MC_MID_B 0x0014
|
||||
#define MC_TAIL_A 0x0000009C
|
||||
#define MC_TAIL_A 0x009C
|
||||
#define MC_TAIL_B 0x0055
|
||||
typedef struct _NV_MULTI_CONTROLLER_PACKET {
|
||||
NV_INPUT_HEADER header;
|
||||
int headerA;
|
||||
short headerB;
|
||||
short controllerNumber;
|
||||
short activeGamepadMask;
|
||||
@@ -98,20 +103,97 @@ typedef struct _NV_MULTI_CONTROLLER_PACKET {
|
||||
short leftStickY;
|
||||
short rightStickX;
|
||||
short rightStickY;
|
||||
int tailA;
|
||||
short tailA;
|
||||
short buttonFlags2; // Sunshine protocol extension (always 0 for GFE)
|
||||
short tailB;
|
||||
} NV_MULTI_CONTROLLER_PACKET, *PNV_MULTI_CONTROLLER_PACKET;
|
||||
|
||||
#define PACKET_TYPE_SCROLL 0xA
|
||||
#define MAGIC_A 0x09
|
||||
#define SCROLL_MAGIC 0x00000009
|
||||
#define SCROLL_MAGIC_GEN5 0x0000000A
|
||||
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)
|
||||
|
||||
+1297
-415
File diff suppressed because it is too large
Load Diff
+69
-14
@@ -1,18 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Limelight.h"
|
||||
#include "Platform.h"
|
||||
#include "Limelight.h"
|
||||
#include "PlatformSockets.h"
|
||||
#include "PlatformThreads.h"
|
||||
#include "PlatformCrypto.h"
|
||||
#include "Video.h"
|
||||
#include "RtpFecQueue.h"
|
||||
#include "Input.h"
|
||||
#include "RtpAudioQueue.h"
|
||||
#include "RtpVideoQueue.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
// Common globals
|
||||
extern char* RemoteAddrString;
|
||||
extern struct sockaddr_storage RemoteAddr;
|
||||
extern SOCKADDR_LEN RemoteAddrLen;
|
||||
extern struct sockaddr_storage LocalAddr;
|
||||
extern SOCKADDR_LEN AddrLen;
|
||||
extern int AppVersionQuad[4];
|
||||
extern STREAM_CONFIGURATION StreamConfig;
|
||||
extern CONNECTION_LISTENER_CALLBACKS ListenerCallbacks;
|
||||
@@ -24,8 +29,41 @@ 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
|
||||
@@ -39,6 +77,17 @@ extern int AudioPacketDuration;
|
||||
#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
|
||||
@@ -53,12 +102,15 @@ extern int AudioPacketDuration;
|
||||
#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);
|
||||
|
||||
@@ -66,28 +118,31 @@ int initializeControlStream(void);
|
||||
int startControlStream(void);
|
||||
int stopControlStream(void);
|
||||
void destroyControlStream(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);
|
||||
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);
|
||||
|
||||
int performRtspHandshake(void);
|
||||
int performRtspHandshake(PSERVER_INFORMATION serverInfo);
|
||||
|
||||
void initializeVideoDepacketizer(int pktSize);
|
||||
void destroyVideoDepacketizer(void);
|
||||
void queueRtpPacket(PRTPFEC_QUEUE_ENTRY queueEntry);
|
||||
void queueRtpPacket(PRTPV_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);
|
||||
|
||||
void initializeAudioStream(void);
|
||||
int initializeAudioStream(void);
|
||||
int notifyAudioPortNegotiationComplete(void);
|
||||
void destroyAudioStream(void);
|
||||
int startAudioStream(void* audioContext, int arFlags);
|
||||
void stopAudioStream(void);
|
||||
|
||||
+482
-59
@@ -20,7 +20,7 @@ extern "C" {
|
||||
#define STREAM_CFG_AUTO 2
|
||||
|
||||
// Values for the 'colorSpace' field below.
|
||||
// Rec. 2020 is only supported with HEVC video streams.
|
||||
// Rec. 2020 is not supported with H.264 video streams on GFE hosts.
|
||||
#define COLORSPACE_REC_601 0
|
||||
#define COLORSPACE_REC_709 1
|
||||
#define COLORSPACE_REC_2020 2
|
||||
@@ -29,6 +29,18 @@ 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;
|
||||
@@ -37,7 +49,9 @@ typedef struct _STREAM_CONFIGURATION {
|
||||
// FPS of the desired video stream
|
||||
int fps;
|
||||
|
||||
// Bitrate of the desired video stream (audio adds another ~1 Mbps)
|
||||
// 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.
|
||||
int bitrate;
|
||||
|
||||
// Max video packet size in bytes (use 1024 if unsure). If STREAM_CFG_AUTO
|
||||
@@ -55,24 +69,10 @@ 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 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;
|
||||
// Specifies the mask of supported video formats.
|
||||
// See VIDEO_FORMAT constants below.
|
||||
int supportedVideoFormats;
|
||||
|
||||
// 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,6 +87,14 @@ 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.
|
||||
@@ -98,7 +106,8 @@ typedef struct _STREAM_CONFIGURATION {
|
||||
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig);
|
||||
|
||||
// These identify codec configuration data in the buffer lists
|
||||
// of frames identified as IDR frames.
|
||||
// of frames identified as IDR frames for H.264 and HEVC formats.
|
||||
// For other codecs, all data is marked as BUFFER_TYPE_PICDATA.
|
||||
#define BUFFER_TYPE_PICDATA 0x00
|
||||
#define BUFFER_TYPE_SPS 0x01
|
||||
#define BUFFER_TYPE_PPS 0x02
|
||||
@@ -114,7 +123,7 @@ typedef struct _LENTRY {
|
||||
// Size of data in bytes (never <= 0)
|
||||
int length;
|
||||
|
||||
// Buffer type (listed above)
|
||||
// Buffer type (listed above, only set for H.264 and HEVC formats)
|
||||
int bufferType;
|
||||
} LENTRY, *PLENTRY;
|
||||
|
||||
@@ -122,10 +131,13 @@ typedef struct _LENTRY {
|
||||
// previous P-frames.
|
||||
#define FRAME_TYPE_PFRAME 0x00
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
@@ -136,21 +148,49 @@ typedef struct _DECODE_UNIT {
|
||||
// Frame type
|
||||
int frameType;
|
||||
|
||||
// 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.
|
||||
unsigned long long receiveTimeMs;
|
||||
// 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;
|
||||
|
||||
// Presentation time in milliseconds with the epoch at the first captured frame.
|
||||
// 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.
|
||||
// This can be used to aid frame pacing or to drop old frames that were queued too
|
||||
// long prior to display.
|
||||
unsigned int presentationTimeMs;
|
||||
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;
|
||||
|
||||
// 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)
|
||||
@@ -180,21 +220,25 @@ typedef struct _DECODE_UNIT {
|
||||
// The maximum number of channels supported
|
||||
#define AUDIO_CONFIGURATION_MAX_CHANNEL_COUNT 8
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// Masks for clients to use to match video codecs without profile-specific details.
|
||||
#define VIDEO_FORMAT_MASK_H264 0x00FF
|
||||
#define VIDEO_FORMAT_MASK_H265 0xFF00
|
||||
#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
|
||||
|
||||
// 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
|
||||
@@ -222,6 +266,16 @@ 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.
|
||||
@@ -319,10 +373,10 @@ void LiInitializeAudioCallbacks(PAUDIO_RENDERER_CALLBACKS arCallbacks);
|
||||
#define STAGE_NONE 0
|
||||
#define STAGE_PLATFORM_INIT 1
|
||||
#define STAGE_NAME_RESOLUTION 2
|
||||
#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_AUDIO_STREAM_INIT 3
|
||||
#define STAGE_RTSP_HANDSHAKE 4
|
||||
#define STAGE_CONTROL_STREAM_INIT 5
|
||||
#define STAGE_VIDEO_STREAM_INIT 6
|
||||
#define STAGE_INPUT_STREAM_INIT 7
|
||||
#define STAGE_CONTROL_STREAM_START 8
|
||||
#define STAGE_VIDEO_STREAM_START 9
|
||||
@@ -369,6 +423,22 @@ 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, ...);
|
||||
|
||||
@@ -386,6 +456,33 @@ 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;
|
||||
@@ -395,21 +492,50 @@ 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
|
||||
@@ -437,6 +563,12 @@ 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);
|
||||
|
||||
@@ -445,8 +577,10 @@ 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 if the
|
||||
// touchscreen is not the primary input method.
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
@@ -456,6 +590,97 @@ 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
|
||||
@@ -467,6 +692,8 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
|
||||
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
|
||||
@@ -475,6 +702,15 @@ 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
|
||||
@@ -492,20 +728,106 @@ int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers);
|
||||
#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(short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
|
||||
int LiSendControllerEvent(int 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 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).
|
||||
// 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.
|
||||
int LiSendMultiControllerEvent(short controllerNumber, short activeGamepadMask,
|
||||
short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
|
||||
int 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.
|
||||
@@ -517,9 +839,18 @@ 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.
|
||||
// NOTE: This will be populated from gettimeofday() if !HAVE_CLOCK_GETTIME and
|
||||
// populated from clock_gettime(CLOCK_MONOTONIC) if HAVE_CLOCK_GETTIME.
|
||||
// It should only ever be compared with the return value from a previous call to itself.
|
||||
uint64_t LiGetMillis(void);
|
||||
|
||||
// This is a simplistic STUN function that can assist clients in getting the WAN address
|
||||
@@ -542,6 +873,36 @@ 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
|
||||
@@ -569,12 +930,17 @@ int LiGetPendingAudioDuration(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
|
||||
@@ -589,6 +955,63 @@ unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex);
|
||||
#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
|
||||
|
||||
+108
-71
@@ -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->lifetimeSize == 0);
|
||||
LC_ASSERT(queueHead->shutdown || queueHead->draining || queueHead->lifetimeSize == 0);
|
||||
|
||||
PltDeleteMutex(&queueHead->mutex);
|
||||
PltCloseEvent(&queueHead->containsDataEvent);
|
||||
PltDeleteConditionVariable(&queueHead->cond);
|
||||
|
||||
return queueHead->head;
|
||||
}
|
||||
@@ -20,10 +20,15 @@ PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead
|
||||
head = queueHead->head;
|
||||
|
||||
// Reinitialize the queue to empty
|
||||
queueHead->head = NULL;
|
||||
queueHead->tail = NULL;
|
||||
queueHead->currentSize = 0;
|
||||
PltClearEvent(&queueHead->containsDataEvent);
|
||||
if (head != NULL) {
|
||||
queueHead->head = NULL;
|
||||
queueHead->tail = NULL;
|
||||
queueHead->currentSize = 0;
|
||||
}
|
||||
else {
|
||||
LC_ASSERT(queueHead->tail == NULL);
|
||||
LC_ASSERT(queueHead->currentSize == 0);
|
||||
}
|
||||
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
|
||||
@@ -36,13 +41,14 @@ int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeB
|
||||
|
||||
memset(queueHead, 0, sizeof(*queueHead));
|
||||
|
||||
err = PltCreateEvent(&queueHead->containsDataEvent);
|
||||
err = PltCreateMutex(&queueHead->mutex);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = PltCreateMutex(&queueHead->mutex);
|
||||
err = PltCreateConditionVariable(&queueHead->cond, &queueHead->mutex);
|
||||
if (err != 0) {
|
||||
PltDeleteMutex(&queueHead->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -52,8 +58,24 @@ int LbqInitializeLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead, int sizeB
|
||||
}
|
||||
|
||||
void LbqSignalQueueShutdown(PLINKED_BLOCKING_QUEUE queueHead) {
|
||||
PltLockMutex(&queueHead->mutex);
|
||||
queueHead->shutdown = true;
|
||||
PltSetEvent(&queueHead->containsDataEvent);
|
||||
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);
|
||||
}
|
||||
|
||||
int LbqGetItemCount(PLINKED_BLOCKING_QUEUE queueHead) {
|
||||
@@ -61,21 +83,25 @@ int LbqGetItemCount(PLINKED_BLOCKING_QUEUE queueHead) {
|
||||
}
|
||||
|
||||
int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOCKING_QUEUE_ENTRY entry) {
|
||||
if (queueHead->shutdown) {
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
bool wasEmpty;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (queueHead->head == NULL) {
|
||||
wasEmpty = queueHead->head == NULL;
|
||||
if (wasEmpty) {
|
||||
LC_ASSERT(queueHead->currentSize == 0);
|
||||
LC_ASSERT(queueHead->tail == NULL);
|
||||
queueHead->head = entry;
|
||||
@@ -95,26 +121,33 @@ int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOC
|
||||
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
|
||||
PltSetEvent(&queueHead->containsDataEvent);
|
||||
if (wasEmpty) {
|
||||
// Only call PltSignalConditionVariable() when transitioning from
|
||||
// empty -> non-empty to avoid a useless syscall for each new entry.
|
||||
PltSignalConditionVariable(&queueHead->cond);
|
||||
}
|
||||
|
||||
return LBQ_SUCCESS;
|
||||
}
|
||||
|
||||
// This must be synchronized with LbqFlushQueueItems by the caller
|
||||
int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
|
||||
if (queueHead->shutdown) {
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
|
||||
if (queueHead->head == NULL) {
|
||||
return LBQ_NO_ELEMENT;
|
||||
}
|
||||
|
||||
PltLockMutex(&queueHead->mutex);
|
||||
|
||||
if (queueHead->head == NULL) {
|
||||
if (queueHead->shutdown) {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_NO_ELEMENT;
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
|
||||
if (queueHead->head == NULL) {
|
||||
if (queueHead->draining) {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
else {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_NO_ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
*data = queueHead->head->data;
|
||||
@@ -126,20 +159,23 @@ 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->head == NULL) {
|
||||
return LBQ_NO_ELEMENT;
|
||||
}
|
||||
|
||||
PltLockMutex(&queueHead->mutex);
|
||||
|
||||
if (queueHead->head == NULL) {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_NO_ELEMENT;
|
||||
if (queueHead->draining) {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
else {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_NO_ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
entry = queueHead->head;
|
||||
@@ -148,7 +184,6 @@ 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);
|
||||
@@ -164,49 +199,51 @@ int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
|
||||
|
||||
int LbqWaitForQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
|
||||
PLINKED_BLOCKING_QUEUE_ENTRY entry;
|
||||
int err;
|
||||
|
||||
|
||||
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
|
||||
if (queueHead->shutdown) {
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
return LBQ_INTERRUPTED;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// If this is a user requested wake, process it now
|
||||
if (queueHead->pendingUserWake) {
|
||||
queueHead->pendingUserWake = false;
|
||||
PltUnlockMutex(&queueHead->mutex);
|
||||
|
||||
break;
|
||||
return LBQ_USER_WAKE;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#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;
|
||||
@@ -16,13 +17,15 @@ typedef struct _LINKED_BLOCKING_QUEUE_ENTRY {
|
||||
|
||||
typedef struct _LINKED_BLOCKING_QUEUE {
|
||||
PLT_MUTEX mutex;
|
||||
PLT_EVENT containsDataEvent;
|
||||
int sizeBound;
|
||||
int currentSize;
|
||||
bool shutdown;
|
||||
int lifetimeSize;
|
||||
PLT_COND cond;
|
||||
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);
|
||||
@@ -33,4 +36,6 @@ 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);
|
||||
|
||||
+85
-30
@@ -6,16 +6,21 @@
|
||||
// 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.
|
||||
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
|
||||
static int serviceEnetHostInternal(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs, bool ignoreInterrupts) {
|
||||
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 (ConnectionInterrupted) {
|
||||
if (!ignoreInterrupts && ConnectionInterrupted) {
|
||||
Limelog("ENet wait interrupted\n");
|
||||
SetLastSocketError(EINTR);
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
@@ -27,39 +32,74 @@ int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
|
||||
|
||||
timeoutMs -= selectedTimeout;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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');
|
||||
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");
|
||||
}
|
||||
else {
|
||||
nextDot = strchr(nextNumber, '.');
|
||||
Limelog("Failed to receive ENet peer disconnection acknowledgement: %d\n", LastSocketFail());
|
||||
}
|
||||
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 -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++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -71,10 +111,17 @@ void* extendBuffer(void* ptr, size_t newSize) {
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
bool isReferenceFrameInvalidationEnabled(void) {
|
||||
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_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();
|
||||
}
|
||||
|
||||
void LiInitializeStreamConfiguration(PSTREAM_CONFIGURATION streamConfig) {
|
||||
@@ -100,3 +147,11 @@ void LiInitializeServerInformation(PSERVER_INFORMATION serverInfo) {
|
||||
uint64_t LiGetMillis(void) {
|
||||
return PltGetMillis();
|
||||
}
|
||||
|
||||
uint64_t LiGetMicroseconds(void) {
|
||||
return PltGetMicroseconds();
|
||||
}
|
||||
|
||||
uint32_t LiGetHostFeatureFlags(void) {
|
||||
return SunshineFeatureFlags;
|
||||
}
|
||||
|
||||
+367
-132
@@ -1,10 +1,9 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "Platform.h"
|
||||
#include "PlatformThreads.h"
|
||||
#include "PlatformSockets.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
#include "Limelight-internal.h"
|
||||
#if defined(__vita__)
|
||||
#include <pthread.h>
|
||||
#include <psp2/kernel/processmgr.h>
|
||||
#endif
|
||||
|
||||
// The maximum amount of time before observing an interrupt
|
||||
// in PltSleepMsInterruptible().
|
||||
@@ -14,14 +13,12 @@ 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)
|
||||
|
||||
@@ -38,12 +35,10 @@ 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
|
||||
hKernel32 = LoadLibraryA("kernel32.dll");
|
||||
setThreadDescriptionFunc = (SetThreadDescription_t)GetProcAddress(hKernel32, "SetThreadDescription");
|
||||
setThreadDescriptionFunc = (SetThreadDescription_t)GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetThreadDescription");
|
||||
if (setThreadDescriptionFunc != NULL) {
|
||||
WCHAR nameW[16];
|
||||
size_t chars;
|
||||
@@ -51,7 +46,6 @@ 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,
|
||||
@@ -74,9 +68,9 @@ void setThreadNameWin32(const char* name) {
|
||||
|
||||
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
|
||||
struct thread_context* ctx = (struct thread_context*)lpParameter;
|
||||
#elif defined(__vita__)
|
||||
int ThreadProc(SceSize args, void *argp) {
|
||||
struct thread_context* ctx = (struct thread_context*)argp;
|
||||
#elif defined(__WIIU__)
|
||||
int ThreadProc(int argc, const char** argv) {
|
||||
struct thread_context* ctx = (struct thread_context*)argv;
|
||||
#else
|
||||
void* ThreadProc(void* context) {
|
||||
struct thread_context* ctx = (struct thread_context*)context;
|
||||
@@ -84,19 +78,17 @@ void* ThreadProc(void* context) {
|
||||
|
||||
#if defined(LC_WINDOWS)
|
||||
setThreadNameWin32(ctx->name);
|
||||
#elif defined(__linux__)
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
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__)
|
||||
#if defined(LC_WINDOWS) || defined(__vita__) || defined(__WIIU__) || defined(__3DS__)
|
||||
return 0;
|
||||
#else
|
||||
return NULL;
|
||||
@@ -105,9 +97,10 @@ void* ThreadProc(void* context) {
|
||||
|
||||
void PltSleepMs(int ms) {
|
||||
#if defined(LC_WINDOWS)
|
||||
WaitForSingleObjectEx(GetCurrentThread(), ms, FALSE);
|
||||
#elif defined(__vita__)
|
||||
sceKernelDelayThread(ms * 1000);
|
||||
SleepEx(ms, FALSE);
|
||||
#elif defined(__3DS__)
|
||||
s64 nsecs = ms * 1000000;
|
||||
svcSleepThread(nsecs);
|
||||
#else
|
||||
useconds_t usecs = ms * 1000;
|
||||
usleep(usecs);
|
||||
@@ -124,15 +117,11 @@ void PltSleepMsInterruptible(PLT_THREAD* thread, int ms) {
|
||||
|
||||
int PltCreateMutex(PLT_MUTEX* mutex) {
|
||||
#if defined(LC_WINDOWS)
|
||||
*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;
|
||||
}
|
||||
InitializeSRWLock(mutex);
|
||||
#elif defined(__WIIU__)
|
||||
OSFastMutex_Init(mutex, "");
|
||||
#elif defined(__3DS__)
|
||||
LightLock_Init(mutex);
|
||||
#else
|
||||
int err = pthread_mutex_init(mutex, NULL);
|
||||
if (err != 0) {
|
||||
@@ -144,11 +133,12 @@ int PltCreateMutex(PLT_MUTEX* mutex) {
|
||||
}
|
||||
|
||||
void PltDeleteMutex(PLT_MUTEX* mutex) {
|
||||
LC_ASSERT(activeMutexes > 0);
|
||||
activeMutexes--;
|
||||
#if defined(LC_WINDOWS)
|
||||
CloseHandle(*mutex);
|
||||
#elif defined(__vita__)
|
||||
sceKernelDeleteMutex(*mutex);
|
||||
// No-op to destroy a SRWLOCK
|
||||
#elif defined(__WIIU__) || defined(__3DS__)
|
||||
|
||||
#else
|
||||
pthread_mutex_destroy(mutex);
|
||||
#endif
|
||||
@@ -156,13 +146,11 @@ void PltDeleteMutex(PLT_MUTEX* mutex) {
|
||||
|
||||
void PltLockMutex(PLT_MUTEX* mutex) {
|
||||
#if defined(LC_WINDOWS)
|
||||
int err;
|
||||
err = WaitForSingleObjectEx(*mutex, INFINITE, FALSE);
|
||||
if (err != WAIT_OBJECT_0) {
|
||||
LC_ASSERT(false);
|
||||
}
|
||||
#elif defined(__vita__)
|
||||
sceKernelLockMutex(*mutex, 1, NULL);
|
||||
AcquireSRWLockExclusive(mutex);
|
||||
#elif defined(__WIIU__)
|
||||
OSFastMutex_Lock(mutex);
|
||||
#elif defined(__3DS__)
|
||||
LightLock_Lock(mutex);
|
||||
#else
|
||||
pthread_mutex_lock(mutex);
|
||||
#endif
|
||||
@@ -170,35 +158,47 @@ void PltLockMutex(PLT_MUTEX* mutex) {
|
||||
|
||||
void PltUnlockMutex(PLT_MUTEX* mutex) {
|
||||
#if defined(LC_WINDOWS)
|
||||
ReleaseMutex(*mutex);
|
||||
#elif defined(__vita__)
|
||||
sceKernelUnlockMutex(*mutex, 1);
|
||||
ReleaseSRWLockExclusive(mutex);
|
||||
#elif defined(__WIIU__)
|
||||
OSFastMutex_Unlock(mutex);
|
||||
#elif defined(__3DS__)
|
||||
LightLock_Unlock(mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PltJoinThread(PLT_THREAD* thread) {
|
||||
LC_ASSERT(thread->cancelled);
|
||||
LC_ASSERT(activeThreads > 0);
|
||||
activeThreads--;
|
||||
|
||||
#if defined(LC_WINDOWS)
|
||||
WaitForSingleObjectEx(thread->handle, INFINITE, FALSE);
|
||||
#elif defined(__vita__)
|
||||
while(thread->alive) {
|
||||
PltSleepMs(10);
|
||||
}
|
||||
if (thread->context != NULL)
|
||||
free(thread->context);
|
||||
CloseHandle(thread->handle);
|
||||
#elif defined(__WIIU__)
|
||||
OSJoinThread(&thread->thread, NULL);
|
||||
#elif defined(__3DS__)
|
||||
threadJoin(thread->thread, U64_MAX);
|
||||
threadFree(thread->thread);
|
||||
#else
|
||||
pthread_join(thread->thread, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PltCloseThread(PLT_THREAD* thread) {
|
||||
void PltDetachThread(PLT_THREAD* thread) {
|
||||
LC_ASSERT(activeThreads > 0);
|
||||
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(__vita__)
|
||||
sceKernelDeleteThread(thread->handle);
|
||||
#elif defined(__WIIU__)
|
||||
OSDetachThread(&thread->thread);
|
||||
#elif defined(__3DS__)
|
||||
threadDetach(thread->thread);
|
||||
#else
|
||||
pthread_detach(thread->thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -210,6 +210,12 @@ 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;
|
||||
|
||||
@@ -221,7 +227,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)
|
||||
@@ -232,25 +238,67 @@ int PltCreateThread(const char* name, ThreadEntry entry, void* context, PLT_THRE
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#elif defined(__vita__)
|
||||
#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))
|
||||
{
|
||||
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);
|
||||
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) {
|
||||
free(ctx);
|
||||
return -1;
|
||||
}
|
||||
sceKernelStartThread(thread->handle, sizeof(struct thread_context), ctx);
|
||||
}
|
||||
#else
|
||||
{
|
||||
int err = pthread_create(&thread->thread, NULL, ThreadProc, ctx);
|
||||
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);
|
||||
if (err != 0) {
|
||||
free(ctx);
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -265,20 +313,14 @@ 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
|
||||
pthread_mutex_init(&event->mutex, NULL);
|
||||
pthread_cond_init(&event->cond, NULL);
|
||||
if (PltCreateMutex(&event->mutex) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (PltCreateConditionVariable(&event->cond, &event->mutex) < 0) {
|
||||
PltDeleteMutex(&event->mutex);
|
||||
return -1;
|
||||
}
|
||||
event->signalled = false;
|
||||
#endif
|
||||
activeEvents++;
|
||||
@@ -286,31 +328,24 @@ 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
|
||||
pthread_mutex_destroy(&event->mutex);
|
||||
pthread_cond_destroy(&event->cond);
|
||||
PltDeleteConditionVariable(&event->cond);
|
||||
PltDeleteMutex(&event->mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PltSetEvent(PLT_EVENT* event) {
|
||||
#if defined(LC_WINDOWS)
|
||||
SetEvent(*event);
|
||||
#elif defined(__vita__)
|
||||
sceKernelLockMutex(event->mutex, 1, NULL);
|
||||
event->signalled = true;
|
||||
sceKernelUnlockMutex(event->mutex, 1);
|
||||
sceKernelSignalCondAll(event->cond);
|
||||
#else
|
||||
pthread_mutex_lock(&event->mutex);
|
||||
PltLockMutex(&event->mutex);
|
||||
event->signalled = true;
|
||||
pthread_mutex_unlock(&event->mutex);
|
||||
pthread_cond_broadcast(&event->cond);
|
||||
PltUnlockMutex(&event->mutex);
|
||||
PltSignalConditionVariable(&event->cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -322,63 +357,262 @@ void PltClearEvent(PLT_EVENT* event) {
|
||||
#endif
|
||||
}
|
||||
|
||||
int PltWaitForEvent(PLT_EVENT* event) {
|
||||
void PltWaitForEvent(PLT_EVENT* event) {
|
||||
#if defined(LC_WINDOWS)
|
||||
DWORD error;
|
||||
|
||||
error = WaitForSingleObjectEx(*event, INFINITE, FALSE);
|
||||
if (error == WAIT_OBJECT_0) {
|
||||
return PLT_WAIT_SUCCESS;
|
||||
}
|
||||
else {
|
||||
LC_ASSERT(false);
|
||||
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;
|
||||
WaitForSingleObjectEx(*event, INFINITE, FALSE);
|
||||
#else
|
||||
pthread_mutex_lock(&event->mutex);
|
||||
PltLockMutex(&event->mutex);
|
||||
while (!event->signalled) {
|
||||
pthread_cond_wait(&event->cond, &event->mutex);
|
||||
PltWaitForConditionVariable(&event->cond, &event->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&event->mutex);
|
||||
|
||||
return PLT_WAIT_SUCCESS;
|
||||
PltUnlockMutex(&event->mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t PltGetMillis(void) {
|
||||
int PltCreateConditionVariable(PLT_COND* cond, PLT_MUTEX* mutex) {
|
||||
#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);
|
||||
InitializeConditionVariable(cond);
|
||||
#elif defined(__WIIU__)
|
||||
OSFastCond_Init(cond, "");
|
||||
#elif defined(__3DS__)
|
||||
CondVar_Init(cond);
|
||||
#else
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
|
||||
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
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
strcpy(dest, src);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int initializePlatform(void) {
|
||||
int err;
|
||||
|
||||
PltTicksInit();
|
||||
|
||||
err = initializePlatformSockets();
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
err = enet_initialize();
|
||||
if (err != 0) {
|
||||
return err;
|
||||
@@ -386,17 +620,18 @@ 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);
|
||||
}
|
||||
|
||||
+100
-2
@@ -1,19 +1,49 @@
|
||||
#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>
|
||||
|
||||
#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>
|
||||
@@ -21,6 +51,7 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -32,6 +63,19 @@
|
||||
# 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"
|
||||
|
||||
@@ -61,7 +105,61 @@
|
||||
#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);
|
||||
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#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);
|
||||
+467
-120
@@ -1,4 +1,3 @@
|
||||
#include "PlatformSockets.h"
|
||||
#include "Limelight-internal.h"
|
||||
|
||||
#define TEST_PORT_TIMEOUT_SEC 3
|
||||
@@ -6,38 +5,61 @@
|
||||
#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
|
||||
|
||||
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string)
|
||||
{
|
||||
char addrstr[INET6_ADDRSTRLEN];
|
||||
#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)
|
||||
{
|
||||
char addrstr[URLSAFESTRING_LEN];
|
||||
|
||||
#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
|
||||
sprintf(string, "[%s]", addrstr);
|
||||
snprintf(string, stringLength, "[%s]", addrstr);
|
||||
}
|
||||
else {
|
||||
else
|
||||
#endif
|
||||
{
|
||||
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
|
||||
sprintf(string, "%s", addrstr);
|
||||
snprintf(string, stringLength, "%s", addrstr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +71,16 @@ 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, so we won't use
|
||||
// it for non-fatal socket operations.
|
||||
// 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
|
||||
return -1;
|
||||
#else
|
||||
struct timeval val;
|
||||
@@ -63,22 +92,8 @@ int setNonFatalRecvTimeoutMs(SOCKET s, int timeoutMs) {
|
||||
#endif
|
||||
}
|
||||
|
||||
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__)
|
||||
#if defined(LC_WINDOWS)
|
||||
// We could have used WSAPoll() but it has some nasty bugs
|
||||
// https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
|
||||
//
|
||||
@@ -103,19 +118,10 @@ 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;
|
||||
@@ -141,15 +147,40 @@ 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;
|
||||
@@ -174,7 +205,14 @@ int recvUdpSocket(SOCKET s, char* buffer, int size, bool useSelect) {
|
||||
if (err < 0 &&
|
||||
(LastSocketError() == EWOULDBLOCK ||
|
||||
LastSocketError() == EINTR ||
|
||||
LastSocketError() == EAGAIN)) {
|
||||
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)) {
|
||||
// Return 0 for timeout
|
||||
return 0;
|
||||
}
|
||||
@@ -200,24 +238,90 @@ void closeSocket(SOCKET s) {
|
||||
#endif
|
||||
}
|
||||
|
||||
SOCKET bindUdpSocket(int addrfamily, int bufferSize) {
|
||||
// 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 s;
|
||||
struct sockaddr_storage addr;
|
||||
LC_SOCKADDR bindAddr;
|
||||
int err;
|
||||
|
||||
LC_ASSERT(addrfamily == AF_INET || addrfamily == AF_INET6);
|
||||
|
||||
s = createSocket(addrfamily, SOCK_DGRAM, IPPROTO_UDP, false);
|
||||
s = createSocket(addressFamily, SOCK_DGRAM, IPPROTO_UDP, false);
|
||||
if (s == INVALID_SOCKET) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
err = LastSocketError();
|
||||
Limelog("bind() failed: %d\n", err);
|
||||
closeSocket(s);
|
||||
@@ -225,56 +329,97 @@ SOCKET bindUdpSocket(int addrfamily, int bufferSize) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
#ifdef LC_DARWIN
|
||||
#if defined(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
|
||||
|
||||
// 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));
|
||||
// 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)
|
||||
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
|
||||
break;
|
||||
}
|
||||
else if (bufferSize - RCV_BUFFER_SIZE_STEP <= RCV_BUFFER_SIZE_MIN) {
|
||||
// Last shot - we're trying the minimum
|
||||
bufferSize = RCV_BUFFER_SIZE_MIN;
|
||||
Limelog("Selected receive buffer size: %d\n", bufferSize);
|
||||
}
|
||||
else {
|
||||
// Lower the requested size by another step
|
||||
bufferSize -= RCV_BUFFER_SIZE_STEP;
|
||||
Limelog("Unable to set receive buffer size: %d\n", LastSocketError());
|
||||
}
|
||||
|
||||
{
|
||||
SOCKADDR_LEN len = sizeof(bufferSize);
|
||||
if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, &len) == 0) {
|
||||
Limelog("Actual receive buffer size: %d\n", bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
return SOCKET_ERROR;
|
||||
#error Please define your platform non-blocking sockets API!
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -304,7 +449,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;
|
||||
struct sockaddr_in6 addr;
|
||||
LC_SOCKADDR addr;
|
||||
struct pollfd pfd;
|
||||
int err;
|
||||
int val;
|
||||
@@ -359,7 +504,7 @@ SOCKET connectTcpSocket(struct sockaddr_storage* dstaddr, SOCKADDR_LEN addrlen,
|
||||
|
||||
// Start connection
|
||||
memcpy(&addr, dstaddr, addrlen);
|
||||
addr.sin6_port = htons(port);
|
||||
SET_PORT(&addr, port);
|
||||
err = connect(s, (struct sockaddr*) &addr, addrlen);
|
||||
if (err < 0) {
|
||||
err = (int)LastSocketError();
|
||||
@@ -367,7 +512,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;
|
||||
@@ -387,6 +532,17 @@ 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);
|
||||
@@ -396,10 +552,11 @@ 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);
|
||||
@@ -411,6 +568,40 @@ 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) {
|
||||
@@ -443,10 +634,43 @@ 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;
|
||||
@@ -462,14 +686,35 @@ 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
|
||||
if (tcpTestPort != 0 && (res->ai_next != NULL || (tcpTestPort & TCP_PORT_FLAG_ALWAYS_TEST))) {
|
||||
// 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)) {
|
||||
SOCKET testSocket = connectTcpSocket((struct sockaddr_storage*)currentAddr->ai_addr,
|
||||
currentAddr->ai_addrlen,
|
||||
(SOCKADDR_LEN)currentAddr->ai_addrlen,
|
||||
tcpTestPort & TCP_PORT_MASK,
|
||||
TEST_PORT_TIMEOUT_SEC);
|
||||
if (testSocket == INVALID_SOCKET) {
|
||||
@@ -480,58 +725,46 @@ int resolveHostName(const char* host, int family, int tcpTestPort, struct sockad
|
||||
closeSocket(testSocket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
memcpy(addr, currentAddr->ai_addr, currentAddr->ai_addrlen);
|
||||
*addrLen = currentAddr->ai_addrlen;
|
||||
|
||||
*addrLen = (SOCKADDR_LEN)currentAddr->ai_addrlen;
|
||||
|
||||
freeaddrinfo(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Limelog("No working addresses found for host: %s\n", host);
|
||||
freeaddrinfo(res);
|
||||
return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#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) {
|
||||
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;
|
||||
}
|
||||
return isPrivateNetworkAddressV4((struct sockaddr_in*)address, false);
|
||||
}
|
||||
#ifdef AF_INET6
|
||||
else if (address->ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)address;
|
||||
static unsigned char linkLocalPrefix[] = {0xfe, 0x80};
|
||||
@@ -551,13 +784,127 @@ 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)
|
||||
#if defined(LC_WINDOWS_DESKTOP)
|
||||
DWORD negotiatedVersion;
|
||||
PWLAN_INTERFACE_INFO_LIST wlanInterfaceList;
|
||||
DWORD i;
|
||||
@@ -572,11 +919,11 @@ void enterLowLatencyMode(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
pfnWlanOpenHandle = GetProcAddress(WlanApiLibraryHandle, "WlanOpenHandle");
|
||||
pfnWlanCloseHandle = GetProcAddress(WlanApiLibraryHandle, "WlanCloseHandle");
|
||||
pfnWlanFreeMemory = GetProcAddress(WlanApiLibraryHandle, "WlanFreeMemory");
|
||||
pfnWlanEnumInterfaces = GetProcAddress(WlanApiLibraryHandle, "WlanEnumInterfaces");
|
||||
pfnWlanSetInterface = GetProcAddress(WlanApiLibraryHandle, "WlanSetInterface");
|
||||
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");
|
||||
|
||||
if (pfnWlanOpenHandle == NULL || pfnWlanCloseHandle == NULL ||
|
||||
pfnWlanFreeMemory == NULL || pfnWlanEnumInterfaces == NULL || pfnWlanSetInterface == NULL) {
|
||||
@@ -631,7 +978,7 @@ void enterLowLatencyMode(void) {
|
||||
}
|
||||
|
||||
void exitLowLatencyMode(void) {
|
||||
#if defined(LC_WINDOWS)
|
||||
#if defined(LC_WINDOWS_DESKTOP)
|
||||
// Closing our WLAN client handle will undo our optimizations
|
||||
if (WlanHandle != NULL) {
|
||||
pfnWlanCloseHandle(WlanHandle, NULL);
|
||||
@@ -660,7 +1007,7 @@ int initializePlatformSockets(void) {
|
||||
#if defined(LC_WINDOWS)
|
||||
WSADATA data;
|
||||
return WSAStartup(MAKEWORD(2, 0), &data);
|
||||
#elif defined(__vita__)
|
||||
#elif defined(__vita__) || defined(__WIIU__) || defined(__3DS__)
|
||||
return 0; // already initialized
|
||||
#elif defined(LC_POSIX) && !defined(LC_CHROME)
|
||||
// Disable SIGPIPE signals to avoid us getting
|
||||
|
||||
+64
-35
@@ -2,46 +2,71 @@
|
||||
|
||||
#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
|
||||
|
||||
// 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 EWOULDBLOCK
|
||||
#undef EWOULDBLOCK
|
||||
#endif
|
||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||
|
||||
#ifdef EINPROGRESS
|
||||
#undef EINPROGRESS
|
||||
#endif
|
||||
#define EINPROGRESS WSAEINPROGRESS
|
||||
|
||||
#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;
|
||||
|
||||
@@ -56,24 +81,7 @@ 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
|
||||
@@ -86,25 +94,46 @@ 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)
|
||||
void addrToUrlSafeString(struct sockaddr_storage* addr, char* string);
|
||||
#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
|
||||
|
||||
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 addrfamily, int bufferSize);
|
||||
SOCKET bindUdpSocket(int addressFamily, struct sockaddr_storage* localAddr, SOCKADDR_LEN addrLen, int bufferSize, int socketQosType);
|
||||
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
|
||||
|
||||
+30
-23
@@ -6,32 +6,29 @@
|
||||
typedef void(*ThreadEntry)(void* context);
|
||||
|
||||
#if defined(LC_WINDOWS)
|
||||
typedef HANDLE PLT_MUTEX;
|
||||
typedef HANDLE PLT_EVENT;
|
||||
typedef SRWLOCK PLT_MUTEX;
|
||||
typedef CONDITION_VARIABLE PLT_COND;
|
||||
typedef struct _PLT_THREAD {
|
||||
HANDLE handle;
|
||||
bool cancelled;
|
||||
} PLT_THREAD;
|
||||
#elif defined(__vita__)
|
||||
typedef int PLT_MUTEX;
|
||||
typedef struct _PLT_EVENT {
|
||||
int mutex;
|
||||
int cond;
|
||||
bool signalled;
|
||||
} PLT_EVENT;
|
||||
#elif defined(__WIIU__)
|
||||
typedef OSFastMutex PLT_MUTEX;
|
||||
typedef OSFastCondition PLT_COND;
|
||||
typedef struct _PLT_THREAD {
|
||||
int handle;
|
||||
OSThread thread;
|
||||
int cancelled;
|
||||
void *context;
|
||||
bool alive;
|
||||
} PLT_THREAD;
|
||||
#elif defined(__3DS__)
|
||||
typedef LightLock PLT_MUTEX;
|
||||
typedef CondVar PLT_COND;
|
||||
typedef struct _PLT_THREAD {
|
||||
Thread thread;
|
||||
bool cancelled;
|
||||
} PLT_THREAD;
|
||||
#elif defined (LC_POSIX)
|
||||
typedef pthread_mutex_t PLT_MUTEX;
|
||||
typedef struct _PLT_EVENT {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool signalled;
|
||||
} PLT_EVENT;
|
||||
typedef pthread_cond_t PLT_COND;
|
||||
typedef struct _PLT_THREAD {
|
||||
pthread_t thread;
|
||||
bool cancelled;
|
||||
@@ -40,27 +37,37 @@ typedef struct _PLT_THREAD {
|
||||
#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);
|
||||
int PltWaitForEvent(PLT_EVENT* event);
|
||||
void PltWaitForEvent(PLT_EVENT* event);
|
||||
|
||||
void PltRunThreadProc(void);
|
||||
|
||||
#define PLT_WAIT_SUCCESS 0
|
||||
#define PLT_WAIT_INTERRUPTED 1
|
||||
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 PltSleepMs(int ms);
|
||||
void PltSleepMsInterruptible(PLT_THREAD* thread, int ms);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,729 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#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);
|
||||
@@ -1,493 +0,0 @@
|
||||
#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) {
|
||||
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
|
||||
int dropIndex = rand() % queue->bufferDataPackets;
|
||||
PRTP_PACKET droppedRtpPacket = NULL;
|
||||
int droppedRtpPacketLength = 0;
|
||||
#endif
|
||||
|
||||
PRTPFEC_QUEUE_ENTRY entry = queue->bufferHead;
|
||||
while (entry != NULL) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Video.h"
|
||||
|
||||
typedef struct _RTPFEC_QUEUE_ENTRY {
|
||||
PRTP_PACKET packet;
|
||||
int length;
|
||||
bool isParity;
|
||||
unsigned long long receiveTimeMs;
|
||||
unsigned int 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;
|
||||
unsigned long long bufferFirstRecvTimeMs;
|
||||
int bufferSize;
|
||||
int bufferLowestSequenceNumber;
|
||||
int bufferHighestSequenceNumber;
|
||||
int bufferFirstParitySequenceNumber;
|
||||
int bufferDataPackets;
|
||||
int bufferParityPackets;
|
||||
int receivedBufferDataPackets;
|
||||
int fecPercentage;
|
||||
int nextContiguousSequenceNumber;
|
||||
|
||||
int 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);
|
||||
@@ -1,256 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#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;
|
||||
int 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);
|
||||
@@ -0,0 +1,805 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#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);
|
||||
+627
-94
File diff suppressed because it is too large
Load Diff
+70
-32
@@ -1,3 +1,4 @@
|
||||
#include "Platform.h"
|
||||
#include "Rtsp.h"
|
||||
|
||||
// Check if String s begins with the given prefix
|
||||
@@ -26,7 +27,7 @@ static int getMessageLength(PRTSP_MESSAGE msg) {
|
||||
// Add length of response-specific strings
|
||||
else {
|
||||
char statusCodeStr[16];
|
||||
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
|
||||
snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
|
||||
count += strlen(statusCodeStr);
|
||||
count += strlen(msg->message.response.statusString);
|
||||
// two spaces and \r\n
|
||||
@@ -62,6 +63,7 @@ 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;
|
||||
@@ -88,7 +90,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
|
||||
messageBuffer[length] = 0;
|
||||
|
||||
// Get the first token of the message
|
||||
token = strtok(messageBuffer, delim);
|
||||
token = strtok_r(messageBuffer, delim, &strtokCtx);
|
||||
if (token == NULL) {
|
||||
exitCode = RTSP_ERROR_MALFORMED;
|
||||
goto ExitFailure;
|
||||
@@ -101,15 +103,15 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
|
||||
protocol = token;
|
||||
|
||||
// Get the status code
|
||||
token = strtok(NULL, delim);
|
||||
statusCode = atoi(token);
|
||||
token = strtok_r(NULL, delim, &strtokCtx);
|
||||
if (token == NULL) {
|
||||
exitCode = RTSP_ERROR_MALFORMED;
|
||||
goto ExitFailure;
|
||||
}
|
||||
statusCode = atoi(token);
|
||||
|
||||
// Get the status string
|
||||
statusStr = strtok(NULL, end);
|
||||
statusStr = strtok_r(NULL, end, &strtokCtx);
|
||||
if (statusStr == NULL) {
|
||||
exitCode = RTSP_ERROR_MALFORMED;
|
||||
goto ExitFailure;
|
||||
@@ -124,12 +126,12 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
|
||||
else {
|
||||
flag = TYPE_REQUEST;
|
||||
command = token;
|
||||
target = strtok(NULL, delim);
|
||||
target = strtok_r(NULL, delim, &strtokCtx);
|
||||
if (target == NULL) {
|
||||
exitCode = RTSP_ERROR_MALFORMED;
|
||||
goto ExitFailure;
|
||||
}
|
||||
protocol = strtok(NULL, delim);
|
||||
protocol = strtok_r(NULL, delim, &strtokCtx);
|
||||
if (protocol == NULL) {
|
||||
exitCode = RTSP_ERROR_MALFORMED;
|
||||
goto ExitFailure;
|
||||
@@ -144,7 +146,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
|
||||
// Parse remaining options
|
||||
while (token != NULL)
|
||||
{
|
||||
token = strtok(NULL, typeFlag == TOKEN_OPTION ? optDelim : end);
|
||||
token = strtok_r(NULL, typeFlag == TOKEN_OPTION ? optDelim : end, &strtokCtx);
|
||||
if (token != NULL) {
|
||||
if (typeFlag == TOKEN_OPTION) {
|
||||
opt = token;
|
||||
@@ -159,7 +161,7 @@ int parseRtspMessage(PRTSP_MESSAGE msg, char* rtspMessage, int length) {
|
||||
}
|
||||
newOpt->flags = 0;
|
||||
newOpt->option = opt;
|
||||
newOpt->content = token;
|
||||
newOpt->content = token + 1; // Skip the protocol defined blank space
|
||||
newOpt->next = NULL;
|
||||
insertOption(&options, newOpt);
|
||||
|
||||
@@ -174,6 +176,16 @@ 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;
|
||||
@@ -308,9 +320,22 @@ 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];
|
||||
@@ -322,44 +347,53 @@ char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
|
||||
|
||||
if (msg->type == TYPE_REQUEST) {
|
||||
// command [space]
|
||||
strcpy(serializedMessage, msg->message.request.command);
|
||||
strcat(serializedMessage, " ");
|
||||
if (!appendString(serializedMessage, &offset, &size, msg->message.request.command) || !appendString(serializedMessage, &offset, &size, " ")) {
|
||||
goto fail;
|
||||
}
|
||||
// target [space]
|
||||
strcat(serializedMessage, msg->message.request.target);
|
||||
strcat(serializedMessage, " ");
|
||||
if (!appendString(serializedMessage, &offset, &size, msg->message.request.target) || !appendString(serializedMessage, &offset, &size, " ")) {
|
||||
goto fail;
|
||||
}
|
||||
// protocol \r\n
|
||||
strcat(serializedMessage, msg->protocol);
|
||||
strcat(serializedMessage, "\r\n");
|
||||
if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// protocol [space]
|
||||
strcpy(serializedMessage, msg->protocol);
|
||||
strcat(serializedMessage, " ");
|
||||
if (!appendString(serializedMessage, &offset, &size, msg->protocol) || !appendString(serializedMessage, &offset, &size, " ")) {
|
||||
goto fail;
|
||||
}
|
||||
// status code [space]
|
||||
sprintf(statusCodeStr, "%d", msg->message.response.statusCode);
|
||||
strcat(serializedMessage, statusCodeStr);
|
||||
strcat(serializedMessage, " ");
|
||||
snprintf(statusCodeStr, sizeof(statusCodeStr), "%d", msg->message.response.statusCode);
|
||||
if (!appendString(serializedMessage, &offset, &size, statusCodeStr) || !appendString(serializedMessage, &offset, &size, " ")) {
|
||||
goto fail;
|
||||
}
|
||||
// status str\r\n
|
||||
strcat(serializedMessage, msg->message.response.statusString);
|
||||
strcat(serializedMessage, "\r\n");
|
||||
if (!appendString(serializedMessage, &offset, &size, msg->message.response.statusString) || !appendString(serializedMessage, &offset, &size, "\r\n")) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
// option content\r\n
|
||||
while (current != NULL) {
|
||||
strcat(serializedMessage, current->option);
|
||||
strcat(serializedMessage, ": ");
|
||||
strcat(serializedMessage, current->content);
|
||||
strcat(serializedMessage, "\r\n");
|
||||
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;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
// Final \r\n
|
||||
strcat(serializedMessage, "\r\n");
|
||||
if (!appendString(serializedMessage, &offset, &size, "\r\n")) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// payload
|
||||
if (msg->payload != NULL) {
|
||||
int offset;
|
||||
|
||||
// Find end of the RTSP message header
|
||||
for (offset = 0; serializedMessage[offset] != 0; offset++);
|
||||
if (msg->payloadLength > size) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Add the payload after
|
||||
memcpy(&serializedMessage[offset], msg->payload, msg->payloadLength);
|
||||
@@ -367,10 +401,14 @@ char* serializeRtspMessage(PRTSP_MESSAGE msg, int* serializedLength) {
|
||||
*serializedLength = offset + msg->payloadLength;
|
||||
}
|
||||
else {
|
||||
*serializedLength = (int)strlen(serializedMessage);
|
||||
*serializedLength = offset;
|
||||
}
|
||||
|
||||
return serializedMessage;
|
||||
|
||||
fail:
|
||||
free(serializedMessage);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Free everything in a msg struct
|
||||
|
||||
+249
-90
@@ -35,21 +35,50 @@ static int getSerializedAttributeListSize(PSDP_OPTION head) {
|
||||
|
||||
currentEntry = currentEntry->next;
|
||||
}
|
||||
return (int)size;
|
||||
// Add one for the null terminator
|
||||
return (int)size + 1;
|
||||
}
|
||||
|
||||
// Populate the serialized attribute list into a string
|
||||
static int fillSerializedAttributeList(char* buffer, PSDP_OPTION head) {
|
||||
static int fillSerializedAttributeList(char* buffer, size_t length, PSDP_OPTION head) {
|
||||
PSDP_OPTION currentEntry = head;
|
||||
int offset = 0;
|
||||
while (currentEntry != NULL) {
|
||||
offset += sprintf(&buffer[offset], "a=%s:", currentEntry->name);
|
||||
memcpy(&buffer[offset], currentEntry->payload, currentEntry->payloadLen);
|
||||
offset += currentEntry->payloadLen;
|
||||
offset += sprintf(&buffer[offset], " \r\n");
|
||||
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;
|
||||
}
|
||||
|
||||
currentEntry = currentEntry->next;
|
||||
}
|
||||
|
||||
// We should have only space for the null terminator left over
|
||||
LC_ASSERT(length == 1);
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -62,9 +91,13 @@ 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);
|
||||
|
||||
@@ -133,25 +166,83 @@ static int addGen4Options(PSDP_OPTION* head, char* addrStr) {
|
||||
char payloadStr[92];
|
||||
int err = 0;
|
||||
|
||||
sprintf(payloadStr, "rtsp://%s:48010", addrStr);
|
||||
LC_ASSERT(RtspPortNumber != 0);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "rtsp://%s:%u", addrStr, RtspPortNumber);
|
||||
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];
|
||||
|
||||
// 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");
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
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");
|
||||
}
|
||||
|
||||
// Recovery mode can cause the FEC percentage to change mid-frame, which
|
||||
@@ -167,7 +258,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
int audioChannelCount;
|
||||
int audioChannelMask;
|
||||
int err;
|
||||
int bitrate;
|
||||
int adjustedBitrate;
|
||||
|
||||
// This must have been resolved to either local or remote by now
|
||||
LC_ASSERT(StreamConfig.streamingRemotely != STREAM_CFG_AUTO);
|
||||
@@ -175,15 +266,66 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
optionHead = NULL;
|
||||
err = 0;
|
||||
|
||||
sprintf(payloadStr, "%d", StreamConfig.width);
|
||||
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);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportWd", payloadStr);
|
||||
sprintf(payloadStr, "%d", StreamConfig.height);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.height);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].clientViewportHt", payloadStr);
|
||||
|
||||
sprintf(payloadStr, "%d", StreamConfig.fps);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.fps);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].maxFPS", payloadStr);
|
||||
|
||||
sprintf(payloadStr, "%d", StreamConfig.packetSize);
|
||||
// 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);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].packetSize", payloadStr);
|
||||
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].rateControlMode", "4");
|
||||
@@ -191,44 +333,43 @@ 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 (bitrate > 500) {
|
||||
bitrate -= 500;
|
||||
if (adjustedBitrate > 500) {
|
||||
adjustedBitrate -= 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.
|
||||
bitrate = bitrate > 100000 ? 100000 : bitrate;
|
||||
adjustedBitrate = adjustedBitrate > 100000 ? 100000 : adjustedBitrate;
|
||||
|
||||
// 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) {
|
||||
sprintf(payloadStr, "%d", bitrate);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", adjustedBitrate);
|
||||
|
||||
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) {
|
||||
@@ -236,7 +377,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].peakBitrate", "4");
|
||||
}
|
||||
|
||||
sprintf(payloadStr, "%d", bitrate);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", adjustedBitrate);
|
||||
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.minimumBitrate", payloadStr);
|
||||
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bw.maximumBitrate", payloadStr);
|
||||
}
|
||||
@@ -286,26 +427,17 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
// If not using slicing, we request 1 slice per frame
|
||||
slicesPerFrame = 1;
|
||||
}
|
||||
sprintf(payloadStr, "%d", slicesPerFrame);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", slicesPerFrame);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].videoEncoderSlicesPerFrame", payloadStr);
|
||||
|
||||
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "2");
|
||||
}
|
||||
else if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-clientSupportHevc", "1");
|
||||
err |= addAttributeString(&optionHead, "x-nv-vqos[0].bitStreamFormat", "1");
|
||||
|
||||
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)) {
|
||||
if (!APP_VERSION_AT_LEAST(7, 1, 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.
|
||||
@@ -314,23 +446,24 @@ 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) {
|
||||
// HDR is not supported on H.264
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "0");
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
// Enable HDR if requested
|
||||
if (NegotiatedVideoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].dynamicRangeMode", "1");
|
||||
}
|
||||
else {
|
||||
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()) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].maxNumReferenceFrames", "0");
|
||||
}
|
||||
else {
|
||||
@@ -340,13 +473,13 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].maxNumReferenceFrames", "1");
|
||||
}
|
||||
|
||||
sprintf(payloadStr, "%d", StreamConfig.clientRefreshRateX100);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", StreamConfig.clientRefreshRateX100);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].clientRefreshRateX100", payloadStr);
|
||||
}
|
||||
|
||||
sprintf(payloadStr, "%d", audioChannelCount);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", audioChannelCount);
|
||||
err |= addAttributeString(&optionHead, "x-nv-audio.surround.numChannels", payloadStr);
|
||||
sprintf(payloadStr, "%d", audioChannelMask);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", audioChannelMask);
|
||||
err |= addAttributeString(&optionHead, "x-nv-audio.surround.channelMask", payloadStr);
|
||||
if (audioChannelCount > 2) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-audio.surround.enable", "1");
|
||||
@@ -357,8 +490,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
}
|
||||
|
||||
if (AppVersionQuad[0] >= 7) {
|
||||
// Decide to use HQ audio based on the original video bitrate, not the HEVC-adjusted value
|
||||
if (OriginalVideoBitrate >= HIGH_AUDIO_BITRATE_THRESHOLD && audioChannelCount > 2 &&
|
||||
if (StreamConfig.bitrate >= 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");
|
||||
@@ -374,13 +506,10 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
err |= addAttributeString(&optionHead, "x-nv-audio.surround.AudioQuality", "0");
|
||||
HighQualitySurroundEnabled = false;
|
||||
|
||||
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
|
||||
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
|
||||
AudioPacketDuration = 10;
|
||||
}
|
||||
else {
|
||||
@@ -389,7 +518,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(payloadStr, "%d", AudioPacketDuration);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", AudioPacketDuration);
|
||||
err |= addAttributeString(&optionHead, "x-nv-aqos.packetDuration", payloadStr);
|
||||
}
|
||||
else {
|
||||
@@ -401,7 +530,7 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
}
|
||||
|
||||
if (AppVersionQuad[0] >= 7) {
|
||||
sprintf(payloadStr, "%d", (StreamConfig.colorSpace << 1) | StreamConfig.colorRange);
|
||||
snprintf(payloadStr, sizeof(payloadStr), "%d", (StreamConfig.colorSpace << 1) | StreamConfig.colorRange);
|
||||
err |= addAttributeString(&optionHead, "x-nv-video[0].encoderCscMode", payloadStr);
|
||||
}
|
||||
|
||||
@@ -414,8 +543,8 @@ static PSDP_OPTION getAttributesList(char*urlSafeAddr) {
|
||||
}
|
||||
|
||||
// Populate the SDP header with required information
|
||||
static int fillSdpHeader(char* buffer, int rtspClientVersion, char*urlSafeAddr) {
|
||||
return sprintf(buffer,
|
||||
static int fillSdpHeader(char* buffer, size_t length, int rtspClientVersion, char*urlSafeAddr) {
|
||||
return snprintf(buffer, length,
|
||||
"v=0\r\n"
|
||||
"o=android 0 %d IN %s %s\r\n"
|
||||
"s=NVIDIA Streaming Client\r\n",
|
||||
@@ -425,37 +554,67 @@ static int fillSdpHeader(char* buffer, int rtspClientVersion, char*urlSafeAddr)
|
||||
}
|
||||
|
||||
// Populate the SDP tail with required information
|
||||
static int fillSdpTail(char* buffer) {
|
||||
return sprintf(buffer,
|
||||
static int fillSdpTail(char* buffer, size_t length) {
|
||||
LC_ASSERT(VideoPortNumber != 0);
|
||||
return snprintf(buffer, length,
|
||||
"t=0 0\r\n"
|
||||
"m=video %d \r\n",
|
||||
AppVersionQuad[0] < 4 ? 47996 : 47998);
|
||||
AppVersionQuad[0] < 4 ? 47996 : VideoPortNumber);
|
||||
}
|
||||
|
||||
// Get the SDP attributes for the stream config
|
||||
char* getSdpPayloadForStreamConfig(int rtspClientVersion, int* length) {
|
||||
PSDP_OPTION attributeList;
|
||||
int offset;
|
||||
int attributeListSize;
|
||||
int offset, written;
|
||||
char* payload;
|
||||
char urlSafeAddr[URLSAFESTRING_LEN];
|
||||
|
||||
addrToUrlSafeString(&RemoteAddr, urlSafeAddr);
|
||||
addrToUrlSafeString(&RemoteAddr, urlSafeAddr, sizeof(urlSafeAddr));
|
||||
|
||||
attributeList = getAttributesList(urlSafeAddr);
|
||||
if (attributeList == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
payload = malloc(MAX_SDP_HEADER_LEN + MAX_SDP_TAIL_LEN +
|
||||
getSerializedAttributeListSize(attributeList));
|
||||
attributeListSize = getSerializedAttributeListSize(attributeList);
|
||||
payload = malloc(MAX_SDP_HEADER_LEN + MAX_SDP_TAIL_LEN + attributeListSize);
|
||||
if (payload == NULL) {
|
||||
freeAttributeList(attributeList);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
offset = fillSdpHeader(payload, rtspClientVersion, urlSafeAddr);
|
||||
offset += fillSerializedAttributeList(&payload[offset], attributeList);
|
||||
offset += fillSdpTail(&payload[offset]);
|
||||
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;
|
||||
}
|
||||
|
||||
freeAttributeList(attributeList);
|
||||
*length = offset;
|
||||
|
||||
+7
-9
@@ -1,7 +1,5 @@
|
||||
#include "Limelight-internal.h"
|
||||
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#define STUN_RECV_TIMEOUT_SEC 3
|
||||
|
||||
#define STUN_MESSAGE_BINDING_REQUEST 0x0001
|
||||
@@ -67,7 +65,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
sprintf(stunPortStr, "%u", stunPort);
|
||||
snprintf(stunPortStr, sizeof(stunPortStr), "%u", stunPort);
|
||||
err = getaddrinfo(stunServer, stunPortStr, &hints, &stunAddrs);
|
||||
if (err != 0 || stunAddrs == NULL) {
|
||||
Limelog("Failed to resolve STUN server: %d\n", err);
|
||||
@@ -75,7 +73,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
sock = bindUdpSocket(hints.ai_family, 2048);
|
||||
sock = bindUdpSocket(hints.ai_family, NULL, 0, 0, SOCK_QOS_TYPE_BEST_EFFORT);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
err = LastSocketFail();
|
||||
Limelog("Failed to connect to STUN server: %d\n", err);
|
||||
@@ -85,7 +83,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);
|
||||
RAND_bytes(reqMsg.transactionId, sizeof(reqMsg.transactionId));
|
||||
PltGenerateRandomData(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++) {
|
||||
@@ -95,7 +93,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, current->ai_addrlen);
|
||||
err = (int)sendto(sock, (char *)&reqMsg, sizeof(reqMsg), 0, current->ai_addr, (SOCKADDR_LEN)current->ai_addrlen);
|
||||
if (err == SOCKET_ERROR) {
|
||||
err = LastSocketFail();
|
||||
Limelog("Failed to send STUN binding request: %d\n", err);
|
||||
@@ -123,7 +121,7 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
|
||||
Limelog("Failed to read STUN binding response: %d\n", err);
|
||||
goto Exit;
|
||||
}
|
||||
else if (bytesRead < sizeof(resp.hdr)) {
|
||||
else if (bytesRead < (int)sizeof(resp.hdr)) {
|
||||
Limelog("STUN message truncated: %d\n", bytesRead);
|
||||
err = -3;
|
||||
goto Exit;
|
||||
@@ -146,8 +144,8 @@ int LiFindExternalAddressIP4(const char* stunServer, unsigned short stunPort, un
|
||||
|
||||
attribute = (PSTUN_ATTRIBUTE_HEADER)(&resp.hdr + 1);
|
||||
bytesRead -= sizeof(resp.hdr);
|
||||
while (bytesRead > sizeof(*attribute)) {
|
||||
if (bytesRead < sizeof(*attribute) + htons(attribute->length)) {
|
||||
while (bytesRead > (int)sizeof(*attribute)) {
|
||||
if (bytesRead < (int)(sizeof(*attribute) + htons(attribute->length))) {
|
||||
Limelog("STUN attribute out of bounds: %d\n", htons(attribute->length));
|
||||
err = -5;
|
||||
goto Exit;
|
||||
|
||||
+61
-13
@@ -7,21 +7,31 @@ typedef struct _QUEUED_DECODE_UNIT {
|
||||
LINKED_BLOCKING_QUEUE_ENTRY entry;
|
||||
} QUEUED_DECODE_UNIT, *PQUEUED_DECODE_UNIT;
|
||||
|
||||
void completeQueuedDecodeUnit(PQUEUED_DECODE_UNIT qdu, int drStatus);
|
||||
bool getNextQueuedDecodeUnit(PQUEUED_DECODE_UNIT* qdu);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// 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;
|
||||
|
||||
#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 {
|
||||
unsigned int streamPacketIndex;
|
||||
unsigned int frameIndex;
|
||||
char flags;
|
||||
char reserved[3];
|
||||
int fecInfo;
|
||||
uint32_t streamPacketIndex;
|
||||
uint32_t frameIndex;
|
||||
uint8_t flags;
|
||||
uint8_t extraFlags;
|
||||
uint8_t multiFecFlags;
|
||||
uint8_t multiFecBlocks;
|
||||
uint32_t fecInfo;
|
||||
} NV_VIDEO_PACKET, *PNV_VIDEO_PACKET;
|
||||
|
||||
#define FLAG_EXTENSION 0x10
|
||||
@@ -30,11 +40,49 @@ typedef struct _NV_VIDEO_PACKET {
|
||||
#define MAX_RTP_HEADER_SIZE 16
|
||||
|
||||
typedef struct _RTP_PACKET {
|
||||
char header;
|
||||
char packetType;
|
||||
unsigned short sequenceNumber;
|
||||
unsigned int timestamp;
|
||||
unsigned int ssrc;
|
||||
uint8_t header;
|
||||
uint8_t packetType;
|
||||
uint16_t sequenceNumber;
|
||||
uint32_t timestamp;
|
||||
uint32_t 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)
|
||||
|
||||
+721
-227
File diff suppressed because it is too large
Load Diff
+144
-70
@@ -1,21 +1,17 @@
|
||||
#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
|
||||
|
||||
#define RTP_RECV_BUFFER (512 * 1024)
|
||||
|
||||
static RTP_FEC_QUEUE rtpQueue;
|
||||
static RTP_VIDEO_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;
|
||||
@@ -29,11 +25,20 @@ static bool receivedFullFrame;
|
||||
// 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);
|
||||
RtpfInitializeQueue(&rtpQueue); //TODO RTP_QUEUE_DELAY
|
||||
RtpvInitializeQueue(&rtpQueue);
|
||||
decryptionCtx = PltCreateCryptoContext();
|
||||
receivedDataFromPeer = false;
|
||||
firstDataTimeMs = 0;
|
||||
receivedFullFrame = false;
|
||||
@@ -41,25 +46,35 @@ void initializeVideoStream(void) {
|
||||
|
||||
// Clean up the video stream
|
||||
void destroyVideoStream(void) {
|
||||
PltDestroyCryptoContext(decryptionCtx);
|
||||
destroyVideoDepacketizer();
|
||||
RtpfCleanupQueue(&rtpQueue);
|
||||
RtpvCleanupQueue(&rtpQueue);
|
||||
}
|
||||
|
||||
// UDP Ping proc
|
||||
static void UdpPingThreadProc(void* context) {
|
||||
char pingData[] = { 0x50, 0x49, 0x4E, 0x47 };
|
||||
struct sockaddr_in6 saddr;
|
||||
SOCK_RET err;
|
||||
static void VideoPingThreadProc(void* context) {
|
||||
char legacyPingData[] = { 0x50, 0x49, 0x4E, 0x47 };
|
||||
LC_SOCKADDR saddr;
|
||||
|
||||
LC_ASSERT(VideoPortNumber != 0);
|
||||
|
||||
memcpy(&saddr, &RemoteAddr, sizeof(saddr));
|
||||
saddr.sin6_port = htons(RTP_PORT);
|
||||
SET_PORT(&saddr, VideoPortNumber);
|
||||
|
||||
// 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)) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
PltSleepMsInterruptible(&udpPingThread, 500);
|
||||
@@ -67,16 +82,21 @@ static void UdpPingThreadProc(void* context) {
|
||||
}
|
||||
|
||||
// Receive thread proc
|
||||
static void ReceiveThreadProc(void* context) {
|
||||
static void VideoReceiveThreadProc(void* context) {
|
||||
int err;
|
||||
int bufferSize, receiveSize;
|
||||
int bufferSize, receiveSize, decryptedSize, minSize;
|
||||
char* buffer;
|
||||
char* encryptedBuffer;
|
||||
int queueStatus;
|
||||
bool useSelect;
|
||||
int waitingForVideoMs;
|
||||
bool encrypted;
|
||||
|
||||
receiveSize = StreamConfig.packetSize + MAX_RTP_HEADER_SIZE;
|
||||
bufferSize = receiveSize + sizeof(RTPFEC_QUEUE_ENTRY);
|
||||
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);
|
||||
buffer = NULL;
|
||||
|
||||
if (setNonFatalRecvTimeoutMs(rtpSocket, UDP_RECV_POLL_TIMEOUT_MS) < 0) {
|
||||
@@ -88,6 +108,19 @@ static void ReceiveThreadProc(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;
|
||||
@@ -97,11 +130,14 @@ static void ReceiveThreadProc(void* context) {
|
||||
if (buffer == NULL) {
|
||||
Limelog("Video Receive: malloc() failed\n");
|
||||
ListenerCallbacks.connectionTerminated(-1);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err = recvUdpSocket(rtpSocket, buffer, receiveSize, useSelect);
|
||||
err = recvUdpSocket(rtpSocket,
|
||||
encrypted ? encryptedBuffer : buffer,
|
||||
receiveSize,
|
||||
useSelect);
|
||||
if (err < 0) {
|
||||
Limelog("Video Receive: recvUdpSocket() failed: %d\n", (int)LastSocketError());
|
||||
ListenerCallbacks.connectionTerminated(LastSocketFail());
|
||||
@@ -118,7 +154,7 @@ static void ReceiveThreadProc(void* context) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Receive timed out; try again
|
||||
continue;
|
||||
}
|
||||
@@ -130,23 +166,68 @@ static void ReceiveThreadProc(void* context) {
|
||||
firstDataTimeMs = PltGetMillis();
|
||||
}
|
||||
|
||||
#ifndef LC_FUZZING
|
||||
if (!receivedFullFrame) {
|
||||
uint64_t now = PltGetMillis();
|
||||
|
||||
if (now - firstDataTimeMs >= FIRST_FRAME_TIMEOUT_SEC * 1000) {
|
||||
if (PltGetMillis() - 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 = htons(packet->sequenceNumber);
|
||||
packet->timestamp = htonl(packet->timestamp);
|
||||
packet->ssrc = htonl(packet->ssrc);
|
||||
packet->sequenceNumber = BE16(packet->sequenceNumber);
|
||||
packet->timestamp = BE32(packet->timestamp);
|
||||
packet->ssrc = BE32(packet->ssrc);
|
||||
|
||||
queueStatus = RtpfAddPacket(&rtpQueue, packet, err, (PRTPFEC_QUEUE_ENTRY)&buffer[receiveSize]);
|
||||
queueStatus = RtpvAddPacket(&rtpQueue, packet, err, (PRTPV_QUEUE_ENTRY)&buffer[decryptedSize]);
|
||||
|
||||
if (queueStatus == RTPF_RET_QUEUED) {
|
||||
// The queue owns the buffer
|
||||
@@ -157,26 +238,28 @@ static void ReceiveThreadProc(void* context) {
|
||||
if (buffer != NULL) {
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
if (encryptedBuffer != NULL) {
|
||||
free(encryptedBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void submitFrame(PQUEUED_DECODE_UNIT qdu) {
|
||||
// Pass the frame to the decoder
|
||||
int ret = VideoCallbacks.submitDecodeUnit(&qdu->decodeUnit);
|
||||
completeQueuedDecodeUnit(qdu, ret);
|
||||
|
||||
void notifyKeyFrameReceived(void) {
|
||||
// Remember that we got a full frame successfully
|
||||
receivedFullFrame = true;
|
||||
}
|
||||
|
||||
// Decoder thread proc
|
||||
static void DecoderThreadProc(void* context) {
|
||||
PQUEUED_DECODE_UNIT qdu;
|
||||
static void VideoDecoderThreadProc(void* context) {
|
||||
while (!PltIsThreadInterrupted(&decoderThread)) {
|
||||
if (!getNextQueuedDecodeUnit(&qdu)) {
|
||||
VIDEO_FRAME_HANDLE frameHandle;
|
||||
PDECODE_UNIT decodeUnit;
|
||||
|
||||
if (!LiWaitForNextVideoFrame(&frameHandle, &decodeUnit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitFrame(qdu);
|
||||
LiCompleteVideoFrame(frameHandle, VideoCallbacks.submitDecodeUnit(decodeUnit));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,10 +284,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) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
PltInterruptThread(&decoderThread);
|
||||
}
|
||||
|
||||
@@ -214,16 +297,10 @@ void stopVideoStream(void) {
|
||||
|
||||
PltJoinThread(&udpPingThread);
|
||||
PltJoinThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 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;
|
||||
@@ -251,7 +328,9 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
return err;
|
||||
}
|
||||
|
||||
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, RTP_RECV_BUFFER);
|
||||
rtpSocket = bindUdpSocket(RemoteAddr.ss_family, &LocalAddr, AddrLen,
|
||||
RTP_RECV_PACKETS_BUFFERED * (StreamConfig.packetSize + MAX_RTP_HEADER_SIZE),
|
||||
SOCK_QOS_TYPE_VIDEO);
|
||||
if (rtpSocket == INVALID_SOCKET) {
|
||||
VideoCallbacks.cleanup();
|
||||
return LastSocketError();
|
||||
@@ -259,7 +338,7 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
|
||||
VideoCallbacks.start();
|
||||
|
||||
err = PltCreateThread("VideoRecv", ReceiveThreadProc, NULL, &receiveThread);
|
||||
err = PltCreateThread("VideoRecv", VideoReceiveThreadProc, NULL, &receiveThread);
|
||||
if (err != 0) {
|
||||
VideoCallbacks.stop();
|
||||
closeSocket(rtpSocket);
|
||||
@@ -267,13 +346,12 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
err = PltCreateThread("VideoDec", DecoderThreadProc, NULL, &decoderThread);
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
err = PltCreateThread("VideoDec", VideoDecoderThreadProc, NULL, &decoderThread);
|
||||
if (err != 0) {
|
||||
VideoCallbacks.stop();
|
||||
PltInterruptThread(&receiveThread);
|
||||
PltJoinThread(&receiveThread);
|
||||
PltCloseThread(&receiveThread);
|
||||
closeSocket(rtpSocket);
|
||||
VideoCallbacks.cleanup();
|
||||
return err;
|
||||
@@ -282,23 +360,19 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
|
||||
if (AppVersionQuad[0] == 3) {
|
||||
// Connect this socket to open port 47998 for our ping thread
|
||||
firstFrameSocket = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
|
||||
firstFrameSocket = connectTcpSocket(&RemoteAddr, AddrLen,
|
||||
FIRST_FRAME_PORT, FIRST_FRAME_TIMEOUT_SEC);
|
||||
if (firstFrameSocket == INVALID_SOCKET) {
|
||||
VideoCallbacks.stop();
|
||||
stopVideoDepacketizer();
|
||||
PltInterruptThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
PltInterruptThread(&decoderThread);
|
||||
}
|
||||
PltJoinThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
PltJoinThread(&decoderThread);
|
||||
}
|
||||
PltCloseThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
PltCloseThread(&decoderThread);
|
||||
}
|
||||
closeSocket(rtpSocket);
|
||||
VideoCallbacks.cleanup();
|
||||
return LastSocketError();
|
||||
@@ -307,22 +381,18 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
|
||||
// Start pinging before reading the first frame so GFE knows where
|
||||
// to send UDP data
|
||||
err = PltCreateThread("VideoPing", UdpPingThreadProc, NULL, &udpPingThread);
|
||||
err = PltCreateThread("VideoPing", VideoPingThreadProc, NULL, &udpPingThread);
|
||||
if (err != 0) {
|
||||
VideoCallbacks.stop();
|
||||
stopVideoDepacketizer();
|
||||
PltInterruptThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
PltInterruptThread(&decoderThread);
|
||||
}
|
||||
PltJoinThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
if ((VideoCallbacks.capabilities & (CAPABILITY_DIRECT_SUBMIT | CAPABILITY_PULL_RENDERER)) == 0) {
|
||||
PltJoinThread(&decoderThread);
|
||||
}
|
||||
PltCloseThread(&receiveThread);
|
||||
if ((VideoCallbacks.capabilities & CAPABILITY_DIRECT_SUBMIT) == 0) {
|
||||
PltCloseThread(&decoderThread);
|
||||
}
|
||||
closeSocket(rtpSocket);
|
||||
if (firstFrameSocket != INVALID_SOCKET) {
|
||||
closeSocket(firstFrameSocket);
|
||||
@@ -343,3 +413,7 @@ int startVideoStream(void* rendererContext, int drFlags) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const RTP_VIDEO_STATS* LiGetRTPVideoStats(void) {
|
||||
return &rtpQueue.stats;
|
||||
}
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @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);
|
||||
Reference in New Issue
Block a user