Skip to content

Fix non-monotonic output timestamps from c2.mtk.hevc.decoder - #3352

Draft
2bitoperations wants to merge 2 commits into
androidx:releasefrom
2bitoperations:fix/mediatek-hevc-output-timestamp-reorder
Draft

Fix non-monotonic output timestamps from c2.mtk.hevc.decoder#3352
2bitoperations wants to merge 2 commits into
androidx:releasefrom
2bitoperations:fix/mediatek-hevc-output-timestamp-reorder

Conversation

@2bitoperations

@2bitoperations 2bitoperations commented Jul 29, 2026

Copy link
Copy Markdown

Fixes #3347

Problem

On Google TV Streamer 4K (MediaTek MT8696, c2.mtk.hevc.decoder),
decoding specific HEVC Main10 content causes the codec to return
output buffers with non-monotonic presentationTimeUs, while the
order in which those buffers are released remains correct — confirmed
by comparing each buffer's release-order rank against its
timestamp-sorted rank: the two never diverge by more than a few
positions, and the timestamp values themselves are individually
correct for the frame they belong to.

Reproduced independently of ExoPlayer, using a minimal test harness
built directly on AMediaCodec/AMediaExtractor (no ExoPlayer, no
Java MediaCodec) against the same file: identical non-monotonic
timestamp pattern. This confirms the defect is in the decoder/HAL
output, not in ExoPlayer's handling of it.

MediaCodecRenderer processes output buffers in raw release order
but uses the codec-reported presentationTimeUs directly for the
render/drop decision in MediaCodecVideoRenderer. When a buffer's
timestamp is behind the previous one, it's treated as arriving too
late and dropped. Measured on affected content: ~30% of decoded video
frames dropped, sustained throughout playback, reproduced in the
Media3 demo app as well as Plex, Jellyfin, and other ExoPlayer-based
players on the same device/file.

Fix

Re-derive each output buffer's presentationTimeUs from its release
order and the stream's known frame duration (from Format.frameRate),
instead of trusting the codec-reported value. Buffer release order is
left untouched — buffers are not held or reordered.

Testing

Verified on the affected device (Google TV Streamer 4K) against three
files that reliably reproduce the issue on unpatched main,
including a 90-second real-world clip: dropped-frame count goes from
~30% to 0, and the fix was visually confirmed on-device — direct
observation of smooth playback, not just log metrics. HOWEVER, with this approach, video and audio lose sync.

An earlier approach that buffered and released output buffers in
sorted-timestamp order was also tried and rejected: it also eliminated
logged drops, but produced visible playback artifacts on-device. That
result is why this PR does not reorder buffers.

On Google TV Streamer 4K (MT8696, c2.mtk.hevc.decoder), specific HEVC
Main10 content causes the codec to return output buffers with
non-monotonic presentationTimeUs, while the buffer release order
itself remains correct. MediaCodecRenderer processes output buffers
in raw release order and uses the codec-reported timestamp directly
for the render/drop decision, causing MediaCodecVideoRenderer to treat
these buffers as arriving too late and drop them.

Measured on affected content: ~30% of decoded video frames dropped,
sustained throughout playback.

This re-derives each output buffer's presentationTimeUs from its
release order and the stream's known frame duration, instead of
trusting the codec-reported value. Buffer release order is untouched.

Verified on-device on the affected hardware/content: dropped frames
go to 0, playback confirmed visually smooth by direct observation.
@google-cla

google-cla Bot commented Jul 29, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

The per-frame duration was rounded to the nearest microsecond once,
then multiplied by a growing frame index. Any fractional-microsecond
remainder in the true frame duration (e.g. 41708.333...us for
24000/1001fps content) was silently dropped every frame and never
recovered, so the error accumulated linearly with runtime: ~0.7ms
over a 90-second clip (undetectable, and the only length tested
before this fix), but ~58ms by the end of a 2-hour movie — enough to
produce audible/visible A/V desync that gets worse the longer
playback continues.

Fix: keep the per-frame duration unrounded and compute each buffer's
offset independently as round(frameIndex * unroundedDurationUs),
rounding only the final result. Error no longer accumulates; it stays
bounded to at most +/-0.5us for the life of the stream.

Found via real-world testing in a third-party player (Plezy) on full-
length movies, where the 90-second on-device test clips used to
validate the original fix could never have revealed it.
@2bitoperations

Copy link
Copy Markdown
Author

Pushed a fix for a second bug: audio/video would slowly drift out of
sync over long playback. Cause was an accumulating rounding error in
the per-frame timestamp math — invisible on a 90s test clip, ~58ms by
the end of a 2-hour movie. Fixed by rounding only the final offset,
not the per-frame duration.

Tested in two real players on the affected hardware (Jellyfin Android
TV, and Plezy). 40 minutes of real playback, no drift, no stutter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant