Skip to content

setPortraitEncodingEnabled(true) produces all-black / corrupted-color frames on Snapdragon 8 Gen 3 devices with Android 16 #3087

@zach-ooo

Description

@zach-ooo

Version

Media3 1.9.2

More version details

Also reproduced on 1.8.0 and 1.9.1. No version of Media3 resolves this issue.

Devices that reproduce the issue

  • Samsung Galaxy S24+ (SM-S926N), Android 16, Snapdragon 8 Gen 3 (SM8650), HEVC encoder: c2.qti.hevc.encoder
    • 2 cases: all-black frames
    • 1 case: light purple tinted frames
  • Samsung Galaxy S24 (SM-S921N), Android 16, Snapdragon 8 Gen 3 (SM8650)
    • 1 case: light brown tinted frames (resolved after app reinstall, before 1.9.2 update)

Common denominator: Snapdragon 8 Gen 3 + Android 16 + portrait HEVC encoding

Devices that do not reproduce the issue

  • Other Samsung Galaxy devices (various models, Android 14/15) — no reports across 16,341 supported device models.
  • Both Samsung Galaxy and iPhone play back the encoded video normally (the issue is encoding-side, not playback).

Reproducible in the demo app?

Not tested

Reproduction steps

  1. Configure Transformer with setPortraitEncodingEnabled(true) and HEVC output:
val transformer = Transformer.Builder(context)
    .setVideoMimeType(MimeTypes.VIDEO_H265)
    .setPortraitEncodingEnabled(true)
    .build()

val sizeEffect = Presentation.createForWidthAndHeight(
    targetWidth, targetHeight, Presentation.LAYOUT_SCALE_TO_FIT
)
val editedMediaItem = EditedMediaItem.Builder(MediaItem.fromUri(inputUri))
    .setEffects(Effects(emptyList(), listOf(sizeEffect)))
    .build()

transformer.start(editedMediaItem, outputPath)
  1. Encode a portrait video (e.g., 720×1280 or 1080×1920) on an affected device.
  2. Transformer completes without errorsonCompleted() callback fires normally.
  3. Inspect the output file.

Expected result

The output video contains correctly encoded frames matching the source content, in portrait orientation.

Actual result

All frames are black (or corrupted solid-color: purple/brown tint). The video file is structurally valid — correct container, valid H.265 bitstream, audio track intact — but every video frame contains no meaningful image data.

ffprobe forensic evidence (from real production output):

Metric Black frame output Normal output
I-frame size 321 bytes ~50,000+ bytes
Average pixel value 0.00 varies
Video bitrate ~13,400 bps ~3,000,000+ bps
Audio ✅ Normal ✅ Normal

The extremely small I-frame size (321 bytes for a 720×1280 frame) confirms the encoder received all-zero input — not a rendering failure, but the encoder reading from uninitialized/wrong memory.

Key observation:

Setting setPortraitEncodingEnabled(false) (default) completely resolves the issue on the same device. This confirms the bug is specifically triggered when portrait resolution (width < height) is passed directly to the hardware HEVC encoder.

Root cause analysis

We traced the full code path through Media3 1.9.2 source:

VideoSampleExporter.javaEncoderWrapper.getSurfaceInfo():

// Step 1: Initial swap for landscape encoding
if (requestedWidth < requestedHeight) {
    int temp = requestedWidth;
    requestedWidth = requestedHeight;
    requestedHeight = temp;
    outputRotationDegrees = 90;
}

// Step 2: Check against allowedEncodingRotationDegrees
if (!allowedEncodingRotationDegrees.contains(outputRotationDegrees)) {
    // When portrait=true: allowedEncodingRotationDegrees = [0]
    // 90 is NOT in [0], so swap BACK to portrait
    int temp = requestedWidth;
    requestedWidth = requestedHeight;
    requestedHeight = temp;
    outputRotationDegrees = 0;
}
  • portrait=false (default): 720×1280 → swapped to 1280×720 → encoder receives landscape → GL shader applies 90° rotation → muxer adds rotation metadata → ✅ Works
  • portrait=true: 720×1280 → swapped to 1280×720 → swap reverted to 720×1280 → encoder receives portrait directly → ❌ Black/corrupted frames on affected devices

Hypothesis: Stride/DMA buffer alignment bug

The Qualcomm HEVC hardware encoder (c2.qti.hevc.encoder) on Snapdragon 8 Gen 3 with Android 16 appears to have a stride calculation bug when KEY_WIDTH < KEY_HEIGHT:

  1. Encoder is configured with portrait resolution (e.g., width=720, height=1280)
  2. GL pipeline correctly renders 720×1280 frames to the encoder's input Surface
  3. Encoder's DMA engine reads from the Surface buffer assuming width >= height for stride calculation
  4. DMA reads from incorrect memory offsets → reads uninitialized (zero) or stale memory
  5. Result: structurally valid H.265 bitstream with all-black or corrupted-color frames

Supporting evidence:

  • Media3 documentation warning: "Enabling portrait encoding is likely to result in more failures" (Transformer.java L330)
  • Android CTS gap: CodecEncoderSurfaceTest does not include portrait resolution test cases → OEMs may not validate portrait encoding
  • Independent confirmation: OpenCV #23570 reports identical behavior (portrait→artifacts, landscape→fine) on Pixel 4a 5G
  • Existing vendor workarounds in Media3: DefaultEncoderFactory already contains overflow workarounds for SM8550, SM7450, SM6450 encoder bugs
  • Color variation explained: Black = zeroed memory, purple/brown tint = stale UV plane data in uninitialized buffer regions

Suggested fix direction

Consider adding a device-specific quirk in VideoSampleExporter or DefaultEncoderFactory that forces landscape encoding (the default path) when the target encoder is known to have portrait stride bugs, even when setPortraitEncodingEnabled(true) is set. Media3 already uses this pattern for other encoder quirks (e.g., deviceNeedsLowerOperatingRateAvoidingOverflowWorkaround).

Related issues

Media

This issue was reported by end users in production. We do not have direct access to the affected devices, so we cannot provide:

  • Source video files (private user content)
  • adb bugreport output
  • MediaCodec logs

However, we were able to retrieve and analyze the encoded output files from our server. The ffprobe forensic data above is from these real production outputs and fully characterizes the corruption pattern.

A minimal reproduction should be achievable with any portrait video (720×1280 or 1080×1920) encoded with setPortraitEncodingEnabled(true) + MimeTypes.VIDEO_H265 on a Galaxy S24/S24+ running Android 16.

Bug Report

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions