Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 83 additions & 9 deletions drivers/providers/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import importlib
import itertools
import logging
from typing import Callable
from typing import Iterator

import numpy as np
Expand Down Expand Up @@ -61,11 +62,45 @@ def __init__(
def _sdk_frames(self) -> Iterator[dict[str, object]] | None:
if not self._use_sdk or _META_SDK is None:
return None
# TODO: Invoke the Meta Ray-Ban camera streaming API once the SDK
# is available. This should yield payloads that include the frame
# bytes together with transport and device metadata.
LOGGER.info("Meta SDK detected; camera streaming is not yet implemented")
return None
camera_api = getattr(_META_SDK, "camera", None)
stream: Callable[..., Iterator[object]] | None = None
if camera_api is not None:
stream = getattr(camera_api, "stream_frames", None)
if stream is None:
LOGGER.warning("Meta SDK detected but camera.stream_frames is unavailable")
return None

sdk_stream = stream(
device_id=self._device_id,
resolution=(self._height, self._width),
transport=self._transport,
)

def _enrich() -> Iterator[dict[str, object]]:
for frame_id, frame in enumerate(sdk_stream):
payload: dict[str, object]
if isinstance(frame, dict):
payload = dict(frame)
else:
payload = {"frame": frame}

payload.setdefault("device_id", self._device_id)
payload.setdefault("transport", self._transport)
payload.setdefault("frame_id", frame_id)
payload.setdefault(
"timestamp_ms",
int(
(
_BASE_TIME
+ timedelta(milliseconds=33 * frame_id)
).timestamp()
* 1000
),
)
payload.setdefault("format", "rgb888")
yield payload

return _enrich()

def get_frames(self) -> Iterator[dict[str, object]]: # type: ignore[override]
sdk_stream = self._sdk_frames()
Expand Down Expand Up @@ -122,10 +157,49 @@ def __init__(
def _sdk_frames(self) -> Iterator[dict[str, object]] | None:
if not self._use_sdk or _META_SDK is None:
return None
# TODO: Connect to the Meta Ray-Ban microphone capture API and
# yield PCM envelopes that include device and transport metadata.
LOGGER.info("Meta SDK detected; microphone capture is not yet implemented")
return None
mic_api = getattr(_META_SDK, "microphone", None)
stream: Callable[..., Iterator[object]] | None = None
if mic_api is not None:
stream = getattr(mic_api, "stream_pcm", None)
if stream is None:
LOGGER.warning("Meta SDK detected but microphone.stream_pcm is unavailable")
return None

sdk_stream = stream(
device_id=self._device_id,
sample_rate_hz=self._sample_rate_hz,
frame_size=self._frame_size,
channels=self._channels,
transport=self._transport,
)

def _enrich() -> Iterator[dict[str, object]]:
for sequence_id, frame in enumerate(sdk_stream):
payload: dict[str, object]
if isinstance(frame, dict):
payload = dict(frame)
else:
payload = {"pcm": frame}

payload.setdefault("device_id", self._device_id)
payload.setdefault("transport", self._transport)
payload.setdefault("sequence_id", sequence_id)
payload.setdefault(
"timestamp_ms",
int(
(
_BASE_TIME
+ timedelta(milliseconds=25 * sequence_id)
).timestamp()
* 1000
),
)
payload.setdefault("sample_rate_hz", self._sample_rate_hz)
payload.setdefault("frame_size", self._frame_size)
payload.setdefault("channels", self._channels)
yield payload

return _enrich()

def get_frames(self) -> Iterator[dict[str, object]]: # type: ignore[override]
sdk_stream = self._sdk_frames()
Expand Down
61 changes: 61 additions & 0 deletions tests/test_provider_mock_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from itertools import islice
from types import SimpleNamespace

import numpy as np
import pytest
Expand Down Expand Up @@ -102,3 +103,63 @@ def test_permissions_request_reports_grants(provider_factory) -> None:
response = permissions.request({"camera", "gps"})
assert response["requested"] == sorted(response["requested"])
assert set(response["granted"]) | set(response["denied"]) == set(response["requested"])


def test_meta_provider_prefers_sdk_when_present(monkeypatch: pytest.MonkeyPatch) -> None:
camera_payloads = [
{
"frame": np.zeros((2, 2, 3), dtype=np.uint8),
"frame_id": 10,
"timestamp_ms": 1234,
"format": "rgb888",
},
{
"frame": np.ones((2, 2, 3), dtype=np.uint8),
"frame_id": 11,
"timestamp_ms": 1235,
"format": "rgb888",
},
]
mic_payloads = [
{
"pcm": np.full((4, 1), 0.5, dtype=np.float32),
"sequence_id": 3,
"timestamp_ms": 2000,
}
]

stub_sdk = SimpleNamespace(
camera=SimpleNamespace(stream_frames=lambda **_: iter(camera_payloads)),
microphone=SimpleNamespace(stream_pcm=lambda **_: iter(mic_payloads)),
)
import drivers.providers.meta as meta_module

monkeypatch.setattr(meta_module, "_META_SDK", stub_sdk)
monkeypatch.setattr(meta_module, "_META_SDK_AVAILABLE", True)

provider = MetaRayBanProvider(
prefer_sdk=True,
device_id="SDK-DEVICE",
transport="wifi",
camera_resolution=(2, 2),
microphone_frame_size=4,
microphone_channels=1,
)

frame = next(provider.iter_frames())
assert frame["frame_id"] == 10
assert frame["device_id"] == "SDK-DEVICE"
assert frame["transport"] == "wifi"
assert frame["timestamp_ms"] == camera_payloads[0]["timestamp_ms"]
assert frame["format"] == camera_payloads[0]["format"]
assert np.array_equal(frame["frame"], camera_payloads[0]["frame"])

audio_chunk = next(provider.iter_audio_chunks())
assert audio_chunk["sequence_id"] == 3
assert audio_chunk["device_id"] == "SDK-DEVICE"
assert audio_chunk["transport"] == "wifi"
assert audio_chunk["timestamp_ms"] == mic_payloads[0]["timestamp_ms"]
assert audio_chunk["sample_rate_hz"] == 16000
assert audio_chunk["frame_size"] == 4
assert audio_chunk["channels"] == 1
assert np.array_equal(np.asarray(audio_chunk["pcm"]), mic_payloads[0]["pcm"])