# ICameraClient

<Badge tone="type">interface</Badge> <Badge>thread-safe</Badge>

```csharp
namespace AR51.Net;

public interface ICameraClient
```

The AR 51 cameras: list them with their calibration values, preview their video, change their image settings, and tune those settings automatically. Use it through [`IAR51Client.Cameras`](/docs/sdk-api/csharp-api/classes/iar51client/).

Cameras are spread over several camera servers; this client covers all of them. All members are safe to use from any thread.

:::tip[How-to]
This page is **reference** (facts only). For a walkthrough with a complete sample, see the [Cameras guide](/docs/csharp-api/cameras/).
:::

## Properties

<div className="apiPropTable apiTable">

| Property | Type | Access | Description |
|---|---|---|---|
| `AutoTuneStatus` | [`AutoTuneStatus`](#autotunestatus) | `get` | The latest auto-tune status reported by the camera servers, whoever started the auto-tune. Camera servers that do not report auto-tune progress get an estimate ([`AutoTuneStatus.IsEstimated`](#autotunestatus)) for each auto-tune this client starts; an auto-tune started by another app is not seen on those servers. |

</div>

## Method summary

<div className="apiTable">

| Method | Returns | |
|---|---|---|
| [`GetCamerasAsync`](#getcamerasasync) | [`Task<IReadOnlyList<CameraInfo>>`](/docs/sdk-api/csharp-api/classes/camerainfo/) |  |
| [`StreamPreviewAsync`](#streampreviewasync) | [`IAsyncEnumerable<CameraFrame>`](#cameraframe) |  |
| [`GetSettingsAsync`](#getsettingsasync) | [`Task<CameraSettings>`](/docs/sdk-api/csharp-api/classes/camerasettings/) |  |
| [`SetSettingsAsync`](#setsettingsasync) | [`Task<CameraSettings>`](/docs/sdk-api/csharp-api/classes/camerasettings/) |  |
| [`StartAutoTuneAsync`](#startautotuneasync) | [`Task`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) |  |
| [`CancelAutoTuneAsync`](#cancelautotuneasync) | [`Task`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) |  |

</div>

→ Full descriptions in [Method details](#method-details) below.

## Events

Standard .NET events (`EventHandler<T>`): subscribe with `+=`, unsubscribe with `-=`. They are raised on a background thread.

<div className="apiTable">

| Event | Args | Raised when |
|---|---|---|
| `AutoTuneStarted` | [`AutoTuneStatusEventArgs`](#autotunestatuseventargs) | Raised when an auto-tune starts, whoever started it. Raised on a background thread. |
| `AutoTuneProgressChanged` | [`AutoTuneStatusEventArgs`](#autotunestatuseventargs) | Raised when an auto-tune's progress changes. Raised on a background thread. |
| `AutoTuneEnded` | [`AutoTuneEndedEventArgs`](#autotuneendedeventargs) | Raised when an auto-tune ends: completed, failed or cancelled. Raised on a background thread. |

</div>

## Method details

### GetCamerasAsync

<ApiBody kind="method">

```csharp
Task<IReadOnlyList<CameraInfo>> GetCamerasAsync(CancellationToken cancellationToken = default);
```

Gets every camera of the system, with its status and calibration values.

<Params>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Cancels the request. Optional.</Param>
</Params>

<Returns type="Task<IReadOnlyList<CameraInfo>>" typeHref="/docs/sdk-api/csharp-api/classes/camerainfo/">The cameras. A camera server that does not answer in time is left out.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">No camera server is available ([`AR51ErrorCode.ServiceUnavailable`](/docs/sdk-api/csharp-api/classes/ar51exception/#ar51errorcode)).</Throw>
</Throws>

<Example>

```csharp
foreach (CameraInfo camera in await ar51.Cameras.GetCamerasAsync())
    Console.WriteLine($"{camera.Name}: {camera.Width}x{camera.Height} @ {camera.FrameRate} fps, " +
                      $"{(camera.IsConnected ? "connected" : "offline")}");
```

</Example>

</ApiBody>

### StreamPreviewAsync

<ApiBody kind="method">

```csharp
IAsyncEnumerable<CameraFrame> StreamPreviewAsync(string cameraId, PreviewOptions? options = null, CancellationToken cancellationToken = default);
```

Streams a camera's live video as JPEG images until the loop ends or `cancellationToken` is cancelled.

<Params>
<Param name="cameraId" type="string">The camera's [`CameraInfo.Id`](/docs/sdk-api/csharp-api/classes/camerainfo/).</Param>
<Param name="options" type="PreviewOptions?" typeHref="#previewoptions">Image size, frame rate and quality. `null` uses the camera's own size and frame rate. Optional.</Param>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Stops the preview. Optional.</Param>
</Params>

<Returns type="IAsyncEnumerable<CameraFrame>" typeHref="#cameraframe">The frames, in the order they arrive.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">The camera does not exist ([`AR51ErrorCode.Rejected`](/docs/sdk-api/csharp-api/classes/ar51exception/#ar51errorcode)), or the stream failed.</Throw>
<Throw type="OperationCanceledException" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.operationcanceledexception">`cancellationToken` was cancelled.</Throw>
</Throws>

:::note
Each preview costs network bandwidth: for many cameras at once, request small images at a low frame rate. Ending the `await foreach` loop stops the preview.
:::

<Example>

```csharp
// A small preview that fits 640 x 480; ending the loop stops the stream.
var options = new PreviewOptions { FitWidth = 640, FitHeight = 480, FrameRate = 5 };
await foreach (CameraFrame frame in ar51.Cameras.StreamPreviewAsync(camera.Id, options))
{
    await File.WriteAllBytesAsync($"{camera.Id}.jpg", frame.Jpeg.ToArray());
    break;
}
```

</Example>

</ApiBody>

### GetSettingsAsync

<ApiBody kind="method">

```csharp
Task<CameraSettings> GetSettingsAsync(string cameraId, CancellationToken cancellationToken = default);
```

Gets a camera's image settings.

<Params>
<Param name="cameraId" type="string">The camera's [`CameraInfo.Id`](/docs/sdk-api/csharp-api/classes/camerainfo/).</Param>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Cancels the request. Optional.</Param>
</Params>

<Returns type="Task<CameraSettings>" typeHref="/docs/sdk-api/csharp-api/classes/camerasettings/">The settings the camera uses now.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">The camera does not exist ([`AR51ErrorCode.Rejected`](/docs/sdk-api/csharp-api/classes/ar51exception/#ar51errorcode)) or could not be reached.</Throw>
</Throws>

</ApiBody>

### SetSettingsAsync

<ApiBody kind="method">

```csharp
Task<CameraSettings> SetSettingsAsync(string cameraId, CameraSettings settings, CancellationToken cancellationToken = default);
```

Changes a camera's image settings. Only the values that differ from the camera's current settings are sent. The server saves them, so they survive a restart.

<Params>
<Param name="cameraId" type="string">The camera's [`CameraInfo.Id`](/docs/sdk-api/csharp-api/classes/camerainfo/).</Param>
<Param name="settings" type="CameraSettings" typeHref="/docs/sdk-api/csharp-api/classes/camerasettings/">The new settings. Start from [`GetSettingsAsync`](#getsettingsasync) and change values with `with`.</Param>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Cancels the request. Optional.</Param>
</Params>

<Returns type="Task<CameraSettings>" typeHref="/docs/sdk-api/csharp-api/classes/camerasettings/">The settings the camera uses afterwards: a camera can adjust a value it cannot apply exactly.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">The camera does not exist or refused a value ([`AR51ErrorCode.Rejected`](/docs/sdk-api/csharp-api/classes/ar51exception/#ar51errorcode)), or could not be reached.</Throw>
</Throws>

:::caution[Affects every app using the system]
Settings are saved on the server and apply to **every app using the system**. Excluding a camera from tracking, or changing its exposure during a live session, changes tracking for everyone.
:::

<Example>

```csharp
CameraSettings settings = await ar51.Cameras.GetSettingsAsync(camera.Id);
await ar51.Cameras.SetSettingsAsync(camera.Id, settings with { ExposureMicroseconds = 4000, Gain = 2 });
```

</Example>

</ApiBody>

### StartAutoTuneAsync

<ApiBody kind="method">

```csharp
Task StartAutoTuneAsync(AutoTuneOptions? options = null, CancellationToken cancellationToken = default);
```

Starts tuning the cameras' color balance (red, green and blue gain) automatically, using each camera's own one-shot white balance; it takes about two seconds. Follow it with `AutoTuneProgressChanged` and `AutoTuneEnded`, then read the new values with [`GetSettingsAsync`](#getsettingsasync).

<Params>
<Param name="options" type="AutoTuneOptions?" typeHref="#autotuneoptions">Which cameras to tune. `null` tunes all of them. Optional.</Param>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Cancels the request. Optional.</Param>
</Params>

<Returns type="Task" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task">A task that completes when the camera servers have started tuning.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">A camera server refused ([`AR51ErrorCode.Rejected`](/docs/sdk-api/csharp-api/classes/ar51exception/#ar51errorcode)) or no camera server is available.</Throw>
</Throws>

:::caution[Affects every app using the system]
Changes the image settings of every camera you include (all of them by default), for **every app using the system**.
:::

<Example>

```csharp
ar51.Cameras.AutoTuneProgressChanged += (_, e) => Console.WriteLine($"auto-tune {e.Status.Progress:P0}");
ar51.Cameras.AutoTuneEnded += (_, e) => Console.WriteLine($"auto-tune {e.Outcome}");
await ar51.Cameras.StartAutoTuneAsync();   // all cameras
```

</Example>

</ApiBody>

### CancelAutoTuneAsync

<ApiBody kind="method">

```csharp
Task CancelAutoTuneAsync(CancellationToken cancellationToken = default);
```

Cancels a running auto-tune, whoever started it. The cameras keep the settings they had before. Does nothing if none is running.

<Params>
<Param name="cancellationToken" type="CancellationToken" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken">Cancels the request. Optional.</Param>
</Params>

<Returns type="Task" typeHref="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task">A task that completes when the camera servers have accepted the request.</Returns>

<Throws>
<Throw type="AR51Exception" typeHref="/docs/sdk-api/csharp-api/classes/ar51exception/">A camera server could not be reached.</Throw>
</Throws>

:::note
A camera server that cannot cancel is not an error: its auto-tune takes about two seconds and finishes on its own, so it ends as [`AutoTuneOutcome.Completed`](#autotuneoutcome).
:::

</ApiBody>

## Related types

Small types used by this interface, documented here.

### PreviewOptions

<Badge tone="type">record · sealed</Badge>

```csharp
public sealed record PreviewOptions
```

Options for [`ICameraClient.StreamPreviewAsync`](#streampreviewasync).

:::note
The image is shrunk to fit inside `FitWidth` × `FitHeight`, keeping the camera's aspect ratio, so you always see the whole picture. It is never enlarged beyond the camera's own size.
:::

<div className="apiPropTable apiTable">

| Property | Type | Access | Default | Description |
|---|---|---|---|---|
| `FitWidth` | `int?` | `get · init` | — | Largest image width, in pixels. `null` = no limit (the camera's own width). |
| `FitHeight` | `int?` | `get · init` | — | Largest image height, in pixels. `null` = no limit (the camera's own height). |
| `FrameRate` | `int?` | `get · init` | — | Frames per second to send. `null` = the camera's own frame rate. |
| `JpegQuality` | `int` | `get · init` | `70` | JPEG quality, from 1 (smallest) to 100 (best). 70 by default. |

</div>

### CameraFrame

<Badge tone="type">record · sealed</Badge>

```csharp
public sealed record CameraFrame(ReadOnlyMemory<byte> Jpeg, int Width, int Height, double CameraFps, DateTimeOffset ReceivedTime);
```

One preview image from a camera.

<div className="apiTable">

| Property | Type | Description |
|---|---|---|
| `Jpeg` | [`ReadOnlyMemory<byte>`](https://learn.microsoft.com/en-us/dotnet/api/system.readonlymemory-1) | The image, JPEG-encoded. |
| `Width` | `int` | Image width, in pixels. |
| `Height` | `int` | Image height, in pixels. |
| `CameraFps` | `double` | Frames per second the camera is capturing at. |
| `ReceivedTime` | [`DateTimeOffset`](https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset) | When this client received the image (local clock). |

</div>

### AutoTuneOptions

<Badge tone="type">record · sealed</Badge>

```csharp
public sealed record AutoTuneOptions
```

Options for [`ICameraClient.StartAutoTuneAsync`](#startautotuneasync).

<div className="apiPropTable apiTable">

| Property | Type | Access | Default | Description |
|---|---|---|---|---|
| `CameraIds` | [`IReadOnlyList<string>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ireadonlylist-1) | `get · init` | `[]` | The cameras to tune, by [`CameraInfo.Id`](/docs/sdk-api/csharp-api/classes/camerainfo/). Empty = all cameras. |

</div>

### AutoTuneStatus

<Badge tone="type">record · sealed</Badge>

```csharp
public sealed record AutoTuneStatus(bool IsRunning, double Progress, IReadOnlyList<CameraAutoTuneStatus> Cameras);
```

The status of an auto-tune across all camera servers.

<div className="apiTable">

| Property | Type | Description |
|---|---|---|
| `IsRunning` | `bool` | Whether an auto-tune is running. |
| `Progress` | `double` | Overall progress, from 0 to 1. |
| `Cameras` | [`IReadOnlyList<CameraAutoTuneStatus>`](#cameraautotunestatus) | Progress of each camera being tuned. |

</div>

<div className="apiPropTable apiTable">

| Property | Type | Access | Description |
|---|---|---|---|
| `IsEstimated` | `bool` | `get · init` | True when the camera servers do not report auto-tune progress and AR51.Net estimates it: progress over about two seconds (the time the servers take), then [`AutoTuneOutcome.Completed`](#autotuneoutcome). A server that reports real progress replaces the estimate as soon as it does. |

</div>

### CameraAutoTuneStatus

<Badge tone="type">record · sealed</Badge>

```csharp
public sealed record CameraAutoTuneStatus(string CameraId, double Progress, string? Error);
```

The auto-tune status of one camera.

<div className="apiTable">

| Property | Type | Description |
|---|---|---|
| `CameraId` | `string` | The camera's [`CameraInfo.Id`](/docs/sdk-api/csharp-api/classes/camerainfo/). |
| `Progress` | `double` | Progress, from 0 to 1. |
| `Error` | `string?` | Why tuning this camera failed, or `null`. |

</div>

### AutoTuneOutcome

<Badge tone="type">enum</Badge>

```csharp
public enum AutoTuneOutcome
```

How an auto-tune ended.

<div className="apiTable">

| Value | Meaning |
|---|---|
| `Completed = 0` | The settings were tuned and applied. |
| `Failed = 1` | Tuning failed; see each camera's [`CameraAutoTuneStatus.Error`](#cameraautotunestatus). |
| `Cancelled = 2` | Someone cancelled it; the cameras kept their previous settings. |

</div>

### AutoTuneStatusEventArgs

<Badge tone="type">class · sealed</Badge>

```csharp
public sealed class AutoTuneStatusEventArgs : EventArgs
```

Data for `ICameraClient.AutoTuneStarted` and `ICameraClient.AutoTuneProgressChanged`.

<div className="apiPropTable apiTable">

| Property | Type | Access | Description |
|---|---|---|---|
| `Status` | [`AutoTuneStatus`](#autotunestatus) | `get` | The auto-tune status. |

</div>

<details>
<summary>Constructor</summary>

```csharp
public AutoTuneStatusEventArgs(AutoTuneStatus status);
```

Creates the event data.

</details>

### AutoTuneEndedEventArgs

<Badge tone="type">class · sealed</Badge>

```csharp
public sealed class AutoTuneEndedEventArgs : EventArgs
```

Data for `ICameraClient.AutoTuneEnded`.

<div className="apiPropTable apiTable">

| Property | Type | Access | Description |
|---|---|---|---|
| `Outcome` | [`AutoTuneOutcome`](#autotuneoutcome) | `get` | How the auto-tune ended. |
| `Status` | [`AutoTuneStatus`](#autotunestatus) | `get` | The final status, with each camera's result. |

</div>

<details>
<summary>Constructor</summary>

```csharp
public AutoTuneEndedEventArgs(AutoTuneOutcome outcome, AutoTuneStatus status);
```

Creates the event data.

</details>

## See also

- [Cameras guide](/docs/csharp-api/cameras/) — walkthrough with a complete, compiled sample
- [Class index](/docs/sdk-api/csharp-api/classes/) — every AR51.Net type
- [Data conventions, threading and errors](/docs/csharp-api/conventions/)
