Producing streamable video from screen using gdigrab and ffmpeg.autogen libav wrapper

I’m working on creating a class that captures the screen, encodes it using H.264, and outputs it in a streamable format to a pipe. For this, I’m using the ffmpeg.autogen version 5.1.2.3 wrapper, along with the libav binaries from ffmpeg-n5.1-latest-win64-gpl-shared-5.1. Although I’m not an expert, I believe the following options are necessary to facilitate a streamable output: frag_keyframe, empty_moov, default_base_moof, and faststart.

The issue isn’t with the receiving end, which I can confirm is functioning correctly. Despite not encountering any errors, the class I’ve written doesn’t seem to produce a streamable output when tested with libVLC. There must be something wrong with the class implementation itself.

VLC OUTPUT:

Creating an input for 'imem://'
using timeshift granularity of 50 MiB
using timeshift path: C:UserskwistAppDataLocalTemp
creating demux: access='imem' demux='any' location='' file='(null)'
looking for access_demux module matching "imem": 15 candidates
`imem://' gives access `imem' demux `any' path `'
Invalid get/release function pointers
no access_demux modules matched
using access module "imem_access"
looking for stream_filter module matching "prefetch,cache_read": 24 candidates
using 16777216 bytes buffer, 16777216 bytes read
using stream_filter module "prefetch"
looking for stream_filter module matching "any": 24 candidates
creating access: imem://
looking for access module matching "imem": 27 candidates
Trying Lua scripts in C:UserskwistAppDataRoamingvlcluaplaylist
Trying Lua scripts in C:Program FilesVideoLANVLCluaplaylist
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistanevia_streams.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistanevia_xml.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistappletrailers.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistbbc_co_uk.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistcue.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistdailymotion.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistjamendo.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistkoreus.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistliveleak.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistnewgrounds.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistrockbox_fm_presets.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistsoundcloud.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylisttwitch.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistvimeo.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistvocaroo.luac
Trying Lua playlist script C:Program FilesVideoLANVLCluaplaylistyoutube.luac
looking for stream_directory module matching "any": 1 candidates
no stream_filter modules matched
no stream_directory modules matched
attachment of directory-extractor failed for imem://
using stream_filter module "record"
looking for demux module matching "any": 55 candidates
creating demux: access='imem' demux='any' location='' file='(null)'
looking for stream_filter module matching "record": 24 candidates
looking for xml reader module matching "any": 1 candidates
using xml reader module "xml"
subtitle demux discarded
TS module discarded (lost sync)
MOD validation failed (ext=)
trying url: imem://
CPU flags: 0x000fd3db
this does not look like an MPEG PS stream, continuing anyway
using demux module "ps"
looking for meta reader module matching "any": 2 candidates
Trying Lua scripts in C:UserskwistAppDataRoamingvlcluametareader
Trying Lua playlist script C:Program FilesVideoLANVLCluametareaderfilename.luac
`imem://' successfully opened
garbage at input from 509, trying to resync...
no meta reader modules matched
Trying Lua scripts in C:Program FilesVideoLANVLCluametareader

The code:

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using FFmpeg.AutoGen;

namespace DeltaFramesStreaming
{
    public unsafe class ScreenStreamer : IDisposable
    {
        private readonly AVCodec* productionCodec;
        private readonly AVCodec* screenCaptureAVCodec;
        private readonly AVCodecContext* productionAVCodecContext;
        private readonly AVFormatContext* productionFormatContext;
        private readonly AVCodecContext* screenCaptureAVCodecContext;
        private readonly AVDictionary* productionAVCodecOptions;
        private readonly AVInputFormat* screenCaptureInputFormat;
        private readonly AVFormatContext* screenCaptureInputFormatContext;
        private readonly int gDIGrabVideoStreamIndex;
        private readonly System.Drawing.Size screenBounds;
        private readonly int _produceAtleastAmount;
        private MemoryStream unsafeToManagedBridgeBuffer;
        private CancellationTokenSource cancellationTokenSource;
        private Task recorderTask;
        private PipeWriter _pipeWriter;

        public ScreenStreamer(int fps, int bitrate, int screenIndex, PipeWriter pipeWriter, int produceAtleastAmount = 1000)
        {
            _pipeWriter = pipeWriter;
            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_network_init();
            recorderTask = Task.CompletedTask;
            cancellationTokenSource = new CancellationTokenSource();
            unsafeToManagedBridgeBuffer = new MemoryStream();
            _produceAtleastAmount = produceAtleastAmount;

            // Allocate and initialize production codec and context
            productionCodec = ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264);
            if (productionCodec == null) throw new ApplicationException("Could not find encoder for codec ID H264.");

            productionAVCodecContext = ffmpeg.avcodec_alloc_context3(productionCodec);
            if (productionAVCodecContext == null) throw new ApplicationException("Could not allocate video codec context.");

