Skip to content

fix: wait for a decoder input buffer instead of dropping NAL units after 5 ms (#89) - #106

Merged
compot-dev merged 3 commits into
v2from
fix/input-buffer-wait
Oct 13, 2026
Merged

compot-dev merged 3 commits into
v2from
fix/input-buffer-wait

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Closes #89.

decodeFrame waited 5 ms for a free input buffer and then dropped the NAL unit. On the AG-216 even a single 1080p20 cell lost about 1 frame per second this way (seen on the debug page as lost 1/s), and every lost slice corrupts the picture until the next key frame. These drops are also the overload signal of #84/#85, so they made the planner see overload that was not there.

  • Wait up to INPUT_WAIT_MS = 40 ms (longer than a frame at 30 fps), polling in 5 ms steps.
  • Between polls, decoded frames are given back (drainOutput): a decoder that waits for output space frees input buffers only then.
  • A NAL unit is dropped (and counted in inputDropped) only after the full wait; the stream's network queue takes the backlog meanwhile, and its overflow stays the other loss signal.

Device check: lost/s of a single full-rate cell and of 8 cells, before and after.

@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Pauses inflate decoder loss signals ✓ Resolved
Description
decodeFrame increments inputDropped when its input-buffer wait ends without a buffer, even if
enabled became false or a close became pending. Stopping or closing a cell therefore contaminates
QuadCell.sample()’s lostFps measurement and can cause the overload planner to pause another cell
despite no decoder-capacity loss.
Code

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[R344-348]

+                    while (inputBufferId < 0 && nowMs() < waitUntil && enabled
+                            && closeWaiting.get() == 0) {
+                        drainOutput(codec);
+                        inputBufferId = codec.dequeueInputBuffer(INPUT_POLL_US);
+                    }
Evidence
The polling loop exits on disablement or a pending close, but the subsequent no-buffer branch
increments inputDropped without distinguishing cancellation from a full timeout. Stopping a cell
disables its decoder, and QuadCell.sample() includes that counter in lostFps, which the decoder
uses to identify overload and pause cells.

Adapt capacity and promotions to measured decoder losses
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[344-358]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[258-264]
android/app/src/main/java/com/openipc/decoder/Decoder.java[911-919]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[341-360]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[198-209]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[256-267]

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 input-buffer wait cancelled by disablement or a pending close is counted as decoder input starvation, producing a false loss measurement that can trigger unnecessary planner pauses.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[341-360]
## Recommended Fix
Distinguish a full input-buffer timeout from lifecycle cancellation. Increment `inputDropped` only when the full wait expires while decoding remains enabled and no close is pending; let frames cancelled by pause or close exit without recording decoder overload.

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



Remediation recommended

2. Camera changes can briefly freeze the screen ✓ Resolved
Description
decodeFrame holds decoderLock throughout the new input-buffer polling loop, while
closeDecoder() must acquire that lock synchronously. If a buffer remains unavailable when a user
saves camera settings or a surface is destroyed, the UI thread can wait through the polling period
before teardown proceeds.
Code

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[R330-333]

+                    long waitUntil = nowMs() + INPUT_WAIT_MS;
+                    while (inputBufferId < 0 && nowMs() < waitUntil) {
+                        drainOutput(codec);
+                        inputBufferId = codec.dequeueInputBuffer(INPUT_POLL_US);
Evidence
decodeFrame enters the monitor before the added polling loop; closeDecoder waits on the same
monitor. Saving from the dialog invokes saveSettings, which calls closeDecoder, and the
surface-destruction callback calls it directly.

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[305-334]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[197-214]
android/app/src/main/java/com/openipc/decoder/MenuManager.java[378-385]
android/app/src/main/java/com/openipc/decoder/Decoder.java[475-487]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[156-165]

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

## Issue description
The longer input-buffer wait holds a lock needed by synchronous decoder teardown, so camera changes can block the UI thread.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[305-334]
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[197-214]
## Recommended Fix
Make the wait cancellable when teardown starts, and arrange decoder teardown so the UI thread does not wait for the polling loop to finish. Preserve serialized access to the codec and avoid queueing input after cancellation.

ⓘ 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 turn these tips off under Display > Comment parts

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo: Camera changes can briefly freeze the screen — accepted and fixed: closeDecoder() announces itself before taking the lock, and setEnabled(false) clears enabled; the input-buffer wait checks both on every 5 ms step and ends at once, so the UI thread waits at most one poll (5 ms), as before.

@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a99918a

@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo: Pauses inflate decoder loss signals — accepted and fixed: inputDropped grows only when the full wait ran out; a wait ended by a pending close or by setEnabled(false) is not counted.

Device result for this PR (together with #86 on top of it, Aceline-1, 8 cameras): with all cells on key frames the decoder losses dropped to 0 over several minutes (before: about 1/s per cell, which made #86 pause cells one by one).

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

Copy link
Copy Markdown

Qodo: Pauses inflate decoder loss signals — accepted and fixed: inputDropped grows only when the full wait ran out; a wait ended by a pending close or by setEnabled(false) is not counted.

Device result for this PR (together with #86 on top of it, Aceline-1, 8 cameras): with all cells on key frames the decoder losses dropped to 0 over several minutes (before: about 1/s per cell, which made #86 pause cells one by one).

Thanks for confirming. This addresses finding 1: cancellation during pause or close no longer contributes to inputDropped. The reported zero decoder losses across eight cameras also validates the fix’s effect on the overload planner.

@compot-dev
compot-dev merged commit fa9aaa3 into v2 Oct 13, 2026
4 checks passed
@compot-dev
compot-dev deleted the fix/input-buffer-wait branch October 13, 2026 09:39
compot-dev added a commit that referenced this pull request Oct 15, 2026
compot-dev added a commit that referenced this pull request Oct 15, 2026
compot-dev added a commit that referenced this pull request Oct 15, 2026
fix: wait for a decoder input buffer instead of dropping NAL units after 5 ms (#89)
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.

Video: a NAL unit is dropped when the decoder has no free input buffer within 5 ms

1 participant