package org.jcodec.samples.streaming; import static org.jcodec.codecs.mpeg12.bitstream.PictureHeader.IntraCoded; import static org.jcodec.containers.mps.MPSDemuxer.mediaStream; import static org.jcodec.containers.mps.MPSDemuxer.videoStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.jcodec.codecs.mpeg12.MPEGDecoder; import org.jcodec.codecs.s302.S302MDecoder; import org.jcodec.common.NIOUtils; import org.jcodec.common.model.AudioBuffer; import org.jcodec.common.model.ChannelLabel; import org.jcodec.common.model.Packet; import org.jcodec.common.model.Rational; import org.jcodec.common.model.Size; import org.jcodec.containers.mps.MPSDemuxer; import org.jcodec.containers.mps.MPSDemuxer.PESPacket; import org.jcodec.containers.mps.MTSDemuxer; import org.jcodec.containers.mps.MTSDemuxer.MTSPacket; import org.jcodec.player.filters.MediaInfo; import org.jcodec.samples.streaming.MTSIndex.FrameEntry; import org.jcodec.samples.streaming.MTSIndex.VideoFrameEntry; import org.junit.Assert; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Streaming adaptor for MPEG transport stream container * * @author The JCodec project * */ public class MTSAdapter implements Adapter { private MTSIndex index; private List<AdapterTrack> tracks; private File mtsFile; public MTSAdapter(File mtsFile, MTSIndex index) throws IOException { this.mtsFile = mtsFile; this.index = index; tracks = new ArrayList<AdapterTrack>(); for (Integer sid : index.getStreamIds()) { if (videoStream(sid)) tracks.add(new VideoAdapterTrack(sid)); else if (MPSDemuxer.audioStream(sid)) tracks.add(new AudioAdapterTrack(sid)); } } @Override public AdapterTrack getTrack(int trackNo) { return tracks.get(trackNo); } @Override public List<AdapterTrack> getTracks() { return tracks; } private static final boolean markerStart(ByteBuffer buf) { return buf.get(0) == 0 && buf.get(1) == 0 && buf.get(2) == 1; } public class AudioAdapterTrack implements Adapter.AudioAdapterTrack { protected int sid; protected FileChannel channel; public AudioAdapterTrack(int sid) throws IOException { this.sid = sid; channel = new FileInputStream(mtsFile).getChannel(); channel.position(index.frame(sid, 0).dataOffset); } protected synchronized Packet frame(FrameEntry e) throws IOException { channel.position(e.dataOffset); MTSPacket ts = MTSDemuxer.readPacket(channel); if (ts == null) return null; int guid = ts.pid; Assert.assertEquals(sid, ts.payload.get(3)); List<ByteBuffer> packets = new LinkedList<ByteBuffer>(); PESPacket pes = MPSDemuxer.readPES(ts.payload, 0); int remaining = pes.length <= 0 ? Integer.MAX_VALUE : pes.length; while (remaining > 0) { if (ts.pid == guid && ts.payload != null) { ByteBuffer part = NIOUtils.read(ts.payload, Math.min(remaining, ts.payload.remaining())); packets.add(part); remaining -= part.remaining(); } ts = MTSDemuxer.readPacket(channel); if (ts == null) return null; if (ts.pid == guid && ts.payloadStart) break; } ByteBuffer data = NIOUtils.combine(packets); return new Packet(data, e.pts, 90000, e.duration, e.frameNo, true, null); } @Override public MediaInfo getMediaInfo() throws IOException { Packet frame = getFrame(0); AudioBuffer decoded = new S302MDecoder().decodeFrame(frame.getData(), ByteBuffer.allocate(frame.getData().remaining())); int frames = index.getNumFrames(sid); FrameEntry e = index.frame(sid, frames - 1); long duration = e.pts; return new MediaInfo.AudioInfo("s302", 90000, duration, frames, name(decoded.getFormat().getChannels()), null, decoded.getFormat(), labels(decoded.getFormat().getChannels())); } private String name(int channels) { switch (channels) { case 1: return "Mono"; case 2: return "Stereo 2.0"; case 4: return "Surround 4.0"; case 8: return "Stereo 2.0 + Surround 5.1"; } return null; } private ChannelLabel[] labels(int channels) { switch (channels) { case 1: return new ChannelLabel[] { ChannelLabel.MONO }; case 2: return new ChannelLabel[] { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT }; case 4: return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT }; case 8: return new ChannelLabel[] { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT, ChannelLabel.CENTER, ChannelLabel.LFE }; } return null; } @Override public int search(long pts) throws IOException { FrameEntry e = index.search(sid, pts); return e == null ? -1 : e.frameNo; } @Override public Packet getFrame(int frameId) throws IOException { FrameEntry e = index.frame(sid, frameId); return e == null ? null : frame(e); } void close() throws IOException { channel.close(); } } public class VideoAdapterTrack implements Adapter.VideoAdapterTrack { protected int sid; protected FileChannel channel; public VideoAdapterTrack(int sid) throws IOException { this.sid = sid; channel = new FileInputStream(mtsFile).getChannel(); channel.position(index.frame(sid, 0).dataOffset); } @Override public int search(long pts) throws IOException { FrameEntry e = index.search(sid, pts); return e == null ? -1 : e.frameNo; } @Override public Packet[] getGOP(int frameNo) throws IOException { FrameEntry e = index.frame(sid, frameNo); return e == null ? null : frames(gop((VideoFrameEntry) e)); } @Override public int gopId(int frameNo) { VideoFrameEntry vfe = (VideoFrameEntry) index.frame(sid, frameNo); return vfe == null ? -1 : vfe.gopId; } private List<VideoFrameEntry> gop(VideoFrameEntry cur) throws IOException { List<VideoFrameEntry> result = new ArrayList<VideoFrameEntry>(); int nextGop = Integer.MAX_VALUE; boolean ngAdded = false; for (int i = cur.gopId;; i++) { VideoFrameEntry fe = (VideoFrameEntry) index.frame(sid, i); if (fe == null) break; if (fe.gopId == cur.gopId) { if (i > nextGop && !ngAdded) { result.add((VideoFrameEntry) index.frame(sid, nextGop)); ngAdded = true; } result.add(fe); } if (fe.gopId > nextGop) break; if (fe.gopId > cur.gopId && nextGop != fe.gopId) { nextGop = fe.gopId; } } return result; } private Packet[] frames(List<VideoFrameEntry> gop) throws IOException { Packet[] result = new Packet[gop.size()]; for (int i = 0; i < gop.size(); i++) { result[i] = frame(gop.get(i)); } return result; } protected synchronized Packet frame(VideoFrameEntry e) throws IOException { channel.position(e.dataOffset); MTSPacket ts = MTSDemuxer.readPacket(channel); if (ts == null) return null; int guid = ts.pid; Assert.assertEquals(sid, ts.payload.get(3)); List<ByteBuffer> packets = null; PESPacket pes = null; boolean skip = false; int marker = 0xffffffff; while (ts != null) { int streamId = ts.payload.get(3); if (ts.payloadStart || (pes.length <= 0 && markerStart(ts.payload) && mediaStream(streamId))) { skip = streamId != sid; pes = MPSDemuxer.readPES(ts.payload, 0); } ByteBuffer data = ts.payload; ByteBuffer leading = data.duplicate(); while (data.hasRemaining()) { marker = (marker << 8) | (data.get() & 0xff); if (marker < 0x100 || marker > 0x1b9) continue; if (packets == null && marker == 0x100) { packets = new ArrayList<ByteBuffer>(); } else if (packets != null && (marker == 0x100 || (marker >= 0x1b0 && marker != 0x1b2 && marker != 0x1b5))) { leading.limit(data.position() - 3); packets.add(leading); packets.add(0, index.getExtraData(sid, e.edInd)); Packet pkt = new Packet(NIOUtils.combine(packets), e.pts, 90000, e.duration, e.frameNo, e.frameType == IntraCoded, e.getTapeTimecode()); pkt.setDisplayOrder(e.displayOrder); return pkt; } } if (!skip && packets != null) packets.add(data); do { ts = MTSDemuxer.readPacket(channel); } while (ts != null && (ts.pid != guid || ts.payload == null)); } return null; } private Packet getFrame(int frameId) throws IOException { VideoFrameEntry e = (VideoFrameEntry) index.frame(sid, frameId); return e == null ? null : frame(e); } @Override public MediaInfo getMediaInfo() throws IOException { Packet frame = getFrame(0); Size sz = MPEGDecoder.getSize(frame.getData()); int frames = index.getNumFrames(sid); FrameEntry e = index.frame(sid, frames - 1); long duration = e.pts; return new MediaInfo.VideoInfo("m2v1", 90000, duration, frames, "", null, new Rational(1, 1), sz); } void close() throws IOException { channel.close(); } } @Override public void close() { List<AdapterTrack> tracks2 = tracks; for (AdapterTrack adapterTrack : tracks2) { try { if (adapterTrack instanceof VideoAdapterTrack) ((VideoAdapterTrack) adapterTrack).close(); else ((AudioAdapterTrack) adapterTrack).close(); } catch (Exception e) { e.printStackTrace(); } } } }