Skip to content

test/ci: unit tests + lint in CI (#34); fix H.265 FU NAL header (#43) - #44

Merged
compot-dev merged 4 commits into
v2from
ci/tests
Oct 12, 2026
Merged

compot-dev merged 4 commits into
v2from
ci/tests

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Fixes #34, fixes #43.

Tests (#34)

28 JVM unit tests in android/app/src/test. No device or emulator is needed; unitTests.returnDefaultValues = true lets the android.jar stubs (such as Log) return defaults.

Class What is covered
SdpParserTest track URLs, H.265 detection, AAC mpeg4-generic with fmtp, fmtp before rtpmap, invalid AU-header layout, static PT 0/8, L16, video and audio sharing a PT number, Opus, no audio
AudioDecodeTest G.711 μ/A-law reference values and monotonicity, little-endian output, L16 byte swap, AAC AudioSpecificConfig, AU-header bit reading
RtpTest RTP header length with CSRC and extension, malformed headers, padding, interleaved framing after garbage, clean EOF, truncation
RtspResponseTest header value without parameters, missing header, 401/malformed/empty response, line-length limit
NalAssemblerTest H.264 single NAL and FU-A, H.265 single NAL and FU, codec-switch callback, empty payload

Lint

android/app/lint-baseline.xml records the 16 existing findings (errors and warnings). lintDebug passes now and fails only on new errors. As existing issues get fixed (#30, #35, ...), the baseline should be regenerated with ./gradlew updateLintBaseline.

CI

  • New test job: ./gradlew testDebugUnitTest lintDebug on every push and pull request, including PRs from forks, since no secrets are needed. Reports are uploaded as an artifact when the job fails.
  • The build job (signed release, release upload, Telegram) now runs only after test succeeds, and is skipped for pull requests.

Bug found by the tests (#43)

NalAssembler built the 2-byte H.265 NAL header of a fragmented unit with the FU header as the second byte. For an IDR that gives 26 93 instead of 26 01, which reads as nuh_layer_id = 18. Conforming decoders drop such NAL units. Fixed in a separate commit (4b90de4); the h265Fu test failed before the fix and passes after it.

Verification

The start fragment wrote only the first byte of the 2-byte NAL header and
then copied from the FU header, so the FU header became the second byte
(nuh_layer_id 18, TemporalId 2 for an IDR). Conforming decoders ignore
such NAL units. Copy the layer-id/TID byte from the payload header, mask
the FU type with 0x3F and skip payload header + FU header (fixes #43).
28 tests for the pure logic: SDP parsing and AudioConfig, G.711/L16
conversion, AAC AudioSpecificConfig and AU-header bit reading, RTP
header length/padding, RTP-over-TCP framing (EOF, truncation, resync),
RTSP response parsing and NalAssembler (H.264 single/FU-A, H.265
single/FU, codec switch). android.jar stubs return default values.

The lint baseline records the 16 known findings, so lintDebug only fails
on new ones.
A new 'test' job runs testDebugUnitTest and lintDebug on every push and
pull request (including forks, no secrets needed) and uploads the reports
on failure. The signed build depends on it and is skipped for pull
requests (fixes #34).
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add JVM tests and CI checks; fix H.265 FU reassembly

🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add 28 JVM tests covering stream parsing, audio conversion, RTP framing, and NAL assembly.
• Correct H.265 fragmented NAL headers so conforming decoders can accept reassembled units.
• Run tests and baseline-aware lint before signed builds; check fork PRs without release secrets.
Diagram

graph TD
  Trigger["Push or PR"] --> Test["CI test job"] --> JVM["JVM tests"] --> Media["Media logic"]
  Test --> Lint["Android lint"] --> Baseline["Known findings"]
  Test -->|push only| Build["Signed build"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use Android instrumented tests
  • ➕ Tests behavior with real Android APIs and codecs.
  • ➖ Requires a device or emulator in CI and slows fork-PR feedback.
2. Use Robolectric for JVM tests
  • ➕ Provides more realistic Android API behavior than default-returning stubs.
  • ➖ Adds a test dependency and runtime overhead for logic that mostly does not need Android behavior.

Recommendation: Keep the lightweight JVM suite and secrets-free PR gate for these protocol helpers. Follow up with an on-device H.265 playback check, since byte-level tests cannot establish decoder compatibility.

Files changed (10) +636 / -3

Bug fix (1) +4 / -3
NalAssembler.javaReconstruct the full H.265 FU NAL header +4/-3

Reconstruct the full H.265 FU NAL header

• Writes the original layer-ID and temporal-ID byte after the reconstructed NAL type, masks the FU type to six bits, and skips the complete FU header before copying payload data.

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

Tests (5) +402 / -0
AudioDecodeTest.javaTest audio conversion and AAC helpers +67/-0

Test audio conversion and AAC helpers

• Checks G.711 reference values and output byte order, L16 conversion, AAC AudioSpecificConfig generation, and AU-header bit extraction.

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

NalAssemblerTest.javaTest single and fragmented H.264/H.265 NAL units +84/-0

Test single and fragmented H.264/H.265 NAL units

• Covers single NALs, fragmented reassembly, codec-switch notification, and empty payloads. The H.265 FU assertion protects the corrected two-byte header.

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

RtpTest.javaTest RTP headers and TCP interleaved framing +78/-0

Test RTP headers and TCP interleaved framing

• Checks CSRCs, extensions, malformed headers, and padding, plus framing recovery after garbage, clean EOF, and truncated packets.

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

RtspResponseTest.javaTest RTSP response parsing +53/-0

Test RTSP response parsing

• Checks header extraction and missing headers, and verifies rejection of error responses, malformed status lines, empty input, and oversized lines.

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

SdpParserTest.javaTest SDP track and audio configuration parsing +120/-0

Test SDP track and audio configuration parsing

• Covers track URLs, H.265 detection, AAC parameters and ordering, invalid AU layouts, static payload types, L16, shared payload numbers, and unsupported or missing audio.

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

Other (4) +230 / -0
build.ymlGate signed builds on unit tests and lint +38/-0

Gate signed builds on unit tests and lint

• Adds a secrets-free job for pushes and pull requests that runs JVM tests and lint and uploads reports on failure. The signed build now depends on that job and skips pull requests.

.github/workflows/build.yml

build.gradleEnable local unit tests and baseline-aware lint +14/-0

Enable local unit tests and baseline-aware lint

• Configures Android stubs to return defaults in JVM tests, applies the lint baseline, and adds the JUnit test dependency.

android/app/build.gradle

lint-baseline.xmlRecord existing Android lint findings +174/-0

Record existing Android lint findings

• Baselines 16 existing findings so lint can flag new issues without requiring unrelated fixes in this PR.

android/app/lint-baseline.xml

libs.versions.tomlDeclare JUnit 4 in the version catalog +4/-0

Declare JUnit 4 in the version catalog

• Adds the JUnit 4.13.2 library alias used by the app module's JVM tests.

android/gradle/libs.versions.toml

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

qodo-free-for-open-source-projects Bot commented Oct 12, 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. AAC ADTS frames can regress unnoticed ✓ Resolved
Description
AudioDecodeTest checks AAC configuration and bit reading but never exercises
AudioManager.processAac() when it detects and extracts ADTS frames. Changes to ADTS sync
validation, frame-length parsing, or multiple-frame handling can therefore pass the new unit-test
suite without coverage.
Code

android/app/src/test/java/com/openipc/decoder/AudioDecodeTest.java[R52-65]

+    @Test
+    public void aacAudioSpecificConfig() {
+        assertArrayEquals(new byte[] {0x11, (byte) 0x88}, AudioManager.buildAacConfig(48000, 1));
+        assertArrayEquals(new byte[] {0x12, 0x10}, AudioManager.buildAacConfig(44100, 2));
+        assertArrayEquals(new byte[] {0x15, (byte) 0x88}, AudioManager.buildAacConfig(8000, 1));
+    }
+
+    @Test
+    public void readBitsAcrossBytes() {
+        // AU header: size = 5 (13 bits), index = 0 (3 bits)
+        byte[] h = {0x00, 0x28};
+        assertEquals(5, AudioManager.readBits(h, 0, 13));
+        assertEquals(0, AudioManager.readBits(h, 13, 3));
+        assertEquals(0b1011, AudioManager.readBits(new byte[] {(byte) 0b0101_1000}, 1, 4));
Evidence
Compliance ID 9 requires unit tests for AAC ADTS parsing. The added AAC tests cover
buildAacConfig() and readBits() only, while the ADTS branch in AudioManager.processAac()
parses sync words and frame lengths without any corresponding test invocation.

Add unit tests for pure parsing and assembly logic
android/app/src/test/java/com/openipc/decoder/AudioDecodeTest.java[52-65]
android/app/src/main/java/com/openipc/decoder/AudioManager.java[285-306]

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 new AAC tests cover AudioSpecificConfig generation and AU-header bit reading, but do not cover the ADTS parsing path in `AudioManager.processAac()`, including sync detection, frame length extraction, truncation, and multiple frames.
## Fix Focus Areas
- android/app/src/test/java/com/openipc/decoder/AudioDecodeTest.java[52-65]
- android/app/src/main/java/com/openipc/decoder/AudioManager.java[285-306]
## Recommended Fix
Add JVM-testable coverage for ADTS frame parsing, including valid single and multiple frames, invalid sync data, invalid or truncated frame lengths, and trailing incomplete frames; expose only the smallest necessary helper or isolate the parsing logic so the tests do not require a real `MediaCodec`.

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


2. Older commits can no longer be released ✓ Resolved
Description
The test job checks out inputs.commit_hash and runs lintDebug against that historical tree,
restoring only .github/ rather than the new lint baseline. When a manual build targets a pre-PR
commit, the existing lint errors are unsuppressed, so test fails and the dependent build job
does not run.
Code

.github/workflows/build.yml[R34-36]

+      - name: Unit tests and lint
+        working-directory: android
+        run: ./gradlew testDebugUnitTest lintDebug
Evidence
The new checkout selects the requested commit and restores only workflow files; the baseline is a
file newly added by this PR. The baseline records existing lint errors, and build requires the
test job to succeed.

.github/workflows/build.yml[20-36]
.github/workflows/build.yml[48-50]
android/app/lint-baseline.xml[4-29]
android/app/build.gradle[66-69]

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

## Issue description
Manual builds of pre-PR commits now fail in the new test job because those commits lack the lint baseline, preventing the release build.
## Fix Focus Areas
- .github/workflows/build.yml[20-36]
- .github/workflows/build.yml[48-50]
## Recommended Fix
Keep running unit tests for the selected commit, but run `lintDebug` only when that checkout contains the lint baseline. Preserve the existing test dependency for the release build.

ⓘ 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 commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread android/app/src/test/java/com/openipc/decoder/AudioDecodeTest.java
Comment thread .github/workflows/build.yml Outdated
- AudioManager.splitAdts() and splitAuHeaders() are now static helpers
  (behaviour unchanged) with tests for single and multiple frames, bad
  sync, layer bits, too-small and truncated frames, two AUs, fragmented
  AUs and short payloads.
- CI: when a manual build targets a commit without lint-baseline.xml,
  unit tests still run but lint is skipped, so older commits can still
  be released.
@compot-dev
compot-dev merged commit ea0c4a8 into v2 Oct 12, 2026
4 checks passed
@compot-dev
compot-dev deleted the ci/tests branch October 13, 2026 17:15
compot-dev added a commit that referenced this pull request Oct 15, 2026
test/ci: unit tests + lint in CI (#34); fix H.265 FU NAL header (#43)
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.

H.265: reassembled FU NAL units get the FU header as the second NAL header byte CI: run lint and unit tests; add the first tests

1 participant