Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Screen Recorder Android Plugin

Discussion in 'Android' started by knighthedspi, Nov 26, 2013.

  1. knighthedspi

    knighthedspi

    Joined:
    Nov 26, 2013
    Posts:
    1
    I'm developing an Unity-Android Plugin to record game screen and create a mp4 video file.I follow to Android Breakout game recorder patch sample in this site : http://bigflake.com/mediacodec/
    First, I create my CustomUnityPlayer class that extends UnityPlayer class and override onDrawFrame method.Here is my CustomUnityPlayer class code :

    Code (csharp):
    1.  
    2.     package com.example.screenrecorder;
    3.     import javax.microedition.khronos.egl.EGLConfig;
    4.     import javax.microedition.khronos.opengles.GL10;
    5.     import android.content.ContextWrapper;
    6.     import android.opengl.EGL14;
    7.     import android.opengl.EGLContext;
    8.     import android.opengl.EGLDisplay;
    9.     import android.opengl.EGLSurface;
    10.     import android.opengl.GLES20;
    11.     import android.opengl.GLSurfaceView;
    12.     import android.opengl.Matrix;
    13.     import android.util.Log;
    14.  
    15.  
    16.     import com.unity3d.player.*;
    17.  
    18.     public class CustomUnityPlayer extends UnityPlayer implements GLSurfaceView.Renderer {
    19.  
    20.     public static final String TAG = "ScreenRecord";
    21.     public static final boolean EXTRA_CHECK = true;         // enable additional assertions
    22.     private GameRecorder recorder;
    23.     static final float mProjectionMatrix[] = new float[16];
    24.     private final float mSavedMatrix[] = new float[16];
    25.     private EGLDisplay mSavedEglDisplay;
    26.     private EGLSurface mSavedEglDrawSurface;
    27.     private EGLSurface mSavedEglReadSurface;
    28.     private EGLContext mSavedEglContext;
    29.    
    30.     // Frame counter, used for reducing recorder frame rate.
    31.     private int mFrameCount;
    32.    
    33.     static final float ARENA_WIDTH = 768.0f;
    34.     static final float ARENA_HEIGHT = 1024.0f;
    35.    
    36.     private int mViewportWidth, mViewportHeight;
    37.     private int mViewportXoff, mViewportYoff;
    38.    
    39.    
    40.  
    41.     private final float[] mViewMatrix = new float[16];
    42.     private final float[] mRotationMatrix = new float[16];
    43.     private float mAngle;
    44.    
    45.     public CustomUnityPlayer(ContextWrapper context) {
    46.         // TODO Auto-generated constructor stub
    47.         super(context);
    48.         this.recorder = GameRecorder.getInstance();
    49.     }
    50.    
    51.      private boolean recordThisFrame() {
    52.             final int TARGET_FPS = 30;
    53.  
    54.             mFrameCount ++;
    55.             switch (TARGET_FPS) {
    56.             case 60:
    57.                 return true;
    58.             case 30:
    59.                 return (mFrameCount  0x01) == 0;
    60.             case 24:
    61.                 // want 2 out of every 5 frames
    62.                 int mod = mFrameCount % 5;
    63.                 return mod == 0 || mod == 2;
    64.             default:
    65.                 return true;
    66.             }
    67.         }
    68.          
    69.     public void onDrawFrame(GL10 gl){
    70.    
    71.         //record this frame
    72.         if (this.recorder.isRecording()  this.recordThisFrame()) { 
    73.            
    74.             saveRenderState();
    75.            
    76.             // switch to recorder state
    77.             this.recorder.makeCurrent();
    78.             super.onDrawFrame(gl);
    79.             this.recorder.getProjectionMatrix(mProjectionMatrix);
    80.             this.recorder.setViewport();
    81.            
    82.             this.recorder.swapBuffers();    
    83.            
    84.             restoreRenderState();
    85.         }
    86.     }
    87.        
    88.     public void onSurfaceCreated(GL10 paramGL10, EGLConfig paramEGLConfig){
    89.         // now repeat it for the game recorder
    90.         if (this.recorder.isRecording()) {
    91.             Log.d(TAG, "configuring GL for recorder");
    92.             saveRenderState();
    93.             this.recorder.firstTimeSetup();
    94.             super.onSurfaceCreated(paramGL10, paramEGLConfig);
    95.             this.recorder.makeCurrent();
    96.             //glSetup();
    97.             restoreRenderState();
    98.  
    99.             mFrameCount = 0;
    100.         }
    101.    
    102.         if (EXTRA_CHECK) Util.checkGlError("onSurfaceCreated end");
    103.     }
    104.    
    105.     public void onSurfaceChanged(GL10 unused, int width, int height) {
    106.         /*
    107.          * We want the viewport to be proportional to the arena size.  That way a 10x10
    108.          * object in arena coordinates will look square on the screen, and our round ball
    109.          * will look round.
    110.          *
    111.          * If we wanted to fill the entire screen with our game, we would want to adjust the
    112.          * size of the arena itself, not just stretch it to fit the boundaries.  This can have
    113.          * subtle effects on gameplay, e.g. the time it takes the ball to travel from the top
    114.          * to the bottom of the screen will be different on a device with a 16:9 display than on
    115.          * a 4:3 display.  Other games might address this differently, e.g. a side-scroller
    116.          * could display a bit more of the level on the left and right.
    117.          *
    118.          * We do want to fill as much space as we can, so we should either be pressed up against
    119.          * the left/right edges or top/bottom.
    120.          *
    121.          * Our game plays best in portrait mode.  We could force the app to run in portrait
    122.          * mode (by setting a value in AndroidManifest, or by setting the projection to rotate
    123.          * the world to match the longest screen dimension), but that's annoying, especially
    124.          * on devices that don't rotate easily (e.g. plasma TVs).
    125.          */
    126.  
    127.         super.onSurfaceChanged(unused, width, height);
    128.         if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged start");
    129.  
    130.         float arenaRatio = ARENA_HEIGHT / ARENA_WIDTH;
    131.         int x, y, viewWidth, viewHeight;
    132.  
    133.         if (height > (int) (width * arenaRatio)) {
    134.             // limited by narrow width; restrict height
    135.             viewWidth = width;
    136.             viewHeight = (int) (width * arenaRatio);
    137.         } else {
    138.             // limited by short height; restrict width
    139.             viewHeight = height;
    140.             viewWidth = (int) (height / arenaRatio);
    141.         }
    142.         x = (width - viewWidth) / 2;
    143.         y = (height - viewHeight) / 2;
    144.  
    145.         Log.d(TAG, "onSurfaceChanged w=" + width + " h=" + height);
    146.         Log.d(TAG, " --> x=" + x + " y=" + y + " gw=" + viewWidth + " gh=" + viewHeight);
    147.  
    148.         GLES20.glViewport(x, y, viewWidth, viewHeight);
    149.  
    150.         mViewportXoff = x;
    151.         mViewportYoff = y;
    152.         mViewportWidth = viewWidth;
    153.         mViewportHeight = viewHeight;
    154.        
    155.        
    156.         // Create an orthographic projection that maps the desired arena size to the viewport
    157.         // dimensions.
    158.         //
    159.         // If we reversed {0, ARENA_HEIGHT} to {ARENA_HEIGHT, 0}, we'd have (0,0) in the
    160.         // upper-left corner instead of the bottom left, which is more familiar for 2D
    161.         // graphics work.  It might cause brain ache if we want to mix in 3D elements though.
    162.         Matrix.orthoM(mProjectionMatrix, 0,  0, ARENA_WIDTH,
    163.                 0, ARENA_HEIGHT,  -1, 1);
    164.  
    165.         Log.d(TAG, "onSurfaceChangedEnd 1 w=" + width + " h=" + height);
    166.        
    167.         if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged end");
    168.         Log.d(TAG, "onSurfaceEnded w=" + width + " h=" + height);
    169.     }
    170.  
    171.    
    172.     public void pause(){
    173.         super.pause();
    174.         this.recorder.gamePaused();
    175.     }
    176.    
    177.    
    178.    
    179.     /**
    180.      * Saves the current projection matrix and EGL state.
    181.      */
    182.     public void saveRenderState() {
    183.         System.arraycopy(mProjectionMatrix, 0, mSavedMatrix, 0, mProjectionMatrix.length);
    184.         mSavedEglDisplay = EGL14.eglGetCurrentDisplay();
    185.         mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
    186.         mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
    187.         mSavedEglContext = EGL14.eglGetCurrentContext();
    188.     }
    189.  
    190.     /**
    191.      * Saves the current projection matrix and EGL state.
    192.      */
    193.     public void restoreRenderState() {
    194.         // switch back to previous state
    195.         if (!EGL14.eglMakeCurrent(mSavedEglDisplay, mSavedEglDrawSurface, mSavedEglReadSurface,
    196.                 mSavedEglContext)) {
    197.             throw new RuntimeException("eglMakeCurrent failed");
    198.         }
    199.         System.arraycopy(mSavedMatrix, 0, mProjectionMatrix, 0, mProjectionMatrix.length);
    200.     }
    201.     }
    202.  
    And then, i create a CustomUnityPlayerActivity to call this class


    Code (csharp):
    1.  
    2.     package com.example.screenrecorder;
    3.  
    4.     import android.content.res.Configuration;
    5.     import android.os.Bundle;
    6.     import android.util.Log;
    7.     import android.view.KeyEvent;
    8.     import android.view.View;
    9.     import android.view.Window;
    10.  
    11.     import com.unity3d.player.UnityPlayerActivity;
    12.  
    13.     public class CustomUnityActivity extends UnityPlayerActivity {
    14.  
    15.     private CustomUnityPlayer mUnityPlayer;
    16.     private GameRecorder mRecorder;
    17.  
    18.     @Override
    19.     protected void onCreate(Bundle paramBundle){
    20.         Log.e("ScreenRecord","oncreate");
    21.         requestWindowFeature(Window.FEATURE_NO_TITLE);
    22.         super.onCreate(paramBundle);
    23.         this.mUnityPlayer = new CustomUnityPlayer(this);
    24.         if (this.mUnityPlayer.getSettings().getBoolean("hide_status_bar", true))
    25.           getWindow().setFlags(1024, 1024);
    26.        
    27.         int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
    28.         boolean trueColor8888 = false;
    29.         mUnityPlayer.init(glesMode, trueColor8888);
    30.        
    31.         View playerView = mUnityPlayer.getView();
    32.         setContentView(playerView);
    33.         playerView.requestFocus();
    34.                
    35.         this.mRecorder = GameRecorder.getInstance();
    36.         this.mRecorder.prepareEncoder(this);
    37.     }
    38.    
    39.     public void beginRecord(){
    40.         Log.e("ScreenRecord","start record");
    41.        
    42.        
    43.         this.mUnityPlayer.saveRenderState();
    44.         this.mRecorder.firstTimeSetup();
    45.         this.mRecorder.setStartRecord(true);
    46.         this.mRecorder.makeCurrent();
    47.         this.mUnityPlayer.restoreRenderState();
    48.     }
    49.    
    50.     public void endRecord(){
    51.         Log.e("ScreenRecord","end record");
    52.         this.mRecorder.endRecord();
    53.         this.mRecorder.setStartRecord(false);
    54.         //this.mTransView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    55.     }
    56.    
    57.     public boolean isRecording(){
    58.         return this.mRecorder.isRecording();
    59.     }
    60.    
    61.     protected void onDestroy()
    62.       {
    63.         super.onDestroy();
    64.         this.mUnityPlayer.quit();
    65.       }
    66.  
    67.       protected void onPause()
    68.       {
    69.         super.onPause();
    70.         this.mUnityPlayer.pause();
    71.       }
    72.  
    73.       protected void onResume()
    74.       {
    75.         super.onResume();
    76.         this.mUnityPlayer.resume();
    77.       }
    78.  
    79.       public void onConfigurationChanged(Configuration paramConfiguration)
    80.       {
    81.         super.onConfigurationChanged(paramConfiguration);
    82.         this.mUnityPlayer.configurationChanged(paramConfiguration);
    83.       }
    84.  
    85.       public void onWindowFocusChanged(boolean paramBoolean)
    86.       {
    87.         super.onWindowFocusChanged(paramBoolean);
    88.         this.mUnityPlayer.windowFocusChanged(paramBoolean);
    89.       }
    90.  
    91.       public boolean onKeyDown(int paramInt, KeyEvent paramKeyEvent)
    92.       {
    93.         return this.mUnityPlayer.onKeyDown(paramInt, paramKeyEvent);
    94.       }
    95.  
    96.       public boolean onKeyUp(int paramInt, KeyEvent paramKeyEvent)
    97.       {
    98.         return this.mUnityPlayer.onKeyUp(paramInt, paramKeyEvent);
    99.       }
    100.     }
    101.  
    My problem is that a video file is created successful but my game can't render anything.I read on http://bigflake.com/mediacodec/ site and recognize that each frame would be render twice (once for the display, once for the video) but I can't do this in Unity.Whenever I try to call super.onDrawFrame(gl) twice in onDrawFrame method , my game will be crashed.

    Any solution for my problem? Any help will be greatly appreciated!

    Thanks and best regard!

    Huy Tran
     
    Last edited: Nov 26, 2013
  2. tuanzi88

    tuanzi88

    Joined:
    Sep 10, 2015
    Posts:
    19
    Hi Knighthedspi,

    Have you found solution or got any other way to record the video especially in Android? I am currently looking for a way to record video in Android system. I will be very appreciated if you can share your wise knowledge. Thanks!
     
  3. Rbhaniwal

    Rbhaniwal

    Joined:
    Jan 23, 2015
    Posts:
    33
    Hello everyone,
    Me too try to record video in unity android.
    have you got any solution for this problem ?
     
  4. Archviz3d

    Archviz3d

    Joined:
    Apr 4, 2016
    Posts:
    92
    Yeah!! Me too!! It would be awsome! Did u get a solution? Or anyone?