PipeWire.NET 0.1.0-alpha

This is a prerelease version of PipeWire.NET.
dotnet add package PipeWire.NET --version 0.1.0-alpha
                    
NuGet\Install-Package PipeWire.NET -Version 0.1.0-alpha
                    
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="PipeWire.NET" Version="0.1.0-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PipeWire.NET" Version="0.1.0-alpha" />
                    
Directory.Packages.props
<PackageReference Include="PipeWire.NET" />
                    
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 PipeWire.NET --version 0.1.0-alpha
                    
#r "nuget: PipeWire.NET, 0.1.0-alpha"
                    
#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 PipeWire.NET@0.1.0-alpha
                    
#: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=PipeWire.NET&version=0.1.0-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=PipeWire.NET&version=0.1.0-alpha&prerelease
                    
Install as a Cake Tool

PipeWire.NET

NuGet build License: MIT

.NET bindings for PipeWire on Linux. Capture and publish video and audio through the local media graph, with an API that stays close to PipeWire while feeling natural in C#.

await using var ctx = new PipeWireContext();
await ctx.StartAsync();

await using var camera = new PipeWireVideoCapture(ctx);
camera.FrameReady += (_, frame) =>
    Console.WriteLine($"{frame.Width}x{frame.Height} {frame.Format}");
camera.Connect();                       // auto-selects the default video source

await Task.Delay(TimeSpan.FromSeconds(5));

What you get

  • Capture and publish, for both video and audio.
  • Source discovery through the registry (cameras, microphones, virtual nodes).
  • Frame timestamps on a shared clock, so audio and video can be kept in sync.
  • Efficient buffers: frames are read straight from shared memory with no copy, and DMA-BUF file descriptors are exposed for GPU import on the capture side.
  • NativeAOT friendly: source-generated P/Invoke, no reflection.

Requirements

OS Linux (x64 / arm64)
Runtime libpipewire-0.3.so.0 (ships with any PipeWire install)
Daemon A running PipeWire daemon plus a session manager such as WirePlumber
.NET .NET 10, or .NET 11 (preview)
sudo apt-get install pipewire wireplumber     # Debian / Ubuntu
sudo dnf install pipewire wireplumber          # Fedora
sudo pacman -S pipewire wireplumber            # Arch

Install

dotnet add package PipeWire.NET

Usage

Discover sources

await using var registry = new PipeWireRegistry(ctx);
await registry.WaitForInitialEnumerationAsync();

foreach (var source in registry.Sources.Where(s => s.IsVideoSource))
    Console.WriteLine($"[{source.NodeId}] {source.Description} ({source.Class})");

Capture video

await using var capture = new PipeWireVideoCapture(ctx);

capture.FrameReady += (_, frame) =>
{
    // frame.Data is valid only inside this handler. Do not store it.
    Process(frame.Data, frame.Stride);
};

capture.Connect(source, preferredFormats: [PixelFormat.Bgra, PixelFormat.Rgba]);

Publish a virtual camera

await using var output = new PipeWireVideoOutput(ctx, "My Virtual Camera",
    width: 1280, height: 720, format: PixelFormat.Bgra, frameRate: 30);

output.FillFrame += (_, pixels, stride, w, h, format) =>
{
    RenderInto(pixels, stride, w, h);   // write straight into the buffer
    return true;
};

output.Connect();

On Linux this is how you feed a tool like OBS: publish a node here, then add a PipeWire video source in OBS and it reads the feed. It is the Linux counterpart to Spout on Windows or Syphon on macOS.

Capture and publish audio

await using var mic = new PipeWireAudioCapture(ctx);
mic.FrameReady += (_, frame) =>
    Mix(frame.Samples, frame.SampleRate, frame.Channels, frame.Format);
mic.Connect();

await using var synth = new PipeWireAudioOutput(ctx, "Synth",
    sampleRate: 48000, channels: 2, format: AudioSampleFormat.F32Le);
synth.FillSamples += (_, buffer, rate, channels, format) => Synthesize(buffer);
synth.Connect();

Both capture types accept a targetObjectName to bind to a specific node by name instead of relying on the session manager's default routing.

Frames

VideoFrame and AudioFrame are ref structs delivered on the loop thread; their data is valid only for the duration of the handler.

VideoFrame carries the pixels (Data, Stride, Width, Height, Format), the negotiated Color info, the backing memory (BufferType, Fd, MapOffset), and timing (see below). AudioFrame carries Samples, SampleRate, Channels, Format, FrameCount, and timing.

Timing and A/V sync

Every stream runs off one graph clock. Each frame carries:

  • CaptureClockNs: the monotonic graph time of the cycle that delivered it. It is the same clock for every stream, so align audio against video on this value to keep them in sync.
  • MediaClockNs and DelayNs: the stream's media position and its latency, for sample-accurate timestamping.

PresentationTimeNs is the content timestamp from the buffer header. Video sources provide it; PipeWire audio does not, so for audio it is -1. Use CaptureClockNs for sync.

Zero copy

On capture, frame.Data points straight into the daemon's mapped buffer, so reading is free. Capture also accepts DMA-BUF buffers, so a GPU source can hand frames over without touching the CPU; frame.BufferType and frame.Fd expose the descriptor for GPU import.

On publish, FillFrame and FillSamples give you a span over the daemon's buffer, so you write the frame once with no intermediate copy.

Screen capture on Wayland

This library does not deal with Wayland directly. Screen capture goes through the org.freedesktop.portal.ScreenCast portal, which after the user grants permission returns a PipeWire node id. Pass that id to PipeWireVideoCapture.Connect(nodeId) and it behaves like any other source. Drive the portal with any D-Bus library; this library takes it from the node id on.

How it is built

The low-level bindings in src/PipeWire.NET/generated/ are produced by ClangSharpPInvokeGenerator from the installed PipeWire headers and committed to the repo, so consumers never run the generator. The hand-written high-level types (PipeWireContext, the four stream classes, PipeWireRegistry) and the SPA pod helpers sit on top.

To regenerate after a PipeWire version bump (on Linux, with libpipewire-0.3-dev and libclang-dev):

dotnet tool install --global ClangSharpPInvokeGenerator --version 21.1.8.3
bash generate/generate.sh

CI runs the generator on every build and fails if the committed output drifts.

Testing

dotnet test --filter "TestCategory!=Integration"     # pure logic, runs anywhere
dotnet test --filter "TestCategory=Integration"      # needs a running daemon

Integration tests run against a live daemon. Some start real producers through GStreamer (videotestsrc, audiotestsrc) and check capture across formats, registry discovery, real frame content, alpha preservation, timestamps, and audio/video sharing one clock. Tests tagged RequiresGpu cover DMA-BUF capture and run on a host with a GPU; everything else runs on CI against a headless PipeWire.

Scope

This library is the PipeWire layer: it delivers correctly formatted, correctly timed frames and samples in and out. Encoding and network transport live above it.

Producing DMA-BUF output is intentionally left out. OBS and other consumers read the host-memory output efficiently, the frames would have to be GPU-resident to benefit, and it is not on the capture path. DMA-BUF on the capture side is supported because that path genuinely benefits.

License

MIT, see LICENSE. PipeWire is MIT licensed and is not redistributed here.

Product Compatible and additional computed target framework versions.
.NET 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.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net11.0

    • No dependencies.

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
0.1.0-alpha 174 6/2/2026