package se.sics.gvod.ls.video;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.TestCase;
import org.junit.Ignore;
import org.junit.Test;
import se.sics.gvod.ls.http.HTTPStreamingClient;
import se.sics.gvod.ls.system.LSConfig;
import se.sics.gvod.ls.system.PieceHandler;
import se.sics.gvod.video.msgs.EncodedSubPiece;
import se.sics.gvod.video.msgs.Piece;
import se.sics.gvod.video.msgs.SubPiece;
/**
*
* @author Niklas Wahlén <nwahlen@kth.se>
*/
public class VideoFECTest extends TestCase {
Map<Integer, VideoFEC> fecs;
Map<Integer, Piece> decodedPieces;
int seed = 17;
Random rnd;
public VideoFECTest(String testName) {
super(testName);
fecs = new HashMap<Integer, VideoFEC>();
decodedPieces = new HashMap<Integer, Piece>();
rnd = new Random(seed);
}
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
// TODO add test methods here. The name must begin with 'test'. For example:
// public void testHello() {}
public void testEncodingDecoding() {
if (LSConfig.FEC_ENCODED_PIECES == LSConfig.FEC_SUB_PIECES) {
return;
}
// create piece
Piece piece = new Piece(0);
SubPiece[] sps = new SubPiece[100];
for (int i = 0; i < sps.length; i++) {
byte[] data = new byte[SubPiece.SUBPIECE_DATA_SIZE];
Arrays.fill(data, (byte) i);
sps[i] = new SubPiece(100 * piece.getId() + i, data, piece);
}
piece.setSubPieces(sps);
System.out.println("Encoding Piece " + piece.getId() + " with " + sps.length + " sub-pieces.");
System.out.println("Using 5 extra pieces for encoding");
VideoFEC fecEncode = new VideoFEC(piece);
VideoFEC fecDecode = new VideoFEC(piece.getId());
assertFalse(fecDecode.isReady());
System.out.print("Transfering sub-pieces ... skipping sub-pieces: ");
int piecesToSkip = LSConfig.FEC_ENCODED_PIECES - LSConfig.FEC_SUB_PIECES;
for (int i = 0; i < fecEncode.getEncodedSubPieces().length; i++) {
if (piecesToSkip > 0) {
if (rnd.nextInt(10) < 3) {
System.out.print(i + " ");
}
piecesToSkip--;
continue;
}
fecDecode.addEncodedSubPiece(fecEncode.getEncodedSubPieces()[i]);
}
System.out.println();
System.out.println(fecDecode.getNumberOfReceivedPieces() + " pieces received");
assertTrue(fecDecode.isReady());
System.out.println("Decoding transfered sub-pieces");
Piece decodedPiece = fecDecode.decode();
assertEquals(piece, decodedPiece);
assertEquals(piece.getId(), decodedPiece.getId());
assertEquals(piece.getSubPieces().length, piece.getSubPieces().length);
for (int i = 0; i < piece.getSubPieces().length || i < decodedPiece.getSubPieces().length; i++) {
SubPiece sp = piece.getSubPieces()[i];
SubPiece decodedSp = decodedPiece.getSubPieces()[i];
for (int j = 0; j < sp.getData().length || j < decodedSp.getData().length; j++) {
assertEquals(sp.getData()[j], decodedSp.getData()[j]);
}
}
System.out.println("Decoded Piece " + decodedPiece.getId()
+ " with " + decodedPiece.getSubPieces().length + " sub-pieces, "
+ SubPiece.SUBPIECE_DATA_SIZE + " bytes in each, "
+ "is equal to original Piece");
}
public void testUsageScenario() {
if (LSConfig.FEC_ENCODED_PIECES == LSConfig.FEC_SUB_PIECES) {
return;
}
System.out.println("### testUsageScenario ###");
int nPieces = 10;
System.out.println("Creating " + nPieces + " Pieces");
System.out.println("Each Piece contains " + LSConfig.FEC_SUB_PIECES + " SubPieces");
System.out.println("Each Piece will be transfered as " + LSConfig.FEC_ENCODED_PIECES + " EncodedSubPieces");
Piece[] pieces = new Piece[nPieces];
List<EncodedSubPiece> encodedSubPieces = new ArrayList<EncodedSubPiece>();
for (int i = 0; i < pieces.length; i++) {
SubPiece[] subPieces = new SubPiece[LSConfig.FEC_SUB_PIECES];
pieces[i] = new Piece(i, subPieces);
for (int j = 0; j < subPieces.length; j++) {
byte[] data = new byte[SubPiece.SUBPIECE_DATA_SIZE];
Arrays.fill(data, (byte) i);
subPieces[j] = new SubPiece(j, data, pieces[i]);
}
VideoFEC fec = new VideoFEC(pieces[i]);
for (int j = 0; j < fec.getEncodedSubPieces().length; j++) {
encodedSubPieces.add(fec.getEncodedSubPiece(j));
}
}
// randomly transfer encoded sub-pieces and let the handler decode them when possible
while (!encodedSubPieces.isEmpty()) {
int ni = rnd.nextInt(encodedSubPieces.size());
deliverEncodedSubPiece(encodedSubPieces.get(ni));
encodedSubPieces.remove(ni);
}
assertEquals(nPieces, decodedPieces.size());
assertEquals(0, fecs.size());
// test consistency
for (int i = 0; i < pieces.length; i++) {
// uncomment for printing more information
// NewPiece decodedPiece = decodedPieces.get(pieces[i].getId());
// System.out.println("Piece " + pieces[i].getId()
// + " == decoded Piece " + decodedPiece.getId()
// + ": " + pieces[i].equals(decodedPiece));
//
// SubPiece[] sps = pieces[i].getSubPieces();
// SubPiece[] decodedSps = decodedPiece.getSubPieces();
// for(int ii = 0; ii < sps.length || ii < decodedSps.length; ii++) {
// System.out.println("\tSubPiece " + sps[ii].getId()
// + " == decodedSubPiece " + decodedSps[ii].getId()
// + ": " + sps[ii].equals(decodedSps[ii]));
// }
assertEquals(pieces[i], decodedPieces.get(pieces[i].getId()));
}
System.out.println("Success. Decoded equals original.");
// Clean up
fecs.clear();
decodedPieces.clear();
}
int n = 15;
int k = 10;
int toBeRemoved = n - k;
public void resetRemove() {
toBeRemoved = n - k;
}
public boolean remove() {
if (toBeRemoved > 0) {
if (Math.random() > 0.6) {
return true;
}
}
return false;
}
public void deliverEncodedSubPiece(EncodedSubPiece p) {
if (decodedPieces.containsKey(p.getParentId())) {
return;
}
VideoFEC fec = fecs.get(p.getParentId());
if (fec == null) {
fec = new VideoFEC(p.getParentId());
fecs.put(p.getParentId(), fec);
System.out.println("deliverEncodedSubPiece(): created VideoFEC instance for Piece " + p.getParentId());
assertEquals(0, fec.getNumberOfReceivedPieces());
}
if (!fec.contains(p)) {
fec.addEncodedSubPiece(p);
if (fec.isReady()) {
Piece piece = fec.decode();
// TODO: make sure no more sub-pieces of this piece is requested
// this is easiest achieved by decoding this piece, encoding it
// again and then adding the sub-pieces to the ThreePhaseGossip.
// - A problem will be finding out the global IDs.
// --- not a problem: just take (for any sub-piece) its global ID
// minus its encoded ID (which is its index in this Piece) to get
// the lowest global ID, and corresponding for the highest
int lowestGlobalID = p.getGlobalId() - p.getEncodedIndex();
//
// Signal to concerned classes that the piece is complete
decodedPieces.put(piece.getId(), piece);
System.out.println("Piece " + piece.getId() + " complete"
+ " (after decoding " + fec.getNumberOfReceivedPieces() + " pieces)");
//
// TODO: remove this FEC from the Map
fecs.remove(piece.getId());
}
}
}
@Ignore
@Test
public void tWithRealFile() {
try {
URL myTestURL = ClassLoader.getSystemClassLoader().getResource("source.mp4");
File sourceFile = new File(myTestURL.toURI());
String destinationFileName = "VideoFECTest.mp4";
System.out.println("Reading and encoding " + sourceFile.getName());
boolean printed = false;
Map<Integer, Piece> sourcePieceBuffer = new HashMap<Integer, Piece>();
List<VideoFEC> sourceFecs = new ArrayList<VideoFEC>();
// source
System.out.println("-- Source encode --");
HTTPStreamingClient stream = new HTTPStreamingClient(sourceFile);
stream.run();
while (stream.hasNextPiece()) {
Piece p = stream.getNextPiece();
VideoFEC fec = new VideoFEC(p);
sourceFecs.add(fec);
if (!printed) {
printed = true;
}
}
System.out.println("Read complete, " + sourceFecs.size() + " pieces read.");
// receiver
System.out.println("-- Transfer, Receiver decode --");
Map<Integer, Piece> pieces = new HashMap<Integer, Piece>();
Map<Integer, VideoFEC> fecs = new HashMap<Integer, VideoFEC>();
Collections.shuffle(sourceFecs);
for (VideoFEC sourceFec : sourceFecs) {
List<EncodedSubPiece> sps = Arrays.asList(sourceFec.getEncodedSubPieces());
Collections.shuffle(sps);
for (EncodedSubPiece sp : sps) {
VideoFEC fec = fecs.get(sp.getParentId());
if (fec == null) {
fec = new VideoFEC(sp.getParentId());
fecs.put(sp.getParentId(), fec);
}
if (!fec.isReady()) {
fec.addEncodedSubPiece(sp);
}
if (fec.isReady() && !pieces.containsKey(fec.getId())) {
pieces.put(fec.getId(), fec.decode());
System.out.println("Piece " + fec.getId() + " decoded");
}
}
}
System.out.println("FEC instances created: " + fecs.size());
PieceHandler.writePieceData(destinationFileName, new ArrayList<Piece>(pieces.values()));
File destinationFile = new File(destinationFileName);
destinationFile.deleteOnExit();
for (int pieceId : sourcePieceBuffer.keySet()) {
Piece sourcePiece = sourcePieceBuffer.get(pieceId);
Piece piece = pieces.get(pieceId);
assert (sourcePiece.equals(piece));
}
assert (destinationFile.length() == sourceFile.length());
// receiver encode
System.out.println("-- Receiver encode --");
printed = false;
List<VideoFEC> receiverFecs = new ArrayList<VideoFEC>();
for (Piece p : pieces.values()) {
VideoFEC fec = new VideoFEC(p);
receiverFecs.add(fec);
if (!printed) {
printed = true;
}
}
// second receiver decode
System.out.println("-- Transfer 2, Receiver 2 decode --");
Map<Integer, Piece> pieces2 = new HashMap<Integer, Piece>();
Map<Integer, VideoFEC> fecs2 = new HashMap<Integer, VideoFEC>();
Collections.shuffle(receiverFecs);
for (VideoFEC receiverFec : receiverFecs) {
List<EncodedSubPiece> sps = Arrays.asList(receiverFec.getEncodedSubPieces());
Collections.shuffle(sps);
for (EncodedSubPiece sp : sps) {
VideoFEC fec = fecs2.get(sp.getParentId());
if (fec == null) {
fec = new VideoFEC(sp.getParentId());
fecs2.put(sp.getParentId(), fec);
}
if (!fec.isReady()) {
fec.addEncodedSubPiece(sp);
}
if (fec.isReady() && !pieces2.containsKey(fec.getId())) {
pieces2.put(fec.getId(), fec.decode());
System.out.println("Piece " + fec.getId() + " decoded");
}
}
}
System.out.println("Fecs created: " + fecs.size());
PieceHandler.writePieceData("2" + destinationFileName, new ArrayList<Piece>(pieces2.values()));
File destinationFile2 = new File("2" + destinationFileName);
destinationFile2.deleteOnExit();
for (int pieceId : sourcePieceBuffer.keySet()) {
Piece sourcePiece = sourcePieceBuffer.get(pieceId);
Piece piece = pieces2.get(pieceId);
assert (sourcePiece.equals(piece));
}
assert (destinationFile2.length() == destinationFile.length());
} catch (URISyntaxException ex) {
Logger.getLogger(VideoFECTest.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(VideoFECTest.class.getName()).log(Level.SEVERE, null, ex);
assert (false);
}
}
}