C#技术问询:如何创建内存中视频片段并实现无文件系统高效录制屏幕最后60秒
Great question—your current bottleneck is definitely those redundant Bitmap ↔ JPEG conversions, which are eating up CPU cycles and wasting memory. Let’s break down how to fix this by cutting out the middleman and handling everything efficiently in memory, without touching the filesystem for intermediate data.
1. Ditch the JPEG Middleman (Accord Library Quick Win)
If you want to stick with the Accord library for simplicity, the first easy fix is to eliminate the JPEG encoding/decoding entirely. Right now you’re converting Bitmaps to JPEG bytes, then converting them back to Bitmaps when writing the video—this double conversion is completely unnecessary.
Instead, store your captured Bitmap frames directly in memory (or better yet, store raw pixel data to reduce object overhead). Here’s how to adjust your code:
// Store raw pixel data instead of JPEG bytes to save memory and avoid conversion List<byte[]> rawFrameData = new List<byte[]>(); int frameWidth = 1920; int frameHeight = 1080; // When capturing a screenshot: using (Bitmap bitmap = CaptureScreen()) // Your existing screenshot method { // Lock the bitmap to get raw pixel data BitmapData bmpData = bitmap.LockBits( new Rectangle(0, 0, frameWidth, frameHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); // Use 24-bit to reduce memory vs 32-bit byte[] pixelData = new byte[bmpData.Stride * frameHeight]; Marshal.Copy(bmpData.Scan0, pixelData, 0, pixelData.Length); rawFrameData.Add(pixelData); bitmap.UnlockBits(bmpData); } // When saving the video: using (VideoFileWriter videoWriter = new VideoFileWriter()) { videoWriter.BitRate = 5000000; videoWriter.FrameRate = 30; videoWriter.Width = frameWidth; videoWriter.Height = frameHeight; videoWriter.VideoCodec = VideoCodec.H264; videoWriter.VideoOptions["crf"] = "18"; videoWriter.VideoOptions["preset"] = "veryfast"; videoWriter.VideoOptions["tune"] = "zerolatency"; videoWriter.Open("output.avi"); foreach (byte[] pixelData in rawFrameData) { using (Bitmap frame = new Bitmap(frameWidth, frameHeight, PixelFormat.Format24bppRgb)) { BitmapData bmpData = frame.LockBits( new Rectangle(0, 0, frameWidth, frameHeight), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); Marshal.Copy(pixelData, 0, bmpData.Scan0, pixelData.Length); frame.UnlockBits(bmpData); videoWriter.WriteVideoFrame(frame); } } }
Note: This cuts out two conversion steps, but storing raw pixel data still uses a lot of memory (1080p 24-bit = ~6MB per frame; 60s at 30fps = ~10.8GB). For longer recordings or higher resolutions, this isn’t feasible—so let’s move to the better solution.
2. Encode Directly to Memory (FFmpeg-Based High Efficiency)
The most efficient approach is to encode video frames directly into a memory buffer as you capture them, instead of storing uncompressed frames. This uses far less memory (compressed H.264 video is ~5-20MB per minute for 1080p) and eliminates all redundant conversions.
We’ll use FFmpeg.AutoGen (a .NET wrapper for FFmpeg) to handle in-memory encoding. Here’s a simplified implementation:
Step 1: Initialize FFmpeg and Configure Encoding
// Register FFmpeg binaries (make sure they're in your project) FFmpegBinariesHelper.RegisterFFmpegBinaries(); ffmpeg.av_register_all(); ffmpeg.avcodec_register_all(); ffmpeg.avformat_network_init(); // Memory buffer to store encoded video data (use a ring buffer for 60s retention) var encodedFramesBuffer = new ConcurrentQueue<byte[]>(); long totalDurationMs = 0; const int targetDurationMs = 60000; // 60 seconds // Create output format context (MP4 container) AVFormatContext* formatContext = null; ffmpeg.avformat_alloc_output_context2(&formatContext, null, "mp4", null); // Custom IO callback to write encoded data to memory instead of file var ioBuffer = (byte*)ffmpeg.av_malloc(4096); var ioContext = ffmpeg.avio_alloc_context( ioBuffer, 4096, 1, // Write mode IntPtr.Zero, null, (opaque, buf, bufSize) => { byte[] data = new byte[bufSize]; Marshal.Copy((IntPtr)buf, data, 0, bufSize); encodedFramesBuffer.Enqueue(data); // Track duration and trim old frames if needed (simplified example) // You'll need to calculate packet duration more accurately using PTS/DTS totalDurationMs += (int)(1000 / 30); // Assume 30fps while (totalDurationMs > targetDurationMs && encodedFramesBuffer.TryDequeue(out _)) { totalDurationMs -= (int)(1000 / 30); } return bufSize; }, null); formatContext->pb = ioContext; // Configure video stream and H.264 encoder AVStream* videoStream = ffmpeg.avformat_new_stream(formatContext, null); AVCodec* codec = ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264); AVCodecContext* codecContext = ffmpeg.avcodec_alloc_context3(codec); codecContext->width = 1920; codecContext->height = 1080; codecContext->time_base = new AVRational { num = 1, den = 30 }; codecContext->framerate = new AVRational { num = 30, den = 1 }; codecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P; codecContext->bit_rate = 5000000; // 5Mbps // Set H.264 options for speed and quality ffmpeg.av_opt_set(codecContext->priv_data, "crf", "18", 0); // Visually lossless ffmpeg.av_opt_set(codecContext->priv_data, "preset", "veryfast", 0); ffmpeg.av_opt_set(codecContext->priv_data, "tune", "zerolatency", 0); // Initialize encoder ffmpeg.avcodec_open2(codecContext, codec, null); ffmpeg.avcodec_parameters_from_context(videoStream->codecpar, codecContext); ffmpeg.avformat_write_header(formatContext, null);
Step 2: Capture and Encode Frames
long frameCount = 0; var swsContext = ffmpeg.sws_getContext( 1920, 1080, AVPixelFormat.AV_PIX_FMT_BGRA, // Match your screenshot format 1920, 1080, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BILINEAR, null, null, null); // In your capture loop: using (Bitmap bitmap = CaptureScreen()) { // Convert Bitmap to FFmpeg's YUV420P format AVFrame* frame = ffmpeg.av_frame_alloc(); frame->width = codecContext->width; frame->height = codecContext->height; frame->format = (int)codecContext->pix_fmt; ffmpeg.av_frame_get_buffer(frame, 32); BitmapData bmpData = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); IntPtr[] srcSlices = new IntPtr[] { bmpData.Scan0 }; int[] srcStrides = new int[] { bmpData.Stride }; ffmpeg.sws_scale(swsContext, srcSlices, srcStrides, 0, bitmap.Height, frame->data, frame->linesize); bitmap.UnlockBits(bmpData); // Encode the frame frame->pts = ffmpeg.av_rescale_q(frameCount, codecContext->time_base, videoStream->time_base); frameCount++; AVPacket packet = new AVPacket(); ffmpeg.av_init_packet(&packet); if (ffmpeg.avcodec_send_frame(codecContext, frame) >= 0) { while (ffmpeg.avcodec_receive_packet(codecContext, &packet) >= 0) { ffmpeg.av_interleaved_write_frame(formatContext, &packet); ffmpeg.av_packet_unref(&packet); } } ffmpeg.av_frame_free(&frame); }
Step 3: Save the Encoded Video to File
When your trigger event fires (e.g., button click):
// Write video trailer to complete the MP4 file ffmpeg.av_write_trailer(formatContext); // Flush the memory buffer to disk using (var fs = new FileStream("output.mp4", FileMode.Create)) { while (encodedFramesBuffer.TryDequeue(out byte[] data)) { fs.Write(data, 0, data.Length); } } // Clean up FFmpeg resources ffmpeg.avcodec_close(codecContext); ffmpeg.avformat_free_context(formatContext); ffmpeg.avio_context_free(&ioContext); ffmpeg.sws_freeContext(swsContext);
3. Alternative: Windows Media Foundation (For Native Windows Performance)
If you prefer using Windows-native APIs, Media Foundation supports in-memory media streams. You’d create a custom IMFMediaSink that writes encoded data to a memory buffer, then feed captured screen frames into an H.264 encoder. This avoids external dependencies like FFmpeg, but has a steeper learning curve.
Key Takeaways
- Avoid redundant conversions: Never encode frames to JPEG if you’re just going to decode them again for video.
- Store compressed data: Encoding directly to memory reduces memory usage drastically compared to uncompressed frames.
- Ring buffer for retention: Track the duration of your encoded buffer and trim old data to keep only the last 60 seconds.
内容的提问来源于stack exchange,提问作者Jirka Picek

