/* * Copyright 2013 Sony Corporation */ package com.thibaudperso.sonycamera.timelapse.ui; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup.LayoutParams; import com.thibaudperso.sonycamera.timelapse.ui.SimpleLiveviewSlicer.Payload; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** * A SurfaceView based class to draw liveview frames serially. */ public class SimpleStreamSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = SimpleStreamSurfaceView.class .getSimpleName(); private boolean mWhileFetching; private final BlockingQueue<byte[]> mJpegQueue = new ArrayBlockingQueue<>(2); private final boolean mInMutableAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; private Thread mDrawerThread; private int mPreviousWidth = 0; private int mPreviousHeight = 0; private final Paint mFramePaint; public SimpleStreamSurfaceView(Context context) { super(context); getHolder().addCallback(this); mFramePaint = new Paint(); mFramePaint.setDither(true); } public SimpleStreamSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); getHolder().addCallback(this); mFramePaint = new Paint(); mFramePaint.setDither(true); } public SimpleStreamSurfaceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); getHolder().addCallback(this); mFramePaint = new Paint(); mFramePaint.setDither(true); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // do nothing. } @Override public void surfaceCreated(SurfaceHolder holder) { // do nothing. } @Override public void surfaceDestroyed(SurfaceHolder holder) { mWhileFetching = false; } /** * Start retrieving and drawing liveview frame data by new threads. * * @return true if the starting is completed successfully, false otherwise. * @exception IllegalStateException when Remote API object is not set. */ public boolean start(final String liveviewUrl) { if (mWhileFetching) { Log.w(TAG, "start() already starting."); return false; } mWhileFetching = true; // A thread for retrieving liveview data from server. Thread mSlicerThread = new Thread(new Runnable() { @Override public void run() { SimpleLiveviewSlicer slicer = null; try { // Create Slicer to open the stream and parse it. slicer = new SimpleLiveviewSlicer(); slicer.open(liveviewUrl); while (mWhileFetching) { final Payload payload = slicer.nextPayload(); if (payload == null) { // never occurs Log.e(TAG, "Liveview Payload is null."); continue; } if (mJpegQueue.size() == 2) { mJpegQueue.remove(); } mJpegQueue.add(payload.jpegData); } } catch (IOException e) { Log.w(TAG, "IOException while fetching: " + e.getMessage()); } finally { // Finalize try { if (slicer != null) { slicer.close(); } } catch (IOException e) { Log.w(TAG, "IOException while closing slicer: " + e.getMessage()); } if (mDrawerThread != null) { mDrawerThread.interrupt(); } mJpegQueue.clear(); mWhileFetching = false; } } }); mSlicerThread.start(); // A thread for drawing liveview frame fetched by above thread. mDrawerThread = new Thread() { @Override public void run() { Log.d(TAG, "Starting drawing liveview frame."); Bitmap frameBitmap = null; BitmapFactory.Options factoryOptions = new BitmapFactory.Options(); factoryOptions.inSampleSize = 1; if (mInMutableAvailable) { initInBitmap(factoryOptions); } while (mWhileFetching) { try { byte[] jpegData = mJpegQueue.take(); frameBitmap = BitmapFactory.decodeByteArray( jpegData, 0, jpegData.length, factoryOptions); } catch (IllegalArgumentException e) { if (mInMutableAvailable) { clearInBitmap(factoryOptions); } continue; } catch (InterruptedException e) { Log.i(TAG, "Drawer thread is Interrupted."); break; } if (mInMutableAvailable) { setInBitmap(factoryOptions, frameBitmap); } drawFrame(frameBitmap); } if (frameBitmap != null) { frameBitmap.recycle(); } mWhileFetching = false; } }; mDrawerThread.start(); return true; } /** * Request to stop retrieving and drawing liveview data. */ public void stop() { mWhileFetching = false; } /** * Check to see whether start() is already called. * * @return true if start() is already called, false otherwise. */ public boolean isStarted() { return mWhileFetching; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void initInBitmap(BitmapFactory.Options options) { options.inBitmap = null; options.inMutable = true; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void clearInBitmap(BitmapFactory.Options options) { if (options.inBitmap != null) { options.inBitmap.recycle(); options.inBitmap = null; } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void setInBitmap(BitmapFactory.Options options, Bitmap bitmap) { options.inBitmap = bitmap; } // Draw frame bitmap onto a canvas. private void drawFrame(Bitmap frame) { if (frame.getWidth() != mPreviousWidth || frame.getHeight() != mPreviousHeight) { onDetectedFrameSizeChanged(frame.getWidth(), frame.getHeight()); return; } Canvas canvas = getHolder().lockCanvas(); if (canvas == null) { return; } int w = frame.getWidth(); int h = frame.getHeight(); Rect src = new Rect(0, 0, w, h); float by = Math .min((float) getWidth() / w, (float) getHeight() / h); int offsetX = (getWidth() - (int) (w * by)) / 2; int offsetY = (getHeight() - (int) (h * by)) / 2; Rect dst = new Rect(offsetX, offsetY, getWidth() - offsetX, getHeight() - offsetY); canvas.drawBitmap(frame, src, dst, mFramePaint); getHolder().unlockCanvasAndPost(canvas); } // Called when the width or height of liveview frame image is changed. private void onDetectedFrameSizeChanged(final int width, final int height) { mPreviousWidth = width; mPreviousHeight = height; drawBlackFrame(); drawBlackFrame(); drawBlackFrame(); // delete triple buffers ((Activity) getContext()).runOnUiThread(new Runnable() { @Override public void run() { //Get the SurfaceView layout parameters LayoutParams lp = getLayoutParams(); lp.width = getWidth(); lp.height = (int) (((float) height / (float) width) * (float) getWidth()); setLayoutParams(lp); } }); } // Draw black screen. private void drawBlackFrame() { Canvas canvas = getHolder().lockCanvas(); if (canvas == null) { return; } Paint paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.FILL); canvas.drawRect(new Rect(0, 0, getWidth(), getHeight()), paint); getHolder().unlockCanvasAndPost(canvas); } }