从零到一:C#实现高性能屏幕录制器完全指南

C#高性能屏幕录制器开发

本文将手把手带你用C#构建一个完整的屏幕录制应用,涵盖视频捕获、音频同步、多线程优化等核心技术。通过2000+行实战代码,你将掌握系统级编程的精髓,提升你的C#技术水平。

数字化办公时代,屏幕录制已成为开发者、产品经理、教育工作者的刚需工具。市面上的录屏软件要么功能单一,要么性能不佳,要么收费昂贵。作为C#开发者,为什么不自己打造一个功能强大、性能卓越的录屏神器呢?

本文将手把手带你用C#构建一个完整的屏幕录制应用,涵盖视频捕获、音频同步、多线程优化等核心技术。通过2000+行实战代码,你将掌握系统级编程的精髓,提升你的C#技术水平。

痛点分析:为什么要自己造轮子?

现有方案的局限性

性能瓶颈:大多数录屏工具在高帧率录制时会出现卡顿、掉帧问题,影响用户体验。

功能限制:无法满足特定需求,如自定义录制区域、多音频源混合、实时压缩等。

成本考虑:商业软件授权费用高昂,开源方案往往缺乏维护。

技术提升:对于C#开发者而言,这是一个绝佳的系统编程实战项目。

核心技术架构解析

多线程并发设计

// 高精度帧捕获线程
private void CaptureScreenFrames(CancellationToken cancellationToken)
{
    Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

    while (_isRecording && !cancellationToken.IsCancellationRequested)
    {
        var currentTime = _stopwatch.ElapsedTicks;

        if (currentTime >= _nextFrameTime)
        {
            var bitmap = CaptureScreenOptimized(_captureArea);

            _frameQueue.Enqueue(new FrameData
            {
                Bitmap = bitmap,
                Index = _frameIndex++,
                Timestamp = DateTime.Now
            });

            _nextFrameTime += _frameInterval;
        }
        else
        {
            // 精确等待,避免CPU空转
            var waitMs = Math.Max(1, (_nextFrameTime - currentTime) / TimeSpan.TicksPerMillisecond);
            Thread.Sleep((int)waitMs);
        }
    }
}

核心要点

  • 使用ThreadPriority.AboveNormal确保帧捕获优先级
  • 高精度计时器避免帧率不稳定
  • 并发队列(ConcurrentQueue)实现生产者-消费者模式

🚀 性能优化:屏幕捕获算法

private Bitmap CaptureScreenOptimized(Rectangle area)
{
    var bitmap = new Bitmap(area.Width, area.Height, PixelFormat.Format24bppRgb);

    using (var graphics = Graphics.FromImage(bitmap))
    {
        // 关键优化设置
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.CompositingQuality = CompositingQuality.HighSpeed;
        graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
        graphics.SmoothingMode = SmoothingMode.None;
        graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;

        graphics.CopyFromScreen(area.X, area.Y, 0, 0, area.Size, 
            CopyPixelOperation.SourceCopy);
    }

    return bitmap;
}

性能提升技巧

  1. 选择Format24bppRgb减少内存占用
  2. 关闭图形渲染质量提升速度
  3. 使用SourceCopy模式避免像素混合计算

音频同步录制:多源混音

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualBasic;
using NAudio.Wave;

namespace AppScreenRecorder
{
    publicclass AudioRecorder : IDisposable
    {
        private WasapiLoopbackCapture _loopbackCapture;
        private WaveInEvent _micCapture;
        privatestring _systemAudioPath;
        privatestring _micAudioPath;
        private WaveFileWriter _audioWriter;
        private WaveFileWriter _micWriter;
        privatebool _isRecording = false;
        privatebool _isPaused = false;

        publicstring SystemAudioPath => _systemAudioPath;
        publicstring MicAudioPath => _micAudioPath;

        public void StartRecording(string basePath, bool recordMic = true, bool recordSystem = true)
        {
            _isRecording = true;
            _isPaused = false;

            if (recordSystem)
            {
                _systemAudioPath = Path.ChangeExtension(basePath, "_system.wav");

                _loopbackCapture = new WasapiLoopbackCapture();
                _loopbackCapture.DataAvailable += OnSystemAudioDataAvailable;
                _loopbackCapture.RecordingStopped += OnRecordingStopped;

                _audioWriter = new WaveFileWriter(_systemAudioPath, _loopbackCapture.WaveFormat);
                _loopbackCapture.StartRecording();
            }

            if (recordMic)
            {
                _micAudioPath = Path.ChangeExtension(basePath, "_mic.wav");

                _micCapture = new WaveInEvent();
                _micCapture.WaveFormat = new WaveFormat(44100, 16, 2);
                _micCapture.DataAvailable += OnMicDataAvailable;

                _micWriter = new WaveFileWriter(_micAudioPath, _micCapture.WaveFormat);
                _micCapture.StartRecording();
            }
        }

        public void StopRecording()
        {
            // 立即设置停止标志
            _isRecording = false;
            _isPaused = false;

            try
            {
                // 立即停止录制设备
                _loopbackCapture?.StopRecording();
                _micCapture?.StopRecording();

                // 立即刷新并关闭写入器
                _audioWriter?.Flush();
                _audioWriter?.Dispose();
                _micWriter?.Flush();
                _micWriter?.Dispose();

                // 释放捕获设备
                _loopbackCapture?.Dispose();
                _micCapture?.Dispose();

                _audioWriter = null;
                _micWriter = null;
                _loopbackCapture = null;
                _micCapture = null;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"停止音频录制时出错: {ex.Message}");
            }
        }

        public void PauseRecording()
        {
            _isPaused = true;
        }

        public void ResumeRecording()
        {
            _isPaused = false;
        }

