package org.jcodec.containers.mps.index;
import org.jcodec.codecs.mpeg12.bitstream.GOPHeader;
import org.jcodec.codecs.mpeg12.bitstream.MPEGHeader;
import org.jcodec.codecs.mpeg12.bitstream.PictureCodingExtension;
import org.jcodec.codecs.mpeg12.bitstream.PictureHeader;
import org.jcodec.codecs.mpeg12.bitstream.SequenceExtension;
import org.jcodec.codecs.mpeg12.bitstream.SequenceHeader;
import org.jcodec.common.io.ByteBufferSeekableByteChannel;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.TapeTimecode;
import org.jcodec.containers.mps.MPSUtils;
import org.jcodec.containers.mps.index.MPSIndex.MPSStreamIndex;
import org.jcodec.platform.Platform;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.lang.System;
import java.nio.ByteBuffer;
public class MPSIndexerTest {
private static <T extends MPEGHeader> byte[] toHex(T struct) {
ByteBuffer bb = ByteBuffer.allocate(1024);
struct.write(bb);
bb.flip();
return NIOUtils.toArray(bb);
}
private static byte[] flatten(byte[][] data) {
int total = 0;
for (int i = 0; i < data.length; i++) {
total += data[i].length;
}
byte[] result = new byte[total];
for (int i = 0, off = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++, off++) {
result[off] = data[i][j];
}
}
return result;
}
static byte[] syncMarker = { 0, 0, 1 };
static byte[] mpegStreamParams = flatten(new byte[][] {
//@formatter:off
syncMarker, { (byte) 0xb3 },
toHex(SequenceHeader.createSequenceHeader(1920, 1080, 1, 1, 512 * 1024, 0, 0, null, null)), syncMarker,
{ (byte) 0xb5 }, toHex(SequenceExtension.createSequenceExtension(100, 1, 1, 0, 0, 0, 0, 0, 0, 0)),
syncMarker, { (byte) 0xb8 },
toHex(new GOPHeader(new TapeTimecode((short) 1, (byte) 0, (byte) 0, (byte) 10, false), false, false))
//@formatter:on
});
static byte[] mpegSlices = flatten(new byte[][] {
//@formatter:off
syncMarker, { 0x1, 0, 0, 0 }, syncMarker, { 0x2, 0, 0, 0 }, syncMarker, { 0x3, 0, 0, 0 },
//@formatter:on
});
static byte[] mpegFrame(int tempRef, int frameType) {
return flatten(new byte[][] {
//@formatter:off
syncMarker, { 0 }, toHex(PictureHeader.createPictureHeader(tempRef, frameType, 0, 0, 0, 0, 0)),
syncMarker, { (byte) 0xb5 }, toHex(new PictureCodingExtension()), mpegSlices
//@formatter:on
});
}
static byte[] iFrame = flatten(new byte[][] { mpegStreamParams, mpegFrame(0, 1) });
public byte[] ts(long ts) {
//@formatter:off
// a3 a2 a1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
// 00 00 00 b7 b6 b5 b4 b3 b2 b1 b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00 00 00 c7 c6 c5 c4 c3 c2 c1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d7 d6 d5 d4 d3 d2 d1 d0 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e7 e6 e5 e4 e3 e2 e1
//@formatter:on
return new byte[] { (byte) ((ts >> 29) << 1), (byte) (ts >> 22), (byte) ((ts >> 15) << 1), (byte) (ts >> 7),
(byte) ((ts & 0x7f) << 1) };
}
public byte[] sub(byte[] arr, int off, int length) {
return Platform.copyOfRangeB(arr, off, Math.min(arr.length, off + length));
}
byte[] _pes(int streamId, int pts, int dts, int payloadLen, int off, byte[] es, boolean zeroLen) {
byte[] sub = sub(es, off, payloadLen);
int pesLen = zeroLen ? 0 : sub.length + 13;
return flatten(new byte[][] {
//@formatter:off
{ 0x00, 0x00, 0x01, (byte) streamId, (byte) (pesLen >> 8), (byte) (pesLen & 0xff), (byte) 0x80,
(byte) 0xc0, 10 },
ts(pts), ts(dts), sub });
//@formatter:on
}
byte[] pes(int streamId, int payloadLen, int off, byte[] es, boolean zeroLen) {
byte[] sub = sub(es, off, payloadLen);
int pesLen = zeroLen ? 0 : sub.length + 3;
return flatten(new byte[][] {
//@formatter:off
{ 0x00, 0x00, 0x01, (byte) streamId, (byte) (pesLen >> 8), (byte) (pesLen & 0xff), (byte) 0x80,
(byte) 0x00, 0 },
sub });
//@formatter:on
}
private void printHex(byte[] mpegPS) {
for (int i = 0; i < mpegPS.length; i += 32) {
for (int j = i; j < Math.min(mpegPS.length, i + 32); j++) {
System.out.print(String.format("%02x", mpegPS[j]) + " ");
}
System.out.println();
}
}
@Test
public void testSingleVideoLengthPresentPtsPresent() throws IOException {
byte[][] mpegFrames = {
//@formatter:off
iFrame, mpegFrame(5, 2), mpegFrame(2, 3), mpegFrame(3, 3), mpegFrame(4, 3),
//@formatter:on
};
byte[] mpegES = flatten(mpegFrames);
int pesLen = mpegFrames[1].length / 3;
byte[][] peses = {
//@formatter:off
pes(0xe0, pesLen, 0, mpegES, false), pes(0xe0, pesLen, pesLen, mpegES, false),
_pes(0xe0, 10000, 9995, pesLen, pesLen * 2, mpegES, false),
pes(0xe0, pesLen, pesLen * 3, mpegES, false), pes(0xe0, pesLen, pesLen * 4, mpegES, false),
_pes(0xe0, 13003, 12998, pesLen, pesLen * 5, mpegES, false),
pes(0xe0, pesLen, pesLen * 6, mpegES, false), pes(0xe0, pesLen, pesLen * 7, mpegES, false),
pes(0xe0, pesLen, pesLen * 8, mpegES, false),
_pes(0xe0, 16006, 16001, pesLen, pesLen * 9, mpegES, false),
pes(0xe0, pesLen, pesLen * 10, mpegES, false), pes(0xe0, pesLen, pesLen * 11, mpegES, false),
_pes(0xe0, 19009, 19004, pesLen, pesLen * 12, mpegES, false),
pes(0xe0, pesLen, pesLen * 13, mpegES, false), pes(0xe0, pesLen, pesLen * 14, mpegES, false),
_pes(0xe0, 21012, 21007, pesLen, pesLen * 15, mpegES, false),
pes(0xe0, pesLen, pesLen * 16, mpegES, false), pes(0xe0, pesLen, pesLen * 17, mpegES, false),
pes(0xe0, pesLen, pesLen * 18, mpegES, false),
//@formatter:on
};
byte[] mpegPS = flatten(peses);
printHex(mpegPS);
MPSIndexer mpsIndexer = new MPSIndexer();
mpsIndexer.indexChannel(new ByteBufferSeekableByteChannel(ByteBuffer.wrap(mpegPS)), null);
MPSIndex index = mpsIndexer.serialize();
long[] pesTokens = index.getPesTokens();
Assert.assertEquals(peses.length, pesTokens.length);
for (int i = 0; i < pesTokens.length; i++) {
Assert.assertEquals(i == pesTokens.length - 1 ? (mpegES.length % pesLen) : pesLen, MPSIndex
.payLoadSize(pesTokens[i]));
Assert.assertEquals(peses[i].length, MPSIndex.pesLen(pesTokens[i]));
}
MPSStreamIndex[] streams = index.getStreams();
Assert.assertEquals(1, streams.length);
MPSStreamIndex stream = streams[0];
Assert.assertArrayEquals(new int[] { 10000, 13003, 16006, 19009, 21012 }, stream.getFpts());
Assert.assertArrayEquals(new int[] { mpegFrames[0].length, mpegFrames[1].length, mpegFrames[2].length,
mpegFrames[3].length, mpegFrames[4].length }, stream.fsizes);
Assert.assertArrayEquals(new int[] { 0 }, stream.sync);
}
@Test
public void testInterleavedLengthPresentPtsPresent() throws IOException {
byte[] EMPTY = { 0, 0 };
byte[][] mpegFrames = {
//@formatter:off
iFrame, mpegFrame(5, 2), mpegFrame(2, 3), mpegFrame(3, 3), mpegFrame(4, 3),
//@formatter:on
};
byte[] mpegES = flatten(mpegFrames);
int pesLen = mpegFrames[1].length / 3;
byte[][] peses = {
//@formatter:off
pes(0xe0, pesLen, 0, mpegES, false), pes(0xe0, pesLen, pesLen, mpegES, false),
_pes(0xbd, 10100, 10095, 2, 0, EMPTY, false),
_pes(0xe0, 10000, 9995, pesLen, pesLen * 2, mpegES, false),
pes(0xe0, pesLen, pesLen * 3, mpegES, false), _pes(0xbd, 16100, 16095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 4, mpegES, false),
_pes(0xe0, 13003, 12998, pesLen, pesLen * 5, mpegES, false),
_pes(0xbd, 22100, 22095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 6, mpegES, false),
pes(0xe0, pesLen, pesLen * 7, mpegES, false), _pes(0xbd, 28100, 28095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 8, mpegES, false),
_pes(0xe0, 16006, 16001, pesLen, pesLen * 9, mpegES, false),
_pes(0xbd, 34100, 34095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 10, mpegES, false),
pes(0xe0, pesLen, pesLen * 11, mpegES, false), _pes(0xbd, 40100, 40095, 2, 0, EMPTY, false),
_pes(0xe0, 19009, 19004, pesLen, pesLen * 12, mpegES, false),
pes(0xe0, pesLen, pesLen * 13, mpegES, false), _pes(0xbd, 46100, 46095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 14, mpegES, false),
_pes(0xe0, 21012, 21007, pesLen, pesLen * 15, mpegES, false),
_pes(0xbd, 52100, 52095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 16, mpegES, false),
pes(0xe0, pesLen, pesLen * 17, mpegES, false), _pes(0xbd, 58100, 58095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 18, mpegES, false),
//@formatter:on
};
byte[] mpegPS = flatten(peses);
printHex(mpegPS);
MPSIndexer mpsIndexer = new MPSIndexer();
mpsIndexer.indexChannel(new ByteBufferSeekableByteChannel(ByteBuffer.wrap(mpegPS)), null);
MPSIndex index = mpsIndexer.serialize();
long[] pesTokens = index.getPesTokens();
Assert.assertEquals(peses.length, pesTokens.length);
for (int i = 0; i < pesTokens.length; i++) {
Assert.assertEquals((i % 3) == 2 ? 2
: i == pesTokens.length - 1 ? (mpegES.length % pesLen) : pesLen, MPSIndex
.payLoadSize(pesTokens[i]));
Assert.assertEquals(peses[i].length, MPSIndex.pesLen(pesTokens[i]));
}
MPSStreamIndex[] streams = index.getStreams();
Assert.assertEquals(2, streams.length);
MPSStreamIndex stream = getVideoStream(streams);
Assert.assertArrayEquals(new int[] { 10000, 13003, 16006, 19009, 21012 }, stream.getFpts());
Assert.assertArrayEquals(new int[] { mpegFrames[0].length, mpegFrames[1].length, mpegFrames[2].length,
mpegFrames[3].length, mpegFrames[4].length }, stream.fsizes);
Assert.assertArrayEquals(new int[] { 0 }, stream.sync);
}
@Test
public void testInterleavedNoLengthPtsPresent() throws IOException {
byte[] EMPTY = { 0, 0 };
byte[][] mpegFrames = {
//@formatter:off
iFrame, mpegFrame(5, 2), mpegFrame(2, 3), mpegFrame(3, 3), mpegFrame(4, 3),
//@formatter:on
};
byte[] mpegES = flatten(mpegFrames);
int pesLen = mpegFrames[1].length / 3;
byte[][] peses = {
//@formatter:off
pes(0xe0, pesLen, 0, mpegES, true), pes(0xe0, pesLen, pesLen, mpegES, true),
_pes(0xbd, 10100, 10095, 2, 0, EMPTY, false), _pes(0xe0, 10000, 9995, pesLen, pesLen * 2, mpegES, true),
pes(0xe0, pesLen, pesLen * 3, mpegES, true), _pes(0xbd, 16100, 16095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 4, mpegES, true), _pes(0xe0, 13003, 12998, pesLen, pesLen * 5, mpegES, true),
_pes(0xbd, 22100, 22095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 6, mpegES, true),
pes(0xe0, pesLen, pesLen * 7, mpegES, true), _pes(0xbd, 28100, 28095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 8, mpegES, true), _pes(0xe0, 16006, 16001, pesLen, pesLen * 9, mpegES, true),
_pes(0xbd, 34100, 34095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 10, mpegES, true),
pes(0xe0, pesLen, pesLen * 11, mpegES, true), _pes(0xbd, 40100, 40095, 2, 0, EMPTY, false),
_pes(0xe0, 19009, 19004, pesLen, pesLen * 12, mpegES, true),
pes(0xe0, pesLen, pesLen * 13, mpegES, true), _pes(0xbd, 46100, 46095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 14, mpegES, true),
_pes(0xe0, 21012, 21007, pesLen, pesLen * 15, mpegES, true),
_pes(0xbd, 52100, 52095, 2, 0, EMPTY, false), pes(0xe0, pesLen, pesLen * 16, mpegES, true),
pes(0xe0, pesLen, pesLen * 17, mpegES, true), _pes(0xbd, 58100, 58095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 18, mpegES, true),
//@formatter:on
};
byte[] mpegPS = flatten(peses);
printHex(mpegPS);
MPSIndexer mpsIndexer = new MPSIndexer();
mpsIndexer.indexChannel(new ByteBufferSeekableByteChannel(ByteBuffer.wrap(mpegPS)), null);
MPSIndex index = mpsIndexer.serialize();
long[] pesTokens = index.getPesTokens();
Assert.assertEquals(peses.length, pesTokens.length);
for (int i = 0; i < pesTokens.length; i++) {
Assert.assertEquals((i % 3) == 2 ? 2
: i == pesTokens.length - 1 ? (mpegES.length % pesLen) : pesLen, MPSIndex
.payLoadSize(pesTokens[i]));
Assert.assertEquals(peses[i].length, MPSIndex.pesLen(pesTokens[i]));
}
MPSStreamIndex[] streams = index.getStreams();
Assert.assertEquals(2, streams.length);
MPSStreamIndex stream = getVideoStream(streams);
Assert.assertArrayEquals(new int[] { 10000, 13003, 16006, 19009, 21012 }, stream.getFpts());
Assert.assertArrayEquals(new int[] { mpegFrames[0].length, mpegFrames[1].length, mpegFrames[2].length,
mpegFrames[3].length, mpegFrames[4].length }, stream.fsizes);
Assert.assertArrayEquals(new int[] { 0 }, stream.sync);
}
@Test
public void testInterleavedNoLengthPtsInterpolation() throws IOException {
byte[] EMPTY = { 0, 0 };
byte[][] mpegFrames = {
//@formatter:off
iFrame, mpegFrame(4, 2), mpegFrame(1, 3), mpegFrame(2, 3), mpegFrame(3, 3),
//@formatter:on
};
byte[] mpegES = flatten(mpegFrames);
int pesLen = mpegFrames[1].length / 3;
byte[][] peses = {
//@formatter:off
pes(0xe0, pesLen, 0, mpegES, true), pes(0xe0, pesLen, pesLen, mpegES, true),
_pes(0xbd, 10100, 10095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 2, mpegES, true), pes(0xe0, pesLen, pesLen * 3, mpegES, true),
_pes(0xbd, 16100, 16095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 4, mpegES, true), _pes(0xe0, 22012, 22007, pesLen, pesLen * 5, mpegES, true),
_pes(0xbd, 22100, 22095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 6, mpegES, true), pes(0xe0, pesLen, pesLen * 7, mpegES, true),
_pes(0xbd, 28100, 28095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 8, mpegES, true), pes(0xe0, pesLen, pesLen * 9, mpegES, true),
_pes(0xbd, 34100, 34095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 10, mpegES, true), pes(0xe0, pesLen, pesLen * 11, mpegES, true),
_pes(0xbd, 40100, 40095, 2, 0, EMPTY, false),
_pes(0xe0, 16006, 16001, pesLen, pesLen * 12, mpegES, true),
pes(0xe0, pesLen, pesLen * 13, mpegES, true),
_pes(0xbd, 46100, 46095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 14, mpegES, true), pes(0xe0, pesLen, pesLen * 15, mpegES, true),
_pes(0xbd, 52100, 52095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 16, mpegES, true), pes(0xe0, pesLen, pesLen * 17, mpegES, true),
_pes(0xbd, 58100, 58095, 2, 0, EMPTY, false),
pes(0xe0, pesLen, pesLen * 18, mpegES, true),
//@formatter:on
};
byte[] mpegPS = flatten(peses);
printHex(mpegPS);
MPSIndexer mpsIndexer = new MPSIndexer();
mpsIndexer.indexChannel(new ByteBufferSeekableByteChannel(ByteBuffer.wrap(mpegPS)), null);
MPSIndex index = mpsIndexer.serialize();
long[] pesTokens = index.getPesTokens();
Assert.assertEquals(peses.length, pesTokens.length);
for (int i = 0; i < pesTokens.length; i++) {
Assert.assertEquals((i % 3) == 2 ? 2
: i == pesTokens.length - 1 ? (mpegES.length % pesLen) : pesLen, MPSIndex
.payLoadSize(pesTokens[i]));
Assert.assertEquals(peses[i].length, MPSIndex.pesLen(pesTokens[i]));
}
MPSStreamIndex[] streams = index.getStreams();
Assert.assertEquals(2, streams.length);
MPSStreamIndex stream = getVideoStream(streams);
Assert.assertArrayEquals(new int[] { 10000, 22012, 13003, 16006, 19009 }, stream.getFpts());
Assert.assertArrayEquals(new int[] { mpegFrames[0].length, mpegFrames[1].length, mpegFrames[2].length,
mpegFrames[3].length, mpegFrames[4].length }, stream.fsizes);
Assert.assertArrayEquals(new int[] { 0 }, stream.sync);
}
private MPSStreamIndex getVideoStream(MPSStreamIndex[] streams) {
for (MPSStreamIndex stream : streams) {
if (MPSUtils.videoStream(stream.streamId))
return stream;
}
return null;
}
}