Skip to content

fix: request an H.264 level that matches the Android output stream - #27

Merged
hatemragab merged 1 commit into
v-chat-sdk:mainfrom
TykanN:fix/android-h264-level-selection
Sep 17, 2026
Merged

hatemragab merged 1 commit into
v-chat-sdk:mainfrom
TykanN:fix/android-h264-level-selection

Conversation

@TykanN

@TykanN TykanN commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Problem

On Android the plugin does not request an H.264 profile/level, so Media3's DefaultEncoderFactory fills in the highest level the encoder advertises (see adjustMediaFormatForH264EncoderSettings → EncoderUtil.findHighestSupportedEncodingLevel). This is the behaviour discussed in androidx/media#2603: many encoders write that level into the stream unchanged, so a 720p or 1080p export can end up declared as High@L6.0/L6.2 on devices whose encoder advertises Level 6.x (e.g. recent Samsung models).

Decoders that cap H.264 at Level 5.x — notably iOS/iPadOS and Safari (<video>, AVPlayer, Photos import) — reject those files even though the actual stream would fit comfortably in Level 3.1–4.2. The level is purely a declaration, so the file is over-specified relative to its own content.

Fix

Request the lowest H.264 level that covers the output stream instead of leaving the level unset:

  • New VVideoH264Level.minimumLevel(width, height, frameRate) picks the level from ITU-T H.264 Table A-1 (MaxFS, MaxMBPS, and the sqrt(8·MaxFS) per-dimension cap). Bitrate limits are intentionally not modelled: the request is advisory and Media3 clamps the bitrate to the encoder's range.
  • VVideoCompressionEngine reads the source track's frame rate (the export keeps it) and HDR transfer via MediaExtractor, computes the output size the same way the existing validation does (crop plan → aspect-ratio-preserving dimensions), and passes High@<level> through DefaultEncoderFactory.setRequestedVideoEncoderSettings(...).
  • Only for video/avc output and API 26+, where Media3 itself already defaults to the High profile. Below API 24 Media3 ignores profile/level, and on API 24–25 it defaults to Baseline, so behaviour there is unchanged.
  • Skipped for HDR sources (ST2084/HLG) so Media3 keeps choosing the HDR profile.

Examples of the requested level: 848×480@30 → 3.1, 1280×720@30 → 3.1, 1920×1080@30 → 4.0, 1920×1080@60 → 4.2, 3840×2160@30 → 5.1, 3840×2160@60 → 5.2.

Compatibility

  • Media3 semantics are preserved: if the encoder does not advertise the requested level, DefaultEncoderFactory resets the request and falls back to today's behaviour. Encoders that derive the level themselves are unaffected.
  • No Dart API, channel-schema, permission, or minimum-platform changes. H.265 output is untouched (Media3 does not adjust the level for HEVC).
  • If the frame rate cannot be read from the container, 60 fps is assumed so the level is over- rather than under-declared.

Testing

  • cd example && flutter build apk --debug
  • cd example/android && ./gradlew :v_video_compressor:testDebugUnitTest — new VVideoH264LevelTest covers the Table A-1 mapping for common outputs, macroblock rounding, orientation independence, the per-dimension cap, and invalid input.
  • Not yet exercised on a device whose encoder advertises Level 6.x in this exact form. The declared level can be checked in the output's avcC box (AVCLevelIndication) or with ffprobe -show_streams (level); confirmation from such devices is welcome.

Refs: androidx/media#2603

Media3's DefaultEncoderFactory requests the highest H.264 level the
encoder advertises when the caller does not specify one, so exports
could be declared as High@L6.x and be rejected by decoders capped at
Level 5.x such as iOS and Safari (androidx/media#2603).

Derive the lowest level covering the output resolution and frame rate
from H.264 Table A-1 and request High@<level> through
VideoEncoderSettings for video/avc output on API 26+. HDR sources are
left to Media3's own profile selection.
@hatemragab
hatemragab merged commit 9e97b8f into v-chat-sdk:main Sep 17, 2026
2 checks passed
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.

2 participants