using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using SharpAvi; using SharpAvi.Output; using SharpAvi.Codecs; namespace ScreenRecorder; public class CaptureService : IDisposable { private AviWriter? _writer; private IAviVideoStream? _videoStream; private Thread? _captureThread; private volatile bool _isRecording; private readonly int _fps; private readonly int _quality; private readonly bool _hideCursor; private readonly Rectangle _captureArea; private string? _outputPath; public CaptureService(int fps = 30, int quality = 100, bool hideCursor = false) { _fps = fps; _quality = quality; _hideCursor = hideCursor; // 录制主屏幕 var screen = System.Windows.Forms.Screen.PrimaryScreen!; _captureArea = screen.Bounds; } public void Start(string outputPath) { _outputPath = outputPath; _isRecording = true; _writer = new AviWriter(outputPath) { FramesPerSecond = _fps, EmitIndex1 = true }; if (_quality == 101) // 无损模式:无压缩 RGB { var rawStream = _writer.AddVideoStream( _captureArea.Width, _captureArea.Height, BitsPerPixel.Bpp32 ); rawStream.Codec = KnownFourCCs.Codecs.Uncompressed; _videoStream = rawStream; } else // MJPEG 有损模式 { _videoStream = _writer.AddMotionJpegVideoStream( _captureArea.Width, _captureArea.Height, quality: _quality ); } _captureThread = new Thread(CaptureLoop) { IsBackground = true }; _captureThread.Start(); } public void Stop() { _isRecording = false; _captureThread?.Join(3000); _writer?.Close(); _writer = null; } private void CaptureLoop() { var interval = TimeSpan.FromMilliseconds(1000.0 / _fps); int w = _captureArea.Width, h = _captureArea.Height; bool lossless = _quality == 101; using var bitmap = new Bitmap(w, h, PixelFormat.Format32bppRgb); using var graphics = Graphics.FromImage(bitmap); int stride = w * 4; var frameData = new byte[stride * h]; while (_isRecording) { var start = DateTime.UtcNow; graphics.CopyFromScreen(_captureArea.Location, Point.Empty, _captureArea.Size); if (!_hideCursor) DrawCursor(graphics); var bits = bitmap.LockBits( new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb ); if (lossless) { // 无压缩 AVI 需要 BGR 从下到上(行翻转) for (int row = 0; row < h; row++) { IntPtr src = bits.Scan0 + row * bits.Stride; int dstOffset = (h - 1 - row) * stride; Marshal.Copy(src, frameData, dstOffset, stride); } } else { Marshal.Copy(bits.Scan0, frameData, 0, frameData.Length); } bitmap.UnlockBits(bits); _videoStream!.WriteFrame(true, frameData, 0, frameData.Length); var elapsed = DateTime.UtcNow - start; var remaining = interval - elapsed; if (remaining > TimeSpan.Zero) Thread.Sleep(remaining); } } public void Dispose() => Stop(); // Win32 API:获取鼠标位置和光标图标 [StructLayout(LayoutKind.Sequential)] private struct CURSORINFO { public int cbSize; public int flags; public IntPtr hCursor; public POINT ptScreenPos; } [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x, y; } [DllImport("user32.dll")] static extern bool GetCursorInfo(ref CURSORINFO ci); [DllImport("user32.dll")] static extern bool DrawIconEx(IntPtr hdc, int x, int y, IntPtr hIcon, int w, int h, int step, IntPtr brush, int flags); private const int CURSOR_SHOWING = 1; private const int DI_NORMAL = 3; private void DrawCursor(Graphics g) { var ci = new CURSORINFO { cbSize = Marshal.SizeOf() }; if (!GetCursorInfo(ref ci) || ci.flags != CURSOR_SHOWING) return; int x = ci.ptScreenPos.x - _captureArea.X; int y = ci.ptScreenPos.y - _captureArea.Y; var hdc = g.GetHdc(); DrawIconEx(hdc, x, y, ci.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL); g.ReleaseHdc(hdc); } }