Instantly share code, notes, and snippets.

Embed
What would you like to do?
ScreenRecorder - High Performance Unity Video Capture Script
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
class BitmapEncoder
{
public static void WriteBitmap(Stream stream, int width, int height, byte[] imageData)
{
using (BinaryWriter bw = new BinaryWriter(stream)) {
// define the bitmap file header
bw.Write ((UInt16)0x4D42); // bfType;
bw.Write ((UInt32)(14 + 40 + (width * height * 4))); // bfSize;
bw.Write ((UInt16)0); // bfReserved1;
bw.Write ((UInt16)0); // bfReserved2;
bw.Write ((UInt32)14 + 40); // bfOffBits;
// define the bitmap information header
bw.Write ((UInt32)40); // biSize;
bw.Write ((Int32)width); // biWidth;
bw.Write ((Int32)height); // biHeight;
bw.Write ((UInt16)1); // biPlanes;
bw.Write ((UInt16)32); // biBitCount;
bw.Write ((UInt32)0); // biCompression;
bw.Write ((UInt32)(width * height * 4)); // biSizeImage;
bw.Write ((Int32)0); // biXPelsPerMeter;
bw.Write ((Int32)0); // biYPelsPerMeter;
bw.Write ((UInt32)0); // biClrUsed;
bw.Write ((UInt32)0); // biClrImportant;
// switch the image data from RGB to BGR
for (int imageIdx = 0; imageIdx < imageData.Length; imageIdx += 3) {
bw.Write(imageData[imageIdx + 2]);
bw.Write(imageData[imageIdx + 1]);
bw.Write(imageData[imageIdx + 0]);
bw.Write((byte)255);
}
}
}
}
/// <summary>
/// Captures frames from a Unity camera in real time
/// and writes them to disk using a background thread.
/// </summary>
///
/// <description>
/// Maximises speed and quality by reading-back raw
/// texture data with no conversion and writing
/// frames in uncompressed BMP format.
/// Created by Richard Copperwaite.
/// </description>
///
[RequireComponent(typeof(Camera))]
public class ScreenRecorder : MonoBehaviour
{
// Public Properties
public int maxFrames; // maximum number of frames you want to record in one video
public int frameRate = 30; // number of frames to capture per second
// The Encoder Thread
private Thread encoderThread;
// Texture Readback Objects
private RenderTexture tempRenderTexture;
private Texture2D tempTexture2D;
// Timing Data
private float captureFrameTime;
private float lastFrameTime;
private int frameNumber;
private int savingFrameNumber;
// Encoder Thread Shared Resources
private Queue<byte[]> frameQueue;
private string persistentDataPath;
private int screenWidth;
private int screenHeight;
private bool threadIsProcessing;
private bool terminateThreadWhenDone;
void Start ()
{
// Set target frame rate (optional)
Application.targetFrameRate = frameRate;
// Prepare the data directory
persistentDataPath = Application.persistentDataPath + "/ScreenRecorder";
print ("Capturing to: " + persistentDataPath + "/");
if (!System.IO.Directory.Exists(persistentDataPath))
{
System.IO.Directory.CreateDirectory(persistentDataPath);
}
// Prepare textures and initial values
screenWidth = GetComponent<Camera>().pixelWidth;
screenHeight = GetComponent<Camera>().pixelHeight;
tempRenderTexture = new RenderTexture(screenWidth, screenHeight, 0);
tempTexture2D = new Texture2D(screenWidth, screenHeight, TextureFormat.RGB24, false);
frameQueue = new Queue<byte[]> ();
frameNumber = 0;
savingFrameNumber = 0;
captureFrameTime = 1.0f / (float)frameRate;
lastFrameTime = Time.time;
// Kill the encoder thread if running from a previous execution
if (encoderThread != null && (threadIsProcessing || encoderThread.IsAlive)) {
threadIsProcessing = false;
encoderThread.Join();
}
// Start a new encoder thread
threadIsProcessing = true;
encoderThread = new Thread (EncodeAndSave);
encoderThread.Start ();
}
void OnDisable()
{
// Reset target frame rate
Application.targetFrameRate = -1;
// Inform thread to terminate when finished processing frames
terminateThreadWhenDone = true;
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (frameNumber <= maxFrames)
{
// Check if render target size has changed, if so, terminate
if(source.width != screenWidth || source.height != screenHeight)
{
threadIsProcessing = false;
this.enabled = false;
throw new UnityException("ScreenRecorder render target size has changed!");
}
// Calculate number of video frames to produce from this game frame
// Generate 'padding' frames if desired framerate is higher than actual framerate
float thisFrameTime = Time.time;
int framesToCapture = ((int)(thisFrameTime / captureFrameTime)) - ((int)(lastFrameTime / captureFrameTime));
// Capture the frame
if(framesToCapture > 0)
{
Graphics.Blit (source, tempRenderTexture);
RenderTexture.active = tempRenderTexture;
tempTexture2D.ReadPixels(new Rect(0, 0, Screen.width, Screen.height),0,0);
RenderTexture.active = null;
}
// Add the required number of copies to the queue
for(int i = 0; i < framesToCapture && frameNumber <= maxFrames; ++i)
{
frameQueue.Enqueue(tempTexture2D.GetRawTextureData());
frameNumber ++;
if(frameNumber % frameRate == 0)
{
print ("Frame " + frameNumber);
}
}
lastFrameTime = thisFrameTime;
}
else //keep making screenshots until it reaches the max frame amount
{
// Inform thread to terminate when finished processing frames
terminateThreadWhenDone = true;
// Disable script
this.enabled = false;
}
// Passthrough
Graphics.Blit (source, destination);
}
private void EncodeAndSave()
{
print ("SCREENRECORDER IO THREAD STARTED");
while (threadIsProcessing)
{
if(frameQueue.Count > 0)
{
// Generate file path
string path = persistentDataPath + "/frame" + savingFrameNumber + ".bmp";
// Dequeue the frame, encode it as a bitmap, and write it to the file
using(FileStream fileStream = new FileStream(path, FileMode.Create))
{
BitmapEncoder.WriteBitmap(fileStream, screenWidth, screenHeight, frameQueue.Dequeue());
fileStream.Close();
}
// Done
savingFrameNumber ++;
print ("Saved " + savingFrameNumber + " frames. " + frameQueue.Count + " frames remaining.");
}
else
{
if(terminateThreadWhenDone)
{
break;
}
Thread.Sleep(1);
}
}
terminateThreadWhenDone = false;
threadIsProcessing = false;
print ("SCREENRECORDER IO THREAD FINISHED");
}
}
@zhangyu0525

