package io.cine.android.streaming; import android.graphics.Bitmap; import android.graphics.Matrix; import android.opengl.GLES20; import android.os.Environment; import android.os.Message; import android.util.Log; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import io.cine.android.BroadcastActivity; import io.cine.android.streaming.gles.GlUtil; /** * Created by lgorse on 2/11/15. * * This class defines the basic configuration required for a screenshot. * The relevant variables are the filepath,the file prefix and the scale. * FilePath means the folder path. * The file name is an aggregate of the prefix, the timestamp of Now() and a png extension. * Scale is important for space management - it allows the user to have some control over how * much space his thumbnails are going to take. */ public class ScreenShot { protected static final String TAG = GlUtil.TAG; private float scale; private String filePath; private String fileFolder; private String prefix; private BroadcastActivity.CameraHandler mCameraHandler; private File screenShotFile; public static final int SAVING_FRAME = 0; public static final int SAVED_FRAME = 1; public static final int FAILED_FRAME = 2; //Basic screenshot: just give us the CameraHandler, we'll do the rest public ScreenShot(BroadcastActivity.CameraHandler mCameraHandler){ this.scale = 1f; this.prefix = ""; this.fileFolder = Environment.getExternalStorageDirectory() + "/cineio/" ; this.mCameraHandler = mCameraHandler; initiateDirectory(); } //Screenshot with all variables defined public ScreenShot(BroadcastActivity.CameraHandler mCameraHandler, float scale, String filePath, String prefix){ this.scale = scale; this.fileFolder = Environment.getExternalStorageDirectory() + "/" + filePath + "/"; this.prefix = prefix; this.mCameraHandler = mCameraHandler; initiateDirectory(); } //Screenshot without the prefix (perhaps not required by the user) public ScreenShot(BroadcastActivity.CameraHandler mCameraHandler, float scale, String filePath){ this.scale = scale; this.fileFolder = Environment.getExternalStorageDirectory() + "/" + filePath + "/"; this.prefix = ""; this.mCameraHandler = mCameraHandler; initiateDirectory(); } //Ensures that the file Folder destination is generated if it doesn't exist private void initiateDirectory() { File directory = new File(fileFolder); directory.mkdirs(); } //Generates the full File. Useful to get the full filepath of the Bitmap public void setScreenShotFile(){ this.screenShotFile = new File(fileFolder, prefix + System.currentTimeMillis() + ".png"); setFilePath(this.screenShotFile.toString()); } public File getScreenShotFile(){ return this.screenShotFile; } public void setScale(float scale){ this.scale = scale; } public float getScale(){ return this.scale; } private void setFilePath(String filePath){ this.filePath = filePath; } public String getFilePath(){ return this.filePath; } public void setPrefix(String prefix){ this.prefix = prefix; } public String getPrefix(){ return this.prefix; } public String getFileFolder(){return this.fileFolder;} //Turns out we need to get the camerahandler in order to send messages to it public BroadcastActivity.CameraHandler getmCameraHandler(){ return this.mCameraHandler; } //Notifies camera handler that we are in the process of saving a frame public void savingMessage() { Message message = new Message(); message.obj = "Saving"; sendCameraHandlerMessage(SAVING_FRAME, message); } //Notifies camera handler that we have saved a frame public void savedMessage() { Message message = new Message(); message.obj = this; sendCameraHandlerMessage(SAVED_FRAME, message); } //Notifies camera handler that the frame save has failed public void failedFrameMessage(String failureMessage){ Message message = new Message(); message.obj = failureMessage; sendCameraHandlerMessage(FAILED_FRAME, message); } /**Handles the save frame messaging back to the camera. * Super useful to handle UI changes based on save status. * @param status */ private void sendCameraHandlerMessage(int status, Message message){ message.what = mCameraHandler.MSG_CAPTURE_FRAME; message.arg1 = status; switch(status){ case SAVING_FRAME: mCameraHandler.sendMessage(message); break; case SAVED_FRAME: mCameraHandler.sendMessage(message); break; case FAILED_FRAME: mCameraHandler.sendMessage(message); break; } } // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the // Bitmap "copy pixels" method wants the same format GL provides. // // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling // here often. // // Making this even more interesting is the upside-down nature of GL, which means // our output will look upside down relative to what appears on screen if the // typical GL conventions are used. /** *The comments above are directly from the Grafika library. I shifted a lot of its content * to this section so it makes sense to have it as a reminder. * * This method saves the contents of a Bytebuffer to an output stream, and generates * a bitmap from that. * * It also applies any scaling and rotates/mirrors the image to take into account * OpenGL scaling. * * @param buf must be rewound prior to being passed to this method. * Essentially buff must be declared, and GLReadPixels called, then the buffer rewound * Then it is ready to be passed to this method. * It's important to do these preparation steps in an EGL context, and not on their own. * That's why we make sure not to include them here. Maybe not a good idea? * @param width * @param height * @throws IOException */ public void saveBitmapFromBuffer(ByteBuffer buf, int width, int height) throws IOException { BufferedOutputStream bos = null; try { Long startTime = System.currentTimeMillis(); savingMessage(); setScreenShotFile(); bos = new BufferedOutputStream(new FileOutputStream(getFilePath())); Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bmp.copyPixelsFromBuffer(buf); Matrix m = new Matrix(); m.preScale(-getScale(), getScale()); m.postRotate(180); bmp = Bitmap.createBitmap(bmp, 0, 0, width, height, m, false); bmp.compress(Bitmap.CompressFormat.PNG, 50, bos); bmp.recycle(); Log.i("time elapsed", String.valueOf(System.currentTimeMillis() - startTime) + " milliseconds"); }catch (IOException e){ failedFrameMessage(e.getMessage()); throw e; } finally { savedMessage(); if (bos != null){ bos.close(); bos.flush(); } } Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + getFilePath() + "'"); } }