456 Commits

Author SHA1 Message Date
Cameron Gutman 2600beaf13 Add support for controllers with dual touchpads
CI / Build (-A ARM64, windows-11-arm) (push) Cancelled after 0s
CI / Build (-A x64, windows-latest) (push) Cancelled after 0s
CI / Build (gcc, g++, ubuntu-latest) (push) Cancelled after 0s
CI / Build (clang, -DUSE_MBEDTLS=ON, clang++, ubuntu-latest, sudo apt install -y libmbedtls-dev) (push) Cancelled after 0s
CI / Build (clang, clang++, ubuntu-24.04-arm) (push) Cancelled after 0s
CI / Build (clang, clang++, ubuntu-latest) (push) Cancelled after 0s
CI / Build (gcc, -DUSE_MBEDTLS=ON, g++, ubuntu-latest, sudo apt install -y libmbedtls-dev) (push) Cancelled after 0s
CI / Build (gcc, g++, ubuntu-24.04-arm) (push) Cancelled after 0s
CI / Build (macos, -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl, macos-latest) (push) Cancelled after 0s
CodeQL / Analyze (cpp) (push) Cancelled after 0s
2026-05-14 22:13:45 -05:00
foxirain 7b026e77be Harden RTSP handling for malformed Session headers and oversized responses 2026-03-30 08:32:12 -05:00
Cameron Gutman 7022b337a9 Refactor BSD socket errors for Win32 and add EMSGSIZE 2026-03-28 15:03:38 -05:00
dependabot[bot] 62687809b1 Bump enet from 78cc9b4 to c7353c0
Bumps [enet](https://github.com/cgutman/enet) from `78cc9b4` to `c7353c0`.
- [Commits](https://github.com/cgutman/enet/compare/78cc9b4b03bdd4f95c277391cac6e0951407072b...c7353c059373f8d3fc83d451f8f1a477be3dc94e)

---
updated-dependencies:
- dependency-name: enet
  dependency-version: c7353c059373f8d3fc83d451f8f1a477be3dc94e
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 19:56:44 -06:00
MrCreativ3001 3fa9191330 Use lowercase windows headers for easier cross-compilation 2026-02-20 19:33:13 -06:00
Cameron Gutman b187204769 Restore fallback scalar path for x86 2026-02-19 20:04:31 -06:00
Cameron Gutman 3872285d8c Fix incorrectly enabling SSSE3 on all x64 Windows platforms 2026-02-19 20:03:20 -06:00
Cameron Gutman a063522db4 Use the SIMDe 128-bit path for all non-x86 systems
SIMDe includes support for AltiVec, LSX, NEON, and more. It also includes fallbacks that are optimized for compiler autovectorization for other architectures (like RISC-V RVV)

Since there's no runtime dispatching for these, they will be selected depending on which instructions are enabled by the toolchain at compile-time.
2026-02-19 19:29:11 -06:00
Cameron Gutman 5551d29ba2 Use SIMDe for NEON acceleration 2026-02-19 00:14:42 -06:00
Andy Grundman de364b6ecd Use nanors for optimized Reed-Solomon FEC decoding (#125) 2026-02-18 23:36:52 -06:00
Cameron Gutman 1d0e91d91a Add ARM64 targets to CI 2026-02-15 21:45:58 -06:00
Cameron Gutman b6b95b68b7 Enable CI for all branches 2026-02-15 21:40:01 -06:00
Cameron Gutman 6250fa29ee Update ENet for FreeBSD compatibility 2026-02-06 23:24:42 -06:00
Cameron Gutman 07c32c80f9 Use a valid port when calling connect() for local address detection
BSDs (including Darwin) don't like using port 0.
2026-02-06 22:53:54 -06:00
7mile 305993b013 fix: use UDP connect to probe local address (#121) 2026-01-29 18:52:06 -06:00
ns6089 2a5a1f3e8a Add support for LTR ACK control messages (#122)
* Add support for LTR ACK control messages
2026-01-20 21:47:38 -06:00
Cameron Gutman 435bc6a5a4 Fix pthread_attr leak 2026-01-11 01:46:30 -06:00
Cameron Gutman 3a377e7d7b Only set an explicit pthread stack size on Vita 2026-01-05 00:23:31 -06:00
Cameron Gutman 0586f3d65f Fix thread context leak on non-Vita platforms 2026-01-05 00:21:35 -06:00
Cameron Gutman b126e481a1 Improve locking for batched mouse and gamepad sensor events
By unlocking the mutex before we enqueue the new entry, we can avoid the input thread immediately contending on the mutex after the new item wakes it up.
2025-11-25 17:30:01 -06:00
Cameron Gutman 20c05eda6a Don't lock the ENet mutex when querying for RTT information 2025-11-25 17:26:01 -06:00
Cameron Gutman 2d984f4b0d Update ENet for improved FreeBSD support 2025-11-09 20:36:42 -06:00
Cameron Gutman e59a5f5b53 Rewrite gamepad input batching to batch on the enqueue-side
- Allows successful batching of interleaved input from multiple controllers
- Avoids enqueue/dequeue overhead and memory allocation when batching
2025-11-09 15:47:12 -06:00
AorsiniYT 1c86405fd0 Fix: Add inclusion of <psp2/kernel/processmgr.h> for Vita compatibility 2025-11-07 00:37:14 -06:00
Andy Grundman e356b2cfde Change presentationTimeMs to presentationTimeUs: retain more resolution when dealing with RTP video timestamps from Sunshine. Include raw rtpTimestamp value for use with integer time APIs. 2025-11-06 20:27:01 -06:00
Andy Grundman fdd026518c macOS/iOS: use clock_gettime_nsec_np(CLOCK_UPTIME_RAW) instead of mach_absolute_time() per Apple's recommendation.
Move has_monotonic_time variable into the final variant of platform-specific time code (used by Linux/Unix).
2025-11-06 20:27:01 -06:00
Andy Grundman a3ebaaf015 Use uint64_t for DECODE_UNIT presentationTimeMs
This still holds a millisecond value sourced from RTP timestamp, but since we're changing the API anyway, now is a good time to future-proof this field.
2025-11-06 20:27:01 -06:00
Andy Grundman 82ee2d6590 Improve support for high-resolution stats
* This patch adds a new microsecond-resolution function call, LiGetMicroseconds(), to complement
the existing LiGetMillis(). Many variables used by stats have been updated to work at this
higher resolution and now provide better results when displaying e.g. sub-millisecond frametime stats.
To try and avoid confusion, variables that now contain microseconds have been renamed with a suffix
of 'Us', and those ending in 'Ms' contain milliseconds. I originally experimented with nanoseconds but it
felt like overkill for our needs.

Public API in Limelight.h:
uint64_t LiGetMicroseconds(void);
uint64_t LiGetMillis(void);
const RTP_AUDIO_STATS* LiGetRTPAudioStats(void);  // provides access to RTP data for the overlay stats
const RTP_VIDEO_STATS* LiGetRTPVideoStats(void);

Note: Users of this library may need to make changes. If using LiGetMillis() to track the duration of
something that is shown to the user, consider switching to LiGetMicroseconds(). Remember to divide by
1000 at time of display to show in milliseconds.
2025-11-06 20:27:01 -06:00
Cameron Gutman 5f2280183c Fix CGN subnet mask 2025-07-15 09:59:57 -05:00
Cameron Gutman 0975a8684f Implement workaround for bad synthesized IPv6 addresses when connecting through a v4-only VPN on iOS 2025-07-14 22:27:54 -05:00
ns6089 ab6e21ff5b Specify cmake max version to support cmake-4.0 (#112) 2025-07-14 20:49:47 -05:00
camelcx c86e0537d1 Fix some edge case on small MTU devices using qsv codec (#107)
Add fall back logic to prevent edge case where SPS SPP is not included in the same packet and key IDR frame is mislabeled as P frame causing assertion error at validation.
2025-07-05 19:13:06 -05:00
armin-25689 29f298ec88 build: Add __FreeBSD__ compilation condition for pthread_setname_np() 2025-07-05 18:58:07 -05:00
dependabot[bot] 1176ca6409 Bump enet from 44c85e1 to 115a10b
Bumps [enet](https://github.com/cgutman/enet) from `44c85e1` to `115a10b`.
- [Commits](https://github.com/cgutman/enet/compare/44c85e16279553d9c052e572bcbfcd745fb74abf...115a10baa1d7f291ff5b870765610fd3b4a6e43c)

---
updated-dependencies:
- dependency-name: enet
  dependency-version: 115a10baa1d7f291ff5b870765610fd3b4a6e43c
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 18:57:52 -05:00
AorsiniYT 34074c9ec0 Fixes for Moonlight compatibility on the PS Vita. (#110)
* Use pthread for vita thread managment

* Add thread name to pthread and remove ipv6 from vita

* Fix inclusion of pthread.h so that it is only included on specific platforms (__vita__)

* Fix memory release in ThreadProc for psvita platform

---------

Co-authored-by: MetalfaceScout <logan.mathis@utahtech.edu>
Co-authored-by: Logan Mathis <33127568+MetalfaceScout@users.noreply.github.com>
2025-07-05 18:57:33 -05:00
Cameron Gutman 262537959f Create dependabot.yml 2025-07-05 18:53:47 -05:00
Cameron Gutman 58902e342f Zero output parameters when BbGet*() fails 2025-06-08 15:49:52 -05:00
Cameron Gutman 84f37631c2 Add ByteBuffer APIs for reading/writing multiple bytes 2025-06-08 15:46:47 -05:00
ReenigneArcher 22a190bdd5 ci(code-ql): use no build mode
CodeQL now supports no build mode for C++ analysis. 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/
2025-06-08 13:41:53 -05:00
ReenigneArcher f2f85efa33 ci(workflows): move to github workflows 2025-03-26 23:05:26 -05:00
ABeltramo e95feaf495 Protocol extension: DualSense adaptive trigger support (#102) 2025-03-25 18:16:11 -05:00
Cameron Gutman d3d3e6cf01 Port improved ping interval logic into the control receive loop 2024-10-20 17:34:35 -05:00
Cameron Gutman 12e603eceb Further improve ENet ping interval wait logic 2024-10-20 17:15:04 -05:00
Cameron Gutman 04a2f1131c Update ENet with improved wakeup logic for enet_host_service() 2024-10-20 16:36:12 -05:00
Cameron Gutman dff1690fe1 Validate channel count before parsing Opus param string 2024-10-15 23:00:16 -05:00
ns6089 0fa805d973 Guard against rtsp response with no content 2024-10-15 22:48:59 -05:00
Cameron Gutman 8599b6042a Use SO_NONBLOCK on Haiku 2024-06-10 23:45:51 -05:00
Cameron Gutman e49637d153 Bump enet submodule for Haiku support 2024-06-10 23:45:10 -05:00
ns6089 907110c4ec Support YUV 4:4:4 formats 2024-06-06 02:31:32 -05:00
Cameron Gutman c245fe599d Use higher packet size for remote streaming over end-to-end IPv6 connection
The IPv6 spec guarantees a minimum of 1280 byte MTUs
2024-05-29 13:57:50 -04:00
Cameron Gutman 5e75d4e1f1 Fix -Wsign-compare warning 2024-05-25 18:31:43 -05:00
Cameron Gutman a3e57feadd Update CI images 2024-05-25 18:31:09 -05:00
Cameron Gutman eb21561541 Don't depend on outputDataLength to be initialized with mbedTLS
OpenSSL has no such requirement, so MbedTLS breakages snuck in.
2024-03-05 21:09:57 -06:00
Cameron Gutman 48d7f1ace1 Fix incorrect function definition 2024-02-25 15:52:04 -06:00
Cameron Gutman ba1fc33672 Fix spurious asserts in the unencrypted audio path with Sunshine 2024-02-20 18:03:50 -06:00
Cameron Gutman 046c231b33 Update ENet submodule to use Wii U userbuffers 2024-02-18 13:41:31 -06:00
GaryOderNichts 3d99869c0c Implement support for userbuffers on Wii U
The Wii U socket implementation has limited memory available. To support larger receive buffers, application memory needs to be provided.
To make sockets use this user memory the `SO_RUSRBUF` option needs to be set on the socket.
2024-02-18 13:39:17 -06:00
Cameron Gutman 8af4562af6 Assert that active objects exist in deleters 2024-02-17 18:20:24 -06:00
Cameron Gutman 59f7f62b62 Fix thread detachment on Vita 2024-02-17 18:14:49 -06:00
Cameron Gutman 9686f6942f Consolidate PltCloseThread() and PltJoinThread() 2024-02-17 18:14:03 -06:00
GaryOderNichts 9545dd7603 Implement detached threads (#88)
The `AsyncTerm` thread is never joined but its handle is immediately closed. This works fine on Windows, but causes issues on other platforms where threads need to be explicitly detached if never joined (pthread, wiiu).

This adds a new `PltDetachThread` function, which can be used instead of `PltCloseThread` to explictly detach a thread, requiring it to no longer be closed.
2024-02-17 17:54:32 -06:00
Cameron Gutman ec171fd7ca Don't use VersionHelpers.h on UWP 2024-02-03 17:20:13 -06:00
Cameron Gutman cbd0ec1b25 Fix signed/unsigned mismatch warnings 2024-02-03 15:20:56 -06:00
Cameron Gutman 92abf6e11d Update ENet with ECN/L4S support for Windows too 2024-02-03 15:19:13 -06:00
Cameron Gutman dc71a16bae Update ENet with ECN/L4S support 2024-02-03 15:14:47 -06:00
Cameron Gutman 7ab34e709a Improve encrypted RTSP message validation 2024-02-03 14:39:12 -06:00
Cameron Gutman 955f13a18d Implement RTSP encryption support 2024-02-02 22:55:53 -06:00
zoeyjodon 35f730fedd Improve 3DS Reliability (#87)
- Simplifies thread priority setup
- Sets a max socket buffer size of 0x20000 for the 3DS
- Fixes a bug with polling timeouts taking longer than intended
- Removes 3DS global socket definition
2024-02-02 18:33:18 -06:00
Cameron Gutman 0f3fa30f62 Update ENet with 3DS fixes and iOS/macOS QoS tagging fix 2024-02-01 18:30:22 -06:00
Cameron Gutman 3acba578b1 Update ENet with QoS tagging fixes 2024-02-01 01:20:19 -06:00
Cameron Gutman c0e3dc64a4 Add basic QoS attributes to our A/V sockets 2024-02-01 00:15:47 -06:00
Cameron Gutman 68153174bc Update ENet to improve connection robustness during transient network interruptions 2024-01-24 23:26:41 -06:00
Cameron Gutman fb2b103e51 Fix propagation of errors from enet_host_service() in sendMessageEnet() 2024-01-24 22:41:03 -06:00
Cameron Gutman e8113f0e66 Use the spare field in the encrypted video header as the frame number
This allows us to skip decrypting extra FEC shards after a frame was reassembled.
2024-01-24 18:36:37 -06:00
Cameron Gutman a517f7cbca Fix -Wsign-compare warnings in debug build 2024-01-18 21:35:20 -06:00
zoey jodon c104a97fa0 Add platform support for the 3DS
Prevent wildcard port binding on the 3DS
Add 3DS threading logic
Add 3DS socket logic
Bump the connection timeout to 60s for the 3DS
2024-01-18 18:30:20 -06:00
Cameron Gutman 2597b5e779 Use a 12-byte AES-GCM IV for the new encryption features 2024-01-18 18:29:47 -06:00
Elia Zammuto 7f99bebc72 UWP Changes (#85) 2024-01-15 16:36:28 -06:00
Cameron Gutman 298f356acb Adjust the requested packet size to account for the encryption header 2024-01-14 18:42:41 -06:00
Cameron Gutman 06f18be4bf Allow negotiation of audio encryption using new encryption flags 2024-01-14 14:10:45 -06:00
Cameron Gutman b74b6e883c Add video encryption support 2024-01-14 14:04:52 -06:00
Cameron Gutman 6083a75d1b Ensure no IV collisions 2024-01-14 13:18:22 -06:00
Cameron Gutman 3430ee2c3a Add control stream encryption v2 support 2024-01-14 12:58:48 -06:00
Cameron Gutman b6bbb4fb26 Add new RTSP encryption flags 2024-01-14 12:08:37 -06:00
Cameron Gutman 15b55a441b Add LiGetExtraLaunchArgs()
The string returned from this function should be appended to the end of the /resume and /launch query parameters.
2024-01-14 11:48:59 -06:00
Cameron Gutman 723cac034b Discard unencrypted packets on encrypted control stream 2024-01-14 11:41:01 -06:00
Cameron Gutman 6e9ed871bc Add support for X-SS-Connect-Data in RTSP SETUP for control stream
This allows the host to provide a 32-bit integer that will be sent
in the data of the ENet connect event, similar to X-SS-Ping-Payload
for video and audio.

The host can use this data to uniquely identify a client when IP
addresses are not stable across the various separate connections,
such as when the client is behind a Carrier-Grade NAT.
2023-12-28 16:56:06 -06:00
Cameron Gutman 50d8dcb072 Update ENet submodule with Apple dual stack socket fix 2023-12-28 16:10:55 -06:00
Cameron Gutman 3aae4cdc59 Improve ENet socket error propagation for better debuggability 2023-12-22 13:45:50 -06:00
Cameron Gutman 3ed3ba6253 Remove separate codec, HDR, and remote bitrate adjustments
We currently scale bitrate based on both remote vs local, SDR vs HDR, and H.264 vs
HEVC vs AV1. This has led to a lot of user confusion wondering why the bitrate
doesn't seem to match their selection in some configurations.

In H.264 local streams, we will currently overshoot the selected bitrate by about
20% due to FEC, while remote streams will be right around the selected bitrate due
to remote-specific FEC bitrate adjustments.

HEVC and AV1 streams (as configured by most clients) basically behave similarly
between local and remote, since the codec bitrate adjustment factor of 75% is nearly
the same as the FEC bitrate adjustment factor of 80%. However, this adjustment was
only performed for SDR streams so local HDR streams would overshoot like H.264.

This change cleans up all this mess by using a single non-codec-specific video
bitrate adjustment for FEC in all cases. It also allows Sunshine to perform the FEC
adjustment on its end if the default FEC value of 20% has been overridden by the
user or if we implement dynamic FEC support in the future.

The net result is HEVC and AV1 SDR streams will only see a tiny bitrate increase,
but HDR and H.264 may see noticable 20% bitrate reductions that may require the
user to adjust their bitrate setting to reach the effective value they got before.
However, the new behavior should be more intuitive for users going forward since
changing codecs, using a VPN, or enabling HDR won't cause significant changes to the
video bitrate.
2023-12-03 20:09:34 -06:00
Cameron Gutman 5de4a5b85a Bind the control stream socket to the local address used for other UDP traffic 2023-10-26 01:27:31 -05:00
Cameron Gutman bbf15af837 Revert "Use connected datagram sockets for audio and video"
This causes problems on multi-homed GFE and Sunshine (prior to v0.21) hosts
because audio and video data can be sent back from an address different than
the one we used as our original destination address.

This reverts commit c13f4a323f.
2023-10-26 01:15:27 -05:00
Cameron Gutman 05c3f9c754 Bind our UDP sockets to the same local address used by RTSP handshake
This means we can ensure a consistent local address for our outgoing PING
traffic to keep the UDP flows alive without having to call connect() which breaks
with multi-homed hosts on GFE and Sunshine v0.20 and earlier.
2023-10-26 01:09:26 -05:00
Cameron Gutman c86f49ee7f Remove batch delay for gamepad motion events
The batch delay will basically always trigger for motion reports that contain both accel and gyro
data (like PS4/PS5 controllers) because we will report accel and gyro data within 1 ms every time.
This gets even worse when multiple controllers are connected and reporting motion data.

There shouldn't be any real need to batch these because they're unreliable sequenced packets,
so we can just remove the delay.
2023-10-16 22:08:02 -05:00
Cameron Gutman c9e5660b8c Track the last sent controller packet by controller index
This avoids stacking batch delay when multiple controller are connected
2023-10-16 22:02:55 -05:00
Cameron Gutman 615b5e2bba Rewrite input batching for mouse and gamepad sensors
The old method was too inflexible (depending on consecutive events to batch) that it couldn't
really handle stressful cases like high polling rate mice combined with multiple gamepads
reporting motion sensor events.
2023-10-16 21:43:05 -05:00
Cameron Gutman 8d30079033 Prevent flooding the queue with motion events 2023-10-16 18:05:42 -05:00
Cameron Gutman 47fa51034a Revert change to send mouse position and gamepad events as unreliable
This can lead to poor behavior on lossy networks
2023-10-16 18:02:57 -05:00
Cameron Gutman f78f2135fa Don't strictly enforce picture data following the first PPS in debug mode 2023-10-13 23:23:27 -05:00
Cameron Gutman 5e844aad08 Fix UBSan warning for signed integer overflow 2023-10-11 20:09:48 -05:00
Cameron Gutman 620b4be477 Add separate LC_ASSERT_VT() for asserts that only apply for valid traffic 2023-10-11 20:01:39 -05:00
Cameron Gutman 574ad6e676 Reject video packets beyond our valid sequence number range for the FEC block 2023-10-11 17:32:39 -05:00
Cameron Gutman 836bc6611f Batch split UTF-8 packets at the ENet layer 2023-10-11 01:18:12 -05:00
Cameron Gutman 8d73a9f8b7 Allow the UTF-8 splitting loop to be interrupted 2023-10-11 01:10:34 -05:00
Cameron Gutman d828868bc8 Fix uninitialized channel and packet flags in split UTF-8 packets 2023-10-11 00:56:54 -05:00
Cameron Gutman da7967632a Add a workaround for clients still using a short to represent button flags 2023-10-10 18:07:32 -05:00
Cameron Gutman 426745fa24 Extend to 32-bit button flags for the older LiSendControllerEvent() API 2023-10-10 18:07:08 -05:00
Cameron Gutman d457fbb487 Fix warning for fopen() in RecorderCallbacks.c 2023-10-06 23:17:25 -05:00
Cameron Gutman 7580f3f8e3 Remove _CRT_SECURE_NO_WARNINGS now that we use safe string functions everywhere 2023-10-06 22:42:25 -05:00
Cameron Gutman 1351f382aa Minor cleanups to RTSP URL parsing 2023-10-06 21:30:45 -05:00
Keane O'Kelley 24750d4b74 Fix buffer overflow in performRtspHandshake (CVE-2023-42800) 2023-10-06 21:14:30 -05:00
Keane O'Kelley 02b7742f4d Fix buffer overflow in parseUrlAddrFromRtspUrlString (CVE-2023-42799) 2023-10-06 21:14:30 -05:00
Cameron Gutman 116267a245 Fix parseSdpAttributeTo[U]Int() to avoid writing to the const input string 2023-10-06 18:12:08 -05:00
Cameron Gutman b2497a3918 Rewrite extractVersionQuadFromString() to avoid copying the input string 2023-10-06 18:09:15 -05:00
Cameron Gutman 8b84d17c8d Replace additional unsafe string functions 2023-10-06 17:33:37 -05:00
Cameron Gutman d055599608 Map Misc button to Guide for GFE hosts 2023-10-05 20:29:18 -05:00
Cameron Gutman 162c581754 Initialize strtokCtx to NULL for first call to strtok_r()
Apparently some strtok_r() implementations require this.
2023-09-27 01:27:12 -05:00
Cameron Gutman 1be56269a3 Fix handling of duplicate packets when OOS data was also received
It's possible for a sequence of packets seqnums like 2, 0, 1, 2 to end up hitting the fast path
again after filling in gap of OOS packets. The fast path assumes duplicate packets are caught
by the next contiguous sequence number check, but this is only true if that value is kept updated.

Since the slow path doesn't update the next contiguous sequence number, it's no longer safe to
use the fast path for the remaining packets in the frame because duplicate packets would be
mishandled.

Queuing duplicate packets violates all sorts of queue assumptions and can cause us to return
corrupt data to the decoder or attempt an FEC recovery without enough shards.
2023-09-23 12:08:16 -04:00
Cameron Gutman 7a6d12fc4e Update ENet submodule to add ENET_NO_INSTALL option 2023-09-20 20:52:28 -05:00
Cameron Gutman c1744de069 Batch async control stream callbacks 2023-09-18 23:02:44 -05:00
Cameron Gutman a258b7e12b Fix leak in unreachable codepath 2023-09-18 22:59:03 -05:00
Cameron Gutman c3e9aea843 Fix non-standard forward declaration 2023-09-18 21:19:59 -05:00
Cameron Gutman 20130e210b Invoke control stream callbacks on a separate thread
Time spent processing client callbacks would contribute to increased control stream latency,
which means higher RTTs, more lengthy waits before retransmissions, and higher client-reported
network latency stats.
2023-09-17 14:49:06 -05:00
Cameron Gutman b2528faa02 Disable GCC static analysis in CI until newer GCC is available in AppVeyor 2023-09-15 23:19:10 -05:00
Cameron Gutman 7f665babf9 Enable GCC static analysis in CI 2023-09-15 22:41:37 -05:00
Cameron Gutman 515bea6fb4 Fix GCC static analysis warnings 2023-09-15 22:03:27 -05:00
Cameron Gutman c13f4a323f Use connected datagram sockets for audio and video
This ensures unwanted data from other sources is dropped and that our own source address doesn't change while streaming.
2023-09-12 23:29:05 -05:00
Cameron Gutman ec6713fd80 Improve checks for runt video packets
Since we always allocate fixed size these aren't likely to be exploitable,
but we ought to fix them anyway. Worst case, we will just read some garbage
and generate corrupt video output.
2023-09-12 22:44:45 -05:00
Cameron Gutman 171858c2e7 Add CodeQL scanning 2023-09-12 21:46:46 -05:00
Cameron Gutman 315d0bbfa3 Use modern string parsing functions in SdpGenerator 2023-09-12 21:24:28 -05:00
Cameron Gutman 5a19e52921 Use modern string parsing functions in RtspParser 2023-09-12 21:20:44 -05:00
Cameron Gutman 91b4186b8a Use strtok_r()/strtok_s() instead of regular strtok() 2023-09-12 20:56:15 -05:00
Cameron Gutman 2bb026c763 Suppress connection warnings for the first sampling period 2023-08-23 21:03:40 -05:00
Cameron Gutman dc62a6f88e Send periodic pings and FEC status messages more frequently 2023-08-19 11:39:55 -05:00
Cameron Gutman e05ee6dfc8 Change FEC status message to avoid truncating large frames 2023-08-19 11:37:44 -05:00
Cameron Gutman 78e06eb613 Batch control stream messages 2023-08-19 11:27:16 -05:00
Cameron Gutman 8b0dbf7158 Send client feature flags indicating FEC status support 2023-08-19 11:03:00 -05:00
Cameron Gutman e0d83f61c2 Fix missing debug message when an I-frame is sent as an RFI recovery frame for AV1 2023-08-14 21:32:38 -05:00
Cameron Gutman 8bca948b61 Fix unused function warning on release builds 2023-08-12 15:08:22 -05:00
Cameron Gutman a0b29fe3dc Avoid asserting on filler NALUs in reference frames on debug builds 2023-08-12 05:32:11 -05:00
Cameron Gutman 325518b47a Add LiGetHostFeatureFlags() API 2023-08-03 22:09:30 -05:00
Cameron Gutman 0f17b4d0c5 Add LI_TOUCH_EVENT_CANCEL_ALL 2023-07-30 19:20:15 -05:00
Cameron Gutman f2cea4d6b0 Update ENet submodule 2023-07-30 14:02:28 -05:00
Cameron Gutman 6d039a646b Fix extended delays for unreliable/unsequenced traffic on high latency connections 2023-07-25 23:30:45 -05:00
Cameron Gutman 92cc6f96d4 Merge latest changes from upstream ENet 2023-07-25 22:03:42 -05:00
Cameron Gutman 77c5d5c282 Tune SO_RCVBUF logic
- Calculate desired SO_RCVBUF by packet size for the video socket
- Use the OS default SO_RCVBUF for audio and STUN sockets
2023-07-25 18:30:19 -05:00
Cameron Gutman 70a2e305bc Extend pen/touch APIs with contact area and orientation 2023-07-22 15:01:03 -05:00
Cameron Gutman 6f4f2607b3 Clarify API documentation 2023-07-22 12:34:02 -05:00
Cameron Gutman d8b2b04bb2 Reuse pressure field in touch/pen events to provide hover distance 2023-07-22 12:33:19 -05:00
Cameron Gutman 27428e655b Move ServerCodecModeSupport values into public header 2023-07-16 15:30:55 -05:00
Cameron Gutman 9b5fbff7ba Only disable 4K H.264 RFI with GFE 2023-07-16 15:13:22 -05:00
Cameron Gutman 2ac25bebaa Send unreliable traffic as reliable when we have just one channel 2023-07-16 15:11:09 -05:00
Cameron Gutman 2d0badde9a Send zero state gyro events as reliable traffic 2023-07-12 01:05:23 -05:00
Cameron Gutman dc803bcd33 Document the correct way to indicate arrival and removal of controllers 2023-07-09 15:24:56 -05:00
Cameron Gutman a3b28eb4d7 Update ENet for UWP builds 2023-07-08 19:15:45 -05:00
Cameron Gutman 49fef03830 Change frameHeaderSize to uint32_t to avoid warning from promotion to signed int 2023-07-08 01:40:44 -05:00
Cameron Gutman 9205a87002 Improve validation of lastPacketPayloadLength field 2023-07-08 01:31:41 -05:00
Mariotaku 659202c3e5 fixed memcpy overlap (reported by AddressSanitizer) (#80)
* removed unneeded memcpy
2023-07-08 01:09:45 -05:00
Cameron Gutman ed7d72c07d Fix single packet frame case for AV1 2023-07-06 01:41:15 -05:00
Cameron Gutman 190b08ecf4 Encode the final packet length in the frame header for AV1
Some decoders (Android) are very strict about trailing padding bytes.
2023-07-06 01:29:24 -05:00
Cameron Gutman 953971c9a3 Fix handling of AV1 keyframes 2023-07-05 18:45:29 -05:00
Cameron Gutman f3b7edbd11 Overflow onto channel 0 rather than distributing
This allows the host to make some assumptions about channel IDs
2023-07-04 16:51:35 -05:00
Cameron Gutman 377d37503f Split controller motion events onto their own channels 2023-07-04 16:18:52 -05:00
Cameron Gutman 53c29f11ba Send mouse deltas reliably for now 2023-07-04 15:48:08 -05:00
Evgeniy S befc9805ab Add workaround for missing last byte in RTSP message
Sometimes on android devices last byte is missing.
This is link to this bug in Goggle Issue Tracker:
https://issuetracker.google.com/issues/150758736?pli=1
2023-07-04 15:04:19 -05:00
Cameron Gutman c8828d586c Don't send periodic pings as reliable traffic 2023-07-04 15:03:01 -05:00
Cameron Gutman 7608e8e69d Enforce setting serverCodecModeSupport field in SERVER_INFORMATION 2023-07-04 14:56:20 -05:00
Cameron Gutman 0095141e08 Split control data into multiple channels and optimize packet flags based on type of data
This can significantly performance on lossy networks by avoiding HOL blocking.
2023-07-04 14:49:21 -05:00
Cameron Gutman 7d59c6e14e Fix motionType value in motion packets 2023-07-04 14:18:27 -05:00
Cameron Gutman 54e1556c40 Constrain the reported controller number by the max gamepad count 2023-07-04 14:16:53 -05:00
Cameron Gutman 9ad56cdd8e Move serverCodecModeSupport into SERVER_INFORMATION struct 2023-07-02 23:54:04 -05:00
Cameron Gutman effba1a16f Remove mention of enableHdr 2023-07-02 23:40:42 -05:00
Cameron Gutman 59a506c15a Only select 10-bit format if also compatible with client 2023-07-02 23:32:04 -05:00
Cameron Gutman 1125dc3dba Replace enableHdr option with directly receiving ServerCodecModeSupport value from XML 2023-07-02 23:28:10 -05:00
Cameron Gutman e36bde4acc Implement AV1 codec support
This has a breaking change to StreamConfiguration that requires client updates.
2023-07-02 22:16:20 -05:00
Cameron Gutman c0792168f5 Add LI_BATTERY_PERCENTAGE_UNKNOWN value 2023-07-02 16:42:09 -05:00
Cameron Gutman a0f8c060c0 Introduce new protocol extensions for controller RGB LEDs and battery state 2023-07-02 14:38:54 -05:00
Cameron Gutman c5dc45e144 Define an unknown tool type and clarify button-only events 2023-06-28 17:25:41 -05:00
Cameron Gutman 28d63b11dd Fix float values in pen events 2023-06-25 20:06:43 -05:00
Cameron Gutman f8899f724d Add a touch event type for leaving hover 2023-06-25 16:58:12 -05:00
Cameron Gutman 8c447137d6 Fix controller arrival packet to actually contain the controller that arrived 2023-06-25 14:27:43 -05:00
Cameron Gutman 44c8b95400 Pen tilt is 0..90 not -90..90 2023-06-25 00:24:55 -05:00
Cameron Gutman c8aac7f71c Define units for controller motion events 2023-06-24 21:28:43 -05:00
Cameron Gutman de0efa861a Several fixes for touch and pen APIs 2023-06-24 21:25:48 -05:00
Cameron Gutman 5cbb6f210d Fix signness of pen tilt parameters 2023-06-24 15:14:17 -05:00
Cameron Gutman 812ec0e2b7 Allow exceeding 4 controllers for Sunshine hosts 2023-06-18 16:36:34 -05:00
Cameron Gutman 9bf09d681e Plumb Sunshine feature flags SDP attribute for protocol extensions 2023-06-18 15:52:51 -05:00
Cameron Gutman 689450954c Add pen and motion batching 2023-06-18 14:30:16 -05:00
Cameron Gutman 372eb94ed0 Expand button flags to support additional buttons with Sunshine 2023-06-18 14:03:29 -05:00
Cameron Gutman 7eea7a7971 Fix endianness issues for BE systems 2023-06-18 13:50:39 -05:00
Cameron Gutman 329c55d52f Send pointer IDs instead of touch indices 2023-06-18 13:48:55 -05:00
Cameron Gutman cd35abbae7 Fix build 2023-06-11 19:46:13 -05:00
Cameron Gutman ce98d4fb2f Introduce connection callbacks for trigger rumble and motion events 2023-06-11 19:42:02 -05:00
Cameron Gutman 4a48024dc8 Introduce new input extension functions for touch, pen, motion, and controller arrival 2023-06-11 19:38:22 -05:00
Cameron Gutman 7970925fe4 Introduce macro function to determine endianness 2023-06-11 19:33:24 -05:00
ns6089 284840bde7 Add support for sunshine host latency 2023-05-03 20:38:42 -05:00
Cameron Gutman 169078d0a9 Allow encoder to pick optimal ref frame count if RFI is supported by the decoder 2023-04-29 13:11:38 -05:00
Cameron Gutman c9426a6a71 Add HDR and colorspace fields to DECODE_UNIT 2023-03-11 11:36:21 -06:00
Cameron Gutman 02f12e4448 Update enet to fix NO_MSGAPI build 2023-03-05 14:32:34 -06:00
Cameron Gutman b77072d399 Don't use GFE modifier workaround with Sunshine 2023-02-25 13:11:54 -06:00
Cameron Gutman d3cb8131d1 Revert "Send TCP FIN after completion of our RTSP request"
Existing servers already know how to parse our messages without this,
so we just risk breaking things with middleboxes that don't do half-closed
connections properly. We'll handle this better with encrypted RTSP.

This reverts commit 95e3e26d12.
2023-02-20 16:58:21 -06:00
Cameron Gutman 7e089435c7 Reduce max number of frame status reports per second 2023-02-20 16:48:12 -06:00
Cameron Gutman 55a6d58225 Fix leak 2023-02-20 16:42:23 -06:00
Cameron Gutman 207f981fd0 Send final FEC frame status info to Sunshine
This info will be used for dynamic FEC and dynamic video packet batch size.
2023-02-20 16:24:48 -06:00
Cameron Gutman 82105f2f8f Declare packetFreed as volatile 2023-02-15 01:42:48 -06:00
Cameron Gutman c88b1bcf8e Fix integer truncation warning on MSVC 2023-02-15 01:33:32 -06:00
Cameron Gutman 54d46ca80f Put backpressure on control stream sender when the window fills up
Without any backpressure, ENet will just let packets accumulate forever.
2023-02-14 23:02:18 -06:00
Cameron Gutman 830187e4da Throttle mouse/controller message rate in the input send thread
This avoids clients from all having to handle this themselves independently.
2023-02-14 21:24:13 -06:00
Cameron Gutman 95e3e26d12 Send TCP FIN after completion of our RTSP request
This allows servers to read until a FIN, just like we do on the client.
2023-02-13 21:47:22 -06:00
Cameron Gutman 8186687093 Fix video header parsing assert with GFE 3.27 2023-02-13 21:26:47 -06:00
Mariotaku cf0a0ced90 Use new mbedtls APIs (#75) 2023-02-13 20:31:45 -06:00
Cameron Gutman be1a48e856 Treat WSA_IO_PENDING as a receive timeout 2023-02-13 20:20:44 -06:00
Cameron Gutman fb12361b3f Fixed ENet to support hosts with multiple possible source addresses 2023-02-12 15:39:58 -06:00
Cameron Gutman dc186082a7 Add protocol extension for multi-client-compatible ping support 2023-02-12 01:23:25 -06:00
HexJacaranda 5e14dbc15e Options value parsing fix 2023-02-11 15:37:52 -06:00
Cameron Gutman 07beb0f0a5 Accept any negative version as Sunshine 2023-01-16 21:38:36 -06:00
Cameron Gutman 08bb69ff7c Fix HScroll packet typedef 2023-01-16 21:24:09 -06:00
Cameron Gutman 4dbb445f8b Suppress Opus header assert with Sunshine 2023-01-16 21:13:18 -06:00
Cameron Gutman e1a7fe84e0 Add missing return 2023-01-16 21:12:24 -06:00
Cameron Gutman 79b5ef0e1e Add Sunshine protocol extension for passing HDR metadata 2023-01-16 21:06:33 -06:00
Cameron Gutman c9a5cea93e Add Sunshine protocol extension to support non-normalized key codes 2023-01-16 21:05:11 -06:00
Cameron Gutman 22adeb3902 Enable high resolution scroll events on Sunshine 2023-01-16 20:58:03 -06:00
Cameron Gutman 071e595766 Implement horizontal scrolling protocol extension for Sunshine 2023-01-16 20:56:51 -06:00
Cameron Gutman ef9ad529a4 Skip PPS NALUs prepended to P-frames 2023-01-02 23:48:16 -06:00
Cameron Gutman 9da6329496 Fix integer truncation warning on MSVC 2022-12-06 17:33:43 -06:00
Cameron Gutman 5c7a5ce129 Remove overzealous assert 2022-12-06 17:23:11 -06:00
Cameron Gutman bc00d957d3 Synthesize a PTS for hosts that don't send one 2022-12-06 17:22:02 -06:00
Cameron Gutman 8169a31ecc Fix incorrect PNV_INPUT_HEADER typedef 2022-11-22 21:30:17 -06:00
Cameron Gutman 0a87fd023d Add magic values for keyboard/mouse up/down 2022-11-22 18:27:27 -06:00
Cameron Gutman 4840e431f4 Call notifyKeyFrameReceived() before the decoder processes the frame
We don't care if the decoder processes it successfully.
2022-11-21 02:22:26 -06:00
Cameron Gutman 1e582e3e58 Improve decode unit validation 2022-11-21 02:17:47 -06:00
Cameron Gutman 549058d9d1 Assert that P-frames are not processed before IDR frames 2022-11-12 19:42:51 -06:00
Cameron Gutman 56d6b7ff5a Ensure RFI is not used when an IDR frame is needed 2022-11-12 19:22:13 -06:00
Cameron Gutman 50c0a51b10 Improve high quality audio support
High quality audio now works on IPv6 and remote connections
2022-11-04 21:44:07 -05:00
happyharryh 7d9df5b731 Update oldestRtpBaseSequenceNumber when advance the queue state 2022-10-31 20:36:55 -05:00
Cameron Gutman 6418c631ba Further improve audio FEC recovery logic when dealing with completely missing FEC blocks 2022-10-25 22:31:18 -05:00
Cameron Gutman 21998586d1 Improve audio recovery when a prior FEC block was completely lost
We now start playing the latest FEC block immediately, instead of needlessly
waiting for a second FEC block to realize we've lost a full block and need
to skip forward.
2022-10-24 23:38:23 -05:00
Cameron Gutman 7ba4eea417 Don't print misleading audio FEC log messages 2022-10-23 22:09:01 -05:00
Cameron Gutman a2f68835f9 Fix signed comparison warning on GCC 2022-10-22 15:08:55 -05:00
Cameron Gutman af1dbfe505 Use APP_VERSION_AT_LEAST in a couple other places 2022-10-22 14:45:19 -05:00
Cameron Gutman b282c7e603 Ensure UNIX implementations of PltGetMillis() are not limited to 32 bits 2022-10-22 14:45:19 -05:00
Cameron Gutman 6d767a8494 Assume the platform supports clock_gettime() if CLOCK_MONOTONIC is defined 2022-10-22 14:45:19 -05:00
Cameron Gutman 9e52b70edf Fix handling of a drop pattern where OOS packets are late but in contiguous order from the previous point 2022-10-22 14:45:19 -05:00
Cameron Gutman 05a925b801 Simplify speculative RFI tracking by looking at missing shards of any type 2022-10-22 14:45:19 -05:00
Cameron Gutman 5f92ecafe7 Avoid triggering RFI wait code for decode unit queue overflow 2022-10-06 21:25:48 -05:00
Cameron Gutman 44668bd256 Distinguish speculative RFIs from normal FEC-initiated RFIs 2022-10-06 21:21:00 -05:00
Cameron Gutman 73f1ec64a8 Suppress speculative RFI for 5 minutes after OOS data is detected 2022-10-06 21:20:44 -05:00
Cameron Gutman 947d1b5aef Predictive RFI -> Speculative RFI 2022-10-06 21:20:44 -05:00
Cameron Gutman 9d25ebec40 Immediately report a lost frame if we find a discontinuity in the frame indices 2022-10-06 21:20:41 -05:00
Cameron Gutman 7d1a081fd0 Fix signature of notifyFrameLost() and add additional assert 2022-10-06 20:38:55 -05:00
Cameron Gutman f2dd7888f7 Immediately request an IDR frame on corrupt frame 2022-10-06 20:36:53 -05:00
Cameron Gutman e951302927 Disable RFI when streaming from a server that doesn't support it 2022-10-06 20:01:40 -05:00
Cameron Gutman 9091238861 Add another predictive RFI detection technique 2022-10-06 01:17:37 -05:00
Cameron Gutman 5e1be51b84 Implement predictive RFI to recover more quickly from frame loss
With predictive RFI, we can recover from a lost frame as soon as the next frame
by predicting whether we will have enough FEC data to be be able to recover a
frame based on the sequence numbers of the received packets.
2022-10-05 22:04:22 -05:00
Cameron Gutman 9240090983 Add LiRequestIdrFrame() API for requesting an IDR frame on demand 2022-10-05 00:46:56 -05:00
Cameron Gutman 502f799a73 Add ML_ERROR_FRAME_CONVERSION error constant 2022-10-04 19:37:35 -05:00
Cameron Gutman bfade1d795 Add workaround for nvstreamer.exe crash on GFE 3.26 when streaming below 720x540 2022-10-04 18:33:12 -05:00
Cameron Gutman 72f4f9e7b1 Disable BLL-FEC on GFE 3.26
The effective FEC percentage is much lower which negatively impacts performance on lossy networks
2022-10-04 17:51:01 -05:00
Cameron Gutman dbfbc91971 Explicitly request an IDR frame in the IDR frame wait path 2022-10-03 18:27:57 -05:00
Cameron Gutman a4870f619b Frame type fields are present all the way back to GFE 3.2 (and probably further) 2022-10-03 18:26:24 -05:00
Cameron Gutman 4c62b5d23a Fix fallback handling of RFI on older servers 2022-10-03 18:25:08 -05:00
Cameron Gutman 3593dea585 Rework RFI to work reliably with HEVC 2022-10-02 22:19:48 -05:00
Cameron Gutman 795e3f644a Ensure the next frame after a DU drop is always an IDR frame
On RFI-capable decoders, this would sometimes be a P-frame
2022-10-02 21:12:01 -05:00
Cameron Gutman 3ae777f973 Try to recover if the frame header parsing fails 2022-09-24 13:43:32 -05:00
Cameron Gutman 54825845e7 Fix large frame header size for GFE 3.26 2022-09-22 21:51:11 -05:00
Cameron Gutman 50603ac16e Fix mishandling of IDR frames with a VUI or AUD NAL 2022-09-11 23:34:38 -05:00
Cameron Gutman e453a4d548 Add 3 byte Annex B start sequence test mode for debugging clients 2022-09-05 17:41:37 -05:00
Cameron Gutman 5a2911ffe4 Rework NALU parsing to be more robust handling 3 byte prefixes 2022-09-05 16:45:14 -05:00
Cameron Gutman ec420615a1 Fix GCC build 2022-08-10 00:18:56 -05:00
Cameron Gutman ef9c5a65cf Return a better error code when the control stream connection fails 2022-08-09 23:26:33 -05:00
Cameron Gutman c72f30da2e Improve logging for control stream connection failure 2022-08-09 23:12:31 -05:00
Cameron Gutman bf22101c7d Add video format mask for 10-bit color 2022-06-26 16:19:44 -05:00
Cameron Gutman d247873ade Add LiSendMouseMoveAsMousePositionEvent() function
This is a useful option for Android and iOS where mouse acceleration
is always enabled and mouse capture is the only feasible way to get
mouse input. It can avoid double-acceleration when being used for
remote desktop mouse mode.
2022-05-28 14:58:34 -05:00
Cameron Gutman e62dc56047 Update notes regarding enableHdr option 2022-02-07 20:16:19 -06:00
Cameron Gutman bc458b8848 Use 15 second timeout for RTSP replies
GFE has an internal 10 second timeout for bringing up the streamer,
so we can end up timing out right as GFE is trying to reply to us.
2022-02-06 20:59:18 -06:00
Cameron Gutman abc7acb5e4 Add HDR mode APIs 2022-01-28 21:29:14 -06:00
Sunguk Lee c6d68d9e87 vita: Reduce TCPv4_MSS to 512 to avoid setsockopt error
`setsockopt` with 536 returned EINVAL error.
SCE would use simple value for limit
2022-01-24 22:19:09 -06:00
Sunguk Lee 4a4a23c7c4 vita: Replace select to poll
now newlib support [this][1]

[1]: https://github.com/vitasdk/newlib/pull/70
2022-01-24 22:16:59 -06:00
Cameron Gutman cfe75eb569 Add workaround for keyboard and UTF-8 events interfering with each other 2022-01-17 21:56:06 -06:00
Cameron Gutman 921b59c467 Add API to wake a waiting thread in LiWaitForNextVideoFrame() 2022-01-17 14:12:11 -06:00
Mariotaku f2e45695b2 use minimum required mbedcrypto 2022-01-12 17:47:04 -06:00
Cameron Gutman 6001ece0b8 Fix uninitialized variable 2021-12-12 16:04:58 -06:00
Cameron Gutman ac1355b922 Fix excessive high-res scroll speed on newer GFE versions 2021-12-12 12:05:20 -06:00
Cameron Gutman 257029f80d Remove redundant packet length field 2021-12-10 02:00:42 -06:00
Cameron Gutman 94d439e5c3 Fix memory corruption with UTF-8 strings over 32 bytes 2021-11-30 19:05:32 +03:00
Cameron Gutman 6a6328a355 Send each Unicode code point individually to avoid straddling packet boundaries 2021-11-30 18:28:09 +03:00
Cameron Gutman 8c55c086d5 Update enet submodule 2021-09-16 01:31:47 -05:00
Mariotaku 5ed9a6508a Fixed build issue with MinGW (#64) 2021-09-16 01:30:47 -05:00
Cameron Gutman 5b2cf1b8f7 Fix handling of hostname with multiple candidate IP addresses when using alternate ports 2021-08-14 12:30:04 -05:00
Cameron Gutman a290ec032b Fix relative mouse and controller batching after 74377a06 2021-08-05 22:14:35 -05:00
Cameron Gutman 8cac195fcf Fix compilation warnings 2021-07-30 01:32:43 -05:00
Cameron Gutman f3e37944fc Split UTF-8 strings into chunks small enough to fit in an NV_UNICODE_PACKET 2021-07-30 01:23:53 -05:00
Cameron Gutman 74377a061b Rework input packets based on new header knowledge 2021-07-30 01:02:35 -05:00
Cameron Gutman ceafe7897c Refactor input packet sending logic into a separate function 2021-07-29 23:49:58 -05:00
Mariotaku 5846a9d6aa Added unicode input event (#63)
* Added unicode input event

* Prevents strcpy overflow

* Updated according to change requests
2021-07-29 21:36:47 -05:00
Cameron Gutman 8abc371fb4 Print warning when audio decryption fails 2021-07-17 22:57:50 -05:00
Cameron Gutman b2c39883bf Fix incorrect IV for encrypted control stream packets on big-endian machines 2021-07-17 22:29:30 -05:00
Cameron Gutman d14f62c26a Fix crash when client provides no video decoder callbacks 2021-07-17 21:55:39 -05:00
Cameron Gutman fa892c5334 Remove SEI prefix NAL units before returning data to clients 2021-07-09 17:37:30 -05:00
Cameron Gutman 5820cc2048 Fix double-free if unencrypted packet is received on encrypted stream 2021-07-08 20:50:09 -05:00
Cameron Gutman 3b9d8a3176 Assert that the port numbers are set prior to being used 2021-07-02 01:44:11 -05:00
Cameron Gutman cdda221d64 Log the parsed ports 2021-07-02 01:30:33 -05:00
Cameron Gutman 5ec8ee7cbf Parse the RTSP port out of the RTSP session URL 2021-07-02 01:17:34 -05:00
Cameron Gutman 0cd3fcf1be Fix off-by-one in port number validation 2021-07-02 01:16:20 -05:00
Cameron Gutman 46887c0447 Parse the ports from RTSP SETUP instead of RTSP DESCRIBE
RTSP SETUP is better because it can provide multiple IP+port candidates
2021-07-02 00:28:48 -05:00
Cameron Gutman f05be47ed8 Replace other hardcoded ports 2021-07-02 00:27:35 -05:00
Cameron Gutman 56ccd99cc7 Dynamically determine audio, video, and control ports from RTSP DESCRIBE response 2021-07-01 23:45:33 -05:00
Cameron Gutman 2660a05084 Fix GCC build 2021-07-01 22:28:17 -05:00
Cameron Gutman 1b642fec73 Fix control flow bugs in poll() error paths 2021-07-01 22:20:09 -05:00
Cameron Gutman 52250b4815 Use O_NONBLOCK for platforms without support for FIONBIO 2021-07-01 22:15:26 -05:00
Cameron Gutman c00f4e15ae Use poll() instead of SO_RCVTIMEO for RTSP timeout support
poll() is more portable than SO_RCVTIMEO
2021-07-01 22:10:46 -05:00
Cameron Gutman 7c346c3104 Fix handling of older GFE versions in surround sound mode
7.1 surround sound would usually behave like CBR content, which never
tripped the logic to detect this incompatibility. Now we will both
detect it via hardcoded version number check, but also detect when the
FEC base sequence number constraint is violated too.
2021-06-20 14:55:44 -05:00
Cameron Gutman 5e3aa93479 Add a recording mode for debugging purposes 2021-06-20 11:15:30 -05:00
Cameron Gutman 75999a6e07 Add Video/Audio prefix to ThreadProc names
It's easier to find them in stack traces that way.
2021-06-20 10:36:05 -05:00
Cameron Gutman eff97414bf Perform the initial audio resync even if we hit the polling timeout 2021-06-19 16:44:48 -05:00
Cameron Gutman c680a60710 Disable audio FEC when using old GFE and Sunshine versions 2021-06-19 16:22:44 -05:00
Cameron Gutman fed1a899aa Add defines for RTP audio payload types 2021-06-19 16:08:18 -05:00
Cameron Gutman b58930c687 Don't start the decoder thread in pull-mode 2021-06-13 15:30:37 -05:00
Cameron Gutman 68c784445c Introduce optional pull-based API for video data 2021-06-13 15:14:38 -05:00
Cameron Gutman b2d4b2c61f Fix spurious exit of fast recovery mode when the stream starts 2021-06-12 18:33:20 -05:00
Cameron Gutman 9999156f26 Update ENet to avoid dependency on qwave.dll 2021-06-12 12:16:39 -05:00
Cameron Gutman ac9c4e82ac Introduce a list to cache freed input packet entries for reuse 2021-06-12 10:43:50 -05:00
Cameron Gutman a13eeddacf Fix Win32 release build 2021-06-09 20:38:22 -05:00
Cameron Gutman f01103af23 Add a free FEC block cache to avoid memory allocations 2021-06-09 20:29:05 -05:00
Cameron Gutman fb9aab0e57 Reimplement LBQ on condition variables 2021-06-09 19:59:52 -05:00
Cameron Gutman 71a267fd28 Change PltWaitForEvent to void return type 2021-06-09 19:37:06 -05:00
Cameron Gutman 61b4fc1fe7 Add condition variable support and reimplement PLT_EVENT with it for non-Win32 platforms 2021-06-09 19:29:31 -05:00
Cameron Gutman ccec4e8475 Add thread naming on macOS and iOS 2021-06-09 03:00:25 -05:00
Cameron Gutman b471edbb80 Add a fast path to drop the audio FEC block with in-order packets
If we have not seen any OOS audio data lately, we can assume that
seeing the next FEC block means the previous block has completed
transmission. If we don't have it all by then, just assume it was
lost and move on. This reduces the perception of audio loss by
reducing the change that we cause the audio device to underrun.
2021-06-09 02:31:55 -05:00
Cameron Gutman c64ba99654 Fix build on GCC/Clang 2021-06-09 01:45:44 -05:00
Cameron Gutman 7b838dd692 Improve audio FEC queue timeout logic 2021-06-09 00:46:14 -05:00
Cameron Gutman ca7a6e7bbe Validate block size before writing data 2021-06-08 20:03:49 -05:00
Cameron Gutman 2228e4812d Improve debugging for A/V data loss and recovery 2021-06-08 20:01:58 -05:00
Cameron Gutman 5a71a4c092 Use 10ms packets for slow Opus decoders
With the old 20ms packets, an FEC block was 80ms of audio data.
This is enough data that waiting for the FEC shards can cause
an audio data underflow. The burst of 80ms of audio data after
reassembly fails can then lead clients to drop samples due to
excessive queued data.
2021-06-07 23:20:40 -05:00
Cameron Gutman 4e7b1e3c37 Merge pull request #60 from GaryOderNichts/wiiu-platform
Wii U platform support
2021-06-04 21:53:58 -05:00
GaryOderNichts e5b39af6a5 some Wii U changes 2021-06-05 01:59:58 +02:00
GaryOderNichts 0c66a50e2d Wii U platform support 2021-06-04 21:36:54 +02:00
Cameron Gutman 509a17dbc3 Perform PLC for each lost packet in the FEC block 2021-06-03 21:03:46 -05:00
Cameron Gutman a8aed6b344 Avoid spurious audio data loss warning on connection start 2021-06-03 21:02:27 -05:00
Cameron Gutman ef33aaa3c8 Fix queued packet leak when audio packet queue overflows 2021-06-02 22:05:45 -05:00
Cameron Gutman e9fd544ff4 Fix FEC queue state assert firing on shutdown 2021-06-02 20:11:20 -05:00
Cameron Gutman e3d4f4e91f Validate the Opus data in debug builds 2021-06-02 20:10:46 -05:00
Cameron Gutman ab51acb712 Only data shards should be eligible for immediate consumption 2021-06-02 19:50:22 -05:00
Cameron Gutman d0adb044cf Fix bugs discovered by FEC state validation logic 2021-06-02 19:36:57 -05:00
Cameron Gutman 565b170e00 Add FEC state validation in debug builds 2021-06-02 19:36:31 -05:00
Cameron Gutman 9361c325bb Rename RtpFecQueue to RtpVideoQueue to match RtpAudioQueue 2021-06-01 22:57:26 -05:00
Cameron Gutman 08e4a47fc2 Add FEC validation mode for audio FEC 2021-06-01 22:43:43 -05:00
Cameron Gutman dd3675db63 Fix mishandling of a completely missing FEC block 2021-06-01 21:31:48 -05:00
Cameron Gutman da7db59414 Fix freeing FEC block after wrapping seqnum with in-order FEC block 2021-06-01 21:16:18 -05:00
Cameron Gutman fde6b05618 Fix incorrect assert on sequence number wrap 2021-06-01 21:14:25 -05:00
Cameron Gutman 683208ddc8 Return all available audio data after FEC block timer expires 2021-06-01 20:01:26 -05:00
Cameron Gutman 3dff15b8c4 Fix memory leak when switching between immediate and queued audio packets 2021-06-01 19:33:56 -05:00
Cameron Gutman 4aa82b0a9f Fix Clang/GCC build 2021-06-01 18:39:19 -05:00
Cameron Gutman 89918324ce Implement audio FEC recovery support 2021-06-01 18:31:56 -05:00
Cameron Gutman 122ce4a568 Enable SO_RCVTIMEO for RTP sockets on Windows 2021-05-25 20:38:30 -05:00
Cameron Gutman d9ea208dea Avoid unlock and immediate relock of mutex 2021-05-25 19:57:06 -05:00
Cameron Gutman 1376c62e5f Remove declaration of old deleted function 2021-05-25 19:54:27 -05:00
Cameron Gutman 59484ea066 Reorder some internal structures to reduce padding 2021-05-25 19:54:03 -05:00
Cameron Gutman 46cea5011d Use GetModuleHandle() for kernel32.dll, since it's guaranteed to be loaded already 2021-05-25 19:40:14 -05:00
Cameron Gutman 4723f8ba7c Update ENet to avoid excessive retransmissions when RTT variance is 0 2021-05-16 15:37:40 -05:00
Cameron Gutman d4e22bb4a6 Update ENet to fix inaccurate RTT values under 8 ms 2021-05-16 14:42:15 -05:00
Cameron Gutman daee5db7bc Use SleepEx() instead of WaitForSingleObjectEx() to implement PltSleepMs()
This was legacy cruft from back when Sleep() was forbidden in Metro apps in Win8.0.
2021-05-16 11:41:38 -05:00
Cameron Gutman c057075eac Set minRequiredFecPackets to better handle lossy links at low bitrates 2021-05-16 09:27:50 -05:00
Cameron Gutman ed9301f3f8 Join the input thread without interrupting first
Otherwise we'll kill it before it finishes draining
2021-05-15 22:56:44 -05:00
Cameron Gutman 431e188b07 Allow the input queue to drain before terminating the thread
This ensures any final input messages (like key ups) make it to the host
2021-05-15 22:01:50 -05:00
Cameron Gutman 71c9ff0d91 Elide the wait if the queue head is already non-null 2021-05-15 21:31:45 -05:00
Cameron Gutman edf1838708 Expose RTT information from ENet 2021-05-15 13:58:54 -05:00
Cameron Gutman 5feb3b6f90 Dynamically adjust wait time to hit the next RTO 2021-05-15 12:47:20 -05:00
Cameron Gutman 50c0648de2 Run a full enet_host_service() rather than just enet_host_flush() on send 2021-05-15 12:26:01 -05:00
Cameron Gutman 97216e1704 Wait for ENet socket readability rather than just sleeping 10 ms 2021-05-15 00:15:16 -05:00
Cameron Gutman 43eb36e17a Simplify and improve graceful disconnect logic 2021-05-14 22:36:13 -05:00
Cameron Gutman 387ff48a65 Perform a graceful disconnection if the termination was locally initiated 2021-05-14 22:06:07 -05:00
Cameron Gutman 1b3c14a792 Increase maximum input queue size 2021-05-14 21:07:14 -05:00
Cameron Gutman 132833deeb Limit RTO to 2x RTT and fix early peer timeout expiration 2021-05-14 20:44:19 -05:00
Cameron Gutman a1a150c300 Refactor IDR/RFI requests to simplify code and fix race conditions 2021-05-14 20:41:20 -05:00
Cameron Gutman cd62147cdf Print a warning when the input queue reaches the maximum size 2021-05-14 20:36:25 -05:00
Cameron Gutman 7743899251 Optimize LBQ to avoid unnecessary syscalls 2021-05-14 18:42:01 -05:00
Cameron Gutman 5d09d43b08 Disable RFI at 4K until the problems are understood 2021-05-06 22:17:04 -05:00
Cameron Gutman ccaca624f3 Attempt to fix audio latency issues on some PCs 2021-05-04 10:21:41 -05:00
Cameron Gutman 65047ac0e2 Switch to VS2019 build images for CI
Requires suppressing warning C5105 caused by Windows SDK headers
2021-04-29 17:59:38 -05:00
Cameron Gutman b0737b882d Adjust MSVC warning level 2021-04-29 17:51:20 -05:00
Mariotaku Lee 179970a0d5 Behavior changes to CMakeLists.txt
- Allows static library compilation
 - Links to MBedTLS/OpenSSL based on build option
 - Limits visibility of some defines and headers
2021-04-29 17:40:47 -05:00
Cameron Gutman bc1b5a1b2f Fix Linux build 2021-04-29 17:40:13 -05:00
Cameron Gutman a000f9f8b8 Validate that claimed length doesn't exceed the actual length 2021-04-29 17:26:59 -05:00
Cameron Gutman 8f371343cd Consolidate includes 2021-04-29 17:22:15 -05:00
Cameron Gutman da68e64d9b Fix various MSVC warnings 2021-04-29 17:21:12 -05:00
Cameron Gutman 77ed77b93b Pull ENet changes from upstream 2021-04-29 09:44:32 -05:00
Cameron Gutman 252a50bb75 Disable WSAECONNRESET errors for UDP sockets on Windows 2021-04-28 20:37:20 -05:00
Cameron Gutman 7549243f40 Add the correct definition for ECONNREFUSED for Windows 2021-04-28 17:30:00 -05:00
Cameron Gutman 9c92c12fea Prevent errno.h from including incompatible error codes with Winsock codes 2021-04-28 17:29:33 -05:00
Cameron Gutman 3ae03998a2 Add retry logic for RTSP handshake connection refused error
This can happen due to a race condition between GFE completing the launch request and starting to listen on TCP 48010
2021-04-28 17:08:27 -05:00
Cameron Gutman 068f7aa9d9 Allow connection interruption during the RTSP handshake 2021-04-28 17:06:04 -05:00
Cameron Gutman bce9f82844 Fix strict aliasing violation (and save a byteswap on each packet) 2021-04-28 17:05:41 -05:00
Cameron Gutman fe205d838d Don't check for errors from sendto() in the ping threads 2021-04-28 17:04:44 -05:00
Cameron Gutman 83b1b17f87 Add specific logging for frames dropped completely 2021-04-27 17:39:00 -05:00
Cameron Gutman c1befbe2a8 Improve audio resync logic to use initial receive time rather send time 2021-04-26 22:43:49 -05:00
Cameron Gutman 33c4e98152 Adjust initial audio resync drop time based on connection handshake latency 2021-04-26 21:39:36 -05:00
Cameron Gutman 7174caf5f1 Implement support for multi-FEC frames 2021-04-26 20:36:07 -05:00
Cameron Gutman 9a5dbcf31c Create a second queue for completed FEC blocks 2021-04-26 20:26:16 -05:00
Cameron Gutman b33e9fbcde Fix list corruption on entry removal 2021-04-26 20:24:39 -05:00
Cameron Gutman ca4019c09f Refactor FEC queue in preparation for multi-block frame support 2021-04-26 17:09:50 -05:00
Cameron Gutman 8dab1ee300 Use stdint.h types for Video.h 2021-04-26 17:08:44 -05:00
Cameron Gutman ae92f15b0a Use SRW locks for our mutex on Windows
These avoid a kernel transition for uncontended cases
2021-04-25 15:04:12 -05:00
Cameron Gutman 13041e0323 Further optimization to avoid needless calls to EVP_aes_128_gcm() and EVP_aes_128_cbc() 2021-04-22 17:49:51 -05:00
Cameron Gutman 8354c403f4 Optimize OpenSSL backend to avoid redundant reinitialization
It is best to pass as few parameters to EVP_*Init_ex() as possible.
Passing a key, IV, or cipher will cause redundant work to happen behind
the scenes as OpenSSL doesn't check whether they have actually changed.

This avoids a malloc()/free() and redoing AES key expansion for every
message that is encrypted and decrypted.
2021-04-22 17:34:48 -05:00
Cameron Gutman 29d2cc6d5b Improve MbedTLS implementation of AES-CBC 2021-04-22 17:08:35 -05:00
Cameron Gutman d7549cd953 Use array initializers compatible with MSVC 2021-04-22 00:32:41 -05:00
Cameron Gutman db81f1e512 Add support for audio stream encryption
Clients must opt-in using the new encryptionFlags field
2021-04-22 00:20:17 -05:00
Cameron Gutman 55cf1f8d30 Remove unnecessary calls to EVP_CIPHER_CTX_reset()
EVP_EncryptInit_ex() and EVP_DecryptInit_ex() free the cipher state as required
2021-04-22 00:17:46 -05:00
Cameron Gutman 625ec431eb Improve AES-CBC code in preparation for audio encryption 2021-04-21 23:59:14 -05:00
Cameron Gutman 3abd3af4b2 Convert 'supportsHevc' and 'enableHdr' fields to bool type 2021-04-21 23:32:43 -05:00
Cameron Gutman b24a71ab0b Add explicit IDR frame request packet for GFE 3.22 2021-04-21 17:52:05 -05:00
Cameron Gutman 3adacb876d Fix incorrect checks for AF_INET6 2021-04-19 23:01:17 -05:00
Cameron Gutman 221af82950 Add workaround for false GCC warning in CI 2021-04-18 00:15:27 -05:00
Cameron Gutman 76bba517b8 AppVeyor build fixes 2021-04-17 23:59:47 -05:00
Cameron Gutman 23a86b0455 Build with OpenSSL and MbedTLS for CI 2021-04-17 23:56:15 -05:00
Cameron Gutman 3979dbd082 Allow compilation on OSes without IPv6 support 2021-04-17 23:40:36 -05:00
Cameron Gutman bced126fdb Fix MSVC builds 2021-04-17 22:44:47 -05:00
Cameron Gutman 873fc6f837 Build fixes 2021-04-17 22:00:53 -05:00
Cameron Gutman 4304e597d8 Add byteswapping macros for big-endian systems 2021-04-17 21:43:13 -05:00
Cameron Gutman efaeade7a6 Small cleanup and bugfix 2021-04-17 20:24:43 -05:00
Cameron Gutman 98d7ceecf7 Add native MbedTLS crypto backend 2021-04-17 19:00:26 -05:00
Cameron Gutman d62ee951a0 Refactor OpenSSL usage into a platform-specific file to allow other crypto backends 2021-04-17 17:47:53 -05:00
Cameron Gutman 5782246b30 Add hack to prevent video glitching on GFE 3.22 when packets per frame exceed 120 2021-04-09 19:30:38 -05:00
Cameron Gutman 31433fc5ee Minor cleanup and bugfix 2021-04-09 13:47:24 -05:00
Cameron Gutman 83431e557d Fix leak on each GFE 3.22 input packet 2021-04-09 13:47:00 -05:00
Cameron Gutman 0a00163f0b Fix audio stream init error check 2021-04-09 13:00:35 -05:00
Cameron Gutman cb11b76682 Fix GCC/Clang warning 2021-04-09 12:20:02 -05:00
Cameron Gutman 6dca9c6754 Add special termination error code for protected content 2021-04-09 11:53:03 -05:00
Cameron Gutman 9841a6e34d Fix parsing of termination message from GFE 3.22 2021-04-09 11:41:59 -05:00
Cameron Gutman 731d52d020 Return a packet length from the decryption process 2021-04-09 10:40:28 -05:00
Cameron Gutman 12f0f3d6d7 Add encrypted control stream support for GFE 3.22
Receive-side not yet fully working
2021-04-09 10:25:24 -05:00
Cameron Gutman f0dbee171b Consider UDP 48000 as part of RTSP for connection testing
GFE 3.22 requires UDP 48000 communication to complete RTSP handshake
2021-04-09 08:39:28 -05:00
Cameron Gutman d0c3513504 Reorder audio initialization and RTSP handshake to avoid RTSP PLAY failure on GFE 3.22 2021-04-09 08:39:28 -05:00
Cameron Gutman 2c13835f32 Increase first-start audio drop for upcoming GFE 3.22 changes 2021-04-09 08:39:28 -05:00
Cameron Gutman c0c200e72c Fix APP_VERSION_AT_LEAST macro definition 2021-04-09 07:51:05 -05:00
Cameron Gutman 9b194cc700 Adjust RTSP requests to conform to GFE 3.22 changes 2021-04-09 07:46:14 -05:00
Cameron Gutman 9a706fd78c Temporarily switch to control protocol 9 on GFE 3.22 2021-04-09 07:45:17 -05:00
Cameron Gutman 415d97aa4d Pass through input for full control stream encryption on GFE 3.22 2021-04-09 07:44:31 -05:00
Cameron Gutman a51cc972d3 Add APP_VERSION_AT_LEAST() macro for cleaner GFE version checks 2021-04-09 07:26:24 -05:00
Cameron Gutman b528867466 Change NAL type parsing to match the spec and add AUD removal for H.264 2021-03-29 19:27:19 -05:00
Cameron Gutman 8b1fbc770e Discard AUD NALs prepended to incoming frames 2021-03-28 10:34:01 -05:00
Cameron Gutman a64af8d8cf Fix unused variable warning on NaCl 2021-01-09 20:08:22 -06:00
Cameron Gutman 021fe902d9 Update ENet submodule to upstream 1.3.17 release 2021-01-09 18:46:13 -06:00
Cameron Gutman fd950b6452 Allow applications to reliably calculate decode unit queue delay 2020-12-31 16:08:00 -06:00
Cameron Gutman 3aa2463856 Add a special error code for early termination 2020-12-24 11:21:47 -06:00
Cameron Gutman 97ca7b099f Report frames to the control stream code before FEC reconstruction 2020-12-24 11:08:31 -06:00
Cameron Gutman cca2ba9aab Add LiStringifyPortFlags() helper function 2020-12-23 13:42:28 -06:00
Cameron Gutman ce546b12b0 Fix undefined behavior due to shifting an int by 31 2020-12-06 00:44:33 -06:00
Cameron Gutman 6dd3f9e7bc Treat GCC/Clang warnings as errors 2020-12-05 23:46:10 -06:00
Cameron Gutman 941ffef2ca Fix final GCC sign warnings 2020-12-05 23:45:49 -06:00
Cameron Gutman 8dc304bcd3 Fix more warnings 2020-12-05 23:30:25 -06:00
Cameron Gutman 3fddfc5557 Fix Clang warnings 2020-12-05 23:20:02 -06:00
Cameron Gutman ac6630ef59 Add AppVeyor CI builds 2020-12-05 20:12:14 -06:00
Cameron Gutman 0ead0df2a1 Remove usage of long types for LP64 and LLP64 consistency 2020-12-05 16:51:03 -06:00
Cameron Gutman 333382ae74 Use stdint types for ByteBuffer 2020-11-30 22:03:24 -06:00
58 changed files with 14993 additions and 3452 deletions
+9
View File
@@ -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"
+79
View File
@@ -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
+80
View File
@@ -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}}"
+3
View File
@@ -1,6 +1,9 @@
.idea/
.vscode/
limelight-common/ARM/
limelight-common/Debug/
Build/
cmake-*/
**/xcuserdata/
limelight-common/Release/
*.sdf
+3
View File
@@ -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
View File
@@ -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()
+21
View File
@@ -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
+21
View File
@@ -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.
+11
View File
@@ -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
+603
View File
@@ -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
+282
View File
@@ -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);
}
+11
View File
@@ -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);
+1
Submodule nanors/deps/simde added at 595b743dce
+178
View File
@@ -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
View File
@@ -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
-641
View File
@@ -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;
}
-42
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+26 -1
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+69 -14
View File
@@ -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
View File
@@ -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
View File
@@ -2,10 +2,10 @@
// Destroy the linked blocking queue and associated mutex and event
PLINKED_BLOCKING_QUEUE_ENTRY LbqDestroyLinkedBlockingQueue(PLINKED_BLOCKING_QUEUE queueHead) {
LC_ASSERT(queueHead->shutdown || queueHead->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;
}
+10 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+470
View File
@@ -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
}
+48
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+102
View File
@@ -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;
}
+729
View File
@@ -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;
}
+88
View File
@@ -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);
-493
View File
@@ -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;
}
}
-39
View File
@@ -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);
-256
View File
@@ -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;
}
-41
View File
@@ -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);
+805
View File
@@ -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;
}
}
+60
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+70 -32
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+144 -70
View File
@@ -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
View File
@@ -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();
}
}
+32
View File
@@ -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);