This document records dependency-build choices whose rationale is not obvious
from the installed package contract alone. ARCHITECTURE.md defines the
current structure and invariants; this file explains why narrowly scoped local
patches exist, which alternatives were rejected, how they are validated, and
when they may be removed.
Review the affected decision whenever the pinned vcpkg baseline, FFmpeg, libplacebo, Visual Studio LLVM toolchain, or Windows SDK is upgraded. A local compatibility patch should be retired once the pinned upstream versions provide the same behavior and the stated validation passes without it.
- Date: 2026-08-03
- Status: Accepted
- Scope: Windows x64
clang-cl/lld-linkFFmpeg build
The Windows dependency package uses Visual Studio's clang-cl because the
pinned libplacebo C build does not support the MSVC cl.exe compiler. FFmpeg
is built with --enable-lto, producing LLVM bitcode archives that are linked
by lld-link under the MSVC ABI.
FFmpeg's MLP x86 filter optionally defines non-local dispatch labels such as
ff_mlp_firorder_* inside GNU inline assembly and references them from C.
Under Windows clang-cl LTO those labels do not remain externally resolvable at
the final archive/link boundary, causing unresolved-symbol failures even
though FFmpeg's separately assembled NASM objects are otherwise compatible.
Keep FFmpeg LTO enabled and keep the external NASM SIMD paths enabled. Under
the specific __clang__ && _MSC_VER combination, compile out only the MLP
GNU-inline-assembly non-local-label implementation and use FFmpeg's C fallback
for that filter.
The policy is implemented by:
0052-allow-clang-cl-lld-link-lto.patch, which admits the otherwise compatible compiler/linker driver pair; and0054-fix-clang-cl-mlp-lto-labels.patch, which applies the narrow MLP exception.
- Disabling LTO for all of FFmpeg would discard a deliberate package-wide optimization to work around one inline-assembly implementation.
- Disabling all x86 assembly would unnecessarily remove compatible external NASM acceleration.
- Switching the dependency package to
cl.exewould make the pinned libplacebo build unsupported. - Publishing the assembly labels outside their current translation boundary would be a broader FFmpeg source change with higher maintenance risk.
TrueHD/MLP decoding remains available through the C implementation for the affected filter. The rest of FFmpeg and its external assembly retain LTO and SIMD support.
Validation requires scripts/build-windows.ps1, the installed-package
verifier, and a final QtAVCore static and shared link. Audio regression
coverage must continue to include TrueHD/MLP decode when generated media is
available.
Remove the MLP patch only when the pinned FFmpeg/LLVM combination can link the
unmodified implementation with LTO through lld-link, followed by the complete
Windows dependency build, verifier, QtAVCore static/shared link, and TrueHD/MLP
decode validation.
- Date: 2026-08-03
- Status: Accepted
- Scope: Windows x64 libplacebo D3D11 package metadata
libplacebo's D3D11 backend translates generated SPIR-V to HLSL through
SPIRV-Cross. The repository package is fully static, but the pinned libplacebo
Meson definition requests the shared SPIRV-Cross C dependency. In addition,
the pinned spirv-cross-c pkg-config metadata publishes the C wrapper archive
without every C++ backend/core archive called by that wrapper.
The incomplete metadata can build the dependency in isolation yet fail later
when linking QtAVCore's D3D11 shared library or an installed-package consumer.
Windows SDK libraries emitted as shlwapi.lib or version.lib also need
normalization before FFmpeg/pkg-config consumers can interpret the flags
consistently.
Enable libplacebo D3D11 only for the Windows dependency target and use the
static SPIRV-Cross C package. Publish the ordered static C, GLSL, HLSL, MSL,
CPP, reflection, utility, and core archive closure in libplacebo.pc, together
with normalized Windows system-library flags.
The policy is implemented by:
- the Windows
spirv-crossdependency inports/libplacebo/vcpkg.json; 0002-fix-windows-shlwapi-linkage.patch;0003-use-static-spirv-cross.patch; and- the installed pkg-config normalization in
ports/libplacebo/portfile.cmake.
QtAVCore exposes only its D3D11 renderer on Windows. Installing libplacebo's other compiled capabilities for the shared Android/OHOS dependency policy does not create a QtAVCore Windows OpenGL or Vulkan backend.
- Using shared SPIRV-Cross libraries would violate the Windows static dependency-package contract.
- Adding the missing archives directly to QtAVCore would leak a dependency-port implementation detail and leave other installed consumers broken.
- Disabling D3D11 would prevent the required Windows libplacebo rendering path.
- Enabling QtAVCore Vulkan or OpenGL on Windows would not repair D3D11's static shader-translation closure and is outside the supported backend policy.
The installed libplacebo pkg-config module is intentionally more explicit than the pinned upstream module. Archive order is part of the Windows static-link contract and must be reviewed during SPIRV-Cross upgrades.
Validation requires scripts/build-windows.ps1, verifier checks for
PL_HAVE_D3D11 and SPIRV-Cross headers, QtAVCore static/shared D3D11 builds,
and static/shared external QtAV::RenderD3D11 package consumers.
Remove the overlay workaround when the pinned libplacebo and SPIRV-Cross/vcpkg metadata natively expose the complete static closure and all validation above passes without local archive-list or Windows-library normalization.
- Date: 2026-08-03
- Status: Accepted
- Scope: Windows x64 clang-cl libplacebo allocation boundary
The first working static D3D11 link failed at runtime while libplacebo created
a D3D11 render pass. Debugger inspection localized the access violation to
pl_d3d11_pass_create: clang-cl emitted an aligned SIMD load, while the
private object returned through libplacebo's parent/public-to-private allocator
boundary was only 8-byte aligned.
The allocator used alignof(max_align_t) both for its flexible data member and
for the offset from a public object to its private object. Under the exercised
Windows x64 MSVC ABI this did not guarantee the 16-byte address required by the
generated code. Fixing only one side of that boundary would leave another
allocation layout inconsistent.
On _WIN64, define the allocator boundary alignment as 16 bytes, apply it to
the flexible data member, and use the same value when rounding the public to
private object offset. Other targets retain their native
alignof(max_align_t) behavior.
The policy is implemented by
0004-align-windows-clang-allocations.patch.
- Disabling compiler optimization or SIMD generation would hide the allocator contract violation and impose broad performance costs.
- Patching only the D3D11 pass structure would leave other libplacebo private allocations exposed to the same boundary mismatch.
- Switching to
cl.exeis not supported by the pinned libplacebo C build. - Adding a QtAVCore-side allocation workaround cannot fix objects allocated inside libplacebo.
Windows x64 libplacebo allocations can consume a small amount of additional padding. No public ABI type changes, and non-Windows targets are unaffected.
Validation requires the complete Windows dependency build and verifier, followed by WARP and hardware D3D11 renderer creation, Advanced Color output, static/shared builds, and bounded real Dolby Vision Profile 5 playback through libplacebo D3D11. A successful link alone is insufficient because this was a runtime alignment failure.
Remove the patch only after an upstream allocator fix with an equivalent Windows x64 guarantee is present in the pinned libplacebo version and the full D3D11 runtime validation passes with the local patch absent.
- Date: 2026-08-05
- Status: Accepted
- Scope: OHOS arm64 FFmpeg OHCodec surface output
FFmpeg 8.1.2's OHCodec surface decoder owns the output index and decoder
reference in a private structure. Its public frame exposes an OH_AVBuffer
pointer, while the last frame release implicitly renders unless the buffer is
marked discard. QtAVCore therefore cannot implement deterministic present,
timed-present, and drop decisions without copying private FFmpeg state or
depending on destructor timing.
The OHCodec buffer-output branch is not an alternative: it maps the native
buffer and copies pixels through av_image_copy2(), violating the project's
direct-surface and future zero-CPU-copy boundaries.
Patch the OHCodec surface path to expose an opaque AVOHCodecBuffer through
installed libavcodec/ohcodec_surface.h. The public functions
av_ohcodec_release_buffer() and av_ohcodec_render_buffer_at_time() make one
native render/drop decision. An atomic flag guarantees that the native output
is decided at most once. A repeated decision through another retained frame
view returns AVERROR(EALREADY) rather than reporting a second successful
queue operation. If the last frame reference is released without an explicit
decision, the fallback unconditionally frees/drops the output rather than
rendering it. The opaque token retains FFmpeg's decoder reference until the
frame buffer is released.
The policy is implemented by
0055-ohcodec-explicit-surface-release.patch.
The installed-package verifier requires the header and both symbols.
- Reconstructing FFmpeg's private OHCodec structure in QtAVCore would couple the backend to an unstable layout and violate the library boundary.
- Calling OHCodec directly from the exposed
OH_AVBufferis insufficient because the output index and retained decoder are not public. - Relying on upstream last-frame destruction leaves presentation timing implicit and cannot express a deterministic drop.
- Using the FFmpeg buffer-output branch would introduce a decoded-pixel CPU map and copy and still would not provide native-buffer GPU ownership.
The OHOS package carries a small public overlay ABI that must stay paired with
the QtAVCore OHCodec backend. This API controls direct surface output only and
makes no native-buffer interop or zero-copy texture claim. Android and Windows
packages are unchanged. QtAVCore maps AVERROR(EALREADY) to a stale import;
the interop must not install a new pending producer association because no
second native buffer or frame-available callback will be produced.
Validation requires scripts/build-ohos.ps1, cmake/verify-install.cmake,
QtAVCore OHOS shared/static target compilation, an installed
QtAV::HWOHCodec consumer, and connected-device HAP coverage that observes
successful timed presentations and explicit drops. The 2026-08-05 Mate 60 Pro
run completed 30 timed H.264 presentations and three explicit drops. The
2026-08-13 player regression additionally held legend.mkv at 0.5x through
repeated full-screen swapchain recreation with balanced native release and
callback counts.
Remove the patch when the pinned upstream FFmpeg OHCodec decoder exposes an equivalent retained, single-decision surface-output API. Re-run the dependency build and verifier, QtAVCore shared/static consumption, and the connected present/drop lifecycle matrix before adopting that replacement.
- Date: 2026-08-07
- Status: Accepted
- Scope: Windows x64 FFmpeg 8.1.2 D3D11VA decoder lifetime
HEVC can call ff_get_format() again when an active SPS changes even when the
selected D3D11 device, pixel format, dimensions, decoder texture, and required
configuration remain unchanged. FFmpeg 8.1.2 first calls
ff_hwaccel_uninit(), then creates a new ID3D11VideoDecoder and a complete
set of output views. Reusing only QtAVCore's initialized
AVHWFramesContext therefore reuses the texture array but not the decoder.
Each decoded frame retains the old libavcodec decoder reference. ETW on Intel
Iris Xe showed the final old-frame release entering
igd11dxva64.dll/igddxvacommon64.dll allocation teardown from QtAVCore's
frame recycler. That teardown serialized the shared D3D11 device while the
renderer itself used less than one millisecond of GPU time. High temperature
amplified the duration, but the decoder reconstruction and release sequence
also reproduced in a cold, non-throttled sample.
Patch the Windows D3D11VA path with an opt-in
AVD3D11VADeviceContext::reuse_decoder field. When enabled, libavcodec stores
an AVBufferRef-owned decoder resource on the initialized
AVD3D11VAFramesContext. The resource owns the ID3D11VideoDecoder, every
output view, and a texture reference. It is reused only when the texture,
array size, profile GUID, sample dimensions, output format, and complete
decoder configuration still match. The normal FFmpeg path is unchanged when
the field is zero.
QtAVCore enables the extension only for its repository-built D3D11VA device
and also retains a compatible initialized frames context across repeated format
selection. The policy is implemented by
0057-d3d11va-reuse-compatible-decoder.patch,
and the installed-package verifier requires the public opt-in field.
- Reusing only the frames context does not retain libavcodec's decoder or output views and did not remove the Intel driver teardown.
- Moving final frame destruction to another QtAVCore thread keeps the render thread responsive to ordinary release cost but cannot prevent a driver-wide serialization on the shared device.
- Forcing software decoding or a decoded-pixel CPU copy would avoid the driver path by giving up the required D3D11VA zero-map pipeline.
- Enabling decoder reuse unconditionally would change upstream behavior for unrelated FFmpeg consumers and could preserve an incompatible decoder.
The Windows package carries a small public overlay ABI and must stay paired with the QtAVCore D3D11VA backend. A compatible decoder now lives until the frames context is destroyed; an incompatible descriptor or configuration still creates a new decoder. Android and OHOS packages do not enable this path.
Validation completed with scripts/build-windows.ps1 and
cmake/verify-install.cmake, followed by fresh QtAVCore static and shared
Release builds and 37/37 CTest in each tree. The D3D11VA lifecycle test proves
that a seek retains the same decoder texture. Post-fix Iris Xe ETW no longer
contained the recycler-to-igd11dxva64.dll decoder teardown stack; remaining
QtAV process allocation events resolved to WinUI/DirectComposition. In a
separate cold run, CPU Package peaked at 72 degrees Celsius with no thermal,
critical-temperature, or power-limit flags. After one seek the renderer
returned from three transient 5-second windows to 24.8-25.2 fps and then held
that cadence with zero coalescing and zero greater-than-80-ms gaps.
Remove the patch when the pinned upstream FFmpeg version retains an equivalent compatible D3D11VA decoder across repeated format selection, or no longer uninitializes unchanged hardware acceleration in that path. Rebuild and verify the Windows dependency package, remove the QtAVCore opt-in, pass static/shared tests, and repeat the cold Intel seek/ETW matrix before adopting the upstream replacement.
- Date: 2026-08-08
- Status: Accepted
- Scope: OHOS arm64 FFmpeg VVC/H.266 hardware decode
FFmpeg 8.1.2, official n9.0, and current upstream master provide the native
software VVC decoder and vvc_mp4toannexb bitstream filter but do not register
an OHCodec VVC wrapper. The recorded Pura X Max advertises and constructs
OMX.hisi.video.decoder.vvc for the supplied 1280x720/60 vvc1 stream, so a
version-only FFmpeg upgrade cannot expose that hardware path to QtAVCore.
Patch the OHCodec decoder family with vvc_ohcodec. Map AV_CODEC_ID_VVC to
OH_AVCODEC_MIMETYPE_VIDEO_VVC, select vvc_mp4toannexb, and reuse the same
surface-output lifetime contract as H.264/HEVC. Decoder selection must first
query OH_AVCodec_GetCapabilityByCategory(mime, false, HARDWARE); the wrapper
does not fall through to an OHCodec software component. QtAVCore's existing
hardware-open policy reopens the same stream with FFmpeg's native software VVC
decoder when the wrapper is missing, the hardware capability is unavailable,
or hardware open fails.
The policy is implemented by
0058-ohcodec-vvc-decoder.patch.
The installed-package verifier requires ff_vvc_oh_decoder and
ff_vvc_mp4toannexb_bsf in the OHOS libavcodec.a archive.
- Upgrading FFmpeg alone does not add the missing wrapper.
- Selecting an OHCodec software component would obscure which implementation ran and duplicate FFmpeg's already retained native software fallback.
- Feeding MP4 length-prefixed VVC packets directly is incompatible with the Annex-B input expected by the exercised hardware decoder.
- Adding an external VVC decoder or encoder would violate the native-decoder dependency policy and expand the distribution/licensing surface.
The wrapper is registered only in OHOS builds with OHCodec available; other
targets and FFmpeg's native VVC decoder remain unchanged. On 2026-08-08 the
Windows-hosted OHOS dependency build completed and verify-install.cmake
passed. A signed Pura X Max HAP then presented all 600 frames of the supplied
1280x720/60 sample through OMX.hisi.video.decoder.vvc, with 694 hardware
frames across EOS, pause/resume, seek/flush, explicit stop, and surface
recreation. Maximum pending output was one and the core video queue remained
zero. A forced missing-device run emitted the hardware-fallback event and
decoded 30 frames from the same media in software without a stale hardware
frame.
Remove the patch when a pinned upstream FFmpeg release provides an equivalent hardware-capability-gated OHCodec VVC wrapper with MP4-to-Annex-B conversion and the same surface-output contract. Rebuild and verify the dependency package, rebuild QtAVCore, and repeat the complete connected 600-frame plus forced-software-fallback lifecycle before adopting it.
- Date: 2026-08-10
- Status: Accepted
- Scope: Windows, Android, and OHOS dependency acquisition and Android Windows host support
QtAVCore previously allowed a missing target prefix to be restored from the newest successful GitHub Actions artifact before attempting a local build. That made local validation depend on workflow retention, transfer reliability, and a second package provenance path. Android dependency production was also documented as macOS-only even though the pinned vcpkg model and Android NDK toolchain can cross-compile the same arm64/API 28 triplet from Windows.
Dependency resolution is local-first and local-only: consume a matching prefix
only when its sibling vcpkg database and installed-package contract are valid;
otherwise run the target's repository entry point and pass
cmake/verify-install.cmake. Do not download an Actions artifact as a local
dependency fallback. CI may still upload artifacts for diagnostics or archival
inspection, but parent builds do not consume them.
Add scripts/build-android.ps1 as the Windows Android entry point. It discovers
the SDK, an already installed NDK, and CMake from Android Studio or explicit
arguments/environment variables. It prints the exact NDK revision and never
installs, upgrades, or replaces SDK components. Android remains arm64-v8a/API
28 with static dependencies and c++_static; the target prefix and vcpkg
triplet do not change.
- Retaining artifact fallback would preserve two dependency-resolution paths and would not prove that the current checkout can reproduce its package.
- Automatically invoking
sdkmanagerwould mutate the developer machine and obscure which NDK the user selected in Android Studio. - Treating an incomplete artifact download as a local prefix would bypass the vcpkg status database and installed-package verifier.
- Reusing OHOS or Windows libraries for Android would violate the target ABI and prefix-isolation contract.
The first local build can take longer and may download upstream source archives through vcpkg, but the produced binaries always come from the current checkout, overlay set, triplet, and local toolchain. Android package provenance includes the printed NDK revision. Windows, OHOS, and Android keep independent install databases and work roots.
Validation requires the directly affected local entry point and
cmake/verify-install.cmake. A Windows-hosted Android policy change additionally
requires QtAVCore static and shared arm64/API 28 cross-builds using that local
prefix. No physical-device installation is part of this dependency contract.
The Windows entry point was exercised with Android Studio CMake 4.1.2 and the
already installed NDK 30.0.15729638-beta2. It locally produced and verified
the FFmpeg 8.1.2 arm64/API 28 prefix, after which QtAVCore static and shared
cross-builds completed against that prefix. No Actions artifact and no SDK/NDK
installation or replacement was used.
Reconsider this policy only if the project adopts a signed, content-addressed dependency distribution with equivalent checkout provenance and verification. Until then, workflow artifacts remain outputs rather than build inputs.
- Date: 2026-08-11
- Status: Accepted
- Scope: OHOS arm64 FFmpeg native software decoding
The player dependency feature retained --enable-small together with LTO and
the target toolchain's release -O2. FFmpeg interprets --enable-small as a
request to optimize for size and appends -Oz, overriding the earlier -O2.
On the connected OHOS arm64 device, a freshly opened low-complexity segment of
4K HEVC Main10 could initially reach its 25 FPS source cadence, but higher
bitrate/complexity segments lost real-time throughput while HEVC residual,
deblocking, and loop-filter functions dominated sampled CPU cycles. ARM64 ASM,
NEON, runtime CPU detection, and LTO were already active, so this was not a
Debug, scalar, or accidentally single-threaded build.
Keep the shared qtav-player feature policy, including --enable-small and
LTO, but append --optflags=-O3 for the OHOS arm64 triplet. This uses FFmpeg's
supported explicit optimization override and leaves Android and Windows
packages unchanged. Keep runtime CPU detection and generic ARM64 code
generation; do not select one device-specific -mcpu or instruction-set
extension for a distributable package.
QtAVCore separately exposes the string property
avcodec.video.threads for software-video decoder experiments. Thread-count
selection belongs to the player session, not the dependency package.
- Retaining effective
-Ozprioritizes binary size over the measured 4K software-decode workload. - Removing ASM, NEON, or LTO would reduce decode throughput and would not address the observed optimization-level mismatch.
- Selecting a device-specific CPU target would make the package unsafe for other supported OHOS arm64 devices.
- Applying
-O3to every target without target evidence would broaden the change beyond the measured regression.
Any change to this policy must run scripts/build-ohos.ps1 locally and pass
cmake/verify-install.cmake. The resulting FFmpeg configure record must show
both the retained feature flags and effective --optflags=-O3. QtAVCore and
the signed OHOS player HAP must then be rebuilt against that verified prefix,
and the connected-device result must report the requested software-decoder
thread count before comparing legend.mkv presentation cadence, CPU load, and
thermal behavior.
The 2026-08-11 validation completed that sequence. The installed configuration
contained -O3, LTO, AArch64, and NEON without -Oz; the signed player reported
four active frame threads and decoded 4,204 legend.mkv frames by position
2:49 (24.9 FPS average). A ten-second device profile attributed 91.6% of cycles
to the four HEVC workers and less than 1% to ArkUI. Presentation totals remained
below decoded totals, so this validates the compiler/thread experiment without
claiming zero end-to-end drops. Detailed counters are retained in
modern/PLAN_HISTORY_2026-08-11_OHOS_SOFTWARE_DECODE.md.
Revisit the target-specific override when FFmpeg changes the semantics of
--enable-small, when binary-size limits require a separate dependency
profile, or when repeatable device measurements show that -O2 or another
portable optimization level provides better sustained throughput.
- Date: 2026-08-13
- Status: Accepted
- Scope: Android FFmpeg MediaCodec output ownership and the installed public release-result contract
FFmpeg 8.1.2 shares one atomic release flag across all retained
AVMediaCodecBuffer views. Its MediaCodec helpers correctly invoke the native
release only once, and also suppress a release after decoder flush changes the
buffer serial. Both suppressed cases nevertheless returned success. A caller
could therefore register an AImageReader timestamp association and wait for a
frame-available callback even though no native output had been queued.
This becomes reachable when Android republishes the same Surface during a size/fullscreen refresh. QtAVCore invalidates the old presentation epoch, then may redraw the retained latest frame in the new epoch. Epoch/timestamp tracking cannot distinguish a real new producer release from FFmpeg's successful no-op; the missing callback is an ownership fact, not a slow-producer timeout or an unknown coded-size problem.
Patch both av_mediacodec_release_buffer() and
av_mediacodec_render_buffer_at_time() so zero means the current call actually
invoked the native output release. Return AVERROR(EALREADY) when another view
already released the shared buffer or decoder flush retired it. Keep all other
native error values unchanged.
Document the result in installed libavcodec/mediacodec.h and require the
contract from the Android branch of cmake/verify-install.cmake. QtAVCore owns
the higher-level mapping to applied, stale, and failed; the dependency layer
does not know about renderer epochs or presentation policy.
- Suppressing redraws by timestamp/frame identity would be an application workaround and could discard a valid frame after a real generation change.
- Waiting less or treating a timeout as success would retain the false pending association and hide the ownership violation.
- Reopening MediaCodec or probing the stream dimensions on every surface-size change does not make an already-released output emit another callback.
- Keeping the result only inside QtAVCore is impossible because the public FFmpeg helper previously erased whether it performed the native release.
Existing callers that check only for negative failure now observe a truthful stale result. QtAVCore's bool convenience functions remain source compatible but return true only for a real applied release; result-returning APIs preserve the three-way distinction.
Any change to this contract must run the directly affected Android dependency
build and cmake/verify-install.cmake, followed by QtAVCore Android
shared/static builds and installed-package consumers. Connected-device
validation must exercise a Surface/fullscreen refresh while MediaCodec
hardware output remains active and confirm that presentation continues without
an unbounded pending image wait.