2013-11-26 16 views
5

Sto sviluppando un plug-in Unity-Android per registrare lo schermo di gioco e creare un file video mp4. Seguo l'esempio di patch per registratore di giochi Android Breakout in questo sito: http://bigflake.com/mediacodec/.
In primo luogo, creare la mia classe CustomUnityPlayer che si estende classe UnityPlayer e sovrascrivere onDrawFrame method.Here è il mio codice di classe CustomUnityPlayer:Screen Recorder Plugin Android in Unity

package com.example.screenrecorder; 
import javax.microedition.khronos.egl.EGLConfig; 
import javax.microedition.khronos.opengles.GL10; 
import android.content.ContextWrapper; 
import android.opengl.EGL14; 
import android.opengl.EGLContext; 
import android.opengl.EGLDisplay; 
import android.opengl.EGLSurface; 
import android.opengl.GLES20; 
import android.opengl.GLSurfaceView; 
import android.opengl.Matrix; 
import android.util.Log; 


import com.unity3d.player.*; 

public class CustomUnityPlayer extends UnityPlayer implements GLSurfaceView.Renderer { 

public static final String TAG = "ScreenRecord"; 
public static final boolean EXTRA_CHECK = true;   // enable additional assertions 
private GameRecorder recorder; 
static final float mProjectionMatrix[] = new float[16]; 
private final float mSavedMatrix[] = new float[16]; 
private EGLDisplay mSavedEglDisplay; 
private EGLSurface mSavedEglDrawSurface; 
private EGLSurface mSavedEglReadSurface; 
private EGLContext mSavedEglContext; 

// Frame counter, used for reducing recorder frame rate. 
private int mFrameCount; 

static final float ARENA_WIDTH = 768.0f; 
static final float ARENA_HEIGHT = 1024.0f; 

private int mViewportWidth, mViewportHeight; 
private int mViewportXoff, mViewportYoff; 



private final float[] mViewMatrix = new float[16]; 
private final float[] mRotationMatrix = new float[16]; 
private float mAngle; 

public CustomUnityPlayer(ContextWrapper context) { 
    // TODO Auto-generated constructor stub 
    super(context); 
    this.recorder = GameRecorder.getInstance(); 
} 

private boolean recordThisFrame() { 
     final int TARGET_FPS = 30; 

     mFrameCount ++; 
     switch (TARGET_FPS) { 
     case 60: 
      return true; 
     case 30: 
      return (mFrameCount & 0x01) == 0; 
     case 24: 
      // want 2 out of every 5 frames 
      int mod = mFrameCount % 5; 
      return mod == 0 || mod == 2; 
     default: 
      return true; 
     } 
    } 

public void onDrawFrame(GL10 gl){ 

    //record this frame 
    if (this.recorder.isRecording() && this.recordThisFrame()) {  

     saveRenderState(); 

     // switch to recorder state 
     this.recorder.makeCurrent(); 
     super.onDrawFrame(gl); 
     this.recorder.getProjectionMatrix(mProjectionMatrix); 
     this.recorder.setViewport(); 

     this.recorder.swapBuffers();  

     restoreRenderState(); 
    } 
} 

public void onSurfaceCreated(GL10 paramGL10, EGLConfig paramEGLConfig){ 
    // now repeat it for the game recorder 
    if (this.recorder.isRecording()) { 
     Log.d(TAG, "configuring GL for recorder"); 
     saveRenderState(); 
     this.recorder.firstTimeSetup(); 
     super.onSurfaceCreated(paramGL10, paramEGLConfig); 
     this.recorder.makeCurrent(); 
     //glSetup(); 
     restoreRenderState(); 

     mFrameCount = 0; 
    } 

    if (EXTRA_CHECK) Util.checkGlError("onSurfaceCreated end"); 
} 

public void onSurfaceChanged(GL10 unused, int width, int height) { 
    /* 
    * We want the viewport to be proportional to the arena size. That way a 10x10 
    * object in arena coordinates will look square on the screen, and our round ball 
    * will look round. 
    * 
    * If we wanted to fill the entire screen with our game, we would want to adjust the 
    * size of the arena itself, not just stretch it to fit the boundaries. This can have 
    * subtle effects on gameplay, e.g. the time it takes the ball to travel from the top 
    * to the bottom of the screen will be different on a device with a 16:9 display than on 
    * a 4:3 display. Other games might address this differently, e.g. a side-scroller 
    * could display a bit more of the level on the left and right. 
    * 
    * We do want to fill as much space as we can, so we should either be pressed up against 
    * the left/right edges or top/bottom. 
    * 
    * Our game plays best in portrait mode. We could force the app to run in portrait 
    * mode (by setting a value in AndroidManifest, or by setting the projection to rotate 
    * the world to match the longest screen dimension), but that's annoying, especially 
    * on devices that don't rotate easily (e.g. plasma TVs). 
    */ 

    super.onSurfaceChanged(unused, width, height); 
    if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged start"); 

    float arenaRatio = ARENA_HEIGHT/ARENA_WIDTH; 
    int x, y, viewWidth, viewHeight; 

    if (height > (int) (width * arenaRatio)) { 
     // limited by narrow width; restrict height 
     viewWidth = width; 
     viewHeight = (int) (width * arenaRatio); 
    } else { 
     // limited by short height; restrict width 
     viewHeight = height; 
     viewWidth = (int) (height/arenaRatio); 
    } 
    x = (width - viewWidth)/2; 
    y = (height - viewHeight)/2; 

    Log.d(TAG, "onSurfaceChanged w=" + width + " h=" + height); 
    Log.d(TAG, " --> x=" + x + " y=" + y + " gw=" + viewWidth + " gh=" + viewHeight); 

    GLES20.glViewport(x, y, viewWidth, viewHeight); 

    mViewportXoff = x; 
    mViewportYoff = y; 
    mViewportWidth = viewWidth; 
    mViewportHeight = viewHeight; 


    // Create an orthographic projection that maps the desired arena size to the viewport 
    // dimensions. 
    // 
    // If we reversed {0, ARENA_HEIGHT} to {ARENA_HEIGHT, 0}, we'd have (0,0) in the 
    // upper-left corner instead of the bottom left, which is more familiar for 2D 
    // graphics work. It might cause brain ache if we want to mix in 3D elements though. 
    Matrix.orthoM(mProjectionMatrix, 0, 0, ARENA_WIDTH, 
      0, ARENA_HEIGHT, -1, 1); 

    Log.d(TAG, "onSurfaceChangedEnd 1 w=" + width + " h=" + height); 

    if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged end"); 
    Log.d(TAG, "onSurfaceEnded w=" + width + " h=" + height); 
} 


public void pause(){ 
    super.pause(); 
    this.recorder.gamePaused(); 
} 



/** 
* Saves the current projection matrix and EGL state. 
*/ 
public void saveRenderState() { 
    System.arraycopy(mProjectionMatrix, 0, mSavedMatrix, 0, mProjectionMatrix.length); 
    mSavedEglDisplay = EGL14.eglGetCurrentDisplay(); 
    mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW); 
    mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ); 
    mSavedEglContext = EGL14.eglGetCurrentContext(); 
} 