This comment has been minimized.

zhangyu0525 commented May 11, 2018

i wanna know how to use this script

@enquel

This comment has been minimized.

enquel commented May 18, 2018

simple and beautiful, works like a charm! You need to comment a part responsible for render texture change detection to make it work.

@zephyo

This comment has been minimized.

zephyo commented May 19, 2018

amazing and fast, thanks so much!! May I ask, how do you encode it to a video format?

edit: couldn't find how to convert it to mp4, but was able to use it to make gifs in-game! ^_^

@ir6is

This comment has been minimized.

ir6is commented Jun 8, 2018

how did you create the gifs in-game?
"You need to comment a part responsible for render texture change detection to make it work.", where is that code?

@nmnsud

This comment has been minimized.

nmnsud commented Jul 9, 2018

It works perfectly! Though I need to convert it into single GIF and make it shareable through android's share intent. I can't figure it out, any help?

@nikosurfing

This comment has been minimized.

nikosurfing commented Oct 3, 2018

How to use this code? Anyone please?

@Hunter1pro

This comment has been minimized.

Hunter1pro commented Oct 23, 2018

i wanna know how to use this script

amazing and fast, thanks so much!! May I ask, how do you encode it to a video format?

edit: couldn't find how to convert it to mp4, but was able to use it to make gifs in-game! ^_^

how did you create the gifs in-game?
"You need to comment a part responsible for render texture change detection to make it work.", where is that code?

It works perfectly! Though I need to convert it into single GIF and make it shareable through android's share intent. I can't figure it out, any help?

How to use this code? Anyone please?

Fast Answer for main quastion, for make video you need to use library convertaion or write own :D - good way use http://www.aforgenet.com/framework/docs/html/4ee1742c-44d3-b250-d6aa-90cd2d606611.htm
Base Advenced Example https://stackoverflow.com/questions/9744026/image-sequence-to-video-stream
Unity Simple Example https://stackoverflow.com/questions/45677275/converting-texture2d-into-a-video

Also how its work currently Need to Be on The Camera and Enabled, (also set max frames - for example (rate 30, max frames 60 its 2 seconds of record) Then "Update" Like ~ when ScreenRendered, callback called to OnRenderImage (https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnRenderImage.html) like one of Base Unity Function (like Update or FixedUpdate, Awake, Enabled end etc... https://docs.unity3d.com/ScriptReference/MonoBehaviour.html )

@Soorya-S

This comment has been minimized.

Soorya-S commented Dec 5, 2018

not working. I have set max frames to 60. It creates the folder but not working. It stops at line if (frameQueue.Count > 0) under EncodeAndSave() function.
and also OnRenderImage(RenderTexture source, RenderTexture destination) is not called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment