Live Link Face Android 1.3.0: Capture Manager cannot transfer takes >~525 MB — the client never uses the protocol's own resume offset

Summary

Updated after further investigation: the root cause is now identified. This is primarily a client-side issue in Capture Manager, not a hard size limit, and the fix is small.

On Live Link Face for Android 1.3.0 (versionCode 139), Capture Manager can never finish downloading a take larger than roughly 525 MB. Two things combine:

1. The device’s CPSEXPORT server ends the connection after about 525 MB in a single session. This happens at the same point regardless of transport (6 GHz Wi-Fi and USB tethering behave identically), which is why it initially looked like a network fault. The app’s own log shows the connection closing first, and only then the write failing:

INFO/ExportConnectionHandler: Connection closed
SEVERE/ExportConnectionHandler: Failed to send file to
Bad state: StreamSink is closed
at ExportConnectionHandler._sendFile (package:face_link_flutter/net/capture_protocol_stack/export/connection_handler.dart:287)

Importantly, the app does not crash here — it keeps running and logging normally.

2. Capture Manager always re-requests the file at offset 0. Every retry therefore starts from scratch, and any file larger than the ~525 MB ceiling can never complete no matter how many times it is retried. Meanwhile FExportRequest in CaptureProtocolStack already carries a uint64 Offset, and the device honours it correctly — resume is fully supported by the protocol and simply is not used by the client.

Verification. I wrote a minimal client that speaks the documented export protocol and, whenever the connection drops, reconnects and re-requests the same file at the byte count already received. With that change alone, four takes that Capture Manager could never transfer — 2.1 GB, 2.7 GB, 5.1 GB and 5.7 GB, 15.8 GB in total — all downloaded successfully in 4 to 11 resumed rounds each, over the same USB tether that always failed via Capture Manager, at 37-96 MB/s. Every file matched the exact length reported by /take/metadata and decoded cleanly in ffprobe with correct duration, frame counts and monotonic timestamps.

So the takes were never corrupt, storage was fine, and the network was not the problem. Having Capture Manager resume at the received offset instead of restarting at 0 would fix this outright, and would also make transfers robust against ordinary connection blips.

Device: Google Pixel 7 Pro (Tensor G2), Android 16 (build CP1A.260405.005). Companion: Live Link Hub / Capture Manager, UE 5.8.1, Windows 11.

Separate second bug (same takes): the in-app Export / save-as-zip path hard-crashes the app with a native SIGABRT on large takes — “Scudo ERROR: internal map failure (error desc=Out of memory)”, with a register at exactly 0x7FFFFFFF. It reproduces immediately after a device reboot with nothing else running, so it is not ambient memory pressure; the packaging path appears to buffer the file in memory rather than streaming it. Full callstack below.

Finally, a request that would prevent this whole class of problem: please store takes in shared/external storage on Android, the way iOS takes are reachable through Files. Users could then plug the phone in and copy takes off directly, rather than depending on export/transfer code paths that can fail.

What Type of Bug are you experiencing?

Virtual Production

Steps to Reproduce

  1. On an Android device (tested: Pixel 7 Pro, Android 16), record a video-only take with an external UVC camera large enough to exceed ~525 MB (a few minutes at 60 fps is enough; the takes here were 2.1-5.7 GB).
  2. Connect the device to Live Link Hub’s Capture Manager and queue that take on the Download pipeline. Observe the job fail mid-transfer with “Ingest failed : No pending data on socket (Error code : 4)”.
  3. Retry. Note that the job restarts from 0% every time and never completes, over Wi-Fi or USB tethering. Larger takes stall at a smaller percentage (a 5.7 GB take fails around 9-21%, a 2.1 GB take around 25-58%) — consistent with a fixed ~525 MB per-connection ceiling rather than a percentage-based failure.
  4. For contrast, request the same file over the export protocol with a non-zero Offset after a failure: the device serves the remainder correctly, and repeating that to completion yields a byte-exact file.
  5. Separately, in the take browser choose Export / save as zip on the same take: the app crashes with the native SIGABRT below.

Expected Result

Capture Manager completes transfers of takes of any size, resuming at the last received byte offset when a connection drops, using the Offset field the protocol already provides. Ideally the export server also sustains a single connection for a whole file. Takes should additionally be reachable in shared storage so a manual copy is always possible.

Observed Result

