Skip to content

feat: software decoder for key-frame cells beyond the hardware limit (#88, step 1) - #138

Merged
compot-dev merged 4 commits into
v2from
feat/sw-keyframes
Oct 13, 2026
Merged

compot-dev merged 4 commits into
v2from
feat/sw-keyframes

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Step 1 of #88 (data from #137: the AG-216's hardware H.265 decoder allows 4 instances; the software c2.android.hevc.decoder allows 32 and decodes ~62 fps at 1080p).

  • A multi-view cell on key frames only (≈ 1 frame/s) that hits the hardware instance limit no longer shows Decoder limit: 4 × H.265: MediaCodecManager creates a software decoder for it (c2.android.* first, else OMX.google.*; isSoftwareOnly() on Android 10+). It holds no slot of the hardware budget.
  • Such a cell is pinned: the Multi-view: adaptive decoding — full rate or key frames per cell, learnt device capacity #85 planner never promotes it to full rate (DecodePlanner.Cell.pinned), and the next cells are not held back. Corner text: … key frames · 1.0 fps · software.
  • The single view never uses a software decoder (unchanged).
  • Planner test for a pinned cell.

Device check: Aceline-1 with all 8 slots on H.265 (or the 4 H.265 cameras + U) — cells 5+ play key frames with · software instead of Decoder limit; CPU of the app and load stay acceptable.

@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. Software cells can exceed codec limits ✓ Resolved
Description
createDecoder bypasses InstanceBudget whenever the hardware slot is exhausted and creates a
software decoder without learning or enforcing that codec's own maximum instance count. When enough
key-frame cells reach the fallback path to exhaust the software decoder's capacity, the app
continues attempting additional instances without applying the decoder-limit handling or displaying
the applicable limit.
Code

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[R324-325]

+            softwareName = limit >= 0 && allowSoftware ? softwareDecoder(type) : null;
+            if (softwareName == null) {
Evidence
The fallback is entered after the hardware budget acquisition fails, but it only checks whether a
software codec name exists and then creates it; no software codec instance limit is learned or
acquired. This conflicts with the requirement to respect per-codec decoder instance limits.

Respect per-codec decoder instance limits
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[322-325]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[340-347]

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 software fallback bypasses per-codec instance accounting, so enough key-frame cells can cause the app to create more software decoder instances than the selected codec supports.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[322-347]
## Recommended Fix
Discover and track the selected software decoder's declared maximum supported instances, acquire and release a separate software-codec budget around its lifecycle, and route acquisition failures through the existing decoder-limit reporting path.

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


2. Software drops demote hardware cells ✓ Resolved
Description
plannerCell() marks a software cell as pinned without separating its lossy flag from
hardware-decoder losses. If that software decoder drops input in three samples,
DecodePlanner.step() can demote a full-rate hardware cell and persist a reduced or failed hardware
capacity even when the hardware decoder has no losses.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[1162]

+        pc.pinned = c.isSoftwareDecoder();
Evidence
Input drops feed the software cell's loss sample. Pinning excludes only promotion, while the
unfiltered loss aggregation can demote a hardware cell and write the resulting capacity values to
settings.

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[438-455]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[273-280]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1153-1163]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[153-193]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1133-1142]

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

## Issue description
Software decoder drops are treated as evidence that the hardware decoder is overloaded, causing hardware demotion and capacity learning.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1153-1163]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[153-193]
## Recommended Fix
Keep pinned software-cell losses out of hardware demotion and capacity-learning decisions. Handle sustained software losses separately if they still require pausing a cell, and test that software-only drops do not demote a healthy hardware cell.

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



Remediation recommended

3. A failed codec query can block more cells ✓ Resolved
Description
softwareDecoder() assigns found to a preferred codec before reading its instance limit, so a
failed capability query leaves that codec paired with the previous candidate’s limit. If the
previous codec has a lower limit, subsequent key-frame cells are denied software slots even when the
selected codec has capacity, and they display the hardware decoder limit instead.
Code

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[R144-146]

+                        found = n;
+                        max = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                                ? ci.getCapabilitiesForType(t).getMaxSupportedInstances()
Evidence
The assignment to found precedes the capability call. Its exception handler leaves found changed
but max unchanged; the resulting limit is learned under the software budget key and used to reject
later acquisitions.

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[141-157]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[162-180]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[343-359]

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

## Issue description
A capability-query exception can leave a newly selected software codec paired with an earlier codec’s instance limit, causing valid fallback attempts to be rejected.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[141-157]
## Recommended Fix
Read and validate each candidate’s instance limit into a local variable before changing `found` or `max`. If the query fails, leave the previous valid candidate and its limit unchanged.

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


4. Software cells hold back hardware streams ✓ Resolved
Description
DecodePlanner.step() skips pinned cells for promotion but still adds their keyLoad to the load
used to evaluate other cells. Once a software cell has decoded frames, its positive load can put an
otherwise eligible hardware cell over the declared capacity, despite the software decoder holding no
hardware slot.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[211]

+            if (c.full || c.paused || !c.playing || c.pinned) {
Evidence
The new pin skips only promotion. A real software cell produces a positive keyLoad, which remains in
the aggregate used for another cell's capacity check; the new test leaves keyLoad at its default
zero.

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[153-162]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[208-229]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[324-337]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[369-379]
android/app/src/test/java/com/openipc/decoder/DecodePlannerTest.java[332-342]

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

## Issue description
Pinned software cells consume the planner's hardware load budget and can delay another cell's promotion.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[153-162]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[208-229]
- android/app/src/test/java/com/openipc/decoder/DecodePlannerTest.java[332-342]
## Recommended Fix
Exclude pinned cells' key-frame load from hardware-capacity calculations. Extend the pinned-cell test with a positive keyLoad and a promotion that fits only when that load is excluded.

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


5. Codec lookup failures disable fallback ✓ Resolved
Description
softwareDecoder() catches a codec-list RuntimeException and caches the resulting null as if
discovery had completed. If enumeration or one codec's metadata access fails once, every later
attempt for that MIME returns null without retrying and the affected cells continue reporting the
hardware decoder limit.
Code

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[R133-135]

+            } catch (RuntimeException ignored) {
+            }
+            SOFTWARE.put(mime, found);
Evidence
The cache treats a stored null as a completed lookup. The fallback caller then interprets that value
as no available software decoder; the repository's other codec enumeration deliberately leaves
failed enumeration uncached.

android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[113-137]
android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[321-334]
android/app/src/main/java/com/openipc/decoder/CodecInfo.java[36-40]

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

## Issue description
A failed codec-list lookup permanently caches absence of a software decoder for that MIME.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java[113-137]
## Recommended Fix
Do not cache a result when enumeration throws. Isolate failures of individual codec entries so one broken entry does not hide other candidates, and allow a later lookup to retry failed discovery.

ⓘ 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

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

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread android/app/src/main/java/com/openipc/decoder/MediaCodecManager.java Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

@compot-dev
compot-dev merged commit 56016e6 into v2 Oct 13, 2026
3 of 4 checks passed
@compot-dev
compot-dev deleted the feat/sw-keyframes branch October 13, 2026 17:16
compot-dev added a commit that referenced this pull request Oct 15, 2026
feat: software decoder for key-frame cells beyond the hardware limit (#88, step 1)
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