Skip to content

Instantly share code, notes, and snippets.

@folkevil
Forked from DashW/ScreenRecorder.cs
Created August 23, 2019 02:34
Show Gist options
  • Save folkevil/defb0ed4cb8cebdd579d56738b85ba90 to your computer and use it in GitHub Desktop.
Save folkevil/defb0ed4cb8cebdd579d56738b85ba90 to your computer and use it in GitHub Desktop.

Revisions

  1. @DashW DashW revised this gist Dec 29, 2015. No changes.
  2. @DashW DashW revised this gist Dec 29, 2015. No changes.
  3. @DashW DashW revised this gist Dec 29, 2015. No changes.
  4. @DashW DashW revised this gist Dec 29, 2015. No changes.
  5. @DashW DashW created this gist Dec 29, 2015.
    231 changes: 231 additions & 0 deletions ScreenRecorder.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,231 @@
    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");
    }
    }