camera-streaming

작성자: facebook

스트리밍, 비디오 프레임, 사진 캡처, 해상도/프레임 속도 구성

npx skills add https://github.com/facebook/meta-wearables-dat-ios --skill camera-streaming

Camera Streaming (iOS)

Guide for implementing camera streaming and photo capture with the DAT SDK.

For PCM audio delivered with this stream, use the audio-streaming skill.

Key concepts

  • DeviceSession: The connection to the glasses. Create and start it first; it can stay connected with no camera attached.
  • Camera: The capability you attach to a started DeviceSession with addCamera(config:). It owns the Stream.
  • Stream: camera.stream — start/stop streaming, capture photos, observe frames, state, and errors.
  • VideoFrame: Individual video frames. Use .makeUIImage() to render, or .sampleBuffer when you need the raw CMSampleBuffer (for example to write compressed frames to a file).
  • StreamConfiguration: Configure video codec, resolution, and frame rate.
  • PhotoData: Still image captured from glasses (.data plus .format).

Treat "start a session" and "start the preview" as two separate user-visible steps, as the CameraAccess sample does. That keeps the session reusable when the user stops and restarts the preview.

Creating a DeviceSession

import MWDATCamera
import MWDATCore

let wearables = Wearables.shared
let deviceSelector = AutoDeviceSelector(wearables: wearables)
// Or for a specific device: SpecificDeviceSelector(device: deviceId)
let deviceSession = try wearables.createSession(deviceSelector: deviceSelector)
try deviceSession.start()

// Wait for the device session to reach the started state
for await state in deviceSession.stateStream() {
    if state == .started { break }
}

Adding a Camera

Once the DeviceSession is started, add the Camera capability and stream through camera.stream.

Check camera permission first. checkPermissionStatus(.camera) is a silent query, while requestPermission(.camera) switches to the Meta AI app. CameraAccess only calls requestPermission after the user confirms the app switch, so the redirect is never a surprise:

if try await wearables.checkPermissionStatus(.camera) != .granted {
    // Confirm with the user, then:
    guard try await wearables.requestPermission(.camera) == .granted else { return }
}

Then attach the camera:

let config = StreamConfiguration(
    videoCodec: .raw,
    resolution: .medium,  // 504x896
    frameRate: 24
)

guard let camera = try deviceSession.addCamera(config: config) else {
    // DeviceSession must be in the started state before adding a camera
    return
}
let stream = camera.stream

Resolution options

ResolutionSize
.high720 x 1280
.medium504 x 896
.low360 x 640

Frame rate options

frameRate is a UInt. Valid values: 2, 7, 15, 24, 30 FPS.

Lower resolution and frame rate yield higher visual quality due to less Bluetooth compression.

Codec options

VideoCodec has two cases:

  • .raw — decoded frames. Render them directly with frame.makeUIImage().
  • .hvc1 — compressed HEVC frames. Use this when you want to write frames to a file in passthrough mode; you must decode frame.sampleBuffer yourself for on-screen preview, as CameraAccess does.

StreamConfiguration() with no arguments defaults to .raw, .medium, and 30 FPS.

Observing stream state

StreamState transitions: stopping → stopped → waitingForDevice → starting → streaming → paused

let stateToken = stream.statePublisher.listen { state in
    Task { @MainActor in
        switch state {
        case .streaming:
            // Stream is active, frames are flowing
        case .waitingForDevice:
            // Waiting for glasses to connect
        case .stopped:
            // Stream ended — release resources
        case .paused:
            // Temporarily suspended — keep connection, wait
        default:
            break
        }
    }
}

Receiving video frames

let frameToken = stream.videoFramePublisher.listen { frame in
    guard let image = frame.makeUIImage() else { return }
    Task { @MainActor in
        self.previewImage = image
    }
}

Observing stream errors

stream.errorPublisher emits typed StreamError values such as .permissionDenied, .deviceNotConnected, .hingesClosed, .thermalHot, .batteryLow, and .photoCaptureFailed. Surface error.localizedDescription rather than maintaining your own error-message map.