Any take over roughly 525 MB can never be transferred through Capture Manager: each attempt stops after ~525 MB with “Ingest failed : No pending data on socket (Error code : 4)”, and the next attempt restarts at offset 0, so the job never converges. The in-app zip export of the same takes crashes the app outright with a Scudo out-of-memory abort. Without an external tool the takes are permanently stranded in app-private storage on an unrooted device. Takes below the ceiling transfer normally.

Affects Versions

5.8

Platform(s)

Android

Upload an image

For crash reports, include your callstack

Build fingerprint: ‘google/cheetah/cheetah:16/CP1A.260405.005/15001963:user/release-keys’
ABI: ‘arm64’
Timestamp: 2026-08-13 14:27:18
Process uptime: 1456s
Cmdline: com.epicgames.facelink
pid: 24620, tid: 24620, name: cgames.facelink >>> com.epicgames.facelink <<<
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: ‘Scudo ERROR: internal map failure (error desc=Out of memory)’
x13 000000007fffffff (register at INT32_MAX)
backtrace:
#00 pc 00000000000754ec /apex/com.android.runtime/lib64/bionic/libc.so (abort+156)
#01 pc 000000000005b018 libc.so (scudo::die()+8)
#02 pc 000000000005bcfc libc.so (scudo::reportRawError(char const*)+28)
#03 pc 000000000005c42c libc.so (scudo::reportMapError(unsigned long)+172)
#04 pc 000000000005b8f0 libc.so (scudo::MemMapLinux::remapImpl(unsigned long, unsigned long, char const*, unsigned long)+208)
#05 pc 0000000000060cd8 libc.so (scudo::MapAllocator<scudo::SecondaryConfigscudo::AndroidNormalConfig>::allocate(…))
#06 pc 000000000005d5f4 libc.so (scudo::Allocator<scudo::AndroidNormalConfig, &scudo_malloc_postinit>::allocate(…))
#07 pc 000000000005d1f0 libc.so (scudo_calloc+48)
#08 pc 0000000000057478 libc.so (calloc+56)
#09 pc 000000000083b1a8 libflutter.so (BuildId: c5ad333303d9470f82282e3003bf27b0a32df737)
#10 pc 00000000008d143c libflutter.so
#11 pc 0000000000383e74 libapp.so (BuildId: dbcf9df9721870ff12fa88920841e9f3)
#12-#17 libapp.so (further app frames)
#35-#38 libflutter.so
#39+ Android runtime / Looper / ActivityThread.main
60 total frames — full tombstone (tombstone_30) available on request.

Additional Notes

Suggested fixes, in order of impact:

  1. Resume instead of restarting. On transfer failure, have Capture Manager reconnect and re-issue FExportRequest with Offset set to the number of bytes already written, rather than 0. This is a client-side change only; the device already implements it correctly.
  2. Fix the ~525 MB per-connection ceiling in the app’s ExportConnectionHandler so one connection can serve a whole file.
  3. Stream rather than buffer in the in-app export/zip path, which currently aborts with a Scudo OOM on large takes.
  4. Store takes in shared/external storage on Android, so a manual recovery path always exists.

One protocol question while you are in there: when a request is served from a non-zero Offset, the trailing 16-byte hash appears to cover only the bytes sent in that session rather than the whole file. That is reasonable, but worth documenting, since a resuming client cannot then use the final hash to validate the complete file. Verifying against the length from /take/metadata works fine as an alternative.

The exact Live Link Hub error when a download fails is: “Ingest failed : No pending data on socket (Error code : 4)”. Note that status 4 is ServerError in FExportResponse::EStatus, so the device is reporting the failure rather than the link merely dropping.

Possibly related environment note: on the same setup, Live Link Hub 5.8.1 also crashes when loading any saved config or restoring an autosave (Assertion failed: IsValid() SharedPointer.h:1133) — reported separately: Live Link Hub 5.8.1 crashes loading ANY saved config or restoring autosave (Assertion failed: IsValid() SharedPointer.h:1133)

Tested on Live Link Face Android 1.3.0 (versionCode 139), minSdk 33 / targetSdk 36. The app’s external storage directory stays empty (takes live in internal app storage), so on an unrooted device there is no manual recovery path — which is what made this production-blocking until the resume workaround was found. Happy to share the full tombstone, further logcat captures, or test any build.

(post deleted by author)