Skip to content

Fix AV1 videos being uploaded without transcoding, and two AV1 playback defects - #488

Merged
23rd merged 4 commits into
forkgram:devfrom
ThatHunky:av1-transcode-fix
Jul 27, 2026
Merged

Fix AV1 videos being uploaded without transcoding, and two AV1 playback defects#488
23rd merged 4 commits into
forkgram:devfrom
ThatHunky:av1-transcode-fix

Conversation

@ThatHunky

Copy link
Copy Markdown

Fixes the AV1 send-side defect reported in #487, plus two related AV1 playback defects.

Not built or device-tested. I have no Android SDK/NDK set up, and gifvideo.cpp needs an NDK rebuild. The diagnosis below is evidence-backed and the change applies cleanly, but please build and test before merging. Flagging this up front rather than letting a reviewer discover it.

1. AV1 videos are uploaded without transcoding — gifvideo.cpp

nGetVideoInfo reports PARAM_NUM_SUPPORTED_VIDEO_CODEC from a hardcoded allowlist:

codec_id == AV_CODEC_ID_H264 || H263 || MPEG4 || VP8 || VP9 ||
(sdkVersion > 21 && codec_id == AV_CODEC_ID_HEVC);

AV_CODEC_ID_AV1 is absent. The consequence chain:

gifvideo.cpp returns 0 → PhotoViewer sets videoConvertSupported = false → the entire compression-setup block is skipped, so resultWidth/compressionsCount/selectedCompression are never populated → compressItem.setState(videoConvertSupported && compressionsCount > 1, …) disables the compression UI → no videoEditedInfo carrying convert params → the SendMessagesHelper gate videoEditedInfo != null && needConvert() fails → the original file is uploaded verbatim.

It fails silently because it isn't an error path — it's a disabled feature. No error, no compression option, no indication the file went out unprocessed.

The fix adds AV1 to the allowlist, gated on API 29 where AOSP ships c2.android.av1-dec, mirroring the existing HEVC guard. Note the convert pipeline decodes via platform MediaCodec, not the bundled FFmpeg — so no build_ffmpeg.sh change is required.

Evidence

Device captures on a Pixel 9 Pro (Telegram 12.9.1, Android 17), logging enabled:

Source Res Route Converted? Codecs instantiated Uploaded from
AV1 4K share sheet cache/sharing/<original>
HEVC 1080p picker exynos.hevc.decoder + exynos.h264.encoder generated cache file
AV1 4K picker c2.google.av1.decoder only — no encoder /Download/… verbatim
HEVC 4K picker exynos.hevc.decoder + exynos.h264.encoder generated cache file

Rows 3 and 4 are the controlled pair — identical resolution, duration, HDR characteristics and route; only the codec differs:

begin convert …/TEST_4K_HEVC_control.mp4 rWidth = 1080 rHeight = 1920 oWidth = 2160 oHeight = 3840
compression completed time=27387 needCompress=true w=1080 h=1920 file size=23,8 MB encoder_name=c2.exynos.h264.encoder

For AV1, neither line appears, no encoder is created, and upload starts at file_part=0 straight from the on-disk path. The AV1 file was freshly re-encoded (different SHA256 and different decoded-frame MD5), ruling out dedup.

2. One transient decoder error permanently disabled AV1 — VideoPlayer.java

onPlayerError wrote unsupport_video/av01 on any MediaCodecDecoderException mentioning av1, and nothing ever cleared it — no expiry, no retry, no reset on upgrade. supportsHardwareDecoder() then returned false for the life of the install, which gates thumbnails, playback, and strips AV1 URIs from the quality list. Codec-instance exhaustion from opening several videos at once is enough to trigger it, after which a device with working hardware AV1 behaves as if it had none. Only recovery was clearing app data.

Replaced with a failure counter (threshold 3) that expires after 30 days and self-resets on app upgrade or when the verdict goes stale, and CodecException.isTransient() failures aren't counted at all.

3. Software decoders were never considered — VideoPlayer.java

supportsHardwareDecoder() skipped every non-hardware-accelerated codec. Android 10+ ships a software AV1 decoder, but the method was used as a blanket gate, so on devices without hardware AV1 the video track was dropped entirely while audio kept playing — the widely reported "AV1 plays sound with a black screen". The video wasn't failing to decode; it was never being decoded.

Added supportsSoftwareDecoder() and supportsDecoder(codec, w, h) (software permitted up to 1080p), used for the three hard gates that discarded the track. supportsHardwareDecoder() is retained for quality-selection heuristics, where preferring hardware is correct.

Review notes

Things a reviewer should push on, stated rather than hidden:

  • Retry termination. The AV1 error path now retries once per player instance (triedAv1CodecFallback), reset on first-frame render and in releasePlayer — neither is on the retry path. Below the blacklist threshold filterByCodec may strip nothing, so an unbounded retry would loop forever.
  • isCodecBlacklisted() writes SharedPreferences from what reads as a predicate (the self-healing reset). Self-limiting: only reached on a cache miss.
  • Two unsynchronized static caches. Upstream already had one; this adds a second following the same pattern. Synchronizing would change behavior beyond this fix's scope.
  • reportCodecFailure's read-modify-write isn't atomic — concurrent failures can lose an increment. That only makes blacklisting slower, never wrong.
  • Residual: three genuinely corrupt AV1 files within 30 days can still blacklist a working codec until the TTL lapses. Wiring a success-path reset needs per-URI codec tracking in onRenderedFirstFrame, which felt too invasive here.

Also reported upstream at https://bugs.telegram.org/c/64017 (DrKLO/Telegram has issues disabled).

nGetVideoInfo reports PARAM_NUM_SUPPORTED_VIDEO_CODEC from a hardcoded
allowlist that omits AV1. PhotoViewer gates videoConvertSupported on it,
so for AV1 input the whole compression setup block is skipped and the
file is uploaded verbatim - no downscale, no transcode, no error.
Any MediaCodecDecoderException mentioning av1 wrote a persistent flag that
was never cleared, so a single transient failure (codec exhaustion, low
memory, one bad file) disabled AV1 app-wide for the life of the install.

Replace it with a failure counter (threshold 3) that expires after 30 days
and resets on app upgrade, and skip counting CodecException.isTransient().
Also allow software decode below 1080p for the hard gates that previously
discarded the video track outright, leaving audio with a black frame.
@23rd
23rd merged commit 3ff8ab2 into forkgram:dev Jul 27, 2026
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