            // Set codec parameters
            screenBounds = RetrieveScreenBounds(screenIndex);
            productionAVCodecContext->width = screenBounds.Width;
            productionAVCodecContext->height = screenBounds.Height;
            productionAVCodecContext->time_base = new AVRational() { den = fps, num = 1 };
            productionAVCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
            productionAVCodecContext->bit_rate = bitrate;

            int result = ffmpeg.av_opt_set(productionAVCodecContext->priv_data, "preset", "veryfast", 0);
            if (result != 0)
            {
                throw new ApplicationException($"Failed to set options with error code {result}.");
            }

            // Open codec
            fixed (AVDictionary** pm = &productionAVCodecOptions)
            {
                result = ffmpeg.av_dict_set(pm, "movflags", "frag_keyframe+empty_moov+default_base_moof+faststart", 0);
                if (result < 0)
                {
                    throw new ApplicationException($"Failed to set dictionary with error code {result}.");
                }

                result = ffmpeg.avcodec_open2(productionAVCodecContext, productionCodec, pm);
                if (result < 0)
                {
                    throw new ApplicationException($"Failed to open codec with error code {result}.");
                }
            }

            // Allocate and initialize screen capture codec and context
            screenCaptureInputFormat = ffmpeg.av_find_input_format("gdigrab");
            if (screenCaptureInputFormat == null) throw new ApplicationException("Could not find input format gdigrab.");

            fixed (AVFormatContext** ps = &screenCaptureInputFormatContext)
            {
                result = ffmpeg.avformat_open_input(ps, "desktop", screenCaptureInputFormat, null);
                if (result < 0)
                {
                    throw new ApplicationException($"Failed to open input with error code {result}.");
                }

                result = ffmpeg.avformat_find_stream_info(screenCaptureInputFormatContext, null);
                if (result < 0)
                {
                    throw new ApplicationException($"Failed to find stream info with error code {result}.");
                }
            }

            gDIGrabVideoStreamIndex = -1;
            for (int i = 0; i < screenCaptureInputFormatContext->nb_streams; i++)
            {
                if (screenCaptureInputFormatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
                {
                    gDIGrabVideoStreamIndex = i;
                    break;
                }
            }

            if (gDIGrabVideoStreamIndex < 0)
            {
                throw new ApplicationException("Failed to find video stream in input.");
            }

            AVCodecParameters* codecParameters = screenCaptureInputFormatContext->streams[gDIGrabVideoStreamIndex]->codecpar;
            screenCaptureAVCodec = ffmpeg.avcodec_find_decoder(codecParameters->codec_id);
            if (screenCaptureAVCodec == null)
            {
                throw new ApplicationException("Could not find decoder for screen capture.");
            }

            screenCaptureAVCodecContext = ffmpeg.avcodec_alloc_context3(screenCaptureAVCodec);
            if (screenCaptureAVCodecContext == null)
            {
                throw new ApplicationException("Could not allocate screen capture codec context.");
            }

            result = ffmpeg.avcodec_parameters_to_context(screenCaptureAVCodecContext, codecParameters);
            if (result < 0)
            {
                throw new ApplicationException($"Failed to copy codec parameters to context with error code {result}.");
            }

            result = ffmpeg.avcodec_open2(screenCaptureAVCodecContext, screenCaptureAVCodec, null);
            if (result < 0)
            {
                throw new ApplicationException($"Failed to open screen capture codec with error code {result}.");
            }
        }

