2
\$\begingroup\$

I'm developing a screen recording as an application log. The recording should capture a full HD screen at 25fps and on request it should save last 60 seconds of record. It is for embedded app, but the app is built for 32 bit and during development the recording app threw OutOfMemory exception during video generating when I used ImageFormat.Png.

I solved this problem - I forgot to dispose bitmap created during looping list of images. But it should work with rest of application (not well written). Is there a way to optimize memory usage of the recording?

I found a template using array but I changed array to list since it is easier to work with a list than with an array for me. Is it a problem in this case? Is it consuming more memory or CPU to add/remove item to the list instead of array?

Recorder:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace ScreenRecord
{
 public class ScreenRecorder
 {
  private Rectangle bounds = new Rectangle(0, 0, 1920, 1080);
  private Timer fpsTimer = new Timer();
  
  private List<Tuple<byte[], DateTime>> Buffer = new List<Tuple<byte[], DateTime>>();
  private int BufferLengthSeconds = 60;

  public List<Tuple<byte[], DateTime>> SaveVideoLog() {
   List<Tuple<byte[], DateTime>> tempBuffer = new List<Tuple<byte[], DateTime>>(Buffer);
   Buffer.Clear();
   return tempBuffer;
  }

  private void AddBitmap(Bitmap bitmap) {
   try {
    DateTime currentTime = DateTime.Now;
    DateTime minTime = currentTime.AddSeconds(- BufferLengthSeconds);
    
    //Buffer.RemoveAll(frame => frame.Item2 < minTime);
    for (int i = 0; i < Buffer.Count; i++) {
     if (Buffer.First().Item2 < minTime)
      Buffer.RemoveAt(0);
     else
      break;
    }
    
    using (MemoryStream ms = new MemoryStream()) {
     bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
     Buffer.Add(Tuple.Create(ms.ToArray(), currentTime));
    }
   }
   catch (Exception ex) {
    Console.WriteLine(ex.Message);
   }
  }
  
  public void RecordVideo()
  {
   using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
   {
    using (Graphics g = Graphics.FromImage(bitmap))
    {
     //Add screen to bitmap:
     g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
    }
    //Save screenshot:
    AddBitmap(bitmap);
   }
  }
  
  public ScreenRecorder()
  {
   fpsTimer.Interval = 40;
   fpsTimer.Start();
   
   fpsTimer.Tick += new EventHandler(timer1_Tick);
  }
  
  private void timer1_Tick(object sender, EventArgs e)
  {
   RecordVideo();
  }
 }
}

Video coder:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using Accord.Video.FFMPEG;


namespace ScreenRecord
{
 public static class VideoConverter
 {
  public static void ConvertToVideoAndSave(List<Tuple<byte[], DateTime>> source, string destinationPath) {
   if (!source.Any())
    return;
   
   using (MemoryStream ms = new MemoryStream(source.First().Item1)) {
    Image frame = Image.FromStream(ms);
    ConvertToVideoAndSave(source, destinationPath, frame.Width, frame.Height);
   }
  }
  
  public static void ConvertToVideoAndSave(List<Tuple<byte[], DateTime>> source, string destinationPath, int width, int height) {
   ConvertToVideoAndSave(source, destinationPath, width, height, 25);
  }
  
  public static void ConvertToVideoAndSave(List<Tuple<byte[], DateTime>> source, string destinationPath, int width, int height, int framerate) {
   ConvertToVideoAndSave(source, destinationPath, width, height, framerate, VideoCodec.H264);
  }
  
  public static void ConvertToVideoAndSave(List<Tuple<byte[], DateTime>> source, string destinationPath, int width, int height, int framerate, VideoCodec codecs) {
   if (!source.Any()) {
    return;
   }

   using (VideoFileWriter writer = new VideoFileWriter()) {
    writer.Open(destinationPath, width, height, framerate, codecs);

    DateTime startTime = source.First().Item2;

    foreach (Tuple<byte[], DateTime> frame in source) {
     using (MemoryStream ms = new MemoryStream(frame.Item1))
     using (Bitmap videoFrame = new Bitmap(ms))
      writer.WriteVideoFrame(videoFrame, frame.Item2 - startTime);
    }

    writer.Close();
   }
  }
 }
}

Main form with two buttons:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace ScreenRecord
{
 public partial class MainForm : Form
 {
  ScreenRecorder rec;
  
  public MainForm()
  {
   InitializeComponent();
  }
  void Button1Click(object sender, EventArgs e)
  {
   rec = new ScreenRecorder();
  }
  void Button2Click(object sender, EventArgs e)
  {
   VideoConverter.ConvertToVideoAndSave( rec.SaveVideoLog(), "D:\\Test.mp4");
  }
 }
}
\$\endgroup\$
1
\$\begingroup\$

I don't think this is the best way of capturing the screen, but I'll leave it to someone with more experience in the area to comment on that. What I will comment on are rather obvious performance issues assuming the overall code stays as it is:

  • List<Tuple<byte[], DateTime>> tempBuffer = new List<Tuple<byte[], DateTime>>(Buffer)

    This copies the list for no obvious reason. The only reason I can think of is to decouple it from an async reader, but by the looks of it everything is sync here. And even so, you can just switch the references without copying everything.

  • Use UtcNow instead of Now, it is both more performant and makes more sense

  • Buffer.RemoveAt(0); shifts the list to the left, so your for cycle is O(n^2)

  • ms.ToArray() copies the entire memory stream.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you, I think I improved all the mentioned points. I'm not just sure with the Buffer.RemoveAt(0) part. I changed it to Buffer.RemoveAll (just commented the for loop and uncomented the line before). My thought was that it would remove max two items from beginning so why to check whole list but load seems equal and it's safer when app gets stuck for a while. If you know better way to capture the screen please share source where I could learn it. If I won't use it here then for future use. I am new in the area of images and video so everyone is better than me \$\endgroup\$ Aug 4 at 7:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.