Skip to content

Add VideoIO: cross-platform video encode + frame-accurate decode#5315

Open
shai-almog wants to merge 18 commits into
masterfrom
videoio-api
Open

Add VideoIO: cross-platform video encode + frame-accurate decode#5315
shai-almog wants to merge 18 commits into
masterfrom
videoio-api

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

What

A new com.codename1.media.VideoIO subsystem — "ImageIO, but for video, and deeper". It uses each platform's native codecs to:

  1. Encode application-rendered frames + audio into a standard video file, with deep control (dimensions, fps, codec, bitrate, GOP, audio params) and runtime codec enumeration (getAvailableEncoders()/getAvailableDecoders(), incl. hardware flags).
  2. Decode an existing clip into frame-accurate RGBA frames at a target fps (VFR→CFR resample) plus the audio track as PCM.

The decode side fills a real gap: Media is a player whose setTime() snaps to key frames with no exact-frame guarantee. VideoReader.frameAt(ms) returns the precise frame; readFrames(fps, cb) walks the whole clip resampling to a constant rate; readAudio() returns interleaved PCM (AudioBuffer).

Gated by VideoIO.isSupported() as usual (mirrors the ImageIO/Display.getImageIO() pattern). Supported on JavaScript, iOS, macOS, Android, Windows, Linux and the desktop simulator. Not TV/Watch/Car.

API (CodenameOne/src/com/codename1/media)

VideoIO (entry/factory + codec enum), VideoReader, VideoWriter, VideoWriterBuilder, VideoFrame, VideoCodec. Wired via Display.getVideoIO()CodenameOneImplementation.getVideoIO() (null default).

// encode app-rendered frames + audio
VideoWriter w = new VideoWriterBuilder().path(out).width(720).height(1280).frameRate(30)
        .videoCodec(VideoIO.CODEC_H264).hasAudio(true).build();
w.writeFrame(image, ptsMillis);  w.writeAudio(pcm, 44100, 2, ptsMillis);  w.close();

// frame-accurate decode + PCM
VideoReader r = VideoIO.getVideoIO().openReader(path);
VideoFrame f = r.frameAt(1000);                 // exact frame at 1s
r.readFrames(10, frame -> { /* RGBA */ return true; });   // VFR -> CFR
AudioBuffer pcm = r.readAudio();

Per-platform backends (proper native APIs)

Platform Backend
JavaSE / simulator ffmpeg/ffprobe (FFMPEGVideoIO/Reader/Writer)
iOS / macOS AVFoundation — AVAssetReader / AVAssetImageGenerator (zero-tolerance seek) / AVAssetWriter + pixel-buffer adaptor (nativeSources/CN1VideoIO.m)
Android MediaCodec + MediaMuxer / MediaExtractor + MediaMetadataRetriever (pure Java)
Windows Media Foundation — IMFSourceReader / IMFSinkWriter / MFTEnumEx (cn1_windows_video.cpp)
Linux GStreamer — filesrc!decodebin!videoconvert!appsink (ACCURATE seek) / appsrc!videoconvert!x264enc!mp4mux!filesink, dlopen'd (cn1_linux_video.c)
JavaScript HTML5 <video>+<canvas> decode + WebCodecs VideoEncoder/AudioEncoder muxed via mp4-muxer/webm-muxer (HTML5VideoIO)

hellocodenameone (HelloCodenameOne.kt + VideoIORoundTripTest) references the API so every port build compiles the native code in CI — the same pattern already used for the Camera/CarPlay natives.

Tests

  • maven/core-unittests VideoIOTest — API contract/behaviour tests against an in-memory double (always run).
  • maven/javase FFMPEGVideoIORoundTripTest — real H.264/AAC encode→decode round trip, including a 6-frame counting animation that verifies frame order survives the lossy codec + audio PCM RMS lands in the expected band.
  • hellocodenameone VideoIORoundTripTest — on-device suite test: encode a 6-frame counting clip (1→6) + a 440 Hz tone, decode the individual frames, and verify the count order + PCM levels. It's an assertion test (no screenshot, so it doesn't touch baselines) and SKIPs where the platform can't encode so it never false-fails CI.
  • Developer-guide chapter (docs/developer-guide/Video-IO.asciidoc) + compilable snippet demo.

