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

{

///

/// 初始化 时间间隔模式的定时任务类;须给定一些必要参数

///

///

/// 定时任务的方法行为,不可为 null;此方法里须加入 try {} catch {} 异常处理,以保证其稳定运行

///

/// 定时任务的间隔时间,单位:毫秒

///

/// 是否先运行行为;默认为 true,先运行行为,再等待时间间隔;若设为 false,则先等待时间间隔,再运行行为

///

public TimedIntervalTask(Action action, int intervalTime, bool whetherToRunFirst = true)

{

TimedAction = action;

IntervalTime = intervalTime;

WhetherToRunFirst = whetherToRunFirst;

Restore();

}

///

/// 还原

///

private void Restore()

{

IsPause = false;

IsStop = false;

IsRunning = false;

IsStarted = false;

}

///

/// 定时的行为

///

private Action TimedAction { get; set; }

///

/// 行为运行的间隔时间,单位:毫秒

///

public int IntervalTime { get; set; }

///

/// 是否先运行 行为,默认 true

///

/// 若为 true,则先运行行为,再等待时间间隔;若设为 false,则先等待时间间隔,再运行行为

///

///

public bool WhetherToRunFirst { get; set; }

///

/// 是否已经启动,true 已启动,false 未启动;默认 false

///

public bool IsStarted { get; private set; } = false;

///

/// 是否暂停行为,默认 false

///

/// 若为 true,则暂停定时行为的运行;若再设为 false,则继续定时行为的运行

///

///

public bool IsPause { get; private set; } = false;

///

/// 是否终止定时任务,默认 false

///

public bool IsStop { get; private set; } = false;

///

/// 定时任务是否在运行中;true 是,false 否;默认 false

///

public bool IsRunning { get; private set; } = false;

///

/// 启动任务运行

/// 注意:再次调用此方法需要先执行 Stop() 终止定时任务

///

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();

});

}

///

/// 终止定时任务

/// 注意:调用该方法后,定时任务会在下一个运行周期终止定时任务,这可能会产生一个等待期;

/// 可通过 IsStarted 属性判断,当值为 false 时(未启动),则表示已终止定时任务,可通过 Startup() 重新启动运行

///

public void Stop()

{

IsStop = true;

}

///

/// 暂停定时任务

///

public void Pause()

{

IsPause = true;

}

///

/// 继续定时任务

///

public void GoOn()

{

IsPause = false;

}

}

View Code

录制任务

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

private ConcurrentQueue bitmaps = new ConcurrentQueue(); // 用来存放 桌面屏幕图片的线程安全队列

private TimedIntervalTask timedIntervalTaskGetScreenImg;

private TimedIntervalTask timedIntervalTaskVideoWriter;

///

/// 录制任务加载

///

private void RecordLoad()

{

bitmaps = new ConcurrentQueue();

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();

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

被跳蚤咬了用什么药
兔子表情包