A FFmpeg.AutoGen wrapper library.
NuGet version (FFmpeg4Sharp) NuGet downloads (FFmpeg4Sharp) Build status
This is NOT a ffmpeg command-line library. Please use FFmpeg shared libraries version >= 5.
Either download from ffmpeg.org according to the license you need, or pull a NuGet redistributable:
NuGet\Install-Package FFmpeg.GPL
NuGet\Install-Package FFmpeg.LGPL
NuGet\Install-Package FFmpeg4Sharp
Namespaces:
using FFmpeg.AutoGen; using FFmpeg.Sharp;
Create a muxer, build an encoder with the fluent builder, then feed frames to EncodeFrame and write the resulting packets.
const string output = "out.mp4"; using var muxer = MediaMuxer.Create(output); using var encoder = MediaEncoder.Video() .OutputFormat(muxer.Format) .Size(800, 600) .Fps(29.97) .Configure(c => c.Ref.thread_count = 0) // 0 = auto (one per core) .Build(); var stream = muxer.AddStream(encoder); muxer.WriteHeader(); using var frame = MediaFrame.CreateVideoFrame(800, 600, encoder.Ref.pix_fmt); for (int i = 0; i < 300; i++) { frame.MakeWritable(); // the encoder may still hold a reference to the frame // ... fill frame.Ref.data[plane] ... frame.Ref.pts = i; // pts in encoder time_base (1/fps) foreach (var packet in encoder.EncodeFrame(frame)) { packet.Ref.stream_index = stream.Ref.index; muxer.WritePacket(packet, encoder.Ref.time_base); // rescales encoder time_base → stream time_base } } muxer.FlushCodecs(new[] { encoder }); // drain the encoder muxer.WriteTrailer();
Use MediaDemuxer.ReadFrames for the common case: route packets to per-stream decoders and yield decoded frames in one call.
var input = "input.mp4"; var output = "frames-out"; using var demuxer = MediaDemuxer.Open(input); var decoders = demuxer .Select(s => (s.Index, decoder: MediaDecoder.CreateDecoder(s.CodecparRef))) .Where(p => p.decoder != null) .ToDictionary(p => p.Index, p => p.decoder); using var convert = new Swscale(); using var bgrFrame = MediaFrame.CreateVideoFrame(decoders.First().Value.Ref.width, decoders.First().Value.Ref.height, AVPixelFormat.AV_PIX_FMT_BGR24); try { foreach (var (streamIndex, frame) in demuxer.ReadFrames(decoders, AVMediaType.AVMEDIA_TYPE_VIDEO)) { convert.Convert(frame, bgrFrame); // single-frame, no array allocation; auto-resets on size change // save bgrFrame somewhere } } finally { foreach (var d in decoders.Values) d.Dispose(); }
using var demuxer = MediaDemuxer.Open("input.mp4"); MediaCodec dec = null; var vi = demuxer.FindBestStream(AVMediaType.AVMEDIA_TYPE_VIDEO, ref dec); using var hwDecoder = new MediaDecoder(dec); hwDecoder.SetCodecParameters(ref demuxer[vi].CodecparRef); hwDecoder.Ref.thread_count = 0; // 0 = auto hwDecoder.InitHWDeviceContext("d3d11va"); // or "cuda", "qsv", "vaapi", ... hwDecoder.Open(); using var pkt = new MediaPacket(); using var recv = new MediaFrame(); using var sw = new MediaFrame(); // optional — omit to keep zero-copy GPU surface foreach (var p in demuxer.ReadPackets(pkt)) { if (p.Ref.stream_index != vi) continue; foreach (var frame in hwDecoder.DecodePacket(p, recv, sw)) { // `frame` is the SW download. Pass null for swFrame above to receive the raw HW surface instead. } }
using var demuxer = MediaDemuxer.Open("input.mp4"); MediaCodec dec = null; int vi = demuxer.FindBestStream(AVMediaType.AVMEDIA_TYPE_VIDEO, ref dec); // One device, shared explicitly across decoder and encoder. using var cuda = HWDeviceContext.Create(AVHWDeviceType.AV_HWDEVICE_TYPE_CUDA); using var hwDecoder = new MediaDecoder(dec); hwDecoder.SetCodecParameters(ref demuxer[vi].CodecparRef); hwDecoder.InitHWDeviceContext(cuda); hwDecoder.Open(); using var hwEncoder = MediaEncoder.Video() .Codec("h264_nvenc") .Size(demuxer[vi].CodecparRef.width, demuxer[vi].CodecparRef.height) .Fps(30) .UseHardware(AVPixelFormat.AV_PIX_FMT_CUDA, AVPixelFormat.AV_PIX_FMT_NV12, cuda) .Bitrate(4_000_000) .Build();
using var resampler = AudioResampler.For(audioDecoder, audioEncoder); foreach (var (_, decoded) in demuxer.ReadFrames(audioDecoders, AVMediaType.AVMEDIA_TYPE_AUDIO)) { foreach (var sized in resampler.Convert(decoded)) using (sized) { foreach (var pkt in audioEncoder.EncodeFrame(sized)) muxer.WritePacket(pkt, audioEncoder.Ref.time_base); } } foreach (var tail in resampler.Flush()) // drain using (tail) { foreach (var pkt in audioEncoder.EncodeFrame(tail)) muxer.WritePacket(pkt, audioEncoder.Ref.time_base); }
More: example/ .
- Codec threading — FFmpeg codecs default to a single thread. Set
thread_countbeforeOpen:.Configure(c => c.Ref.thread_count = 0)on the builder, ordecoder.Ref.thread_count = 0beforeOpen().0means auto (one per core). - Reuse frames/packets in hot loops —
DecodePacket(pkt, recvFrame),EncodeFrame(frame, recvPacket)andReadPackets(pkt)all accept a reusable receive object; passing one avoids a native alloc/free per call (ReadFramesalready does this internally). PlainforeachoverDecodePacket/EncodeFrameuses the zero-allocation struct enumerator; going through LINQ boxes it. - Dispose deterministically — a single 4K NV12 frame holds ~12 MB of native memory the GC cannot see. Rely on
using/Dispose, not finalizers, or native memory grows far ahead of any GC pressure. - Skip unwanted streams at the demuxer — for streams you never consume, set
demuxer[i].Ref.discard = AVDiscard.AVDISCARD_ALL:av_read_framethen drops their packets internally (mpegts skips parsing them entirely) instead of surfacing them just to be ignored. On inputs with many audio/subtitle tracks this cuts demux work several-fold. - Open latency —
MediaDemuxer.Open(..., findStreamInfo: false)skips the probing pass (which can pre-read megabytes) for known-format / low-latency inputs; callFindStreamInfolater or fill decoder parameters yourself. - Single-stream muxing —
WritePacketDirectwrites viaav_write_frame, bypassing the interleaving queue; use it when the output has one stream or you interleave yourself (dts must increase monotonically per stream). For many-stream live muxing, bound interleave memory withmuxer.Ref.max_interleave_delta. - Scaling —
Swscaleis single-threaded by default; setSwscaleOptions.Threads(FFmpeg ≥ 5.1), or run heavy scale/overlay chains in aMediaFilterGraphwithThreadCount = 0(auto). - Hardware pipelines — keep frames on the GPU: pass
swFrame: nulltoDecodePacketfor zero-copy surfaces, and share oneHWDeviceContext/HWFramesContextacross decoder → filters → encoder (see the HW sections above). Download (TransferToSoftware) only when CPU access is genuinely needed.
This release is a heavyweight cleanup driven by an audit (see docs/migration-7-to-8.md for full details and before/after snippets).
Highlights:
MediaFrame.Clone()/MediaPacket.Clone()no longer leak (disposedValuedefault flipped).MediaDemuxer.Open(Stream)/MediaMuxer.Create(Stream)no longer close your stream by default — passleaveOpen: falseto opt in.MediaIOContextcallbacks catch managed exceptions and surface them asIOExceptionon the next managed call (no more crashes from network blips).MediaDemuxer.ReadPacketsno longer yields a ghost packet at EOF; pair withReadPacketsCloned()for safe enqueuing.MediaCodecParserContext.ParserPackets— fixed NRE and dangling-pointer-on-byte[] bug.MediaEncoder.EncodeFrameno longer callsav_frame_make_writableinfinally(callframe.MakeWritable()yourself before reuse).DecodePacket/EncodeFramereturn allocation-free struct cursors (Frames/Packets) and send their input eagerly at call time — plainforeachcode is unaffected; see the migration guide.- New builder:
MediaEncoder.Video()/MediaEncoder.Audio()— the 14 legacyCreateVideoEncoder/CreateAudioEncoderoverloads are removed. TheMediaEncoder.CreateEncoder(codecpar)one-liner remains. - New
MediaCodecContext.Open(opts)instance method. TheAction<MediaCodecContext>-based lambda factories (MediaDecoder.Create,MediaEncoder.Create, theActionparameters onCreateDecoder/CreateEncoder) are removed — configure with straight-line code between the ctor andOpen(). - New hardware encoder path:
MediaEncoder.Video().UseHardware(...)+MediaCodecContext.AttachHWDevice/AttachHWFramesContext. - New
MediaFrame.IsHardwareFrame/TransferToSoftware/AllocateOnHWFrames. - New
AudioResampler,Swscale.Options,Swresample.Flush(...). IConverter.Convertnow returnsint(frames written), notIEnumerable<MediaFrame>. The old enumerable shape (ConvertEnumerable) has been removed.- Typo fixes:
MediaCodec.GetSampelFmts→GetSampleFormats,MediaFilter.GetGetFilters→GetFilters(old names removed). MediaDictionaryindexer returnsnullon miss instead of throwing.- PascalCase field-mirror shortcuts (
Width,Pts,StreamIndex, ...) are removed — raw field access goes through the.Ref.snake_caseescape hatch (p.Ref.stream_index,frame.Ref.width, ...). Take locals withref var x = ref frame.Ref;— a plainvarcopies the struct.
DllNotFoundException: avformat-XX.dll — FFmpeg.AutoGen does not ship native binaries. Either install FFmpeg.GPL / FFmpeg.LGPL NuGets, or set the loader's search root before any FFmpeg call:
ffmpeg.RootPath = @"C:\path\to\ffmpeg\bin"; // or AppDomain.CurrentDomain.BaseDirectory, or any folder that contains avcodec/avformat/avutil/swscale/swresample DLLs
Version mismatch — this library tracks FFmpeg shared libraries 7.x / 8.x (corresponding FFmpeg.AutoGen versions 7.x / 8.x). FFmpeg 6.x or earlier are not supported.
AV_DICT_DONT_STRDUP_KEY / AV_DICT_DONT_STRDUP_VAL — these flags transfer ownership of an av_malloc'd buffer to the dictionary, which the managed wrapper cannot do safely. They are marked [Obsolete(error=true)].
HW decode falls back to software silently — pass fallbackToSw: false (the default) to InitHWDeviceContext to make this fail instead. Use fallbackToSw: true to opt into the graceful fallback.
HW encode EINVAL on first frame — feed frames whose format matches the encoder's hwPixelFormat, allocated with frame.AllocateOnHWFrames(encoder.GetHWFrames()) instead of AllocateBuffer().
Stream gets unexpectedly closed — MediaDemuxer.Open(Stream) / MediaMuxer.Create(Stream) default to leaveOpen: true since 8.1.0, but if you upgraded from 7.x your old call sites may still be wiring the wrapper's lifecycle to your stream. Inspect the third (boolean) argument.
- Easy API for cut/seek/mute audio clip.
- Easy API for cut/seek video clip.
- More examples and tests.
- Filter graph parser (
avfilter_graph_parse2). - Subtitle support.
- Async/IAsyncEnumerable surface (demux/encode/mux).
- FFmpeg.AutoGen — the underlying P/Invoke bindings.
- FFmpeg API documentation.
This project is licensed under the MIT license.
If you use FFmpeg builds licensed under the GPL, that license is contagious.