        private void OnSystemAudioDataAvailable(object sender, WaveInEventArgs e)
        {
            if (_isRecording && !_isPaused && _audioWriter != null)
            {
                try
                {
                    _audioWriter.Write(e.Buffer, 0, e.BytesRecorded);
                }
                catch (ObjectDisposedException)
                {

                }
            }
        }

        private void OnMicDataAvailable(object sender, WaveInEventArgs e)
        {
            if (_isRecording && !_isPaused && _micWriter != null)
            {
                try
                {
                    _micWriter.Write(e.Buffer, 0, e.BytesRecorded);
                }
                catch (ObjectDisposedException)
                {

                }
            }
        }
        private void OnRecordingStopped(object sender, StoppedEventArgs e)
        {
            if (e.Exception != null)
            {
                Console.WriteLine($"音频录制出错: {e.Exception.Message}");
            }
        }

        public void Dispose()
        {
            StopRecording();
        }
    }
}

关键技术点

  • WasapiLoopbackCapture实现系统音频捕获
  • 双音频流独立录制,后期FFmpeg合并
  • 异步写入避免音频丢失

视频编码:FFMpegCore集成

复制

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FFMpegCore;
using FFMpegCore.Enums;

namespace AppScreenRecorder
{
    publicclass ScreenRecorder : IDisposable
    {
        privatebool _isRecording = false;
        privatebool _isPaused = false;
        privatestring _outputPath;
        private Rectangle _captureArea;
        privatestring _tempDirectory;
        privateint _frameRate = 30;
        privateint _quality = 720;
        privatestring _tempVideoPath;

        // 使用并发队列来缓存帧
        private readonly ConcurrentQueue<FrameData> _frameQueue = new ConcurrentQueue<FrameData>();
        private Task _captureTask;
        private Task _saveTask;
        private CancellationTokenSource _cancellationTokenSource;

        // 高精度计时器
        private System.Diagnostics.Stopwatch _stopwatch;
        privatelong _nextFrameTime;
        privatelong _frameInterval;
        privateint _frameIndex = 0;

        publicstring TempVideoPath => _tempVideoPath;

        privatestruct FrameData
        {
            public Bitmap Bitmap;
            publicint Index;
            public DateTime Timestamp;
        }

        public ScreenRecorder()
        {
            _tempDirectory = Path.Combine(Path.GetTempPath(),
                "ScreenRecorder_" + Guid.NewGuid().ToString("N")[..8]);
            Directory.CreateDirectory(_tempDirectory);
        }

        public async Task StartRecording(string outputPath, Rectangle? captureArea = null,
            int frameRate = 30, int quality = 720)
        {
            _outputPath = outputPath;
            _captureArea = captureArea ?? Screen.PrimaryScreen.Bounds;
            _frameRate = frameRate;
            _quality = quality;
            _isRecording = true;
            _isPaused = false;
            _frameIndex = 0;

            // 初始化高精度计时器
            _stopwatch = System.Diagnostics.Stopwatch.StartNew();
            _frameInterval = TimeSpan.TicksPerSecond / _frameRate;
            _nextFrameTime = _frameInterval;

            _cancellationTokenSource = new CancellationTokenSource();

            // 启动捕获和保存任务
            _captureTask = Task.Run(() => CaptureScreenFrames(_cancellationTokenSource.Token));
            _saveTask = Task.Run(() => SaveFramesToDisk(_cancellationTokenSource.Token));

            await Task.CompletedTask;
        }

        public async Task StopRecording()
        {
            // 立即停止录制标志
            _isRecording = false;
            _isPaused = false;

            Console.WriteLine("开始停止录制...");

            if (_cancellationTokenSource != null)
            {
                // 立即取消捕获任务
                _cancellationTokenSource.Cancel();

                try
                {
                    // 等待捕获任务完成
                    if (_captureTask != null)
                    {
                        var timeoutTask = Task.Delay(3000); // 3秒超时
                        var completedTask = await Task.WhenAny(_captureTask, timeoutTask);
                        if (completedTask == timeoutTask)
                        {
                            Console.WriteLine("警告: 捕获任务超时");
                        }
                        else
                        {
                            Console.WriteLine("捕获任务已完成");
                        }
                    }

                    // 等待保存任务处理完所有帧
                    if (_saveTask != null)
                    {
                        Console.WriteLine($"等待保存任务完成,队列剩余: {_frameQueue.Count} 帧");

                        var timeoutTask = Task.Delay(10000); // 10秒超时,给保存任务更多时间
                        var completedTask = await Task.WhenAny(_saveTask, timeoutTask);
                        if (completedTask == timeoutTask)
                        {
                            Console.WriteLine("警告: 保存任务超时");
                            // 即使超时也继续,清理剩余帧
                            while (_frameQueue.TryDequeue(out var frame))
                            {
                                frame.Bitmap?.Dispose();
                            }
                        }
                        else
                        {
                            Console.WriteLine("保存任务已完成");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"停止录制任务时出错: {ex.Message}");
                }
            }

            // 停止计时器
            _stopwatch?.Stop();

            // 此时所有帧应该都已保存完毕,生成视频
            if (_frameIndex > 0)
            {
                Console.WriteLine($"开始生成视频,总帧数: {_frameIndex}");
                var tempVideoPath = Path.Combine(_tempDirectory, "temp_video.mp4");
                await GenerateVideoOnly(tempVideoPath);
                _tempVideoPath = tempVideoPath;
                Console.WriteLine("视频生成完成");
            }
        }

        public Task PauseRecording()
        {
            _isPaused = true;
            return Task.CompletedTask;
        }

        public Task ResumeRecording()
        {
            _isPaused = false;
            // 重置计时器以避免时间跳跃
            if (_stopwatch != null)
            {
                _stopwatch.Restart();
                _nextFrameTime = _frameInterval;
            }
            return Task.CompletedTask;
        }

        private void CaptureScreenFrames(CancellationToken cancellationToken)
        {
            Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
            Console.WriteLine("捕获任务开始");

            while (_isRecording && !cancellationToken.IsCancellationRequested)
            {
                if (_isPaused)
                {
                    // 暂停时重置计时器
                    _stopwatch.Restart();
                    _nextFrameTime = _frameInterval;
                    Thread.Sleep(50);
                    continue;
                }

                var currentTime = _stopwatch.ElapsedTicks;

                if (currentTime >= _nextFrameTime)
                {
                    // 最后一次检查录制状态
                    if (!_isRecording)
                    {
                        break;
                    }

                    try
                    {
                        var bitmap = CaptureScreenOptimized(_captureArea);

                        // 检查队列大小,避免内存溢出
                        if (_frameQueue.Count < 500) 
                        {
                            _frameQueue.Enqueue(new FrameData
                            {
                                Bitmap = bitmap,
                                Index = _frameIndex++,
                                Timestamp = DateTime.Now
                            });
                        }
                        else
                        {
                            bitmap?.Dispose();
                            Console.WriteLine("队列满,丢弃一帧");
                        }

                        _nextFrameTime += _frameInterval;

                        if (currentTime > _nextFrameTime + _frameInterval * 2)
                        {
                            _nextFrameTime = currentTime + _frameInterval;
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"捕获帧时出错: {ex.Message}");
                        _nextFrameTime += _frameInterval;
                    }
                }
                else
                {
                    // 精确等待
                    var waitTicks = _nextFrameTime - currentTime;
                    var waitMs = Math.Max(1, Math.Min(waitTicks / TimeSpan.TicksPerMillisecond, 15));
                    Thread.Sleep((int)waitMs);
                }
            }

            Console.WriteLine($"捕获任务结束,总共捕获 {_frameIndex} 帧,队列剩余: {_frameQueue.Count}");
        }

        private void SaveFramesToDisk(CancellationToken cancellationToken)
        {
            Thread.CurrentThread.Priority = ThreadPriority.Normal;
            var processedFrames = 0;

            Console.WriteLine("保存任务开始");

            while (true)
            {
                bool hasMoreFrames = _frameQueue.TryDequeue(out var frameData);

                if (hasMoreFrames)
                {
                    try
                    {
                        var frameFile = Path.Combine(_tempDirectory, $"frame_{frameData.Index:D6}.png");

                        using (frameData.Bitmap)
                        {
                            frameData.Bitmap.Save(frameFile, ImageFormat.Png);
                        }

                        processedFrames++;

                        if (processedFrames % 100 == 0)
                        {
                            Console.WriteLine($"已保存 {processedFrames} 帧,队列剩余: {_frameQueue.Count}");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"保存帧时出错: {ex.Message}");
                        frameData.Bitmap?.Dispose();
                    }
                }
                else
                {
                    if (cancellationToken.IsCancellationRequested && !_isRecording)
                    {
                        break;
                    }
                    Thread.Sleep(10);
                }
            }

            Console.WriteLine($"保存任务结束,总共保存 {processedFrames} 帧");
        }

        private Bitmap CaptureScreenOptimized(Rectangle area)
        {
            var bitmap = new Bitmap(area.Width, area.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            using (var graphics = Graphics.FromImage(bitmap))
            {
                graphics.CompositingMode = CompositingMode.SourceCopy;
                graphics.CompositingQuality = CompositingQuality.HighSpeed;
                graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
                graphics.SmoothingMode = SmoothingMode.None;
                graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;

                graphics.CopyFromScreen(area.X, area.Y, 0, 0, area.Size, CopyPixelOperation.SourceCopy);
            }

            return bitmap;
        }

        private async Task GenerateVideoOnly(string outputPath)
        {
            try
            {
                var inputPattern = Path.Combine(_tempDirectory, "frame_%06d.png");

                await FFMpegArguments
                    .FromFileInput(inputPattern, false, options => options
                        .WithFramerate(_frameRate))
                    .OutputToFile(outputPath, true, options => options
                        .WithVideoCodec(VideoCodec.LibX264)
                        .WithCustomArgument("-preset ultrafast") 
                        .WithCustomArgument("-tune zerolatency") 
                        .WithVideoBitrate(GetBitrateForQuality(_quality))
                        .WithFramerate(_frameRate)
                        .WithVideoFilters(filterOptions => filterOptions
                            .Scale(GetVideoSize(_quality))))
                    .ProcessAsynchronously();
            }
            catch (Exception ex)
            {
                thrownew Exception($"视频生成失败: {ex.Message}", ex);
            }
        }

        public static async Task MergeVideoAndAudio(string videoPath, string audioPath, string outputPath)
        {
            try
            {
                await FFMpegArguments
                    .FromFileInput(videoPath)
                    .AddFileInput(audioPath)
                    .OutputToFile(outputPath, true, options => options
                        .WithCustomArgument("-c:v copy")
                        .WithAudioCodec(AudioCodec.Aac)
                        .WithAudioBitrate(128)
                        .WithCustomArgument("-shortest"))
                    .ProcessAsynchronously();
            }
            catch (Exception ex)
            {
                thrownew Exception($"视频音频合并失败: {ex.Message}", ex);
            }
        }

        public static async Task MergeVideoWithMultipleAudio(string videoPath, string systemAudioPath,
            string micAudioPath, string outputPath)
        {
            try
            {
                bool hasMicAudio = !string.IsNullOrEmpty(micAudioPath) && File.Exists(micAudioPath);

                if (hasMicAudio)
                {
                    await FFMpegArguments
                        .FromFileInput(videoPath)
                        .AddFileInput(systemAudioPath)
                        .AddFileInput(micAudioPath)
                        .OutputToFile(outputPath, true, options => options
                            .WithCustomArgument("-c:v copy")
                            .WithAudioCodec(AudioCodec.Aac)
                            .WithAudioBitrate(128)
                            .WithCustomArgument("-filter_complex [1:a][2:a]amix=inputs=2:duration=shortest[a]")
                            .WithCustomArgument("-map 0:v")
                            .WithCustomArgument("-map [a]"))
                        .ProcessAsynchronously();
                }
                else
                {
                    await MergeVideoAndAudio(videoPath, systemAudioPath, outputPath);
                }
            }
            catch (Exception ex)
            {
                thrownew Exception($"视频多音频合并失败: {ex.Message}", ex);
            }
        }

        private int GetBitrateForQuality(int quality)
        {
            return quality switch
            {
                1080 => 4000,
                720 => 2000,
                480 => 1000,
                _ => 2000
            };
        }

        private VideoSize GetVideoSize(int quality)
        {
            return quality switch
            {
                1080 => VideoSize.FullHd,
                720 => VideoSize.Hd,
                480 => VideoSize.Ed,
                _ => VideoSize.Hd
            };
        }

        private void CleanupTempFiles()
        {
            try
            {
                if (Directory.Exists(_tempDirectory))
                {
                    Directory.Delete(_tempDirectory, true);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"清理临时文件时出错: {ex.Message}");
            }
        }

        public void Dispose()
        {
            if (_isRecording)
            {
                _isRecording = false;
            }

            _cancellationTokenSource?.Cancel();

            while (_frameQueue.TryDequeue(out var frame))
            {
                frame.Bitmap?.Dispose();
            }

            _stopwatch?.Stop();
            _cancellationTokenSource?.Dispose();
            CleanupTempFiles();
        }
    }
}

编码优化策略

  • ultrafast预设平衡质量与速度
  • amix滤镜实现音频混合
  • 视频流复制(-c:v copy)避免重复编码

用户界面:WPF现代化设计

<Window x:Class="AppScreenRecorder.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:local="clr-namespace:AppScreenRecorder"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" KeyDown="MainWindow_KeyDown"
        Focusable="True">
    <Window.Resources>
        <!-- 样式定义 -->
        <Style x:Key="ModernButtonStyle" TargetType="Button">
            <Setter Property="Background" Value="#FF2196F3"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Padding" Value="20,10"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="Cursor" Value="Hand"/>
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="FontWeight" Value="Medium"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}"
                                CornerRadius="5"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <ContentPresenter HorizontalAlignment="Center" 
                                            VerticalAlignment="Center"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#FF1976D2"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" Value="#FF0D47A1"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="StopButtonStyle" TargetType="Button" BasedOn="{StaticResource ModernButtonStyle}">
            <Setter Property="Background" Value="#FFF44336"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#FFD32F2F"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter Property="Background" Value="#FFB71C1C"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="GroupBoxStyle" TargetType="GroupBox">
            <Setter Property="BorderBrush" Value="#FFCCCCCC"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Margin" Value="10"/>
            <Setter Property="Padding" Value="10"/>
            <Setter Property="FontWeight" Value="Medium"/>
        </Style>

