Capture PS3 Eye pixels through direct driver
This commit is contained in:
parent
0d9c7824b8
commit
58e7af8a76
11 changed files with 593 additions and 1 deletions
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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<byte> 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]<int, int, int, int, nint> create;
|
||||
private readonly delegate* unmanaged[Stdcall]<nint, int> start;
|
||||
private readonly delegate* unmanaged[Stdcall]<nint, out int, out int, out int, out long, out ulong, byte*, int, int> read;
|
||||
private readonly delegate* unmanaged[Stdcall]<nint, void> destroy;
|
||||
|
||||
private Native(nint library)
|
||||
{
|
||||
this.library = library;
|
||||
create = (delegate* unmanaged[Stdcall]<int, int, int, int, nint>)NativeLibrary.GetExport(library, "mimir_ps3eye_create");
|
||||
start = (delegate* unmanaged[Stdcall]<nint, int>)NativeLibrary.GetExport(library, "mimir_ps3eye_start");
|
||||
read = (delegate* unmanaged[Stdcall]<nint, out int, out int, out int, out long, out ulong, byte*, int, int>)NativeLibrary.GetExport(library, "mimir_ps3eye_read");
|
||||
destroy = (delegate* unmanaged[Stdcall]<nint, void>)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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue