-
Notifications
You must be signed in to change notification settings - Fork 749
Description
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
- Configure
TransformerwithsetPortraitEncodingEnabled(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)- Encode a portrait video (e.g., 720×1280 or 1080×1920) on an affected device.
- Transformer completes without errors —
onCompleted()callback fires normally. - 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.java → EncoderWrapper.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 → ✅ Worksportrait=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:
- Encoder is configured with portrait resolution (e.g.,
width=720, height=1280) - GL pipeline correctly renders 720×1280 frames to the encoder's input Surface
- Encoder's DMA engine reads from the Surface buffer assuming
width >= heightfor stride calculation - DMA reads from incorrect memory offsets → reads uninitialized (zero) or stale memory
- 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:
CodecEncoderSurfaceTestdoes 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:
DefaultEncoderFactoryalready 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
- output video has flipped width and height #2788 — Portrait encoding behavior explanation (closed, different issue — rotation metadata question)
GlUtil::focusRenderTargetshould explicitly check and set theGL_DRAW_BUFFERstate when switching from surfaceless EGL contexts to ones with a surface. #2982 — GL_DRAW_BUFFER fix in 1.9.2 (we confirmed this does NOT resolve our issue)
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 bugreportoutput- 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
- You will email the zip file produced by
adb bugreportto android-media-github@google.com after filing this issue.