WPF 实现调用 WindowsAPI 实现屏幕录制

WPF 实现调用 WindowsAPI 实现屏幕录制WPF 实现调用 WindowsAPI 实现屏幕录制控件名:DesktopRecord作 者:WPFDevelopersOrg – 驚鏵原文链接

大家好,欢迎来到IT知识分享网。WPF

WPF 实现调用 WindowsAPI 实现屏幕录制

控件名:DesktopRecord

作 者:WPFDevelopersOrg – 驚鏵

原文链接[1]:https://github.com/yanjinhuagood/DesktopRecord

  • 框架使用.NET4

  • Visual Studio 2022

  • 接着上一篇做一个不依赖 ffmpeg 实现屏幕录制

  • 1000 毫秒调用 WindowsAPI 进行截取屏幕获取图像,并保存 jpg 文件到指定路径(保存的文件从 0.jpgn.jpg )。

1)获取屏幕图片并保存为 jpg 代码如下:

 private static BitmapSource CaptureScreen()
{
IntPtr desk = GetDesktopWindow();
IntPtr dc = GetWindowDC(desk);

IntPtr memdc = CreateCompatibleDC(dc);
IntPtr bitmap = CreateCompatibleBitmap(dc, screenWidth, screenHeight);
SelectObject(memdc, bitmap);
BitBlt(memdc, 0, 0, screenWidth, screenHeight, dc, 0, 0, 0xCC0020);
BitmapSource source = Imaging.CreateBitmapSourceFromHBitmap(bitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
ReleaseDC(desk, dc);
return source;
}

Task.Factory.StartNew(() =>
{
while (IsRunning)
{
Thread.Sleep(1000);
num += 1;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
var drawingVisual = new DrawingVisual();
POINT mousePosition;
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(CaptureScreen(),
new Rect(new Point(),
new Size(screenWidth, screenHeight)));

if (GetCursorPos(out mousePosition))
{
var cursorSize = 30;
var cursorHalfSize = cursorSize / 2;
var cursorCenterX = mousePosition.X - SystemParameters.VirtualScreenLeft;
var cursorCenterY = mousePosition.Y - SystemParameters.VirtualScreenTop;
drawingContext.DrawImage(GetCursorIcon(),
new Rect(new Point(cursorCenterX, cursorCenterY),
new Size(cursorSize, cursorSize)));

}
}
var png = Path.Combine(tempDir, $"{num}.jpg");
using (FileStream stream = new FileStream(png, FileMode.Create))
{
var bitmap = new RenderTargetBitmap((int)screenWidth, (int)screenHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
var bitmapEncoder = BitmapFrame.Create(bitmap);
bitmapEncoder.Freeze();
var encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 50;
encoder.Frames.Add(bitmapEncoder);
encoder.Save(stream);
encoder.Frames.Clear();
GC.Collect();
}
}));
}
});
  • 当点击开始录制按钮时将窗体最小化,停止录制时通过循环之前保存的文件夹地址排序循环添加每一帧图像到 GifBitmapEncoder.Frames 中,但是在使用自带的 GifBitmapEncoder 发现内存占用很高,当使用完成后没有释放 GC ,所以放弃了使用它。哪位大佬有好的方式欢迎分享

  • 使用了GifEncoder 自己写入 GIF 文件。

  • 保存 gif 文件可以使用以下库

    • FreeImage.NET
    • WpfAnimatedGif
    • ImageTools
    • Magick.NET
    • GifRenderer

2) MainWindow.xaml 代码如下:

<wd:Window
x:Class="DesktopRecord.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:DesktopRecord.ViewModel"
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
Title="屏幕录制"
Width="525"
Height="200"
Icon="/screen.ico"
ResizeMode="CanMinimize"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">

<wd:Window.DataContext>
<vm:MainVM />
</wd:Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="ffmpeg 录制">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">

<Button
Margin="0,0,5,0"
Command="{Binding MyStart}"
Content="{Binding MyTime}"
Style="{StaticResource WD.SuccessPrimaryButton}" />

