Skip to content

feat: toy drones — FLOW-UFO, KY UFO RTSP and Jieli (#187) - #215

Merged
compot-dev merged 27 commits into
v2from
feat/flowufo-187
Oct 16, 2026
Merged

compot-dev merged 27 commits into
v2from
feat/flowufo-187

Conversation

@compot-dev

@compot-dev compot-dev commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #187, closes #200. Version 2.15.

Tested by the reporter with test builds 1–5 (D16, K200, YL3); on Aceline-1 the grid and single view play as before.

openipc-ai and others added 15 commits October 15, 2026 14:50
A flowufo:// slot (flowufo://192.168.169.1:8800, host and port optional)
plays the video of Wi-Fi toy drones with a FLOW-UFO-xxxxxx access point,
in single view and in grid cells like an http:// MJPEG slot. Video only:
no flight commands are sent.

The client probes the drone until fragments arrive, collects the JPEG
fragments of the last four frames, acknowledges them every 25 ms (masks
of missing fragments, whole-frame requests for lagging slots and for
the next frame after a pause) and probes again after 3 s of silence.
The frames carry only the scan data: the headers are generated (IJG
tables at the frame's quality, baseline 4:4:4, standard Huffman tables).
Not yet checked on a drone: the log shows the raw header bytes of the
first fragments and the start of the first frame, /stats.json the
protocol counters under "flowufo".

The socket is bound to the network whose subnet holds the drone, so
that a Wi-Fi without internet is used even with mobile data on
(ACCESS_NETWORK_STATE). Version 2.10 (35).
A single fragment far behind no longer resets the window: only a run of
them counts as a drone restart. Stale fragments and duplicates do not
extend the quiet time; frames at or below the base do not hold up the
request for the next frame; a short fragment inside the body stays
missing, a short last one ends the body. Send errors reopen the socket
after 3 s by time and probe again from the new port. A cell's toy
status is cleared with every status change. Refs #187.
…lots, dqt switch) and the KY UFO 7099 heartbeat (#187)

Compared with marshallrichards/turbodrone (Apache-2.0, wifi_uav and cooingdv backends;
no code taken over, credited in docs/toys.md and the comments):

- ACK quality parameters 32 4B 14 2D by default; ?qpara= (or ?q=) with 8 hex digits
- bytes 0x34/0x35 are main/flow camera status: shown in the stats, no family warning
- slots 5 or more frames behind are left out of the ACK instead of a seq+4 request
- port 8800 (default) probes 8801 too, one socket per port; locks onto the socket the
  first 0x93 packet arrives on (source address only, the drone sends from port 1234);
  BL618 timing on 8801: probes every 200 ms, ACKs every 50 ms only with a record
- ?dqt=zigzag|raster|q50 for the JPEG quantisation tables, shown in the stats
- no flight command in the ACK, with the reason in a comment
- RTSP: 01 01 to UDP 7099 every second for rtsp://<host>:7070/webcam, or with
  ?heartbeat=<port> (off with ?heartbeat=off; the option is not sent to the camera);
  heartbeat object in /stats.json
- subnet hint in the status when a flowufo:// slot targets the phone itself or another
  toy family's network
- docs/toys.md: families by Wi-Fi name, the new options, KY UFO RTSP, credits (#196)
jieli://192.168.80.1 for toys with an HF_xxxxxxxx Wi-Fi (Jieli AC79, vendor app
DVRunning 2), after the PCAPdroid capture in #196 and turbodrone's research notes
(Apache-2.0, behaviour reference only, no code taken over):

- CTP on TCP 3333 (CTP: + u16 topic length + topic + u32 JSON length + JSON), a reader
  that reassembles split and joined messages and skips what is not CTP
- APP_ACCESS, DATE_TIME, CTP_KEEP_ALIVE every min(timeout / 4, 7.5 s), OPEN_RT_STREAM
  with the size of VIDEO_PARAM (else 640x480 at 25), CLOSE_RT_STREAM on stop
- video expected on the phone's UDP 6666 (unverified): PT 26 into the RTP/JPEG path,
  PT 96 into the H.264 path, whole JPEG datagrams too; first packets logged with
  sender, PT and bytes; no video for 5 s: said in the status, UDP 1234 and 2224 opened
  for the log only, OPEN_RT_STREAM again every 10 s
- options w, h, fps, rtp, format; sockets bound to the toy's network (TCP from Android 6)
- jieli object in /stats.json; subnet hints both ways between flowufo:// and jieli://
- never FLIGHT_CONTROL, nothing to UDP 3456
- docs/toys.md: HF / Jieli section, credits; unit tests with a fake toy on loopback
…silence reset, network binding from Android 6 (Qodo on #188)
… video (Qodo on #198)

- a KEEP_ALIVE_INTERVAL announced after the first keepalive applies at once: the next
  one is due interval ms after the last one sent, not on the old schedule; test with
  the announcement after the first keepalive
- the quiet status from the CTP thread and "connected" from the UDP thread are
  serialised, and a quiet notice is dropped when video arrived meanwhile
feat: Jieli HF toy drones over CTP (#187)
…aries, so chatty graphics logs no longer push them out (#187)
…r-clockwise, ?rotate= to override (#200)

The K200's camera is mounted sideways. For :7070/webcam slots the software
MJPEG path draws the frames turned 90° CCW and reports the turned size, so the
view fits a landscape picture; ?rotate=0|90|180|270 overrides it for any RTSP
MJPEG slot and is not sent to the camera. H.264/H.265 are not turned (#201).
/stats.json gets rotate; screenshot and debug snapshot copy the turned surface.
@compot-dev compot-dev added the awaiting device test PR ready, merge only after testing on a real device label Oct 15, 2026
…en startup fails; Jieli: only usable video counts as receiving, a stopped slot cannot hang on a blocked CTP write, diagnostic ports freed after 30 s, topic stats bounded, a dropout after the stream request reconnects without back-off
@compot-dev

Copy link
Copy Markdown
Collaborator Author

Open Qodo findings from #188 and #198 (both on code that is now in this branch) are fixed in 3f4ae3e:

Finding Fix
#188 3. Failed startup leaks drone sockets FlowUfo.Client.open() closes a socket whose setup throws; a failure while opening the probe sockets closes those already open
#188 4. Stopping a toy can leave its session stuck Jieli.Client.close() gives the session 1 s to send CLOSE_RT_STREAM and finish, then closes the connected TCP socket itself, which releases a blocked write
#198 1. Bad packets keep the toy marked as live Jieli.Listener.onRtp/onJpeg return whether the packet or frame was usable; only usable video refreshes the "receiving" time, so a toy sending broken packets is asked for the stream again (test unusableVideoIsNotReceiving)
#198 2. Silent toy slots block other UDP streams A diagnostic port (now only 1234: 2224 became a video port) listens for 30 s or 20 packets, then is freed; the counts stay in the stats (checked in noVideoListensElsewhereAndAsksAgain)
#198 3. Toy messages can exhaust phone memory Up to 64 distinct CTP topics are counted one by one, the rest as other_topics
#198 4. Toy reconnects wait up to 30 s after a dropout A session that got as far as OPEN_RT_STREAM (streamRequested) and then loses the toy ends like a PLAY that worked: the back-off is reset

The rest of this PR waits for the YL3 raw frames (test build 4) in #187.

…, not the whole datagram; jieli:// turns 90° by default; raw frames outlive the session (#187)
@compot-dev
compot-dev marked this pull request as ready for review October 16, 2026 16:00
@compot-dev compot-dev changed the title feat: FLOW-UFO toy drones over UDP (#187) feat: toy drones — FLOW-UFO, KY UFO RTSP and Jieli (#187) Oct 16, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add video support for FLOW-UFO, Jieli, and KY UFO toys

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Play FLOW-UFO and Jieli toy-drone video in single view and grid cells without flight controls.
• Add KY UFO RTSP heartbeats and MJPEG rotation for sideways cameras.
• Add network-aware routing, diagnostics, protocol tests, and setup guidance; Jieli picture quality
 remains experimental.
Diagram

graph TD
  S["Slot views"] --> R["RtspSession"] --> F["FLOW-UFO client"] --> J["JPEG renderer"]
  R --> L["Jieli client"] --> J
  R --> H["RTSP heartbeat"]
  F --> N["Network binding"]
  L --> N
  R --> D["Debug stats"]
Loading
High-Level Assessment

Keep the protocol-specific clients behind the existing session and playback paths: FLOW-UFO, Jieli, and RTSP have different handshakes but can reuse established JPEG/RTP handling. A separate transport framework would add migration risk without a clear benefit here. Prioritize physical-device checks of the remaining Jieli picture-quality uncertainty.

Files changed (25) +5970 / -178

Enhancement (11) +3433 / -12
DebugServer.javaExpose raw Jieli frames for diagnosis +17/-0

Expose raw Jieli frames for diagnosis

• Adds endpoints for the most recent assembled Jieli JPEG and dropped-frame bytes, including after a session ends.

android/app/src/main/java/com/openipc/decoder/DebugServer.java

Decoder.javaSurface toy status, diagnostics, and rotated playback +48/-1

Surface toy status, diagnostics, and rotated playback

• Displays connection details, labels the new transports, and publishes toy and heartbeat statistics. Passes MJPEG rotation to the renderer and drops queued older JPEGs when drawing falls behind.

android/app/src/main/java/com/openipc/decoder/Decoder.java

FlowUfo.javaImplement the FLOW-UFO UDP video client +1234/-0

Implement the FLOW-UFO UDP video client

• Adds URL options, dual-port probing, fragment-window acknowledgements, silence recovery, and JPEG reconstruction. Records protocol diagnostics and offers subnet hints without sending flight commands.

android/app/src/main/java/com/openipc/decoder/FlowUfo.java

Jieli.javaImplement the Jieli CTP and UDP video client +1412/-0

Implement the Jieli CTP and UDP video client

• Adds CTP stream negotiation and keepalives, RTP video reception, and assembly of the toy's separate UDP 2224 JPEG format. Includes retries, bounded diagnostics, raw-frame capture, and status reporting without flight controls.

android/app/src/main/java/com/openipc/decoder/Jieli.java

JpegAssembler.javaSupport FLOW-UFO JPEG quantization and sampling +20/-2

Support FLOW-UFO JPEG quantization and sampling

• Adds IJG quality scaling through quality 100 and configurable luma sampling for synthesized JPEG headers. Preserves the existing RTP/JPEG header behavior.

android/app/src/main/java/com/openipc/decoder/JpegAssembler.java

JpegRenderer.javaRotate MJPEG during software rendering +53/-7

Rotate MJPEG during software rendering

• Adds quarter- and half-turn drawing, reports the displayed dimensions, and bypasses hardware JPEG rendering when rotation is required.

android/app/src/main/java/com/openipc/decoder/JpegRenderer.java

NetInfo.javaBind toy sockets to the matching network +87/-0

Bind toy sockets to the matching network

• Finds the Android network whose address prefix contains the toy and binds TCP or UDP sockets to it. Falls back to default routing when binding is unavailable.

android/app/src/main/java/com/openipc/decoder/NetInfo.java

QuadCell.javaAdd toy status and diagnostics to grid cells +24/-1

Add toy status and diagnostics to grid cells

• Shows detailed connection text while a toy cell waits, exposes protocol and heartbeat statistics, and applies MJPEG rotation in grid playback.

android/app/src/main/java/com/openipc/decoder/QuadCell.java

RtspClient.javaForward toy details through the single-view client +18/-0

Forward toy details through the single-view client

• Forwards connection details and exposes session statistics, Jieli raw frames, and MJPEG rotation to the single-view controller.

android/app/src/main/java/com/openipc/decoder/RtspClient.java

RtspSession.javaWire toy clients and KY UFO behavior into sessions +384/-1

Wire toy clients and KY UFO behavior into sessions

• Routes flowufo:// and jieli:// slots through their clients while reusing the existing video callbacks and managing their lifecycles. Adds RTSP heartbeat and MJPEG rotation options, compatible-toy defaults, status details, and statistics.

android/app/src/main/java/com/openipc/decoder/RtspSession.java

UdpHeartbeat.javaKeep compatible KY UFO RTSP streams active +136/-0

Keep compatible KY UFO RTSP streams active

• Sends the limited 01 01 UDP heartbeat on a schedule for the life of a compatible RTSP connection and exposes send statistics.

android/app/src/main/java/com/openipc/decoder/UdpHeartbeat.java

Bug fix (1) +4 / -0
StreamProbes.javaAvoid competing toy-drone stream checks +4/-0

Avoid competing toy-drone stream checks

• Skips background single-view checks for FLOW-UFO and Jieli slots because a probe could take video from the active viewer.

android/app/src/main/java/com/openipc/decoder/StreamProbes.java

Tests (5) +2282 / -0
FlowUfoTest.javaExercise FLOW-UFO protocol and playback +1174/-0

Exercise FLOW-UFO protocol and playback

• Tests URL options, fragment windows, acknowledgement bytes, JPEG headers, subnet hints, and dual-port selection. A loopback drone test covers recovery and JPEG output; reflective ImageIO checks decoded pictures.

android/app/src/test/java/com/openipc/decoder/FlowUfoTest.java

JieliTest.javaExercise Jieli sessions against a simulated toy +833/-0

Exercise Jieli sessions against a simulated toy

• Tests CTP framing and commands, URL validation, JPEG piece assembly, keepalives, stream requests, retry behavior, and RTP/JPEG playback on loopback.

android/app/src/test/java/com/openipc/decoder/JieliTest.java

RotateTest.javaVerify MJPEG rotation options and geometry +104/-0

Verify MJPEG rotation options and geometry

• Checks compatible-toy rotation defaults, URL overrides, displayed dimensions, and corner mappings for supported turns.

android/app/src/test/java/com/openipc/decoder/RotateTest.java

RtspSessionTest.javaTest heartbeat lifecycle in an RTSP session +45/-0

Test heartbeat lifecycle in an RTSP session

• Verifies heartbeat packets are sent during a configured session, stop with the slot, and remain absent for ordinary cameras.

android/app/src/test/java/com/openipc/decoder/RtspSessionTest.java

UdpHeartbeatTest.javaTest heartbeat selection and scheduling +126/-0

Test heartbeat selection and scheduling

• Checks RTSP URL defaults and overrides, packet payload and cadence, shutdown, and statistics.

android/app/src/test/java/com/openipc/decoder/UdpHeartbeatTest.java

Documentation (6) +247 / -164
README.mdList the newly supported toy families +1/-1

List the newly supported toy families

• Expands the toys documentation link to name FLOW-UFO, KY UFO, and Jieli drones.

README.md

README.ru.mdExpand the Russian toys documentation link +1/-1

Expand the Russian toys documentation link

• Names FLOW-UFO, KY UFO, and Jieli drones in the Russian README index.

README.ru.md

README.zh-CN.mdExpand the Chinese toys documentation link +1/-1

Expand the Chinese toys documentation link

• Names FLOW-UFO, KY UFO, and Jieli drones in the Chinese README index.

README.zh-CN.md

debug.mdDocument toy diagnostics and raw-frame endpoints +101/-100

Document toy diagnostics and raw-frame endpoints

• Documents Jieli frame downloads and the new flowufo, jieli, heartbeat, and rotate fields in debug output. Also reformats existing reference tables.

docs/debug.md

toys.mdGuide setup and troubleshooting for three toy families +104/-26

Guide setup and troubleshooting for three toy families

• Adds Wi-Fi-family identification, FLOW-UFO options and verified behavior, KY UFO heartbeat and rotation guidance, and experimental Jieli setup and diagnostics. Updates the tested-toys status.

docs/toys.md

transports.mdDocument toy transport URLs and options +39/-35

Document toy transport URLs and options

• Describes released FLOW-UFO support, experimental Jieli playback, and KY UFO RTSP heartbeat and rotation options. Reformats several existing transport tables.

docs/transports.md

Other (2) +4 / -2
build.gradleSet the app version to 2.15 +2/-2

Set the app version to 2.15

• Raises versionCode from 38 to 40 and versionName from 2.13 to 2.15.

android/app/build.gradle

AndroidManifest.xmlPermit inspection of available Android networks +2/-0

Permit inspection of available Android networks

• Adds ACCESS_NETWORK_STATE so toy sockets can be bound to a Wi-Fi network even when it has no internet.

android/app/src/main/AndroidManifest.xml

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

qodo-free-for-open-source-projects Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Rotated streams skip hardware decoding 📎 Requirement gap ➹ Performance
Description
JpegRenderer.render skips the hardware JPEG decoder whenever rotate is nonzero and always
decodes those frames through the bitmap path. Any MJPEG URL using 90, 180, or 270 degree rotation
therefore consumes the slower software decoder even when a suitable hardware decoder is available,
including the rotated camera path introduced here.
Code

android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[R50-54]

+    int[] render(Frame frame, Surface s, int rotate, JpegCodec.Current current) {
      if (s == null || !s.isValid() || !current.holds()) return null;
-        // The hardware JPEG decoder first, where there is one (#132)
-        if (!hwFailed && !drawn && JpegCodec.candidate() != null) {
+        // The hardware JPEG decoder first, where there is one (#132); a turned frame is drawn
+        // by the app, which turns it while drawing (#200)
+        if (rotate == 0 && !hwFailed && !drawn && JpegCodec.candidate() != null) {
Evidence
Compliance rule 6 requires using a suitable hardware JPEG decoder when available and falling back to
bitmap decoding only when hardware decoding is unavailable or cannot be configured. The changed
condition explicitly prevents hardware decoding for every nonzero rotation, regardless of decoder
availability.

Use hardware MJPEG decoding when available
android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[50-54]

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

## Issue description
Rotated MJPEG streams bypass the available hardware JPEG decoder because `JpegRenderer.render` requires `rotate == 0` before attempting hardware decoding. This violates the hardware-decoding requirement for suitable decoders and forces rotated streams through the slower bitmap path.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[50-54]
- android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[90-108]
## Recommended Fix
Keep hardware JPEG decoding enabled for rotated streams when a suitable decoder is available, applying the rotation through the output surface or display transform. Use the bitmap renderer only when hardware decoder configuration fails or no suitable decoder exists.

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



Remediation recommended

2. Repeated packets corrupt toy video ✓ Resolved
Description
RtStream.feed() adds every piece's length to filled, even when its offset overlaps bytes already
received. A duplicated UDP piece can therefore make an incomplete frame appear complete; the
assembler then validates or forwards the incomplete JPEG and resets before the missing piece
arrives.
Code

android/app/src/main/java/com/openipc/decoder/Jieli.java[R649-651]

+            System.arraycopy(b, off + HEADER, frame, at, piece);
+            filled += piece;
+            if (filled < size) {
Evidence
The packet format supplies a piece offset, but the copy and count do not check whether that range
was already filled. Completion and reset depend on the resulting count, and received datagrams are
passed directly to this assembler.

android/app/src/main/java/com/openipc/decoder/Jieli.java[586-592]
android/app/src/main/java/com/openipc/decoder/Jieli.java[639-669]
android/app/src/main/java/com/openipc/decoder/Jieli.java[1243-1274]

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

## Issue description
Repeated or overlapping UDP pieces inflate the received-byte count, allowing an incomplete JPEG to be finalized.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Jieli.java[639-661]
- android/app/src/test/java/com/openipc/decoder/JieliTest.java[135-151]
## Recommended Fix
Track received offset ranges or bytes for each frame and advance completion only for newly covered data. Add tests with duplicated and overlapping pieces that verify no frame is emitted until every offset is present.

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


3. Reordered packets lose toy frames ✓ Resolved
Description
RtStream.feed() drops and resets an incomplete frame as soon as a piece marked LAST_PIECE
arrives. If UDP delivers that piece before an earlier piece, the earlier piece cannot complete the
frame when it subsequently arrives, even though all its data was delivered.
Code

android/app/src/main/java/com/openipc/decoder/Jieli.java[R651-655]

+            if (filled < size) {
+                if (last) {
+                    drop("incomplete: " + filled + " of " + size + " bytes at the last piece",
+                            filled);
+                    size = -1;
Evidence
An incomplete assembly is reset on the last-piece flag. Later noninitial pieces require the active
frame size to match, so they are ignored after that reset.

android/app/src/main/java/com/openipc/decoder/Jieli.java[630-657]
android/app/src/main/java/com/openipc/decoder/Jieli.java[1243-1267]

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

## Issue description
An out-of-order last UDP piece immediately discards a frame whose missing pieces may still arrive.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Jieli.java[639-669]
- android/app/src/test/java/com/openipc/decoder/JieliTest.java[135-151]
## Recommended Fix
Record that the last piece has arrived, but retain the assembly until all offsets are covered or a bounded expiry or newer frame supersedes it. Test delivery of the last piece before a middle piece.

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


Grey Divider

Tip of the day
💡 Did you know, you can pick a display mode instead of tweaking every Display option by hand

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +50 to +54
int[] render(Frame frame, Surface s, int rotate, JpegCodec.Current current) {
if (s == null || !s.isValid() || !current.holds()) return null;
// The hardware JPEG decoder first, where there is one (#132)
if (!hwFailed && !drawn && JpegCodec.candidate() != null) {
// The hardware JPEG decoder first, where there is one (#132); a turned frame is drawn
// by the app, which turns it while drawing (#200)
if (rotate == 0 && !hwFailed && !drawn && JpegCodec.candidate() != null) {

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. Rotated streams skip hardware decoding 📎 Requirement gap ➹ Performance

JpegRenderer.render skips the hardware JPEG decoder whenever rotate is nonzero and always
decodes those frames through the bitmap path. Any MJPEG URL using 90, 180, or 270 degree rotation
therefore consumes the slower software decoder even when a suitable hardware decoder is available,
including the rotated camera path introduced here.
Agent Prompt
## Issue description
Rotated MJPEG streams bypass the available hardware JPEG decoder because `JpegRenderer.render` requires `rotate == 0` before attempting hardware decoding. This violates the hardware-decoding requirement for suitable decoders and forces rotated streams through the slower bitmap path.

## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[50-54]
- android/app/src/main/java/com/openipc/decoder/JpegRenderer.java[90-108]

## Recommended Fix
Keep hardware JPEG decoding enabled for rotated streams when a suitable decoder is available, applying the rotation through the output surface or display transform. Use the bitmap renderer only when hardware decoder configuration fails or no suitable decoder exists.

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

Comment thread android/app/src/main/java/com/openipc/decoder/Jieli.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/Jieli.java Outdated
@compot-dev

compot-dev commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Qodo review on 776d32d:

  1. Rotated streams skip hardware decoding: true, and left as it is in this PR. The single view draws on a SurfaceView since fix: single view on a SurfaceView instead of a TextureView (#76) #185 (ANR on AG-216: RenderThread blocked in eglSwapBuffers with 8 multi-view SurfaceViews #76), whose content can't be turned when a hardware decoder writes into it; turning it would need a GL path or a TextureView, which ANR on AG-216: RenderThread blocked in eglSwapBuffers with 8 multi-view SurfaceViews #76 moved away from. The rotated streams here are toy cameras at 240×320 to 640×480, which the bitmap path draws easily, and the single view now drops stale MJPEG frames instead of queueing them. Hardware decoding with rotation goes to Per-slot rotation and mirror in the camera settings #201 (per-slot rotation), where rotation becomes a general setting.
  2. Repeated packets corrupt toy video and 3. Reordered packets lose toy frames: fixed in 367b897 (RtStream keeps a bit per byte received: a repeated piece counts once and is ignored if identical; a frame is no longer given up when its last piece comes early, only when a different frame starts). Tests rtStreamIgnoresRepeatedPieces, rtStreamTakesPiecesOutOfOrder, rtStreamDropsAFrameWithAPieceMissing.

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

Copy link
Copy Markdown

Qodo review on 776d32d:

  1. Rotated streams skip hardware decoding: true, and left as it is in this PR. The single view draws on a SurfaceView since fix: single view on a SurfaceView instead of a TextureView (#76) #185 (ANR on AG-216: RenderThread blocked in eglSwapBuffers with 8 multi-view SurfaceViews #76), whose content can't be turned when a hardware decoder writes into it; turning it would need a GL path or a TextureView, which ANR on AG-216: RenderThread blocked in eglSwapBuffers with 8 multi-view SurfaceViews #76 moved away from. The rotated streams here are toy cameras at 240×320 to 640×480, which the bitmap path draws easily, and the single view now drops stale MJPEG frames instead of queueing them. Hardware decoding with rotation goes to Per-slot rotation and mirror in the camera settings #201 (per-slot rotation), where rotation becomes a general setting.
  2. Repeated packets corrupt toy video and 3. Reordered packets lose toy frames: fixed in 6cf3ff6 (RtStream keeps a bit per byte received: a repeated piece counts once and is ignored if identical; a frame is no longer given up when its last piece comes early, only when a different frame starts). Tests rtStreamIgnoresRepeatedPieces, rtStreamTakesPiecesOutOfOrder, rtStreamDropsAFrameWithAPieceMissing.

Understood. The current review therefore has finding 1 intentionally left open for the planned per-slot rotation work in #201, while findings 2 and 3 were fixed in commit 6cf3ff6 with corresponding tests. No dismissal or implementation action was requested.

@compot-dev
compot-dev merged commit 1c2fe3e into v2 Oct 16, 2026
4 checks passed
compot-dev added a commit that referenced this pull request Oct 16, 2026
@compot-dev
compot-dev deleted the feat/flowufo-187 branch October 16, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting device test PR ready, merge only after testing on a real device

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KY UFO toys (rtsp://…:7070/webcam): turn the picture 90° counter-clockwise

2 participants