package org.jcodec.samples.streaming;
import static org.jcodec.codecs.mpeg12.bitstream.PictureHeader.IntraCoded;
import static org.jcodec.codecs.s302.S302MUtils.labels;
import static org.jcodec.codecs.s302.S302MUtils.name;
import static org.jcodec.common.NIOUtils.readableFileChannel;
import static org.jcodec.containers.mps.MPSDemuxer.videoStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.codecs.mpeg12.MPEGDecoder;
import org.jcodec.codecs.s302.S302MDecoder;
import org.jcodec.common.FileChannelWrapper;
import org.jcodec.common.model.AudioBuffer;
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.Track;
import org.jcodec.player.filters.MediaInfo;
import org.jcodec.samples.streaming.MTSIndex.FrameEntry;
import org.jcodec.samples.streaming.MTSIndex.VideoFrameEntry;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Streaming adaptor for MPEG program stream container
*
* @author The JCodec project
*
*/
public class MPSAdapter implements Adapter {
private MTSIndex index;
private List<AdapterTrack> tracks = new ArrayList<AdapterTrack>();
private MPSDemuxer demuxer;
private FileChannelWrapper channel;
public MPSAdapter(File mtsFile, MTSIndex index) throws IOException {
channel = readableFileChannel(mtsFile);
demuxer = new MPSDemuxer(channel);
List<Track> tracks2 = demuxer.getTracks();
for (Track track : tracks2) {
if (videoStream(track.getSid()))
tracks.add(new MPSVideoAdapterTrack(track));
else
tracks.add(new MPSAudioAdapterTrack(track));
}
this.index = index;
}
abstract class MPSAdapterTrack implements AdapterTrack {
protected Track track;
public MPSAdapterTrack(Track track) {
this.track = track;
}
@Override
public int search(long pts) throws IOException {
FrameEntry e = index.search(track.getSid(), pts);
return e == null ? -1 : e.frameNo;
}
}
class MPSAudioAdapterTrack extends MPSAdapterTrack implements Adapter.AudioAdapterTrack {
private int curFrame = -1;
public MPSAudioAdapterTrack(Track track) {
super(track);
}
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(track.getSid());
FrameEntry e = index.frame(track.getSid(), 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()));
}
@Override
public Packet getFrame(int frameId) throws IOException {
FrameEntry e = index.frame(track.getSid(), frameId);
if (e == null)
return null;
synchronized (demuxer) {
if (curFrame != frameId) {
demuxer.seekByte(curFrame);
curFrame = frameId;
}
return frame(e);
}
}
private Packet frame(FrameEntry e) throws IOException {
Packet frame = track.getFrame(ByteBuffer.allocate(0x20000));
return new Packet(frame.getData(), e.pts, 90000, e.duration, e.frameNo, true, null);
}
}
class MPSVideoAdapterTrack extends MPSAdapterTrack implements Adapter.VideoAdapterTrack {
private int curFrame = -1;
public MPSVideoAdapterTrack(Track track) {
super(track);
}
public MediaInfo getMediaInfo() throws IOException {
Packet frame = getFrame(0);
Size sz = MPEGDecoder.getSize(frame.getData());
int frames = index.getNumFrames(track.getSid());
FrameEntry e = index.frame(track.getSid(), frames - 1);
long duration = e.pts;
return new MediaInfo.VideoInfo("m2v1", 90000, duration, frames, "", null, new Rational(1, 1), sz);
}
@Override
public Packet[] getGOP(int frameNo) throws IOException {
FrameEntry e = index.frame(track.getSid(), frameNo);
return e == null ? null : frames(gop((VideoFrameEntry) e));
}
@Override
public int gopId(int frameNo) {
VideoFrameEntry vfe = (VideoFrameEntry) index.frame(track.getSid(), 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(track.getSid(), i);
if (fe == null)
break;
if (fe.gopId == cur.gopId) {
if (i > nextGop && !ngAdded) {
result.add((VideoFrameEntry) index.frame(track.getSid(), 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 Packet frame(VideoFrameEntry e) throws IOException {
Packet frame;
synchronized (demuxer) {
if (e.frameNo != curFrame) {
demuxer.seekByte(e.dataOffset);
curFrame = e.frameNo;
}
frame = track.getFrame(ByteBuffer.allocate(0x40000));
}
if (frame == null)
return null;
// packets.add(0, index.getExtraData(sid, e.edInd));
// NIOUtils.combine(packets)
Packet pkt = new Packet(frame.getData(), e.pts, 90000, e.duration, e.frameNo, e.frameType == IntraCoded,
e.getTapeTimecode());
pkt.setDisplayOrder(e.displayOrder);
return pkt;
}
private Packet getFrame(int frameId) throws IOException {
VideoFrameEntry e = (VideoFrameEntry) index.frame(track.getSid(), frameId);
return e == null ? null : frame(e);
}
}
@Override
public AdapterTrack getTrack(int trackNo) {
return tracks.get(trackNo);
}
@Override
public List<AdapterTrack> getTracks() {
return tracks;
}
@Override
public void close() throws IOException {
channel.close();
}
}