/*
* Copyright 2013 Sony Corporation
*/
package com.example.sony.cameraremote.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* SimpleLiveviewSlicer.
*/
public class SimpleLiveviewSlicer {
/**
* Payload data class. See also Camera Remote API specification document to
* know the data structure.
*/
public static final class Payload {
/** jpeg data container. */
private byte[] jpegData;
/** padding data container. */
private byte[] paddingData;
/**
* jpegData getter.
* @return jpegData
*/
public byte[] getJpegData() {
return jpegData;
}
/**
* jpegData setter.
* @param jpegDataParam jpegData
*/
public void setJpegData(final byte[] jpegDataParam) {
jpegData = jpegDataParam;
}
/**
* paddingData getter.
* @return paddingData
*/
public byte[] getPaddingData() {
return paddingData;
}
/**
* paddingData setter.
* @param paddingDataParam paddingData
*/
public void setPaddingData(final byte[] paddingDataParam) {
paddingData = paddingDataParam;
}
/**
* Constructor.
* @param jpgData jpegData
* @param padData padding
*/
private Payload(final byte[] jpgData, final byte[] padData) {
setJpegData(jpgData);
setPaddingData(padData);
}
}
/** タイムアウト設定. */
private static final int CONNECTION_TIMEOUT = 2000; // [msec]
/** toInteger用のstartIndexの値定義. */
private static final int JPG_TO_INTEGER_STARTINDEX = 4;
/** toInteger用のcounterの値定義. */
private static final int JPG_TO_INTEGER_COUNTER = 3;
/** toInteger用のstartIndexの値定義. */
private static final int PAD_TO_INTEGER_STARTINDEX = 7;
/** toInteger用のcounterの値定義. */
private static final int PAD_TO_INTEGER_COUNTER = 1;
/** 数値利用のために定義. */
private static final int NUMB_THREE = 3;
/** 数値利用のために定義. */
private static final int NUMB_FOUR = 4;
/** 数値利用のために定義. */
private static final int NUMB_115 = 115;
/** 数値利用のために定義. */
private static final int NUMB_FF = 0xFF;
/** payloadheadder値. */
private static final int PAYLOAD_ZERO = 0x24;
/** payloadheadder値. */
private static final int PAYLOAD_ONE = 0x35;
/** payloadheadder値. */
private static final int PAYLOAD_TWO = 0x68;
/** payloadheadder値. */
private static final int PAYLOAD_THREE = 0x79;
/**
* バッファの最大幅.
*/
private static final int BUFFER_MAX_VAL = 1024;
/** 値定義. */
private static final int RET_VALUE = 8;
/** Intへの変換用. */
private static final int CONVERT_TO_INT = 0xff;
/** HTTPコネクションインスタンス. */
private HttpURLConnection mHttpConn;
/** 入力ストリーム. */
private InputStream mInputStream;
/**
* Opens Liveview HTTP GET connection and prepares for reading Packet data.
*
* @param liveviewUrl Liveview data url that is obtained by DD.xml or result
* of startLiveview API.
* @throws IOException generic errors or exception.
*/
public void open(final String liveviewUrl) throws IOException {
if (mInputStream != null || mHttpConn != null) {
throw new IllegalStateException("Slicer is already open.");
}
final URL url = new URL(liveviewUrl);
mHttpConn = (HttpURLConnection) url.openConnection();
mHttpConn.setRequestMethod("GET");
mHttpConn.setConnectTimeout(CONNECTION_TIMEOUT);
mHttpConn.connect();
if (mHttpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
mInputStream = mHttpConn.getInputStream();
}
if (mInputStream == null) {
throw new IOException("open error: " + liveviewUrl);
}
}
/**
* Closes the connection.
*
* @throws IOException generic errors or exception.
*/
public void close() throws IOException {
if (mInputStream != null) {
mInputStream.close();
mInputStream = null;
}
if (mHttpConn != null) {
mHttpConn.disconnect();
mHttpConn = null;
}
}
/**
* Reads liveview stream and slice one Packet. If server is not ready for
* liveview data, this API calling will be blocked until server returns next
* data.
*
* @return Payload data of sliced Packet
* @throws IOException generic errors or exception.
*/
public Payload nextPayload() throws IOException {
if (mInputStream != null) {
// Common Header
int readLength = 1 + 1 + 2 + NUMB_FOUR;
byte[] commonHeader = readBytes(mInputStream, readLength);
if (commonHeader == null || commonHeader.length != readLength) {
throw new IOException("Cannot read stream for common header.");
}
if (commonHeader[0] != (byte) NUMB_FF) {
throw new IOException("Unexpected data format. (Start byte)");
}
if (commonHeader[1] != (byte) 0x01) {
throw new IOException("Unexpected data format. (Payload byte)");
}
// Payload Header
readLength = NUMB_FOUR + NUMB_THREE + 1 + NUMB_FOUR + 1 + NUMB_115;
byte[] payloadHeader = readBytes(mInputStream, readLength);
if (payloadHeader == null || payloadHeader.length != readLength) {
throw new IOException("Cannot read stream for payload header.");
}
if (payloadHeader[0] != (byte) PAYLOAD_ZERO
|| payloadHeader[1] != (byte) PAYLOAD_ONE
|| payloadHeader[2] != (byte) PAYLOAD_TWO
|| payloadHeader[NUMB_THREE] != (byte) PAYLOAD_THREE) {
throw new IOException("Unexpected data format. (Start code)");
}
int jpegSize = bytesToInt(payloadHeader, JPG_TO_INTEGER_STARTINDEX, JPG_TO_INTEGER_COUNTER);
int paddingSize = bytesToInt(payloadHeader, PAD_TO_INTEGER_STARTINDEX, PAD_TO_INTEGER_COUNTER);
// Payload Data
byte[] jpegData = readBytes(mInputStream, jpegSize);
byte[] paddingData = readBytes(mInputStream, paddingSize);
return new Payload(jpegData, paddingData);
}
return null;
}
/**
* Converts byte array to int.
* @param byteData byteData
* @param startIndex Index
* @param count count
* @return ret
*/
private static int bytesToInt(final byte[] byteData, final int startIndex,
final int count) {
int ret = 0;
for (int i = startIndex; i < startIndex + count; i++) {
ret = (ret << RET_VALUE) | (byteData[i] & CONVERT_TO_INT);
}
return ret;
}
/**
* Reads byte array from the indicated input stream.
* @param in inputstream
* @param length length
* @return ret
* @throws IOException IO
*/
private static byte[] readBytes(final InputStream in, final int length)
throws IOException {
ByteArrayOutputStream tmpByteArray = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_MAX_VAL];
while (true) {
int trialReadlen = Math.min(buffer.length,
length - tmpByteArray.size());
int readlen = in.read(buffer, 0, trialReadlen);
if (readlen < 0) {
break;
}
tmpByteArray.write(buffer, 0, readlen);
if (length <= tmpByteArray.size()) {
break;
}
}
byte[] ret = tmpByteArray.toByteArray();
tmpByteArray.close();
return ret;
}
}