package org.jcodec.containers.mxf.streaming;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.MP4Util;
import org.jcodec.containers.mp4.boxes.EndianBox.Endian;
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.containers.mxf.MXFConst.MXFCodecMapping;
import org.jcodec.containers.mxf.MXFDemuxer;
import org.jcodec.containers.mxf.MXFDemuxer.MXFDemuxerTrack;
import org.jcodec.containers.mxf.MXFDemuxer.MXFPacket;
import org.jcodec.containers.mxf.model.GenericDescriptor;
import org.jcodec.containers.mxf.model.GenericPictureEssenceDescriptor;
import org.jcodec.containers.mxf.model.GenericSoundEssenceDescriptor;
import org.jcodec.containers.mxf.model.KLV;
import org.jcodec.containers.mxf.model.TimelineTrack;
import org.jcodec.containers.mxf.model.UL;
import org.jcodec.movtool.streaming.VirtualPacket;
import org.jcodec.movtool.streaming.VirtualTrack;
import org.jcodec.movtool.streaming.tracks.ByteChannelPool;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* A virtual track that extracts frames from MXF as it those were from MP4
*
* @author The JCodec project
*
*/
public class MXFVirtualTrack implements VirtualTrack {
private MXFDemuxerTrack track;
private ByteChannelPool fp;
private UL essenceUL;
public MXFVirtualTrack(MXFDemuxerTrack track, ByteChannelPool fp) throws IOException {
this.fp = fp;
this.track = track;
this.essenceUL = track.getEssenceUL();
}
public static MXFDemuxer createDemuxer(SeekableByteChannel channel) throws IOException {
return new PatchedMXFDemuxer(channel);
}
@Override
public VirtualPacket nextPacket() throws IOException {
MXFPacket nextFrame = (MXFPacket) track.nextFrame();
if (nextFrame == null)
return null;
return new MXFVirtualPacket(nextFrame);
}
public class MXFVirtualPacket implements VirtualPacket {
private MXFPacket pkt;
public MXFVirtualPacket(MXFPacket pkt) {
this.pkt = pkt;
}
@Override
public ByteBuffer getData() throws IOException {
SeekableByteChannel ch = null;
try {
ch = fp.getChannel();
ch.position(pkt.getOffset());
KLV kl = KLV.readKL(ch);
while (kl != null && !essenceUL.equals(kl.key)) {
ch.position(ch.position() + kl.len);
kl = KLV.readKL(ch);
}
return kl != null && essenceUL.equals(kl.key) ? NIOUtils.fetchFrom(ch, (int) kl.len) : null;
} finally {
NIOUtils.closeQuietly(ch);
}
}
@Override
public int getDataLen() throws IOException {
return pkt.getLen();
}
@Override
public double getPts() {
return pkt.getPtsD();
}
@Override
public double getDuration() {
return pkt.getDurationD();
}
@Override
public boolean isKeyframe() {
return pkt.isKeyFrame();
}
@Override
public int getFrameNo() {
return (int) pkt.getFrameNo();
}
}
@Override
public SampleEntry getSampleEntry() {
return toSampleEntry(track.getDescriptor());
}
private SampleEntry toSampleEntry(GenericDescriptor d) {
if (track.isVideo()) {
GenericPictureEssenceDescriptor ped = (GenericPictureEssenceDescriptor) d;
VideoSampleEntry se = MP4Muxer.videoSampleEntry(MP4Util.getFourcc(track.getCodec().getCodec()), new Size(
ped.getDisplayWidth(), ped.getDisplayHeight()), "JCodec");
Rational ar = ped.getAspectRatio();
se.add(new PixelAspectExt(
new Rational((int) ((1000 * ar.getNum() * ped.getDisplayHeight()) / (ar.getDen() * ped
.getDisplayWidth())), 1000)));
return se;
} else if (track.isAudio()) {
GenericSoundEssenceDescriptor sed = (GenericSoundEssenceDescriptor) d;
int sampleSize = sed.getQuantizationBits() >> 3;
MXFCodecMapping codec = track.getCodec();
return MP4Muxer.audioSampleEntry(sampleSize == 3 ? "in24" : "sowt", 0, sampleSize, sed.getChannelCount(),
(int) sed.getAudioSamplingRate().asFloat(), codec == MXFCodecMapping.PCM_S16BE ? Endian.BIG_ENDIAN
: Endian.LITTLE_ENDIAN);
}
throw new RuntimeException("Can't get sample entry");
}
@Override
public VirtualEdit[] getEdits() {
return null;
}
@Override
public int getPreferredTimescale() {
return -1;
}
@Override
public void close() {
fp.close();
}
public static class PatchedMXFDemuxer extends MXFDemuxer {
public PatchedMXFDemuxer(SeekableByteChannel ch) throws IOException {
super(ch);
}
@Override
protected MXFDemuxerTrack createTrack(UL ul, TimelineTrack track, GenericDescriptor descriptor)
throws IOException {
return new MXFDemuxerTrack(ul, track, descriptor) {
@Override
public MXFPacket readPacket(long off, int len, long pts, int timescale, int duration, int frameNo, boolean kf)
throws IOException {
return new MXFPacket(null, pts, timescale, duration, frameNo, kf, null, off, len);
}
};
}
}
public int getTrackId() {
return track.getTrackId();
}
}