c# 基于FFmpeg dll的简单录屏

基于FFmpeg dll的简单录屏

使用FFMediaToolkit能较为容易的实现录屏或者录制组件等功能。

此功能在windows桌面端运行情况良好,但是移动端会出现问题,因此被弃用,大致记录一下录屏思想。

类库准备

FFmpeg的dll准备,FFmpeg.AutoGen可以生成相关的dll,不过后来不能生成了,就需要自己去下载相应的dll

下载地址:https://github.com/BtbN/FFmpeg-Builds

然后在NGET里搜索并且安装FFMediaToolkit

代码编写

DLL加载

加载的源码 FFMediaToolkit的GitHub主页有,我这稍微改了改

我只用了x64的dll所以把dll文件在dll/FFMediaToolkit。加载方式其实就是FFmpeg.Autgen的加载方式。

   internal static bool LoadFFmpegDll()
        {
            var current = Environment.CurrentDirectory;
            Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32");
            if (!Environment.Is64BitProcess)
            {
                Console.WriteLine("x86环境不支持录制");
                return false;
            }
            var probe = Path.Combine("dll", "FFMediaToolkit");//相对路径
            while (current != null)
            {
                var ffmpegBinaryPath = Path.Combine(current, probe);
                if (Directory.Exists(ffmpegBinaryPath))
                {
                    Console.WriteLine($"FFmpeg binaries found in: {ffmpegBinaryPath}");
                    FFmpegLoader.FFmpegPath = ffmpegBinaryPath;
                    return true;
                }
                current = Directory.GetParent(current)?.FullName;
            }
            return false;
        }
View Code

录屏核心代码

复制屏幕像素转为bitmap

         private readonly Rectangle _bounds = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
        private Bitmap GetScreenImgByteArray()
        {
            Bitmap bitmap = new Bitmap(_bounds.Width, _bounds.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                graphics.CopyFromScreen(System.Drawing.Point.Empty, System.Drawing.Point.Empty, _bounds.Size, CopyPixelOperation.SourceCopy);
                return bitmap;
            }
        }
View Code

创建录制路径

  MediaOutput videofile;
 int recordfps = 25;
  private bool CreatRecordPath(string videotemperpath)
{
  VideoEncoderSettings settings = new VideoEncoderSettings(_bounds.Width, _bounds.Height, framerate: recordfps, codec: VideoCodec.H264)
                    {
                        EncoderPreset = EncoderPreset.Fast,
                        CRF = 17
                    };
                    videofile = MediaBuilder.CreateContainer(videotemperpath).WithVideo(settings).Create();
                }

}
View Code

初始化录制时间类

因为正常写定时器录制总是出现,帧率过快的问题。经过一番查找,使用了别人封装的时间间隔类 TimedIntervalTask,来保证录制帧率正常。

 public sealed class TimedIntervalTask
        {
            /// <summary>
            /// 初始化 时间间隔模式的定时任务类;须给定一些必要参数
            /// </summary>
            /// <param name="action">
            /// 定时任务的方法行为,不可为 null;此方法里须加入 try {} catch {} 异常处理,以保证其稳定运行
            /// </param>
            /// <param name="intervalTime">定时任务的间隔时间,单位:毫秒</param>
            /// <param name="whetherToRunFirst">
            /// 是否先运行行为;默认为 true,先运行行为,再等待时间间隔;若设为 false,则先等待时间间隔,再运行行为
            /// </param>
            public TimedIntervalTask(Action action, int intervalTime, bool whetherToRunFirst = true)
            {
                TimedAction = action;
                IntervalTime = intervalTime;
                WhetherToRunFirst = whetherToRunFirst;
                Restore();
            }

            /// <summary>
            /// 还原
            /// </summary>
            private void Restore()
            {
                IsPause = false;
                IsStop = false;
                IsRunning = false;
                IsStarted = false;
            }

            /// <summary>
            /// 定时的行为
            /// </summary>
            private Action TimedAction { get; set; }

            /// <summary>
            /// 行为运行的间隔时间,单位:毫秒
            /// </summary>
            public int IntervalTime { get; set; }

            /// <summary>
            /// 是否先运行 行为,默认 true
            /// <para>
            /// 若为 true,则先运行行为,再等待时间间隔;若设为 false,则先等待时间间隔,再运行行为
            /// </para>
            /// </summary>
            public bool WhetherToRunFirst { get; set; }

            /// <summary>
            /// 是否已经启动,true 已启动,false 未启动;默认 false
            /// </summary>
            public bool IsStarted { get; private set; } = false;

            /// <summary>
            /// 是否暂停行为,默认 false
            /// <para>
            /// 若为 true,则暂停定时行为的运行;若再设为 false,则继续定时行为的运行
            /// </para>
            /// </summary>
            public bool IsPause { get; private set; } = false;

            /// <summary>
            /// 是否终止定时任务,默认 false
            /// </summary>
            public bool IsStop { get; private set; } = false;

            /// <summary>
            /// 定时任务是否在运行中;true 是,false 否;默认 false
            /// </summary>
            public bool IsRunning { get; private set; } = false;

            /// <summary>
            /// 启动任务运行
            /// <para>注意:再次调用此方法需要先执行 Stop() 终止定时任务</para>
            /// </summary>
            public void Startup()
            {
                if (TimedAction == null || IsStarted)
                {
                    return;
                }

                Task.Run(async () =>
                {
                    IsStarted = true;

                    while (!IsStop)
                    {
                        IntervalTime = IntervalTime < 1 ? 1 : IntervalTime;
                        if (IsPause)
                        {
                            IsRunning = false;
                            await Task.Delay(IntervalTime);
                            continue;
                        }
                        else
                        {
                            IsRunning = true;
                        }

                        if (WhetherToRunFirst)
                        {
                            _ = Task.Run(() =>
                            {
                                try
                                {
                                    TimedAction();
                                }
                                catch (Exception)
                                {
                                    // 不做处理
                                }
                            });
                            await Task.Delay(IntervalTime);
                        }
                        else
                        {
                            await Task.Delay(IntervalTime);
                            _ = Task.Run(() =>
                            {
                                try
                                {
                                    TimedAction();
                                }
                                catch (Exception)
                                {
                                    // 不做处理
                                }
                            });
                        }
                    }

                    Restore();
                });
            }

            /// <summary>
            /// 终止定时任务
            /// <para>注意:调用该方法后,定时任务会在下一个运行周期终止定时任务,这可能会产生一个等待期;</para>
            /// <para>可通过 IsStarted 属性判断,当值为 false 时(未启动),则表示已终止定时任务,可通过 Startup() 重新启动运行</para>
            /// </summary>
            public void Stop()
            {
                IsStop = true;
            }

            /// <summary>
            /// 暂停定时任务
            /// </summary>
            public void Pause()
            {
                IsPause = true;
            }

            /// <summary>
            /// 继续定时任务
            /// </summary>
            public void GoOn()
            {
                IsPause = false;
            }
        }
