diff --git a/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraImageMonitor.java b/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraImageMonitor.java index 84998a8..3c47dd8 100644 --- a/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraImageMonitor.java +++ b/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraImageMonitor.java @@ -16,6 +16,7 @@ package ve.ucv.ciens.ccg.nxtcam.camera; import ve.ucv.ciens.ccg.nxtcam.utils.Logger; +import android.graphics.Rect; public class CameraImageMonitor{ private final String TAG = "CAM_MONITOR"; @@ -23,13 +24,15 @@ public class CameraImageMonitor{ private Object imageMonitor; private byte[] image; - private boolean imgProduced; - private boolean imgConsumed; + private boolean imageProduced; + private boolean imageConsumed; + private Rect imageSize; private CameraImageMonitor(){ - imgProduced = false; - imgConsumed = true; + imageProduced = false; + imageConsumed = true; imageMonitor = new Object(); + imageSize = null; } private static class SingletonHolder{ @@ -40,13 +43,21 @@ public class CameraImageMonitor{ return SingletonHolder.INSTANCE; } + public void setImageParameters(int width, int height){ + imageSize = new Rect(0, 0, width, height); + } + + public Rect getImageParameters(){ + return imageSize; + } + public void setImageData(byte[] image){ - if(imgConsumed){ + if(imageConsumed){ Logger.log_d(TAG, CLASS_NAME + ".setImageData() :: Copying new image."); synchronized(this.imageMonitor){ this.image = image; - imgProduced = true; - imgConsumed = false; + imageProduced = true; + imageConsumed = false; this.imageMonitor.notifyAll(); } Logger.log_d(TAG, CLASS_NAME + ".setImageData() :: Data copy finished."); @@ -59,20 +70,20 @@ public class CameraImageMonitor{ byte[] returnImg; Logger.log_d(TAG, CLASS_NAME + ".getImageData() :: Entry point."); synchronized(imageMonitor){ - while(!imgProduced){ + while(!imageProduced){ Logger.log_d(TAG, CLASS_NAME + ".getImageData() :: Waiting for new image."); try{ imageMonitor.wait(); }catch(InterruptedException ie){ } } Logger.log_d(TAG, CLASS_NAME + ".getImageData() :: Retrieving new image."); returnImg = image; - imgProduced = false; - imgConsumed = true; + imageProduced = false; + imageConsumed = true; } Logger.log_d(TAG, CLASS_NAME + ".getImageData() :: New image retrieved."); return returnImg; } public synchronized boolean hasChanged(){ - return imgConsumed; + return imageConsumed; } } diff --git a/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraPreview.java b/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraPreview.java index 9348f43..610590f 100644 --- a/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraPreview.java +++ b/src/ve/ucv/ciens/ccg/nxtcam/camera/CameraPreview.java @@ -138,6 +138,9 @@ public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback @Override public void onPreviewFrame(byte[] data, Camera camera){ + Size previewSize = camera.getParameters().getPreviewSize(); + if(imgMonitor.hasChanged()) + imgMonitor.setImageParameters(previewSize.width, previewSize.height); Logger.log_d(TAG, CLASS_NAME + ".onPreviewFrame() :: Preview received"); Logger.log_d(TAG, CLASS_NAME + ".onPreviewFrame() :: Frame has" + (imgMonitor.hasChanged() ? "" : " not") + " been consumed."); imgMonitor.setImageData(data); diff --git a/src/ve/ucv/ciens/ccg/nxtcam/network/ImageTransferThread.java b/src/ve/ucv/ciens/ccg/nxtcam/network/ImageTransferThread.java index 58ea2e8..e57bac3 100644 --- a/src/ve/ucv/ciens/ccg/nxtcam/network/ImageTransferThread.java +++ b/src/ve/ucv/ciens/ccg/nxtcam/network/ImageTransferThread.java @@ -15,30 +15,36 @@ */ package ve.ucv.ciens.ccg.nxtcam.network; -import java.io.BufferedReader; -import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; import ve.ucv.ciens.ccg.nxtcam.camera.CameraImageMonitor; +import ve.ucv.ciens.ccg.nxtcam.network.protocols.ImageDataMessage; +import ve.ucv.ciens.ccg.nxtcam.network.protocols.ImageTransferProtocol; +import ve.ucv.ciens.ccg.nxtcam.network.protocols.ImageTransferProtocolMessage; import ve.ucv.ciens.ccg.nxtcam.utils.Logger; import ve.ucv.ciens.ccg.nxtcam.utils.ProjectConstants; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.YuvImage; public class ImageTransferThread extends Thread{ private final String TAG = "IM_THREAD"; private final String CLASS_NAME = ImageTransferThread.class.getSimpleName(); + private enum thread_state_t {WAIT_FOR_ACK, WAIT_FOR_READY, CAN_SEND, END_STREAM}; + private boolean pause, done; private Object threadPauseMonitor; private CameraImageMonitor camMonitor; private Socket socket; - private BufferedWriter writer; - private BufferedReader reader; - private byte[] image; - private String serverIp; + private ObjectOutputStream writer; + private ObjectInputStream reader; private String serverIp; + private thread_state_t threadState; public ImageTransferThread(String serverIp){ this.serverIp = serverIp; @@ -49,17 +55,94 @@ public class ImageTransferThread extends Thread{ writer = null; reader = null; camMonitor = CameraImageMonitor.getInstance(); + threadState = thread_state_t.WAIT_FOR_READY; } public void run(){ + byte[] image; + Object auxiliary; + ImageTransferProtocolMessage simpleMessage; + ImageDataMessage imageMessage; + connectToServer(); + if(socket.isConnected()){ Logger.log_e(TAG, CLASS_NAME + ".run() :: Not connected to a server. Finishing thread."); }else{ while(!done){ - checkPause(); - image = camMonitor.getImageData(); - // TODO: implement image transfer protocol. + // checkPause(); + switch(threadState){ + case WAIT_FOR_READY: + auxiliary = readMessage(); + + if(!validateImageTransferProtocolMessage(auxiliary)){ + // If the message received is not valid then send an UNRECOGNIZED message to the server. + Logger.log_d(TAG, CLASS_NAME + ".run() :: Received an unrecognized protocol message. State WAIT_FOR_READY."); + Logger.log_d(TAG, CLASS_NAME + ".run() :: Sending UNRECOGNIZED message to server."); + sendUnrecognizedMessage(); + + }else{ + // Else if the passed the validity check then proceed to the next protocol state. + simpleMessage = (ImageTransferProtocolMessage)auxiliary; + if(simpleMessage.message == ImageTransferProtocol.FLOW_CONTROL_CONTINUE) + threadState = thread_state_t.CAN_SEND; + else if(simpleMessage.message == ImageTransferProtocol.STREAM_CONTROL_END) + threadState = thread_state_t.END_STREAM; + } + break; + + case WAIT_FOR_ACK: + auxiliary = readMessage(); + + if(!validateImageTransferProtocolMessage(auxiliary)){ + // If the message received is not valid then send an UNRECOGNIZED message to the server. + Logger.log_d(TAG, CLASS_NAME + ".run() :: Received an unrecognized protocol message. State WAIT_FOR_ACK."); + Logger.log_d(TAG, CLASS_NAME + ".run() :: Sending UNRECOGNIZED message to server."); + sendUnrecognizedMessage(); + + }else{ + // Else if the passed the validity check then proceed to the next protocol state. + simpleMessage = (ImageTransferProtocolMessage)auxiliary; + if(simpleMessage.message == ImageTransferProtocol.ACK_SEND_NEXT) + threadState = thread_state_t.CAN_SEND; + else if(simpleMessage.message == ImageTransferProtocol.ACK_WAIT) + threadState = thread_state_t.WAIT_FOR_READY; + else if(simpleMessage.message == ImageTransferProtocol.STREAM_CONTROL_END) + threadState = thread_state_t.END_STREAM; + } + break; + + case CAN_SEND: + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + // Get the image and it's parameters from the monitor. + Rect imageSize = camMonitor.getImageParameters(); + image = camMonitor.getImageData(); + + // Compress the image as Jpeg. + YuvImage yuvImage = new YuvImage(image, ImageFormat.NV21, imageSize.width(), imageSize.height(), null); + yuvImage.compressToJpeg(imageSize, 90, outputStream); + + // Prepare the message for sending. + imageMessage = new ImageDataMessage(); + imageMessage.imageWidth = imageSize.width(); + imageMessage.imageHeight = imageSize.height(); + imageMessage.data = outputStream.toByteArray(); + + // Send the message. + try{ + writer.writeObject(imageMessage); + }catch(IOException io){ + Logger.log_e(TAG, CLASS_NAME + ".run() :: Error sending image to the server: " + io.getMessage()); + } + break; + + case END_STREAM: + // Simply disconnect from the server. + disconnect(); + break; + } + } } } @@ -68,8 +151,8 @@ public class ImageTransferThread extends Thread{ try{ Logger.log_i(TAG, CLASS_NAME + ".connectToServer() :: Connecting to the server at " + serverIp); socket = new Socket(InetAddress.getByName(serverIp), ProjectConstants.SERVER_TCP_PORT_1); - writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); - reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + writer = new ObjectOutputStream(socket.getOutputStream()); + reader = new ObjectInputStream(socket.getInputStream()); Logger.log_i(TAG, CLASS_NAME + ".connectToServer() :: Connection successful."); }catch(IOException io){ Logger.log_e(TAG, CLASS_NAME + ".connectToServer() :: Connection failed with message: " + io.getMessage()); @@ -100,6 +183,45 @@ public class ImageTransferThread extends Thread{ } } + private Object readMessage(){ + Object auxiliary; + + // Read a message from the server stream. + try{ + auxiliary = reader.readObject(); + + }catch(IOException io){ + Logger.log_e(TAG, CLASS_NAME + ".run() :: IOException when reading in WAIT_FOR_READY state."); + auxiliary = null; + return null; + }catch(ClassNotFoundException cn){ + Logger.log_e(TAG, CLASS_NAME + ".run() :: ClassNotFoundException when reading in WAIT_FOR_READY state."); + auxiliary = null; + return null; + } + + return auxiliary; + } + + private boolean validateImageTransferProtocolMessage(Object message){ + if(message != null && message instanceof ImageTransferProtocolMessage) + return true; + else + return false; + } + + private void sendUnrecognizedMessage(){ + ImageTransferProtocolMessage message = new ImageTransferProtocolMessage(); + message.message = ImageTransferProtocol.UNRECOGNIZED; + + try{ + writer.writeObject(message); + }catch(IOException io){ + Logger.log_e(TAG, CLASS_NAME + ".run() :: IOException when writing UNRECOGNIZED in WAIT_FOR_READY state."); + } + Logger.log_d(TAG, CLASS_NAME + ".run() :: UNRECOGNIZED message sent."); + } + public synchronized void pauseThread(){ pause = true; Logger.log_d(TAG, CLASS_NAME + ".pauseThread() :: Pausing thread."); diff --git a/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageDataMessage.java b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageDataMessage.java new file mode 100644 index 0000000..619d4c7 --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageDataMessage.java @@ -0,0 +1,18 @@ +package ve.ucv.ciens.ccg.nxtcam.network.protocols; + +import java.io.Serializable; + +public final class ImageDataMessage extends ProtocolMessage implements Serializable{ + private static final long serialVersionUID = 9989L; + public static final int magicNumber = 0x10; + + public int imageWidth; + public int imageHeight; + public byte[] data; + + public ImageDataMessage(){ + imageWidth = -1; + imageHeight = -1; + data = null; + } +} \ No newline at end of file diff --git a/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocol.java b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocol.java index 0461bfb..e27005c 100644 --- a/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocol.java +++ b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocol.java @@ -15,17 +15,31 @@ */ package ve.ucv.ciens.ccg.nxtcam.network.protocols; -public abstract class ImageTransferProtocol{ - public static enum ProtocolState{ - SALUTE, IMG_FOLLOWS, SEND_DATA, PAUSED, WAITING, GOODBYE +public final class ImageTransferProtocol{ + public static final byte STREAM_CONTROL_END = 0x10; + public static final byte ACK_SEND_NEXT = 0x20; + public static final byte ACK_WAIT = 0x30; + public static final byte FLOW_CONTROL_WAIT = 0x40; + public static final byte FLOW_CONTROL_CONTINUE = 0x50; + public static final byte IMAGE_DATA = 0x60; + public static final byte UNRECOGNIZED = (byte)0xFF; + + public static boolean checkValidityOfMessage(byte message){ + boolean validity; + + switch(message){ + case STREAM_CONTROL_END: + case ACK_SEND_NEXT: + case ACK_WAIT: + case FLOW_CONTROL_WAIT: + case FLOW_CONTROL_CONTINUE: + case IMAGE_DATA: + case UNRECOGNIZED: + validity = true; + default: + validity = false; + } + + return validity; } - - public static final byte MSG_HELLO = (byte)0x89; - public static final byte MSG_GOODBYE = (byte)0x90; - public static final byte MSG_IMG_DATA = (byte)0x42; - public static final byte CMD_IMG_FOLLOWS = (byte)0x10; - public static final byte CMD_PAUSE = (byte)0x15; - public static final byte CMD_IMG_WAIT = (byte)0x20; - public static final byte ACK_SEND_IMG = (byte)0x40; - public static final byte ACK_IMG_RCVD = (byte)0x50; } diff --git a/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocolMessage.java b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocolMessage.java new file mode 100644 index 0000000..aa702a3 --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ImageTransferProtocolMessage.java @@ -0,0 +1,14 @@ +package ve.ucv.ciens.ccg.nxtcam.network.protocols; + +import java.io.Serializable; + +public final class ImageTransferProtocolMessage extends ProtocolMessage implements Serializable{ + private static final long serialVersionUID = 8898L; + public static final int magicNumber = 0x20; + + public byte message; + + public ImageTransferProtocolMessage(){ + message = -1; + } +} \ No newline at end of file diff --git a/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ProtocolMessage.java b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ProtocolMessage.java new file mode 100644 index 0000000..e50c0ec --- /dev/null +++ b/src/ve/ucv/ciens/ccg/nxtcam/network/protocols/ProtocolMessage.java @@ -0,0 +1,8 @@ +package ve.ucv.ciens.ccg.nxtcam.network.protocols; + +import java.io.Serializable; + +@SuppressWarnings("serial") +public abstract class ProtocolMessage implements Serializable { + public static int magicNumber = 0x00; +}