GstSharp.Net

Note

This API reference is generated from main, which under the 1.28.x compatibility promise is additive over the latest release published on NuGet.

GstSharp.Net

NuGet CI License

Modern .NET bindings for GStreamer 1.28, designed for NativeAOT from the start.

  • .NET 10, IsAotCompatible=true on 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, or xsltproc.
  • 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; no dllmap, no environment variables required.

Generated module set: Gst, GstBase, GstApp, GstVideo, GstAudio, GstPbutils, GstSdp, GstWebRTC, GstNet, GstRtsp, GstAllocators, GstTag, GstTranscoder, GstPlay, GstRtp, GstRtspServer, GES. An eighteenth 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.Rtp GstRtp: the RTP and RTCP buffer helpers of gst-plugins-base, the payloader and depayloader base classes and the header extension API, which is what drives the payloader and depayloader elements the RTP plugins ship and the header extensions built into the library. Authoring a payloader of your own means subclassing GstRTPBasePayload, which the binding does not ship: the module binds the classes as they are used from the outside, not as they are derived from.
GstSharp.Net.RtspServer GstRtspServer: the RTSP server of gst-rtsp-server — the server itself, its clients and sessions, the mount points a media factory is attached to, the media and streams a factory builds, and their ONVIF variants. Needs libgstrtspserver-1.0, which ships separately from the core GStreamer libraries.
GstSharp.Net.Allocators GstAllocators: the file descriptor, DMA-BUF, shared memory and DRM dumb allocators.
GstSharp.Net.Tag GstTag: tag parsing and writing for ID3, Vorbis comments, XMP and EXIF.
GstSharp.Net.Transcoder GstTranscoder: transcoding a media URI into another one against an encoding profile. Needs the transcode plugin of gst-plugins-bad — uritranscodebin and transcodebin — at run time, which ships separately from the libgsttranscoder-1.0 library this module imports from.
GstSharp.Net.Play GstPlay: a high level playback API — a URI, the play controls around it, its media information and its message bus — from gst-plugins-bad. Upstream marks the library API unstable (docs/libs/play/index.md in the GStreamer monorepo). The 1.24 floor carries only the index-based track selection API, which the generator marks [Obsolete] because upstream deprecated it in 1.26 in favour of the track-id calls. Play.Start() is gst_play_play, renamed because Play.Play is not a legal C# member name.
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), GST0002 (a buffer mapping that is never released), GST0003 (a subclass that overrides an OnX vfunc without declaring the matching slot in its DefineSubclass call), GST0004 (the converse, a declared slot with no override behind it) and GST0005 (a CreateWrapper that throws away the SubclassCtorArgs it was handed, so the wrapper it builds is not the one the runtime asked for); 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, GstTranscoder and GstPlay, gstreamer1.0-plugins-bad for the transcode plugin the transcoder drives, 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:

  • MiniObject and Boxed wrappers (Buffer, Caps, Sample, Message, Structure, ...) own a reference and must be disposed. GST0001 enforces it.
  • GObject wrappers (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, after SetState(State.Null).
  • A few calls consume their mini object or boxed argument (AppSrc.PushBuffer, Element.SendEvent, BufferPool.SetConfig, WebRTCSessionDescription.New, ...). Dispose is idempotent, so a using around the argument stays correct.
  • A call that takes a GObject argument over is handed a reference of its own and the wrapper stays yours: it is usable after the call and its handlers keep firing (StreamCollection.AddStream, RTSPMountPoints.AddFactory, ...).
  • 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

The table below is the short index; docs/samples.md is the long-form companion, with the samples grouped by theme, the binding APIs each one exercises and what it deliberately leaves out.

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/AppSrcPush The source half: the application generates the audio and pushes it into an appsrc in push mode, only while need-data says the pipeline wants it and never after enough-data. Bounded by a buffer count, and --output turns the run into a byte count gate on top of the exit code. dotnet run --project samples/AppSrcPush -- --buffers 200
samples/RtpPacketDump The RTP module end to end: every packet an rtpL16pay produced, read through a mapped RTPBuffer — sequence number, timestamp, SSRC, payload type, marker and payload length — and then a compound RTCP packet built through RTCPBuffer/RTCPPacket, unmapped, mapped again and walked back. Its header comment states the lifetime rules of docs/ownership.md that the mapped structures impose. dotnet run --project samples/RtpPacketDump
samples/CustomMeta A metadata implementation of the application's own: an API type registered with no tags, Meta.Register<T> over an unmanaged payload, one item attached to every frame that goes into an appsrc, and the same payload read back off the frames a videoconvert allocated on the other side. Its header comment says which of the two mechanisms — the empty tag list and the transformation delegate — does what. dotnet run --project samples/CustomMeta
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 namehave-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. Property enumeration is implemented; the one case its header comment records as out of reach is the shell quoting of a property value that is not UTF-8, which a managed string has already decoded by the time the sample sees it. dotnet run --project samples/GstDeviceMonitor
samples/GstDiscoverer A port of gst-discoverer-1.0, synchronous path: TryDiscoverUri 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. dotnet run --project samples/GstDiscoverer -- <file-or-uri>
samples/GstInspect A full port of gst-inspect-1.0: the registry census, and every section of an element page — factory and plugin details, the type hierarchy, implemented interfaces, element flags, pad templates with their caps, clocking interaction, URI handling with its protocols, pads, the whole property listing with ranges, defaults and enumeration and flags tables, signals and action signals with their C signatures, children and presets, and the Type: line and Pad Properties block of a pad template whose pads are a class of their own. The CI legs whose gst-inspect-1.0 is 1.28.3 or newer — macOS and the Windows MinGW leg today; an older tool prints an older page format, so those legs skip the diff with a warning — diff it against the real tool on the same install and fail on any difference, so it reproduces that tool's quirks rather than fixing them; its header names the quirks, and what else is out of reach. dotnet run --project samples/GstInspect -- fakesink
samples/GstTranscode Transcoding one URI into another against a serialized GstEncodingProfile, on the route the transcoder documents as the recommended one: RunAsync plus a polled API bus, with no main loop and no signal adapter. It is also where the hand-written ParseError earns its keep — the imported one aborts the process on an error that carries no details. dotnet run --project samples/GstTranscode -- file:///in.ogg file:///out.ogg
samples/GstPlay A port of gst-play-1.0's user experience onto the Gst.Play.Play object: a playlist, from the command line or from a --playlist file, the keyboard controls, --volume, --audiosink/--videosink, --flags, --wait-on-eos, --visualization and --list-visualizations, with the API bus read by a timed pop rather than watched from a main loop. It writes the two sink properties on the playbin that GetPipeline() answers, the way the C tool does; PlayVideoOverlayVideoRenderer, the other way to place the video, is for a GUI application that has a window handle to embed it in. Headless is the default — nothing reads the keyboard without --interactive. dotnet run --project samples/GstPlay -- --duration 10 <file-or-uri>
samples/RtspServer The port of gst-rtsp-server's examples/test-launch.c: one mount point built from a gst-launch description, served until it is asked to stop, and then shut down in the order the library documents. Without a launch line it serves a test tone, so it runs on a machine with no media on it. dotnet run --project samples/RtspServer -- --port 8554
samples/RtspClient The client half: rtspsrc plays one mount point, select-stream decides which announced stream is set up — a signal with a return value, connected by name — and the pads that follow are built into a receiving branch that counts the L16 audio at an appsink. Without a URL it hosts the mount RtspServer serves by default and plays that, so the whole conversation runs in one process, with the pipeline reaching NULL before the server is taken down. dotnet run --project samples/RtspClient -- rtsp://127.0.0.1:8554/test
samples/GesCustomSource A timeline of the editing services whose clip and whose source are managed types: the clip builds its own track element and the source answers the element behind it, both through overrides of the class struct slots. It is the smallest application that exercises the child contract of docs/subclassing.md. dotnet run --project samples/GesCustomSource -- --timeout 20
samples/GesLaunch A port of ges-launch-1.0: the ges: description with its escaping and its synthesized +track keywords, --load/--save/--save-only of a project, the render branch with its encoding profile, the preview sinks and the keyboard controls. It is where the asynchronous half of the editing services shows: loaded is deferred through an idle source, so the sample iterates the default main context until the project answers, and its header comment says why it may not push a context of its own. dotnet run --project samples/GesLaunch -- +test-clip snow d=1.0
samples/WebRtcLoopback Two webrtcbin elements in one pipeline calling each other: the offer/answer round trip and the ICE candidate exchange, emitted and connected by name because webrtcbin has no .gir, with the negotiation callbacks, the streaming threads and the polled bus of the main thread all sharing state. No signalling server — both peers are in the process. dotnet run --project samples/WebRtcLoopback -- --buffers 100 --print-sdp
samples/AotSmoke The NativeAOT gate: initialise, make an element, release it, and run the managed subclasses of the sample - an element, a source and sink pair, an audio sink, a video sink, an audio encoder and an element an element factory made - with zero trimming warnings. dotnet publish samples/AotSmoke -r win-x64 -c Release /p:PublishAot=true

PlaybinPlayer, AppSinkSpans, AppSrcPush and CustomMeta also take --native-path <directory>, --flavor msvc\|mingw and --timeout <seconds>; GstLaunch, GstTypefind, GstDeviceMonitor, GstDiscoverer and GstInspect take the first two. Four of those five ports add one option of their 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: 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, and GstDiscoverer --fail-on-error does the same for a URI that could not be discovered. GstInspect adds none: its page is diffed against the C tool's byte for byte, so it prints nothing the C tool does not.

GstTranscode takes no option of its own: it is <src-uri> <dst-uri> [<profile>], where the profile defaults to application/ogg:audio/x-vorbis. It needs the uritranscodebin and transcodebin elements of the transcode plugin of gst-plugins-bad at run time, which ship separately from the libgsttranscoder-1.0 library the module imports from; without them the sample says so and stops rather than reporting a transcoding failure.

GstPlay takes a playlist of URIs or paths and these options of its own: --playlist <file> to append the entries of a playlist file to it, --volume <0..1>, --audiosink <factory> and --videosink <factory>, --flags <spec> to write the initial flags of the playbin as +-joined nicks or a number, --visualization <name> beside --list-visualizations, --duration <seconds> to bound an unattended run, --wait-on-eos to keep the last frame up instead of moving on, --shuffle to reorder the playlist once before playback, and --interactive to read the keyboard (press k for the list of keys). --wait-on-eos needs one of --duration and --interactive, because on its own it would never end. Its header comment says which keys the C tool puts elsewhere and what it leaves out — --gapless and --instant-uri among them, both of which need a playbin driven directly. Cycling tracks goes through the index-based setters, which upstream deprecated in 1.26 and the generator therefore marks [Obsolete]; they are the only ones that exist on the 1.24 floor, so the sample calls them under a #pragma warning disable CS0618.

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
BasicTutorial07 Multithreading and pad availability a tee, its request pads, a queue per branch
BasicTutorial08 Short-cutting the pipeline appsrc, appsink, a tee and its request pads
BasicTutorial09 Media information gathering GstDiscoverer, an answer that arrives as a signal, the topology
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 options the tutorials do not have (--headless, BasicTutorial13 --keys and the per-tutorial bounds), 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 an allowlist of base classes. A C# type can derive from one of twenty eight classes — Gst.Element, Gst.Bin, Gst.Pad, the GstBase sources, sinks, transforms, parsers and aggregators including AggregatorPad, the GstAudio sinks, sources, filters and codecs, the GstVideo sinks, filters and codecs, and the timeline elements, sources and clips of the editing services — override the vfuncs of the class and be called back through the native vtable. A type that also states how its wrapper is built can be registered with gst_element_register and made by gst_element_factory_make or named in a pipeline description. It can install properties, define signals and implement GstURIHandler, so gst_element_make_from_uri finds it. What is not there yet: construct properties, interfaces other than GstURIHandler, and defining a new interface from managed code. See docs/subclassing.md.
  • Writing GValue-typed structures is incomplete. Reading is covered — Value.GetBoxed<T>() for a boxed value and Value.GetMiniObject<T>() for a caps, a tag list or a sample — and building a GValue for every fundamental type is in progress. A boxed value that is not a mini object still comes back from EmitSignal as an opaque handle rather than as a typed wrapper.
  • scope="async" callbacks are not generated. The Gio asynchronous pattern is exposed as Task-returning methods instead, hand written per operation; see docs/gio-async.md.
  • The byte and bit cursors are out of scope. GstByteReader, GstByteWriter, GstBitReader and GstBitWriter walk 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.Map and Gst.Base.Adapter.Map hand one out, Gst.Buffer.NewMemdup takes one back — and the masked 32 bit start code scan is bound on Gst.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 under GstBase in girs/skip-report.md. This is a decision, not a gap, and it does not change within 1.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.x of this package is generated from GStreamer 1.28, and a move to GStreamer 1.30 makes the package 1.30.0.
  • patch is the binding's own counter. It has nothing to do with the GStreamer patch release; 1.28.3 does 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

The API reference and these guides are published as a site at https://masa-iwm.github.io/GstSharp.Net/, built from main.

Page Contents
docs/ownership.md Who owns a wrapper, who disposes it, and the GType registry. Start here.
docs/analyzers.md GST0001 to GST0005, 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.
docs/platform-notes.md Behaviour that is a property of one platform, such as which Windows device providers a DeviceMonitor can watch.
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.
benches/ A BenchmarkDotNet harness for the dispatch, property, mapping and interning paths. Every CI job builds it, none runs it. See benches/README.md.

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 resolves libgstreamer-1.0 at 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.