/** 
* Saves the current projection matrix and EGL state. 
*/ 
public void restoreRenderState() { 
    // switch back to previous state 
    if (!EGL14.eglMakeCurrent(mSavedEglDisplay, mSavedEglDrawSurface, mSavedEglReadSurface, 
      mSavedEglContext)) { 
     throw new RuntimeException("eglMakeCurrent failed"); 
    } 
    System.arraycopy(mSavedMatrix, 0, mProjectionMatrix, 0, mProjectionMatrix.length); 
} 
} 

E poi, creo una CustomUnityPlayerActivity per chiamare questa classe

package com.example.screenrecorder; 

import android.content.res.Configuration; 
import android.os.Bundle; 
import android.util.Log; 
import android.view.KeyEvent; 
import android.view.View; 
import android.view.Window; 

import com.unity3d.player.UnityPlayerActivity; 

public class CustomUnityActivity extends UnityPlayerActivity { 

private CustomUnityPlayer mUnityPlayer; 
private GameRecorder mRecorder; 

@Override 
protected void onCreate(Bundle paramBundle){ 
    Log.e("ScreenRecord","oncreate"); 
    requestWindowFeature(Window.FEATURE_NO_TITLE); 
    super.onCreate(paramBundle); 
    this.mUnityPlayer = new CustomUnityPlayer(this); 
    if (this.mUnityPlayer.getSettings().getBoolean("hide_status_bar", true)) 
     getWindow().setFlags(1024, 1024); 

    int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1); 
    boolean trueColor8888 = false; 
    mUnityPlayer.init(glesMode, trueColor8888); 

    View playerView = mUnityPlayer.getView(); 
    setContentView(playerView); 
    playerView.requestFocus(); 

    this.mRecorder = GameRecorder.getInstance(); 
    this.mRecorder.prepareEncoder(this); 
} 

