Repository navigation
feat: adaptive multi-view decoding (full rate or key frames per cell) and D for the debug text (#85) - #86
Conversation
|
/review |
Device test on the AG-216 (CI builds), 3×3 grid with 8 cameras on main streams
Final state: |
Code Review by Qodo
1.
|
| for (int i = cells.size() - 1; i >= 0; i--) { | ||
| Cell c = cells.get(i); | ||
| if (!c.paused && c.playing && countActive(cells) > 1) { | ||
| return change(nowMs, Action.PAUSE, c.index); | ||
| } |
There was a problem hiding this comment.
3. The last working camera can be paused needlessly 📎 Requirement gap ≡ Correctness
DecodePlanner.step() requires a pause candidate to be playing, but its countActive(cells) > 1 guard counts every non-paused cell, including cells that are connecting, buffering, or disconnected. When the only playing cell loses frames even on key frames, any such inactive slot can make the fallback pause that sole functioning camera despite there being no other stream contributing decode load or providing a picture.
Agent Prompt
## Issue description
The pause-as-last-resort fallback can pause the sole playing camera when another configured cell is non-paused but not playing, such as a connecting, buffering, or disconnected cell. The guard should reflect streams actually playing rather than all non-paused slots.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[144-150]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[173-177]
## Recommended Fix
Change `countActive(List<Cell>)` to count only cells where `c.playing` is true and `c.paused` is false, or use an equivalent playing-cell count at the pause-fallback call site. Add a test with one lossy playing key-frame cell and one disconnected cell to verify that the sole playing cell is not paused.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (nowMs - quietSinceMs >= PROBE_QUIET_MS && load > proven) { | ||
| proven = load; | ||
| probeWaitMs = PROBE_QUIET_MS; |
There was a problem hiding this comment.
2. A full-rate cell can flip between full rate and key frames forever 🐞 Bug ≡ Correctness
When a promotion fits and comes after 30 s without losses, the next lossless sample sets proven to the new load B, because quietSinceMs is reset only for probes and not for fitting promotions. If B then causes losses, the demotion computes capacity = max(proven, …) = B, so the same cell fits again and is promoted again after SETTLE_MS, and the cycle repeats every few seconds while B is saved as the learnt capacity.
Agent Prompt
## Issue description
`proven` is set to a load that has only just been reached, because the 30 s quiet window is measured from the last loss and not from the last load change. A demotion then cannot lower capacity below that unproven load, so the planner promotes and demotes the same cell over and over.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[113-127]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[159-166]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[179-182]
## Recommended Fix
Measure 'proven' from the later of the last loss and the last change. For example, require `nowMs - max(quietSinceMs, lastChangeMs) >= PROBE_QUIET_MS`, or reset `quietSinceMs` in `change()` for every PROMOTE. Also, on DEMOTE, lower `proven` when it is >= the failing load. Add a test where a fitting promotion after 30 s of quiet causes losses, and assert that the cell is not promoted again.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
All five Qodo findings are accepted and listed in #85; they will be fixed in this PR before merging. |
…data, U view, write deadline - IPv6 site-local (fec0::/10) is no longer a local client: isSiteLocalAddress() counts for IPv4 only. - /log is served from LogBuffer, a ring of the last 500 redacted lines fed by a logcat reader that follows the app's own log for the life of the process. - Each cell reports behind_s (seconds in a row the decoder made it lose frames), with overload_after_s for the threshold (#83). Full rate / key frames comes with #86. - In the U view the JSON has a "udp" object instead of the last single-view state. - A request has a 5 s deadline, writing included: a client that stops reading has its socket closed and cannot hold a worker.
5b3e1a5 to
19dd5a9
Compare
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
19dd5a9 to
d40650f
Compare
…e lowers a proven load at or above it (Qodo on #86)
|
Qodo review of #86 — decisions (all five accepted, fixed after rebasing on
Also in this PR now: |
…85) - Every camera stays on screen: cells start on key frames only (IDR/IRAP and parameter sets), then are promoted to full rate in priority order (the audio cell, then slot order) while the estimated load (macroblocks/s) fits the capacity. Promotion waits for the next key frame. Decoder losses (queue overflow, no input buffer) for 3 s demote the lowest-priority full cell and lower the capacity; a load that plays 30 s without losses is proven, and a probe up to 25 % above the capacity is tried. Pausing remains only as a last resort when even key frames are lost. - DecodePlanner holds this logic (pure Java, tested). The capacity starts from the decoder's declared budget (getSupportedFrameRatesFor 1080p) and the learnt value is stored per device (settings 'decoder_capacity'). - Corner text: 'WxH codec key frames · N fps' for key-frame cells. - D in the menu after U turns the debug text on/off (stored). Off: only cells that need attention (not connected, errors, limits, paused, key frames) show text. - READMEs: D in the menu table, adaptive grid and the measured AG-216 budget. - Tests: DecodePlannerTest; CellStatsTest updated (key-frame start, key NAL types).
… slots freed late
- A decoder that fails to start is reported to the cell ('Decoder error') instead of
showing normal zero-fps stats
- While a codec's instance limit is unknown, its decoders start one at a time, so
concurrent cells cannot exceed the limit before it is learnt (Android 5 and errors
count as unlimited)
- The instance slot is given back only after the codec is released, so a waiting cell
cannot start while the device still holds the maximum
- A decoder limit message is not replaced by a connection error
- Only playing cells are demoted or paused (#85 planner)
…ot by a fixed margin A fixed 25 % margin kept a box that is stronger than declared from ever trying a second 1080p30 camera (CI test). Probes now go up to any load below the smallest one at which the decoder lost frames, so a failed configuration is not retried every 30 s.
…er raise capacity on failure (#85) Device test: the second camera was promoted on a per-second fps that jumped, the load ignored what the seven key-frame cells cost, and a failure at a higher estimated load raised the learnt capacity (302k -> 507k blocks/s). - Rates are smoothed over about 3 s for planning and for the key-frame display - Key-frame cells add their measured cost to the load; a promotion replaces it - After losses the capacity can only go down (never below a proven load)
…#85) Device test: the second camera was re-probed about once a minute, because the noisy load estimate dipped just under the failed one, and failed each time (a short glitch). Probes now stay 10 % below the smallest failed load, and the wait between probes doubles after every failure (30 s up to 10 min), resetting once a probe holds.
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
…om 5 ms input drops)
… no blinking on the debug page
…e lowers a proven load at or above it (Qodo on #86)
72d456b to
7afe60b
Compare
|
Qodo still lists findings 2 and 3 on 7afe60b, but the quoted code is from before the fixes: |
Agreed—the current code addresses both findings:
The published review is stale and still displays findings 2 and 3 as open, but I can’t update the review status from this thread without an explicit dismissal request. |
…data, U view, write deadline - IPv6 site-local (fec0::/10) is no longer a local client: isSiteLocalAddress() counts for IPv4 only. - /log is served from LogBuffer, a ring of the last 500 redacted lines fed by a logcat reader that follows the app's own log for the life of the process. - Each cell reports behind_s (seconds in a row the decoder made it lose frames), with overload_after_s for the threshold (#83). Full rate / key frames comes with #86. - In the U view the JSON has a "udp" object instead of the last single-view state. - A request has a 5 s deadline, writing included: a client that stops reading has its socket closed and cannot hold a worker.
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
…e lowers a proven load at or above it (Qodo on #86)
feat: adaptive multi-view decoding (full rate or key frames per cell) and D for the debug text (#85)
Closes #85. Also fixes Qodo's post-merge findings on #84 (see the reply there).
Adaptive decoding: every camera stays on screen.
WxH codec key frames · N fps.DecodePlanner(pure Java, tested) promotes cells to full rate in priority order (the audio cell, then slot order) while the estimated load (macroblocks/s) fits the capacity. A promotion waits for the next key frame.decoder_capacity). A pause remains only as a last resort, if even key frames are lost.D in the menu (after U): debug text on/off, stored. Off: only cells that need attention show text (not connected, errors, decoder limit, paused, key frames).
Fixes from Qodo on #84: a decoder start failure shows
Decoder error; decoders of a type start one at a time until the limit is known; the instance slot is returned afterrelease(); a decoder limit message is not replaced by a connection error; only playing cells are demoted or paused.READMEs (EN/RU/ZH): D in the menu, the adaptive grid, the measured AG-216 budget. Tests:
DecodePlannerTest,CellStatsTest. Device test with the CI build follows.