package org.jcodec.containers.mkv;
import static org.jcodec.containers.mkv.Type.Seek;
import static org.jcodec.containers.mkv.Type.SeekHead;
import static org.jcodec.containers.mkv.Type.SeekID;
import static org.jcodec.containers.mkv.Type.SeekPosition;
import static org.jcodec.containers.mkv.ebml.Element.getEbmlSize;
import static org.jcodec.containers.mkv.ebml.UnsignedIntegerElement.getMinByteSizeUnsigned;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.containers.mkv.ebml.BinaryElement;
import org.jcodec.containers.mkv.ebml.Element;
import org.jcodec.containers.mkv.ebml.MasterElement;
import org.jcodec.containers.mkv.ebml.UnsignedIntegerElement;
public class SeekHeadIndexer {
List<SeekHeadIndexer.SeekMock> a = new ArrayList<SeekHeadIndexer.SeekMock>();
long currentDataOffset = 0;
public void add(Element e) {
SeekHeadIndexer.SeekMock z = SeekMock.make(e);
z.dataOffset = currentDataOffset;
z.seekPointerSize = getMinByteSizeUnsigned(z.dataOffset);
currentDataOffset += z.size;
// System.out.println("Added id:"+Reader.printAsHex(z.id)+" offset:"+z.dataOffset+" size:"+z.size+" seekpointer size:"+z.seekPointerSize);
a.add(z);
}
public MasterElement indexSeekHead(){
int seekHeadSize = computeSeekHeadSize();
MasterElement seekHead = Type.createElementByType(SeekHead);
for(SeekHeadIndexer.SeekMock z : a){
MasterElement seek = Type.createElementByType(Seek);
BinaryElement seekId = Type.createElementByType(SeekID);
seekId.setData(z.id);
seek.addChildElement(seekId);
UnsignedIntegerElement seekPosition = Type.createElementByType(Type.SeekPosition);
seekPosition.set(z.dataOffset+seekHeadSize);
if (seekPosition.getData().length != z.seekPointerSize)
System.err.println("estimated size of seekPosition differs from the one actually used. ElementId: "+Reader.printAsHex(z.id)+" "+seekPosition.getData().length+" vs "+z.seekPointerSize);
seek.addChildElement(seekPosition);
seekHead.addChildElement(seek);
}
ByteBuffer mux = seekHead.mux();
if (mux.limit() != seekHeadSize)
System.err.println("estimated size of seekHead differs from the one actually used. "+mux.limit()+" vs "+seekHeadSize);
return seekHead;
}
public int computeSeekHeadSize() {
int seekHeadSize = estimateSize();
boolean reindex = false;
do {
reindex = false;
for (SeekHeadIndexer.SeekMock z : a) {
int minSize = getMinByteSizeUnsigned(z.dataOffset + seekHeadSize);
if (minSize > z.seekPointerSize) {
System.out.println("Size "+seekHeadSize+" seems too small for element "+Reader.printAsHex(z.id)+" increasing size by one.");
z.seekPointerSize +=1;
seekHeadSize += 1;
reindex = true;
break;
} else if (minSize < z.seekPointerSize) {
throw new RuntimeException("Downsizing the index is not well thought through.");
}
}
} while (reindex);
return seekHeadSize;
}
int estimateSize() {
int s = Type.SeekHead.id.length + 1;
s += estimeteSeekSize(a.get(0).id.length, 1);
for (int i = 1; i < a.size(); i++) {
s += estimeteSeekSize(a.get(i).id.length, a.get(i).seekPointerSize);
}
return s;
}
public static int estimeteSeekSize(int idLength, int offsetSizeInBytes) {
int seekIdSize = SeekID.id.length + getEbmlSize(idLength) + idLength;
int seekPositionSize = SeekPosition.id.length + getEbmlSize(offsetSizeInBytes) + offsetSizeInBytes;
int seekSize = Seek.id.length + getEbmlSize(seekIdSize + seekPositionSize) + seekIdSize + seekPositionSize;
return seekSize;
}
public static class SeekMock {
public long dataOffset;
byte[] id;
int size;
int seekPointerSize;
public static SeekHeadIndexer.SeekMock make(Element e) {
SeekHeadIndexer.SeekMock z = new SeekMock();
z.id = e.id;
z.size = (int) e.getSize();
return z;
}
}
}