@@ -221,10 +221,12 @@ Patch the OHCodec surface path to expose an opaque `AVOHCodecBuffer` through
221221installed ` libavcodec/ohcodec_surface.h ` . The public functions
222222` av_ohcodec_release_buffer() ` and ` av_ohcodec_render_buffer_at_time() ` make one
223223native render/drop decision. An atomic flag guarantees that the native output
224- is decided at most once. If the last frame reference is released without an
225- explicit decision, the fallback unconditionally frees/drops the output rather
226- than rendering it. The opaque token retains FFmpeg's decoder reference until
227- the frame buffer is released.
224+ is decided at most once. A repeated decision through another retained frame
225+ view returns ` AVERROR(EALREADY) ` rather than reporting a second successful
226+ queue operation. If the last frame reference is released without an explicit
227+ decision, the fallback unconditionally frees/drops the output rather than
228+ rendering it. The opaque token retains FFmpeg's decoder reference until the
229+ frame buffer is released.
228230
229231The policy is implemented by
230232[ ` 0055-ohcodec-explicit-surface-release.patch ` ] ( ports/ffmpeg/0055-ohcodec-explicit-surface-release.patch ) .
@@ -246,13 +248,18 @@ The installed-package verifier requires the header and both symbols.
246248The OHOS package carries a small public overlay ABI that must stay paired with
247249the QtAVCore OHCodec backend. This API controls direct surface output only and
248250makes no native-buffer interop or zero-copy texture claim. Android and Windows
249- packages are unchanged.
251+ packages are unchanged. QtAVCore maps ` AVERROR(EALREADY) ` to a stale import;
252+ the interop must not install a new pending producer association because no
253+ second native buffer or frame-available callback will be produced.
250254
251255Validation requires ` scripts/build-ohos.ps1 ` , ` cmake/verify-install.cmake ` ,
252256QtAVCore OHOS shared/static target compilation, an installed
253257` QtAV::HWOHCodec ` consumer, and connected-device HAP coverage that observes
254258successful timed presentations and explicit drops. The 2026-08-05 Mate 60 Pro
255- run completed 30 timed H.264 presentations and three explicit drops.
259+ run completed 30 timed H.264 presentations and three explicit drops. The
260+ 2026-08-13 player regression additionally held ` legend.mkv ` at 0.5x through
261+ repeated full-screen swapchain recreation with balanced native release and
262+ callback counts.
256263
257264### Retirement condition
258265
@@ -535,3 +542,64 @@ Revisit the target-specific override when FFmpeg changes the semantics of
535542` --enable-small ` , when binary-size limits require a separate dependency
536543profile, or when repeatable device measurements show that ` -O2 ` or another
537544portable optimization level provides better sustained throughput.
545+
546+ ## FD-009: Report a suppressed Android MediaCodec output release as stale
547+
548+ - Date: 2026-08-13
549+ - Status: Accepted
550+ - Scope: Android FFmpeg MediaCodec output ownership and the installed public
551+ release-result contract
552+
553+ ### Context
554+
555+ FFmpeg 8.1.2 shares one atomic release flag across all retained
556+ ` AVMediaCodecBuffer ` views. Its MediaCodec helpers correctly invoke the native
557+ release only once, and also suppress a release after decoder flush changes the
558+ buffer serial. Both suppressed cases nevertheless returned success. A caller
559+ could therefore register an AImageReader timestamp association and wait for a
560+ frame-available callback even though no native output had been queued.
561+
562+ This becomes reachable when Android republishes the same Surface during a
563+ size/fullscreen refresh. QtAVCore invalidates the old presentation epoch, then
564+ may redraw the retained latest frame in the new epoch. Epoch/timestamp tracking
565+ cannot distinguish a real new producer release from FFmpeg's successful no-op;
566+ the missing callback is an ownership fact, not a slow-producer timeout or an
567+ unknown coded-size problem.
568+
569+ ### Decision
570+
571+ Patch both ` av_mediacodec_release_buffer() ` and
572+ ` av_mediacodec_render_buffer_at_time() ` so zero means the current call actually
573+ invoked the native output release. Return ` AVERROR(EALREADY) ` when another view
574+ already released the shared buffer or decoder flush retired it. Keep all other
575+ native error values unchanged.
576+
577+ Document the result in installed ` libavcodec/mediacodec.h ` and require the
578+ contract from the Android branch of ` cmake/verify-install.cmake ` . QtAVCore owns
579+ the higher-level mapping to applied, stale, and failed; the dependency layer
580+ does not know about renderer epochs or presentation policy.
581+
582+ ### Rejected alternatives
583+
584+ - Suppressing redraws by timestamp/frame identity would be an application
585+ workaround and could discard a valid frame after a real generation change.
586+ - Waiting less or treating a timeout as success would retain the false pending
587+ association and hide the ownership violation.
588+ - Reopening MediaCodec or probing the stream dimensions on every surface-size
589+ change does not make an already-released output emit another callback.
590+ - Keeping the result only inside QtAVCore is impossible because the public
591+ FFmpeg helper previously erased whether it performed the native release.
592+
593+ ### Consequences and validation
594+
595+ Existing callers that check only for negative failure now observe a truthful
596+ stale result. QtAVCore's bool convenience functions remain source compatible
597+ but return true only for a real applied release; result-returning APIs preserve
598+ the three-way distinction.
599+
600+ Any change to this contract must run the directly affected Android dependency
601+ build and ` cmake/verify-install.cmake ` , followed by QtAVCore Android
602+ shared/static builds and installed-package consumers. Connected-device
603+ validation must exercise a Surface/fullscreen refresh while MediaCodec
604+ hardware output remains active and confirm that presentation continues without
605+ an unbounded pending image wait.
0 commit comments