You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF录屏应用开发:如何将截屏帧合成为视频文件?

WPF截屏帧合成视频解决方案

你的核心需求是把截屏得到的帧合成视频,WPF本身没有原生视频编码API,推荐使用FFmpeg.AutoGen(开源免费、功能全面)来实现,以下是具体步骤和代码示例:

一、优化截屏代码

当前你返回的BitmapImage是用于WPF控件显示的,编码视频时直接用System.Drawing.Bitmap效率更高,修改CaptureScreen方法:

private System.Drawing.Bitmap CaptureScreen()
{
    var screenSize = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
    var target = new System.Drawing.Bitmap(screenSize.Width, screenSize.Height);
    using (var g = System.Drawing.Graphics.FromImage(target))
    {
        g.CopyFromScreen(0, 0, 0, 0, screenSize.Size);
    }
    return target;
}

二、集成FFmpeg.AutoGen实现视频编码

1. 安装依赖

通过NuGet安装FFmpeg.AutoGen,同时需要下载对应平台的FFmpeg原生库(比如Windows下的avcodec-58.dll、avformat-58.dll等),将这些dll放到项目输出目录,并设置“复制到输出目录”为“始终复制”。

2. 视频编码核心实现

创建一个视频编码器类,负责初始化、帧编码、资源清理:

using FFmpeg.AutoGen;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

public class ScreenRecorder
{
    private AVFormatContext* _formatContext;
    private AVStream* _videoStream;
    private AVCodecContext* _codecContext;
    private SwsContext* _swsContext;
    private AVPacket _packet;
    private AVFrame* _frame;
    private int _frameIndex;
    private readonly int _width;
    private readonly int _height;
    private readonly int _fps;

    public ScreenRecorder(string outputPath, int width, int height, int fps = 30)
    {
        _width = width;
        _height = height;
        _fps = fps;
        InitializeFFmpeg();
        InitializeOutputContext(outputPath);
        InitializeVideoStream();
        InitializeFrameAndConverter();
    }

    private void InitializeFFmpeg()
    {
        ffmpeg.av_register_all();
        ffmpeg.avformat_network_init();
    }

    private void InitializeOutputContext(string outputPath)
    {
        ffmpeg.avformat_alloc_output_context2(&_formatContext, null, null, outputPath);
        if (_formatContext == null)
        {
            throw new InvalidOperationException("Failed to create output context");
        }
    }

    private void InitializeVideoStream()
    {
        var codec = ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264);
        if (codec == null)
        {
            throw new InvalidOperationException("H.264 encoder not found");
        }

        _videoStream = ffmpeg.avformat_new_stream(_formatContext, codec);
        _codecContext = ffmpeg.avcodec_alloc_context3(codec);

        _codecContext->codec_id = AVCodecID.AV_CODEC_ID_H264;
        _codecContext->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO;
        _codecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
        _codecContext->width = _width;
        _codecContext->height = _height;
        _codecContext->time_base = new AVRational { num = 1, den = _fps };
        _codecContext->framerate = new AVRational { num = _fps, den = 1 };
        _codecContext->bit_rate = 4000000; // 4Mbps码率,可根据需求调整

        if (_formatContext->oformat->flags.HasFlag(AVFMT_GLOBALHEADER))
        {
            _codecContext->flags |= ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER;
        }

        if (ffmpeg.avcodec_open2(_codecContext, codec, null) < 0)
        {
            throw new InvalidOperationException("Failed to open codec");
        }

