2013-11-26 4 views
5

Я разрабатываю Unity-Android Plugin для записи экрана игры и создания видеофайла mp4. Я следую примеру патча Android-рекордера Android на этом сайте: http://bigflake.com/mediacodec/.
Во-первых, я создаю свой класс CustomUnityPlayer, который расширяет класс UnityPlayer и переопределить onDrawFrame method.Here мой класс CustomUnityPlayer код:Плагин для Android-плагинов для снятия скриншотов

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

И потом, я создать CustomUnityPlayerActivity назвать этот класс

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

Моя проблема заключается в том, что видеофайл создается успешно, но моя игра не может отобразить ничего. Я читаю на сайте Android Media Codec sample и узнаю, что каждый кадр будет отображаться дважды (один раз для отображения, один раз для видео), но я не могу сделать это в Unity.Whenever, я пытаюсь вызвать super.onDrawFrame (gl) дважды в методе onDrawFrame, моя игра будет разбита.

Любое решение для моей проблемы? Любая помощь будет оценена!

Спасибо и наилучшим образом!

Зуй Tran

+1

FWIW есть два основных подхода: (1) сделать несколько кадров дважды (как это сделано в Breakout), (2) визуализировать внеэкранное FBO и blit дважды. В зависимости от сложности сцены, она может быть дешевле, чем другая. Если вы используете GLES 3, есть хитрость, чтобы избежать одной копии в подходе №2. В любом случае, настоящий трюк - это интеграция с Unity. – fadden

+0

Спасибо за ваш ответ. Теперь моя проблема заключается в интеграции с Unity.Can вы объясняете более понятный подход # 2.Я попытался с подходом №1, как описано выше, но не получится! – knighthedspi

+0

FWIW, все три подхода продемонстрированы в Grafika (https://github.com/google/grafika). См. «Запись приложения GL-приложения». – fadden

ответ

4

Наконец я использую FrameBufferObject (FBO), чтобы сделать закадровый и получить его связывания текстуры блитирования дважды:

  • Рендер к поверхности видеосигнала
  • Пересмотр на экран устройства

Вы можете найти более подробно об этом решении по ссылке на мой другой вопрос use FBO to record Unity gamescreen

4

Kamcord плагин может помочь вам: http://www.kamcord.com/

+1

Kamcord в настоящее время работает только на устройствах Nexus 4 и 7 под управлением Android 4.3 Jelly Bean и Unity 4.2.Я хочу разработать этот плагин для других устройств. – knighthedspi

+2

Поскольку Kamcord интегрирован с игровым движком, это лучший подход с технической точки зрения, но я бы рекомендовал внимательно изучить юридические условия ... вы соглашаетесь на их мониторинг ваших приложений, и вы не сможете публиковать свои видеоролики («Конечные пользователи смогут только для доступа к видео Камкорда через каналы распространения, определенные Kamcord по своему собственному усмотрению »). – fadden

+0

Как я уже говорил выше, Kamcord в настоящее время поддерживает только устройства Nexus. В моем случае я хочу, чтобы мой плагин мог работать со многими другими устройствами. – knighthedspi

Смежные вопросы