Skip to content

Expose kernel TCP RTT for Java RTMP sockets - #2211

Closed
AubreyHaskett wants to merge 1 commit into
pedroSG94:masterfrom
AubreyHaskett:aeh/tcp-info-rtt
Closed

AubreyHaskett wants to merge 1 commit into
pedroSG94:masterfrom
AubreyHaskett:aeh/tcp-info-rtt

Conversation

@AubreyHaskett

@AubreyHaskett AubreyHaskett commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

👋 RootEncoder exposes RTMP ping/pong RTT, but not the RTT tracked by the kernel for the underlying TCP connection. This PR adds an optional API for reading the smoothed TCP RTT reported by the Android kernel.

  • Exposes getTcpRtt(): Long? through RtmpClient, RtmpStreamClient, and GenericStreamClient.
  • Supports direct RTMP and RTMPS connections using SocketType.JAVA on Android 10 and newer.
  • Returns RTT in microseconds, matching Linux tcp_info.tcpi_rtt.
  • Returns null when no eligible active socket exists, the platform is unsupported, the connection is not established, the native library is unavailable, or the kernel query fails.

Implementation

The Java socket descriptor is duplicated with ParcelFileDescriptor.fromSocket() and queried through a small JNI shim using getsockopt(IPPROTO_TCP, TCP_INFO).

The native query validates the returned structure length and requires tcpi_state == TCP_ESTABLISHED before reading tcpi_rtt. Socket access is synchronized, and the RTMP client verifies that the queried socket was not replaced during a reconnect.

The library retains its existing minimum SDK of 16. The native shim uses the selected NDK's API 21 floor, is loaded only on Android 10 or newer, and is packaged in the common artifact.

Design question

This is the first native code and CMake/NDK toolchain maintained by RootEncoder. Every published protocol artifact depends on common, whose release AAR now contains the JNI library for four ABIs (about 15 KB total uncompressed, or roughly 3–4 KB per ABI).

common currently owns the socket and descriptor lifetime, so keeping the shim there avoids exposing descriptors or adding a provider API and leaves room for RTSP-over-TCP reuse. If keeping common native-free is preferred, I can instead split this into an optional native artifact with an integration point in common. (I'll also add this as a comment in the changed files).

Testing

  • :common:assembleDebug
  • :common:assembleRelease
  • :common:testDebugUnitTest
  • :rtmp:testDebugUnitTest
  • :library:assembleDebug
  • :library:compileReleaseKotlin
  • :app:compileDebugKotlin
  • JitPack commit build 8215bf95: successful; all nine modules published, and the downloaded common AAR contains the JNI library for arm64-v8a, armeabi-v7a, x86, and x86_64.
  • Sustained RTMPS sampling on a Samsung Galaxy S21 running Android 14: approximately 949 one-second RTT samples over 15 minutes 49 seconds, with no null samples, reconnects, or stream interruptions observed.

@AubreyHaskett
AubreyHaskett marked this pull request as ready for review October 7, 2026 19:32
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.22.1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin/Java cannot call Linux’s getsockopt(TCP_INFO) directly. We need the small C++ JNI bridge in tcp_info.cpp, and Android’s normal Kotlin compiler cannot compile C++.

This is the first native code and CMake/NDK toolchain maintained by RootEncoder. Every published protocol artifact depends on common, whose release AAR now contains the JNI library for four ABIs (about 15 KB total uncompressed, or roughly 3–4 KB per ABI).
common currently owns the socket and descriptor lifetime, so keeping the shim there avoids exposing descriptors or adding a provider API and leaves room for RTSP-over-TCP reuse.

But if keeping common native-free is preferred, I can instead split this into an optional native artifact with an integration point in common.

#include <netinet/tcp.h>
#include <sys/socket.h>

extern "C" JNIEXPORT jlong JNICALL

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file performs one read-only query against the active TCP socket. It does not send packets or change the connection. it's like a small JNI adapter: Kotlin socket --> file descriptor --> kernel TCP_INFO --> smoothed RTT

@pedroSG94

Copy link
Copy Markdown
Owner

Hello,

Thank you for the PR but the idea of this project is keep all code only in java or kotlin (as you can see in the project description). Since you are using C++ for this feature, I have to discard it.

@pedroSG94 pedroSG94 closed this Oct 8, 2026
@AubreyHaskett

Copy link
Copy Markdown
Contributor Author

Hello,

Thank you for the PR but the idea of this project is keep all code only in java or kotlin (as you can see in the project description). Since you are using C++ for this feature, I have to discard it.

Ah, makes sense. I missed the project description, was looking at contribution guidelines. Thanks for reviewing @pedroSG94!

@AubreyHaskett

Copy link
Copy Markdown
Contributor Author

@pedroSG94 - been thinking about this...would you accept JNA? JNA could call Android’s native getsockopt() and read TCP_INFO without RootEncoder being the one writing a JNI C++ function.

@pedroSG94

Copy link
Copy Markdown
Owner

Hello,

Thank you for the interest but, no.
JNA still use JNI internally. That produce the typical problems with libraries using C++ which I want avoid:

  • You need to be care of new architectures support.
  • The library is no proguard friendly using C++ (currently you don't need proguard rules for this library).
  • Dependencies using C++ normally are really heavy.
  • Avoid maintain C++ code (if you have a problem you need open an issue and sometimes create the PR if necessary).

extra-sources module is an exception since it is only an "extra". A module that you can optionally compile and use third party libraries. The perfect example was the USB library that I had to add a PR to the repository because had problems supporting 16K page android devices (the new architecture).
Since this dependency has to be added in the library core. I have to decline it.

A suggestion that could be added is a getter of the java socket and then you can get the descriptor from it and get the rtt using JNA/JNI or as you want in your side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants