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");
}
}
}