Android: stream and preview¶
Open a session¶
val config = SessionConfig(width = 1920, height = 1080, fps = 30)
val session = withContext(Dispatchers.IO) { device.open(config) }
if (!session.start()) {
// the camera didn't offer a matching format
}
The camera chooses the USB transfer mode (bulk or isochronous) itself; there's nothing to configure.
The frame flow¶
session.frames is a Kotlin Flow of H.264 access units — each with the video, the motion samples
captured since the previous frame and (on cameras with microphones) audio embedded. Any number of
consumers can collect it at once — for example a preview and a recorder.
session.frames
.onEach { frame ->
// frame.annexB: one H.264 access unit; frame.ptsUs: microseconds
}
.launchIn(scope)
A 1 Hz stream health flow reports measured frame rates, dropped or corrupt frames, the transfer mode and stalls.
Live preview (Jetpack Compose)¶
LivePreview(
frames = session.frames,
width = session.negotiatedWidth,
height = session.negotiatedHeight,
)
LiveAudio plays the embedded audio during preview on cameras with microphones.
Stereo cameras¶
Trinet Stereo and Stereo GS send both eyes side by side in one frame (3840×1080; the left half is the left eye). Ask for the wide frame with a mono fallback and the same code opens any camera:
val config = SessionConfig(
width = 3840, height = 1080, fps = 30,
fallbackWidth = 1920, fallbackHeight = 1080,
)
val session = withContext(Dispatchers.IO) { device.open(config) }
check(session.start())
session.layout // StreamLayout.SIDE_BY_SIDE or StreamLayout.MONO
session.isSideBySide // true on a stereo camera
- Detect stereo from the stream shape, not the USB product ID — stereo and mono cameras can share a product ID.
- Preview one eye: pass
cropEye = 0(left) or1(right) toLivePreview. - Record with the negotiated size; recordings keep both eyes and are marked side-by-side.
- Calibration:
device.getCalibrationAny()returns a mono or a two-eye stereo calibration. - Global shutter: Stereo GS behaves the same; its frames report a readout time of zero and the shutter type is reported as global. A rolling-shutter calibration isn't valid on a global-shutter camera.
Some phones' hardware decoders reject 3840-wide video; LivePreview falls back to software decoding
automatically.
Stop¶
Cancel your collectors, then session.close() and device.close().
More: streaming guide on GitHub.