使用Accord.Video.FFMPEG录屏时CPU与内存占用过高问题求助
解决Accord.Video.FFMPEG录屏时CPU和内存占用过高的问题
看了你的代码和问题描述,核心问题出在GDI+资源未正确释放,以及编码配置优化不足,这两个点是导致CPU飙升和内存暴增的主要原因。下面一步步给你拆解解决方案:
1. 修复GDI+资源泄漏(内存暴增的核心原因)
你代码里的Graphics、Pen这些GDI+对象都实现了IDisposable接口,但你没有手动释放它们——GC不会自动回收非托管的GDI资源,这会导致这些对象一直占用内存,最终累积到700MB以上。同时,未释放的资源也会让CPU因为资源竞争而升高。
修改你的帧处理代码,用using语句自动包裹需要释放的对象:
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs) { try { if (this._isRecording) { int screenRecordingLength = screenRecrodingRule != null ? screenRecrodingRule.length : 500; // 克隆原帧,避免影响框架内部的资源管理 using (Bitmap frame = (Bitmap)eventArgs.Frame.Clone()) { using (Graphics graphics = Graphics.FromImage(frame)) { CURSORINFO pci; pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO)); if (GetCursorInfo(out pci) && pci.flags == CURSOR_SHOWING) { int x = pci.ptScreenPos.x - screenLeft; int y = pci.ptScreenPos.y - screenTop; Color c = Color.Yellow; float width = 2; int radius = 30; if ((Control.MouseButtons & MouseButtons.Left) != 0 || (Control.MouseButtons & MouseButtons.Right) != 0) { c = Color.OrangeRed; width = 4; radius = 35; } // 用using包裹Pen,自动释放资源 using (Pen p = new Pen(c, width)) { graphics.DrawEllipse(p, x - radius / 2, y - radius / 2, radius, radius); } // 确保Hdc资源一定会被释放 IntPtr hdc = graphics.GetHdc(); try { DrawIcon(hdc, x, y, pci.hCursor); } finally { graphics.ReleaseHdc(hdc); } } // 恢复帧写入,异常时记录日志而非跳过,避免帧堆积 try { this._writer.WriteVideoFrame(frame, (DateTime.Now - videoStartTime)); } catch (Exception ex) { log.ErrorFormat("SR: Failed to write video frame: {0}", ex.Message); } } } // 原有停止逻辑保持不变 if (DateTime.Now.Subtract(videoStartTime).TotalSeconds > screenRecordingLength) { log.DebugFormat("SR: Stopping the video capturing as the time limit is exceded config length:{0}, video length: {1}.", screenRecrodingRule.length, DateTime.Now.Subtract(videoStartTime).TotalSeconds); isVideoCaptureCompeted = true; previousScreenRecodringRule = null; _streamVideo.SignalToStop(); Thread.Sleep(100); _writer.Flush(); Thread.Sleep(100); _writer.Close(); StopVideoCapture(); } } else { _streamVideo.SignalToStop(); Thread.Sleep(100); _writer.Flush(); Thread.Sleep(100); _writer.Close(); } } catch (Exception ex) { log.ErrorFormat("SR: Exception occured while capturing the video {0}, {1}", ex.Message, ex.StackTrace); } }
关键修改点:
- 用
using包裹Bitmap、Graphics、Pen,确保每次处理完帧后自动释放所有GDI资源。 - 对
GetHdc()的调用添加try-finally,保证ReleaseHdc()一定会执行,避免Hdc资源泄漏。 - 恢复
WriteVideoFrame的调用,异常时记录日志而不是跳过,避免未释放的帧堆积。
2. 优化FFMPEG编码配置(降低CPU占用)
默认的编码配置可能没有做优化,导致CPU压力过大。初始化VideoFileWriter时,选择高效的编码格式并调整参数:
// 示例初始化代码 int fps = 15; // 降低帧率到15-20fps,足够流畅且减少CPU压力 int bitrate = 1000000; // 调整码率,平衡质量和文件大小/CPU占用 _writer = new VideoFileWriter(); _writer.Open( "output.mp4", screenWidth, screenHeight, fps, VideoCodec.H264, // 使用H.264编码,比默认编码更高效 bitrate ); // 添加CRF参数,恒定质量模式,减少编码波动,降低CPU占用 _writer.AddOption("crf", "23"); // 23是默认值,值越高压缩率越高(质量略降),CPU占用越低
3. 其他优化细节
- 缓存状态减少重复计算:比如鼠标按钮状态的判断,可以缓存前一次的状态,只有当状态变化时才更新颜色/尺寸,减少重复运算。
- 监控帧队列堆积:如果
WriteVideoFrame的速度跟不上采集速度,会导致帧在内存中堆积。可以添加日志监控帧写入耗时,或设置不超过屏幕刷新频率的帧率(一般15-25fps足够)。 - 彻底释放录屏资源:确保
StopVideoCapture方法中正确释放_streamVideo资源,比如调用_streamVideo.WaitForStop()而非仅SignalToStop(),避免线程残留。
至于你尝试的GC.Collect()无效,是因为GC只能回收托管资源,而GDI+的Graphics、Pen等包含非托管资源,必须手动调用Dispose()才能释放,所以单纯调用GC无法解决内存问题。
内容的提问来源于stack exchange,提问作者Ravi Kanth
相关产品推荐
相关产品推荐