        ffmpeg.avcodec_parameters_from_context(_videoStream->codecpar, _codecContext);
    }

    private void InitializeFrameAndConverter()
    {
        _frame = ffmpeg.av_frame_alloc();
        _frame->format = (int)AVPixelFormat.AV_PIX_FMT_YUV420P;
        _frame->width = _width;
        _frame->height = _height;
        ffmpeg.av_frame_get_buffer(_frame, 0);

        _swsContext = ffmpeg.sws_getContext(
            _width, _height, AVPixelFormat.AV_PIX_FMT_BGR24,
            _width, _height, AVPixelFormat.AV_PIX_FMT_YUV420P,
            ffmpeg.SWS_BILINEAR, null, null, null);

        _packet = new AVPacket();
        ffmpeg.av_init_packet(&_packet);
    }

    public void EncodeFrame(Bitmap bitmap)
    {
        // 将Bitmap转换为BGR24格式的像素数据
        var bitmapData = bitmap.LockBits(new Rectangle(0, 0, _width, _height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        try
        {
            var srcLinesize = new int[] { bitmapData.Stride };
            var srcData = new IntPtr[] { bitmapData.Scan0 };
            
            ffmpeg.sws_scale(
                _swsContext,
                srcData,
                srcLinesize,
                0,
                _height,
                _frame->data,
                _frame->linesize);
        }
        finally
        {
            bitmap.UnlockBits(bitmapData);
        }

        _frame->pts = _frameIndex++;

        // 编码帧
        if (ffmpeg.avcodec_send_frame(_codecContext, _frame) >= 0)
        {
            while (ffmpeg.avcodec_receive_packet(_codecContext, &_packet) >= 0)
            {
                ffmpeg.av_packet_rescale_ts(&_packet, _codecContext->time_base, _videoStream->time_base);
                ffmpeg.av_interleaved_write_frame(_formatContext, &_packet);
                ffmpeg.av_packet_unref(&_packet);
            }
        }
    }

    public void FinishRecording()
    {
        // 发送空帧,处理剩余编码数据
        ffmpeg.avcodec_send_frame(_codecContext, null);
        while (ffmpeg.avcodec_receive_packet(_codecContext, &_packet) >= 0)
        {
            ffmpeg.av_packet_rescale_ts(&_packet, _codecContext->time_base, _videoStream->time_base);
            ffmpeg.av_interleaved_write_frame(_formatContext, &_packet);
            ffmpeg.av_packet_unref(&_packet);
        }

        // 写入文件尾
        ffmpeg.av_write_trailer(_formatContext);

        // 清理资源
        ffmpeg.avcodec_close(_codecContext);
        ffmpeg.av_frame_free(&_frame);
        ffmpeg.sws_freeContext(_swsContext);
        ffmpeg.avformat_free_context(_formatContext);
    }
}

3. 修改录屏逻辑

在你的WPF窗口类中,集成上述编码器,调整DispatcherTimer的逻辑:

private ScreenRecorder _recorder;
private DispatcherTimer _dispatcherTimer;

// 开始录屏方法
private void StartRecording()
{
    var screenSize = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
    _recorder = new ScreenRecorder("output.mp4", screenSize.Width, screenSize.Height, 30);
    
    _dispatcherTimer = new DispatcherTimer();
    _dispatcherTimer.Interval = TimeSpan.FromMilliseconds(1000 / 30); // 30帧每秒
    _dispatcherTimer.Tick += DispatcherTimer_Tick;
    _dispatcherTimer.Start();
}

private void DispatcherTimer_Tick(object sender, EventArgs e)
{
    using (var bitmap = CaptureScreen())
    {
        ScreenImage.Source = BitmapToBitmapImage(bitmap); // 继续显示到WPF控件
        _recorder.EncodeFrame(bitmap);
    }
}

// 结束录屏方法
private void StopRecording()
{
    _dispatcherTimer.Stop();
    _recorder.FinishRecording();
}

// 辅助方法:Bitmap转BitmapImage用于WPF显示
private BitmapImage BitmapToBitmapImage(Bitmap bitmap)
{
    using (var memory = new System.IO.MemoryStream())
    {
        bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
        memory.Position = 0;
        var bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.StreamSource = memory;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.EndInit();
        bitmapImage.Freeze(); // 冻结避免跨线程问题
        return bitmapImage;
    }
}

三、注意事项

  • 性能优化:录屏时尽量避免UI卡顿,可将编码逻辑放到后台线程(比如用Task.Run包裹编码步骤);调整帧率和码率平衡画质与文件大小。
  • FFmpeg库兼容:确保下载的FFmpeg库版本与FFmpeg.AutoGen的版本匹配,避免运行时错误。
  • 内存管理:所有Bitmap和FFmpeg资源要及时释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Sarper Makas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 12:27:13