Skip to content

[Android] Video with non-square SAR (pasp / VUI) is silently downscaled to display size — 3840×2160 SAR 1:2 produces a 1920×2160 texture; request option to ignore SAR #2490

Description

@ondevChrisD

Unity version

2022.3.60f1

Unity editor platform

Windows

AVPro Video edition

Core

AVPro Video version

3.4.0

Device hardware

Quest 2 & 3

Which Android OS version are you using?

14

Unity Graphics API

Vulkan, OpenGLES 3

Video API

media3/ExoPlayer

Texture format

BGRA

Audio output

System Direct

Any other Media Player component configuration required to reproduce the issue.

Which output component(s) are you using?

No response

Any other component configuration required to reproduce the issue.

No response

The issue

Summary

When a video carries a non-square Sample Aspect Ratio, the Android player produces its output texture at the SAR-adjusted display size instead of the coded size. For a 3840×2160 file with SAR 1:2 the texture is 1920×2160 (3840 × ½ = 1920, height untouched). Half the horizontal pixels of every frame are discarded before the app can access them. There is no API to detect or disable this.

The OS-native player (Meta's media player) renders the same bytes at full 3840×2160 on the same devices.

Scope of reproduction: Quest 2 and Quest 3; Vulkan and OpenGLES 3; Texture and XRCompositionLayer output modes; VP9 Profile 0 (8-bit) and HEVC Main (8-bit). The only property that matters is SAR 1:2, carried in the mp4 pasp box and/or the HEVC VUI (aspect_ratio_idc = 255 / Extended_SAR, sar 1/2).

Steps to reproduce (self-contained — no file download needed)

Generate a bug file and a control file. Identical pixels; only the SAR tag differs:

:: BUG file — SAR 1:2 lands in BOTH the container pasp box and the HEVC VUI:
ffmpeg -f lavfi -i testsrc2=size=3840x2160:rate=30 -t 5 -vf setsar=1/2 -c:v libx265 -crf 28 -pix_fmt yuv420p -tag:v hvc1 repro_sar12.mp4

:: CONTROL file — same content, SAR 1:1:
ffmpeg -f lavfi -i testsrc2=size=3840x2160:rate=30 -t 5 -vf setsar=1/1 -c:v libx265 -crf 28 -pix_fmt yuv420p -tag:v hvc1 control_sar11.mp4

Play each with ExoPlayer + BGRA and log the produced texture size (e.g. TextureProducer.GetTexture().width).

  • Expected: both produce a 3840×2160 texture; SAR (if honored at all) applied at display time or exposed to the app.
  • Actual: the control produces 3840×2160; the SAR 1:2 file produces 1920×2160.

Evidence from our production app (Quest kiosk / 360 player)

File | Codec / bits | SAR | Texture produced -- | -- | -- | -- 3840×2160 (web-sourced) | VP9 P0, 8-bit | 1:2 (pasp) | 1920×2160 Same content re-encoded | HEVC Main, 8-bit | 1:2 (propagated into VUI by ffmpeg/x265) | 1920×2160 4096×2048 control (unrelated content) | HEVC, 8-bit | 1:1 | 4096×2048 (correct) Same HEVC bytes, SAR rewritten to 1:1 losslessly (-c copy -bsf:v hevc_metadata=sample_aspect_ratio=1/1 -aspect 3840:2160; decoded-frame md5 stream verified bit-identical) | HEVC Main, 8-bit | 1:1 | 3840×2160

Applog lines from our device, unfixed vs fixed file:

[ORACLE-BUG]     <-- paste the 1920x2160 line here
[ORACLE-FIXED]   <-- paste the 3840x2160 line here
[ORACLE-Q3]      <-- paste the Quest 3 line here

The last table row is the decisive experiment: not one pixel of the bitstream changed (framemd5-identical decode), only the SAR tag — and the texture width doubled. Since this reproduces across both graphics APIs, both output modes, and two different SoCs, the behavior sits in the shared native path below those splits (the C# side reports plane dimensions straight from AVPPlayerGetTexture).

We run the BGRA (non-OES) path; we have not tested whether the OES fast path is also affected.

Why we believe this needs a change

SAR-awareness is legitimate for flat video display, but the current implementation:

  1. Is destructive — it downscales rather than applying aspect at draw time (or upscaling), permanently discarding detail.
  2. Is silent — nothing in the API reports that the texture is not the coded resolution. This cost us a multi-week investigation across codecs, bit depths, graphics APIs and devices before we found the cause.
  3. Is wrong for VR/360 content — the projection mapping defines geometry, so SAR must not resample the source. Web-sourced VR files not uncommonly carry incorrect pasp tags; ours implies a nonsensical 8:9 display aspect for equirectangular content, which the OS player ignores.
  4. Diverges from the platform's native player on the same device and file.
  5. Contradicts AVPro's own cross-platform design — when Windows/Media Foundation had the inverse PAR problem (Pixel Aspect Ratio not respected when using Media Foundation #1237), the fix kept the unscaled texture and rendered stretched to the correct DAR at display time via DisplayUGUI/DisplayIMGUI/ResolveToTexture. The Android path pre-bakes the aspect into the texture instead.

Request

Preferred: always produce the texture at coded resolution and expose SAR through the API so applications apply it at display time — matching ExoPlayer's own model (VideoSize.pixelWidthHeightRatio) and Unity's VideoPlayer. Alternatively a PlatformOptionsAndroid flag such as ignoreSampleAspectRatio. At minimum, if resampling stays the default for back-compat, make it non-destructive (upscale, never downscale) and emit a log line stating the texture was resized due to SAR.

Worth noting the plugin's own design already follows the model we're asking for: AVPPlayerVideoTrackInfo carries dimensions, transform, stereoMode, bitsPerComponent and yCbCrTransform — display-geometry metadata carried as data for the application to apply, rather than baked into pixels. A pixelAspectRatio field alongside transform would be a small, consistent extension rather than a new concept. (Related question: does dimensions report coded or SAR-adjusted size for these files? If it reports coded size while the texture is the adjusted size, that's an internal inconsistency in itself.)

There is also an asymmetry worth flagging: per #1945, on Android the container's rotation/display transform is not applied (users reported transform coming back as identity and had to supply ExoPlayer's matrix manually), yet the container's SAR is applied, destructively. Two members of the same metadata class treated in opposite ways.

Related existing issues (searched first — this does not appear to be a duplicate)

Nothing in the Known Issues page covers SAR/PAR on any platform.

Workaround for anyone else hitting this (lossless, seconds per file)

:: HEVC (rewrites VUI + container):
ffmpeg -i in.mp4 -map 0 -c copy -bsf:v hevc_metadata=sample_aspect_ratio=1/1 -aspect 3840:2160 out.mp4

:: VP9 / container-only:
ffmpeg -i in.mp4 -map 0 -c copy -aspect 3840:2160 out.mp4

:: Audit a library:
ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of csv=p=0 file.mp4
:: anything other than 1:1 or N/A is affected

Media information

Demo testing videos (use only for research)

Original (with SAR issue)
https://s3.ap-southeast-2.amazonaws.com/ccd.odysee/SparcVR/content/default/media/videos/full/Athens%2C%20Greece%20City%20Trip%20VR360%203D%20%5BydUQtOcX_xc%5D_original.mp4

Fixed( Slice of Original )
https://s3.ap-southeast-2.amazonaws.com/ccd.odysee/SparcVR/content/default/media/videos/full/Athens%2C%20Greece%20City%20Trip%20VR360%203D%20%5BydUQtOcX_xc%5D.mp4

Comparing the Same Videos on 2 different players o2svrplayer(AVPro) & horizon
Screen captures:
o2svrplayer(AVPro):
Tets_07_A-com.canopycreative.o2svrplayer-20260707-014309_g.jpg
Image

Native horizon media player:
Tets_07_B-com.oculus.horizonmediaplayer-20260707-014643-g.jpg
Image

Logcat output

Metadata

Metadata

Assignees

Labels

AndroidAndroid platformtriageNeeds triage

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions