2025-11-03
C#
00

目录

项目概述
开发环境准备
用户界面设计
核心功能实现
总结

在数字化时代,屏幕录制工具已成为教育培训、软件演示和内容创作的必备工具。本文将详细讲解如何使用C#开发一款功能完善的屏幕录制工具,从界面设计到核心功能实现,一步步带您构建自己的屏幕捕获应用。

项目概述

我们将开发一个Windows窗体应用程序,具备以下核心功能:

  • 支持全屏、当前窗口录制
  • 可调节帧率控制视频质量和文件大小
  • 支持鼠标光标捕获
  • 使用MJPEG编码器实现高效视频压缩
  • 录制完成后可直接预览视频文件

开发环境准备

开发此应用需要以下环境和工具:

  • Visual Studio(2019或更高版本)
  • .NET Framework 4.5+
  • SharpAvi库(用于AVI视频文件创建)

首先,创建一个Windows窗体应用程序,并通过NuGet包管理器安装SharpAvi库:

PowerShell
Install-Package SharpAvi

用户界面设计

一个直观的用户界面对于屏幕录制工具至关重要。我们的界面包含以下关键元素:

  1. 开始/停止录制按钮
  2. 录制区域选择下拉框(全屏/当前窗口/自定义区域)
  3. 帧率设置数值控件
  4. 鼠标捕获选项复选框
  5. 状态显示标签

界面初始化代码如下:

