package org.jcodec.samples.streaming;
import static org.jcodec.common.NIOUtils.readableFileChannel;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
import org.jcodec.containers.mp4.demuxer.AbstractMP4DemuxerTrack;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mp4.demuxer.PCMMP4DemuxerTrack;
import org.jcodec.containers.mp4.demuxer.TimecodeMP4DemuxerTrack;
import org.jcodec.player.filters.MediaInfo;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Streaming adaptor for Quicktime movies
*
* @author The JCodec project
*
*/
public class QTAdapter implements Adapter {
private MP4Demuxer demuxer;
private ArrayList<AdapterTrack> tracks;
private SeekableByteChannel is;
public QTAdapter(File file) throws IOException {
is = readableFileChannel(file);
demuxer = new MP4Demuxer(is);
tracks = new ArrayList<AdapterTrack>();
for (AbstractMP4DemuxerTrack demuxerTrack : demuxer.getTracks()) {
if (demuxerTrack.getBox().isAudio()) {
tracks.add(new QTAudioAdaptorTrack(demuxerTrack));
} else if (demuxerTrack.getBox().isVideo()) {
tracks.add(new QTVideoAdaptorTrack(demuxerTrack, demuxer.getTimecodeTrack()));
}
}
}
@Override
public AdapterTrack getTrack(int trackNo) {
return tracks.get(trackNo);
}
@Override
public List<AdapterTrack> getTracks() {
return tracks;
}
public static class QTVideoAdaptorTrack implements VideoAdapterTrack {
protected AbstractMP4DemuxerTrack demuxerTrack;
private TimecodeMP4DemuxerTrack timecodeTrack;
public QTVideoAdaptorTrack(AbstractMP4DemuxerTrack demuxerTrack, TimecodeMP4DemuxerTrack timecodeTrack) {
this.demuxerTrack = demuxerTrack;
this.timecodeTrack = timecodeTrack;
}
public synchronized int search(long pts) throws IOException {
if (!demuxerTrack.seek(pts))
return -1;
return (int) demuxerTrack.getCurFrame();
}
@Override
public MediaInfo getMediaInfo() {
TrakBox box = demuxerTrack.getBox();
VideoSampleEntry vse = (VideoSampleEntry) box.getSampleEntries()[0];
PixelAspectExt pasp = Box.findFirst(vse, PixelAspectExt.class, "pasp");
Rational r = new Rational(1, 1);
if (pasp != null) {
r = pasp.getRational();
}
return new MediaInfo.VideoInfo(vse.getFourcc(), box.getTimescale(), box.getMediaDuration(),
box.getFrameCount(), box.getName(), null, r, new Size(vse.getWidth(), vse.getHeight()));
}
@Override
public Packet[] getGOP(int gopId) throws IOException {
if (!demuxerTrack.gotoFrame(gopId))
return null;
MP4Packet frames = (MP4Packet) demuxerTrack.nextFrame();
return frames == null ? null : new Packet[] { timecodeTrack == null ? frames : timecodeTrack
.getTimecode(frames) };
}
@Override
public int gopId(int frameNo) {
return frameNo;
}
}
public static class QTAudioAdaptorTrack implements AudioAdapterTrack {
protected AbstractMP4DemuxerTrack demuxerTrack;
public QTAudioAdaptorTrack(AbstractMP4DemuxerTrack demuxerTrack) {
this.demuxerTrack = demuxerTrack;
}
private Packet getFrameNonPCM(int frameNo) throws IOException {
if (demuxerTrack.getCurFrame() != frameNo)
demuxerTrack.gotoFrame(frameNo);
return demuxerTrack.nextFrame();
}
private int searchFramePCM(long pts) throws IOException {
demuxerTrack.seek(pts);
return (int) (demuxerTrack.getCurFrame() >> 11);
}
private Packet getFramePCM(int frameNo) throws IOException {
frameNo <<= 11;
if (demuxerTrack.getCurFrame() != frameNo)
demuxerTrack.gotoFrame(frameNo);
Packet packet = demuxerTrack.nextFrame();
if (packet == null)
return null;
return new Packet(packet.getData(), packet.getPts(), packet.getTimescale(), packet.getDuration(),
packet.getFrameNo() >> 11, packet.isKeyFrame(), packet.getTapeTimecode());
}
@Override
public MediaInfo getMediaInfo() {
TrakBox box = demuxerTrack.getBox();
AudioSampleEntry se = (AudioSampleEntry) box.getSampleEntries()[0];
boolean pcm = demuxerTrack instanceof PCMMP4DemuxerTrack;
return new MediaInfo.AudioInfo(se.getFourcc(), box.getTimescale(), box.getMediaDuration(),
(box.getFrameCount() >> (pcm ? 11 : 0)), box.getName(), null, se.getFormat(), se.getLabels());
}
private boolean isPCM(AbstractMP4DemuxerTrack track) {
SampleEntry se = track.getSampleEntries()[0];
return (se instanceof AudioSampleEntry) && ((AudioSampleEntry) se).isPCM();
}
private boolean isPCM(TrakBox track) {
SampleEntry se = track.getSampleEntries()[0];
return (se instanceof AudioSampleEntry) && ((AudioSampleEntry) se).isPCM();
}
public synchronized int search(long pts) throws IOException {
if (isPCM(demuxerTrack)) {
return searchFramePCM(pts);
} else {
if (!demuxerTrack.seek(pts))
return -1;
return (int) demuxerTrack.getCurFrame();
}
}
@Override
public synchronized Packet getFrame(int frameId) throws IOException {
if (isPCM(demuxerTrack)) {
return getFramePCM(frameId);
} else {
return getFrameNonPCM(frameId);
}
}
}
@Override
public void close() {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}