Skip to content

Add Ground Station Streaming: hand video/telemetry ports to an external app - #125

Open
mengelh wants to merge 1 commit into
OpenIPC:masterfrom
mengelh:ground-station-streaming
Open

Add Ground Station Streaming: hand video/telemetry ports to an external app#125
mengelh wants to merge 1 commit into
OpenIPC:masterfrom
mengelh:ground-station-streaming

Conversation

@mengelh

@mengelh mengelh commented Sep 9, 2026

Copy link
Copy Markdown

PixelPilot's own WfbngLink::initAgg() already sends decrypted video
(RTP/H264) and MAVLink to 127.0.0.1:5600/14550 unconditionally via
wfb-ng's AggregatorUDPv4 -- but the app's own in-app video player and
MAVLink OSD parser also bind those same ports, so nothing else on the
device can. This adds a way to free them up for any other UDP-based
ground station app (QGroundControl, Mission Planner, a custom OSD, ...)
running on the same device, without touching devourer/wfb-ng at all.

Two independent settings-menu toggles under "Ground Station Streaming",
"Video" and "Telemetry" (a ground station app might only care about
one channel -- e.g. a video-only OSD box, or a telemetry-only
companion computer):

  • Video: stops/starts the in-app UDPReceiver + decoder on port 5600.
  • Telemetry: stops/starts the in-app MAVLink OSD parser on port 14550.

Both apply immediately (no restart) by starting/stopping the relevant
in-app consumer right away, and persist via SharedPreferences so they
survive a restart.

onPause()/onStop() now skip wfbLinkManager.stopAdapters() while either
channel is streaming, since the whole point is for wfb-ng to keep
forwarding while this Activity is backgrounded (e.g. the ground station
app in the foreground instead) -- both channels share one USB/wfb-ng
adapter, so it can't be paused for one channel while kept alive for the
other. This only covers ordinary Activity lifecycle transitions, not
memory-pressure eviction; a Foreground Service would be needed for
guaranteed long-running background survival, which is out of scope
here.

Verified on-device (RTL8812AU, Galaxy Tab S7 FE) with QGroundControl:
both video and telemetry arrive correctly with their respective toggle
enabled; each toggle independently frees/rebinds only its own port;
the USB/wfb-ng pipeline is never restarted by toggling or by
backgrounding while streaming is active; background/foreground cycling
with only one channel enabled still keeps the pipeline alive.

🤖 Generated with Claude Code

…al app

PixelPilot's own WfbngLink::initAgg() already sends decrypted video
(RTP/H264) and MAVLink to 127.0.0.1:5600/14550 unconditionally via
wfb-ng's AggregatorUDPv4 -- but the app's own in-app video player and
MAVLink OSD parser also bind those same ports, so nothing else on the
device can. This adds a way to free them up for any other UDP-based
ground station app (QGroundControl, Mission Planner, a custom OSD, ...)
running on the same device, without touching devourer/wfb-ng at all.

Two independent settings-menu toggles under "Ground Station Streaming",
"Video" and "Telemetry" (a ground station app might only care about
one channel -- e.g. a video-only OSD box, or a telemetry-only
companion computer):
- Video: stops/starts the in-app UDPReceiver + decoder on port 5600.
- Telemetry: stops/starts the in-app MAVLink OSD parser on port 14550.

Both apply immediately (no restart) by starting/stopping the relevant
in-app consumer right away, and persist via SharedPreferences so they
survive a restart.

onPause()/onStop() now skip wfbLinkManager.stopAdapters() while either
channel is streaming, since the whole point is for wfb-ng to keep
forwarding while this Activity is backgrounded (e.g. the ground station
app in the foreground instead) -- both channels share one USB/wfb-ng
adapter, so it can't be paused for one channel while kept alive for the
other. This only covers ordinary Activity lifecycle transitions, not
memory-pressure eviction; a Foreground Service would be needed for
guaranteed long-running background survival, which is out of scope
here.

Verified on-device (RTL8812AU, Galaxy Tab S7 FE) with QGroundControl:
both video and telemetry arrive correctly with their respective toggle
enabled; each toggle independently frees/rebinds only its own port;
the USB/wfb-ng pipeline is never restarted by toggling or by
backgrounding while streaming is active; background/foreground cycling
with only one channel enabled still keeps the pipeline alive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add ground station video and telemetry streaming controls

✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds independent persisted toggles to release video and telemetry UDP ports to ground stations.
• Applies consumer changes immediately while keeping shared wfb-ng adapters active during
 backgrounding.
Diagram

graph TD
  MENU["Streaming Toggles"] --> PREFS[("Shared Preferences")] --> ACTIVITY["Video Activity"]
  ACTIVITY --> VIDEO["Video Player"]
  ACTIVITY --> MAVLINK["MAVLink Parser"]
  ACTIVITY --> WFB["wfb-ng Pipeline"] --> PORTS["UDP Ports"] --> STATION["Ground Station"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move streaming ownership into a foreground service
  • ➕ Provides reliable background operation under activity destruction and memory pressure.
  • ➕ Separates adapter and stream lifecycles from the user interface.
  • ➕ Offers an explicit Android-visible long-running operation.
  • ➖ Requires broader service, notification, binding, and cleanup changes.
  • ➖ Substantially increases implementation and review scope.
  • ➖ Needs careful migration of shared native adapter ownership.

Recommendation: The current activity-level approach is appropriate for an incremental port-handoff feature because it reuses existing wfb-ng forwarding and avoids native pipeline changes. A foreground service should be considered as a follow-up if guaranteed long-running background streaming is required.

Files changed (1) +129 / -5

Enhancement (1) +129 / -5
VideoActivity.javaAdd persisted ground-station stream handoff controls +129/-5

Add persisted ground-station stream handoff controls

• Adds independent video and telemetry menu toggles that immediately stop or restart the corresponding in-app UDP consumers and persist their state. Lifecycle handling now keeps the shared USB/wfb-ng adapter active while either external stream is enabled and avoids rebinding the video or telemetry ports on startup and resume.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Telemetry never returns in-app 🐞 Bug ≡ Correctness
Description
setTelemetryStreamingEnabled() calls nativeStop() and later nativeStart(), but the native stop
signal is only incremented and is never reset before a replacement listener starts. Every
enable-then-disable cycle therefore launches a listener that exits immediately, while a rapid cycle
can additionally bind before the old socket releases port 14550.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1239-1243]

+            MavlinkNative.nativeStop(this);
+            handler.removeCallbacks(runnable);
+        } else {
+            MavlinkNative.nativeStart(this);
+            handler.post(runnable);
Evidence
The toggle directly introduces a stop-then-start lifecycle, while the native implementation uses a
persistent global stop signal and detached thread. The receive loop only runs while that signal is
zero, and stopping does not close or join the listener before another thread attempts to bind the
same port.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
app/mavlink/src/main/cpp/mavlink.cpp[57-58]
app/mavlink/src/main/cpp/mavlink.cpp[78-95]
app/mavlink/src/main/cpp/mavlink.cpp[377-388]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Make the native MAVLink listener safely reusable so disabling ground-station telemetry reliably restores the in-app telemetry display.

## Issue Context
The new toggle stops and restarts the listener, but `nativeStop()` permanently increments a process-global signal and does not wait for the existing socket thread to finish. `nativeStart()` neither resets that signal nor synchronizes against the previous listener.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[57-58]
- app/mavlink/src/main/cpp/mavlink.cpp[377-388]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Exiting leaves the radio pipeline running 🐞 Bug ☼ Reliability
Description
The new lifecycle conditions skip stopAdapters() whenever either persisted streaming preference is
enabled, but the activity has no destruction cleanup for the retained native link, USB connection,
thread, or callback timer. Finishing or recreating the activity therefore leaves its old pipeline
holding the adapter and activity context, and reopening constructs another link that attempts to use
the same statically tracked device.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1660-1662]

+        if (!isGroundStationStreamingActive()) {
+            wfbLinkManager.stopAdapters();
+        }
Evidence
Both changed lifecycle branches can retain the adapter after the activity is destroyed, and no
onDestroy() cleanup exists. Adapter membership is static, whereas native links, worker maps, USB
connections, timers, and callbacks belong to individual activity-created WfbNgLink instances, so a
later activity cannot safely adopt the retained instance.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1641-1683]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[367-373]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[20-25]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[175-185]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[33-62]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[95-134]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Preserve streaming during temporary background transitions without orphaning the USB and native pipeline when the activity is actually destroyed.

## Issue Context
The activity now skips adapter shutdown in both pause and stop, while `WfbNgLink` owns per-instance native state, threads, USB connections, and a timer retaining the activity. Add explicit terminal cleanup or move ownership into a lifecycle-appropriate service; distinguish ordinary backgrounding from finishing or recreation.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1660-1662]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1676-1680]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[53-67]
- app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[209-233]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This changes lifecycle handling, UDP consumers, native MAVLink control, persisted settings, and shared adapter behavior, creating meaningful cross-path and background-execution risks despite being confined to one file.

Grey Divider

Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +1239 to +1243
MavlinkNative.nativeStop(this);
handler.removeCallbacks(runnable);
} else {
MavlinkNative.nativeStart(this);
handler.post(runnable);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Telemetry never returns in-app 🐞 Bug ≡ Correctness

setTelemetryStreamingEnabled() calls nativeStop() and later nativeStart(), but the native stop
signal is only incremented and is never reset before a replacement listener starts. Every
enable-then-disable cycle therefore launches a listener that exits immediately, while a rapid cycle
can additionally bind before the old socket releases port 14550.
Agent Prompt
## Issue description
Make the native MAVLink listener safely reusable so disabling ground-station telemetry reliably restores the in-app telemetry display.

## Issue Context
The new toggle stops and restarts the listener, but `nativeStop()` permanently increments a process-global signal and does not wait for the existing socket thread to finish. `nativeStart()` neither resets that signal nor synchronizes against the previous listener.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[57-58]
- app/mavlink/src/main/cpp/mavlink.cpp[377-388]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1660 to +1662
if (!isGroundStationStreamingActive()) {
wfbLinkManager.stopAdapters();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Exiting leaves the radio pipeline running 🐞 Bug ☼ Reliability

The new lifecycle conditions skip stopAdapters() whenever either persisted streaming preference is
enabled, but the activity has no destruction cleanup for the retained native link, USB connection,
thread, or callback timer. Finishing or recreating the activity therefore leaves its old pipeline
holding the adapter and activity context, and reopening constructs another link that attempts to use
the same statically tracked device.
Agent Prompt
## Issue description
Preserve streaming during temporary background transitions without orphaning the USB and native pipeline when the activity is actually destroyed.

## Issue Context
The activity now skips adapter shutdown in both pause and stop, while `WfbNgLink` owns per-instance native state, threads, USB connections, and a timer retaining the activity. Add explicit terminal cleanup or move ownership into a lifecycle-appropriate service; distinguish ordinary backgrounding from finishing or recreation.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1660-1662]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1676-1680]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[53-67]
- app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[209-233]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

1 participant