C#
private void Form1_Load(object sender, EventArgs e) { // 初始化录制区域下拉菜单 cmbRecordingArea.Items.Clear(); cmbRecordingArea.Items.Add("全屏"); cmbRecordingArea.Items.Add("当前窗口"); cmbRecordingArea.SelectedIndex = 0; // 初始化状态标签 lblStatus.Text = "准备就绪"; // 初始化帧率 numFrameRate.Value = 30; // 默认设置 chkRecordMouse.Checked = true; }

核心功能实现

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using SharpAvi; using SharpAvi.Output; using SharpAvi.Codecs; namespace AppAvi { public partial class Form1 : Form { private bool _isRecording = false; // 录制状态标志 private string _outputPath; // 视频输出路径 private Task _recordingTask; // 录制任务 private CancellationTokenSource _cancellationTokenSource; // 取消令牌源 private bool _processingComplete = false; // 处理完成标志 /// <summary> /// 窗口矩形结构体 /// </summary> [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } /// <summary> /// 导入获取窗口矩形的Windows API /// </summary> [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); /// <summary> /// 构造函数 /// </summary> public Form1() { InitializeComponent(); } /// <summary> /// 开始录制按钮点击事件处理 /// </summary> private void btnRecord_Click(object sender, EventArgs e) { if (_isRecording) return; try { // 禁用开始按钮,启用停止按钮 btnRecord.Enabled = false; btnStop.Enabled = true; // 设置录制状态 _isRecording = true; lblStatus.Text = "正在录制中..."; // 创建输出文件路径 _outputPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"ScreenCapture_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.avi"); // 获取录制参数 - 在UI线程上捕获这些值 int frameRate = (int)numFrameRate.Value; bool recordMouse = chkRecordMouse.Checked; int selectedAreaIndex = cmbRecordingArea.SelectedIndex; // 开始录制 _cancellationTokenSource = new CancellationTokenSource(); _recordingTask = StartRecordingAsync(frameRate, recordMouse, selectedAreaIndex, _cancellationTokenSource.Token); } catch (Exception ex) { MessageBox.Show($"录制失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); ResetUI(); } } /// <summary> /// 停止录制按钮点击事件处理 /// </summary> private async void btnStop_Click(object sender, EventArgs e) { if (!_isRecording) return; try { lblStatus.Text = "正在保存录制文件..."; // 停止录制 _cancellationTokenSource.Cancel(); await _recordingTask; _processingComplete = true; // 重置UI状态 ResetUI(); // 询问是否打开录制文件 if (File.Exists(_outputPath)) { var result = MessageBox.Show($"录制已完成并保存至:\n{_outputPath}\n\n是否立即查看视频?", "录制完成", MessageBoxButtons.YesNo, MessageBoxIcon.Information); if (result == DialogResult.Yes) { System.Diagnostics.Process.Start(_outputPath); } } } catch (Exception ex) { MessageBox.Show($"停止录制过程失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); ResetUI(); } } /// <summary> /// 重置UI状态 /// </summary> private void ResetUI() { btnRecord.Enabled = true; btnStop.Enabled = false; _isRecording = false; if (_processingComplete) lblStatus.Text = "处理完成,准备就绪"; else lblStatus.Text = "准备就绪"; } /// <summary> /// 异步录制方法 /// </summary> private async Task StartRecordingAsync(int frameRate, bool recordMouse, int selectedAreaIndex, CancellationToken cancellationToken) { await Task.Run(() => { Rectangle bounds; // 根据选择的区域确定录制区域 switch (selectedAreaIndex) { case 0: // 全屏 bounds = Screen.PrimaryScreen.Bounds; break; case 1: // 当前窗口 IntPtr handle = Process.GetCurrentProcess().MainWindowHandle; GetWindowRect(handle, out RECT rect); bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top); break; default: bounds = Screen.PrimaryScreen.Bounds; break; } // 创建一个MJPEG编码器,设置质量为75% var encoder = new MJPEGEncoder(bounds.Width, bounds.Height, 75); try { using (var writer = new AviWriter(_outputPath) { FramesPerSecond = frameRate, EmitIndex1 = true }) { // 创建视频流 var videoStream = writer.AddVideoStream(); videoStream.Width = bounds.Width; videoStream.Height = bounds.Height; videoStream.Codec = new FourCC("MJPG"); videoStream.BitsPerPixel = BitsPerPixel.Bpp24; using (var screenCapture = new ScreenCapture(bounds, recordMouse)) { var frameInterval = TimeSpan.FromSeconds(1.0 / frameRate); var watch = new Stopwatch(); watch.Start(); var frameCount = 0; var lastFrameTime = TimeSpan.Zero; while (!cancellationToken.IsCancellationRequested) { var timestamp = watch.Elapsed; // 确保按照指定的帧率捕获 if (timestamp - lastFrameTime >= frameInterval) { using (var bitmap = screenCapture.Capture()) { try { // 使用MJPEG编码器编码图像 byte[] compressedFrame = encoder.EncodeFrame(bitmap); // 写入编码后的视频帧 videoStream.WriteFrame(true, compressedFrame, 0, compressedFrame.Length); frameCount++; lastFrameTime = timestamp; // 在UI线程上更新状态 if (frameCount % 30 == 0) // 每30帧更新一次UI { this.Invoke((MethodInvoker)delegate { lblStatus.Text = $"正在录制中... 已录制 {frameCount} 帧"; }); } } catch (Exception ex) { Debug.WriteLine($"编码/写入视频帧时出错: {ex.Message}"); } } } else { // 如果时间间隔不够,让线程休眠一小段时间以减少CPU使用 Thread.Sleep(1); } } // 录制完成后更新UI this.Invoke((MethodInvoker)delegate { lblStatus.Text = $"录制已完成,共 {frameCount} 帧"; }); } } // 确认文件是否写入成功 if (File.Exists(_outputPath)) { FileInfo fi = new FileInfo(_outputPath); Debug.WriteLine($"AVI文件大小: {fi.Length / (1024 * 1024):F2}MB"); } } catch (Exception ex) { Debug.WriteLine($"录制过程中发生严重错误: {ex.Message}"); Debug.WriteLine($"错误堆栈: {ex.StackTrace}"); } }, cancellationToken); } /// <summary> /// 窗体关闭事件处理 /// </summary> protected override void OnFormClosing(FormClosingEventArgs e) { // 如果正在录制,确保停止 if (_isRecording) { try { _cancellationTokenSource.Cancel(); _recordingTask.Wait(1000); // 等待录制任务结束,最多等待1秒 } catch { /* 忽略关闭时的异常 */ } } base.OnFormClosing(e); } /// <summary> /// 屏幕捕获类 /// </summary> private class ScreenCapture : IDisposable { private readonly Rectangle _bounds; // 捕获区域 private readonly bool _captureMouse; // 是否捕获鼠标 private readonly Graphics _graphics; // 绘图对象 private readonly Bitmap _bitmap; // 位图对象 /// <summary> /// 创建屏幕捕获实例 /// </summary> /// <param name="bounds">捕获区域</param> /// <param name="captureMouse">是否捕获鼠标</param> public ScreenCapture(Rectangle bounds, bool captureMouse) { _bounds = bounds; _captureMouse = captureMouse; _bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb); // 改为24bpp以匹配MJPEG _graphics = Graphics.FromImage(_bitmap); } /// <summary> /// 捕获屏幕内容 /// </summary> /// <returns>捕获的屏幕图像</returns> public Bitmap Capture() { // 捕获屏幕 _graphics.CopyFromScreen( _bounds.Left, _bounds.Top, 0, 0, _bounds.Size, CopyPixelOperation.SourceCopy); // 如果需要,绘制鼠标光标 if (_captureMouse) { DrawMouseCursor(_graphics); } return (Bitmap)_bitmap.Clone(); } /// <summary> /// 绘制鼠标光标 /// </summary> /// <param name="g">绘图对象</param> private void DrawMouseCursor(Graphics g) { // 获取鼠标位置 var cursorPosition = Cursor.Position; // 如果鼠标在捕获区域内 if (_bounds.Contains(cursorPosition)) { // 获取当前鼠标光标 var cursor = Cursors.Default; // 计算相对于捕获区域的位置 var x = cursorPosition.X - _bounds.Left; var y = cursorPosition.Y - _bounds.Top; // 绘制光标 cursor.Draw(g, new Rectangle(x, y, cursor.Size.Width, cursor.Size.Height)); } } /// <summary> /// 释放资源 /// </summary> public void Dispose() { _graphics.Dispose(); _bitmap.Dispose(); } } /// <summary> /// MJPEG编码器类 - 用于将Bitmap编码为MJPEG格式 /// </summary> private class MJPEGEncoder { private readonly int _width; // 图像宽度 private readonly int _height; // 图像高度 private readonly long _quality; // JPEG质量 (1-100) private readonly ImageCodecInfo _jpegCodecInfo; // JPEG编码器信息 private readonly EncoderParameters _encoderParams; // 编码器参数 /// <summary> /// 创建MJPEG编码器 /// </summary> /// <param name="width">图像宽度</param> /// <param name="height">图像高度</param> /// <param name="quality">JPEG质量 (1-100)</param> public MJPEGEncoder(int width, int height, long quality) { _width = width; _height = height; _quality = quality; // 获取JPEG编码器 _jpegCodecInfo = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid); // 设置JPEG压缩质量 _encoderParams = new EncoderParameters(1); _encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, _quality); } /// <summary> /// 编码视频帧为JPEG格式 /// </summary> /// <param name="bitmap">要编码的位图</param> /// <returns>编码后的JPEG字节数组</returns> public byte[] EncodeFrame(Bitmap bitmap) { using (var memoryStream = new MemoryStream()) { // 将Bitmap保存为JPEG到内存流 bitmap.Save(memoryStream, _jpegCodecInfo, _encoderParams); // 返回JPEG压缩后的字节数组 return memoryStream.ToArray(); } } } /// <summary> /// 窗体加载事件 /// </summary> private void Form1_Load(object sender, EventArgs e) { // 初始化录制区域下拉菜单 cmbRecordingArea.Items.Clear(); cmbRecordingArea.Items.Add("全屏"); cmbRecordingArea.Items.Add("当前窗口"); cmbRecordingArea.SelectedIndex = 0; // 初始化状态标签 lblStatus.Text = "准备就绪"; // 初始化帧率 numFrameRate.Value = 15; // 默认设置 chkRecordMouse.Checked = true; } } }

image.png

总结

这个项目展示了如何从零开始构建一个功能完整的屏幕录制工具,结合了UI设计、图形处理、多线程编程和视频编码等多项技术,是C#桌面应用开发的一个很好的实例。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!