<Button
Margin="5,0,0,0"
Command="{Binding MyStop}"
Content="停止录制"
Style="{StaticResource WD.DangerPrimaryButton}" />

</StackPanel>
</TabItem>
<TabItem Header="WindowsAPI 录制">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">

<Button
Margin="0,0,5,0"
Command="{Binding RecordCommand}"
Content="开始录制"
Style="{StaticResource WD.SuccessPrimaryButton}" />

<Button
Margin="5,0,0,0"
wd:Loading.Child="{x:Static wd:NormalLoading.Default}"
wd:Loading.IsShow="{Binding IsShow}"
Command="{Binding RecordStopCommand}"
Content="停止录制"
Style="{StaticResource WD.DangerPrimaryButton}" />

</StackPanel>
</TabItem>
</TabControl>
</Grid>
</wd:Window>

3)创建 MainVM.cs 代码如下:

using DesktopRecord.Helper;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using WPFDevelopers.Controls;
using WPFDevelopers.Helpers;

namespace DesktopRecord.ViewModel
{
public class MainVM : ViewModelBase
{
private DispatcherTimer tm = new DispatcherTimer();

public int currentCount = 0;

private string myTime = "开始录制";

public string MyTime
{
get { return myTime; }
set
{
myTime = value;
NotifyPropertyChange("MyTime");
}
}


private bool isStart = true;

public bool IsStart
{
get { return isStart; }
set
{
isStart = value;
NotifyPropertyChange("IsStart");
}
}


private bool _isShow;

public bool IsShow
{
get { return _isShow; }
set
{
_isShow = value;
NotifyPropertyChange("IsShow");
}
}

private ICommand myStart;

public ICommand MyStart
{
get
{
return myStart ?? (myStart = new RelayCommand(p =>
{
App.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
if (!FFmpegHelper.Start())
{
App.Current.MainWindow.WindowState = System.Windows.WindowState.Normal;
MessageBox.Show("未找到 【ffmpeg.exe】,请下载", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
return;
}
tm.Tick += tm_Tick;
tm.Interval = TimeSpan.FromSeconds(1);
tm.Start();
IsStart = false;
}, a =>
{
return IsStart;
}));
}
}
private void tm_Tick(object sender, EventArgs e)
{
currentCount++;
MyTime = "录制中(" + currentCount + "s)";
}
/// <summary>
/// 获取或设置
/// </summary>
private ICommand myStop;
/// <summary>
/// 获取或设置
/// </summary>
public ICommand MyStop
{
get
{
return myStop ?? (myStop = new RelayCommand(p =>
{
var task = new Task(() =>
{
FFmpegHelper.Stop();
MyTime = "开始录制";
tm.Stop();
currentCount = 0;
IsShow = true;
});
task.ContinueWith(previousTask =>
{
IsShow = false;
IsStart = true;
Process.Start(AppDomain.CurrentDomain.BaseDirectory);
}, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}, a =>
{
return !IsStart;
}));
}
}
public ICommand RecordCommand { get; }
public ICommand RecordStopCommand { get; }
public MainVM()
{
RecordCommand = new RelayCommand(Record, CanExecuteRecordCommand);
RecordStopCommand = new RelayCommand(RecordStop);
}
void Record(object parameter)
{
App.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
Win32.Start();
IsStart = false;
}

private bool CanExecuteRecordCommand(object parameter)
{
return IsStart;
}
void RecordStop(object parameter)
{
var task = new Task(() =>
{
Win32.Stop();
IsShow = true;
Win32.Save($"DesktopRecord_{DateTime.Now.ToString("yyyyMMddHHmmss")}.gif");
});
task.ContinueWith(previousTask =>
{
IsShow = false;
IsStart = true;
Process.Start(AppDomain.CurrentDomain.BaseDirectory);
}, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}

}
}

4)创建 Win32.cs 代码如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace DesktopRecord.Helper
{
public class Win32
{
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();

[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hwnd);

[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, System.Int32 dwRop);

[DllImport("user32.dll")]
private static extern bool GetCursorInfo(out CURSORINFO pci);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINT ptScreenPos;
}

[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr handle);

private static string basePath = AppDomain.CurrentDomain.BaseDirectory;


private static string tempDir = Path.Combine(Path.GetTempPath(), "DesktopRecord");
private static Thread _thread = ;

public static bool IsRunning = false;
static int screenWidth = Convert.ToInt32(SystemParameters.PrimaryScreenWidth);
static int screenHeight = Convert.ToInt32(SystemParameters.PrimaryScreenHeight);

private static BitmapSource GetCursorIcon()
{
var cursorInfo = new CURSORINFO { cbSize = Marshal.SizeOf(typeof(CURSORINFO)) };
if (GetCursorInfo(out cursorInfo) && cursorInfo.hCursor != IntPtr.Zero)
{
try
{
return Imaging.CreateBitmapSourceFromHIcon(cursorInfo.hCursor, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DestroyIcon(cursorInfo.hCursor);
}
}

return ;
}
private static BitmapSource CaptureScreen()
{
IntPtr desk = GetDesktopWindow();
IntPtr dc = GetWindowDC(desk);

IntPtr memdc = CreateCompatibleDC(dc);
IntPtr bitmap = CreateCompatibleBitmap(dc, screenWidth, screenHeight);
SelectObject(memdc, bitmap);
BitBlt(memdc, 0, 0, screenWidth, screenHeight, dc, 0, 0, 0xCC0020);
BitmapSource source = Imaging.CreateBitmapSourceFromHBitmap(bitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
ReleaseDC(desk, dc);
return source;
}

public static void Start()
{
if (_thread == )
{
IsRunning = true;
_thread = new Thread(Record);
_thread.Start();
}
}
public static void Stop()
{
if (_thread != )
{
IsRunning = false;
_thread = ;
}
}
private static void Record()
{
if (!Directory.Exists(tempDir))
Directory.CreateDirectory(tempDir);
else
{
foreach (string file in Directory.GetFiles(tempDir))
File.Delete(file);
}
int num = 0;
Task.Factory.StartNew(() =>
{
while (IsRunning)
{
Thread.Sleep(20);
num += 1;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
var drawingVisual = new DrawingVisual();
POINT mousePosition;
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(CaptureScreen(),
new Rect(new Point(),
new Size(screenWidth, screenHeight)));

if (GetCursorPos(out mousePosition))
{
var cursorSize = 30;
var cursorHalfSize = cursorSize / 2;
var cursorCenterX = mousePosition.X - SystemParameters.VirtualScreenLeft;
var cursorCenterY = mousePosition.Y - SystemParameters.VirtualScreenTop;
drawingContext.DrawImage(GetCursorIcon(),
new Rect(new Point(cursorCenterX, cursorCenterY),
new Size(cursorSize, cursorSize)));

}
}

var png = Path.Combine(tempDir, $"{num}.jpg");
using (FileStream stream = new FileStream(png, FileMode.Create))
{
var bitmap = new RenderTargetBitmap((int)screenWidth, (int)screenHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
var bitmapEncoder = BitmapFrame.Create(bitmap);
bitmapEncoder.Freeze();
var encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 50;
encoder.Frames.Add(bitmapEncoder);
encoder.Save(stream);
encoder.Frames.Clear();
GC.Collect();
}
}));
}
});


}

public static void ClearRecording()
{
if (Directory.Exists(tempDir))
Directory.Delete(tempDir, true);
Directory.CreateDirectory(tempDir);
}

public static void Save(string output)
{
try
{
output = Path.Combine(basePath, output);
var imagePaths = Directory.GetFiles(tempDir, "*.jpg", SearchOption.TopDirectoryOnly);
if (imagePaths.Length == 0) return;

#region GC不释放,暂时弃用

//using (var gifFileStream = new FileStream(Output, FileMode.Create))
//{
// var gifBitmapEncoder = new GifBitmapEncoder();
// var jpgs = Directory.GetFiles(tempDir, "*.jpg", SearchOption.TopDirectoryOnly);
// if (jpgs.Length == 0) return;
// foreach (string file in jpgs)
// {
// using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
// {
// var bitmapDecoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
// var bitmapFrame = bitmapDecoder.Frames[0];
// bitmapDecoder.Frames[0].Freeze();
// gifBitmapEncoder.Frames.Add(bitmapFrame);
// bitmapFrame = ;
// bitmapDecoder = ;
// GC.Collect();
// stream.Dispose();
// }
// }
// gifBitmapEncoder.Save(gifFileStream);
// gifBitmapEncoder.Frames.Clear();
// gifBitmapEncoder = ;
// GC.Collect();
// GC.WaitForPendingFinalizers();
//}
#endregion


var bitmapFrames = new List<BitmapFrame>();
foreach (string imagePath in imagePaths)
{
var frame = BitmapFrame.Create(new Uri(imagePath, UriKind.RelativeOrAbsolute));
bitmapFrames.Add(frame);
}
using (var gifStream = new MemoryStream())
{
using (var encoder = new GifEncoder(gifStream))
{
foreach (var imagePath in imagePaths)
{
var image = System.Drawing.Image.FromFile(imagePath);
encoder.AddFrame(image, 0, 0, TimeSpan.FromSeconds(0));
}
}
gifStream.Position = 0;
using (var fileStream = new FileStream(output, FileMode.Create))
{
fileStream.Write(gifStream.ToArray(), 0, gifStream.ToArray().Length);
}
}


}
catch
{
throw;
}

}

}
}

5)创建 GifEncoder.cs 代码如下:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace DesktopRecord.Helper
{
public class GifEncoder : IDisposable
{
#region Header Constants
private const string FileType = "GIF";
private const string FileVersion = "89a";
private const byte FileTrailer = 0x3b;

private const int ApplicationExtensionBlockIdentifier = 0xff21;
private const byte ApplicationBlockSize = 0x0b;
private const string ApplicationIdentification = "NETSCAPE2.0";

private const int GraphicControlExtensionBlockIdentifier = 0xf921;
private const byte GraphicControlExtensionBlockSize = 0x04;

private const long SourceGlobalColorInfoPosition = 10;
private const long SourceGraphicControlExtensionPosition = 781;
private const long SourceGraphicControlExtensionLength = 8;
private const long SourceImageBlockPosition = 789;
private const long SourceImageBlockHeaderLength = 11;
private const long SourceColorBlockPosition = 13;
private const long SourceColorBlockLength = 768;
#endregion

private bool _isFirstImage = true;
private int? _width;
private int? _height;
private int? _repeatCount;
private readonly Stream _stream;

public TimeSpan FrameDelay { get; set; }

/// <summary>
/// Encodes multiple images as an animated gif to a stream. <br />
/// ALWAYS ALWAYS ALWAYS wire this in a using block <br />
/// Disposing the encoder will complete the file. <br />
/// Uses default .net GIF encoding and adds animation headers.
/// </summary>
/// <param name="stream">The stream that will be written to.</param>
/// <param name="width">Sets the width for this gif or to use the first frame's width.</param>
/// <param name="height">Sets the height for this gif or to use the first frame's height.</param>
public GifEncoder(Stream stream, int? width = , int? height = , int? repeatCount = )
{
_stream = stream;
_width = width;
_height = height;
_repeatCount = repeatCount;
}

/// <summary>
/// Adds a frame to this animation.
/// </summary>
/// <param name="img">The image to add</param>
/// <param name="x">The positioning x offset this image should be displayed at.</param>
/// <param name="y">The positioning y offset this image should be displayed at.</param>
public void AddFrame(Image img, int x = 0, int y = 0, TimeSpan? frameDelay = )
{
using (var gifStream = new MemoryStream())
{
img.Save(gifStream, ImageFormat.Gif);
if (_isFirstImage) // Steal the global color table info
{
InitHeader(gifStream, img.Width, img.Height);
}
WriteGraphicControlBlock(gifStream, frameDelay.GetValueOrDefault(FrameDelay));
WriteImageBlock(gifStream, !_isFirstImage, x, y, img.Width, img.Height);
}
_isFirstImage = false;
}

private void InitHeader(Stream sourceGif, int w, int h)
{
// File Header
WriteString(FileType);
WriteString(FileVersion);
WriteShort(_width.GetValueOrDefault(w)); // Initial Logical Width
WriteShort(_height.GetValueOrDefault(h)); // Initial Logical Height
sourceGif.Position = SourceGlobalColorInfoPosition;
WriteByte(sourceGif.ReadByte()); // Global Color Table Info
WriteByte(0); // Background Color Index
WriteByte(0); // Pixel aspect ratio
WriteColorTable(sourceGif);

// App Extension Header
WriteShort(ApplicationExtensionBlockIdentifier);
WriteByte(ApplicationBlockSize);
WriteString(ApplicationIdentification);
WriteByte(3); // Application block length
WriteByte(1);
WriteShort(_repeatCount.GetValueOrDefault(0)); // Repeat count for images.
WriteByte(0); // terminator
}

private void WriteColorTable(Stream sourceGif)
{
sourceGif.Position = SourceColorBlockPosition; // Locating the image color table
var colorTable = new byte[SourceColorBlockLength];
sourceGif.Read(colorTable, 0, colorTable.Length);
_stream.Write(colorTable, 0, colorTable.Length);
}

private void WriteGraphicControlBlock(Stream sourceGif, TimeSpan frameDelay)
{
sourceGif.Position = SourceGraphicControlExtensionPosition; // Locating the source GCE
var blockhead = new byte[SourceGraphicControlExtensionLength];
sourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE

WriteShort(GraphicControlExtensionBlockIdentifier); // Identifier
WriteByte(GraphicControlExtensionBlockSize); // Block Size
WriteByte(blockhead[3] & 0xf7 | 0x08); // Setting disposal flag
WriteShort(Convert.ToInt32(frameDelay.TotalMilliseconds / 10)); // Setting frame delay
WriteByte(blockhead[6]); // Transparent color index
WriteByte(0); // Terminator
}

private void WriteImageBlock(Stream sourceGif, bool includeColorTable, int x, int y, int h, int w)
{
sourceGif.Position = SourceImageBlockPosition; // Locating the image block
var header = new byte[SourceImageBlockHeaderLength];
sourceGif.Read(header, 0, header.Length);
WriteByte(header[0]); // Separator
WriteShort(x); // Position X
WriteShort(y); // Position Y
WriteShort(h); // Height
WriteShort(w); // Width

if (includeColorTable) // If first frame, use global color table - else use local
{
sourceGif.Position = SourceGlobalColorInfoPosition;
WriteByte(sourceGif.ReadByte() & 0x3f | 0x80); // Enabling local color table
WriteColorTable(sourceGif);
}
else
{
WriteByte(header[9] & 0x07 | 0x07); // Disabling local color table
}

WriteByte(header[10]); // LZW Min Code Size

// Read/Write image data
sourceGif.Position = SourceImageBlockPosition + SourceImageBlockHeaderLength;

var dataLength = sourceGif.ReadByte();
while (dataLength > 0)
{
var imgData = new byte[dataLength];
sourceGif.Read(imgData, 0, dataLength);

_stream.WriteByte(Convert.ToByte(dataLength));
_stream.Write(imgData, 0, dataLength);
dataLength = sourceGif.ReadByte();
}

_stream.WriteByte(0); // Terminator

}

private void WriteByte(int value)
{
_stream.WriteByte(Convert.ToByte(value));
}

private void WriteShort(int value)
{
_stream.WriteByte(Convert.ToByte(value & 0xff));
_stream.WriteByte(Convert.ToByte((value >> 8) & 0xff));
}

private void WriteString(string value)
{
_stream.Write(value.ToArray().Select(c => (byte)c).ToArray(), 0, value.Length);
}

public void Dispose()
{
// Complete File
WriteByte(FileTrailer);

// Pushing data
_stream.Flush();
}
}
}


参考资料

[1]

原文链接: https://github.com/yanjinhuagood/DesktopRecord


免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/50235.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信