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; }
录屏核心代码
复制屏幕像素转为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; } }
创建录制路径
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(); } }
初始化录制时间类
因为正常写定时器录制总是出现,帧率过快的问题。经过一番查找,使用了别人封装的时间间隔类 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; } }
录制任务
这里开了两个线程,使用图片队列,一个获取图片,一个写入视频
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); }
录制开始
在录制开始前需要先创建录制路径,录制路径自己设置
int framescount = 0; private void StartRecord() { framescount = 0; bitmaps = new ConcurrentQueue<Bitmap>(); RecordLoad(); timedIntervalTaskGetScreenImg.IntervalTime = GetFpsTime(); timedIntervalTaskGetScreenImg.Startup(); timedIntervalTaskVideoWriter.Startup(); }
录制结束
因为使用图片队列,所以录制完成需等图片队列清空后再释放资源
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(); } }

浙公网安备 33010602011771号