package org.jcodec.movtool.streaming; import static org.jcodec.movtool.streaming.MovieHelper.produceHeader; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.jcodec.containers.mp4.Brand; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Virtual movie. A movie constructed on-the-fly from virtual track data. * * @author The JCodec project * */ public class VirtualMovie { private PacketChunk[] chunks; private MovieSegment headerChunk; private long size; private VirtualTrack[] tracks; private Brand brand; public VirtualMovie(VirtualTrack... tracks) throws IOException { this(Brand.MP4, tracks); } public VirtualMovie(Brand brand, VirtualTrack... tracks) throws IOException { this.tracks = tracks; this.brand = brand; muxTracks(); } private void muxTracks() throws IOException { List<PacketChunk> chch = new ArrayList<PacketChunk>(); VirtualPacket[] heads = new VirtualPacket[tracks.length], tails = new VirtualPacket[tracks.length]; for (int curChunk = 1;; curChunk++) { int min = -1; for (int i = 0; i < heads.length; i++) { if (heads[i] == null) { heads[i] = tracks[i].nextPacket(); if (heads[i] == null) continue; } min = min == -1 || heads[i].getPts() < heads[min].getPts() ? i : min; } if (min == -1) break; chch.add(new PacketChunk(heads[min], min, curChunk, size)); if (heads[min].getDataLen() >= 0) size += heads[min].getDataLen(); else System.err.println("WARN: Negative frame data len!!!"); tails[min] = heads[min]; heads[min] = tracks[min].nextPacket(); } chunks = chch.toArray(new PacketChunk[0]); long dataSize = size; int headerSize = produceHeader(chunks, tracks, dataSize, brand).remaining(); size += headerSize; for (PacketChunk ch : chch) { ch.offset(headerSize); } headerChunk = headerChunk(produceHeader(chunks, tracks, dataSize, brand)); chunks = chch.toArray(new PacketChunk[0]); } private MovieSegment headerChunk(final ByteBuffer header) { return new MovieSegment() { public ByteBuffer getData() { return header.duplicate(); } public int getNo() { return 0; } public long getPos() { return 0; } public int getDataLen() { return header.remaining(); } }; } public class PacketChunk implements MovieSegment { private VirtualPacket packet; private int track; private int no; private long pos; public PacketChunk(VirtualPacket packet, int track, int no, long pos) { this.packet = packet; this.track = track; this.no = no; this.pos = pos; } public ByteBuffer getData() throws IOException { return packet.getData() == null ? null : packet.getData().duplicate(); } public int getNo() { return no; } public long getPos() { return pos; } public void offset(int off) { pos += off; } public int getDataLen() throws IOException { return packet.getDataLen(); } public VirtualPacket getPacket() { return packet; } public int getTrack() { return track; } } public void close() throws IOException { for (VirtualTrack virtualTrack : tracks) { virtualTrack.close(); } } public MovieSegment getPacketAt(long position) throws IOException { if (position >= 0 && position < headerChunk.getDataLen()) return headerChunk; for (int i = 0; i < chunks.length - 1; i++) { if (chunks[i + 1].getPos() > position) return chunks[i]; } if (position < size) return chunks[chunks.length - 1]; return null; } public MovieSegment getPacketByNo(int no) { if (no > chunks.length) return null; if (no == 0) return headerChunk; return chunks[no - 1]; } public long size() { return size; } }