let errorToken = stream.errorPublisher.listen { error in
    Task { @MainActor in
        self.errorMessage = error.localizedDescription
    }
}

Keep listener tokens alive for as long as you need callbacks. MWDATCore provides ListenerTokenBag and AnyListenerToken.store(in:) for that:

let streamTokens = ListenerTokenBag()
stream.statePublisher.listen { _ in }.store(in: streamTokens)
stream.videoFramePublisher.listen { _ in }.store(in: streamTokens)
// On teardown:
streamTokens.clear()

Starting and stopping

// Start the stream capability
stream.start()

// Stop the camera — this cascades to its stream child and detaches the camera
// from the session, so a later addCamera() can register a new one.
camera.stop()

// Stop the parent device session when you're done with all capabilities.
// Teardown cascades parent -> child, not child -> parent.
deviceSession.stop()

In-stream photo capture

This is the lightweight capture path used while video is streaming. For standalone high-quality capture with resolution, quality, transfer progress, and its own lifecycle, use the camera-capture skill.

Capture a still photo while streaming. capturePhoto(format:) returns Bool: false means the capture was not started (for example another capture is already in flight), so no photoDataPublisher event will arrive.

// Listen for photo data
let photoToken = stream.photoDataPublisher.listen { photoData in
    let imageData = photoData.data  // photoData.format is .jpeg or .heic
    // Convert to UIImage or save
}

// Trigger capture
let started = stream.capturePhoto(format: .jpeg)
if !started {
    // Re-enable the shutter button and tell the user to try again
}

Bandwidth and quality

Resolution and frame rate are constrained by Bluetooth Classic bandwidth. The SDK automatically reduces quality when bandwidth is limited:

  1. First lowers resolution (e.g., High → Medium)
  2. Then reduces frame rate (e.g., 30 → 24), never below 15 FPS

Request lower settings for higher visual quality per frame.

Links

facebook의 다른 스킬

gc-safe-coding
facebook
전체 설명과 근거는 doc/GCSafeCoding.md를 참조하십시오.
app-review-prep
facebook
Meta 앱을 App Review용으로 준비합니다 — 현재 상태, 미해결 요구 사항, 부여된 권한, 제출 내역을 확인합니다. 앱을 제출하기 전에 사용하세요…
api-health
facebook
Meta 앱의 API 상태를 모니터링합니다 — 비율 제한, 호출량, API 지원 중단 여부를 확인합니다. 트래픽 제한을 진단하거나, 용량을 계획하거나, API 버전 변경에 대비하는 데 사용합니다…
debug-webhooks
facebook
Meta 앱의 웹훅 문제를 해결합니다 — 활성 구독을 검사하고, 잘못된 구성을 식별하며, 테스트 페이로드를 전송하여 전달을 확인합니다. 다음과 같은 경우에 사용하세요…
api-integration
facebook
개발자가 Meta API 통합을 처음부터 설정하도록 안내합니다 — 적절한 API를 찾아내고, 설정 가이드, 인증 요구 사항 등을 가져옵니다…
webhook-setup
facebook
Meta 앱용 웹훅을 처음부터 끝까지 설정하세요 — 사용 가능한 주제를 탐색하고, 필드를 구독하고, 테스트 페이로드로 검증합니다. 웹훅을 구성할 때 사용하세요…
test-ui
facebook
iwsdk CLI를 사용하여 포크 예제에 대해 Test UI 시스템(PanelUI, ScreenSpace)을 테스트합니다.
flags
facebook
React 릴리스 채널 간 기능 플래그 상태를 검사하고 비교합니다. 모든 채널(www, www-modern, canary, next, experimental, rn 변형)의 플래그를 보거나 --diff로 특정 채널을 비교합니다. 출력 형식은 기본 테이블 보기, CSV 내보내기, 정리 상태 그룹화를 포함합니다. 플래그 상태는 기호로 표시됩니다: 활성화(✅), 비활성화(❌), 변형 테스트(🧪), 프로파일링 전용(📊). 일반적인 실수: __VARIANT__ 플래그는 www에서 두 상태 모두 테스트되며, --diff를 사용하여 의미 있는 차이를 찾습니다...