// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) (2018-2024) Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% using System; using System.Runtime.InteropServices; using UnityEngine.XR.MagicLeap.Native; namespace UnityEngine.XR.MagicLeap { public partial class MLCameraBase { internal partial class NativeBindings { /// /// Helper funcction for marshaling the intrinsic calibration parameters. /// private static MLCamera.IntrinsicCalibrationParameters? CreateIntrinsicParameters(IntPtr intrinsicsPointer) { if (intrinsicsPointer != IntPtr.Zero) { MLCameraIntrinsicCalibrationParameters intrinsics = Marshal.PtrToStructure(intrinsicsPointer); return new MLCamera.IntrinsicCalibrationParameters(intrinsics.Width, intrinsics.Height, MLConvert.ToUnity(intrinsics.FocalLength), MLConvert.ToUnity(intrinsics.PrincipalPoint), intrinsics.FOV, intrinsics.Distortion); } return null; } [AOT.MonoPInvokeCallback(typeof(DeviceAvailabilityStatusDelegate))] private static void OnDeviceAvailableCallback(ref MLCameraDeviceAvailabilityInfo info) { // TODO : revive // MLThreadDispatch.ScheduleMain(() => // { // // If OnDeviceAvailableCallback was called in succession before the re-connect sequence was completed, // // multiple instances of this lambda would be queued. So use wasDeviceDisconnected to gate // // any attempts at reconnection. // if (!Instance.wasDeviceDisconnected) return; // // Tear everything down and set flags for what was enabled while doing so. // // We don't necessarily need to stop any ongoing capture, because it has // // already been stopped internally on the platform. We just need to // // call MLCameraDisconnect() and have the correct Unity side flags to be // // set so we know what to resume on reconnection. // // Without this teardown, ml_camera's state assumes its stil connected to // // the camera and proceeds with a successful MLCameraConnect() but fails on // // all other functions. // DidNativeCallSucceed(Instance.Pause( /* flagsOnly */ true).Result, "MLCamera.Pause()"); // // Build everything back up as it was. Keep retrying connection on every OnDeviceAvailableCallback until it succeeds. // Instance.wasDeviceDisconnected = !Instance.Resume( /* retry */ true).IsOk; // }); MLThreadDispatch.Call(info.CamId, internalOnDeviceAvailable); } [AOT.MonoPInvokeCallback(typeof(DeviceAvailabilityStatusDelegate))] private static void OnDeviceUnavailableCallback(ref MLCameraDeviceAvailabilityInfo info) { MLThreadDispatch.Call(info.CamId, internalOnDeviceUnavailable); } [AOT.MonoPInvokeCallback(typeof(OnDeviceIdleDelegate))] private static void OnDeviceIdleCallback(IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLThreadDispatch.Call(camera.OnDeviceIdle); } [AOT.MonoPInvokeCallback(typeof(OnDeviceStreamingDelegate))] private static void OnDeviceStreamingCallback(IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLThreadDispatch.Call(camera.OnDeviceStreaming); } [AOT.MonoPInvokeCallback(typeof(OnDeviceDisconnectedDelegate))] private static void OnDeviceDisconnectCallback(MLCamera.DisconnectReason reason, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLThreadDispatch.Call(reason, camera.OnDeviceDisconnected); } [AOT.MonoPInvokeCallback(typeof(OnErrorDataCallback))] private static void OnDeviceErrorCallback(MLCamera.ErrorType error, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLThreadDispatch.Call(error, camera.OnDeviceError); } [AOT.MonoPInvokeCallback(typeof(OnCaptureFailedDelegate))] private static void OnCaptureFailCallback(ref MLCameraResultExtras extra, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLCamera.IntrinsicCalibrationParameters? lambdaIntrinsics = CreateIntrinsicParameters(extra.Intrinsics); MLCamera.ResultExtras lambdaExtra = new MLCamera.ResultExtras(extra.FrameNumber, extra.VcamTimestamp, lambdaIntrinsics); MLThreadDispatch.Call(lambdaExtra, camera.OnCaptureFailed); } [AOT.MonoPInvokeCallback(typeof(OnCaptureAbortedDelegate))] private static void OnCaptureAbortCallback(IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; MLThreadDispatch.Call(camera.OnCaptureAborted); } [AOT.MonoPInvokeCallback(typeof(OnCaptureCompletedDelegate))] private static void OnCaptureCompleteCallback(ulong metadataHandle, ref MLCameraResultExtras extra, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; if (camera != null) { if (camera.previousCaptureTimestamp != 0) { camera.currentFPS = 1000f / Math.Abs(DateTime.Now.Millisecond - camera.previousCaptureTimestamp); } camera.previousCaptureTimestamp = DateTime.Now.Millisecond; MLCamera.IntrinsicCalibrationParameters? lambdaIntrinsics = CreateIntrinsicParameters(extra.Intrinsics); var lambdaExtra = new MLCamera.ResultExtras(extra.FrameNumber, extra.VcamTimestamp, lambdaIntrinsics); MLThreadDispatch.Call(new MLCamera.Metadata(metadataHandle), lambdaExtra, camera.OnCaptureCompleted); } } [AOT.MonoPInvokeCallback(typeof(OnImageBufferAvailableDelegate))] private static void OnImageBufferAvailableCallback(ref MLCameraOutput output, ulong metadataHandle, ref MLCameraResultExtras extra, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; if (camera.OnRawImageAvailable?.GetInvocationList().Length > 0) { MLCamera.IntrinsicCalibrationParameters? lambdaIntrinsics = CreateIntrinsicParameters(extra.Intrinsics); MLCamera.ResultExtras lambdaExtra = new MLCamera.ResultExtras(extra.FrameNumber, extra.VcamTimestamp, lambdaIntrinsics); // always marshal image data to managed memory in case of image captures since there isnt really // a use case for providing the unmanaged memory ptr, specially when that ptr is invalidated // once this callback exits. // TODO : revisit to see if we wanna use the circular buffer here. How frequently would image captures be taken // to warrant the use of a circular buffer? MLThreadDispatch.Call(output.CreateFrameInfo(copyToManagedMemory: true), lambdaExtra, new MLCamera.Metadata(metadataHandle), camera.OnRawImageAvailable); } } [AOT.MonoPInvokeCallback(typeof(OnVideoBufferAvailableDelegate))] private static void OnVideoBufferAvailableCallback(ref MLCameraOutput output, ulong metadataHandle, ref MLCameraResultExtras extra, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; bool shouldCopyToManaged = camera.OnRawVideoFrameAvailable?.GetInvocationList().Length > 0; MLCamera.IntrinsicCalibrationParameters? lambdaIntrinsics = CreateIntrinsicParameters(extra.Intrinsics); MLCamera.ResultExtras lambdaExtra = new MLCamera.ResultExtras(extra.FrameNumber, extra.VcamTimestamp, lambdaIntrinsics); if (shouldCopyToManaged) { for (int i = 0; i < output.PlaneCount; ++i) { if (camera.byteArrays[i] == null || camera.byteArrays[i].Length != output.Planes[i].Size) { camera.byteArrays[i] = new byte[output.Planes[i].Size]; } } } MLCamera.CameraOutput frameInfo = output.CreateFrameInfo(shouldCopyToManaged, shouldCopyToManaged ? camera.byteArrays : null); camera.OnRawVideoFrameAvailable_NativeCallbackThread?.Invoke(frameInfo, lambdaExtra, new MLCamera.Metadata(metadataHandle)); if (shouldCopyToManaged) { MLThreadDispatch.Call(frameInfo, lambdaExtra, new MLCamera.Metadata(metadataHandle), camera.OnRawVideoFrameAvailableInternal); } } [AOT.MonoPInvokeCallback(typeof(OnPreviewBufferAvailableDelegate))] private static void OnPreviewBufferAvailableCallback(ulong bufferHandle, ulong metadataHandle, ref MLCameraResultExtras extra, IntPtr data) { GCHandle gcHandle = GCHandle.FromIntPtr(data); var camera = gcHandle.Target as MLCameraBase; if (camera == null) { return; } MLCamera.IntrinsicCalibrationParameters? lambdaIntrinsics = CreateIntrinsicParameters(extra.Intrinsics); MLCamera.ResultExtras lambdaExtra = new MLCamera.ResultExtras(extra.FrameNumber, extra.VcamTimestamp, lambdaIntrinsics); if (camera.previewRenderer != null) { camera.previewRenderer.PreviewBuffer = bufferHandle; } MLThreadDispatch.Call(camera.GLPluginEvent); MLThreadDispatch.Call(new MLCamera.Metadata(metadataHandle), lambdaExtra, camera.OnPreviewBufferAvailable); } } } }