package com.almalence.sony.cameraremote;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
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 com.almalence.sony.cameraremote.utils.SimpleLiveviewSlicer;
import com.almalence.sony.cameraremote.utils.SimpleLiveviewSlicer.Payload;
/**
* 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<byte[]>(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 int mSurfaceWidth = 0;
private int mSurfaceHeight = 0;
Matrix rotateMatrix = new Matrix();
private final Paint mFramePaint;
private StreamErrorListener mErrorListener;
private StreamFrameListener mFrameListener;
/**
* Constructor
*
* @param context
*/
public SimpleStreamSurfaceView(Context context)
{
super(context);
getHolder().addCallback(this);
mFramePaint = new Paint();
mFramePaint.setDither(true);
}
/**
* Constructor
*
* @param context
* @param attrs
*/
public SimpleStreamSurfaceView(Context context, AttributeSet attrs)
{
super(context, attrs);
getHolder().addCallback(this);
mFramePaint = new Paint();
mFramePaint.setDither(true);
}
/**
* Constructor
*
* @param context
* @param attrs
* @param defStyle
*/
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.
* @see SimpleLiveviewSurfaceView#bindRemoteApi(SimpleRemoteApi)
*/
public boolean start(final String streamUrl, StreamFrameListener frameListener, StreamErrorListener errorListener)
{
mErrorListener = errorListener;
mFrameListener = frameListener;
rotateMatrix.reset();
rotateMatrix.postRotate(90);
if (streamUrl == null)
{
Log.e(TAG, "start() streamUrl is null.");
mWhileFetching = false;
mErrorListener.onError(StreamErrorListener.StreamErrorReason.OPEN_ERROR);
return false;
}
if (mWhileFetching)
{
Log.w(TAG, "start() already starting.");
return false;
}
mWhileFetching = true;
// A thread for retrieving liveview data from server.
new Thread()
{
@Override
public void run()
{
Log.d(TAG, "Starting retrieving streaming data from server.");
SimpleLiveviewSlicer slicer = null;
try
{
// Create Slicer to open the stream and parse it.
slicer = new SimpleLiveviewSlicer();
slicer.open(streamUrl);
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);
if (mFrameListener != null)
{
mFrameListener.onFrameAvailable(payload.jpegData);
}
}
} catch (IOException e)
{
Log.w(TAG, "IOException while fetching: " + e.getMessage());
mErrorListener.onError(StreamErrorListener.StreamErrorReason.IO_EXCEPTION);
} finally
{
if (slicer != null)
{
slicer.close();
}
if (mDrawerThread != null)
{
mDrawerThread.interrupt();
}
mJpegQueue.clear();
mWhileFetching = false;
}
}
}.start();
// A thread for drawing liveview frame fetched by above thread.
mDrawerThread = new Thread()
{
@Override
public void run()
{
Log.d(TAG, "Starting drawing stream 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);
if (frameBitmap != null)
{
Bitmap rotatedBitmap = Bitmap.createBitmap(frameBitmap, 0, 0, frameBitmap.getWidth(),
frameBitmap.getHeight(), rotateMatrix, true);
frameBitmap.recycle();
frameBitmap = rotatedBitmap;
}
} 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.
*
* @param frame
*/
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);
mSurfaceWidth = dst.width();
mSurfaceHeight = dst.height();
}
public int getSurfaceWidth() {
return mSurfaceWidth;
}
public int getSurfaceHeight() {
return mSurfaceHeight;
}
/**
* Called when the width or height of liveview frame image is changed.
*
* @param width
* @param height
*/
private void onDetectedFrameSizeChanged(int width, int height)
{
Log.d(TAG, "Change of aspect ratio detected");
mPreviousWidth = width;
mPreviousHeight = height;
drawBlackFrame();
drawBlackFrame();
drawBlackFrame(); // delete triple buffers
}
/**
* 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);
}
public interface StreamErrorListener
{
enum StreamErrorReason
{
IO_EXCEPTION, OPEN_ERROR,
}
void onError(StreamErrorReason reason);
}
public interface StreamFrameListener
{
void onFrameAvailable(byte[] jpegData);
}
}