View Code

录制任务

这里开了两个线程,使用图片队列,一个获取图片,一个写入视频

   private ConcurrentQueue<Bitmap> bitmaps = new ConcurrentQueue<Bitmap>();  // 用来存放 桌面屏幕图片的线程安全队列
        private TimedIntervalTask timedIntervalTaskGetScreenImg;
        private TimedIntervalTask timedIntervalTaskVideoWriter;
        /// <summary>
        /// 录制任务加载
        /// </summary>
        private void RecordLoad()
        {
            bitmaps = new ConcurrentQueue<Bitmap>();
            timedIntervalTaskGetScreenImg = new TimedIntervalTask(() =>
            {
                if (timedIntervalTaskGetScreenImg.IsRunning)
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        bitmaps.Enqueue(GetScreenImgByteArray());
                        framescount++;
                    }));
                }
            }, GetFpsTime());
            timedIntervalTaskVideoWriter = new TimedIntervalTask(() =>
            {
                var ts = TimeSpan.FromSeconds(framescount / recordgfps);
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    timetip.Text = $"{ts.Hours}:{ts.Minutes}:{ts.Seconds}";

                    while (bitmaps.Count > 0)
                    {
                        Bitmap bitmap = null;
                        if (bitmaps.TryDequeue(out bitmap))
                        {
                            BitmapData bitLock = bitmap.LockBits(new Rectangle(System.Drawing.Point.Empty, gridsize), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                            ImageData bitmapData = ImageData.FromPointer(bitLock.Scan0, ImagePixelFormat.Bgr24, gridsize);
                            videofile.Video.AddFrame(bitmapData);
                            bitmap.UnlockBits(bitLock);
                            bitmap.Dispose();
                        }
                        GC.Collect();
                    }
                }));
            }, 200);
        }
View Code

录制开始

在录制开始前需要先创建录制路径,录制路径自己设置

int framescount = 0;
private void StartRecord()
{
   framescount = 0;
                    bitmaps = new ConcurrentQueue<Bitmap>();
                    RecordLoad();
                    timedIntervalTaskGetScreenImg.IntervalTime = GetFpsTime();
                    timedIntervalTaskGetScreenImg.Startup();
                    timedIntervalTaskVideoWriter.Startup();
}
View Code

录制结束

因为使用图片队列,所以录制完成需等图片队列清空后再释放资源

 private void StopRecord()
        {
            if (!timedIntervalTaskGetScreenImg.IsStarted)
            {
                return;
            }
            timedIntervalTaskGetScreenImg.Stop();
            timedIntervalTaskVideoWriter.IntervalTime = 100;
            Task.Run(async () =>
            {
                while (bitmaps.Count >= 0)
                {
                    await Task.Delay(30);
                }
            });
            timedIntervalTaskVideoWriter.Stop();
            if (videofile != null)
            {
                videofile.Video.Dispose();
                videofile.Dispose();
            }
        }
View Code

 

posted @ 2023-03-14 14:01  莫如风  阅读(714)  评论(1)    收藏  举报