Verification done locally

  • JavaSE round trip: passes (real ffmpeg H.264/AAC).
  • iOS/macOS: CN1VideoIO.m compiles against the real iPhoneOS SDK via the generated ParparVM project; the translator's mangled bindings match (it links). Three real bugs were found & fixed this way.
  • Linux: cn1_linux_video.c compiles clean against real GStreamer 1.24.2 in an arm64 container.
  • JavaScript: the full JS port build produces the browser bundle with the decode + WebCodecs-encode code translated in.
  • Android/Windows/Linux port Java compiles; Windows MF native is built via the clang cross-compile in CI; Linux native in the container/CI.

Notes

  • The JS encoder loads mp4-muxer/webm-muxer from a CDN (same mechanism the port already uses for VideoJS). If a fully offline self-contained bundle is wanted, the libs can be vendored into webapp/assets as a follow-up.
  • JS audio decode (PCM extraction) is not yet wired (the reader reports hasAudio()==false); frame decode + full encode work.

🤖 Generated with Claude Code

New com.codename1.media.VideoIO subsystem (parallel to ImageIO, but deeper) for
encoding application-rendered frames + audio and decoding clips to frame-accurate
RGBA frames + PCM, using each platform's native codecs. The decode side fills a
real gap: Media is a player whose setTime() snaps to key frames, whereas
VideoReader.frameAt()/readFrames() decode exact frames and resample VFR->CFR.
Gated by VideoIO.isSupported() as usual; not available on TV/Watch/Car.

Public API (CodenameOne/src/com/codename1/media): VideoIO, VideoReader,
VideoWriter, VideoWriterBuilder, VideoFrame, VideoCodec; wired through
Display.getVideoIO() / CodenameOneImplementation.getVideoIO().

Per-platform backends (proper native APIs):
- JavaSE simulator: ffmpeg/ffprobe (FFMPEGVideoIO/Reader/Writer + FFMPEGSupport)
- iOS / macOS: AVFoundation (AVAssetReader / AVAssetImageGenerator zero-tolerance
  seek / AVAssetWriter + pixel buffer adaptor) - nativeSources/CN1VideoIO.m
- Android: MediaCodec + MediaMuxer / MediaExtractor + MediaMetadataRetriever (pure Java)
- Windows: Media Foundation (IMFSourceReader / IMFSinkWriter / MFTEnumEx) - cn1_windows_video.cpp
- Linux: GStreamer appsink/appsrc (ACCURATE seek; x264enc/mp4mux) - cn1_linux_video.c
- JavaScript: HTML5 video+canvas decode + WebCodecs encode (VideoEncoder/AudioEncoder
  muxed via mp4-muxer/webm-muxer) - HTML5VideoIO

Tests:
- maven/core-unittests VideoIOTest: API contract tests against a mock double
- maven/javase FFMPEGVideoIORoundTripTest: real H.264/AAC round trip incl. a
  6-frame counting animation verifying frame order + audio PCM RMS
- hellocodenameone VideoIORoundTripTest: on-device suite test (encode a 6-frame
  counting clip + audio, decode the frames, verify count order + PCM levels);
  SKIPs where the platform can't encode so it never false-fails CI

Docs: developer-guide Video-IO chapter + compilable snippet demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 11 screenshots: 11 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Native Windows port (x64)

Compared 138 screenshots: 137 matched, 1 missing reference.

  • VideoIODecodedFrames — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/windows/screenshots/VideoIODecodedFrames.png.

    VideoIODecodedFrames
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as VideoIODecodedFrames.png in workflow artifacts.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 71ms / native 4ms = 17.7x speedup
SIMD float-mul (64K x300) java 71ms / native 5ms = 14.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Native Windows port (cross-compiled)

Compared 138 screenshots: 137 matched, 1 missing reference.

  • VideoIODecodedFrames — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/windows/screenshots/VideoIODecodedFrames.png.

    VideoIODecodedFrames
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as VideoIODecodedFrames.png in workflow artifacts.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 65ms / native 3ms = 21.6x speedup
SIMD float-mul (64K x300) java 65ms / native 3ms = 21.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Native Windows port (arm64)

Compared 138 screenshots: 137 matched, 1 missing reference.

  • VideoIODecodedFrames — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/windows/screenshots/VideoIODecodedFrames.png.

    VideoIODecodedFrames
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as VideoIODecodedFrames.png in workflow artifacts.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 62ms / native 3ms = 20.6x speedup
SIMD float-mul (64K x300) java 60ms / native 3ms = 20.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 139 screenshots: 139 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 139 screenshots: 139 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

shai-almog and others added 2 commits June 30, 2026 05:04
… STL-free

- HTML5VideoIO.java: use the PolyForm Noncommercial header required for
  JavaScriptPort sources (JavaScriptPortSmokeIntegrationTest).
- developer-guide Video-IO.asciidoc: inline the code samples (like
  Motion-Sensors) and drop the compiled demos snippet, which referenced the
  not-yet-released VideoIO API and broke the demos/docs build.
- cn1_windows_video.cpp: drop <string>/std::string/std::wstring (plain wchar_t
  buffers + malloc). The always-compiled native sources are built with clang-cl
  against the xwin MSVC SDK whose <yvals_core.h> rejects the toolchain Clang
  version (STL1000) when the C++ STL is pulled in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 390 total, 0 failed, 14 skipped

Benchmark Results

  • Execution Time: 17976 ms

  • Hotspots (Top 20 sampled methods):

    • 24.66% com.codename1.tools.translator.Parser.addToConstantPool (400 samples)
    • 14.18% java.util.ArrayList.indexOf (230 samples)
    • 3.76% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (61 samples)
    • 3.51% com.codename1.tools.translator.BytecodeMethod.optimize (57 samples)
    • 3.33% java.lang.StringBuilder.append (54 samples)
    • 2.65% com.codename1.tools.translator.BytecodeMethod.equals (43 samples)
    • 2.47% org.objectweb.asm.tree.analysis.Analyzer.analyze (40 samples)
    • 2.10% java.lang.System.identityHashCode (34 samples)
    • 1.91% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (31 samples)
    • 1.42% com.codename1.tools.translator.Parser.classIndex (23 samples)
    • 1.42% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (23 samples)
    • 1.29% java.lang.String.equals (21 samples)
    • 1.17% com.codename1.tools.translator.Parser.resolveDupForms (19 samples)
    • 1.11% org.objectweb.asm.ClassReader.readCode (18 samples)
    • 1.05% java.lang.Object.hashCode (17 samples)
    • 0.92% com.codename1.tools.translator.BytecodeMethod.addInstruction (15 samples)
    • 0.92% java.util.HashMap.hash (15 samples)
    • 0.92% java.lang.StringCoding.encode (15 samples)
    • 0.86% com.codename1.tools.translator.bytecodes.Invoke.addDependencies (14 samples)
    • 0.86% java.io.UnixFileSystem.getBooleanAttributes0 (14 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 140 screenshots: 140 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 194 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 8ms = 7.8x speedup
SIMD float-mul (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 445.000 ms
Base64 CN1 decode 257.000 ms
Base64 native encode 1404.000 ms
Base64 encode ratio (CN1/native) 0.317x (68.3% faster)
Base64 native decode 332.000 ms
Base64 decode ratio (CN1/native) 0.774x (22.6% faster)
Base64 SIMD encode 56.000 ms
Base64 encode ratio (SIMD/CN1) 0.126x (87.4% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.175x (82.5% faster)
Base64 encode ratio (SIMD/native) 0.040x (96.0% faster)
Base64 decode ratio (SIMD/native) 0.136x (86.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 18.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.056x (94.4% faster)
Image applyMask (SIMD off) 89.000 ms
Image applyMask (SIMD on) 67.000 ms
Image applyMask ratio (SIMD on/off) 0.753x (24.7% faster)
Image modifyAlpha (SIMD off) 122.000 ms
Image modifyAlpha (SIMD on) 66.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.541x (45.9% faster)
Image modifyAlpha removeColor (SIMD off) 86.000 ms
Image modifyAlpha removeColor (SIMD on) 65.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.756x (24.4% faster)

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 142 screenshots: 142 matched.

Native Android coverage

  • 📊 Line coverage: 9.80% (9856/100562 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.60% (48142/559872), branch 4.22% (2153/50988), complexity 4.29% (2319/54069), method 6.54% (1840/28114), class 10.52% (420/3993)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.80% (9856/100562 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.60% (48142/559872), branch 4.22% (2153/50988), complexity 4.29% (2319/54069), method 6.54% (1840/28114), class 10.52% (420/3993)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 240ms / native 149ms = 1.6x speedup
SIMD float-mul (64K x300) java 126ms / native 137ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 183.000 ms
Base64 CN1 decode 200.000 ms
Base64 native encode 684.000 ms
Base64 encode ratio (CN1/native) 0.268x (73.2% faster)
Base64 native decode 779.000 ms
Base64 decode ratio (CN1/native) 0.257x (74.3% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 133 screenshots: 133 matched.
✅ JavaScript-port screenshot tests passed.

shai-almog and others added 2 commits June 30, 2026 06:57
…he snapshot pin

The Build Hugo Website job compiles the local JavaScriptPort sources (now incl.
HTML5VideoIO) via the initializr's cn1-local-workspace profile, which is meant
to track the bootstrapped repo HEAD (8.0-SNAPSHOT). Commit 5ad94b0 (a release
version bump to 7.0.255) had re-pinned that profile to 7.0.255, so the new
HTML5VideoIO failed to compile against the released core (no VideoIO).

- scripts/initializr/pom.xml: restore the cn1-local-workspace profile to
  8.0-SNAPSHOT so the website demo builds against the repo build.
- scripts/initializr/update-cn1-version.sh: after the (release-only) global
  bump, restore the cn1-local-workspace profile to the repo SNAPSHOT version
  (read from maven/pom.xml) so a future version bump never re-pins it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The developer-guide-docs workflow treats Vale, asciidoctor and
LanguageTool warnings as build-breaking. The new Video-IO chapter
tripped 3 Vale alerts and 1 LanguageTool match:

- Vale Microsoft.Adverbs: dropped "deliberately".
- Vale Microsoft.Contractions: "does not" -> "doesn't", "It is" -> "It's".
- LanguageTool TYPOS: "resampler" is a legitimate video term (VFR->CFR
  converter); added it to languagetool-accept.txt next to "transcoder".

Verified locally: vale reports 0 alerts and run_languagetool.py reports
0 matches against the rendered chapter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 138 screenshots: 138 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

iOS screenshot updates

Compared 137 screenshots: 136 matched, 1 missing reference.

  • VideoIODecodedFrames — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/VideoIODecodedFrames.png.

    VideoIODecodedFrames
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as VideoIODecodedFrames.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 215 seconds

Build and Run Timing

Metric Duration
Simulator Boot 78000 ms
Simulator Boot (Run) 0 ms
App Install 13000 ms
App Launch 3000 ms
Test Execution 466000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 290ms / native 6ms = 48.3x speedup
SIMD float-mul (64K x300) java 230ms / native 13ms = 17.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 568.000 ms
Base64 CN1 decode 392.000 ms
Base64 native encode 1620.000 ms
Base64 encode ratio (CN1/native) 0.351x (64.9% faster)
Base64 native decode 964.000 ms
Base64 decode ratio (CN1/native) 0.407x (59.3% faster)
Base64 SIMD encode 130.000 ms
Base64 encode ratio (SIMD/CN1) 0.229x (77.1% faster)
Base64 SIMD decode 209.000 ms
Base64 decode ratio (SIMD/CN1) 0.533x (46.7% faster)
Base64 encode ratio (SIMD/native) 0.080x (92.0% faster)
Base64 decode ratio (SIMD/native) 0.217x (78.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 40.000 ms
Image createMask (SIMD on) 53.000 ms
Image createMask ratio (SIMD on/off) 1.325x (32.5% slower)
Image applyMask (SIMD off) 191.000 ms
Image applyMask (SIMD on) 334.000 ms
Image applyMask ratio (SIMD on/off) 1.749x (74.9% slower)
Image modifyAlpha (SIMD off) 361.000 ms
Image modifyAlpha (SIMD on) 264.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.731x (26.9% faster)
Image modifyAlpha removeColor (SIMD off) 278.000 ms
Image modifyAlpha removeColor (SIMD on) 463.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.665x (66.5% slower)

@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

iOS Metal screenshot updates

Compared 141 screenshots: 140 matched, 1 missing reference.

  • VideoIODecodedFrames — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/VideoIODecodedFrames.png.

    VideoIODecodedFrames
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as VideoIODecodedFrames.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 537 seconds

Build and Run Timing

Metric Duration
Simulator Boot 93000 ms
Simulator Boot (Run) 1000 ms
App Install 15000 ms
App Launch 3000 ms
Test Execution 401000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 102ms / native 5ms = 20.4x speedup
SIMD float-mul (64K x300) java 89ms / native 4ms = 22.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 1216.000 ms
Base64 CN1 decode 335.000 ms
Base64 native encode 1674.000 ms
Base64 encode ratio (CN1/native) 0.726x (27.4% faster)
Base64 native decode 1097.000 ms
Base64 decode ratio (CN1/native) 0.305x (69.5% faster)
Base64 SIMD encode 99.000 ms
Base64 encode ratio (SIMD/CN1) 0.081x (91.9% faster)
Base64 SIMD decode 166.000 ms
Base64 decode ratio (SIMD/CN1) 0.496x (50.4% faster)
Base64 encode ratio (SIMD/native) 0.059x (94.1% faster)
Base64 decode ratio (SIMD/native) 0.151x (84.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 47.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.064x (93.6% faster)
Image applyMask (SIMD off) 494.000 ms
Image applyMask (SIMD on) 91.000 ms
Image applyMask ratio (SIMD on/off) 0.184x (81.6% faster)
Image modifyAlpha (SIMD off) 209.000 ms
Image modifyAlpha (SIMD on) 392.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.876x (87.6% slower)
Image modifyAlpha removeColor (SIMD off) 257.000 ms
Image modifyAlpha removeColor (SIMD on) 195.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.759x (24.1% faster)

The build-test SpotBugs quality gate forbids DE_MIGHT_IGNORE (silently
swallowed exceptions). AndroidVideoReader had two empty catch blocks the
detector flagged (probeStreams frame-rate fallback and close()), plus the
sibling probe catch in the same method. Give each a real body:

- probeStreams() frame-rate float fallback: log a one-line diagnostic
  instead of swallowing (Log.p, no stack spam for a benign fallback).
- probeStreams() outer catch: Log.e the real probe failure.
- close(): Log.e a retriever.release() failure.

The remaining empty catches in the file are inside finally cleanup blocks,
which the DroppedException detector excludes (confirmed: readAudio's finally
catches were not flagged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

shai-almog and others added 4 commits June 30, 2026 14:12
…screenshots

The VideoIORoundTripTest was registered first in the device runner and a
startup VIDEOIO_DIAG block enumerated encoders before any screenshot. Both
touch the native media stack before the screenshot suite runs, which broke
two unrelated CI jobs:

- Mac native: initializing AVFoundation/VideoToolbox shifts the macOS display
  color space, so MainActivity + AdsScreen (and every later screen) captured
  with subtly different colors -> "Screenshot differs". Content was identical;
  only the color profile moved.
- Linux musl (Alpine): the round-trip's background GStreamer worker (started
  first) kept running after the runner's per-test deadline advanced past it,
  then contended with / deadlocked GTK-Cairo rendering ~38 screenshots in, so
  the native suite stalled at pngs=38 (need 100). glibc x64/arm64 were fine.

Fixes:
- Move VideoIORoundTripTest to LAST in DEFAULT_TEST_CLASSES. It takes no
  screenshot, so every baseline keeps master's exact order/state; its
  encode/decode now runs only after all 100 captures.
- Remove the startup VIDEOIO_DIAG block (native compile coverage still comes
  from the test referencing VideoIO; the diag added nothing but early init).
- Make the round-trip worker a daemon so a blocked native media call can never
  keep the suite process alive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…API)

Thread.setDaemon(boolean) is not in the Codename One CLDC11 whitelist, so the
bytecode-compliance check rejected it, failing hellocodenameone-common and
every platform build that depends on it (Android/iOS/Mac/Linux/JS).

The daemon flag was only defensive; the real fix -- running the VideoIO
round-trip test last so its native media worker can't perturb screenshots --
does not need it. The worker's native calls are already bounded (<=30s waits),
so it finishes on its own after the suite has captured every screenshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The watch target builds the shared CN1VideoIO.m, but watchOS marks the entire
AVFoundation video stack (AVAssetReader / AVAssetWriter / AVAssetImageGenerator
and the AVVideoCodecType* constants) API_UNAVAILABLE, so the watch build failed
to compile. (tvOS and iOS have these APIs and built fine.)

- CN1VideoIO.m: wrap the AVFoundation imports + reader/writer state + helpers +
  real entry points in #if !TARGET_OS_WATCH, and provide watchOS stub entry
  points (return unsupported) so the shared translated IOSNative bytecode still
  links.
- IOSImplementation.getVideoIO(): return null on the Watch and TV targets, so
  VideoIO.isSupported() is correctly false there (matches the documented
  platform support) and the native video methods are never called at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 216 screenshots: 216 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

shai-almog and others added 3 commits June 30, 2026 20:52
The @since-tags gate only scanned CodenameOne/src, so hallucinated/garbage
@SInCE markers in the platform ports slipped through: three "@SInCE 8.0"
(a release that doesn't exist; the latest is 7.0.x) and three "@SInCE 12130"
(a stray build number) across JavaSEPort, AndroidImplementation, BillingSupport,
Executor and JavaFXLoader. All pre-existing, all corrected to @SInCE 7.0
(a released tag where those features already shipped).

check-since-tags.sh now also scans the CN1-authored implementation packages
under Ports/ and maven/ (**/com/codename1/**), in a LENIENT mode that only
rejects a non-released @SInCE whose leading version component is >= the latest
released major (e.g. 8.0, 7.1, 12130) -- i.e. a hallucinated current/future
release. Legacy/upstream refs that legitimately sit next to that code (Java's
"@SInCE 1.3" in the adapted Base64, etc.) are left alone, and vendored trees
outside com/codename1 are never scanned. The workflow now also triggers on
port/maven source changes. Verified: the check flags a stray "@SInCE 8.0" and
is clean after the fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the animation screenshot test that encodes a six-frame counting clip
(digits 1..6 over a ramping grey background), decodes it back with the video
decoder, and lays the SIX DECODED frames out as the standard 2x3 animation
grid. A decode regression is then visible -- the digits stop appearing, decode
out of order, or come back blank. This is the visual companion to
VideoIORoundTripTest (which does the strict programmatic checks).

The native encode/decode runs off the EDT; only once the decoded frames are in
hand does it show the host form and capture the grid (an off-screen image, so
it is immune to the macOS AVFoundation colour-space shift). It is registered
after the last normal screenshot test for the same reason VideoIORoundTripTest
runs last. Where the platform cannot encode (iOS simulator H.264, unsupported
targets, a browser without WebCodecs) it reports SKIPPED and emits no
screenshot. Lossy per-codec decode is absorbed by per-baseline .tolerance
files; baselines are captured per platform from CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The grey ramp made the grid effectively black and white. Give each of the six
frames a distinct saturated hue (red, orange, yellow, green, blue, violet) with
a contrast-picked digit, so the decoded frames are told apart by colour as well
as number. The solid colour blocks survive 4:2:0 chroma subsampling cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shai-almog and others added 5 commits June 30, 2026 21:22
Media Foundation delivers/consumes RGB32 bottom-up by default, so encoded and
decoded frames came out upside down on Windows. Request a top-down layout
explicitly by setting a positive MF_MT_DEFAULT_STRIDE on both the decoder's
RGB32 output media type (pinned to the native frame size) and the encoder's
RGB32 input media type, matching Codename One's top-down RGBA convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oid baseline

The earlier MF_MT_DEFAULT_STRIDE approach corrupted the Windows decode (forcing
w*4 sheared the decoder's padded stride into green striping). Revert that and
instead handle Media Foundation's bottom-up RGB32 convention with explicit
vertical row-flips on both sides: the encoder writes rows reversed (so frames
store right-side-up) and the decoder reads rows reversed (so output is Codename
One's top-down RGBA), using MF's natural stride. Round-trip is identity and
external clips decode upright.

Also seed the Android baseline for VideoIODecodedFramesScreenshotTest from the
CI capture (the decoded 1..6 grid renders correctly there) with a tolerance file
covering codec edge ringing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e upright

Flipping rows on both encode and decode cancelled out, leaving the frames still
upside down. Media Foundation flips our top-down RGBA only on the RGB32 *input*
(encode); its H.264/HEVC decode hands RGB32 back top-down. So compensate once,
on the encoder, and read the decoder straight. Round-trip is now upright and
external clips decode the right way up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cn1FrameAt drew the decoded CGImage into a raw CGBitmapContext through an extra
vertical CTM flip (translate+scale), which mirrored every decoded frame -- the
Mac Catalyst capture of the decoded 1..6 grid came back upside down (the iOS
simulator can't encode so it was never exercised there, and the round-trip
assertion test is flip-invariant). A raw CGBitmapContext already stores row 0 as
the top of the image, so draw straight. The encoder writes a top-down
CVPixelBuffer, so the round-trip and external clips are now upright.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…frames test

Mac Catalyst has full AVFoundation, so it encodes/decodes the 1..6 clip and the
screenshot test produces there (unlike the iOS simulator, which skips). Commit
the CI capture -- now upright after the AVFoundation orientation fix -- as the
baseline with a codec-ringing tolerance, so build-mac-native has a reference to
compare against.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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