Skip to content
Merged
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
47 changes: 33 additions & 14 deletions CollimationCircles/Services/CameraControlService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CollimationCircles.Models;
using CollimationCircles.Services.Uvc;
using CollimationCircles.Services.Zwo;
using CommunityToolkit.Diagnostics;
using System;
Expand All @@ -18,32 +19,40 @@ public void Set(ControlType controlName, double value, Camera camera)

logger.Info($"Dispatching camera control set: camera='{camera.Name}', api={camera.APIType}, control={controlName}, value={value}");

// set camera control for V4L2 (Linux cameras)
if (camera.APIType is APIType.V4l2)
// UVC camera controls (Linux/Windows/macOS)
if (camera.APIType is APIType.Uvc)
{
new V4L2CameraDetect().SetControl(camera, controlName, value);
if (OperatingSystem.IsMacOS())
{
new UvcCameraDetectMac().SetControl(camera, controlName, value);
}
}
// set camera control for ZWO astro cameras (Windows/macOS)

// ZWO camera controls (Linux/Windows/macOS)
else if (camera.APIType is APIType.Zwo)
{
new ZWOCameraDetect().SetControl(camera, controlName, value);
}
// set camera control for macOS UVC cameras (IOKit + libusb)
else if (camera.APIType is APIType.Uvc)

// V4L2 camera controls (Linux cameras)
else if (camera.APIType is APIType.V4l2)
{
new MacOSCameraDetect().SetControl(camera, controlName, value);
new V4L2CameraDetect().SetControl(camera, controlName, value);
}
// set camera control for macOS system cameras (AVFoundation/QTCapture fallback)

// macOS system cameras (AVFoundation/QTCapture fallback)
else if (camera.APIType is APIType.QTCapture)
{
new MacOSCameraDetect().SetControl(camera, controlName, value);
}
// set camera control for DirectShow (Windows)

// DirectShow cameras (Windows)
else if (camera.APIType is APIType.Dshow)
{
new DShowCameraDetect().SetControl(camera, controlName, value);
}
// set camera control for Raspberry PI Camera

// Raspberry Pi cameras (Linux)
else if (camera.APIType is APIType.LibCamera)
{
new RasPiCameraDetect().SetControl(camera, controlName, value);
Expand All @@ -56,13 +65,19 @@ public void SetAuto(ControlType controlName, bool isAuto, Camera camera)

logger.Info($"Dispatching camera auto-control set: camera='{camera.Name}', api={camera.APIType}, control={controlName}, isAuto={isAuto}, isPlaying={camera.IsPlaying}");

if (camera.APIType is APIType.Zwo)
// UVC camera auto-controls (Linux/Windows/macOS)
if (camera.APIType is APIType.Uvc)
{
new ZWOCameraDetect().SetControlAuto(camera, controlName, isAuto);
if (OperatingSystem.IsMacOS())
{
new UvcCameraDetectMac().SetControlAuto(camera, controlName, isAuto);
}
}
else if (camera.APIType is APIType.Uvc)

// ZWO camera auto-controls (Linux/Windows/macOS)
else if (camera.APIType is APIType.Zwo)
{
new MacOSCameraDetect().SetControlAuto(camera, controlName, isAuto);
new ZWOCameraDetect().SetControlAuto(camera, controlName, isAuto);
}
}

Expand All @@ -80,6 +95,10 @@ public async Task<List<Camera>> GetCameraList()
var macosCameras = await new MacOSCameraDetect().GetCameras();
cameras.AddRange(macosCameras);

// Also detect UVC cameras via libuvc on macOS
var macosUvcCameras = await new UvcCameraDetectMac().GetCameras();
cameras.AddRange(macosUvcCameras);

var raspiCameras = await new RasPiCameraDetect().GetCameras();
cameras.AddRange(raspiCameras);

Expand Down
85 changes: 4 additions & 81 deletions CollimationCircles/Services/MacOSCameraDetect.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using CollimationCircles.Models;
using CollimationCircles.Services.Uvc;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.Mvvm.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down Expand Up @@ -122,9 +120,10 @@ private static IEnumerable<Camera> ParseSystemProfilerCameras(string json, int s
_ = TryExtractVidPid(modelId, out vendorId, out productId);
}