        public void Start()
        {
            recorderTask = Task.Run(async () =>
            {
                AVPacket* packet = ffmpeg.av_packet_alloc();
                AVFrame* rawFrame = ffmpeg.av_frame_alloc();
                AVFrame* compatibleFrame = null;
                byte* dstBuffer = null;

                try
                {
                    while (!cancellationTokenSource.Token.IsCancellationRequested)
                    {
                        if (ffmpeg.av_read_frame(screenCaptureInputFormatContext, packet) >= 0)
                        {
                            if (packet->stream_index == gDIGrabVideoStreamIndex)
                            {
                                int response = ffmpeg.avcodec_send_packet(screenCaptureAVCodecContext, packet);
                                if (response < 0)
                                {
                                    throw new ApplicationException($"Error while sending a packet to the decoder: {response}");
                                }

                                response = ffmpeg.avcodec_receive_frame(screenCaptureAVCodecContext, rawFrame);
                                if (response == ffmpeg.AVERROR(ffmpeg.EAGAIN) || response == ffmpeg.AVERROR_EOF)
                                {
                                    continue;
                                }
                                else if (response < 0)
                                {
                                    throw new ApplicationException($"Error while receiving a frame from the decoder: {response}");
                                }

                                compatibleFrame = ConvertToCompatiblePixelFormat(rawFrame, out dstBuffer);

                                response = ffmpeg.avcodec_send_frame(productionAVCodecContext, compatibleFrame);
                                if (response < 0)
                                {
                                    throw new ApplicationException($"Error while sending a frame to the encoder: {response}");
                                }

                                while (response >= 0)
                                {
                                    response = ffmpeg.avcodec_receive_packet(productionAVCodecContext, packet);
                                    if (response == ffmpeg.AVERROR(ffmpeg.EAGAIN) || response == ffmpeg.AVERROR_EOF)
                                    {
                                        break;
                                    }
                                    else if (response < 0)
                                    {
                                        throw new ApplicationException($"Error while receiving a packet from the encoder: {response}");
                                    }

                                    using var packetStream = new UnmanagedMemoryStream(packet->data, packet->size);
                                    packetStream.CopyToAsync(unsafeToManagedBridgeBuffer).GetAwaiter().GetResult();
                                    byte[] managedBytes = unsafeToManagedBridgeBuffer.ToArray();
                                    _pipeWriter.WriteAsync(managedBytes).AsTask().GetAwaiter().GetResult();
                                    unsafeToManagedBridgeBuffer.SetLength(0);
                                }
                            }
                        }
                        ffmpeg.av_packet_unref(packet);
                        ffmpeg.av_frame_unref(rawFrame);
                        if (compatibleFrame != null)
                        {
                            ffmpeg.av_frame_unref(compatibleFrame);
                            ffmpeg.av_free(dstBuffer);
                        }
                    }
                }
                finally
                {
                    ffmpeg.av_packet_free(&packet);
                    ffmpeg.av_frame_free(&rawFrame);
                    if (compatibleFrame != null)
                    {
                        ffmpeg.av_frame_free(&compatibleFrame);
                    }
                }
            });
        }

        public AVFrame* ConvertToCompatiblePixelFormat(AVFrame* srcFrame, out byte* dstBuffer)
        {
            AVFrame* dstFrame = ffmpeg.av_frame_alloc();
            int buffer_size = ffmpeg.av_image_get_buffer_size(productionAVCodecContext->pix_fmt, productionAVCodecContext->width, productionAVCodecContext->height, 1);
            byte_ptrArray4 dstData = new byte_ptrArray4();
            int_array4 dstLinesize = new int_array4();
            dstBuffer = (byte*)ffmpeg.av_malloc((ulong)buffer_size);
            ffmpeg.av_image_fill_arrays(ref dstData, ref dstLinesize, dstBuffer, productionAVCodecContext->pix_fmt, productionAVCodecContext->width, productionAVCodecContext->height, 1);

            dstFrame->format = (int)productionAVCodecContext->pix_fmt;
            dstFrame->width = productionAVCodecContext->width;
            dstFrame->height = productionAVCodecContext->height;
            dstFrame->data.UpdateFrom(dstData);
            dstFrame->linesize.UpdateFrom(dstLinesize);

            SwsContext* swsContext = ffmpeg.sws_getContext(screenCaptureAVCodecContext->width,
                screenCaptureAVCodecContext->height,
                (AVPixelFormat)screenCaptureAVCodecContext->pix_fmt,
                productionAVCodecContext->width,
                productionAVCodecContext->height,
                productionAVCodecContext->pix_fmt,
                ffmpeg.SWS_FAST_BILINEAR,
                null,
                null,
                null);

            ffmpeg.sws_scale(swsContext, srcFrame->data, srcFrame->linesize, 0, screenCaptureAVCodecContext->height, dstFrame->data, dstFrame->linesize);
            ffmpeg.sws_freeContext(swsContext);

            return dstFrame;
        }

        public void Dispose()
        {
            cancellationTokenSource.Cancel();
            recorderTask.Wait();

            fixed (AVCodecContext** pProductionAVCodecContext = &productionAVCodecContext)
            {
                ffmpeg.avcodec_free_context(pProductionAVCodecContext);
            }


            ffmpeg.avformat_free_context(productionFormatContext);


            fixed (AVCodecContext** pScreenCaptureAVCodecContext = &screenCaptureAVCodecContext)
            {
                ffmpeg.avcodec_free_context(pScreenCaptureAVCodecContext);
            }


            ffmpeg.avformat_free_context(screenCaptureInputFormatContext);


            unsafeToManagedBridgeBuffer?.Dispose();
            cancellationTokenSource.Dispose();
        }


        private System.Drawing.Size RetrieveScreenBounds(int screenIndex)
        {
            return new System.Drawing.Size(1920, 1080);
        }
    }
}

I also conducted research on testing methods for writing output to an MP4 file, utilizing mp4box.exe to retrieve the atom boxes. You can view an 8-second sample at the following link:

https://pastebin.com/FNsqmHXm

Finally
I’m wondering if there’s a clear reason why this class isn’t generating output that can be streamed, and what the solution might be.

I experimented with different movflags settings, including testing the dash option, hoping it would clarify the streaming module for VLC, with no success.

New contributor

TheYapperTheBeest is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật