Periphery.Camera.Avalonia 4.1.0-alpha.2

This is a prerelease version of Periphery.Camera.Avalonia.
dotnet add package Periphery.Camera.Avalonia --version 4.1.0-alpha.2
                    
NuGet\Install-Package Periphery.Camera.Avalonia -Version 4.1.0-alpha.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Periphery.Camera.Avalonia" Version="4.1.0-alpha.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Periphery.Camera.Avalonia" Version="4.1.0-alpha.2" />
                    
Directory.Packages.props
<PackageReference Include="Periphery.Camera.Avalonia" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Periphery.Camera.Avalonia --version 4.1.0-alpha.2
                    
#r "nuget: Periphery.Camera.Avalonia, 4.1.0-alpha.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Periphery.Camera.Avalonia@4.1.0-alpha.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Periphery.Camera.Avalonia&version=4.1.0-alpha.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Periphery.Camera.Avalonia&version=4.1.0-alpha.2&prerelease
                    
Install as a Cake Tool

Periphery.Camera.Avalonia

Avalonia controls for Periphery.Camera. Drop <CameraPreview> into a window, bind it to a DeviceInfo, and a live camera feed appears — no capture loop, no UI-thread plumbing, no session-host wiring.

Install

Add the package alongside Periphery.Camera:

dotnet add package Periphery.Camera.Avalonia

Use it

<Window xmlns="https://github.com/avaloniaui"
        xmlns:cam="https://periphery.dev/camera-avalonia">

  <Grid RowDefinitions="Auto,*,Auto">

    
    <ComboBox x:Name="DevicePicker" Grid.Row="0" .../>

    
    <cam:CameraPreview Grid.Row="1"
                       Name="Preview"
                       Device="{Binding ElementName=DevicePicker, Path=SelectedItem}"
                       MaxResolution="1280,720"/>

    
    <TextBlock Grid.Row="2"
               Text="{Binding ElementName=Preview, Path=StatusDescription}"/>

  </Grid>
</Window>

That's the whole integration. The control owns:

  • DeviceSessionHost<CameraSession> — opens the session, runs the capture loop, handles unplug/replug reconnect.
  • Format negotiation, pixel conversion or JPEG decode, and UI-thread marshalling.
  • Disposal when the control leaves the visual tree.

Public surface

Member Kind Default Purpose
Device DP, read/write null The camera to preview. Setting null disconnects.
MaxResolution DP, read/write 1280×720 Max resolution to negotiate at session open.
IsLive DP, read-only false true when a session is active and frames are flowing.
StatusDescription DP, read-only "Idle." UI-friendly status string. Bind to a TextBlock.
LastError DP, read-only null Most recent open-time / capture-loop error. Cleared on successful reconnect.

All five are AvaloniaProperty registrations, so MVVM bindings work without manual INotifyPropertyChanged plumbing.

Formats

The control opens the camera on the best format it can display, out of what the camera advertises. There is no AllowOnlyPixelFormats filter to configure: the policy is fixed and it is this table.

Camera format How it reaches the screen Cost per frame
Bgra32 strided row copy into a Bgra8888 WriteableBitmap one memcpy per row
Rgba32 strided row copy into an Rgba8888 WriteableBitmap one memcpy per row
Mjpeg Skia JPEG decode into a fresh Bitmap a decode and one allocation
Nv12 scalar BT.601 conversion into a Bgra8888 WriteableBitmap a managed per-pixel loop
Yuy2 scalar BT.601 conversion into a Bgra8888 WriteableBitmap a managed per-pixel loop

Anything else — Uyvy, I420, Yv12, Nv21, Bgr24, Rgb24, Argb32, Gray8, Gray16 — fails at OpenAsync, and LastError carries a message naming the camera's formats and the five above.

Selection order is area, then frame rate, then the table's order. Resolution and frame rate are what a viewer sees; the format preference decides between the several formats a camera usually offers at the same resolution and rate. A 1280×720 MJPEG stream at 30 fps therefore wins over a 320×240 BGRA32 one, and loses to a 1280×720 BGRA32 one.

Colour is BT.601 limited range, which is what UVC cameras overwhelmingly tag. CameraFormat carries no colorimetry, so there is nothing better to key off; a BT.709 source comes out slightly oversaturated.

Surfaces and memory

Raw formats write into a WriteableBitmap that is reused across frames, keyed on width, height and Avalonia pixel format, and reallocated when any of the three changes. Steady state is two surfaces — one being written, one being displayed — so a 1280×720 preview holds about 7 MB of surface and a 1080p one about 16.6 MB. MaxResolution defaults to 1280×720.

MJPEG still allocates a Bitmap per frame. Skia's decoder produces its own output and there is nothing to write into.

Scope

  • Windows and Linux, per Periphery.Camera. On Linux, V4L2 exposes no 32-bit RGB format at all, so the native-copy path is unreachable there and a camera arrives as MJPEG, YUY2 or NV12.
  • No Stretch / StretchDirection. The preview is always uniform-fit within the control's bounds.

Architecture

CameraPreview is a custom Control (no XAML, no inner Image) that:

  • Implements ICameraFrameSink from Periphery.Camera — the same shape as frame-flow's IVideoSink. The pipeline runtime that contract names as the caller was never built (ADR-0045), so the control drives its own capture loop and calls its own OnFormatChangedAsync before the first frame of each session.
  • Drops on overwrite via Interlocked.Exchange — fast cameras don't pile up unrendered frames; the latest pending frame supersedes the previous one, which is disposed and counted via DroppedFrameCount.
  • Renders at a fixed ~60 Hz cadence via a DispatcherTimer that invalidates the visual; the actual Render(DrawingContext) override pulls whatever frame is pending and draws it directly via DrawImage. This decouples render rate from camera frame rate so a 240 Hz industrial camera doesn't trigger 240 invalidations per second.

This is the same pattern frame-flow's FrameFlowVideoView / AvaloniaVideoSink use, including the double-buffered WriteableBitmap write path.

Threading. Three threads. The capture thread runs PresentAsync, converts or decodes the frame, and publishes the surface. The UI thread claims it in Render and records a draw command. The compositor replays that command and performs the actual DrawBitmap, so the bitmap is read after Render returns. Surfaces travel forward and back through Interlocked.Exchange slots, and the capture thread never writes into the current front surface. A just-retired one can still be referenced by an unreplayed draw list; WriteableBitmap.Lock() and the compositor's DrawBitmap take the same Skia monitor, so that write cannot tear, though one side may wait for the other.

The pure core. Format policy (PreviewPixelFormats, PreviewFormatChoice) and the pixel work (PreviewPixels) are total functions over scalars and spans with no Avalonia types, per the functional-core preference recorded in ADR-0052. That is also how they are tested: Avalonia's default headless render interface accepts every pixel format and hands Lock() a throwaway Rgba8888 buffer at width * 4, so a headless pixel test would pass for NV12 without converting anything.

The control has no inner layout, so consumers can wrap it in their own frame, overlay status text, etc.

Cross-references

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.1.0-alpha.2 30 8/30/2026