package org.jcodec.containers.mps;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.transform.Source;
import org.jcodec.common.Assert;
import org.jcodec.common.FileChannelWrapper;
import org.jcodec.common.IntObjectMap;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* MPEG TS demuxer
*
* @author The JCodec project
*
*/
public class MTSDemuxer implements MPEGDemuxer {
private MPSDemuxer psDemuxer;
private SeekableByteChannel tsChannel;
public static Set<Integer> getPrograms(SeekableByteChannel src) throws IOException {
long rem = src.position();
Set<Integer> guids = new HashSet<Integer>();
for (int i = 0; guids.size() == 0 || i < guids.size() * 500; i++) {
MTSPacket pkt = readPacket(src);
if (pkt == null)
break;
if (pkt.payload == null)
continue;
ByteBuffer payload = pkt.payload;
if (!guids.contains(pkt.pid) && (payload.duplicate().getInt() & ~0xff) == 0x100) {
guids.add(pkt.pid);
}
}
src.position(rem);
return guids;
}
public static Set<Integer> getPrograms(File file) throws IOException {
FileChannelWrapper fc = null;
try {
fc = NIOUtils.readableFileChannel(file);
return getPrograms(fc);
} finally {
NIOUtils.closeQuietly(fc);
}
}
public MTSDemuxer(final SeekableByteChannel src, int filterGuid) throws IOException {
this.tsChannel = (new TSChannel(src, filterGuid));
psDemuxer = new MPSDemuxer(this.tsChannel);
}
public List<? extends MPEGDemuxerTrack> getTracks() {
return psDemuxer.getTracks();
}
public List<? extends MPEGDemuxerTrack> getVideoTracks() {
return psDemuxer.getVideoTracks();
}
public List<? extends MPEGDemuxerTrack> getAudioTracks() {
return psDemuxer.getAudioTracks();
}
private static class TSChannel implements SeekableByteChannel {
private SeekableByteChannel src;
private ByteBuffer data;
private int filterGuid;
public TSChannel(SeekableByteChannel source, int filterGuid) {
this.src = source;
this.filterGuid = filterGuid;
}
public boolean isOpen() {
return src.isOpen();
}
public void close() throws IOException {
src.close();
}
public int read(ByteBuffer dst) throws IOException {
while (data == null || !data.hasRemaining()) {
MTSPacket packet = getPacket(src);
if (packet == null)
return -1;
data = packet.payload;
}
int toRead = Math.min(dst.remaining(), data.remaining());
dst.put(NIOUtils.read(data, toRead));
return toRead;
}
public int write(ByteBuffer src) throws IOException {
throw new UnsupportedOperationException();
}
public long position() throws IOException {
return src.position();
}
public SeekableByteChannel position(long newPosition) throws IOException {
src.position(newPosition);
data = null;
return this;
}
public long size() throws IOException {
return src.size();
}
public SeekableByteChannel truncate(long size) throws IOException {
return src.truncate(size);
}
protected MTSPacket getPacket(ReadableByteChannel channel) throws IOException {
MTSPacket pkt;
do {
pkt = readPacket(channel);
if (pkt == null)
return null;
} while (pkt.pid <= 0xf || pkt.pid == 0x1fff || pkt.payload == null);
while (pkt.pid != filterGuid) {
pkt = readPacket(channel);
if (pkt == null)
return null;
}
return pkt;
}
}
public static class MTSPacket {
public ByteBuffer payload;
public boolean payloadStart;
public int pid;
public MTSPacket(int guid, boolean payloadStart, ByteBuffer payload) {
this.pid = guid;
this.payloadStart = payloadStart;
this.payload = payload;
}
}
public static MTSPacket readPacket(ReadableByteChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(188);
if (NIOUtils.read(channel, buffer) != 188)
return null;
buffer.flip();
return parsePacket(buffer);
}
public static MTSPacket parsePacket(ByteBuffer buffer) {
int marker = buffer.get() & 0xff;
Assert.assertEquals(0x47, marker);
int guidFlags = buffer.getShort();
int guid = (int) guidFlags & 0x1fff;
int payloadStart = (guidFlags >> 14) & 0x1;
int b0 = buffer.get() & 0xff;
int counter = b0 & 0xf;
if ((b0 & 0x20) != 0) {
int taken = 0;
taken = (buffer.get() & 0xff) + 1;
NIOUtils.skip(buffer, taken - 1);
}
return new MTSPacket(guid, payloadStart == 1, ((b0 & 0x10) != 0) ? buffer : null);
}
public static int probe(final ByteBuffer b) {
IntObjectMap<List<ByteBuffer>> streams = new IntObjectMap<List<ByteBuffer>>();
while (true) {
try {
ByteBuffer sub = NIOUtils.read(b, 188);
if (sub.remaining() < 188)
break;
MTSPacket tsPkt = parsePacket(sub);
if (tsPkt == null)
break;
List<ByteBuffer> data = streams.get(tsPkt.pid);
if (data == null) {
data = new ArrayList<ByteBuffer>();
streams.put(tsPkt.pid, data);
}
if (tsPkt.payload != null)
data.add(tsPkt.payload);
} catch (Throwable t) {
break;
}
}
int maxScore = 0;
int[] keys = streams.keys();
for (int i : keys) {
int score = MPSDemuxer.probe(NIOUtils.combine(streams.get(i)));
if (score > maxScore)
maxScore = score;
}
return maxScore;
}
@Override
public void seekByte(long offset) throws IOException {
tsChannel.position(offset - (offset % 188));
psDemuxer.reset();
}
}