        <Style x:Key="StatusLabelStyle" TargetType="Label">
            <Setter Property="FontSize" Value="16"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Margin" Value="10"/>
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 标题栏 -->
        <Border Grid.Row="0" Background="#FF2196F3" Padding="20,15">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="🎬" FontSize="24" Margin="0,0,10,0"/>
                <TextBlock Text="C# 录屏神器" FontSize="20" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
                <TextBlock Text="轻松实现视频音频同步录制" FontSize="12" Foreground="#FFBBDEFB" VerticalAlignment="Center" Margin="20,0,0,0"/>
            </StackPanel>
        </Border>

        <!-- 主要内容区域 -->
        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
            <StackPanel Margin="20">

                <!-- 录制控制区域 -->
                <GroupBox Header="🎥 录制控制" Style="{StaticResource GroupBoxStyle}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="0,0,0,15">
                            <Button Name="RecordButton" Content="开始录制" 
                                    Style="{StaticResource ModernButtonStyle}"
                                    Click="RecordButton_Click" MinWidth="120"/>
                            <Button Name="StopButton" Content="停止录制" 
                                    Style="{StaticResource StopButtonStyle}"
                                    Click="StopButton_Click" MinWidth="120"
                                    IsEnabled="False"/>
                            <Button Name="PauseButton" Content="暂停录制" 
                                    Style="{StaticResource ModernButtonStyle}"
                                    Click="PauseButton_Click" MinWidth="120"
                                    IsEnabled="False"/>
                        </StackPanel>

