Conversation
Use one shared pending-media helper for GStreamer recordings and local main-video JPEG snapshots on Android 10 and newer. Write through borrowed descriptors, publish only completed media, and avoid a second app-private copy. Keep descriptor ownership through muxer shutdown, preserve owned-video storage limits, and retain legacy destinations for other backends and older Android. Add recording descriptor and QML image-save regressions plus Android acceptance guidance. Snapshot counter/completion feedback and interrupted-recording recovery remain separate.
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (13.63%) is below the target coverage (30.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #15075 +/- ##
==========================================
+ Coverage 25.47% 34.85% +9.38%
==========================================
Files 769 950 +181
Lines 65912 85936 +20024
Branches 30495 39606 +9111
==========================================
+ Hits 16788 29951 +13163
+ Misses 37285 35987 -1298
- Partials 11839 19998 +8159
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 680 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
Build ResultsPlatform Status
All builds passed. Pre-commit
Pre-commit hooks: 2 passed, 77 failed, 7 skipped. Test Resultslinux-coverage-integration: 52 passed, 0 skipped Code CoverageCoverage: 71.2% No baseline available for comparison Artifact Sizes
Updated: 2026-09-10 15:19:11 UTC • Commit: 057897b • Triggered by: Linux |
Bug Description
On recent Android versions, streaming videos and local video snapshots are saved under QGC's app-specific
Android/data/...directory. A successful capture is therefore difficult to find in Gallery/Photos or ordinary file managers.Related to the storage-location discussion in #12989. #12999 fixed the recording filename, but did not resolve shared-media discoverability. This PR addresses new GStreamer recordings and local snapshots on Android 10/API 29+; it does not implement SD-card selection or migrate existing files.
Root Cause
The writers use filesystem paths in QGC's app-specific storage. These paths do not create shared MediaStore entries. A MediaStore content URI also cannot be passed to a GStreamer filesystem sink as a pathname.
Solution
MediaStore.VideounderMovies/QGroundControl/usingfdsink. Keep descriptor ownership through muxer shutdown, require finalization and a received keyframe, and remove empty attempts.VideoManager::saveImage. Encode JPEG directly intoMediaStore.ImagesunderPictures/QGroundControl/, flush/close before publication, and discard failed image writes. The main-stream guard avoids duplicate snapshots when thermal video is visible.There is one retained media file, not an app-private copy plus an exported copy. The existing MP4/MOV faststart muxer can still use temporary storage. No new storage permission or export UI is added.
User-visible behavior and limitations
This intentionally changes the default destination for the supported Android path to primary shared storage. Published media survives uninstall and can be accessed by appropriately authorized media apps or included in user-configured Gallery/cloud backup. Publication itself does not upload anything.
Auto-Delete Saved Recordings still applies and is enabled by default on mobile. Users who want to retain all published videos must disable it. Existing private clips are not migrated; telemetry
.asssidecars remain private.Nonempty unfinished recordings remain pending, subject to Android expiry; this is not crash recovery. There is no silent private-storage fallback on MediaStore error. Gallery codec/container support varies. The local-photo counter and backend screenshot-completion logs are separate from this destination change.
Testing
Validation on commit
057897b0f:VideoManagerTestpassed, including a real QML-to-C++ invocation producing a decodable JPEG and empty-input rejection.GStreamerTestpassed: 89 passes and 16 platform/GPU-capability skips. Both filesystem and borrowed-descriptor H.265 MP4 finalization/decoding cases passed, not skipped; the latter verifies the sink leaves its caller-owned descriptor open.QGCMediaStoreTest: 4 passed (video/JPEG MIME mapping and unsupported input).ctest --output-on-failure -L Unit --parallel 4: 266/267 passed. The untouchedBluetoothWorkerTest::_testDisconnectResetsReconnectStatefails on the host BlueZ warningInvalid address to remote address passed.This also reproduces alone and was present in the local validation notes for fix(Joystick): match actual update rate to joystick setting #14797.git diff --check, focused formatting and source-policy checks passed. The broader pre-commit run still reports existing whole-file formatting/QML/assertion findings and missing local clang-tidy; no unrelated cleanup is included.QGCUsbSerialManager.javaand 77 warnings.lintDebugreturning success is not a clean lint gate becauseabortOnError=false.The backend README includes manual Gallery, repeated capture/stop-start, thermal, permissions, retention, failure-path, API-28 fallback, and release/JNI acceptance checks. End-to-end device acceptance of this combined revision and release/R8 runtime validation are not claimed by the automated tests.
Platforms Tested
Flight Stacks Tested
Checklist
masterBy submitting this pull request, I confirm that my contribution is made under the terms of the project's dual license (Apache 2.0 and GPL v3).