package org.jcodec.containers.mkv;
import static org.jcodec.common.io.IOUtils.readFileToByteArray;
import static org.jcodec.containers.mkv.MKVType.Block;
import static org.jcodec.containers.mkv.MKVType.BlockGroup;
import static org.jcodec.containers.mkv.MKVType.Cluster;
import static org.jcodec.containers.mkv.MKVType.DocType;
import static org.jcodec.containers.mkv.MKVType.DocTypeReadVersion;
import static org.jcodec.containers.mkv.MKVType.DocTypeVersion;
import static org.jcodec.containers.mkv.MKVType.EBML;
import static org.jcodec.containers.mkv.MKVType.Segment;
import static org.jcodec.containers.mkv.MKVType.SimpleBlock;
import static org.jcodec.containers.mkv.MKVType.Timecode;
import static org.jcodec.containers.mkv.MKVType.findFirst;
import org.jcodec.common.io.FileChannelWrapper;
import org.jcodec.common.io.IOUtils;
import org.jcodec.containers.mkv.boxes.EbmlBase;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlString;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.boxes.MkvBlock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.System;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class MKVMuxerTest {
private MKVTestSuite suite;
@Ignore @Test
public void testRead10Frames() throws Exception {
byte[][] rawFrames = new byte[10][];
rawFrames[0] = readFileToByteArray(new File("src/test/resources/mkv/10frames01.vp8"));
rawFrames[1] = readFileToByteArray(new File("src/test/resources/mkv/10frames02.vp8"));
rawFrames[2] = readFileToByteArray(new File("src/test/resources/mkv/10frames03.vp8"));
rawFrames[3] = readFileToByteArray(new File("src/test/resources/mkv/10frames04.vp8"));
rawFrames[4] = readFileToByteArray(new File("src/test/resources/mkv/10frames05.vp8"));
rawFrames[5] = readFileToByteArray(new File("src/test/resources/mkv/10frames06.vp8"));
rawFrames[6] = readFileToByteArray(new File("src/test/resources/mkv/10frames07.vp8"));
rawFrames[7] = readFileToByteArray(new File("src/test/resources/mkv/10frames08.vp8"));
rawFrames[8] = readFileToByteArray(new File("src/test/resources/mkv/10frames09.vp8"));
rawFrames[9] = readFileToByteArray(new File("src/test/resources/mkv/10frames10.vp8"));
File file = new File("src/test/resources/mkv/10frames.webm");
FileInputStream inputStream = new FileInputStream(file);
MKVParser parser = new MKVParser(new FileChannelWrapper(inputStream.getChannel()));
List<EbmlMaster> tree = null;
try {
tree = parser.parse();
} finally {
if (inputStream != null)
inputStream.close();
inputStream = null;
}
MKVType[] path = { MKVType.Segment, MKVType.Cluster };
EbmlMaster[] cc = MKVType.findAllTree(tree, EbmlMaster.class, path);
inputStream = new FileInputStream(file);
Assert.assertNotNull(cc);
Assert.assertEquals(2, cc.length);
try {
FileChannel frameReadingChannel = inputStream.getChannel();
List<MkvBlock> bs = getBlocksByTrackNumber(cc[0], 1);
Assert.assertNotNull(bs);
Assert.assertEquals(5, bs.size());
for (int i = 0; i < 5; i++) {
FileChannel channel = frameReadingChannel;
ByteBuffer bb = ByteBuffer.allocate(bs.get(i).dataLen);
frameReadingChannel.position(bs.get(i).dataOffset);
frameReadingChannel.read(bb);
ByteBuffer[] frames = bs.get(i).getFrames(bb);
Assert.assertNotNull(frames);
Assert.assertEquals(1, frames.length);
Assert.assertArrayEquals(rawFrames[i], MKVMuxerTest.bufferToArray(frames[0]));
}
bs = getBlocksByTrackNumber(cc[1], 1);
Assert.assertNotNull(bs);
Assert.assertEquals(5, bs.size());
for (int i = 5; i < 10; i++) {
ByteBuffer bb = ByteBuffer.allocate(bs.get(i-5).dataLen);
frameReadingChannel.position(bs.get(i-5).dataOffset);
frameReadingChannel.read(bb);
ByteBuffer[] frames = bs.get(i - 5).getFrames(bb);
Assert.assertNotNull(frames);
Assert.assertEquals(1, frames.length);
Assert.assertArrayEquals(rawFrames[i], MKVMuxerTest.bufferToArray(frames[0]));
}
} finally {
IOUtils.closeQuietly(inputStream);
}
}
@Ignore @Test
public void showKeyFrames() throws IOException {
FileInputStream inputStream = new FileInputStream(suite.test5);
MKVParser p = new MKVParser(new FileChannelWrapper(inputStream.getChannel()));
List<EbmlMaster> t = null;
try {
t = p.parse();
} finally {
IOUtils.closeQuietly(inputStream);
}
MKVType[] path1 = { Segment, Cluster };
for (EbmlMaster c : MKVType.findAllTree(t, EbmlMaster.class, path1)) {
for (EbmlBase e : c.children) {
if (e.type.equals(SimpleBlock)) {
MkvBlock be = (MkvBlock) e;
System.out.println("offset: " + be.size() + " timecode: " + be.timecode);
} else if (e.type.equals(BlockGroup)) {
MKVType[] path = { BlockGroup, MKVType.Block };
MkvBlock be = (MkvBlock) MKVType.findFirst(e, path);
System.out.println("offset: " + be.size() + " timecode: " + be.timecode);
}
}
}
}
public void printTrackInfo() throws Exception {
FileInputStream inputStream = new FileInputStream(suite.test1);
MKVParser parser = new MKVParser(new FileChannelWrapper(inputStream.getChannel()));
List<EbmlMaster> tree = null;
try {
tree = parser.parse();
} finally {
if (inputStream != null)
inputStream.close();
inputStream = null;
}
MKVType[] path = { MKVType.Segment, MKVType.Cues, MKVType.CuePoint, MKVType.CueTime };
EbmlUint[] tcs = MKVType.findAllTree(tree, EbmlUint.class, path);
for (EbmlUint tc : tcs)
System.out.println("CueTime " + tc.getUint() + " " + tc.offset);
}
@Ignore @Test
public void testMatroskaBytes() throws Exception {
Assert.assertArrayEquals(new byte[] { 0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61 }, "matroska".getBytes());
}
@Ignore @Test
public void testEBMLHeaderMuxin() throws Exception {
EbmlMaster ebmlHeaderElem = (EbmlMaster) MKVType.createByType(EBML);
EbmlString docTypeElem = (EbmlString) MKVType.createByType(DocType);
docTypeElem.setString("matroska");
EbmlUint docTypeVersionElem = (EbmlUint) MKVType.createByType(DocTypeVersion);
docTypeVersionElem.setUint(1);
EbmlUint docTypeReadVersionElem = (EbmlUint) MKVType.createByType(DocTypeReadVersion);
docTypeReadVersionElem.setUint(1);
ebmlHeaderElem.add(docTypeElem);
ebmlHeaderElem.add(docTypeVersionElem);
ebmlHeaderElem.add(docTypeReadVersionElem);
ByteBuffer bb = ebmlHeaderElem.getData();
System.out.println("c: " + bb.capacity() + " p: " + bb.position() + " l: " + bb.limit());
}
@Ignore @Test
public void testEbmlMasterMuxig() throws Exception {
EbmlMaster ebmlHeaderElem = (EbmlMaster) MKVType.createByType(EBML);
EbmlString docTypeElem = (EbmlString) MKVType.createByType(DocType);
docTypeElem.setString("matroska");
ebmlHeaderElem.add(docTypeElem);
ByteBuffer bb = ebmlHeaderElem.getData();
Assert.assertArrayEquals(new byte[] { 0x1A, 0x45, (byte) 0xDF, (byte) 0xA3, (byte) 0x8B, 0x42, (byte) 0x82, (byte) 0x88, 0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61 }, bb.array());
}
@Ignore @Test
public void testEmptyEbmlMasterMuxig() throws Exception {
EbmlMaster ebmlHeaderElem = (EbmlMaster) MKVType.createByType(EBML);
ByteBuffer bb = ebmlHeaderElem.getData();
Assert.assertArrayEquals(new byte[] { 0x1A, 0x45, (byte) 0xDF, (byte) 0xA3, (byte) 0x80 }, bb.array());
}
@Before
public void setUp() throws IOException {
suite = MKVTestSuite.read();
if (!suite.isSuitePresent())
Assert.fail("MKV test suite is missing, please download from http://www.matroska.org/downloads/test_w1.html, and save to the path recorded in src/test/resources/mkv/suite.properties");
}
@Ignore @Test
public void copyMuxing() throws Exception {
FileInputStream inputStream = new FileInputStream(suite.test3);
MKVParser parser = new MKVParser(new FileChannelWrapper(inputStream.getChannel()));
List<EbmlMaster> tree = null;
try {
tree = parser.parse();
} finally {
if (inputStream != null)
inputStream.close();
inputStream = null;
}
FileInputStream remuxerInputStream = new FileInputStream(suite.test3);
FileOutputStream os = new FileOutputStream(new File(suite.test3.getParent(), "copy-" + suite.test3.getName()));
try {
FileChannel channel = remuxerInputStream.getChannel();
MKVType[] path = { Segment, Cluster, SimpleBlock };
for (MkvBlock be : MKVType.findAllTree(tree, MkvBlock.class, path)) {
ByteBuffer bb = ByteBuffer.allocate(be.dataLen);
channel.position(be.dataOffset);
int read = channel.read(bb);
bb.flip();
be.readFrames(bb);
}
MKVType[] path1 = { Segment, Cluster, BlockGroup, Block };
for (MkvBlock be : MKVType.findAllTree(tree, MkvBlock.class, path1)) {
ByteBuffer bb = ByteBuffer.allocate(be.dataLen);
channel.position(be.dataOffset);
int read = channel.read(bb);
bb.flip();
be.readFrames(bb);
}
for (EbmlMaster e : tree)
e.mux(new FileChannelWrapper(os.getChannel()));
} finally {
if (remuxerInputStream != null)
remuxerInputStream.close();
if (os != null)
os.close();
}
}
public void testName() throws Exception {
FileInputStream inputStream = new FileInputStream(suite.test3);
MKVParser parser = new MKVParser(new FileChannelWrapper(inputStream.getChannel()));
List<EbmlMaster> tree = null;
try {
tree = parser.parse();
} finally {
if (inputStream != null)
inputStream.close();
inputStream = null;
}
MKVType[] path = { MKVType.Segment, MKVType.Cluster };
EbmlMaster[] cc = MKVType.findAllTree(tree, EbmlMaster.class, path);
printCueTable(cc);
}
private void printCueTable(EbmlMaster[] cc) {
long time = 0;
long predictedOffset = 0;
for (EbmlMaster c : cc) {
long csize = c.size();
MKVType[] path = { MKVType.Cluster, MKVType.Timecode };
System.out.println("cluster " + ((EbmlUint) MKVType.findFirst(c, path)).getUint() + " size: " + csize + " predOffset: " + predictedOffset);
long min = getMinTimecode(c, 1);
long max = getMaxTimecode(c, 1);
while (min <= time && time <= max) {
System.out.println("timecode: " + time + " offset: " + c.offset);
time += 1000;
}
predictedOffset += csize;
}
}
public static long getMinTimecode(EbmlMaster c, int trackNr) {
MKVType[] path = { Cluster, Timecode };
EbmlUint timecode = (EbmlUint) MKVType.findFirst(c, path);
long clusterTimecode = timecode.getUint();
long minTimecode = clusterTimecode;
for (MkvBlock be : getBlocksByTrackNumber(c, trackNr))
if (clusterTimecode + be.timecode < minTimecode)
minTimecode = clusterTimecode + be.timecode;
return minTimecode;
}
public static long getMaxTimecode(EbmlMaster c, int trackNr) {
MKVType[] path = { Cluster, Timecode };
EbmlUint timecode = (EbmlUint) findFirst(c, path);
long clusterTimecode = timecode.getUint();
long maxTimecode = clusterTimecode;
for (MkvBlock be : getBlocksByTrackNumber(c, trackNr))
if (clusterTimecode + be.timecode > maxTimecode)
maxTimecode = clusterTimecode + be.timecode;
return maxTimecode;
}
public static List<MkvBlock> getBlocksByTrackNumber(EbmlMaster c, long nr) {
List<MkvBlock> blocks = new ArrayList<MkvBlock>();
for (EbmlBase child : c.children) {
MkvBlock block = null;
if (child.type.equals(SimpleBlock))
block = (MkvBlock) child;
else if (child.type.equals(BlockGroup)) {
MKVType[] path = { BlockGroup, Block };
block = (MkvBlock) findFirst(child, path);
} else
continue;
if (block.trackNumber == nr)
blocks.add(block);
}
return blocks;
}
@Ignore @Test
public void testBasicMathDivision() throws Exception {
int framesPerCluster = 25;
int i = 0;
Assert.assertEquals(0, i%framesPerCluster);
i=25;
Assert.assertEquals(0, i%framesPerCluster);
i=50;
Assert.assertEquals(0, i%framesPerCluster);
}
@Ignore @Test
public void testBasicLinkedList() throws Exception {
LinkedList<Integer> ll = new LinkedList<Integer>();
ll.add(1);
Assert.assertEquals(Integer.valueOf(1), ll.getLast());
ll.add(2);
Assert.assertEquals(Integer.valueOf(2), ll.getLast());
ll.add(3);
Assert.assertEquals(Integer.valueOf(3), ll.getLast());
}
public static byte[] bufferToArray(ByteBuffer bb) {
byte[] ar = new byte[bb.remaining()];
bb.get(ar);
return ar;
}
}