                        <Label Name="StatusLabel" Content="就绪" Grid.Row="1" Grid.Column="0"
                               Style="{StaticResource StatusLabelStyle}" Foreground="#FF4CAF50"/>

                        <StackPanel Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Orientation="Vertical">
                            <TextBlock Text="录制时长:" FontWeight="Medium" Margin="0,0,0,5"/>
                            <Label Name="RecordTimeLabel" Content="00:00:00" 
                                   FontSize="18" FontFamily="Consolas" 
                                   Background="#FFF5F5F5" Padding="10,5"
                                   HorizontalContentAlignment="Center"/>
                        </StackPanel>
                    </Grid>
                </GroupBox>

                <!-- 录制设置区域 -->
                <GroupBox Header="⚙️ 录制设置" Style="{StaticResource GroupBoxStyle}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <!-- 视频设置 -->
                        <TextBlock Text="视频质量:" Grid.Row="0" Grid.Column="0" Margin="0,0,0,5" FontWeight="Medium"/>
                        <ComboBox Name="VideoQualityComboBox" Grid.Row="0" Grid.Column="1" Margin="10,0,0,10">
                            <ComboBoxItem Content="高质量 (1080p)" IsSelected="True"/>
                            <ComboBoxItem Content="标准质量 (720p)"/>
                            <ComboBoxItem Content="低质量 (480p)"/>
                        </ComboBox>

                        <TextBlock Text="帧率 (FPS):" Grid.Row="1" Grid.Column="0" Margin="0,0,0,5" FontWeight="Medium"/>
                        <ComboBox Name="FrameRateComboBox" Grid.Row="1" Grid.Column="1" Margin="10,0,0,10">
                            <ComboBoxItem Content="60 FPS"/>
                            <ComboBoxItem Content="30 FPS" IsSelected="True"/>
                            <ComboBoxItem Content="24 FPS"/>
                            <ComboBoxItem Content="15 FPS"/>
                        </ComboBox>

                        <!-- 音频设置 -->
                        <CheckBox Name="RecordSystemAudioCheckBox" Content="录制系统音频" 
                                  Grid.Row="2" Grid.Column="0" IsChecked="True" Margin="0,10"/>
                        <CheckBox Name="RecordMicAudioCheckBox" Content="录制麦克风" 
                                  Grid.Row="2" Grid.Column="1" IsChecked="True" Margin="10,10,0,10"/>

                        <!-- 录制区域 -->
                        <TextBlock Text="录制区域:" Grid.Row="3" Grid.Column="0" Margin="0,0,0,5" FontWeight="Medium"/>
                        <ComboBox Name="RecordAreaComboBox" Grid.Row="3" Grid.Column="1" Margin="10,0,0,10">
                            <ComboBoxItem Content="全屏录制" IsSelected="True"/>
                            <ComboBoxItem Content="选择窗口"/>
                            <ComboBoxItem Content="自定义区域"/>
                        </ComboBox>

                        <!-- 输出路径 -->
                        <TextBlock Text="保存路径:" Grid.Row="4" Grid.Column="0" Margin="0,10,0,5" FontWeight="Medium"/>
                        <StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Margin="10,10,0,0">
                            <TextBox Name="OutputPathTextBox" Width="200" Margin="0,0,10,0" 
                                     Text="C:\录屏文件\录屏_" IsReadOnly="True"/>
                            <Button Content="浏览..." Click="BrowseButton_Click" 
                                    Style="{StaticResource ModernButtonStyle}" Padding="10,5"/>
                        </StackPanel>
                    </Grid>
                </GroupBox>


                <!-- 快捷键设置 -->
                <GroupBox Header="⌨️ 快捷键" Style="{StaticResource GroupBoxStyle}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <TextBlock Text="开始/停止录制:" Grid.Row="0" Grid.Column="0" Margin="0,0,20,5" FontWeight="Medium"/>
                        <TextBlock Text="F9" Grid.Row="0" Grid.Column="1" FontFamily="Consolas" Background="#FFF0F0F0" Padding="5,2"/>

                        <TextBlock Text="暂停/恢复:" Grid.Row="1" Grid.Column="0" Margin="0,0,20,5" FontWeight="Medium"/>
                        <TextBlock Text="F10" Grid.Row="1" Grid.Column="1" FontFamily="Consolas" Background="#FFF0F0F0" Padding="5,2"/>

                        <TextBlock Text="截图:" Grid.Row="2" Grid.Column="0" Margin="0,0,20,5" FontWeight="Medium"/>
                        <TextBlock Text="F11" Grid.Row="2" Grid.Column="1" FontFamily="Consolas" Background="#FFF0F0F0" Padding="5,2"/>
                    </Grid>
                </GroupBox>

            </StackPanel>
        </ScrollViewer>

        <!-- 底部状态栏 -->
        <Border Grid.Row="2" Background="#FFF5F5F5" BorderBrush="#FFDDDDDD" BorderThickness="0,1,0,0">
            <Grid Margin="20,10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>

                <StackPanel Grid.Column="0" Orientation="Horizontal">
                    <TextBlock Text="状态:" Margin="0,0,10,0" VerticalAlignment="Center"/>
                    <Ellipse Name="StatusIndicator" Width="12" Height="12" Fill="#FF4CAF50" Margin="0,0,10,0" VerticalAlignment="Center"/>
                    <TextBlock Name="DetailStatusLabel" Text="系统就绪" VerticalAlignment="Center"/>
                </StackPanel>

                <StackPanel Grid.Column="1" Orientation="Horizontal">
                    <Button Content="📁 打开输出文件夹" Click="OpenOutputFolderButton_Click" 
                            Style="{StaticResource ModernButtonStyle}" Padding="10,5" Margin="0,0,10,0"/>
                </StackPanel>
            </Grid>
        </Border>

    </Grid>
</Window>
    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Threading;
    using System.Threading.Tasks;
    using Microsoft.Win32;
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows.Forms;
    using MessageBox = System.Windows.MessageBox;
    using System.Windows.Input;
    using KeyEventArgs = System.Windows.Input.KeyEventArgs;
    
    namespace AppScreenRecorder
    {
        public partial class MainWindow : Window
        {
            private ScreenRecorder _screenRecorder;
            private AudioRecorder _audioRecorder;
            private DispatcherTimer _recordingTimer;
            private DateTime _startTime;
            privatebool _isRecording = false;
            privatebool _isPaused = false;
    
            public MainWindow()
            {
                InitializeComponent();
                InitializeTimer();
                SetInitialState();
                this.Focusable = true;
                this.Focus();
            }
    
            private void InitializeTimer()
            {
                _recordingTimer = new DispatcherTimer();
                _recordingTimer.Interval = TimeSpan.FromSeconds(1);
                _recordingTimer.Tick += UpdateRecordingTime;
            }
    
            private void SetInitialState()
            {
                RecordButton.IsEnabled = true;
                StopButton.IsEnabled = false;
                PauseButton.IsEnabled = false;
                StatusLabel.Content = "就绪";
                StatusLabel.Foreground = System.Windows.Media.Brushes.Green;
                RecordTimeLabel.Content = "00:00:00";
                StatusIndicator.Fill = System.Windows.Media.Brushes.Green;
                DetailStatusLabel.Text = "系统就绪";
    
                // 设置默认输出路径
                string defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "录屏文件");
                if (!Directory.Exists(defaultPath))
                {
                    Directory.CreateDirectory(defaultPath);
                }
                OutputPathTextBox.Text = Path.Combine(defaultPath, "录屏_");
            }
    
            private async void RecordButton_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    if (!_isRecording)
                    {
                        await StartRecording();
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"开始录制时出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    SetInitialState();
                }
            }
    
            private async void StopButton_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    await StopRecording();
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"停止录制时出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            private void PauseButton_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    if (_isPaused)
                    {
                        ResumeRecording();
                    }
                    else
                    {
                        PauseRecording();
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"暂停/恢复录制时出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            private async Task StartRecording()
            {
                // 生成输出文件路径
                string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
                string finalOutputPath = OutputPathTextBox.Text + timestamp + ".mp4";
                string audioBasePath = OutputPathTextBox.Text + timestamp;
    
                // 获取录制设置
                var quality = GetSelectedQuality();
                var frameRate = GetSelectedFrameRate();
                var recordArea = GetRecordArea();
    
                // 更新UI状态
                _isRecording = true;
                _isPaused = false;
                _startTime = DateTime.Now;
    
                RecordButton.IsEnabled = false;
                StopButton.IsEnabled = true;
                PauseButton.IsEnabled = true;
                PauseButton.Content = "暂停录制";
    
                StatusLabel.Content = "录制中...";
                StatusLabel.Foreground = System.Windows.Media.Brushes.Red;
                StatusIndicator.Fill = System.Windows.Media.Brushes.Red;
                DetailStatusLabel.Text = "正在录制屏幕...";
    
                // 启动计时器
                _recordingTimer.Start();
    
                try
                {
                    // 初始化录制器
                    _screenRecorder = new ScreenRecorder();
                    _audioRecorder = new AudioRecorder();
    
                    // 启动音频录制(如果需要)
                    bool recordMic = RecordMicAudioCheckBox.IsChecked ?? false;
                    bool recordSystem = RecordSystemAudioCheckBox.IsChecked ?? false;
    
                    if (recordMic || recordSystem)
                    {
                        _audioRecorder.StartRecording(audioBasePath, recordMic, recordSystem);
                    }
    
                    // 启动屏幕录制
                    await _screenRecorder.StartRecording(finalOutputPath, recordArea, frameRate, quality);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"录制失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    await StopRecording();
                    throw;
                }
            }
    
            private async Task StopRecording()
            {
                if (!_isRecording) return;
    
                _isRecording = false;
                _recordingTimer.Stop();
    
                // 更新UI状态
                StatusLabel.Content = "正在停止录制...";
                StatusLabel.Foreground = System.Windows.Media.Brushes.Orange;
                StatusIndicator.Fill = System.Windows.Media.Brushes.Orange;
                DetailStatusLabel.Text = "正在停止录制,请稍候...";
    
                try
                {
                    string tempVideoPath = null;
                    string systemAudioPath = null;
                    string micAudioPath = null;
    
                    // 1. 立即停止屏幕录制
                    if (_screenRecorder != null)
                    {
                        await _screenRecorder.StopRecording();
                        tempVideoPath = _screenRecorder.TempVideoPath;
                    }
    
                    // 2. 立即停止音频录制
                    if (_audioRecorder != null)
                    {
                        _audioRecorder.StopRecording();
                        systemAudioPath = _audioRecorder.SystemAudioPath;
                        micAudioPath = _audioRecorder.MicAudioPath;
                    }
    
                    // 3. 减少等待时间,只等待文件系统同步
                    await Task.Delay(200);
    
                    // 更新UI状态
                    StatusLabel.Content = "处理中...";
                    DetailStatusLabel.Text = "正在合并视频和音频...";
    
                    // 4. 生成最终输出路径
                    string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
                    string finalOutputPath = OutputPathTextBox.Text + timestamp + ".mp4";
    
                    // 5. 合并视频和音频
                    await MergeVideoAndAudio(tempVideoPath, systemAudioPath, micAudioPath, finalOutputPath);
    
                    // 6. 清理资源
                    _screenRecorder?.Dispose();
                    _audioRecorder?.Dispose();
                    _screenRecorder = null;
                    _audioRecorder = null;
    
                    // 恢复UI状态
                    SetInitialState();
    
                    MessageBox.Show($"录制完成!\n文件保存至:{finalOutputPath}", "成功",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    _screenRecorder?.Dispose();
                    _audioRecorder?.Dispose();
                    _screenRecorder = null;
                    _audioRecorder = null;
    
                    MessageBox.Show($"停止录制时出错:{ex.Message}", "错误",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                    SetInitialState();
                }
            }
    
            private async Task MergeVideoAndAudio(string videoPath, string systemAudioPath,
        string micAudioPath, string outputPath)
            {
                try
                {
                    if (string.IsNullOrEmpty(videoPath) || !File.Exists(videoPath))
                    {
                        thrownew Exception("视频文件不存在");
                    }
    
                    if (string.IsNullOrEmpty(systemAudioPath) && string.IsNullOrEmpty(micAudioPath))
                    {
                        File.Copy(videoPath, outputPath, true);
                        return;
                    }
    
                    if (!string.IsNullOrEmpty(systemAudioPath) && File.Exists(systemAudioPath) &&
                        !string.IsNullOrEmpty(micAudioPath) && File.Exists(micAudioPath))
                    {
                        await ScreenRecorder.MergeVideoWithMultipleAudio(videoPath, systemAudioPath,
                            micAudioPath, outputPath);
                    }
    
                    elseif (!string.IsNullOrEmpty(systemAudioPath) && File.Exists(systemAudioPath))
                    {
                        await ScreenRecorder.MergeVideoAndAudio(videoPath, systemAudioPath, outputPath);
                    }
    
                    elseif (!string.IsNullOrEmpty(micAudioPath) && File.Exists(micAudioPath))
                    {
                        await ScreenRecorder.MergeVideoAndAudio(videoPath, micAudioPath, outputPath);
                    }
                    else
                    {
                        File.Copy(videoPath, outputPath, true);
                    }
    
                    CleanupTempFiles(videoPath, systemAudioPath, micAudioPath);
                }
                catch (Exception ex)
                {
                    thrownew Exception($"合并视频音频失败: {ex.Message}", ex);
                }
            }
    
            // 清理临时文件的方法
            private void CleanupTempFiles(params string[] filePaths)
            {
                foreach (var filePath in filePaths)
                {
                    if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                    {
                        try
                        {
                            File.Delete(filePath);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"删除临时文件失败: {ex.Message}");
                        }
                    }
                }
            }
    
    
            private void PauseRecording()
            {
                if (!_isRecording) return;
    
                _isPaused = true;
                _recordingTimer.Stop();
    
                _screenRecorder?.PauseRecording();
                _audioRecorder?.PauseRecording();
    
                PauseButton.Content = "恢复录制";
                StatusLabel.Content = "已暂停";
                StatusLabel.Foreground = System.Windows.Media.Brushes.Orange;
                StatusIndicator.Fill = System.Windows.Media.Brushes.Orange;
                DetailStatusLabel.Text = "录制已暂停";
            }
    
            private void ResumeRecording()
            {
                if (!_isRecording) return;
    
                _isPaused = false;
                _recordingTimer.Start();
    
                _screenRecorder?.ResumeRecording();
                _audioRecorder?.ResumeRecording();
    
                PauseButton.Content = "暂停录制";
                StatusLabel.Content = "录制中...";
                StatusLabel.Foreground = System.Windows.Media.Brushes.Red;
                StatusIndicator.Fill = System.Windows.Media.Brushes.Red;
                DetailStatusLabel.Text = "正在录制屏幕...";
            }
    
            private void UpdateRecordingTime(object sender, EventArgs e)
            {
                if (_isRecording && !_isPaused)
                {
                    TimeSpan elapsed = DateTime.Now - _startTime;
                    RecordTimeLabel.Content = elapsed.ToString(@"hh\:mm\:ss");
                }
            }
    
            private int GetSelectedQuality()
            {
                var selectedItem = VideoQualityComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem;
                return selectedItem?.Content.ToString() switch
                {
                    "高质量 (1080p)" => 1080,
                    "标准质量 (720p)" => 720,
                    "低质量 (480p)" => 480,
                    _ => 720
                };
            }
    
            private int GetSelectedFrameRate()
            {
                var selectedItem = FrameRateComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem;
                return selectedItem?.Content.ToString() switch
                {
                    "60 FPS" => 60,
                    "30 FPS" => 30,
                    "24 FPS" => 24,
                    "15 FPS" => 15,
                    _ => 30
                };
            }
    
            private Rectangle? GetRecordArea()
            {
                var selectedItem = RecordAreaComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem;
                return selectedItem?.Content.ToString() switch
                {
                    "全屏录制" => Screen.PrimaryScreen.Bounds,
                    "选择窗口" => null, // 这里可以添加窗口选择逻辑
                    "自定义区域" => null, // 这里可以添加区域选择逻辑
                    _ => Screen.PrimaryScreen.Bounds
                };
            }
    
            private void BrowseButton_Click(object sender, RoutedEventArgs e)
            {
                var dialog = new System.Windows.Forms.FolderBrowserDialog();
                dialog.Description = "选择录屏文件保存路径";
    
                if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    OutputPathTextBox.Text = Path.Combine(dialog.SelectedPath, "录屏_");
                }
            }
    
            private void OpenOutputFolderButton_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    string folderPath = Path.GetDirectoryName(OutputPathTextBox.Text);
                    if (Directory.Exists(folderPath))
                    {
                        Process.Start("explorer.exe", folderPath);
                    }
                    else
                    {
                        MessageBox.Show("输出文件夹不存在!", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"打开文件夹失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            private async void MainWindow_KeyDown(object sender, KeyEventArgs e)
            {
                try
                {
                    switch (e.Key)
                    {
                        case Key.F9:
                            await HandleF9KeyPress();
                            e.Handled = true;
                            break;
                        case Key.F10:
                            HandleF10KeyPress();
                            e.Handled = true;
                            break;
                        case Key.F11:
                            HandleF11KeyPress();
                            e.Handled = true;
                            break;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"快捷键操作出错:{ex.Message}", "错误",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            // F9 - 开始/停止录制
            private async Task HandleF9KeyPress()
            {
                if (_isRecording)
                {
                    await StopRecording();
                }
                else
                {
                    await StartRecording();
                }
            }
    
            // F10 - 暂停/恢复录制
            private void HandleF10KeyPress()
            {
                if (!_isRecording)
                {
                    MessageBox.Show("请先开始录制后再使用暂停功能", "提示",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
    
                if (_isPaused)
                {
                    ResumeRecording();
                }
                else
                {
                    PauseRecording();
                }
            }
    
            // F11 - 截图功能
            private void HandleF11KeyPress()
            {
                try
                {
                    TakeScreenshot();
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"截图失败:{ex.Message}", "错误",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            // 截图功能实现
            private void TakeScreenshot()
            {
                try
                {
                    var bounds = Screen.PrimaryScreen.Bounds;
                    using (var bitmap = new Bitmap(bounds.Width, bounds.Height))
                    {
                        using (var graphics = System.Drawing.Graphics.FromImage(bitmap))
                        {
                            graphics.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
                        }
    
                        // 生成截图文件名
                        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
                        string screenshotPath = Path.Combine(
                            Path.GetDirectoryName(OutputPathTextBox.Text),
                            $"截图_{timestamp}.png");
    
                        // 确保目录存在
                        Directory.CreateDirectory(Path.GetDirectoryName(screenshotPath));
    
                        // 保存截图
                        bitmap.Save(screenshotPath, System.Drawing.Imaging.ImageFormat.Png);
    
                        // 显示成功消息
                        MessageBox.Show($"截图已保存:\n{screenshotPath}", "截图成功",
                            MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
                catch (Exception ex)
                {
                    thrownew Exception($"截图操作失败:{ex.Message}");
                }
            }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                this.Focus();
            }
    
            private void MainWindow_Activated(object sender, EventArgs e)
            {
                this.Focus();
            }
    
    
            protected override void OnClosed(EventArgs e)
            {
                // 确保在关闭窗口时停止录制
                if (_isRecording)
                {
                    _screenRecorder?.Dispose();
                    _audioRecorder?.Dispose();
                }
                _recordingTimer?.Stop();
                base.OnClosed(e);
            }
        }
    }

    图片

    UI设计亮点

    • 全键盘快捷键支持,提升操作效率
    • 实时状态反馈,用户体验友好
    • 响应式布局适配不同屏幕尺寸

    关键问题与解决方案

    内存泄漏防护

    public void Dispose()
    {
        if (_isRecording)
        {
            _isRecording = false;
        }
    
        _cancellationTokenSource?.Cancel();
    
        // 清理帧缓存
        while (_frameQueue.TryDequeue(out var frame))
        {
            frame.Bitmap?.Dispose();
        }
    
        _stopwatch?.Stop();
        _cancellationTokenSource?.Dispose();
        CleanupTempFiles();
    }

      线程安全保障

      // 使用并发集合确保线程安全
      private readonly ConcurrentQueue<FrameData> _frameQueue = new ConcurrentQueue<FrameData>();
      
      // 取消令牌统一管理
      private CancellationTokenSource _cancellationTokenSource;

      异常处理机制

      private async Task StopRecording()
      {
          try
          {
              // 设置超时机制,避免无限等待
              var timeoutTask = Task.Delay(3000);
              var completedTask = await Task.WhenAny(_captureTask, timeoutTask);
      
              if (completedTask == timeoutTask)
              {
                  Console.WriteLine("警告: 捕获任务超时");
              }
          }
          catch (Exception ex)
          {
              Console.WriteLine($"停止录制时出错: {ex.Message}");
          }
      }

      部署与扩展

      NuGet包依赖

      <PackageReference Include="FFMpegCore" Version="4.8.0" />
      <PackageReference Include="NAudio" Version="2.1.0" />
      <PackageReference Include="System.Drawing.Common" Version="7.0.0" />

      性能监控

      // 队列监控
      if (_frameQueue.Count > 500)
      {
          bitmap?.Dispose();
          Console.WriteLine("队列满,丢弃一帧");
      }
      
      // 处理进度反馈
      if (processedFrames % 100 == 0)
      {
          Console.WriteLine($"已保存 {processedFrames} 帧,队列剩余: {_frameQueue.Count}");
      }

      核心收获总结

      通过这个屏幕录制器项目,你掌握了:

      系统级编程:Windows API调用、GDI+图形处理、音频设备访问等底层技术

      多线程并发:生产者-消费者模式、线程优先级管理、并发集合使用

      性能优化:内存管理、CPU密集型任务优化、I/O操作异步化

      这不仅仅是一个录屏工具,更是一个完整的C#系统编程实战案例。代码中的设计模式、性能优化技巧、异常处理机制都可以应用到其他项目中。

      AI大模型学习福利

      作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

      一、全套AGI大模型学习路线

      AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

      因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

      二、640套AI大模型报告合集

      这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

      因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

      三、AI大模型经典PDF籍

      随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


      因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

      四、AI大模型商业化落地方案

      因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

      作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

      资源下载链接为: https://pan.quark.cn/s/3d8e22c21839 在软件开发领域,屏幕像功能有着极为广泛的应用,尤其在教学、演示及游戏录制等场景中,它的重要性不言而喻。本项目旨在借助C#编程语言以及Windows Forms(Winform)框架来实现屏幕像功能。C#是微软推出的种面向对象的编程语言,而Winform作为.NET Framework的部分,主要用于构建桌面应用程序。要实现屏幕像,关键在于掌握捕获屏幕图像、处理视频编码以及保存录制结果这三大核心技术。 首先,捕获屏幕图像的过程可以通过C#中的Graphics类和Bitmap类来完成。具体操作是先创建个与屏幕分辨率致的Bitmap对象,接着利用Graphics.FromHdc()方法获取设备上下文,最后调用CopyFromScreen()方法,将屏幕内容复制到Bitmap上。如此来,每更新次Bitmap,就能获取到屏幕的最新帧。 其次,要将捕获到的屏幕图像转化为连续的视频流,就需要进行视频编码。常见的视频编码库有FFmpeg和DirectShow。在C#环境下,可以选用托管的FFmpeg库,像SharpFFmpeg,或者通过P/Invoke调用DirectShow的API来实现。编码过程中涉及帧间预测、熵编码、量化等多个步骤,其目的是在尽可能减少数据量的同时,保持较好的视觉质量。 再者,为了保证视频播放的流畅性,必须对帧率进行控制。这可以通过设置计时实现,通过控制每秒捕获图像的次数,确保帧率的稳定性。例如,若目标帧率为30 fps,那么计时的Interval属性就应设置为33.33毫秒。 此外,录制完成的视频需要以特定的文件格式进行保存,比如MP4、AVI等。这需要依据所选用的编码库来具体实现。以FFmpeg为例,可以创建个OutputStream,并调用其Wri
      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值