This tutorial explains how to use Intel® INDE Media Pack for Android* to add video capturing capability to a LibGDX based game.
Before getting started, download and install Intel INDE by visiting http://intel.com/software/inde. After installing Intel INDE, choose to download and install the Media Pack for Android. For additional assistance visit the Intel INDE forum.
Go to the installation folder of Media Pack for Android -> libs and copy two jar files (android-<version>.jar and domain-<version>.jar) to your application’s libs folder:
We are ready to go now, but first let me explain how GLCapture class works. GLCapture has its own rendering surface and an OpenGL* context. All frames rendered to this surface could be encoded as video frames. Before the first use we need to setup some required parameters like video resolution, bit rate, path to video file, etc.
Our example is based on the GDX Invaders sample application from libGDX package. Let see how to add video capturing functionality into this game.
As we can’t directly add Android functionality to a core project we need to create an abstract interface inside the core project and then implement this interface for Android port:
public interface GameScreenCapture { // Initiates video capturing process public void start(); // Interrupts video capturing process public void stop(); // Check the state of capturing process public boolean isStarted(); // Switch the current OpenGL context to the video capturing surface public void beginCapture(); // Restore OpenGL context public void endCapture(); // width and height of video frame public int getFrameWidth(); public int getFrameHeight(); }
To keep our code more clear we will use VideoCapture class from the Intel® INDE Media Pack for Android Samples application. VideoCapture is a singleton wrapper for GLCapture which takes care about initialization and provides just a few methods:
public void start(String videoPath)
Launches capturing process, sets up the video format and destination file path.
public void stop()
Interrupts capturing process.
public void beginCaptureFrame()
This method checks if the video surface is configured. If it’s not the method tries to configure video surface first by calling setSurfaceSize() and then by calling beginCaptureFrame() of GLCapture.
public void endCaptureFrame()
calls endCaptureFrame of GLCapture.
Now let’s add GameScreenCapture implementation for an Android project:
public class AndroidGameScreenCapture implements GameScreenCapture { // Path to video file private String mVideoPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/inviders.mp4"; public AndroidGameScreenCapture() { } @Override public void start() { try { // start could throw IOException in case if file couldn't be opened VideoCapture.getInstance().start(mVideoPath); } catch (IOException e) { } } @Override public void stop() { VideoCapture.getInstance().stop(); } @Override public boolean isStarted() { return VideoCapture.getInstance().isStarted(); } @Override public void beginCapture() { VideoCapture.getInstance().beginCaptureFrame(); } @Override public void endCapture() { VideoCapture.getInstance().endCaptureFrame(); } @Override public int getFrameWidth() { return VideoCapture.getInstance().getFrameWidth(); } @Override public int getFrameHeight() { return VideoCapture.getInstance().getFrameHeight(); } }
We define this class as a member of AndroidApplication inside the Android application port:
public class GdxInvadersAndroid extends AndroidApplication { private AndroidGameScreenCapture mScreenCapture = new AndroidGameScreenCapture(); … }
We pass this instance to a game object via constructor:
public void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); … initialize(new GdxInvaders(mScreenCapture), config); }
And for that we need to change this constructor and add a member variable to store this object:
public class GdxInvaders extends Game { GameScreenCapture gameScreenCapture; … public GdxInvaders(GameScreenCapture gameScreenCapture) { … } }
Our plan is to add the capturing functionality only for the GameLoop screen, so another step is to pass this object to the GameLoop screen:
public class GameLoop extends InvadersScreen implements SimulationListener { GameScreenCapture gameScreenCapture; … public GameLoop (GameScreenCapture gameScreenCapture) { … } }
Now we need to add some logic for the game screen. In our sample app we added a recording button onto the game screen. By pressing this button the user can start and stop the capturing process (see screenshots below).
Inside a click handler we simply switch the state of the video capturing based on its current state:
if(gameScreenCapture.isStarted()) { gameScreenCapture.stop(); } else { gameScreenCapture.start(); }
Now everything is ready except the main part: video capturing functionality inside the rendering loop. There are at least two ways to capture frames: double rendering and using frame buffer. In this sample we will to implement both.
Render twice is simpler method to implement:
public void draw (float delta) { … // Render scene to display renderer.render(simulation, delta); // Video capture available and in a recording state if(gameScreenCapture != null && gameScreenCapture.isStarted()) { // Switch OpenGL context gameScreenCapture.beginCapture(); // Render scene, keep in mind delta should 0 // as we are not going to update game logic twice renderer.render(simulation, 0); // switch back to original context gameScreenCapture.endCapture(); } }
Another approach to capture video is to use a Frame Buffer. Thanks to libGDX there is already a class which we can use to implement this solution. We need to just make some changes in the GameLoop class:
// Frame buffer to be used as a render target FrameBuffer fbo; // Texture region to copy frame buffer texture to display and video surface TextureRegion fboRegion; public GameLoop (GameScreenCapture gameScreenCapture) { fbo = new FrameBuffer(Pixmap.Format.RGB888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false); fboRegion = new TextureRegion(mFBO.getColorBufferTexture()); fboRegion.flip(false, true); } public void draw (float delta) { // Set Frame Buffer as a render target fbo.begin(); // Render scene renderer.render(simulation, delta); // Restore original render target fbo.end(); … // Draw Frame Buffer texture to display spriteBatch.begin(); spriteBatch.draw(fboRegion, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); spriteBatch.end(); // Video capture available and in a recording state if(gameScreenCapture != null && gameScreenCapture.isStarted()) { // Switch OpenGL context gameScreenCapture.beginCapture(); // Draw Frame Buffer texture to video surface spriteBatch.begin(); spriteBatch.draw(mFBORegion, 0, 0, gameScreenCapture.getFrameWidth(), gameScreenCapture.getFrameHeight()); spriteBatch.end(); // switch back to original context gameScreenCapture.endCapture(); } }