From 58e7af8a76f55c3d09da82fcabc0cd3686035bde Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 27 May 2026 14:33:36 +0100 Subject: [PATCH] Capture PS3 Eye pixels through direct driver --- config/mimir-runtime.ks-camera.example.json | 56 +++++ docs/implementation-plan.md | 7 + native/camera_capture/CMakeLists.txt | 44 +++- .../camera_capture/mimir_ps3eye_capture.cpp | 215 ++++++++++++++++++ notes/current-system-map.md | 6 + notes/fresh-workspace-handoff.md | 5 + src/Mimir.BufferSmoke/Program.cs | 56 +++++ .../MimirPs3EyeVideoCaptureDriver.cs | 172 ++++++++++++++ .../MimirRuntimeConfiguration.cs | 24 ++ state/evidence.jsonl | 1 + state/map.yaml | 8 + 11 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 native/camera_capture/mimir_ps3eye_capture.cpp create mode 100644 src/Mimir.Runtime/Synchronization/MimirPs3EyeVideoCaptureDriver.cs diff --git a/config/mimir-runtime.ks-camera.example.json b/config/mimir-runtime.ks-camera.example.json index 0a35e3c..b7ce5f3 100644 --- a/config/mimir-runtime.ks-camera.example.json +++ b/config/mimir-runtime.ks-camera.example.json @@ -15,6 +15,62 @@ "pixelFormat": "YUY2", "minimumFramesPerSecond": 100.0, "queueDepth": 8 + }, + { + "sourceId": "ps3-eye-0", + "displayName": "PS3 Eye 0", + "kind": "Video", + "origin": "LocalDevice", + "enabled": true, + "adapter": "ps3eye", + "command": "../native/camera_capture/build/Release/mimir_ps3eye_capture.dll", + "cameraIndex": 0, + "width": 320, + "height": 240, + "framesPerSecond": 187 + }, + { + "sourceId": "ps3-eye-1", + "displayName": "PS3 Eye 1", + "kind": "Video", + "origin": "LocalDevice", + "enabled": true, + "adapter": "ps3eye", + "command": "../native/camera_capture/build/Release/mimir_ps3eye_capture.dll", + "cameraIndex": 1, + "width": 320, + "height": 240, + "framesPerSecond": 187 + }, + { + "sourceId": "kiyo-pro-yuy2", + "displayName": "Kiyo Pro YUY2", + "kind": "Video", + "origin": "LocalDevice", + "enabled": true, + "adapter": "ks-camera", + "command": "../native/camera_capture/build/Release/mimir_camera_capture.dll", + "pathNeedle": "vid_1532&pid_0e05&mi_00", + "width": 1920, + "height": 1080, + "pixelFormat": "YUY2", + "minimumFramesPerSecond": 25.0, + "queueDepth": 8 + }, + { + "sourceId": "kiyo-yuy2", + "displayName": "Kiyo YUY2", + "kind": "Video", + "origin": "LocalDevice", + "enabled": true, + "adapter": "ks-camera", + "command": "../native/camera_capture/build/Release/mimir_camera_capture.dll", + "pathNeedle": "vid_1532&pid_0e03&mi_00", + "width": 640, + "height": 480, + "pixelFormat": "YUY2", + "minimumFramesPerSecond": 25.0, + "queueDepth": 8 } ] } diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 4102358..9e8c163 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -208,6 +208,13 @@ a named invariant that the native runtime cannot protect yet. and lets `MimirVideoCaptureDriverSource` upload those raw frames into Fensalir texture leases. MJPG/H264 remain outside this lane; they need a device/GPU decode producer rather than a CPU convenience detour. + PS3 Eyes use the sibling `MimirPs3EyeVideoCaptureDriver` backed by + `native/camera_capture/mimir_ps3eye_capture.dll`; it opens the existing + WinUSB/libusb PS3EYEDriver path in process and emits Bayer8 frames through the + same source/upload/copy-count machinery. + Current direct-driver smokes prove real-frame upload for LeapUVC 640x240 + YUY2, both PS3 Eyes at 320x240 Bayer8, Kiyo Pro 1920x1080 YUY2, and regular + Kiyo 640x480 YUY2. Regular Kiyo 1280x720 YUY2 did not open. Fensalir can still resolve imported shared `Texture2D` resources by native D3D12 handle and accepts Mimir video format names. `Mimir.BufferSmoke --fensalir-camera-observation-smoke` verifies the split, diff --git a/native/camera_capture/CMakeLists.txt b/native/camera_capture/CMakeLists.txt index 3e26966..35fb87a 100644 --- a/native/camera_capture/CMakeLists.txt +++ b/native/camera_capture/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(mimir_camera_capture LANGUAGES CXX) +project(mimir_camera_capture LANGUAGES C CXX) add_library(mimir_camera_capture SHARED mimir_ks_camera_capture.cpp) @@ -12,3 +12,45 @@ target_compile_definitions(mimir_camera_capture PRIVATE target_link_libraries(mimir_camera_capture PRIVATE setupapi ksuser) + +set(PSMOVEAPI_ROOT "$ENV{TEMP}/mimir-driver-probes/psmoveapi" CACHE PATH "Root of a psmoveapi checkout with external/libusb-1.0 and external/PS3EYEDriver.") +set(LIBUSB_ROOT "${PSMOVEAPI_ROOT}/external/libusb-1.0" CACHE PATH "libusb source root.") +set(PS3EYE_ROOT "${PSMOVEAPI_ROOT}/external/PS3EYEDriver" CACHE PATH "PS3EYEDriver source root.") + +add_library(mimir_libusb_static STATIC + ${LIBUSB_ROOT}/libusb/core.c + ${LIBUSB_ROOT}/libusb/descriptor.c + ${LIBUSB_ROOT}/libusb/hotplug.c + ${LIBUSB_ROOT}/libusb/io.c + ${LIBUSB_ROOT}/libusb/strerror.c + ${LIBUSB_ROOT}/libusb/sync.c + ${LIBUSB_ROOT}/libusb/os/events_windows.c + ${LIBUSB_ROOT}/libusb/os/threads_windows.c + ${LIBUSB_ROOT}/libusb/os/windows_common.c + ${LIBUSB_ROOT}/libusb/os/windows_usbdk.c + ${LIBUSB_ROOT}/libusb/os/windows_winusb.c) +target_include_directories(mimir_libusb_static PUBLIC + ${LIBUSB_ROOT}/libusb + ${LIBUSB_ROOT}/libusb/os + ${LIBUSB_ROOT}/msvc) +target_compile_definitions(mimir_libusb_static PRIVATE + _CRT_SECURE_NO_WARNINGS + _WIN32_WINNT=0x0601) +target_link_libraries(mimir_libusb_static PRIVATE setupapi ole32 advapi32) + +add_library(mimir_ps3eye_vendor STATIC + ${PS3EYE_ROOT}/src/ps3eye.cpp + ${PS3EYE_ROOT}/src/ps3eye_capi.cpp) +target_include_directories(mimir_ps3eye_vendor PUBLIC + ${PS3EYE_ROOT}/src + ${LIBUSB_ROOT}/libusb) +target_link_libraries(mimir_ps3eye_vendor PUBLIC mimir_libusb_static) + +add_library(mimir_ps3eye_capture SHARED + mimir_ps3eye_capture.cpp) +target_compile_features(mimir_ps3eye_capture PRIVATE cxx_std_20) +target_compile_definitions(mimir_ps3eye_capture PRIVATE + WIN32_LEAN_AND_MEAN + NOMINMAX) +target_include_directories(mimir_ps3eye_capture PRIVATE ${PS3EYE_ROOT}/src) +target_link_libraries(mimir_ps3eye_capture PRIVATE mimir_ps3eye_vendor) diff --git a/native/camera_capture/mimir_ps3eye_capture.cpp b/native/camera_capture/mimir_ps3eye_capture.cpp new file mode 100644 index 0000000..e18b7d8 --- /dev/null +++ b/native/camera_capture/mimir_ps3eye_capture.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ps3eye_capi.h" + +namespace +{ +struct MimirPs3EyeFrame +{ + std::vector data; + std::int64_t timestampNs = 0; + std::uint64_t sequence = 0; +}; + +struct MimirPs3EyeCapture +{ + ps3eye_t* eye = nullptr; + std::thread worker; + std::mutex mutex; + std::condition_variable condition; + std::deque queue; + std::atomic running = false; + int cameraIndex = 0; + int width = 0; + int height = 0; + int fps = 0; + std::uint64_t sequence = 0; +}; + +std::mutex g_ps3eye_mutex; +int g_ps3eye_users = 0; + +long long nowNs() +{ + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +void retainPs3Eye() +{ + std::lock_guard lock(g_ps3eye_mutex); + if (g_ps3eye_users++ == 0) + { + ps3eye_init(); + } +} + +void releasePs3Eye() +{ + std::lock_guard lock(g_ps3eye_mutex); + if (g_ps3eye_users > 0 && --g_ps3eye_users == 0) + { + ps3eye_uninit(); + } +} + +void pushFrame(MimirPs3EyeCapture& capture, const std::vector& source) +{ + MimirPs3EyeFrame frame; + frame.data = source; + frame.timestampNs = nowNs(); + frame.sequence = capture.sequence++; + + { + std::lock_guard lock(capture.mutex); + capture.queue.push_back(std::move(frame)); + while (capture.queue.size() > 32) + { + capture.queue.pop_front(); + } + } + + capture.condition.notify_one(); +} + +void captureThread(MimirPs3EyeCapture* capture) +{ + std::vector frame(static_cast(capture->width) * static_cast(capture->height)); + while (capture->running.load(std::memory_order_acquire)) + { + ps3eye_grab_frame(capture->eye, frame.data()); + pushFrame(*capture, frame); + } +} +} + +extern "C" +{ +__declspec(dllexport) MimirPs3EyeCapture* mimir_ps3eye_create(int cameraIndex, int width, int height, int fps) +{ + if (cameraIndex < 0 || width <= 0 || height <= 0 || fps <= 0) + { + return nullptr; + } + + retainPs3Eye(); + if (cameraIndex >= ps3eye_count_connected()) + { + releasePs3Eye(); + return nullptr; + } + + auto* capture = new MimirPs3EyeCapture(); + capture->cameraIndex = cameraIndex; + capture->width = width; + capture->height = height; + capture->fps = fps; + capture->eye = ps3eye_open(cameraIndex, width, height, fps, PS3EYE_FORMAT_BAYER); + if (!capture->eye) + { + delete capture; + releasePs3Eye(); + return nullptr; + } + + return capture; +} + +__declspec(dllexport) int mimir_ps3eye_start(MimirPs3EyeCapture* capture) +{ + if (!capture || capture->running.exchange(true)) + { + return 0; + } + + capture->worker = std::thread(captureThread, capture); + return 1; +} + +__declspec(dllexport) int mimir_ps3eye_read( + MimirPs3EyeCapture* capture, + int* width, + int* height, + int* stride, + std::int64_t* timestampNs, + std::uint64_t* sequence, + unsigned char* destination, + int destinationBytes) +{ + if (!capture || !destination || destinationBytes <= 0) + { + return 0; + } + + MimirPs3EyeFrame frame; + { + std::lock_guard lock(capture->mutex); + if (capture->queue.empty()) + { + return 0; + } + + frame = std::move(capture->queue.front()); + capture->queue.pop_front(); + } + + if (frame.data.size() > static_cast(destinationBytes)) + { + return -static_cast(frame.data.size()); + } + + std::memcpy(destination, frame.data.data(), frame.data.size()); + if (width) + { + *width = capture->width; + } + if (height) + { + *height = capture->height; + } + if (stride) + { + *stride = capture->width; + } + if (timestampNs) + { + *timestampNs = frame.timestampNs; + } + if (sequence) + { + *sequence = frame.sequence; + } + + return static_cast(frame.data.size()); +} + +__declspec(dllexport) void mimir_ps3eye_destroy(MimirPs3EyeCapture* capture) +{ + if (!capture) + { + return; + } + + capture->running.store(false, std::memory_order_release); + if (capture->worker.joinable()) + { + capture->worker.join(); + } + if (capture->eye) + { + ps3eye_close(capture->eye); + } + + delete capture; + releasePs3Eye(); +} +} diff --git a/notes/current-system-map.md b/notes/current-system-map.md index 85d54c6..4ced503 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -349,6 +349,12 @@ is the first production-shaped KS/UVC driver: it opens a Kernel Streaming pin in process, queues uncompressed UVC frames, and feeds the same `IMimirVideoCaptureDriver` source path. MJPG/H264 still need a device/GPU decode producer; the KS driver rejects compressed capture formats. +`MimirPs3EyeVideoCaptureDriver` plus +`native/camera_capture/mimir_ps3eye_capture.dll` does the same for PS3 Eye raw +WinUSB/libusb capture, emitting Bayer8 frames through the common upload lane. +Driver smokes have pulled and uploaded real frames for LeapUVC 640x240 YUY2, +both PS3 Eyes at 320x240 Bayer8, Kiyo Pro 1920x1080 YUY2, and regular Kiyo +640x480 YUY2. Regular Kiyo 1280x720 YUY2 did not open. Metadata-only cadence frames do not create camera surface intents or fake payload requests. The old direct `AquariumGpuSensorFrame` bridge proof has been removed from Mimir's active proof path. Camera image claims currently defer diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 5595c38..6a415ef 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -58,6 +58,11 @@ Get-Content .\state\evidence.jsonl -Tail 8 The first direct KS/UVC driver is `MimirKsVideoCaptureDriver` backed by `native/camera_capture/mimir_camera_capture.dll`; the example config `config/mimir-runtime.ks-camera.example.json` targets LeapUVC 640x240 YUY2. + The same native folder also has `mimir_ps3eye_capture.dll` and + `MimirPs3EyeVideoCaptureDriver` for PS3 Eye Bayer8 frames through WinUSB/libusb. + Fresh driver smokes pulled and uploaded real frames for LeapUVC 640x240 YUY2, + both PS3 Eyes at 320x240 Bayer8, Kiyo Pro 1920x1080 YUY2, and regular Kiyo + 640x480 YUY2. Regular Kiyo 1280x720 YUY2 did not open. Compressed MJPG/H264 still need a device/GPU decode producer, not this raw upload lane. Foreign shared texture handles are import edges, not the primary hot path. diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index efe3e5d..b1c83e9 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -118,6 +118,11 @@ if (args.Any(arg => string.Equals(arg, "--ks-camera-driver-smoke", StringCompari return RunKsCameraDriverSmoke(args); } +if (args.Any(arg => string.Equals(arg, "--ps3eye-driver-smoke", StringComparison.OrdinalIgnoreCase))) +{ + return RunPs3EyeDriverSmoke(args); +} + if (args.Any(arg => string.Equals(arg, "--mimir-spectrum-upload-smoke", StringComparison.OrdinalIgnoreCase))) { return RunMimirSpectrumUploadSmoke(); @@ -871,6 +876,57 @@ static int RunKsCameraDriverSmoke(string[] args) return 1; } +static int RunPs3EyeDriverSmoke(string[] args) +{ + var library = ParseStringOption(args, "--native-library", Path.GetFullPath("native/camera_capture/build/Release/mimir_ps3eye_capture.dll")); + var sourceId = ParseStringOption(args, "--source-id", "ps3-eye-0"); + var cameraIndex = ParseIntOption(args, "--camera-index", 0); + var width = ParseIntOption(args, "--width", 320); + var height = ParseIntOption(args, "--height", 240); + var fps = ParseIntOption(args, "--fps", 187); + var timeoutMs = ParseIntOption(args, "--timeout-ms", 3000); + + var broker = new FakeFieldResourceBroker(); + using var driver = new MimirPs3EyeVideoCaptureDriver(new MimirPs3EyeVideoCaptureDriverOptions( + library, + sourceId, + cameraIndex, + width, + height, + fps)); + using var source = new MimirVideoCaptureDriverSource( + new MimirStreamDescriptor(sourceId, MimirStreamKind.Video, MimirStreamOrigin.LocalDevice), + driver, + static () => StopwatchTicksToNs(Stopwatch.GetTimestamp())); + source.AttachTextureLeaseClient(new MimirFensalirTextureLeaseClient(broker)); + + var deadline = Stopwatch.GetTimestamp() + (long)(timeoutMs / 1000.0 * Stopwatch.Frequency); + while (Stopwatch.GetTimestamp() < deadline) + { + if (!source.TryRead(out var sample)) + { + Thread.Sleep(1); + continue; + } + + var frame = sample.VideoFrame; + Console.WriteLine( + $"ps3eye-driver-smoke source={sample.SourceId} camera={cameraIndex} {frame?.Width}x{frame?.Height} format={frame?.PixelFormat} liveBytes={sample.ByteLength} uploadedBytes={source.LastUploadedByteLength} uploaded={broker.UploadedResourceKey} copies={source.LastUploadedCopyCount}"); + return frame != null && + frame.Width == width && + frame.Height == height && + frame.PixelFormat == MimirVideoPixelFormat.Bayer8 && + source.LastUploadedByteLength == width * height && + source.LastUploadedCopyCount == 1 && + broker.UploadedResourceKey == MimirFensalirTextureLeaseClient.ResourceKeyForSource(sourceId) + ? 0 + : 1; + } + + Console.WriteLine($"ps3eye-driver-smoke timeout source={sourceId} camera={cameraIndex} {width}x{height}@{fps}"); + return 1; +} + static long StopwatchTicksToNs(long ticks) { return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency))); diff --git a/src/Mimir.Runtime/Synchronization/MimirPs3EyeVideoCaptureDriver.cs b/src/Mimir.Runtime/Synchronization/MimirPs3EyeVideoCaptureDriver.cs new file mode 100644 index 0000000..f1c5df3 --- /dev/null +++ b/src/Mimir.Runtime/Synchronization/MimirPs3EyeVideoCaptureDriver.cs @@ -0,0 +1,172 @@ +using System.Runtime.InteropServices; + +namespace Mimir.Runtime.Synchronization; + +public sealed record MimirPs3EyeVideoCaptureDriverOptions( + string NativeLibraryPath, + string SourceId, + int CameraIndex, + int Width, + int Height, + int FramesPerSecond); + +public sealed unsafe class MimirPs3EyeVideoCaptureDriver : IMimirVideoCaptureDriver, IMimirFensalirTextureLeaseReceiver +{ + private readonly Native native; + private readonly nint capture; + private readonly byte[] scratch; + private readonly string sourceId; + private MimirFensalirTextureLeaseClient? textureLeaseClient; + private string resourceKey = ""; + private bool disposed; + + public MimirPs3EyeVideoCaptureDriver(MimirPs3EyeVideoCaptureDriverOptions options) + { + if (string.IsNullOrWhiteSpace(options.NativeLibraryPath)) + { + throw new ArgumentException("PS3 Eye capture requires a native library path.", nameof(options)); + } + + if (string.IsNullOrWhiteSpace(options.SourceId)) + { + throw new ArgumentException("PS3 Eye capture requires a source id.", nameof(options)); + } + + native = Native.Load(options.NativeLibraryPath); + sourceId = options.SourceId; + capture = native.Create(options.CameraIndex, options.Width, options.Height, options.FramesPerSecond); + if (capture == 0) + { + throw new InvalidOperationException($"PS3 Eye capture could not open camera {options.CameraIndex} at {options.Width}x{options.Height}@{options.FramesPerSecond}."); + } + + scratch = new byte[checked(options.Width * options.Height)]; + if (!native.Start(capture)) + { + throw new InvalidOperationException("PS3 Eye capture could not start."); + } + } + + public string DriverName => "ps3eye-winusb-direct"; + + public void AttachTextureLeaseClient(MimirFensalirTextureLeaseClient? client) + { + textureLeaseClient = client; + resourceKey = ""; + } + + public bool TryCapture(out MimirVideoFrameDescriptor frame, out ReadOnlyMemory data) + { + fixed (byte* destination = scratch) + { + var byteLength = native.Read( + capture, + out var width, + out var height, + out var stride, + out var timestampNs, + out var sequence, + destination, + scratch.Length); + if (byteLength <= 0) + { + frame = default!; + data = default; + return false; + } + + EnsureLease(width, height, stride, timestampNs, sequence); + frame = new MimirVideoFrameDescriptor( + width, + height, + MimirVideoPixelFormat.Bayer8, + stride, + timestampNs, + ResourceKey: resourceKey, + ProducerFenceValue: sequence); + data = scratch.AsMemory(0, byteLength).ToArray(); + return true; + } + } + + public void Dispose() + { + if (disposed) + { + return; + } + + disposed = true; + native.Destroy(capture); + native.Dispose(); + } + + private void EnsureLease(int width, int height, int stride, long timestampNs, ulong version) + { + if (!string.IsNullOrWhiteSpace(resourceKey) || textureLeaseClient == null) + { + return; + } + + if (textureLeaseClient.TryLeaseTexture2D(new MimirFensalirTextureLeaseRequest( + sourceId, + width, + height, + MimirVideoPixelFormat.Bayer8, + stride, + timestampNs, + version), out var lease)) + { + resourceKey = lease.Frame.ResourceKey; + } + } + + private sealed class Native : IDisposable + { + private readonly nint library; + private readonly delegate* unmanaged[Stdcall] create; + private readonly delegate* unmanaged[Stdcall] start; + private readonly delegate* unmanaged[Stdcall] read; + private readonly delegate* unmanaged[Stdcall] destroy; + + private Native(nint library) + { + this.library = library; + create = (delegate* unmanaged[Stdcall])NativeLibrary.GetExport(library, "mimir_ps3eye_create"); + start = (delegate* unmanaged[Stdcall])NativeLibrary.GetExport(library, "mimir_ps3eye_start"); + read = (delegate* unmanaged[Stdcall])NativeLibrary.GetExport(library, "mimir_ps3eye_read"); + destroy = (delegate* unmanaged[Stdcall])NativeLibrary.GetExport(library, "mimir_ps3eye_destroy"); + } + + public static Native Load(string path) => new(NativeLibrary.Load(path)); + + public nint Create(int cameraIndex, int width, int height, int framesPerSecond) => + create(cameraIndex, width, height, framesPerSecond); + + public bool Start(nint capture) => start(capture) != 0; + + public int Read( + nint capture, + out int width, + out int height, + out int stride, + out long timestampNs, + out ulong sequence, + byte* destination, + int destinationBytes) => + read(capture, out width, out height, out stride, out timestampNs, out sequence, destination, destinationBytes); + + public void Destroy(nint capture) + { + if (capture != 0) + { + destroy(capture); + } + } + + public void Dispose() + { + NativeLibrary.Free(library); + } + } +} diff --git a/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs b/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs index 33186bd..bb03b78 100644 --- a/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs +++ b/src/Mimir.Runtime/Synchronization/MimirRuntimeConfiguration.cs @@ -172,6 +172,26 @@ public sealed class MimirRuntimeConfiguration static () => StopwatchTicksToNs(System.Diagnostics.Stopwatch.GetTimestamp()))); } + if (string.Equals(stream.Adapter, "ps3eye", StringComparison.OrdinalIgnoreCase) + || string.Equals(stream.Adapter, "ps3eye-direct", StringComparison.OrdinalIgnoreCase)) + { + if (!OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException("The Mimir PS3 Eye source requires Windows."); + } + + return new MimirStreamSourceFactory(descriptor, () => new MimirVideoCaptureDriverSource( + descriptor, + new MimirPs3EyeVideoCaptureDriver(new MimirPs3EyeVideoCaptureDriverOptions( + ResolveCommand(stream.Command, configDirectory), + stream.SourceId, + stream.CameraIndex, + stream.Width, + stream.Height, + stream.FramesPerSecond)), + static () => StopwatchTicksToNs(System.Diagnostics.Stopwatch.GetTimestamp()))); + } + if (string.Equals(stream.Adapter, "frame-events", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Adapter, "json-lines", StringComparison.OrdinalIgnoreCase)) { @@ -352,6 +372,10 @@ public sealed class MimirStreamConfig public int QueueDepth { get; set; } = 8; + public int CameraIndex { get; set; } + + public int FramesPerSecond { get; set; } + public string DisplayNameForSource(string sourceId) { if (SourceLabels.TryGetValue(sourceId, out var label) && !string.IsNullOrWhiteSpace(label)) diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 81071b9..853739a 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -179,3 +179,4 @@ {"ts": "2026-05-27T14:07:32+01:00", "type": "decision", "status": "ok", "note": "Raw single-plane CPU-origin camera frames now have an explicit Fensalir texture upload fallback. MimirVideoCaptureDriverSource uploads returned frame bytes into the leased Fensalir Texture2D through the broker, clears the live managed payload after successful upload, and increments MimirVideoFrameDescriptor.UnavoidableCopyCount. CPU NV12 upload is rejected until Fensalir has a real planar copy path; GPU/direct producers should bypass this lane and remain at zero copies."} {"ts": "2026-05-27T14:21:26+01:00", "type": "decision", "status": "ok", "note": "Mimir now has a production-shaped in-process KS/UVC camera driver. native/camera_capture builds mimir_camera_capture.dll, which opens a Kernel Streaming capture pin and queues uncompressed UVC frames; MimirKsVideoCaptureDriver feeds those frames through IMimirVideoCaptureDriver so MimirVideoCaptureDriverSource can upload single-plane raw pixels into Fensalir texture leases and count the unavoidable copy. MJPG/H264 remain future device/GPU decode producer work."} {"ts": "2026-05-27T14:25:26+01:00", "type": "verification", "status": "ok", "note": "The new in-process KS/UVC driver pulled a real LeapUVC 640x240 YUY2 frame through native/camera_capture, MimirKsVideoCaptureDriver, and MimirVideoCaptureDriverSource, then uploaded 307200 bytes into the Fensalir texture lease for mimir:resource:camera:leap-stereo-ir:texture2d with UnavoidableCopyCount=1. Managed live payload bytes were cleared after upload."} +{"ts": "2026-05-27T14:31:40+01:00", "type": "verification", "status": "ok", "note": "Direct camera driver smokes pulled and uploaded real camera pixels into Fensalir texture leases: LeapUVC 640x240 YUY2 uploaded 307200 bytes, PS3 Eye 0 and 1 each uploaded 320x240 Bayer8 / 76800 bytes, Kiyo Pro uploaded 1920x1080 YUY2 / 4147200 bytes, and regular Kiyo uploaded 640x480 YUY2 / 614400 bytes. All successful paths cleared live managed payload bytes after upload and reported UnavoidableCopyCount=1. Regular Kiyo 1280x720 YUY2 did not open; compressed MJPG/H264 still require a device/GPU decode producer."} diff --git a/state/map.yaml b/state/map.yaml index 47b3439..89d965d 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -329,6 +329,14 @@ accepted_design: in process, queues uncompressed UVC frames, and feeds the same driver source and Fensalir lease upload path. MJPG/H264 remain future device/GPU decode producer work. + `MimirPs3EyeVideoCaptureDriver` plus + `native/camera_capture/mimir_ps3eye_capture.dll` owns the equivalent raw + WinUSB/libusb PS3 Eye path and emits Bayer8 frames through the same upload + and copy-count machinery. + Fresh direct-driver smokes uploaded real frames for LeapUVC 640x240 YUY2, + PS3 Eye 0/1 320x240 Bayer8, Kiyo Pro 1920x1080 YUY2, and regular Kiyo + 640x480 YUY2. Regular Kiyo 1280x720 YUY2 did not open; compressed + MJPG/H264 remain device/GPU decode producer work. `Mimir.BufferSmoke --fensalir-camera-observation-smoke` verifies the split, and `--fensalir-texture-lease-smoke` verifies the engine-owned lease path. Camera image claims currently defer until Fensalir