// Use Uvc APIType for cameras with vendor/product IDs (real UVC cameras)
// Fall back to QTCapture for built-in/virtual cameras without UVC IDs
APIType apiType = (vendorId > 0 && productId > 0) ? APIType.Uvc : APIType.QTCapture;
// Use QTCapture for all system_profiler cameras.
// UVC cameras (with vendor/product IDs) are now detected by
// UvcCameraDetectMac in Services/Uvc/.
APIType apiType = APIType.QTCapture;

yield return new Camera
{
Expand Down Expand Up @@ -333,15 +332,6 @@ public async Task<List<ICameraControl>> GetControls(Camera camera)

List<ICameraControl> controls = [];

// For UVC cameras with vendor/product IDs, controls are enumerated at
// stream start by UvcFrameSource.EnumerateControls (via libuvc).
// Return empty list here — placeholders will be replaced on Play.
if (camera.APIType is APIType.Uvc && camera.VendorId > 0 && camera.ProductId > 0)
{
logger.Info($"UVC camera '{camera.Name}' (VID={camera.VendorId} PID={camera.ProductId}) — controls will be enumerated on stream start");
return controls;
}

// QTCapture cameras: discover controls via AVFoundation (Swift script)
if (!OperatingSystem.IsMacOS() || camera.APIType is not APIType.QTCapture)
{
Expand Down Expand Up @@ -501,31 +491,6 @@ public List<string> GetCommandLineParameters(Camera camera, ICommandBuilder? bui

public void SetControl(Camera camera, ControlType controlType, double value)
{
// For UVC cameras, control setting is handled via UvcFrameSource
// which has the device open during streaming.
if (camera.APIType is APIType.Uvc)
{
try
{
logger.Info($"macOS UVC set request: camera='{camera.Name}', control={controlType}, value={value}");
var uvcFrameSource = Ioc.Default.GetRequiredService<IUvcFrameSource>();
bool ok = uvcFrameSource.SetControl(controlType.ToString(), (long)value);
if (!ok)
{
logger.Warn($"Failed to set UVC control {controlType}={value} on '{camera.Name}'");
}
else
{
logger.Info($"macOS UVC set request completed: camera='{camera.Name}', control={controlType}, value={value}");
}
}
catch (Exception ex)
{
logger.Error(ex, $"Error setting UVC control {controlType} on '{camera.Name}'");
}
return;
}

// QTCapture: AVFoundation mode-only controls (numeric set is not supported)
if (!OperatingSystem.IsMacOS() || camera.APIType is not APIType.QTCapture)
{
Expand Down Expand Up @@ -553,48 +518,6 @@ public void SetControl(Camera camera, ControlType controlType, double value)

public void SetControlAuto(Camera camera, ControlType controlType, bool isAuto)
{
// UVC cameras: route to UvcFrameSource (libuvc)
if (camera.APIType is APIType.Uvc)
{
try
{
logger.Info($"macOS UVC auto set request: camera='{camera.Name}', control={controlType}, isAuto={isAuto}");
var uvcFrameSource = Ioc.Default.GetRequiredService<IUvcFrameSource>();

string autoName = controlType switch
{
ControlType.ExposureTime => "AutoExposure",
ControlType.FocusAbsolute => "AutoFocus",
ControlType.WhiteBalance => "AutoWhiteBalance",
ControlType.Hue => "HueAuto",
ControlType.Contrast => "ContrastAuto",
_ => string.Empty
};

if (!string.IsNullOrEmpty(autoName))
{
bool ok = uvcFrameSource.SetAutoControl(autoName, isAuto);
if (!ok)
{
logger.Warn($"Failed to set UVC auto control {controlType}={isAuto} on '{camera.Name}'");
}
else
{
logger.Info($"macOS UVC auto set request completed: camera='{camera.Name}', control={controlType}, isAuto={isAuto}");
}
}
else
{
logger.Warn($"No UVC auto-control mapping for {controlType} on '{camera.Name}'");
}
}
catch (Exception ex)
{
logger.Error(ex, $"Error setting UVC auto control {controlType} on '{camera.Name}'");
}
return;
}

// QTCapture: set auto mode via AVFoundation Swift script
if (!OperatingSystem.IsMacOS() || camera.APIType is not APIType.QTCapture)
{
Expand Down
Loading
Loading