MediaToolkitNet.Abstractions
0.3.0
See the version list below for details.
dotnet add package MediaToolkitNet.Abstractions --version 0.3.0
NuGet\Install-Package MediaToolkitNet.Abstractions -Version 0.3.0
<PackageReference Include="MediaToolkitNet.Abstractions" Version="0.3.0" />
<PackageVersion Include="MediaToolkitNet.Abstractions" Version="0.3.0" />
<PackageReference Include="MediaToolkitNet.Abstractions" />
paket add MediaToolkitNet.Abstractions --version 0.3.0
#r "nuget: MediaToolkitNet.Abstractions, 0.3.0"
#:package MediaToolkitNet.Abstractions@0.3.0
#addin nuget:?package=MediaToolkitNet.Abstractions&version=0.3.0
#tool nuget:?package=MediaToolkitNet.Abstractions&version=0.3.0
MediaToolkit.NET
Low-level wrappers over the native media stacks for .NET 10, plus one cross-platform API on top of them: device enumeration, camera and microphone capture, audio playback, decoding, recording, probing and transcoding.
Interop goes through [LibraryImport] and unmanaged function pointers. COM on
Windows is called directly through vtable slots, without [ComImport] or the
built-in COM marshaller, so the assemblies stay AOT- and trim-safe.
Install
dotnet add package MediaToolkitNet.All
MediaToolkitNet.All pulls in every backend and selects one at runtime. Take a
single backend instead when you do not want the rest:
| Package | Contents |
|---|---|
MediaToolkitNet.All |
Facade: backend registry and per-platform selection |
MediaToolkitNet.Abstractions |
Abstractions only; no native code, no dependencies |
MediaToolkitNet.Interop |
Native loading, UTF-8 marshalling, COM vtable access |
MediaToolkitNet.FFmpeg |
FFmpeg 7.x, 8.x or 9.x: demux, decode, encode, mux, filter graphs, probing, transcoding, frame reader |
MediaToolkitNet.Mpv |
libmpv client API: playback, filter chains, probing, and its encoding mode as a transcoder |
MediaToolkitNet.Windows |
Media Foundation, WASAPI, DirectShow (devices, graph, Sample Grabber) |
MediaToolkitNet.Linux |
V4L2, ALSA, PulseAudio |
MediaToolkitNet.GStreamer |
GStreamer 1.x on Linux: pipelines, appsink, GstDiscoverer probing, transcoding |
MediaToolkitNet.MacOS |
AVFoundation, CoreAudio |
Quick start
using MediaToolkitNet;
using MediaToolkitNet.Abstractions.Capture;
using MediaToolkitNet.Abstractions.Frames;
// Whichever backend this machine actually has.
using var capture = MediaToolkitNetBackends.CreateVideoCapture(VideoCaptureSettings.Default);
capture.FrameCaptured += (in VideoFrame frame) =>
{
// The frame is borrowed: it is valid only inside this handler.
// Copy the data out if you need to keep it.
ReadOnlySpan<byte> luma = frame.GetPlane(0);
};
capture.Start();
List what a machine can do:
foreach (var backend in MediaToolkitNetBackends.Registered)
{
Console.WriteLine($"{backend.Name}: {backend.IsAvailable}, {backend.Capabilities}");
}
foreach (var device in MediaToolkitNetBackends.EnumerateDevices())
{
Console.WriteLine($"{device.Kind} {device.Name}");
}
Convert, probe and read files
A conversion is described once, as a TranscodeRequest: which streams of which
inputs, copied or re-encoded, subtitles converted or burned in, a trim, tags and
chapters. The first backend that can do all of it runs the job — FFmpeg, then
GStreamer, then mpv — and one that cannot says why before anything is written.
using MediaToolkitNet.Abstractions.Recording;
using MediaToolkitNet.Abstractions.Transcoding;
// Every stream copied into another container.
await MediaToolkitNetBackends.TranscodeAsync(TranscodeRequest.Remux("in.mkv", "out.mp4"));
// A minute of it, re-encoded, with subtitles from another file burned in.
await MediaToolkitNetBackends.TranscodeAsync(new TranscodeRequest("clip.mp4")
{
Inputs = ["in.mkv", "in.en.srt"],
Start = TimeSpan.FromMinutes(1),
End = TimeSpan.FromMinutes(2),
Streams =
[
OutputStream.Video(StreamSource.First(MediaStreamKind.Video),
new VideoOutputSettings(MediaCodec.H264) { Width = 1280, Quality = 23 }),
OutputStream.Audio(StreamSource.First(MediaStreamKind.Audio, language: "eng"),
new AudioOutputSettings(MediaCodec.Aac) { BitrateBitsPerSecond = 160_000 }),
OutputStream.BurnIn(StreamSource.First(MediaStreamKind.Subtitle, input: 1),
StreamSource.First(MediaStreamKind.Video)),
],
});
A specific backend can be asked directly, and asked first whether it can:
var transcoder = new FFmpegTranscoder();
foreach (var issue in transcoder.Validate(request))
{
Console.WriteLine(issue); // "error: ..." or "warning: ..."
}
await transcoder.RunAsync(request, new Progress<TranscodeProgress>(p => Console.Write($"\r{p.Fraction:P0}")));
The backends differ a good deal:
| FFmpeg | GStreamer | mpv | |
|---|---|---|---|
| Stream copy, container change | yes | video and audio | — |
| Re-encode video / audio | yes | yes | one track each |
| Several audio or video streams | yes | yes | — |
| Subtitle copy / conversion | yes | — | — |
| Subtitle burn-in | text (libass) | text | yes |
| Several inputs | yes | yes | external audio and subtitles |
| Trim, tags | yes | yes | yes |
| Chapters | yes | Matroska only, untrimmed | — |
Probing reads the container, streams (codec, language, title, default and forced
flags), tags and chapters, and numbers streams the same way on every backend.
FFmpegMediaReader reads decoded frames, in order or at a time, converted to the
format asked for:
MediaInfo info = MediaToolkitNetBackends.Probe("in.mkv");
using var reader = FFmpegMediaReader.Open("in.mkv",
new MediaReaderOptions { VideoPixelFormat = PixelFormat.Bgra32, VideoWidth = 640, VideoHeight = 360 });
reader.Video!.TryReadAt(TimeSpan.FromSeconds(12), (in VideoFrame frame) => Save(frame));
foreach (var cue in reader.SubtitleStreams[0].ReadAll())
{
Console.WriteLine($"{cue.Start} {cue.Text}");
}
Processing graphs
Each stack is wrapped in its own terms, because the shapes are genuinely different:
// libavfilter: the same chain the ffmpeg command line takes after -vf.
var input = new VideoFormat(1920, 1080, PixelFormat.Bgr24, 30);
using var chain = FFmpegFilterGraph.ForVideo(input, "scale=640:-1,hflip");
chain.Push(sourceFrame);
while (chain.TryReceive((in VideoFrame frame) => Save(frame)))
{
}
// DirectShow: filters and pins, with frames handed back by the Sample Grabber.
using var graph = DirectShowGraph.Create();
var grabber = DirectShowSampleGrabber.AddTo(graph);
grabber.AcceptVideo(PixelFormat.Bgr24);
grabber.VideoGrabbed += (in VideoFrame frame) => Save(frame);
// GStreamer: a whole pipeline from one gst-launch string.
using var pipeline = GStreamerPipeline.Parse(
"filesrc location=clip.mp4 ! decodebin ! videoconvert ! appsink name=sink");
var sink = pipeline.GetSink("sink");
What each backend provides
| Capability | Windows | Linux | macOS | FFmpeg | mpv |
|---|---|---|---|---|---|
| Device enumeration | MF + WASAPI + DShow | ALSA + V4L2 | CoreAudio + AVF | avdevice | — |
| Video capture | IMFSourceReader |
V4L2 mmap | AVCaptureSession |
— | — |
| Audio capture | WASAPI (+ loopback) | ALSA / PulseAudio | AudioQueue | — | — |
| Audio render | WASAPI | ALSA / PulseAudio | AudioQueue | — | — |
| Playback | — | — | AVPlayer |
decode to frames | full |
| Recording to file | IMFSinkWriter |
— | — | encode + mux | — |
| Filter graph | DirectShow + Sample Grabber | GStreamer pipeline | — | libavfilter | vf / af chains |
| Probing a file | — | GstDiscoverer | — | libavformat | track list |
| Transcoding | — | GStreamer pipeline | — | full | encoding mode |
| Reading frames from a file | — | — | — | FFmpegMediaReader |
— |
A dash means the backend does not take that role: FFmpeg owns no audio output, mpv never hands frames back, and on Linux and macOS writing files is the FFmpeg backend's job.
Requirements
- .NET 10, and a 64-bit process: every mapped struct layout assumes a 64-bit pointer.
- No native binary ships in any package. The platform backends only call
libraries that are already part of the operating system, but:
MediaToolkitNet.FFmpegneeds FFmpeg 7.x, 8.x or 9.x as shared libraries. The loader reads the versions it actually got and refuses a mixture belonging to no single series.libavfilteris optional: without it filter graphs and re-encoding transcodes are unavailable;MediaToolkitNet.Mpvneeds libmpv (libmpv-2.dll,libmpv.so.2), built with encoding support for transcoding;MediaToolkitNet.GStreamerneeds GStreamer 1.x and its plugins, and binds the Linux library names, so it reports itself unavailable elsewhere. Probing needs gst-plugins-base (libgstpbutils).
- Native libraries are looked for in any directory given to
NativeSearchPaths.Prepend, then beside the application, in itsnativeandruntimes/<rid>/nativefolders, and finally where the operating system looks (on Windows, thePATH).
A backend whose libraries are missing reports IsAvailable == false instead of
throwing, so probing is always safe.
Status
This is an early release and the public API may still change.
Exercised against real hardware:
- on Windows 11: device enumeration, camera capture, WASAPI audio capture, recording to H.264, HEVC and AAC through Media Foundation, the DirectShow graph with the Sample Grabber, and FFmpeg and libmpv transcoding;
- the FFmpeg backend against FFmpeg 7.1.5 on Debian 13 and Windows 11, and against FFmpeg 9.0.1 on Windows 11, with every struct offset checked against the library actually installed;
- libavfilter, FFmpeg transcoding and the frame reader, and probing and transcoding through GStreamer 1.26.2 and libmpv 2.5, on Debian 13.
Not run yet: mpv playback, FFmpeg 8 and 9 transcoding, the macOS backend, and the V4L2, ALSA and PulseAudio paths on Linux. They compile and are guarded at runtime — a layout that cannot be confirmed disables the feature rather than proceeding — but that is not the same as having been run. See the release notes for the full picture before depending on it.
Links
MIT licensed.
| 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
- No dependencies.
NuGet packages (8)
Showing the top 5 NuGet packages that depend on MediaToolkitNet.Abstractions:
| Package | Downloads |
|---|---|
|
MediaToolkitNet.Interop
Shared native interop primitives for MediaToolkit.NET: native library probing and loading, UTF-8 marshalling helpers, and raw COM vtable access through function pointers instead of [ComImport]. |
|
|
MediaToolkitNet.FFmpeg
FFmpeg backend for MediaToolkit.NET: demuxing, decoding, encoding and muxing through libavformat, libavcodec, libavutil, libswscale and libswresample, plus probing, transcoding with subtitle conversion and burn-in, and a frame reader. Supports the FFmpeg 7.x, 8.x and 9.x ABI and validates the struct layout at startup. The FFmpeg shared libraries are not included and must be present at runtime. |
|
|
MediaToolkitNet.Mpv
libmpv backend for MediaToolkit.NET: a self-rendering media player driven through the mpv client API, including property access, the event loop, filter chains and encoding mode. libmpv is not included and must be present at runtime. |
|
|
MediaToolkitNet.MacOS
macOS backend for MediaToolkit.NET: AVFoundation capture and playback driven through the Objective-C runtime, and CoreAudio AudioQueue for PCM capture and render. |
|
|
MediaToolkitNet.Windows
Windows backend for MediaToolkit.NET: camera capture and file recording through Media Foundation, audio capture, loopback and playback through WASAPI, and DirectShow device enumeration. COM is called through raw vtable slots, so no built-in COM marshalling is required. |
GitHub repositories
This package is not used by any popular GitHub repositories.
MediaToolkit.NET 0.3.0 - the transcoding release.
A file can now be probed, converted and read frame by frame. A conversion is
described once, as a TranscodeRequest, and run by whichever backend can do it:
FFmpeg is asked first, then GStreamer, then mpv. Each says what it cannot do
before anything is written, and when all refuse the exception carries every
refusal.
The README on this page covers what the library does and what it needs at
runtime. These notes cover only what is specific to this release.
ADDED
- Transcoding API (MediaToolkitNet.Abstractions): TranscodeRequest, OutputStream
(copy, drop, re-encode, convert subtitles, burn them in), StreamSource
selectors, VideoOutputSettings and AudioOutputSettings, typed video and audio
filters, TranscodePlan with the rules every backend shares, IMediaTranscoder
with Validate and RunAsync, progress, cancellation that deletes the partial
file, and TranscodeRejectedException listing every issue.
- IMediaProber and MediaInfo: container, duration, streams with codec, language,
title and flags, tags and chapters. Streams are numbered in container order on
every backend.
- Subtitle codecs in MediaCodec (SubRip, Ass, WebVtt, MovText, DvdSubtitle, Pgs)
and ISubtitleRecorder, which FFmpegRecorder implements.
- MediaToolkitNetBackends.Probe, CreateTranscoder and TranscodeAsync.
- FFmpeg: FFmpegTranscoder (stream copy, re-encoding through libavfilter,
several inputs, subtitle conversion, burn-in through libass, trims, tags,
chapters), FFmpegProber, FFmpegMediaReader for decoded frames in order or at a
time in the format asked for, and FFmpegMuxer at the packet level.
- GStreamer: GStreamerProber on GstDiscoverer, and GStreamerTranscoder, which
builds one pipeline from the plan: copy and re-encode of video and audio from
any number of inputs, text burn-in, trims and tags.
- mpv: MpvProber, and MpvTranscoder on mpv's encoding mode: one video and one
audio track re-encoded, subtitles burned in, trims and tags.
- Sample commands: probe with a backend name, transcode (with --vencoder and
--aencoder), read, mpv-args and gst-args. encoders now lists video encoder
MFTs too.
CHANGED
- GStreamerBackend.Capabilities is Probing | Transcoding instead of None.
- Probe and CreateTranscoder try FFmpeg, then GStreamer, then mpv.
- MediaToolkitNet.Mpv references MediaToolkitNet.FFmpeg for the encoder, muxer
and filter names they share. No FFmpeg library is loaded unless the FFmpeg
backend is used.
FIXED (defects of earlier releases found by running the code)
- Media Foundation could not record video: the GUIDs of MF_MT_INTERLACE_MODE and
MFT_CATEGORY_VIDEO_ENCODER were wrong. Every GUID has been checked against the
Windows SDK headers, and the camera records to H.264 and HEVC.
- A bit rate of 0 overwrote the encoder's own default instead of leaving it to
the encoder. Media Foundation's H.264 encoder refused to start at 0.
- libavfilter audio graphs failed on every series: abuffer takes
channel_layout, not ch_layout.
- FFmpegDecoder reported zero audio channels.
- Three GstMessageType values were wrong; the GstMessage::type probe caught it.
- GStreamerSink.NegotiatedFormat was always empty, and a pull asked for the
wrong kind of frame dropped the sample silently. Planar audio is now
delivered.
- gst_plugin_feature_get_name is a macro and was bound as a function.
VERIFIED BY RUNNING (Debian 13, FFmpeg 7.1.5, GStreamer 1.26.2, libmpv 2.5)
- All 206 unit tests and 85 integration tests pass, three runs in a row. The
integration tests read every backend's output back with the FFmpeg prober and
reader, so no backend checks its own work.
- FFmpeg: remux, subtitle conversion with timing identical to ffmpeg's own,
container changes, trims that cut straddling cues at both ends, scaling, frame
rate, CRF, resampling for Opus, burn-in checked by luma, VP9 with WebVTT in
WebM.
- GStreamer: the prober agrees with FFmpeg's; encodes into MP4, Matroska and
WebM; copies with and without a trim; burn-in from the same file and from a
separate SRT.
- mpv, through libmpv: the prober agrees with FFmpeg's and reads a bare SRT or
ASS file; encodes scaled and trimmed; burn-in from the same file and from a
separate SRT; copying refused with the reason. An encoder mpv lacks, or one
that cannot open, is now refused or reported instead of producing a file
without that stream or stalling.
- Windows 11 with FFmpeg 7.1.5 (LGPL) and libmpv 2.5: remux, subtitle
conversion, H.264 through h264_nvenc, libopenh264 and h264_mf, VP9 in WebM,
burn-in through libass from an absolute Windows path with a comma, quotes and
brackets in it; mpv encodes and burn-in; Media Foundation camera recording to
H.264 and HEVC.
NOT EXERCISED YET - PLEASE READ BEFORE DEPENDING ON THIS
- mpv playback, and libmpv other than 2.5.
- FFmpeg 8 and 9 transcoding: the new offsets were checked against their
headers, not by running.
- GStreamer other than 1.26.
KNOWN LIMITATIONS
- GStreamer cannot write subtitles as a stream: its muxers take plain UTF-8 and
its parsers produce Pango markup. The transcoder refuses and names FFmpeg.
- GStreamer 1.26's matroskamux writes tag values containing spaces serialised,
and writes the input's chapters whatever the request says and untrimmed. The
transcoder warns about both.
- mpv copies nothing, writes one video and one audio track, and no chapters.
In MP4 it writes the audio stream first.
- Burn-in is text only on FFmpeg and GStreamer. PGS can be copied but not
produced.
- The GStreamer backend binds the Linux library names, so it is unavailable on
Windows and macOS even where GStreamer is installed.
- Media Foundation video recording needs a video encoder MFT, which Windows N
without the Media Feature Pack lacks; the recorder says so.
- MacOSBackend cannot write files, FFmpegPlayer only decodes, and only the mpv
client API is wrapped, not its render API.
Full notes: https://github.com/yartat/MediaToolkit.NET/releases/tag/v0.3.0
Issues: https://github.com/yartat/MediaToolkit.NET/issues