diff --git a/.gitignore b/.gitignore index 4b819f55..4afaccd8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ local.properties /app/videonative/compile_commands.json /app/wfbngrtl8812/compile_commands.json /compile_commands.json +/tmp +video.sdp +plan.md diff --git a/README.md b/README.md index f1b0f9b2..0d2155e0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ where (brace yourself) most things actually work. - [LiveVideo10ms](https://github.com/Consti10/LiveVideo10ms): excellent video decoder from [Consti10](https://github.com/Consti10) converted into a module. - [wfb-ng](https://github.com/svpcom/wfb-ng): library allowing the broadcast of the video feed over the air. -The wfb-ng [gs.key](https://github.com/OpenIPC/PixelPilot/raw/main/app/src/main/assets/gs.key) is embedded in the app. +The wfb-ng [gs.key](https://github.com/OpenIPC/PixelPilot/raw/master/app/src/main/assets/gs.key) is embedded in the app. The settings menu allows selecting a different key from your phone. Supported rtl8812au wifi adapter are listed [here](https://github.com/OpenIPC/PixelPilot/blob/master/app/src/main/res/xml/usb_device_filter.xml). diff --git a/app/src/main/java/com/openipc/pixelpilot/VideoActivity.java b/app/src/main/java/com/openipc/pixelpilot/VideoActivity.java index a834a8bb..e3c8da26 100644 --- a/app/src/main/java/com/openipc/pixelpilot/VideoActivity.java +++ b/app/src/main/java/com/openipc/pixelpilot/VideoActivity.java @@ -568,6 +568,9 @@ private void showSettingsMenu(View anchor) { // Drone submenu setupDroneSubMenu(popup); + // UDP Forwarding submenu + setupUdpForwardingSubMenu(popup); + // Help submenu setupHelpSubMenu(popup); @@ -920,6 +923,88 @@ private void setupDroneSubMenu(PopupMenu popup) { }); } + private void setupUdpForwardingSubMenu(PopupMenu popup) { + SubMenu forwardMenu = popup.getMenu().addSubMenu("UDP Forwarding"); + + SharedPreferences prefs = getSharedPreferences("general", MODE_PRIVATE); + boolean enabled = prefs.getBoolean("forward_udp_enabled", false); + String ip = prefs.getString("forward_udp_ip", "192.168.1.100"); + int port = prefs.getInt("forward_udp_port", 5600); + + MenuItem enableItem = forwardMenu.add("Enable"); + enableItem.setCheckable(true); + enableItem.setChecked(enabled); + enableItem.setOnMenuItemClickListener(item -> { + boolean newState = !item.isChecked(); + item.setChecked(newState); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean("forward_udp_enabled", newState); + editor.apply(); + updateUdpForwardingState(); + return true; + }); + + MenuItem configItem = forwardMenu.add("Config Target (" + ip + ":" + port + ")"); + configItem.setOnMenuItemClickListener(item -> { + showUdpForwardingDialog(); + return true; + }); + } + + private void showUdpForwardingDialog() { + SharedPreferences prefs = getSharedPreferences("general", MODE_PRIVATE); + String ip = prefs.getString("forward_udp_ip", "192.168.1.100"); + int port = prefs.getInt("forward_udp_port", 5600); + + android.widget.LinearLayout layout = new android.widget.LinearLayout(this); + layout.setOrientation(android.widget.LinearLayout.VERTICAL); + layout.setPadding(50, 30, 50, 30); + + final android.widget.EditText ipEditText = new android.widget.EditText(this); + ipEditText.setHint("Destination IP Address"); + ipEditText.setText(ip); + layout.addView(ipEditText); + + final android.widget.EditText portEditText = new android.widget.EditText(this); + portEditText.setHint("Destination Port"); + portEditText.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); + portEditText.setText(String.valueOf(port)); + layout.addView(portEditText); + + new android.app.AlertDialog.Builder(this) + .setTitle("UDP Forwarding Config") + .setView(layout) + .setPositiveButton("Save", (dialog, which) -> { + String newIp = ipEditText.getText().toString().trim(); + String newPortStr = portEditText.getText().toString().trim(); + int newPort = 5600; + try { + newPort = Integer.parseInt(newPortStr); + } catch (NumberFormatException ignored) {} + + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("forward_udp_ip", newIp); + editor.putInt("forward_udp_port", newPort); + editor.apply(); + + Toast.makeText(this, "Forwarding settings saved: " + newIp + ":" + newPort, Toast.LENGTH_SHORT).show(); + updateUdpForwardingState(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private void updateUdpForwardingState() { + SharedPreferences prefs = getSharedPreferences("general", MODE_PRIVATE); + boolean enabled = prefs.getBoolean("forward_udp_enabled", false); + String ip = prefs.getString("forward_udp_ip", "192.168.1.100"); + int port = prefs.getInt("forward_udp_port", 5600); + + if (videoPlayer != null) { + videoPlayer.setUdpForwarding(ip, port, enabled); + } + } + /** * Submenu for help items, such as sending logs. */ @@ -1358,6 +1443,7 @@ protected void onResume() { wfbLinkManager.startAdapters(); videoPlayer.start(); + updateUdpForwardingState(); videoPlayer.startAudio(); osdManager.restoreOSDConfig(); diff --git a/app/videonative/src/main/cpp/UdpReceiver.cpp b/app/videonative/src/main/cpp/UdpReceiver.cpp index 3c2e071a..1fc6d17f 100644 --- a/app/videonative/src/main/cpp/UdpReceiver.cpp +++ b/app/videonative/src/main/cpp/UdpReceiver.cpp @@ -131,6 +131,16 @@ void UDPReceiver::receiveFromUDPLoop() // ssize_t message_length = recv(mSocket, buff, (size_t) mBuffsize, MSG_WAITALL); if (message_length > 0) { // else -1 was returned;timeout/No data received + // 1. Forward packet first (minimize latency) + { + std::lock_guard lock(mForwardMutex); + if (mForwardEnabled) + { + sendto(mSocket, buff->data(), message_length, 0, (struct sockaddr*) &mDestAddr, sizeof(mDestAddr)); + } + } + + // 2. Local processing onDataReceivedCallback(buff->data(), (size_t) message_length); nReceivedBytes += message_length; @@ -162,3 +172,19 @@ int UDPReceiver::getPort() const { return mPort; } + +void UDPReceiver::setForwarding(const std::string& ip, int port, bool enabled) +{ + std::lock_guard lock(mForwardMutex); + mForwardIP = ip; + mForwardPort = port; + mForwardEnabled = enabled; + + memset(&mDestAddr, 0, sizeof(mDestAddr)); + mDestAddr.sin_family = AF_INET; + mDestAddr.sin_port = htons(port); + if (inet_pton(AF_INET, ip.c_str(), &mDestAddr.sin_addr) <= 0) + { + mForwardEnabled = false; + } +} diff --git a/app/videonative/src/main/cpp/UdpReceiver.h b/app/videonative/src/main/cpp/UdpReceiver.h index 04627a06..b76a93da 100644 --- a/app/videonative/src/main/cpp/UdpReceiver.h +++ b/app/videonative/src/main/cpp/UdpReceiver.h @@ -13,6 +13,7 @@ #include #include #include +#include // Starts a new thread that continuously checks for new data on UDP port class UDPReceiver @@ -64,6 +65,8 @@ class UDPReceiver int getPort() const; + void setForwarding(const std::string& ip, int port, bool enabled); + private: void receiveFromUDPLoop(); @@ -84,6 +87,12 @@ class UDPReceiver // 65,507 bytes (65,535 − 8 byte UDP header − 20 byte IP header). static constexpr const size_t UDP_PACKET_MAX_SIZE = 65507; JavaVM* javaVm; + + std::mutex mForwardMutex; + std::string mForwardIP = ""; + int mForwardPort = 0; + bool mForwardEnabled = false; + struct sockaddr_in mDestAddr; }; #endif // FPVUE_UDPRECEIVER_H diff --git a/app/videonative/src/main/cpp/VideoPlayer.cpp b/app/videonative/src/main/cpp/VideoPlayer.cpp index 6c2f4bf8..6175d585 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.cpp +++ b/app/videonative/src/main/cpp/VideoPlayer.cpp @@ -172,6 +172,7 @@ void VideoPlayer::start(JNIEnv* env, jobject androidContext) -16, [this](const uint8_t* data, size_t data_length) { onNewRTPData(data, data_length); }, WANTED_UDP_RCVBUF_SIZE); + mUDPReceiver->setForwarding(mForwardIP, mForwardPort, mForwardEnabled); mUDPReceiver->startReceiving(); mUDSReceiver.release(); @@ -250,6 +251,17 @@ void VideoPlayer::stopDvr() stopProcessing(); } +void VideoPlayer::setForwarding(const std::string& ip, int port, bool enabled) +{ + mForwardIP = ip; + mForwardPort = port; + mForwardEnabled = enabled; + if (mUDPReceiver) + { + mUDPReceiver->setForwarding(ip, port, enabled); + } +} + //----------------------------------------------------JAVA // bindings--------------------------------------------------------------- #define JNI_METHOD(return_type, method_name) \ @@ -293,6 +305,19 @@ extern "C" native(videoPlayerN)->stop(env, androidContext); } + JNI_METHOD(void, nativeSetUdpForwarding) + (JNIEnv* env, jclass jclass1, jlong nativeInstance, jstring ipStr, jint port, jboolean enabled) + { + VideoPlayer* p = native(nativeInstance); + if (p) + { + const char* ip = env->GetStringUTFChars(ipStr, nullptr); + std::string ip_cpp(ip); + env->ReleaseStringUTFChars(ipStr, ip); + p->setForwarding(ip_cpp, port, enabled); + } + } + JNI_METHOD(void, nativeSetVideoSurface) (JNIEnv* env, jclass jclass1, jlong videoPlayerN, jobject surface, jint index) { diff --git a/app/videonative/src/main/cpp/VideoPlayer.h b/app/videonative/src/main/cpp/VideoPlayer.h index db450278..830d423e 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.h +++ b/app/videonative/src/main/cpp/VideoPlayer.h @@ -52,6 +52,8 @@ class VideoPlayer bool isRecording() { return (get_time_ms() - last_dvr_write) <= 500; } + void setForwarding(const std::string& ip, int port, bool enabled); + private: void onNewNALU(const NALU& nalu); @@ -112,6 +114,10 @@ class VideoPlayer void processQueue(); + std::string mForwardIP = ""; + int mForwardPort = 0; + bool mForwardEnabled = false; + public: AudioDecoder audioDecoder; VideoDecoder videoDecoder; diff --git a/app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java b/app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java index 54190856..d375921c 100644 --- a/app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java +++ b/app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java @@ -50,6 +50,8 @@ public VideoPlayer(final AppCompatActivity parent) { public static native void nativeSetVideoSurface(long nativeInstance, Surface surface, int index); + public static native void nativeSetUdpForwarding(long nativeInstance, String ip, int port, boolean enabled); + public static native void nativeStartDvr(long nativeInstance, int fd, int fmp4_enabled); public static native void nativeStopDvr(long nativeInstance); @@ -124,6 +126,11 @@ public boolean isRunning() { return timer != null; } + public void setUdpForwarding(String ip, int port, boolean enabled) { + verifyApplicationThread(); + nativeSetUdpForwarding(nativeVideoPlayer, ip, port, enabled); + } + public void startDvr(int fd, boolean enabled_fmp4) { nativeStartDvr(nativeVideoPlayer, fd, enabled_fmp4 ? 1 : 0); } diff --git a/test_APK/app-debug.apk b/test_APK/app-debug.apk new file mode 100644 index 00000000..939e0a6f Binary files /dev/null and b/test_APK/app-debug.apk differ