public void beginRecord(){ 
    Log.e("ScreenRecord","start record"); 


    this.mUnityPlayer.saveRenderState(); 
    this.mRecorder.firstTimeSetup(); 
    this.mRecorder.setStartRecord(true); 
    this.mRecorder.makeCurrent(); 
    this.mUnityPlayer.restoreRenderState(); 
} 

public void endRecord(){ 
    Log.e("ScreenRecord","end record"); 
    this.mRecorder.endRecord(); 
    this.mRecorder.setStartRecord(false); 
    //this.mTransView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 
} 

public boolean isRecording(){ 
    return this.mRecorder.isRecording(); 
} 

protected void onDestroy() 
    { 
    super.onDestroy(); 
    this.mUnityPlayer.quit(); 
    } 

    protected void onPause() 
    { 
    super.onPause(); 
    this.mUnityPlayer.pause(); 
    } 

    protected void onResume() 
    { 
    super.onResume(); 
    this.mUnityPlayer.resume(); 
    } 

    public void onConfigurationChanged(Configuration paramConfiguration) 
    { 
    super.onConfigurationChanged(paramConfiguration); 
    this.mUnityPlayer.configurationChanged(paramConfiguration); 
    } 

    public void onWindowFocusChanged(boolean paramBoolean) 
    { 
    super.onWindowFocusChanged(paramBoolean); 
    this.mUnityPlayer.windowFocusChanged(paramBoolean); 
    } 

    public boolean onKeyDown(int paramInt, KeyEvent paramKeyEvent) 
    { 
    return this.mUnityPlayer.onKeyDown(paramInt, paramKeyEvent); 
    } 

    public boolean onKeyUp(int paramInt, KeyEvent paramKeyEvent) 
    { 
    return this.mUnityPlayer.onKeyUp(paramInt, paramKeyEvent); 
    } 
} 

Il mio problema è che un file video è stato creato con successo ma il mio gioco non può eseguire il rendering. Ho letto sul sito Android Media Codec sample e riconosco che ogni fotogramma verrebbe visualizzato due volte (una volta per il display, una volta per il video) ma non posso questo in Unity. Ogni volta che provo a chiamare super.onDrawFrame (gl) due volte nel metodo onDrawFrame, il mio gioco verrà arrestato.

Qualche soluzione per il mio problema? Qualsiasi aiuto sarà molto apprezzato!

Grazie e cordiali saluti!

Huy Tran

+1

FWIW, ci sono due approcci di base: (1) renderizzare alcuni fotogrammi due volte (come fatto in Breakout), (2) renderizzare a un FBO offscreen e blitare due volte. A seconda della complessità della scena, uno può essere più economico dell'altro. Se stai usando GLES 3, c'è un trucco per evitare una copia nell'approccio # 2. In ogni caso, il vero trucco è l'integrazione con Unity. – fadden

+0

Grazie per la tua risposta. Ora il mio problema è l'integrazione con Unity. Puoi spiegare più chiaramente l'approccio # 2. Ho provato con l'approccio n. 1 come descritto sopra ma non ci riesco! – knighthedspi

+0

FWIW, tutti e tre gli approcci sono dimostrati in Grafika (https://github.com/google/grafika). Vedi l'attività "Registra app GL". – fadden

risposta

4

Infine ho utilizzare un FrameBufferObject (OSA) per rendere fuori campo e ottenere la sua consistenza legame blit due volte:

  • Render alla superficie del video
  • Ridisegna allo schermo del dispositivo

È possibile trovare ulteriori dettagli su questa soluzione facendo riferimento alla mia altra domanda use FBO to record Unity gamescreen

4

Kamcord plug-in in grado di aiutare a: http://www.kamcord.com/

+1

Attualmente Kamcord funziona solo su dispositivi Nexus 4 e 7 con Android 4.3 Jelly Bean e Unity 4.2. Desidero sviluppare questo plugin per altri dispositivi – knighthedspi

+2

Poiché Kamcord è integrato con il motore di gioco, questo è l'approccio migliore da un punto di vista tecnico, ma raccomanderei un'attenta revisione dei termini legali ... acconsenti al loro monitoraggio delle tue app e potresti non essere in grado di pubblicare i tuoi video ("Gli utenti finali potranno solo accedere ai video di Kamcord attraverso i canali di distribuzione determinati da Kamcord a sua esclusiva discrezione "). – fadden

+0

Come ho detto sopra, attualmente Kamcord supporta solo i dispositivi Nexus. Nel mio caso, voglio che il mio plug-in possa funzionare con molti altri dispositivi. – knighthedspi