GstSharp.Net.App
1.28.2
See the version list below for details.
dotnet add package GstSharp.Net.App --version 1.28.2
NuGet\Install-Package GstSharp.Net.App -Version 1.28.2
<PackageReference Include="GstSharp.Net.App" Version="1.28.2" />
<PackageVersion Include="GstSharp.Net.App" Version="1.28.2" />
<PackageReference Include="GstSharp.Net.App" />
paket add GstSharp.Net.App --version 1.28.2
#r "nuget: GstSharp.Net.App, 1.28.2"
#:package GstSharp.Net.App@1.28.2
#addin nuget:?package=GstSharp.Net.App&version=1.28.2
#tool nuget:?package=GstSharp.Net.App&version=1.28.2
GstSharp.Net
Modern .NET bindings for GStreamer 1.28, designed for NativeAOT from the start.
- .NET 10,
IsAotCompatible=trueon every shipping assembly. [LibraryImport]everywhere: interop stubs are produced at build time by the runtime source generator, not by reflection or IL emit.- Zero reflection on the runtime path. Native types are mapped through a generated registry of function pointers, so trimming and AOT compilation keep the whole surface intact.
- Generated from
.gir, by a generator that lives in this repository (generator/GstSharp.Generator). The generated C# is committed, so consumers never need Python,gapi, orxsltproc. - Cross platform: Windows (both the MSVC and the MinGW flavor of the
official GStreamer builds, plus MSYS2), macOS and Linux. The native library is
located at startup by
NativeLoader; nodllmap, no environment variables required.
Generated module set: Gst, GstBase, GstApp, GstVideo, GstAudio,
GstPbutils, GstSdp, GstWebRTC, GstNet, GstRtsp, GES. A twelfth
module, GstController, is written by hand against the public module SPI and
ships alongside them.
Packages
One version for the whole set, and every identifier starts with GstSharp.Net,
so a single packageSourceMapping pattern covers all of them.
| Package | Contents |
|---|---|
GstSharp.Net |
Gst core, the hand-written runtime (native loader, marshalling, GObject/GLib layer) and the Roslyn analyzers. Every other package depends on it. |
GstSharp.Net.Base |
GstBase. |
GstSharp.Net.Controller |
GstController: the interpolation, LFO and trigger control sources, and the direct, ARGB and proxy control bindings that drive a property from one. Hand written against the public module SPI — see docs/modules.md. |
GstSharp.Net.App |
GstApp: appsrc and appsink. |
GstSharp.Net.Video |
GstVideo. |
GstSharp.Net.Audio |
GstAudio. |
GstSharp.Net.Pbutils |
GstPbutils. |
GstSharp.Net.Sdp |
GstSdp: SDP session descriptions and MIKEY key management. |
GstSharp.Net.WebRTC |
GstWebRTC: session descriptions, ICE, transports and data channels for webrtcbin. |
GstSharp.Net.Net |
GstNet: network clocks and time providers. |
GstSharp.Net.Rtsp |
GstRtsp: RTSP connections, messages, URLs and transports. |
GstSharp.Net.GES |
GES: the editing services — timelines, layers, clips and the assets behind them. Initialise through GES.GstGES.Initialize(), which runs ges_init on top of the usual startup. |
The analyzers ship inside GstSharp.Net rather than as a package of their own:
they cannot get out of step with the binding that way, and no second package
reports the same diagnostic twice. They are GST0001 (a wrapper that owns a
reference and never releases it) and GST0002 (a buffer mapping that is never
released); see docs/analyzers.md.
They travel along the package dependency, so a project that references only a
module package — GstSharp.Net.Sdp, say — gets them too. Every module clears
PrivateAssets on its reference to GstSharp.Net to say so, because the
default would pack a dependency that asks for the analyzer assets to be left
behind.
The packages target plain net10.0 and carry managed code only. GStreamer
itself is not bundled: install it separately and let NativeLoader find it.
Installation
dotnet add package GstSharp.Net
dotnet add package GstSharp.Net.App # and one per module you use
Where the packages come from
The set is published to nuget.org. The .nupkg files are also attached to every
GitHub release for
offline use. The copies on GitHub Packages exist for the project's own
release plumbing; that feed requires authentication and is not the intended
way to consume the bindings.
The GstSharp.Net* prefix is why every package identifier shares it: in a
solution that pins feeds with packageSourceMapping, one pattern covers the
whole set.
The native GStreamer
The packages contain managed code only. Install GStreamer itself:
| Platform | How |
|---|---|
| Windows | The official runtime installer of one flavor; msvc and mingw both work. The development installer is not needed — the binding carries its own interop and never compiles against the headers. |
| Windows (MSYS2) | pacman -S mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base mingw-w64-x86_64-gst-plugins-good |
| Linux (Debian/Ubuntu) | apt install libgstreamer1.0-0 libgstreamer-plugins-base1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good, plus libgstreamer-plugins-bad1.0-0 for GstWebRTC and libges-1.0-0 for GES |
| macOS | brew install gstreamer |
Supported versions. The surface is generated from the 1.28.6 .gir files.
The floor at run time is GStreamer 1.24: the struct layouts the ABI probes
mirror have been stable since then. An entry point that GStreamer added after
1.24 is present in the managed surface and throws EntryPointNotFoundException
against an older library — the missing export is the documented behavior, not a
bug. CI runs the whole suite against four installations: Ubuntu 24.04 (1.24, the
floor), the official Windows MSVC build (1.28.6), MSYS2's MinGW build, and
Homebrew on macOS.
Getting started
using Gst;
using Gst.GLib;
GstSharp.Initialize();
// gst_parse_launch returns a GstElement; the cast goes through the type
// registry, which is what turns it into a Pipeline wrapper.
if (Global.ParseLaunch("playbin uri=file:///path/to/movie.mkv") is not Pipeline pipeline)
{
Console.Error.WriteLine("that description is not a pipeline.");
return 1;
}
// A pipeline this code built and stops is the one GObject wrapper a consumer
// disposes; see docs/ownership.md.
using (pipeline)
{
// The bus wrapper is interned and shared with every other lookup of the
// same bus, so it is not disposed here.
Bus bus = pipeline.GetBus();
pipeline.SetState(State.Playing);
// A Message is a mini object: the wrapper owns a reference and has to be
// released. GST0001 reports it when it is not.
using Message? message = bus.TimedPopFiltered(
ClockTime.None,
MessageType.Eos | MessageType.Error);
if (message?.Type == MessageType.Error)
{
(GException error, string? debug) = message.ParseError();
Console.Error.WriteLine($"{message.SourceName}: {error.Message}");
Console.Error.WriteLine($"debug: {debug}");
}
// Back to NULL before the pipeline is released: one that is still PLAYING
// when its last reference goes away leaves its streaming threads running.
pipeline.SetState(State.Null);
}
return 0;
GstSharp.Initialize() loads the native libraries, runs gst_init and fills
the type registry. A module that is only ever named — in a cast, in a type
test — never runs its own initialiser, so reach for the module entry point when
that is all an application does with it: Gst.App.GstApp.Initialize(),
Gst.Base.GstBase.Initialize(), and one next to every other module. The
GType registry
section explains the failure this avoids.
Ownership and lifetime
Read
docs/ownership.md
before writing anything that runs for longer than a second. The short version:
MiniObjectandBoxedwrappers (Buffer,Caps,Sample,Message,Structure, ...) own a reference and must be disposed.GST0001enforces it.GObjectwrappers (Element,Pipeline,Bus,Pad, ...) are interned and shared: normally do not dispose them. Disposal acts for every holder at once. The sanctioned exception is a pipeline this code created, afterSetState(State.Null).- A few calls consume their argument (
AppSrc.PushBuffer,Element.SendEvent,BufferPool.SetConfig,WebRTCSessionDescription.New, ...).Disposeis idempotent, so ausingaround the argument stays correct. - An application with no main loop should call
GstSharp.DrainPendingReleases()periodically — once per poll of the bus is the natural place.
When the loader cannot find GStreamer
NativeLoader probes the registry, the documented environment variables, the
known installation directories, PATH, and an application-bundled
runtimes/<rid> tree. When none of that wins, say where to look:
GstSharp.Initialize(new GstSharpOptions
{
NativeSearchPath = @"C:\gstreamer\1.0\msvc_x86_64\bin",
WindowsFlavor = GstFlavor.Msvc, // or GstFlavor.MinGW
});
And to find out what it did pick, which is the first thing to log in an application that ships to machines you do not own:
Console.WriteLine(NativeLoader.ResolvedDirectory); // the directory that won
Console.WriteLine(NativeLoader.ResolvedFlavor); // Msvc / MinGW, Windows only
Console.WriteLine(NativeLoader.ResolvedOrigin); // which probe stage found it
Console.WriteLine(NativeLoader.ResolvedSourceDescription); // that stage, in words
Console.WriteLine(NativeLoader.GetLoadedModulePath("Gst")); // the file actually mapped
One flavor and one directory are pinned for every module, so a process can never end up with half an MSVC and half a MinGW GStreamer.
Samples
| Sample | What it shows | Run it |
|---|---|---|
samples/PlaybinPlayer |
A pipeline from a description, driven by a polled bus. No main loop, no signal handler. | dotnet run --project samples/PlaybinPlayer |
samples/AppSinkSpans |
Raw video out of an appsink, read through a Span<byte> over the mapped GStreamer memory. Pull mode and signal mode produce the same checksum. |
dotnet run --project samples/AppSinkSpans -- --mode pull |
samples/GstLaunch |
A port of gst-launch-1.0: the whole bus loop, the preroll/buffering/progress state machine, -t -c -v -q -m -e -X -f, --gst-* passthrough and the exit codes of the C tool. One binary with per-OS behavior — Ctrl+C through a GstLaunchInterrupt application message everywhere, SIGHUP and SIGQUIT on POSIX, the multimedia timer on Windows. Its header comment lists what it cannot match. |
dotnet run --project samples/GstLaunch -- videotestsrc num-buffers=100 ! fakesink |
samples/GstTypefind |
A port of gst-typefind-1.0: filesrc ! typefind ! fakesink per file, PAUSED and a blocking GetState, directory recursion, and the <file> - <caps> line of the C tool. It is the sample that connects a signal by name — have-type on a plugin element no .gir describes — and its header comment records what that emission can and cannot hand over. |
dotnet run --project samples/GstTypefind -- <file-or-directory> |
samples/GstDeviceMonitor |
A port of gst-device-monitor-1.0: DeviceMonitor with the DEVICE_CLASSES[:FILTER_CAPS] filters, the device listing with caps and properties, and --follow for hotplug — all of it as messages on the monitor's bus, polled rather than watched from a main loop. Its header comment lists the shell-quoting and property-enumeration parts of the C tool that the binding cannot reach yet. |
dotnet run --project samples/GstDeviceMonitor |
samples/GstDiscoverer |
A port of gst-discoverer-1.0, synchronous path: DiscoverUri per URI, the result and duration, the topology walk with its container recursion, the per-stream blocks for audio, video and subtitles, --verbose tags and --toc. Its output is byte for byte the C tool's on generated media; its header comment says why -a is absent and what a failed discovery cannot report. |
dotnet run --project samples/GstDiscoverer -- <file-or-uri> |
samples/GstInspect |
A partial port of gst-inspect-1.0: the registry census, and the element page as far as the bound surface reaches — factory and plugin details, the type hierarchy, pad templates with their caps, URI handling and the property listing. Every page ends with a note naming the sections it does not print, and its header says what each of them would need. |
dotnet run --project samples/GstInspect -- fakesink |
samples/AotSmoke |
The NativeAOT gate: initialise, make an element, release it, with zero trimming warnings. | dotnet publish samples/AotSmoke -r win-x64 -c Release /p:PublishAot=true |
PlaybinPlayer and AppSinkSpans also take --native-path <directory>,
--flavor msvc\|mingw and --timeout <seconds>; GstLaunch, GstTypefind,
GstDeviceMonitor, GstDiscoverer and GstInspect take the first two. Each
of the five ports adds one option of its own that the C tool does not have, so
that a path which normally needs a console signal or a person can be run
unattended, or so that what the port cannot do stays visible:
GstLaunch --interrupt-after <seconds> drives its Ctrl+C path,
GstDeviceMonitor --follow-for <seconds> bounds a hotplug run,
GstTypefind --fail-on-unknown turns a file whose type was not found into a
non-zero exit code, GstDiscoverer --fail-on-error does the same for a URI
that could not be discovered, and GstInspect --no-coverage-note takes the
closing note off a page that is being diffed against the C tool's.
The official tutorials
samples/tutorials/ holds the GStreamer basic
tutorials ported
onto this binding, one runnable project per tutorial, with the upstream
numbering kept:
| Project | Upstream page | What it teaches |
|---|---|---|
BasicTutorial01 |
Hello world | ParseLaunch, the states, the bus |
BasicTutorial02 |
GStreamer concepts | factories, a pipeline, a link, a property, a parsed error |
BasicTutorial03 |
Dynamic pipelines | pad-added, linking pads, reading caps |
BasicTutorial04 |
Time management | position, duration, the seeking query, SeekSimple |
BasicTutorial06 |
Media formats and pad capabilities | caps, structures, fields, pad templates |
BasicTutorial08 |
Short-cutting the pipeline | appsrc, appsink, a tee and its request pads |
BasicTutorial13 |
Playback speed | seek events with a rate, reverse playback, step events |
The walkthrough text stays upstream; each file carries a header comment saying
where the port differs from the C original and why — a polled bus instead of a
GMainLoop, using instead of gst_*_unref, a typed event instead of
g_signal_connect. samples/tutorials/README.md is the index and explains the
two options the tutorials do not have (--headless and
BasicTutorial13 --keys), which exist so that a tutorial can be run unattended.
dotnet run --project samples/tutorials/BasicTutorial02
dotnet run --project samples/tutorials/BasicTutorial03 -- <file-or-uri>
Properties and signals without a generated binding
Not everything is in the .gir files. Plugins add properties and signals that
introspection never sees, and GStreamer publishes a good deal of its
functionality as action signals, which are calls dressed up as signals. The
by-name surface on Gst.GObject.Object reaches all of it, without reflection:
using Gst.GObject;
// A property no binding exposes, read and written by name.
using Value swapchain = sink.GetProperty("swapchain-handle");
nint handle = swapchain.GetPointer();
using Value sync = Value.CreateFor(false, GType.Boolean);
sink.SetProperty("sync", sync);
// An action signal is a call dressed up as a signal.
sink.EmitSignal("resize", 1920, 1080);
// A signal the .gir never mentioned, connected and disconnected by name.
ulong id = playbin.ConnectSignal("about-to-finish", (sender, args) => null);
playbin.RemoveHandler(id);
Arguments are validated against the signature the object declares before anything is emitted, and a mismatch names the expectation.
What the generator did not bind, and why, is listed per module and per
reason in
girs/skip-report.md.
The gaps worth naming here:
- Subclassing is limited to a closed set of base classes. A C# type can
derive from
Gst.Element,Gst.Bin,Gst.Base.BaseSrc,Gst.Base.PushSrc,Gst.Base.BaseSinkorGst.Base.BaseTransform, override a curated set of vfuncs and be called back through the native vtable. What is not there yet: the rest of the vfuncs, properties and signals on managed types, and construction from native code — an element registered this way cannot be built bygst_element_factory_makeor named in a pipeline description. Seedocs/subclassing.md. - Writing GValue-typed structures is incomplete. Reading is covered —
Value.GetBoxed<T>()for a boxed value andValue.GetMiniObject<T>()for a caps, a tag list or a sample — and building aGValuefor every fundamental type is in progress. A boxed value that is not a mini object still comes back fromEmitSignalas an opaque handle rather than as a typed wrapper. scope="async"callbacks are not generated. The Gio asynchronous pattern is exposed asTask-returning methods instead, hand written per operation; seedocs/gio-async.md.- The byte and bit cursors are out of scope.
GstByteReader,GstByteWriter,GstBitReaderandGstBitWriterwalk a block of memory the caller already owns, and .NET has that surface built in:Span<byte>,BinaryPrimitives(every width and endianness as a single inlined instruction),ArrayBufferWriter<byte>for the growing writer. The memory is already reached as a span —Gst.Buffer.MapandGst.Base.Adapter.Maphand one out,Gst.Buffer.NewMemduptakes one back — and the masked 32 bit start code scan is bound onGst.Base.Adapter.MaskedScanUint32. The C cursors add a bit level position with fixed width reads only — no Exp-Golomb, nothing codec aware. The 148 dropped methods are listed underGstBaseingirs/skip-report.md. This is a decision, not a gap, and it does not change within1.28.x.
Namespaces
The C# namespace of a module is its gir namespace under Gst: GstBase
becomes Gst.Base, GstApp becomes Gst.App, GstWebRTC becomes
Gst.WebRTC, and so on. GES is the exception: its gir namespace is
already top level, so its C# namespace is GES, mirroring the gir rather than
inventing a prefix the C library does not have.
Gst.GLib, Gst.GObject and Gst.Interop are the hand-written runtime, not
generated from the GLib girs. Gst.Gio is a deliberately small hand-written
subset of Gio — cancellables, sockets, TLS — covering what the GStreamer
surface hands out and nothing else.
Status and versioning
Stable. From 1.28.1 on the public surface only grows: new members
appear as the binding closes gaps, existing signatures stay. A behavioral
bug — ownership, lifetime, a wrong contract — is fixed in a patch release and
called out in the release notes. A change that would break compilation waits
for the next GStreamer series.
1.28.1 itself is the one deliberate exception, taken in the first week of
the series while nothing depended on the surface: it re-projected the types a
value copy could not represent — the buffer metas, the static caps and pad
templates — and repaired the shipped members that discarded or overran what
the native call wrote. That window is closed. The compatibility promise above
counts from 1.28.1, and 1.28.0 is unlisted for it.
The version is <gstreamer-major>.<gstreamer-minor>.<binding-patch>:
- major and minor track the GStreamer series the bindings are generated
from.
1.28.xof this package is generated from GStreamer 1.28, and a move to GStreamer 1.30 makes the package1.30.0. - patch is the binding's own counter. It has nothing to do with the
GStreamer patch release;
1.28.3does not imply GStreamer 1.28.3, and the runtime floor stays 1.24 regardless.
Extending the binding
A hand-written binding module can live outside this repository. An assembly
that references the GstSharp.Net package can register its own native library
with the loader, hand its GType-to-wrapper table to TypeRegistry, and derive
its wrappers through the protected constructors of Gst.GObject.Object,
Gst.GObject.InitiallyUnowned, Gst.MiniObject and Gst.GObject.Boxed — with
no InternalsVisibleTo from here. GstSharp.Net.Controller is that module
written out: it binds all of libgstcontroller-1.0, it ships, and nothing
grants it the internals of anything.
It attaches to the generated hierarchy too. Every generated wrapper class —
Gst.Element, Gst.Object, Gst.ControlSource and the rest — carries the same
protected constructor, so a module's classes derive from the wrapper of the
nearest native ancestor and are shaped like the C types they stand for. That
means the members of the generated ancestors are inherited and that generated
API taking one of them takes your wrapper:
Gst.Controller.InterpolationControlSource really is a Gst.ControlSource, and
GES.TrackElement.SetControlSource accepts it, across three assemblies and
still with no grant of internals anywhere.
Generator-backed modules are not supported. The generator is a tool of this repository, not a product, and the code it emits uses internals; a module is written by hand.
docs/modules.md
is the guide: the three registration calls, the obligations that come with each
wrapper base, how to attach to the generated hierarchy and what is still closed
— everything about a generated class except its constructor — and the worked
example, file by file.
Documentation
| Page | Contents |
|---|---|
docs/ownership.md |
Who owns a wrapper, who disposes it, and the GType registry. Start here. |
docs/analyzers.md |
GST0001 and GST0002, what they catch and how to satisfy them. |
docs/gio-async.md |
How Gio's *_async / *_finish pairs become Task-returning methods. |
docs/subclassing.md |
Deriving from Element, Bin and the GstBase classes in C#: the guide is §11, the design is the rest. |
docs/modules.md |
Writing a binding module for a library this repository does not cover, from your own assembly. |
girs/skip-report.md |
Every gir symbol the generator did not bind, grouped by reason. |
eng/ci-notes.md |
Why the workflows look the way they do, and how to run each gate by hand. |
CONTRIBUTING.md |
Build, test, regenerate, and the quality gates a change has to pass. |
Repository layout
| Path | Contents |
|---|---|
girs/ |
Vendored .gir inputs and overlay files. See girs/README.md. |
generator/ |
The .gir to C# generator (console application, no NuGet dependencies). |
src/ |
Shipping libraries: the bindings, the hand-written runtime under src/GstSharp.Net/Core/, and the Roslyn analyzers. |
samples/ |
Runnable samples, including the NativeAOT smoke test and, under samples/tutorials/, the official GStreamer tutorials ported onto the binding. |
tests/ |
Generator unit tests, analyzer tests, and integration tests that need a native GStreamer. |
Building
dotnet build
dotnet test
A native GStreamer installation is only needed for the integration tests and the
samples. See
CONTRIBUTING.md
for the generator commands and the quality gates.
License
LGPL-2.1-or-later. See LICENSE.
The bindings are generated from GStreamer's .gir files and embed their
documentation text, which is LGPL licensed; the same license therefore applies
to the generated sources.
What that means in practice — not legal advice, and not a substitute for reading the license or asking a lawyer:
- GStreamer is loaded dynamically, through
[LibraryImport]against the shared libraries an installation provides. Nothing in these packages links GStreamer statically, including under NativeAOT: an AOT-published application is one native executable of managed origin that still resolveslibgstreamer-1.0at run time. - A closed-source application may use these packages. The LGPL condition that matters is that the user can replace the LGPL parts, which dynamic loading satisfies: point the application at a different GStreamer installation and it uses that one.
- Changes to GstSharp.Net itself are LGPL and belong back here, because the generated sources carry GStreamer's own documentation text.
- The GStreamer installation is a separate question. Its plugins carry their own licenses — several of the "ugly" and "bad" sets are GPL or carry patent conditions — and shipping a runtime alongside an application means auditing what is in it.
| Product | Versions 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. |
-
net10.0
- GstSharp.Net (>= 1.28.2)
- GstSharp.Net.Base (>= 1.28.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.