package se.sics.gvod.ls.video;
import com.onionnetworks.fec.FECCode;
import com.onionnetworks.fec.FECCodeFactory;
import com.onionnetworks.util.Buffer;
import java.nio.charset.Charset;
import java.util.*;
import se.sics.gvod.ls.system.LSConfig;
import se.sics.gvod.video.msgs.EncodedSubPiece;
import se.sics.gvod.video.msgs.Piece;
import se.sics.gvod.video.msgs.SubPiece;
/**
* Class used to encode/decode a Piece (a set of SubPieces).
*
* @author Niklas Wahlén <nwahlen@kth.se>
*/
public class VideoFEC {
// Java FEC from OnionNetworks
// TODO: move this instance into VideoIO, and pass it to the constructor?
private FECCode fec;
// core variables needed
private int k, n, packetSize;
// arrays
private byte[][] sourceData;
private byte[][] encodedData;
private int[] encodedIndices;
// the piece
private Piece piece;
// encoding
private EncodedSubPiece[] encodedSubPieces;
// decoding
private int receivedPieces;
private boolean decoded;
private Map<Integer, EncodedSubPiece> receivedPiecesMap;
// bookkeeping
private Set<Integer> received;
private Set<Integer> missing;
/**
* Constructor used when having less than k sub-pieces (k being the number
* of source sub-pieces). #addEncodedSubPiece is then called until the
* object contains k sub-pieces, whereafter #decode can be called.
*
* @param pieceId ID of the Piece to be decoded.
*/
public VideoFEC(int pieceId) {
/*
* Initialize FEC
*/
piece = new Piece(pieceId);
// source pieces
k = LSConfig.FEC_SUB_PIECES;
// total encoded pieces
n = LSConfig.FEC_ENCODED_PIECES;
packetSize = SubPiece.SUBPIECE_DATA_SIZE;
fec = FECCodeFactory.getDefault().createFECCode(k, n);
receivedPieces = 0;
encodedData = new byte[n][packetSize];
/*
* Prepare decoding
*/
sourceData = new byte[k][packetSize];
// only need k indices for decoding
encodedIndices = new int[k];
received = new HashSet<Integer>();
missing = new HashSet<Integer>();
for (int i = 0; i < n; i++) {
missing.add(i);
}
decoded = false;
receivedPiecesMap = new HashMap<Integer, EncodedSubPiece>();
}
/**
* Constructor used for encoding. After calling this constructor, call
* #getEncodedSubPieces to get the encoded sub-pieces for transmitting.
*
* @param piece The piece which contains the sub-pieces to be transmitted.
* Piece.
*/
public VideoFEC(Piece piece) {
if (piece.getSubPieces().length != LSConfig.FEC_SUB_PIECES) {
throw new IllegalArgumentException("A Piece has to contain "
+ LSConfig.FEC_SUB_PIECES + " sub-pieces "
+ "(this had " + piece.getSubPieces().length + ")");
}
int startGlobalID = piece.getId() * LSConfig.FEC_ENCODED_PIECES;
/*
* Initialize FEC
*/
this.piece = piece;
// source pieces
k = LSConfig.FEC_SUB_PIECES;
// total pieces to send (source+encoded)
n = LSConfig.FEC_ENCODED_PIECES;
packetSize = SubPiece.SUBPIECE_DATA_SIZE;
fec = FECCodeFactory.getDefault().createFECCode(k, n);
receivedPieces = k;
encodedData = new byte[n][packetSize];
// encoding results in n indices
encodedIndices = new int[n];
/*
* Encode
*/
byte[][] pieceData = new byte[k][packetSize];
Buffer[] sourceBuffer = new Buffer[k];
Buffer[] encodedBuffer = new Buffer[n];
for (int i = 0; i < sourceBuffer.length; i++) {
sourceBuffer[i] = new Buffer(Arrays.copyOf(piece.getSubPiece(i).getData(), SubPiece.SUBPIECE_DATA_SIZE));
if (piece.getSubPieces()[i].getId() != i) {
throw new RuntimeException("Encoding error. SubPiece[" + i + "] has id " + piece.getSubPieces()[i].getId());
}
}
for (int i = 0; i < encodedBuffer.length; i++) {
encodedBuffer[i] = new Buffer(encodedData[i]);
encodedIndices[i] = i;
}
// TODO: How it should be done
// for(int e = k, i=0; e < n; e++) {
// encodedIndices[i++] = e;
// }
fec.encode(sourceBuffer, encodedBuffer, encodedIndices);
// Prepare EncodedSubPieces
encodedSubPieces = new EncodedSubPiece[n];
for (int i = 0; i < n; i++) {
encodedSubPieces[i] = new EncodedSubPiece(startGlobalID + i, i, encodedData[i], piece.getId());
if (i < k) {
if (!Arrays.equals(encodedSubPieces[i].getData(), piece.getSubPieces()[i].getData())
|| encodedSubPieces[i].getEncodedIndex() != piece.getSubPieces()[i].getId()
|| encodedSubPieces[i].getParentId() != piece.getId()) {
throw new RuntimeException("Encoding error"
+ "\n - encoded sub-piece[id:" + encodedSubPieces[i].getEncodedIndex() + ", parent: " + encodedSubPieces[i].getParentId() + "]"
+ "\n - original sub-piece[id: " + piece.getSubPieces()[i].getId() + ", parent: " + piece.getId() + "]");
}
}
}
}
public Piece decode() {
if (!isReady()) {
// TODO: change to a more suitable exception class
throw new RuntimeException("This piece is not available for decoding.");
}
if(decoded) {
throw new RuntimeException("This piece (" + piece.getId() + ") was already decoded.");
}
Buffer[] sourceBuffer = new Buffer[k];
for (int i = 0; i < sourceBuffer.length; i++) {
sourceBuffer[i] = new Buffer(sourceData[i]);
}
fec.decode(sourceBuffer, encodedIndices);
SubPiece[] subPieces = new SubPiece[k];
for (int i = 0; i < sourceData.length; i++) {
subPieces[i] = new SubPiece(i, sourceData[i], piece);
}
piece.setSubPieces(subPieces);
decoded = true;
return piece;
}
public EncodedSubPiece[] getEncodedSubPieces() {
return encodedSubPieces;
}
public EncodedSubPiece getEncodedSubPiece(int i) {
return encodedSubPieces[i];
}
public Piece getPiece() {
return piece;
}
public void addEncodedSubPiece(EncodedSubPiece p) {
if (p.getParentId() != piece.getId()) {
throw new IllegalArgumentException("The added sub-piece does not belong to this piece.");
}
if (isReady()) {
throw new IllegalArgumentException("There are already enough sub-pieces added to this piece"
+ " (" + (receivedPieces) + ").");
}
if (received.contains(p.getGlobalId())) {
throw new IllegalArgumentException("This sub-piece was already added to the decoder.");
}
encodedIndices[receivedPieces] = p.getEncodedIndex();
// TODO - possibly don't need to copy this array of bytes.
sourceData[receivedPieces] = Arrays.copyOf(p.getData(),p.getData().length);
receivedPieces++;
// bookkeeping
received.add(p.getGlobalId());
missing.remove(p.getEncodedIndex());
}
public int getNumberOfReceivedPieces() {
return receivedPieces;
}
public boolean isReady() {
return receivedPieces == k;
}
public boolean contains(EncodedSubPiece p) {
return received.contains(p.getGlobalId());
}
public int getId() {
return piece.getId();
}
public Set<Integer> getMissing() {
return missing;
}
public boolean isDecoded() {
return decoded;
}
}