package org.jcodec.containers.mps;
import static org.jcodec.common.NIOUtils.getRel;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.jcodec.common.Assert;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.containers.mps.MPSUtils.MPEGMediaDescriptor;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class MTSUtils {
public static enum StreamType {
RESERVED(0x0, false, false),
VIDEO_MPEG1(0x01, true, false),
VIDEO_MPEG2(0x02, true, false),
AUDIO_MPEG1(0x03, false, true),
AUDIO_MPEG2(0x04, false, true),
PRIVATE_SECTION(0x05, false, false),
PRIVATE_DATA(0x06, false, false),
MHEG(0x7, false, false),
DSM_CC(0x8, false, false),
ATM_SYNC(0x9, false, false),
DSM_CC_A(0xa, false, false),
DSM_CC_B(0xb, false, false),
DSM_CC_C(0xc, false, false),
DSM_CC_D(0xd, false, false),
MPEG_AUX(0xe, false, false),
AUDIO_AAC_ADTS(0x0f, false, true),
VIDEO_MPEG4(0x10, true, false),
AUDIO_AAC_LATM(0x11, false, true),
FLEXMUX_PES(0x12, false, false),
FLEXMUX_SEC(0x13, false, false),
DSM_CC_SDP(0x14, false, false),
META_PES(0x15, false, false),
META_SEC(0x16, false, false),
DSM_CC_DATA_CAROUSEL(0x17, false, false),
DSM_CC_OBJ_CAROUSEL(0x18, false, false),
DSM_CC_SDP1(0x19, false, false),
IPMP(0x1a, false, false),
VIDEO_H264(0x1b, true, false),
AUDIO_AAC_RAW(0x1c, false, true),
SUBS(0x1d, false, false),
AUX_3D(0x1e, false, false),
VIDEO_AVC_SVC(0x1f, true, false),
VIDEO_AVC_MVC(0x20, true, false),
VIDEO_J2K(0x21, true, false),
VIDEO_MPEG2_3D(0x22, true, false),
VIDEO_H264_3D(0x23, true, false),
VIDEO_CAVS(0x42, false, true),
IPMP_STREAM(0x7f, false, false),
AUDIO_AC3(0x81, false, true),
AUDIO_DTS(0x8a, false, true);
private int tag;
private boolean video;
private boolean audio;
private static EnumSet<StreamType> typeEnum = EnumSet.allOf(StreamType.class);
private StreamType(int tag, boolean video, boolean audio) {
this.tag = tag;
this.video = video;
this.audio = audio;
}
public static StreamType fromTag(int streamTypeTag) {
for (StreamType streamType : typeEnum) {
if (streamType.tag == streamTypeTag)
return streamType;
}
return null;
}
public int getTag() {
return tag;
}
public boolean isVideo() {
return video;
}
public boolean isAudio() {
return audio;
}
};
public static class Section {
private int sectionNumber;
private int lastSectionNumber;
public int getSectionNumber() {
return sectionNumber;
}
public int getLastSectionNumber() {
return lastSectionNumber;
}
}
public static class PMT extends Section {
private int pcrPid;
private List<Tag> tags;
private List<PMTStream> streams;
public PMT(int pcrPid, List<Tag> tags, List<PMTStream> streams) {
this.pcrPid = pcrPid;
this.tags = tags;
this.streams = streams;
}
public int getPcrPid() {
return pcrPid;
}
public List<Tag> getTags() {
return tags;
}
public List<PMTStream> getStreams() {
return streams;
}
}
public static class Tag {
private int tag;
private ByteBuffer content;
public Tag(int tag, ByteBuffer content) {
this.tag = tag;
this.content = content;
}
public int getTag() {
return tag;
}
public ByteBuffer getContent() {
return content;
}
}
public static class PMTStream {
private int streamTypeTag;
private int pid;
private List<MPEGMediaDescriptor> descriptors;
private StreamType streamType;
public PMTStream(int streamTypeTag, int pid, List<MPEGMediaDescriptor> descriptors) {
this.streamTypeTag = streamTypeTag;
this.pid = pid;
this.descriptors = descriptors;
this.streamType = StreamType.fromTag(streamTypeTag);
}
public int getStreamTypeTag() {
return streamTypeTag;
}
public StreamType getStreamType() {
return streamType;
}
public int getPid() {
return pid;
}
public List<MPEGMediaDescriptor> getDesctiptors() {
return descriptors;
}
}
public static int parsePAT(ByteBuffer data) {
parseSection(data);
int pmtPid = -1;
while (data.remaining() > 4) {
int programNum = data.getShort() & 0xffff;
int w = data.getShort();
if (programNum != 0)
pmtPid = w & 0x1fff;
}
return pmtPid;
}
public static PMT parsePMT(ByteBuffer data) {
parseSection(data);
// PMT itself
int w1 = data.getShort() & 0xffff;
int pcrPid = w1 & 0x1fff;
int w2 = data.getShort() & 0xffff;
int programInfoLength = w2 & 0xfff;
List<Tag> tags = parseTags(NIOUtils.read(data, programInfoLength));
List<PMTStream> streams = new ArrayList<PMTStream>();
while (data.remaining() > 4) {
int streamType = data.get() & 0xff;
int wn = data.getShort() & 0xffff;
int elementaryPid = wn & 0x1fff;
System.out.println(String.format("Elementary stream: [%d,%d]", streamType, elementaryPid));
int wn1 = data.getShort() & 0xffff;
int esInfoLength = wn1 & 0xfff;
ByteBuffer read = NIOUtils.read(data, esInfoLength);
streams.add(new PMTStream(streamType, elementaryPid, MPSUtils.parseDescriptors(read)));
}
return new PMT(pcrPid, tags, streams);
}
public static void parseSection(ByteBuffer data) {
int tableId = data.get() & 0xff;
int w0 = data.getShort() & 0xffff;
int sectionSyntaxIndicator = w0 >> 15;
if (((w0 >> 14) & 1) != 0)
throw new RuntimeException("Invalid PMT");
int sectionLength = w0 & 0xfff;
data.limit(data.position() + sectionLength);
int programNumber = data.getShort() & 0xffff;
int b0 = data.get() & 0xff;
int versionNumber = (b0 >> 1) & 0x1f;
int currentNextIndicator = b0 & 1;
int sectionNumber = data.get() & 0xff;
int lastSectionNumber = data.get() & 0xff;
}
private static void parseEsInfo(ByteBuffer read) {
}
private static List<Tag> parseTags(ByteBuffer bb) {
List<Tag> tags = new ArrayList<Tag>();
while (bb.hasRemaining()) {
int tag = bb.get();
int tagLen = bb.get();
System.out.println(String.format("TAG: [0x%x, 0x%x]", tag, tagLen));
tags.add(new Tag(tag, NIOUtils.read(bb, tagLen)));
}
return tags;
}
public static List<PMTStream> getPrograms(File src) throws IOException {
SeekableByteChannel ch = null;
try {
ch = NIOUtils.readableFileChannel(src);
return getProgramGuids(ch);
} finally {
NIOUtils.closeQuietly(ch);
}
}
public static List<PMTStream> getProgramGuids(SeekableByteChannel in) throws IOException {
PMTExtractor ex = new PMTExtractor();
ex.readTsFile(in);
PMT pmt = ex.getPmt();
return pmt.getStreams();
}
private static class PMTExtractor extends TSReader {
private int pmtGuid = -1;
private PMT pmt;
@Override
protected boolean onPkt(int guid, boolean payloadStart, ByteBuffer tsBuf, long filePos) {
if (guid == 0) {
pmtGuid = parsePAT(tsBuf);
} else if (pmtGuid != -1 && guid == pmtGuid) {
pmt = parsePMT(tsBuf);
return false;
}
return true;
}
public PMT getPmt() {
return pmt;
}
};
public abstract static class TSReader {
// Buffer must have an integral number of MPEG TS packets
public static final int BUFFER_SIZE = 188 << 9;
public void readTsFile(SeekableByteChannel ch) throws IOException {
ch.position(0);
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
for (long pos = ch.position(); ch.read(buf) != -1; pos = ch.position()) {
buf.flip();
while (buf.hasRemaining()) {
ByteBuffer tsBuf = NIOUtils.read(buf, 188);
pos += 188;
Assert.assertEquals(0x47, tsBuf.get() & 0xff);
int guidFlags = ((tsBuf.get() & 0xff) << 8) | (tsBuf.get() & 0xff);
int guid = (int) guidFlags & 0x1fff;
int payloadStart = (guidFlags >> 14) & 0x1;
int b0 = tsBuf.get() & 0xff;
int counter = b0 & 0xf;
if ((b0 & 0x20) != 0) {
NIOUtils.skip(tsBuf, tsBuf.get() & 0xff);
}
boolean sectionSyntax = payloadStart == 1 && (getRel(tsBuf, getRel(tsBuf, 0) + 2) & 0x80) == 0x80;
if (sectionSyntax) {
NIOUtils.skip(tsBuf, tsBuf.get() & 0xff);
}
if (!onPkt(guid, payloadStart == 1, tsBuf, pos - tsBuf.remaining()))
return;
}
buf.flip();
}
}
protected abstract boolean onPkt(int guid, boolean payloadStart, ByteBuffer tsBuf, long filePos);
}
public static int getVideoPid(File src) throws IOException {
List<PMTStream> streams = MTSUtils.getPrograms(src);
for (PMTStream stream : streams) {
if (stream.getStreamType().isVideo())
return stream.getPid();
}
throw new RuntimeException("No video stream");
}
public static int getAudioPid(File src) throws IOException {
List<PMTStream> streams = MTSUtils.getPrograms(src);
for (PMTStream stream : streams) {
if (stream.getStreamType().isVideo())
return stream.getPid();
}
throw new RuntimeException("No video stream");
}
public static int[] getMediaPids(File src) throws IOException {
IntArrayList result = new IntArrayList();
List<PMTStream> streams = MTSUtils.getPrograms(src);
for (PMTStream stream : streams) {
if (stream.getStreamType().isVideo() || stream.getStreamType().isAudio())
result.add(stream.getPid());
}
return result.toArray();
}
}