www in docker support
This commit is contained in:
parent
539a848e95
commit
c227fce036
2145 changed files with 399596 additions and 58 deletions
|
|
@ -0,0 +1,13 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChannelBox;
|
||||
import org.jcodec.containers.mp4.boxes.WaveExtension;
|
||||
|
||||
public class AudioBoxes extends Boxes {
|
||||
public AudioBoxes() {
|
||||
this.mappings.put(WaveExtension.fourcc(), WaveExtension.class);
|
||||
this.mappings.put(ChannelBox.fourcc(), ChannelBox.class);
|
||||
this.mappings.put("esds", Box.LeafBox.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.Header;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class BoxFactory implements IBoxFactory {
|
||||
private static IBoxFactory instance = new BoxFactory(new DefaultBoxes());
|
||||
|
||||
private static IBoxFactory audio = new BoxFactory(new AudioBoxes());
|
||||
|
||||
private static IBoxFactory data = new BoxFactory(new DataBoxes());
|
||||
|
||||
private static IBoxFactory sample = new BoxFactory(new SampleBoxes());
|
||||
|
||||
private static IBoxFactory timecode = new BoxFactory(new TimecodeBoxes());
|
||||
|
||||
private static IBoxFactory video = new BoxFactory(new VideoBoxes());
|
||||
|
||||
private static IBoxFactory waveext = new BoxFactory(new WaveExtBoxes());
|
||||
|
||||
private Boxes boxes;
|
||||
|
||||
public static IBoxFactory getDefault() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public BoxFactory(Boxes boxes) {
|
||||
this.boxes = boxes;
|
||||
}
|
||||
|
||||
public Box newBox(Header header) {
|
||||
Class<? extends Box> claz = this.boxes.toClass(header.getFourcc());
|
||||
if (claz == null)
|
||||
return new Box.LeafBox(header);
|
||||
Box box = Platform.newInstance(claz, new Object[] { header });
|
||||
if (box instanceof NodeBox) {
|
||||
NodeBox nodebox = (NodeBox)box;
|
||||
if (nodebox instanceof org.jcodec.containers.mp4.boxes.SampleDescriptionBox) {
|
||||
nodebox.setFactory(sample);
|
||||
} else if (nodebox instanceof org.jcodec.containers.mp4.boxes.VideoSampleEntry) {
|
||||
nodebox.setFactory(video);
|
||||
} else if (nodebox instanceof org.jcodec.containers.mp4.boxes.AudioSampleEntry) {
|
||||
nodebox.setFactory(audio);
|
||||
} else if (nodebox instanceof org.jcodec.containers.mp4.boxes.TimecodeSampleEntry) {
|
||||
nodebox.setFactory(timecode);
|
||||
} else if (nodebox instanceof org.jcodec.containers.mp4.boxes.DataRefBox) {
|
||||
nodebox.setFactory(data);
|
||||
} else if (nodebox instanceof org.jcodec.containers.mp4.boxes.WaveExtension) {
|
||||
nodebox.setFactory(waveext);
|
||||
} else {
|
||||
nodebox.setFactory(this);
|
||||
}
|
||||
}
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.Header;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class BoxUtil {
|
||||
public static Box parseBox(ByteBuffer input, Header childAtom, IBoxFactory factory) {
|
||||
Box box = factory.newBox(childAtom);
|
||||
if (childAtom.getBodySize() < 134217728L) {
|
||||
box.parse(input);
|
||||
return box;
|
||||
}
|
||||
return new Box.LeafBox(Header.createHeader("free", 8L));
|
||||
}
|
||||
|
||||
public static Box parseChildBox(ByteBuffer input, IBoxFactory factory) {
|
||||
ByteBuffer fork = input.duplicate();
|
||||
while (input.remaining() >= 4 && fork.getInt() == 0)
|
||||
input.getInt();
|
||||
if (input.remaining() < 4)
|
||||
return null;
|
||||
Header childAtom = Header.read(input);
|
||||
if (childAtom != null && (long)input.remaining() >= childAtom.getBodySize())
|
||||
return parseBox(NIOUtils.read(input, (int)childAtom.getBodySize()), childAtom, factory);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends Box> T as(Class<T> class1, Box.LeafBox box) {
|
||||
try {
|
||||
T res = Platform.<T>newInstance(class1, new Object[] { box.getHeader() });
|
||||
res.parse(box.getData().duplicate());
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsBox(NodeBox box, String path) {
|
||||
Box b = NodeBox.findFirstPath(box, Box.class, new String[] { path });
|
||||
return (b != null);
|
||||
}
|
||||
|
||||
public static boolean containsBox2(NodeBox box, String path1, String path2) {
|
||||
Box b = NodeBox.findFirstPath(box, Box.class, new String[] { path1, path2 });
|
||||
return (b != null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
|
||||
public abstract class Boxes {
|
||||
protected final Map<String, Class<? extends Box>> mappings = new HashMap<>();
|
||||
|
||||
public Class<? extends Box> toClass(String fourcc) {
|
||||
return this.mappings.get(fourcc);
|
||||
}
|
||||
|
||||
public void override(String fourcc, Class<? extends Box> cls) {
|
||||
this.mappings.put(fourcc, cls);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.mappings.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.jcodec.containers.mp4.boxes.FileTypeBox;
|
||||
|
||||
public final class Brand {
|
||||
public static final Brand MOV = new Brand("qt ", 512, new String[] { "qt " });
|
||||
|
||||
public static final Brand MP4 = new Brand("isom", 512, new String[] { "isom", "iso2", "avc1", "mp41" });
|
||||
|
||||
private FileTypeBox ftyp;
|
||||
|
||||
private Brand(String majorBrand, int version, String[] compatible) {
|
||||
this.ftyp = FileTypeBox.createFileTypeBox(majorBrand, version, Arrays.asList(compatible));
|
||||
}
|
||||
|
||||
public FileTypeBox getFileTypeBox() {
|
||||
return this.ftyp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
public class Chunk {
|
||||
private long offset;
|
||||
|
||||
private long startTv;
|
||||
|
||||
private int sampleCount;
|
||||
|
||||
private int sampleSize;
|
||||
|
||||
private int[] sampleSizes;
|
||||
|
||||
private int sampleDur;
|
||||
|
||||
private int[] sampleDurs;
|
||||
|
||||
private int entry;
|
||||
|
||||
public Chunk(long offset, long startTv, int sampleCount, int sampleSize, int[] sampleSizes, int sampleDur, int[] sampleDurs, int entry) {
|
||||
this.offset = offset;
|
||||
this.startTv = startTv;
|
||||
this.sampleCount = sampleCount;
|
||||
this.sampleSize = sampleSize;
|
||||
this.sampleSizes = sampleSizes;
|
||||
this.sampleDur = sampleDur;
|
||||
this.sampleDurs = sampleDurs;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
|
||||
public long getStartTv() {
|
||||
return this.startTv;
|
||||
}
|
||||
|
||||
public int getSampleCount() {
|
||||
return this.sampleCount;
|
||||
}
|
||||
|
||||
public int getSampleSize() {
|
||||
return this.sampleSize;
|
||||
}
|
||||
|
||||
public int[] getSampleSizes() {
|
||||
return this.sampleSizes;
|
||||
}
|
||||
|
||||
public int getSampleDur() {
|
||||
return this.sampleDur;
|
||||
}
|
||||
|
||||
public int[] getSampleDurs() {
|
||||
return this.sampleDurs;
|
||||
}
|
||||
|
||||
public int getEntry() {
|
||||
return this.entry;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
if (this.sampleDur > 0)
|
||||
return this.sampleDur * this.sampleCount;
|
||||
int sum = 0;
|
||||
for (int j = 0; j < this.sampleDurs.length; j++) {
|
||||
int i = this.sampleDurs[j];
|
||||
sum += i;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
if (this.sampleSize > 0)
|
||||
return (long)(this.sampleSize * this.sampleCount);
|
||||
long sum = 0L;
|
||||
for (int j = 0; j < this.sampleSizes.length; j++) {
|
||||
int i = this.sampleSizes[j];
|
||||
sum += (long)i;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class ChunkReader {
|
||||
private int curChunk;
|
||||
|
||||
private int sampleNo;
|
||||
|
||||
private int s2cIndex;
|
||||
|
||||
private int ttsInd = 0;
|
||||
|
||||
private int ttsSubInd = 0;
|
||||
|
||||
private long chunkTv = 0L;
|
||||
|
||||
private long[] chunkOffsets;
|
||||
|
||||
private SampleToChunkBox.SampleToChunkEntry[] sampleToChunk;
|
||||
|
||||
private SampleSizesBox stsz;
|
||||
|
||||
private TimeToSampleBox.TimeToSampleEntry[] tts;
|
||||
|
||||
private SampleDescriptionBox stsd;
|
||||
|
||||
public ChunkReader(TrakBox trakBox) {
|
||||
TimeToSampleBox stts = trakBox.getStts();
|
||||
this.tts = stts.getEntries();
|
||||
ChunkOffsetsBox stco = trakBox.getStco();
|
||||
ChunkOffsets64Box co64 = trakBox.getCo64();
|
||||
this.stsz = trakBox.getStsz();
|
||||
SampleToChunkBox stsc = trakBox.getStsc();
|
||||
if (stco != null) {
|
||||
this.chunkOffsets = stco.getChunkOffsets();
|
||||
} else {
|
||||
this.chunkOffsets = co64.getChunkOffsets();
|
||||
}
|
||||
this.sampleToChunk = stsc.getSampleToChunk();
|
||||
this.stsd = trakBox.getStsd();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return (this.curChunk < this.chunkOffsets.length);
|
||||
}
|
||||
|
||||
public Chunk next() {
|
||||
if (this.curChunk >= this.chunkOffsets.length)
|
||||
return null;
|
||||
if (this.s2cIndex + 1 < this.sampleToChunk.length && (long)(this.curChunk + 1) == this.sampleToChunk[this.s2cIndex + 1].getFirst())
|
||||
this.s2cIndex++;
|
||||
int sampleCount = this.sampleToChunk[this.s2cIndex].getCount();
|
||||
int[] samplesDur = null;
|
||||
int sampleDur = 0;
|
||||
if (this.ttsSubInd + sampleCount <= this.tts[this.ttsInd].getSampleCount()) {
|
||||
sampleDur = this.tts[this.ttsInd].getSampleDuration();
|
||||
this.ttsSubInd += sampleCount;
|
||||
} else {
|
||||
samplesDur = new int[sampleCount];
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
if (this.ttsSubInd >= this.tts[this.ttsInd].getSampleCount() && this.ttsInd < this.tts.length - 1) {
|
||||
this.ttsSubInd = 0;
|
||||
this.ttsInd++;
|
||||
}
|
||||
samplesDur[i] = this.tts[this.ttsInd].getSampleDuration();
|
||||
this.ttsSubInd++;
|
||||
}
|
||||
}
|
||||
int size = 0;
|
||||
int[] sizes = null;
|
||||
if (this.stsz.getDefaultSize() > 0) {
|
||||
size = getFrameSize();
|
||||
} else {
|
||||
sizes = Platform.copyOfRangeI(this.stsz.getSizes(), this.sampleNo, this.sampleNo + sampleCount);
|
||||
}
|
||||
int dref = this.sampleToChunk[this.s2cIndex].getEntry();
|
||||
Chunk chunk = new Chunk(this.chunkOffsets[this.curChunk], this.chunkTv, sampleCount, size, sizes, sampleDur, samplesDur, dref);
|
||||
this.chunkTv += (long)chunk.getDuration();
|
||||
this.sampleNo += sampleCount;
|
||||
this.curChunk++;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private int getFrameSize() {
|
||||
int size = this.stsz.getDefaultSize();
|
||||
Box box = this.stsd.getBoxes().get(this.sampleToChunk[this.s2cIndex].getEntry() - 1);
|
||||
if (box instanceof AudioSampleEntry)
|
||||
return ((AudioSampleEntry)box).calcFrameSize();
|
||||
return size;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.chunkOffsets.length;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mp4.boxes.AliasBox;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.DataInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.DataRefBox;
|
||||
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
|
||||
public class ChunkWriter {
|
||||
private long[] offsets;
|
||||
|
||||
private SampleEntry[] entries;
|
||||
|
||||
private SeekableByteChannel[] inputs;
|
||||
|
||||
private int curChunk;
|
||||
|
||||
private SeekableByteChannel out;
|
||||
|
||||
byte[] buf;
|
||||
|
||||
private TrakBox trak;
|
||||
|
||||
public ChunkWriter(TrakBox trak, SeekableByteChannel[] inputs, SeekableByteChannel out) {
|
||||
int size;
|
||||
this.buf = new byte[8092];
|
||||
this.entries = trak.getSampleEntries();
|
||||
ChunkOffsetsBox stco = trak.getStco();
|
||||
ChunkOffsets64Box co64 = trak.getCo64();
|
||||
if (stco != null) {
|
||||
size = (stco.getChunkOffsets()).length;
|
||||
} else {
|
||||
size = (co64.getChunkOffsets()).length;
|
||||
}
|
||||
this.inputs = inputs;
|
||||
this.offsets = new long[size];
|
||||
this.out = out;
|
||||
this.trak = trak;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
NodeBox stbl = NodeBox.<NodeBox>findFirstPath(this.trak, NodeBox.class, Box.path("mdia.minf.stbl"));
|
||||
stbl.removeChildren(new String[] { "stco", "co64" });
|
||||
stbl.add(ChunkOffsets64Box.createChunkOffsets64Box(this.offsets));
|
||||
cleanDrefs(this.trak);
|
||||
}
|
||||
|
||||
private void cleanDrefs(TrakBox trak) {
|
||||
MediaInfoBox minf = trak.getMdia().getMinf();
|
||||
DataInfoBox dinf = trak.getMdia().getMinf().getDinf();
|
||||
if (dinf == null) {
|
||||
dinf = DataInfoBox.createDataInfoBox();
|
||||
minf.add(dinf);
|
||||
}
|
||||
DataRefBox dref = dinf.getDref();
|
||||
if (dref == null) {
|
||||
dref = DataRefBox.createDataRefBox();
|
||||
dinf.add(dref);
|
||||
}
|
||||
dref.getBoxes().clear();
|
||||
dref.add(AliasBox.createSelfRef());
|
||||
SampleEntry[] sampleEntries = trak.getSampleEntries();
|
||||
for (int i = 0; i < sampleEntries.length; i++) {
|
||||
SampleEntry entry = sampleEntries[i];
|
||||
entry.setDrefInd((short)1);
|
||||
}
|
||||
}
|
||||
|
||||
private SeekableByteChannel getInput(Chunk chunk) {
|
||||
SampleEntry se = this.entries[chunk.getEntry() - 1];
|
||||
return this.inputs[se.getDrefInd() - 1];
|
||||
}
|
||||
|
||||
public void write(Chunk chunk) throws IOException {
|
||||
SeekableByteChannel input = getInput(chunk);
|
||||
input.setPosition(chunk.getOffset());
|
||||
long pos = this.out.position();
|
||||
this.out.write(NIOUtils.fetchFromChannel(input, (int)chunk.getSize()));
|
||||
this.offsets[this.curChunk++] = pos;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.AliasBox;
|
||||
import org.jcodec.containers.mp4.boxes.UrlBox;
|
||||
|
||||
public class DataBoxes extends Boxes {
|
||||
public DataBoxes() {
|
||||
this.mappings.put(UrlBox.fourcc(), UrlBox.class);
|
||||
this.mappings.put(AliasBox.fourcc(), AliasBox.class);
|
||||
this.mappings.put("cios", AliasBox.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.ClearApertureBox;
|
||||
import org.jcodec.containers.mp4.boxes.ClipRegionBox;
|
||||
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.DataInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.DataRefBox;
|
||||
import org.jcodec.containers.mp4.boxes.EditListBox;
|
||||
import org.jcodec.containers.mp4.boxes.EncodedPixelBox;
|
||||
import org.jcodec.containers.mp4.boxes.FileTypeBox;
|
||||
import org.jcodec.containers.mp4.boxes.GenericMediaInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.HandlerBox;
|
||||
import org.jcodec.containers.mp4.boxes.IListBox;
|
||||
import org.jcodec.containers.mp4.boxes.KeysBox;
|
||||
import org.jcodec.containers.mp4.boxes.LoadSettingsBox;
|
||||
import org.jcodec.containers.mp4.boxes.MediaBox;
|
||||
import org.jcodec.containers.mp4.boxes.MediaHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.MetaBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieExtendsBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieExtendsHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieFragmentBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieFragmentHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.NameBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.PartialSyncSamplesBox;
|
||||
import org.jcodec.containers.mp4.boxes.ProductionApertureBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
|
||||
import org.jcodec.containers.mp4.boxes.SegmentIndexBox;
|
||||
import org.jcodec.containers.mp4.boxes.SegmentTypeBox;
|
||||
import org.jcodec.containers.mp4.boxes.SoundMediaHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.SyncSamplesBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimecodeMediaInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrackExtendsBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrackFragmentBaseMediaDecodeTimeBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrackFragmentBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrackFragmentHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrackHeaderBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrunBox;
|
||||
import org.jcodec.containers.mp4.boxes.UdtaBox;
|
||||
import org.jcodec.containers.mp4.boxes.VideoMediaHeaderBox;
|
||||
|
||||
public class DefaultBoxes extends Boxes {
|
||||
public DefaultBoxes() {
|
||||
this.mappings.put(MovieExtendsBox.fourcc(), MovieExtendsBox.class);
|
||||
this.mappings.put(MovieExtendsHeaderBox.fourcc(), MovieExtendsHeaderBox.class);
|
||||
this.mappings.put(SegmentIndexBox.fourcc(), SegmentIndexBox.class);
|
||||
this.mappings.put(SegmentTypeBox.fourcc(), SegmentTypeBox.class);
|
||||
this.mappings.put(TrackExtendsBox.fourcc(), TrackExtendsBox.class);
|
||||
this.mappings.put(VideoMediaHeaderBox.fourcc(), VideoMediaHeaderBox.class);
|
||||
this.mappings.put(FileTypeBox.fourcc(), FileTypeBox.class);
|
||||
this.mappings.put(MovieBox.fourcc(), MovieBox.class);
|
||||
this.mappings.put(MovieHeaderBox.fourcc(), MovieHeaderBox.class);
|
||||
this.mappings.put(TrakBox.fourcc(), TrakBox.class);
|
||||
this.mappings.put(TrackHeaderBox.fourcc(), TrackHeaderBox.class);
|
||||
this.mappings.put("edts", NodeBox.class);
|
||||
this.mappings.put(EditListBox.fourcc(), EditListBox.class);
|
||||
this.mappings.put(MediaBox.fourcc(), MediaBox.class);
|
||||
this.mappings.put(MediaHeaderBox.fourcc(), MediaHeaderBox.class);
|
||||
this.mappings.put(MediaInfoBox.fourcc(), MediaInfoBox.class);
|
||||
this.mappings.put(HandlerBox.fourcc(), HandlerBox.class);
|
||||
this.mappings.put(DataInfoBox.fourcc(), DataInfoBox.class);
|
||||
this.mappings.put("stbl", NodeBox.class);
|
||||
this.mappings.put(SampleDescriptionBox.fourcc(), SampleDescriptionBox.class);
|
||||
this.mappings.put(TimeToSampleBox.fourcc(), TimeToSampleBox.class);
|
||||
this.mappings.put("stss", SyncSamplesBox.class);
|
||||
this.mappings.put("stps", PartialSyncSamplesBox.class);
|
||||
this.mappings.put(SampleToChunkBox.fourcc(), SampleToChunkBox.class);
|
||||
this.mappings.put(SampleSizesBox.fourcc(), SampleSizesBox.class);
|
||||
this.mappings.put(ChunkOffsetsBox.fourcc(), ChunkOffsetsBox.class);
|
||||
this.mappings.put("keys", KeysBox.class);
|
||||
this.mappings.put(IListBox.fourcc(), IListBox.class);
|
||||
this.mappings.put("mvex", NodeBox.class);
|
||||
this.mappings.put("moof", NodeBox.class);
|
||||
this.mappings.put("traf", NodeBox.class);
|
||||
this.mappings.put("mfra", NodeBox.class);
|
||||
this.mappings.put("skip", NodeBox.class);
|
||||
this.mappings.put(MetaBox.fourcc(), MetaBox.class);
|
||||
this.mappings.put(DataRefBox.fourcc(), DataRefBox.class);
|
||||
this.mappings.put("ipro", NodeBox.class);
|
||||
this.mappings.put("sinf", NodeBox.class);
|
||||
this.mappings.put(ChunkOffsets64Box.fourcc(), ChunkOffsets64Box.class);
|
||||
this.mappings.put(SoundMediaHeaderBox.fourcc(), SoundMediaHeaderBox.class);
|
||||
this.mappings.put("clip", NodeBox.class);
|
||||
this.mappings.put(ClipRegionBox.fourcc(), ClipRegionBox.class);
|
||||
this.mappings.put(LoadSettingsBox.fourcc(), LoadSettingsBox.class);
|
||||
this.mappings.put("tapt", NodeBox.class);
|
||||
this.mappings.put("gmhd", NodeBox.class);
|
||||
this.mappings.put("tmcd", Box.LeafBox.class);
|
||||
this.mappings.put("tref", NodeBox.class);
|
||||
this.mappings.put("clef", ClearApertureBox.class);
|
||||
this.mappings.put("prof", ProductionApertureBox.class);
|
||||
this.mappings.put("enof", EncodedPixelBox.class);
|
||||
this.mappings.put(GenericMediaInfoBox.fourcc(), GenericMediaInfoBox.class);
|
||||
this.mappings.put(TimecodeMediaInfoBox.fourcc(), TimecodeMediaInfoBox.class);
|
||||
this.mappings.put(UdtaBox.fourcc(), UdtaBox.class);
|
||||
this.mappings.put(CompositionOffsetsBox.fourcc(), CompositionOffsetsBox.class);
|
||||
this.mappings.put(NameBox.fourcc(), NameBox.class);
|
||||
this.mappings.put("mdta", Box.LeafBox.class);
|
||||
this.mappings.put(MovieFragmentHeaderBox.fourcc(), MovieFragmentHeaderBox.class);
|
||||
this.mappings.put(TrackFragmentHeaderBox.fourcc(), TrackFragmentHeaderBox.class);
|
||||
this.mappings.put(MovieFragmentBox.fourcc(), MovieFragmentBox.class);
|
||||
this.mappings.put(TrackFragmentBox.fourcc(), TrackFragmentBox.class);
|
||||
this.mappings.put(TrackFragmentBaseMediaDecodeTimeBox.fourcc(), TrackFragmentBaseMediaDecodeTimeBox.class);
|
||||
this.mappings.put(TrunBox.fourcc(), TrunBox.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.Header;
|
||||
|
||||
public interface IBoxFactory {
|
||||
Box newBox(Header paramHeader);
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.common.model.TapeTimecode;
|
||||
|
||||
public class MP4Packet extends Packet {
|
||||
private long mediaPts;
|
||||
|
||||
private int entryNo;
|
||||
|
||||
private long fileOff;
|
||||
|
||||
private int size;
|
||||
|
||||
private boolean psync;
|
||||
|
||||
public static MP4Packet createMP4PacketWithTimecode(MP4Packet other, TapeTimecode timecode) {
|
||||
return createMP4Packet(other.data, other.pts, other.timescale, other.duration, other.frameNo, other.frameType, timecode, other.displayOrder, other.mediaPts, other.entryNo);
|
||||
}
|
||||
|
||||
public static MP4Packet createMP4PacketWithData(MP4Packet other, ByteBuffer frm) {
|
||||
return createMP4Packet(frm, other.pts, other.timescale, other.duration, other.frameNo, other.frameType, other.tapeTimecode, other.displayOrder, other.mediaPts, other.entryNo);
|
||||
}
|
||||
|
||||
public static MP4Packet createMP4Packet(ByteBuffer data, long pts, int timescale, long duration, long frameNo, Packet.FrameType iframe, TapeTimecode tapeTimecode, int displayOrder, long mediaPts, int entryNo) {
|
||||
return new MP4Packet(data, pts, timescale, duration, frameNo, iframe, tapeTimecode, displayOrder, mediaPts, entryNo, 0L, 0, false);
|
||||
}
|
||||
|
||||
public MP4Packet(ByteBuffer data, long pts, int timescale, long duration, long frameNo, Packet.FrameType iframe, TapeTimecode tapeTimecode, int displayOrder, long mediaPts, int entryNo, long fileOff, int size, boolean psync) {
|
||||
super(data, pts, timescale, duration, frameNo, iframe, tapeTimecode, displayOrder);
|
||||
this.mediaPts = mediaPts;
|
||||
this.entryNo = entryNo;
|
||||
this.fileOff = fileOff;
|
||||
this.size = size;
|
||||
this.psync = psync;
|
||||
}
|
||||
|
||||
public int getEntryNo() {
|
||||
return this.entryNo;
|
||||
}
|
||||
|
||||
public long getMediaPts() {
|
||||
return this.mediaPts;
|
||||
}
|
||||
|
||||
public long getFileOff() {
|
||||
return this.fileOff;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public boolean isPsync() {
|
||||
return this.psync;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
public final class MP4TrackType {
|
||||
public static final MP4TrackType VIDEO = new MP4TrackType("vide");
|
||||
|
||||
public static final MP4TrackType SOUND = new MP4TrackType("soun");
|
||||
|
||||
public static final MP4TrackType TIMECODE = new MP4TrackType("tmcd");
|
||||
|
||||
public static final MP4TrackType HINT = new MP4TrackType("hint");
|
||||
|
||||
public static final MP4TrackType TEXT = new MP4TrackType("text");
|
||||
|
||||
public static final MP4TrackType HYPER_TEXT = new MP4TrackType("wtxt");
|
||||
|
||||
public static final MP4TrackType CC = new MP4TrackType("clcp");
|
||||
|
||||
public static final MP4TrackType SUB = new MP4TrackType("sbtl");
|
||||
|
||||
public static final MP4TrackType MUSIC = new MP4TrackType("musi");
|
||||
|
||||
public static final MP4TrackType MPEG1 = new MP4TrackType("MPEG");
|
||||
|
||||
public static final MP4TrackType SPRITE = new MP4TrackType("sprt");
|
||||
|
||||
public static final MP4TrackType TWEEN = new MP4TrackType("twen");
|
||||
|
||||
public static final MP4TrackType CHAPTERS = new MP4TrackType("chap");
|
||||
|
||||
public static final MP4TrackType THREE_D = new MP4TrackType("qd3d");
|
||||
|
||||
public static final MP4TrackType STREAMING = new MP4TrackType("strm");
|
||||
|
||||
public static final MP4TrackType OBJECTS = new MP4TrackType("obje");
|
||||
|
||||
public static final MP4TrackType DATA = new MP4TrackType("url ");
|
||||
|
||||
private static final MP4TrackType[] _values = new MP4TrackType[] {
|
||||
VIDEO, SOUND, TIMECODE, HINT, TEXT, HYPER_TEXT, CC, SUB, MUSIC, MPEG1,
|
||||
SPRITE, TWEEN, CHAPTERS, THREE_D, STREAMING, OBJECTS, DATA };
|
||||
|
||||
private String handler;
|
||||
|
||||
private MP4TrackType(String handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public String getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
public static MP4TrackType fromHandler(String handler) {
|
||||
for (int i = 0; i < _values.length; i++) {
|
||||
MP4TrackType val = _values[i];
|
||||
if (val.getHandler().equals(handler))
|
||||
return val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jcodec.common.AutoFileChannelWrapper;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.io.IOUtils;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.FileTypeBox;
|
||||
import org.jcodec.containers.mp4.boxes.Header;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieFragmentBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
|
||||
public class MP4Util {
|
||||
private static Map<Codec, String> codecMapping = new HashMap<>();
|
||||
|
||||
static {
|
||||
codecMapping.put(Codec.MPEG2, "m2v1");
|
||||
codecMapping.put(Codec.H264, "avc1");
|
||||
codecMapping.put(Codec.J2K, "mjp2");
|
||||
}
|
||||
|
||||
public static class Movie {
|
||||
private FileTypeBox ftyp;
|
||||
|
||||
private MovieBox moov;
|
||||
|
||||
public Movie(FileTypeBox ftyp, MovieBox moov) {
|
||||
this.ftyp = ftyp;
|
||||
this.moov = moov;
|
||||
}
|
||||
|
||||
public FileTypeBox getFtyp() {
|
||||
return this.ftyp;
|
||||
}
|
||||
|
||||
public MovieBox getMoov() {
|
||||
return this.moov;
|
||||
}
|
||||
}
|
||||
|
||||
public static MovieBox createRefMovie(SeekableByteChannel input, String url) throws IOException {
|
||||
MovieBox movie = parseMovieChannel(input);
|
||||
TrakBox[] tracks = movie.getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
trakBox.setDataRef(url);
|
||||
}
|
||||
return movie;
|
||||
}
|
||||
|
||||
public static MovieBox parseMovieChannel(SeekableByteChannel input) throws IOException {
|
||||
for (Atom atom : getRootAtoms(input)) {
|
||||
if ("moov".equals(atom.getHeader().getFourcc()))
|
||||
return (MovieBox)atom.parseBox(input);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Movie createRefFullMovie(SeekableByteChannel input, String url) throws IOException {
|
||||
Movie movie = parseFullMovieChannel(input);
|
||||
TrakBox[] tracks = movie.moov.getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
trakBox.setDataRef(url);
|
||||
}
|
||||
return movie;
|
||||
}
|
||||
|
||||
public static Movie parseFullMovieChannel(SeekableByteChannel input) throws IOException {
|
||||
FileTypeBox ftyp = null;
|
||||
for (Atom atom : getRootAtoms(input)) {
|
||||
if ("ftyp".equals(atom.getHeader().getFourcc())) {
|
||||
ftyp = (FileTypeBox)atom.parseBox(input);
|
||||
continue;
|
||||
}
|
||||
if ("moov".equals(atom.getHeader().getFourcc()))
|
||||
return new Movie(ftyp, (MovieBox)atom.parseBox(input));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<MovieFragmentBox> parseMovieFragments(SeekableByteChannel input) throws IOException {
|
||||
MovieBox moov = null;
|
||||
LinkedList<MovieFragmentBox> fragments = new LinkedList<>();
|
||||
for (Atom atom : getRootAtoms(input)) {
|
||||
if ("moov".equals(atom.getHeader().getFourcc())) {
|
||||
moov = (MovieBox)atom.parseBox(input);
|
||||
continue;
|
||||
}
|
||||
if ("moof".equalsIgnoreCase(atom.getHeader().getFourcc()))
|
||||
fragments.add((MovieFragmentBox)atom.parseBox(input));
|
||||
}
|
||||
for (MovieFragmentBox fragment : fragments)
|
||||
fragment.setMovie(moov);
|
||||
return fragments;
|
||||
}
|
||||
|
||||
public static List<Atom> getRootAtoms(SeekableByteChannel input) throws IOException {
|
||||
input.setPosition(0L);
|
||||
List<Atom> result = new ArrayList<>();
|
||||
long off = 0L;
|
||||
while (off < input.size()) {
|
||||
input.setPosition(off);
|
||||
Header atom = Header.read(NIOUtils.fetchFromChannel(input, 16));
|
||||
if (atom == null)
|
||||
break;
|
||||
result.add(new Atom(atom, off));
|
||||
off += atom.getSize();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Atom findFirstAtomInFile(String fourcc, File input) throws IOException {
|
||||
SeekableByteChannel c = new AutoFileChannelWrapper(input);
|
||||
try {
|
||||
return findFirstAtom(fourcc, c);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(c);
|
||||
}
|
||||
}
|
||||
|
||||
public static Atom findFirstAtom(String fourcc, SeekableByteChannel input) throws IOException {
|
||||
List<Atom> rootAtoms = getRootAtoms(input);
|
||||
for (Atom atom : rootAtoms) {
|
||||
if (fourcc.equals(atom.getHeader().getFourcc()))
|
||||
return atom;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Atom atom(SeekableByteChannel input) throws IOException {
|
||||
long off = input.position();
|
||||
Header atom = Header.read(NIOUtils.fetchFromChannel(input, 16));
|
||||
return (atom == null) ? null : new Atom(atom, off);
|
||||
}
|
||||
|
||||
public static class Atom {
|
||||
private long offset;
|
||||
|
||||
private Header header;
|
||||
|
||||
public Atom(Header header, long offset) {
|
||||
this.header = header;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
|
||||
public Header getHeader() {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
public Box parseBox(SeekableByteChannel input) throws IOException {
|
||||
input.setPosition(this.offset + this.header.headerSize());
|
||||
return BoxUtil.parseBox(NIOUtils.fetchFromChannel(input, (int)this.header.getBodySize()), this.header, BoxFactory.getDefault());
|
||||
}
|
||||
|
||||
public void copy(SeekableByteChannel input, WritableByteChannel out) throws IOException {
|
||||
input.setPosition(this.offset);
|
||||
NIOUtils.copy(input, out, this.header.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
public static MovieBox parseMovie(File source) throws IOException {
|
||||
SeekableByteChannel input = null;
|
||||
try {
|
||||
input = NIOUtils.readableChannel(source);
|
||||
return parseMovieChannel(input);
|
||||
} finally {
|
||||
if (input != null)
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static MovieBox createRefMovieFromFile(File source) throws IOException {
|
||||
SeekableByteChannel input = null;
|
||||
try {
|
||||
input = NIOUtils.readableChannel(source);
|
||||
return createRefMovie(input, "file://" + source.getCanonicalPath());
|
||||
} finally {
|
||||
if (input != null)
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeMovieToFile(File f, MovieBox movie) throws IOException {
|
||||
SeekableByteChannel out = null;
|
||||
try {
|
||||
out = NIOUtils.writableChannel(f);
|
||||
writeMovie(out, movie);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeMovie(SeekableByteChannel out, MovieBox movie) throws IOException {
|
||||
doWriteMovieToChannel(out, movie, 0);
|
||||
}
|
||||
|
||||
public static void doWriteMovieToChannel(SeekableByteChannel out, MovieBox movie, int additionalSize) throws IOException {
|
||||
int sizeHint = estimateMoovBoxSize(movie) + additionalSize;
|
||||
Logger.debug("Using " + sizeHint + " bytes for MOOV box");
|
||||
ByteBuffer buf = ByteBuffer.allocate(sizeHint * 4);
|
||||
movie.write(buf);
|
||||
buf.flip();
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
public static Movie parseFullMovie(File source) throws IOException {
|
||||
SeekableByteChannel input = null;
|
||||
try {
|
||||
input = NIOUtils.readableChannel(source);
|
||||
return parseFullMovieChannel(input);
|
||||
} finally {
|
||||
if (input != null)
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static Movie createRefFullMovieFromFile(File source) throws IOException {
|
||||
SeekableByteChannel input = null;
|
||||
try {
|
||||
input = NIOUtils.readableChannel(source);
|
||||
return createRefFullMovie(input, "file://" + source.getCanonicalPath());
|
||||
} finally {
|
||||
if (input != null)
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeFullMovieToFile(File f, Movie movie) throws IOException {
|
||||
SeekableByteChannel out = null;
|
||||
try {
|
||||
out = NIOUtils.writableChannel(f);
|
||||
writeFullMovie(out, movie);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeFullMovie(SeekableByteChannel out, Movie movie) throws IOException {
|
||||
doWriteFullMovieToChannel(out, movie, 0);
|
||||
}
|
||||
|
||||
public static void doWriteFullMovieToChannel(SeekableByteChannel out, Movie movie, int additionalSize) throws IOException {
|
||||
int sizeHint = estimateMoovBoxSize(movie.getMoov()) + additionalSize;
|
||||
Logger.debug("Using " + sizeHint + " bytes for MOOV box");
|
||||
ByteBuffer buf = ByteBuffer.allocate(sizeHint + 128);
|
||||
movie.getFtyp().write(buf);
|
||||
movie.getMoov().write(buf);
|
||||
buf.flip();
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
public static int estimateMoovBoxSize(MovieBox movie) {
|
||||
return movie.estimateSize() + 4096;
|
||||
}
|
||||
|
||||
public static String getFourcc(Codec codec) {
|
||||
return codecMapping.get(codec);
|
||||
}
|
||||
|
||||
public static ByteBuffer writeBox(Box box, int approxSize) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(approxSize);
|
||||
box.write(buf);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.model.RationalLarge;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.Edit;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimecodeSampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.containers.mp4.demuxer.TimecodeMP4DemuxerTrack;
|
||||
|
||||
public class QTTimeUtil {
|
||||
public static long getEditedDuration(TrakBox track) {
|
||||
List<Edit> edits = track.getEdits();
|
||||
if (edits == null)
|
||||
return track.getDuration();
|
||||
long duration = 0L;
|
||||
for (Edit edit : edits)
|
||||
duration += edit.getDuration();
|
||||
return duration;
|
||||
}
|
||||
|
||||
public static long frameToTimevalue(TrakBox trak, int frameNumber) {
|
||||
TimeToSampleBox stts = NodeBox.<TimeToSampleBox>findFirstPath(trak, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
|
||||
TimeToSampleBox.TimeToSampleEntry[] timeToSamples = stts.getEntries();
|
||||
long pts = 0L;
|
||||
int sttsInd = 0, sttsSubInd = frameNumber;
|
||||
while (sttsSubInd >= timeToSamples[sttsInd].getSampleCount()) {
|
||||
sttsSubInd -= timeToSamples[sttsInd].getSampleCount();
|
||||
pts += (long)(timeToSamples[sttsInd].getSampleCount() * timeToSamples[sttsInd].getSampleDuration());
|
||||
sttsInd++;
|
||||
}
|
||||
return pts + (long)(timeToSamples[sttsInd].getSampleDuration() * sttsSubInd);
|
||||
}
|
||||
|
||||
public static int timevalueToFrame(TrakBox trak, long tv) {
|
||||
TimeToSampleBox.TimeToSampleEntry[] tts = NodeBox.<TimeToSampleBox>findFirstPath(trak, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts")).getEntries();
|
||||
int frame = 0;
|
||||
for (int i = 0; tv > 0L && i < tts.length; i++) {
|
||||
long rem = tv / (long)tts[i].getSampleDuration();
|
||||
tv -= (long)(tts[i].getSampleCount() * tts[i].getSampleDuration());
|
||||
frame = (int)((long)frame + ((tv > 0L) ? (long)tts[i].getSampleCount() : rem));
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
public static long mediaToEdited(TrakBox trak, long mediaTv, int movieTimescale) {
|
||||
if (trak.getEdits() == null)
|
||||
return mediaTv;
|
||||
long accum = 0L;
|
||||
for (Edit edit : trak.getEdits()) {
|
||||
if (mediaTv < edit.getMediaTime())
|
||||
return accum;
|
||||
long duration = trak.rescale(edit.getDuration(), (long)movieTimescale);
|
||||
if (edit.getMediaTime() != -1L && mediaTv >=
|
||||
edit.getMediaTime() && mediaTv < edit.getMediaTime() + duration) {
|
||||
accum += mediaTv - edit.getMediaTime();
|
||||
break;
|
||||
}
|
||||
accum += duration;
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
|
||||
public static long editedToMedia(TrakBox trak, long editedTv, int movieTimescale) {
|
||||
if (trak.getEdits() == null)
|
||||
return editedTv;
|
||||
long accum = 0L;
|
||||
for (Edit edit : trak.getEdits()) {
|
||||
long duration = trak.rescale(edit.getDuration(), (long)movieTimescale);
|
||||
if (accum + duration > editedTv)
|
||||
return edit.getMediaTime() + editedTv - accum;
|
||||
accum += duration;
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
|
||||
public static int qtPlayerFrameNo(MovieBox movie, int mediaFrameNo) {
|
||||
TrakBox videoTrack = movie.getVideoTrack();
|
||||
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
|
||||
return tv2QTFrameNo(movie, editedTv);
|
||||
}
|
||||
|
||||
public static int tv2QTFrameNo(MovieBox movie, long tv) {
|
||||
TrakBox videoTrack = movie.getVideoTrack();
|
||||
TrakBox timecodeTrack = movie.getTimecodeTrack();
|
||||
if (timecodeTrack != null && BoxUtil.containsBox2(videoTrack, "tref", "tmcd"))
|
||||
return timevalueToTimecodeFrame(timecodeTrack, new RationalLarge(tv, (long)videoTrack.getTimescale()),
|
||||
movie.getTimescale());
|
||||
return timevalueToFrame(videoTrack, tv);
|
||||
}
|
||||
|
||||
public static String qtPlayerTime(MovieBox movie, int mediaFrameNo) {
|
||||
TrakBox videoTrack = movie.getVideoTrack();
|
||||
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
|
||||
int sec = (int)(editedTv / (long)videoTrack.getTimescale());
|
||||
return String.format("%02d", sec / 3600) + "_" + String.format("%02d", sec / 3600) + "_" + String.format("%02d", sec % 3600 / 60);
|
||||
}
|
||||
|
||||
public static String qtPlayerTimecodeFromMovie(MovieBox movie, TimecodeMP4DemuxerTrack timecodeTrack, int mediaFrameNo) throws IOException {
|
||||
TrakBox videoTrack = movie.getVideoTrack();
|
||||
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
|
||||
TrakBox tt = timecodeTrack.getBox();
|
||||
int ttTimescale = tt.getTimescale();
|
||||
long ttTv = editedToMedia(tt, editedTv * (long)ttTimescale / (long)videoTrack.getTimescale(), movie.getTimescale());
|
||||
return formatTimecode(
|
||||
timecodeTrack.getBox(),
|
||||
timecodeTrack.getStartTimecode() +
|
||||
timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, (long)ttTimescale),
|
||||
movie.getTimescale()));
|
||||
}
|
||||
|
||||
public static String qtPlayerTimecode(TimecodeMP4DemuxerTrack timecodeTrack, RationalLarge tv, int movieTimescale) throws IOException {
|
||||
TrakBox tt = timecodeTrack.getBox();
|
||||
int ttTimescale = tt.getTimescale();
|
||||
long ttTv = editedToMedia(tt, tv.multiplyS((long)ttTimescale), movieTimescale);
|
||||
return formatTimecode(
|
||||
timecodeTrack.getBox(),
|
||||
timecodeTrack.getStartTimecode() +
|
||||
timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, (long)ttTimescale), movieTimescale));
|
||||
}
|
||||
|
||||
public static int timevalueToTimecodeFrame(TrakBox timecodeTrack, RationalLarge tv, int movieTimescale) {
|
||||
TimecodeSampleEntry se = (TimecodeSampleEntry)timecodeTrack.getSampleEntries()[0];
|
||||
return (int)(2L * tv.multiplyS((long)se.getTimescale()) / (long)se.getFrameDuration() + 1L) / 2;
|
||||
}
|
||||
|
||||
public static String formatTimecode(TrakBox timecodeTrack, int counter) {
|
||||
TimecodeSampleEntry tmcd = NodeBox.<TimecodeSampleEntry>findFirstPath(timecodeTrack, TimecodeSampleEntry.class, Box.path("mdia.minf.stbl.stsd.tmcd"));
|
||||
byte nf = tmcd.getNumFrames();
|
||||
String tc = String.format("%02d", counter % nf);
|
||||
counter /= nf;
|
||||
tc = String.format("%02d", counter % 60) + ":" + String.format("%02d", counter % 60);
|
||||
counter /= 60;
|
||||
tc = String.format("%02d", counter % 60) + ":" + String.format("%02d", counter % 60);
|
||||
counter /= 60;
|
||||
tc = String.format("%02d", counter) + ":" + String.format("%02d", counter);
|
||||
return tc;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.SampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.TimecodeSampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class SampleBoxes extends Boxes {
|
||||
public SampleBoxes() {
|
||||
clear();
|
||||
override("ap4h", VideoSampleEntry.class);
|
||||
override("apch", VideoSampleEntry.class);
|
||||
override("apcn", VideoSampleEntry.class);
|
||||
override("apcs", VideoSampleEntry.class);
|
||||
override("apco", VideoSampleEntry.class);
|
||||
override("avc1", VideoSampleEntry.class);
|
||||
override("cvid", VideoSampleEntry.class);
|
||||
override("jpeg", VideoSampleEntry.class);
|
||||
override("smc ", VideoSampleEntry.class);
|
||||
override("rle ", VideoSampleEntry.class);
|
||||
override("rpza", VideoSampleEntry.class);
|
||||
override("kpcd", VideoSampleEntry.class);
|
||||
override("png ", VideoSampleEntry.class);
|
||||
override("mjpa", VideoSampleEntry.class);
|
||||
override("mjpb", VideoSampleEntry.class);
|
||||
override("SVQ1", VideoSampleEntry.class);
|
||||
override("SVQ3", VideoSampleEntry.class);
|
||||
override("mp4v", VideoSampleEntry.class);
|
||||
override("dvc ", VideoSampleEntry.class);
|
||||
override("dvcp", VideoSampleEntry.class);
|
||||
override("gif ", VideoSampleEntry.class);
|
||||
override("h263", VideoSampleEntry.class);
|
||||
override("tiff", VideoSampleEntry.class);
|
||||
override("raw ", VideoSampleEntry.class);
|
||||
override("2vuY", VideoSampleEntry.class);
|
||||
override("yuv2", VideoSampleEntry.class);
|
||||
override("v308", VideoSampleEntry.class);
|
||||
override("v408", VideoSampleEntry.class);
|
||||
override("v216", VideoSampleEntry.class);
|
||||
override("v410", VideoSampleEntry.class);
|
||||
override("v210", VideoSampleEntry.class);
|
||||
override("m2v1", VideoSampleEntry.class);
|
||||
override("m1v1", VideoSampleEntry.class);
|
||||
override("xd5b", VideoSampleEntry.class);
|
||||
override("dv5n", VideoSampleEntry.class);
|
||||
override("jp2h", VideoSampleEntry.class);
|
||||
override("mjp2", VideoSampleEntry.class);
|
||||
override("ac-3", AudioSampleEntry.class);
|
||||
override("cac3", AudioSampleEntry.class);
|
||||
override("ima4", AudioSampleEntry.class);
|
||||
override("aac ", AudioSampleEntry.class);
|
||||
override("celp", AudioSampleEntry.class);
|
||||
override("hvxc", AudioSampleEntry.class);
|
||||
override("twvq", AudioSampleEntry.class);
|
||||
override(".mp1", AudioSampleEntry.class);
|
||||
override(".mp2", AudioSampleEntry.class);
|
||||
override("midi", AudioSampleEntry.class);
|
||||
override("apvs", AudioSampleEntry.class);
|
||||
override("alac", AudioSampleEntry.class);
|
||||
override("aach", AudioSampleEntry.class);
|
||||
override("aacl", AudioSampleEntry.class);
|
||||
override("aace", AudioSampleEntry.class);
|
||||
override("aacf", AudioSampleEntry.class);
|
||||
override("aacp", AudioSampleEntry.class);
|
||||
override("aacs", AudioSampleEntry.class);
|
||||
override("samr", AudioSampleEntry.class);
|
||||
override("AUDB", AudioSampleEntry.class);
|
||||
override("ilbc", AudioSampleEntry.class);
|
||||
override(Platform.stringFromBytes(new byte[] { (byte)109, (byte)115, (byte)0, (byte)17 }), AudioSampleEntry.class);
|
||||
override(Platform.stringFromBytes(new byte[] { (byte)109, (byte)115, (byte)0, (byte)49 }), AudioSampleEntry.class);
|
||||
override("aes3", AudioSampleEntry.class);
|
||||
override("NONE", AudioSampleEntry.class);
|
||||
override("raw ", AudioSampleEntry.class);
|
||||
override("twos", AudioSampleEntry.class);
|
||||
override("sowt", AudioSampleEntry.class);
|
||||
override("MAC3 ", AudioSampleEntry.class);
|
||||
override("MAC6 ", AudioSampleEntry.class);
|
||||
override("ima4", AudioSampleEntry.class);
|
||||
override("fl32", AudioSampleEntry.class);
|
||||
override("fl64", AudioSampleEntry.class);
|
||||
override("in24", AudioSampleEntry.class);
|
||||
override("in32", AudioSampleEntry.class);
|
||||
override("ulaw", AudioSampleEntry.class);
|
||||
override("alaw", AudioSampleEntry.class);
|
||||
override("dvca", AudioSampleEntry.class);
|
||||
override("QDMC", AudioSampleEntry.class);
|
||||
override("QDM2", AudioSampleEntry.class);
|
||||
override("Qclp", AudioSampleEntry.class);
|
||||
override(".mp3", AudioSampleEntry.class);
|
||||
override("mp4a", AudioSampleEntry.class);
|
||||
override("lpcm", AudioSampleEntry.class);
|
||||
override("tmcd", TimecodeSampleEntry.class);
|
||||
override("time", TimecodeSampleEntry.class);
|
||||
override("c608", SampleEntry.class);
|
||||
override("c708", SampleEntry.class);
|
||||
override("text", SampleEntry.class);
|
||||
override("fdsc", SampleEntry.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
|
||||
|
||||
public class SampleOffsetUtils {
|
||||
public static ByteBuffer getSampleData(int sample, File file) throws IOException {
|
||||
MovieBox moov = MP4Util.parseMovie(file);
|
||||
MediaInfoBox minf = moov.getAudioTracks().get(0).getMdia().getMinf();
|
||||
ChunkOffsetsBox stco = NodeBox.<ChunkOffsetsBox>findFirstPath(minf, ChunkOffsetsBox.class, Box.path("stbl.stco"));
|
||||
SampleToChunkBox stsc = NodeBox.<SampleToChunkBox>findFirstPath(minf, SampleToChunkBox.class, Box.path("stbl.stsc"));
|
||||
SampleSizesBox stsz = NodeBox.<SampleSizesBox>findFirstPath(minf, SampleSizesBox.class, Box.path("stbl.stsz"));
|
||||
long sampleOffset = getSampleOffset(sample, stsc, stco, stsz);
|
||||
MappedByteBuffer map = NIOUtils.mapFile(file);
|
||||
map.position((int)sampleOffset);
|
||||
map.limit(map.position() + stsz.getSizes()[sample]);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static long getSampleOffset(int sample, SampleToChunkBox stsc, ChunkOffsetsBox stco, SampleSizesBox stsz) {
|
||||
int chunkBySample = getChunkBySample(sample, stco, stsc);
|
||||
int firstSampleAtChunk = getFirstSampleAtChunk(chunkBySample, stsc, stco);
|
||||
long offset = stco.getChunkOffsets()[chunkBySample - 1];
|
||||
int[] sizes = stsz.getSizes();
|
||||
for (int i = firstSampleAtChunk; i < sample; i++)
|
||||
offset += (long)sizes[i];
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int getFirstSampleAtChunk(int chunk, SampleToChunkBox stsc, ChunkOffsetsBox stco) {
|
||||
int chunks = (stco.getChunkOffsets()).length;
|
||||
int samples = 0;
|
||||
for (int i = 1; i <= chunks &&
|
||||
i != chunk; i++) {
|
||||
int samplesInChunk = getSamplesInChunk(i, stsc);
|
||||
samples += samplesInChunk;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
public static int getChunkBySample(int sampleOfInterest, ChunkOffsetsBox stco, SampleToChunkBox stsc) {
|
||||
int chunks = (stco.getChunkOffsets()).length;
|
||||
int startSample = 0;
|
||||
int endSample = 0;
|
||||
for (int i = 1; i <= chunks; i++) {
|
||||
int samplesInChunk = getSamplesInChunk(i, stsc);
|
||||
endSample = startSample + samplesInChunk;
|
||||
if (sampleOfInterest >= startSample && sampleOfInterest < endSample)
|
||||
return i;
|
||||
startSample = endSample;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int getSamplesInChunk(int chunk, SampleToChunkBox stsc) {
|
||||
SampleToChunkBox.SampleToChunkEntry[] sampleToChunk = stsc.getSampleToChunk();
|
||||
int sampleCount = 0;
|
||||
for (SampleToChunkBox.SampleToChunkEntry sampleToChunkEntry : sampleToChunk) {
|
||||
if (sampleToChunkEntry.getFirst() > (long)chunk)
|
||||
return sampleCount;
|
||||
sampleCount = sampleToChunkEntry.getCount();
|
||||
}
|
||||
return sampleCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class TimeUtil {
|
||||
public static final long MOV_TIME_OFFSET;
|
||||
|
||||
static {
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
calendar.set(1904, 0, 1, 0, 0, 0);
|
||||
calendar.set(14, 0);
|
||||
MOV_TIME_OFFSET = calendar.getTimeInMillis();
|
||||
}
|
||||
|
||||
public static Date macTimeToDate(int movSec) {
|
||||
return new Date(fromMovTime(movSec));
|
||||
}
|
||||
|
||||
public static long fromMovTime(int movSec) {
|
||||
return (long)movSec * 1000L + MOV_TIME_OFFSET;
|
||||
}
|
||||
|
||||
public static int toMovTime(long millis) {
|
||||
return (int)((millis - MOV_TIME_OFFSET) / 1000L);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
public class TimecodeBoxes extends Boxes {}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.codecs.h264.mp4.AvcCBox;
|
||||
import org.jcodec.containers.mp4.boxes.CleanApertureExtension;
|
||||
import org.jcodec.containers.mp4.boxes.ColorExtension;
|
||||
import org.jcodec.containers.mp4.boxes.FielExtension;
|
||||
import org.jcodec.containers.mp4.boxes.GamaExtension;
|
||||
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
|
||||
|
||||
public class VideoBoxes extends Boxes {
|
||||
public VideoBoxes() {
|
||||
this.mappings.put(PixelAspectExt.fourcc(), PixelAspectExt.class);
|
||||
this.mappings.put(AvcCBox.fourcc(), AvcCBox.class);
|
||||
this.mappings.put(ColorExtension.fourcc(), ColorExtension.class);
|
||||
this.mappings.put(GamaExtension.fourcc(), GamaExtension.class);
|
||||
this.mappings.put(CleanApertureExtension.fourcc(), CleanApertureExtension.class);
|
||||
this.mappings.put(FielExtension.fourcc(), FielExtension.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import org.jcodec.containers.mp4.boxes.EndianBox;
|
||||
import org.jcodec.containers.mp4.boxes.FormatBox;
|
||||
|
||||
public class WaveExtBoxes extends Boxes {
|
||||
public WaveExtBoxes() {
|
||||
this.mappings.put(FormatBox.fourcc(), FormatBox.class);
|
||||
this.mappings.put(EndianBox.fourcc(), EndianBox.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package org.jcodec.containers.mp4;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.Header;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.containers.mp4.muxer.MP4Muxer;
|
||||
|
||||
public class WebOptimizedMP4Muxer extends MP4Muxer {
|
||||
private ByteBuffer header;
|
||||
|
||||
private long headerPos;
|
||||
|
||||
public static WebOptimizedMP4Muxer withOldHeader(SeekableByteChannel output, Brand brand, MovieBox oldHeader) throws IOException {
|
||||
int size = (int)oldHeader.getHeader().getSize();
|
||||
TrakBox vt = oldHeader.getVideoTrack();
|
||||
SampleToChunkBox stsc = vt.getStsc();
|
||||
size -= (stsc.getSampleToChunk()).length * 12;
|
||||
size += 12;
|
||||
ChunkOffsetsBox stco = vt.getStco();
|
||||
if (stco != null) {
|
||||
size -= (stco.getChunkOffsets()).length << 2;
|
||||
size += vt.getFrameCount() << 3;
|
||||
} else {
|
||||
ChunkOffsets64Box co64 = vt.getCo64();
|
||||
size -= (co64.getChunkOffsets()).length << 3;
|
||||
size += vt.getFrameCount() << 3;
|
||||
}
|
||||
return new WebOptimizedMP4Muxer(output, brand, size + (size >> 1));
|
||||
}
|
||||
|
||||
public WebOptimizedMP4Muxer(SeekableByteChannel output, Brand brand, int headerSize) throws IOException {
|
||||
super(output, brand.getFileTypeBox());
|
||||
this.headerPos = output.position() - 24L;
|
||||
output.setPosition(this.headerPos);
|
||||
this.header = ByteBuffer.allocate(headerSize);
|
||||
output.write(this.header);
|
||||
this.header.clear();
|
||||
Header.createHeader("wide", 8L).writeChannel(output);
|
||||
Header.createHeader("mdat", 1L).writeChannel(output);
|
||||
this.mdatOffset = output.position();
|
||||
NIOUtils.writeLong(output, 0L);
|
||||
}
|
||||
|
||||
public void storeHeader(MovieBox movie) throws IOException {
|
||||
long mdatEnd = this.out.position();
|
||||
long mdatSize = mdatEnd - this.mdatOffset + 8L;
|
||||
this.out.setPosition(this.mdatOffset);
|
||||
NIOUtils.writeLong(this.out, mdatSize);
|
||||
this.out.setPosition(this.headerPos);
|
||||
try {
|
||||
movie.write(this.header);
|
||||
this.header.flip();
|
||||
int rem = this.header.capacity() - this.header.limit();
|
||||
if (rem < 8)
|
||||
this.header.duplicate().putInt(this.header.capacity());
|
||||
this.out.write(this.header);
|
||||
if (rem >= 8)
|
||||
Header.createHeader("free", (long)rem).writeChannel(this.out);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
Logger.warn("Could not web-optimize, header is bigger then allocated space.");
|
||||
Header.createHeader("free", (long)this.header.remaining()).writeChannel(this.out);
|
||||
this.out.setPosition(mdatEnd);
|
||||
MP4Util.writeMovie(this.out, movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class AVC1Box extends VideoSampleEntry {
|
||||
public AVC1Box() {
|
||||
super(new Header("avc1"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class AliasBox extends FullBox {
|
||||
public static final int DirectoryName = 0;
|
||||
|
||||
public static final int DirectoryIDs = 1;
|
||||
|
||||
public static final int AbsolutePath = 2;
|
||||
|
||||
public static final int AppleShareZoneName = 3;
|
||||
|
||||
public static final int AppleShareServerName = 4;
|
||||
|
||||
public static final int AppleShareUserName = 5;
|
||||
|
||||
public static final int DriverName = 6;
|
||||
|
||||
public static final int RevisedAppleShare = 9;
|
||||
|
||||
public static final int AppleRemoteAccessDialup = 10;
|
||||
|
||||
public static final int UNIXAbsolutePath = 18;
|
||||
|
||||
public static final int UTF16AbsolutePath = 14;
|
||||
|
||||
public static final int UFT16VolumeName = 15;
|
||||
|
||||
public static final int VolumeMountPoint = 19;
|
||||
|
||||
private String type;
|
||||
|
||||
private short recordSize;
|
||||
|
||||
private short version;
|
||||
|
||||
private short kind;
|
||||
|
||||
private String volumeName;
|
||||
|
||||
private int volumeCreateDate;
|
||||
|
||||
private short volumeSignature;
|
||||
|
||||
private short volumeType;
|
||||
|
||||
private int parentDirId;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private int fileNumber;
|
||||
|
||||
private int createdLocalDate;
|
||||
|
||||
private String fileTypeName;
|
||||
|
||||
private String creatorName;
|
||||
|
||||
private short nlvlFrom;
|
||||
|
||||
private short nlvlTo;
|
||||
|
||||
private int volumeAttributes;
|
||||
|
||||
private short fsId;
|
||||
|
||||
private List<ExtraField> extra;
|
||||
|
||||
public static String fourcc() {
|
||||
return "alis";
|
||||
}
|
||||
|
||||
public static class ExtraField {
|
||||
short type;
|
||||
|
||||
int len;
|
||||
|
||||
byte[] data;
|
||||
|
||||
public ExtraField(short type, int len, byte[] bs) {
|
||||
this.type = type;
|
||||
this.len = len;
|
||||
this.data = bs;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Platform.stringFromCharset4(this.data, 0, this.len, (this.type == 14 || this.type == 15) ? "UTF-16" : "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public AliasBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer is) {
|
||||
super.parse(is);
|
||||
if ((this.flags & 0x1) != 0)
|
||||
return;
|
||||
this.type = NIOUtils.readString(is, 4);
|
||||
this.recordSize = is.getShort();
|
||||
this.version = is.getShort();
|
||||
this.kind = is.getShort();
|
||||
this.volumeName = NIOUtils.readPascalStringL(is, 27);
|
||||
this.volumeCreateDate = is.getInt();
|
||||
this.volumeSignature = is.getShort();
|
||||
this.volumeType = is.getShort();
|
||||
this.parentDirId = is.getInt();
|
||||
this.fileName = NIOUtils.readPascalStringL(is, 63);
|
||||
this.fileNumber = is.getInt();
|
||||
this.createdLocalDate = is.getInt();
|
||||
this.fileTypeName = NIOUtils.readString(is, 4);
|
||||
this.creatorName = NIOUtils.readString(is, 4);
|
||||
this.nlvlFrom = is.getShort();
|
||||
this.nlvlTo = is.getShort();
|
||||
this.volumeAttributes = is.getInt();
|
||||
this.fsId = is.getShort();
|
||||
NIOUtils.skip(is, 10);
|
||||
this.extra = new ArrayList<>();
|
||||
while (true) {
|
||||
short type = is.getShort();
|
||||
if (type == -1)
|
||||
break;
|
||||
int len = is.getShort();
|
||||
byte[] bs = NIOUtils.toArray(NIOUtils.read(is, len + 1 & 0xFFFFFFFE));
|
||||
if (bs == null)
|
||||
break;
|
||||
this.extra.add(new ExtraField(type, len, bs));
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
if ((this.flags & 0x1) != 0)
|
||||
return;
|
||||
out.put(JCodecUtil2.asciiString(this.type), 0, 4);
|
||||
out.putShort(this.recordSize);
|
||||
out.putShort(this.version);
|
||||
out.putShort(this.kind);
|
||||
NIOUtils.writePascalStringL(out, this.volumeName, 27);
|
||||
out.putInt(this.volumeCreateDate);
|
||||
out.putShort(this.volumeSignature);
|
||||
out.putShort(this.volumeType);
|
||||
out.putInt(this.parentDirId);
|
||||
NIOUtils.writePascalStringL(out, this.fileName, 63);
|
||||
out.putInt(this.fileNumber);
|
||||
out.putInt(this.createdLocalDate);
|
||||
out.put(JCodecUtil2.asciiString(this.fileTypeName), 0, 4);
|
||||
out.put(JCodecUtil2.asciiString(this.creatorName), 0, 4);
|
||||
out.putShort(this.nlvlFrom);
|
||||
out.putShort(this.nlvlTo);
|
||||
out.putInt(this.volumeAttributes);
|
||||
out.putShort(this.fsId);
|
||||
out.put(new byte[10]);
|
||||
for (ExtraField extraField : this.extra) {
|
||||
out.putShort(extraField.type);
|
||||
out.putShort((short)extraField.len);
|
||||
out.put(extraField.data);
|
||||
}
|
||||
out.putShort((short)-1);
|
||||
out.putShort((short)0);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int sz = 166;
|
||||
if ((this.flags & 0x1) == 0)
|
||||
for (ExtraField extraField : this.extra)
|
||||
sz += 4 + extraField.data.length;
|
||||
return 12 + sz;
|
||||
}
|
||||
|
||||
public int getRecordSize() {
|
||||
return this.recordSize;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return this.fileName;
|
||||
}
|
||||
|
||||
public List<ExtraField> getExtras() {
|
||||
return this.extra;
|
||||
}
|
||||
|
||||
public ExtraField getExtra(int type) {
|
||||
for (ExtraField extraField : this.extra) {
|
||||
if (extraField.type == type)
|
||||
return extraField;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isSelfRef() {
|
||||
return ((this.flags & 0x1) != 0);
|
||||
}
|
||||
|
||||
public static AliasBox createSelfRef() {
|
||||
AliasBox alis = new AliasBox(new Header(fourcc()));
|
||||
alis.setFlags(1);
|
||||
return alis;
|
||||
}
|
||||
|
||||
public String getUnixPath() {
|
||||
ExtraField extraField = getExtra(18);
|
||||
return (extraField == null) ? null : ("/" + extraField.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.jcodec.api.NotSupportedException;
|
||||
import org.jcodec.common.AudioFormat;
|
||||
import org.jcodec.common.model.ChannelLabel;
|
||||
import org.jcodec.common.model.Label;
|
||||
import org.jcodec.containers.mp4.boxes.channel.ChannelLayout;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class AudioSampleEntry extends SampleEntry {
|
||||
public static int kAudioFormatFlagIsFloat = 1;
|
||||
|
||||
public static int kAudioFormatFlagIsBigEndian = 2;
|
||||
|
||||
public static int kAudioFormatFlagIsSignedInteger = 4;
|
||||
|
||||
public static int kAudioFormatFlagIsPacked = 8;
|
||||
|
||||
public static int kAudioFormatFlagIsAlignedHigh = 16;
|
||||
|
||||
public static int kAudioFormatFlagIsNonInterleaved = 32;
|
||||
|
||||
public static int kAudioFormatFlagIsNonMixable = 64;
|
||||
|
||||
private short channelCount;
|
||||
|
||||
private short sampleSize;
|
||||
|
||||
private float sampleRate;
|
||||
|
||||
private short revision;
|
||||
|
||||
private int vendor;
|
||||
|
||||
private int compressionId;
|
||||
|
||||
private int pktSize;
|
||||
|
||||
private int samplesPerPkt;
|
||||
|
||||
private int bytesPerPkt;
|
||||
|
||||
private int bytesPerFrame;
|
||||
|
||||
private int bytesPerSample;
|
||||
|
||||
private short version;
|
||||
|
||||
private int lpcmFlags;
|
||||
|
||||
public static AudioSampleEntry createAudioSampleEntry(Header header, short drefInd, short channelCount, short sampleSize, int sampleRate, short revision, int vendor, int compressionId, int pktSize, int samplesPerPkt, int bytesPerPkt, int bytesPerFrame, int bytesPerSample, short version) {
|
||||
AudioSampleEntry audio = new AudioSampleEntry(header);
|
||||
audio.drefInd = drefInd;
|
||||
audio.channelCount = channelCount;
|
||||
audio.sampleSize = sampleSize;
|
||||
audio.sampleRate = (float)sampleRate;
|
||||
audio.revision = revision;
|
||||
audio.vendor = vendor;
|
||||
audio.compressionId = compressionId;
|
||||
audio.pktSize = pktSize;
|
||||
audio.samplesPerPkt = samplesPerPkt;
|
||||
audio.bytesPerPkt = bytesPerPkt;
|
||||
audio.bytesPerFrame = bytesPerFrame;
|
||||
audio.bytesPerSample = bytesPerSample;
|
||||
audio.version = version;
|
||||
return audio;
|
||||
}
|
||||
|
||||
private static final List<Label> MONO = Arrays.asList(Label.Mono);
|
||||
|
||||
private static final List<Label> STEREO = Arrays.asList(Label.Left, Label.Right);
|
||||
|
||||
private static final List<Label> MATRIX_STEREO = Arrays.asList(Label.LeftTotal, Label.RightTotal);
|
||||
|
||||
public static final Label[] EMPTY = new Label[0];
|
||||
|
||||
public AudioSampleEntry(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.version = input.getShort();
|
||||
this.revision = input.getShort();
|
||||
this.vendor = input.getInt();
|
||||
this.channelCount = input.getShort();
|
||||
this.sampleSize = input.getShort();
|
||||
this.compressionId = input.getShort();
|
||||
this.pktSize = input.getShort();
|
||||
long sr = Platform.unsignedInt(input.getInt());
|
||||
this.sampleRate = (float)sr / 65536.0F;
|
||||
if (this.version == 1) {
|
||||
this.samplesPerPkt = input.getInt();
|
||||
this.bytesPerPkt = input.getInt();
|
||||
this.bytesPerFrame = input.getInt();
|
||||
this.bytesPerSample = input.getInt();
|
||||
} else if (this.version == 2) {
|
||||
input.getInt();
|
||||
this.sampleRate = (float)Double.longBitsToDouble(input.getLong());
|
||||
this.channelCount = (short)input.getInt();
|
||||
input.getInt();
|
||||
this.sampleSize = (short)input.getInt();
|
||||
this.lpcmFlags = input.getInt();
|
||||
this.bytesPerFrame = input.getInt();
|
||||
this.samplesPerPkt = input.getInt();
|
||||
}
|
||||
parseExtensions(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort(this.version);
|
||||
out.putShort(this.revision);
|
||||
out.putInt(this.vendor);
|
||||
if (this.version < 2) {
|
||||
out.putShort(this.channelCount);
|
||||
if (this.version == 0) {
|
||||
out.putShort(this.sampleSize);
|
||||
} else {
|
||||
out.putShort((short)16);
|
||||
}
|
||||
out.putShort((short)this.compressionId);
|
||||
out.putShort((short)this.pktSize);
|
||||
out.putInt((int)Math.round((double)this.sampleRate * 65536.0D));
|
||||
if (this.version == 1) {
|
||||
out.putInt(this.samplesPerPkt);
|
||||
out.putInt(this.bytesPerPkt);
|
||||
out.putInt(this.bytesPerFrame);
|
||||
out.putInt(this.bytesPerSample);
|
||||
}
|
||||
} else if (this.version == 2) {
|
||||
out.putShort((short)3);
|
||||
out.putShort((short)16);
|
||||
out.putShort((short)-2);
|
||||
out.putShort((short)0);
|
||||
out.putInt(65536);
|
||||
out.putInt(72);
|
||||
out.putLong(Double.doubleToLongBits((double)this.sampleRate));
|
||||
out.putInt(this.channelCount);
|
||||
out.putInt(2130706432);
|
||||
out.putInt(this.sampleSize);
|
||||
out.putInt(this.lpcmFlags);
|
||||
out.putInt(this.bytesPerFrame);
|
||||
out.putInt(this.samplesPerPkt);
|
||||
}
|
||||
writeExtensions(out);
|
||||
}
|
||||
|
||||
public short getChannelCount() {
|
||||
return this.channelCount;
|
||||
}
|
||||
|
||||
public int calcFrameSize() {
|
||||
if (this.version == 0 || this.bytesPerFrame == 0)
|
||||
return (this.sampleSize >> 3) * this.channelCount;
|
||||
return this.bytesPerFrame;
|
||||
}
|
||||
|
||||
public int calcSampleSize() {
|
||||
return calcFrameSize() / this.channelCount;
|
||||
}
|
||||
|
||||
public short getSampleSize() {
|
||||
return this.sampleSize;
|
||||
}
|
||||
|
||||
public float getSampleRate() {
|
||||
return this.sampleRate;
|
||||
}
|
||||
|
||||
public int getBytesPerFrame() {
|
||||
return this.bytesPerFrame;
|
||||
}
|
||||
|
||||
public int getBytesPerSample() {
|
||||
return this.bytesPerSample;
|
||||
}
|
||||
|
||||
public short getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public ByteOrder getEndian() {
|
||||
EndianBox endianBox = NodeBox.<EndianBox>findFirstPath(this, EndianBox.class, new String[] { WaveExtension.fourcc(), EndianBox.fourcc() });
|
||||
if (endianBox == null) {
|
||||
if ("twos".equals(this.header.getFourcc()))
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
if ("lpcm".equals(this.header.getFourcc()))
|
||||
return ((this.lpcmFlags & kAudioFormatFlagIsBigEndian) != 0) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
|
||||
if ("sowt".equals(this.header.getFourcc()))
|
||||
return ByteOrder.LITTLE_ENDIAN;
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
return endianBox.getEndian();
|
||||
}
|
||||
|
||||
public boolean isFloat() {
|
||||
return ("fl32".equals(this.header.getFourcc()) || "fl64".equals(this.header.getFourcc()) || ("lpcm"
|
||||
.equals(this.header.getFourcc()) && (this.lpcmFlags & kAudioFormatFlagIsFloat) != 0));
|
||||
}
|
||||
|
||||
public static Set<String> pcms = new HashSet<>();
|
||||
|
||||
static {
|
||||
pcms.add("raw ");
|
||||
pcms.add("twos");
|
||||
pcms.add("sowt");
|
||||
pcms.add("fl32");
|
||||
pcms.add("fl64");
|
||||
pcms.add("in24");
|
||||
pcms.add("in32");
|
||||
pcms.add("lpcm");
|
||||
}
|
||||
|
||||
public boolean isPCM() {
|
||||
return pcms.contains(this.header.getFourcc());
|
||||
}
|
||||
|
||||
public AudioFormat getFormat() {
|
||||
return new AudioFormat((int)this.sampleRate, calcSampleSize() << 3, this.channelCount, true,
|
||||
(getEndian() == ByteOrder.BIG_ENDIAN));
|
||||
}
|
||||
|
||||
public ChannelLabel[] getLabels() {
|
||||
ChannelBox channelBox = NodeBox.<ChannelBox>findFirst(this, ChannelBox.class, "chan");
|
||||
if (channelBox != null) {
|
||||
Label[] labels = getLabelsFromChan(channelBox);
|
||||
if (this.channelCount == 2)
|
||||
return translate(translationStereo, labels);
|
||||
return translate(translationSurround, labels);
|
||||
}
|
||||
switch (this.channelCount) {
|
||||
case 1:
|
||||
return new ChannelLabel[] { ChannelLabel.MONO };
|
||||
case 2:
|
||||
return new ChannelLabel[] { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT };
|
||||
case 6:
|
||||
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER, ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT };
|
||||
}
|
||||
ChannelLabel[] lbl = new ChannelLabel[this.channelCount];
|
||||
Arrays.fill(lbl, ChannelLabel.MONO);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
private ChannelLabel[] translate(Map<Label, ChannelLabel> translation, Label[] labels) {
|
||||
ChannelLabel[] result = new ChannelLabel[labels.length];
|
||||
int i = 0;
|
||||
for (int j = 0; j < labels.length; j++) {
|
||||
Label label = labels[j];
|
||||
result[i++] = translation.get(label);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AudioSampleEntry compressedAudioSampleEntry(String fourcc, int drefId, int sampleSize, int channels, int sampleRate, int samplesPerPacket, int bytesPerPacket, int bytesPerFrame) {
|
||||
AudioSampleEntry ase = createAudioSampleEntry(Header.createHeader(fourcc, 0L), (short)drefId, (short)channels, (short)16, sampleRate, (short)0, 0, 65534, 0, samplesPerPacket, bytesPerPacket, bytesPerFrame, 2, (short)0);
|
||||
return ase;
|
||||
}
|
||||
|
||||
public static AudioSampleEntry audioSampleEntry(String fourcc, int drefId, int sampleSize, int channels, int sampleRate, ByteOrder endian) {
|
||||
AudioSampleEntry ase = createAudioSampleEntry(Header.createHeader(fourcc, 0L), (short)drefId, (short)channels, (short)16, sampleRate, (short)0, 0, 65535, 0, 1, sampleSize, channels * sampleSize, sampleSize, (short)1);
|
||||
NodeBox wave = new NodeBox(new Header("wave"));
|
||||
ase.add(wave);
|
||||
wave.add(FormatBox.createFormatBox(fourcc));
|
||||
wave.add(EndianBox.createEndianBox(endian));
|
||||
wave.add(Box.terminatorAtom());
|
||||
return ase;
|
||||
}
|
||||
|
||||
public static String lookupFourcc(AudioFormat format) {
|
||||
if (format.getSampleSizeInBits() == 16 && !format.isBigEndian())
|
||||
return "sowt";
|
||||
if (format.getSampleSizeInBits() == 24)
|
||||
return "in24";
|
||||
throw new NotSupportedException("Audio format " + String.valueOf(format) + " is not supported.");
|
||||
}
|
||||
|
||||
public static AudioSampleEntry audioSampleEntryPCM(AudioFormat format) {
|
||||
return audioSampleEntry(lookupFourcc(format), 1, format.getSampleSizeInBits() >> 3,
|
||||
format.getChannels(), format.getSampleRate(),
|
||||
format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static Map<Label, ChannelLabel> translationStereo = new HashMap<>();
|
||||
|
||||
private static Map<Label, ChannelLabel> translationSurround = new HashMap<>();
|
||||
|
||||
static {
|
||||
translationStereo.put(Label.Left, ChannelLabel.STEREO_LEFT);
|
||||
translationStereo.put(Label.Right, ChannelLabel.STEREO_RIGHT);
|
||||
translationStereo.put(Label.HeadphonesLeft, ChannelLabel.STEREO_LEFT);
|
||||
translationStereo.put(Label.HeadphonesRight, ChannelLabel.STEREO_RIGHT);
|
||||
translationStereo.put(Label.LeftTotal, ChannelLabel.STEREO_LEFT);
|
||||
translationStereo.put(Label.RightTotal, ChannelLabel.STEREO_RIGHT);
|
||||
translationStereo.put(Label.LeftWide, ChannelLabel.STEREO_LEFT);
|
||||
translationStereo.put(Label.RightWide, ChannelLabel.STEREO_RIGHT);
|
||||
translationSurround.put(Label.Left, ChannelLabel.FRONT_LEFT);
|
||||
translationSurround.put(Label.Right, ChannelLabel.FRONT_RIGHT);
|
||||
translationSurround.put(Label.LeftCenter, ChannelLabel.FRONT_CENTER_LEFT);
|
||||
translationSurround.put(Label.RightCenter, ChannelLabel.FRONT_CENTER_RIGHT);
|
||||
translationSurround.put(Label.Center, ChannelLabel.CENTER);
|
||||
translationSurround.put(Label.CenterSurround, ChannelLabel.REAR_CENTER);
|
||||
translationSurround.put(Label.CenterSurroundDirect, ChannelLabel.REAR_CENTER);
|
||||
translationSurround.put(Label.LeftSurround, ChannelLabel.REAR_LEFT);
|
||||
translationSurround.put(Label.LeftSurroundDirect, ChannelLabel.REAR_LEFT);
|
||||
translationSurround.put(Label.RightSurround, ChannelLabel.REAR_RIGHT);
|
||||
translationSurround.put(Label.RightSurroundDirect, ChannelLabel.REAR_RIGHT);
|
||||
translationSurround.put(Label.RearSurroundLeft, ChannelLabel.SIDE_LEFT);
|
||||
translationSurround.put(Label.RearSurroundRight, ChannelLabel.SIDE_RIGHT);
|
||||
translationSurround.put(Label.LFE2, ChannelLabel.LFE);
|
||||
translationSurround.put(Label.LFEScreen, ChannelLabel.LFE);
|
||||
translationSurround.put(Label.LeftTotal, ChannelLabel.STEREO_LEFT);
|
||||
translationSurround.put(Label.RightTotal, ChannelLabel.STEREO_RIGHT);
|
||||
translationSurround.put(Label.LeftWide, ChannelLabel.STEREO_LEFT);
|
||||
translationSurround.put(Label.RightWide, ChannelLabel.STEREO_RIGHT);
|
||||
}
|
||||
|
||||
public static Label[] getLabelsFromSampleEntry(AudioSampleEntry se) {
|
||||
ChannelBox channel = NodeBox.<ChannelBox>findFirst(se, ChannelBox.class, "chan");
|
||||
if (channel != null)
|
||||
return getLabelsFromChan(channel);
|
||||
short channelCount = se.getChannelCount();
|
||||
switch (channelCount) {
|
||||
case 1:
|
||||
return new Label[] { Label.Mono };
|
||||
case 2:
|
||||
return new Label[] { Label.Left, Label.Right };
|
||||
case 3:
|
||||
return new Label[] { Label.Left, Label.Right, Label.Center };
|
||||
case 4:
|
||||
return new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround };
|
||||
case 5:
|
||||
return new Label[] { Label.Left, Label.Right, Label.Center, Label.LeftSurround, Label.RightSurround };
|
||||
case 6:
|
||||
return new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround };
|
||||
}
|
||||
Label[] res = new Label[channelCount];
|
||||
Arrays.fill(res, Label.Mono);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Label[] getLabelsFromTrack(TrakBox trakBox) {
|
||||
return getLabelsFromSampleEntry((AudioSampleEntry)trakBox.getSampleEntries()[0]);
|
||||
}
|
||||
|
||||
public static void setLabel(TrakBox trakBox, int channel, Label label) {
|
||||
Label[] labels = getLabelsFromTrack(trakBox);
|
||||
labels[channel] = label;
|
||||
_setLabels(trakBox, labels);
|
||||
}
|
||||
|
||||
public static void _setLabels(TrakBox trakBox, Label[] labels) {
|
||||
ChannelBox channel = NodeBox.<ChannelBox>findFirstPath(trakBox, ChannelBox.class, new String[] { "mdia", "minf", "stbl", "stsd", null, "chan" });
|
||||
if (channel == null) {
|
||||
channel = ChannelBox.createChannelBox();
|
||||
NodeBox.<SampleEntry>findFirstPath(trakBox, SampleEntry.class, new String[] { "mdia", "minf", "stbl", "stsd", null }).add(channel);
|
||||
}
|
||||
setLabels(labels, channel);
|
||||
}
|
||||
|
||||
public static void setLabels(Label[] labels, ChannelBox channel) {
|
||||
channel.setChannelLayout(ChannelLayout.kCAFChannelLayoutTag_UseChannelDescriptions.getCode());
|
||||
ChannelBox.ChannelDescription[] list = new ChannelBox.ChannelDescription[labels.length];
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
list[i] = new ChannelBox.ChannelDescription(labels[i].getVal(), 0, new float[] { 0.0F, 0.0F, 0.0F });
|
||||
}
|
||||
channel.setDescriptions(list);
|
||||
}
|
||||
|
||||
public static Label[] getLabelsByBitmap(long channelBitmap) {
|
||||
List<Label> result = new ArrayList<>();
|
||||
Label[] values = Label.values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Label label = values[i];
|
||||
if ((label.bitmapVal & channelBitmap) != 0L)
|
||||
result.add(label);
|
||||
}
|
||||
return result.<Label>toArray(new Label[0]);
|
||||
}
|
||||
|
||||
public static Label[] extractLabels(ChannelBox.ChannelDescription[] descriptions) {
|
||||
Label[] result = new Label[descriptions.length];
|
||||
for (int i = 0; i < descriptions.length; i++)
|
||||
result[i] = descriptions[i].getLabel();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Label[] getLabelsFromChan(ChannelBox box) {
|
||||
long tag = (long)box.getChannelLayout();
|
||||
if (tag >> 16L == 147L) {
|
||||
int n = (int)tag & 0xFFFF;
|
||||
Label[] res = new Label[n];
|
||||
for (int j = 0; j < n; j++)
|
||||
res[j] = Label.getByVal(0x10000 | j);
|
||||
return res;
|
||||
}
|
||||
ChannelLayout[] values = ChannelLayout.values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
ChannelLayout layout = values[i];
|
||||
if ((long)layout.getCode() == tag) {
|
||||
if (layout == ChannelLayout.kCAFChannelLayoutTag_UseChannelDescriptions)
|
||||
return extractLabels(box.getDescriptions());
|
||||
if (layout == ChannelLayout.kCAFChannelLayoutTag_UseChannelBitmap)
|
||||
return getLabelsByBitmap((long)box.getChannelBitmap());
|
||||
return layout.getLabels();
|
||||
}
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.StringUtils;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mp4.IBoxFactory;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public abstract class Box {
|
||||
public Header header;
|
||||
|
||||
public static final int MAX_BOX_SIZE = 134217728;
|
||||
|
||||
@UsedViaReflection
|
||||
public Box(Header header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public Header getHeader() {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
public abstract void parse(ByteBuffer paramByteBuffer);
|
||||
|
||||
public void write(ByteBuffer buf) {
|
||||
ByteBuffer dup = buf.duplicate();
|
||||
NIOUtils.skip(buf, 8);
|
||||
doWrite(buf);
|
||||
this.header.setBodySize(buf.position() - dup.position() - 8);
|
||||
Preconditions.checkState((this.header.headerSize() == 8L));
|
||||
this.header.write(dup);
|
||||
}
|
||||
|
||||
protected abstract void doWrite(ByteBuffer paramByteBuffer);
|
||||
|
||||
public abstract int estimateSize();
|
||||
|
||||
public String getFourcc() {
|
||||
return this.header.getFourcc();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
dump(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void dump(StringBuilder sb) {
|
||||
sb.append("{\"tag\":\"" + this.header.getFourcc() + "\"}");
|
||||
}
|
||||
|
||||
public static Box terminatorAtom() {
|
||||
return createLeafBox(new Header(Platform.stringFromBytes(new byte[4])), ByteBuffer.allocate(0));
|
||||
}
|
||||
|
||||
public static String[] path(String path) {
|
||||
return StringUtils.splitC(path, '.');
|
||||
}
|
||||
|
||||
public static LeafBox createLeafBox(Header atom, ByteBuffer data) {
|
||||
LeafBox leaf = new LeafBox(atom);
|
||||
leaf.data = data;
|
||||
return leaf;
|
||||
}
|
||||
|
||||
public static Box parseBox(ByteBuffer input, Header childAtom, IBoxFactory factory) {
|
||||
Box box = factory.newBox(childAtom);
|
||||
if (childAtom.getBodySize() < 134217728L) {
|
||||
box.parse(input);
|
||||
return box;
|
||||
}
|
||||
return new LeafBox(Header.createHeader("free", 8L));
|
||||
}
|
||||
|
||||
public static <T extends Box> T asBox(Class<T> class1, Box box) {
|
||||
try {
|
||||
T res = Platform.<T>newInstance(class1, new Object[] { box.getHeader() });
|
||||
ByteBuffer buffer = ByteBuffer.allocate((int)box.getHeader().getBodySize());
|
||||
box.doWrite(buffer);
|
||||
buffer.flip();
|
||||
res.parse(buffer);
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LeafBox extends Box {
|
||||
ByteBuffer data;
|
||||
|
||||
public LeafBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.data = NIOUtils.read(input, (int)this.header.getBodySize());
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
return this.data.duplicate();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
NIOUtils.write(out, this.data);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return this.data.remaining() + Header.estimateHeaderSize(this.data.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.model.Label;
|
||||
|
||||
public class ChannelBox extends FullBox {
|
||||
private int channelLayout;
|
||||
|
||||
private int channelBitmap;
|
||||
|
||||
private ChannelDescription[] descriptions;
|
||||
|
||||
public static class ChannelDescription {
|
||||
private int channelLabel;
|
||||
|
||||
private int channelFlags;
|
||||
|
||||
private float[] coordinates;
|
||||
|
||||
public ChannelDescription(int channelLabel, int channelFlags, float[] coordinates) {
|
||||
this.coordinates = new float[3];
|
||||
this.channelLabel = channelLabel;
|
||||
this.channelFlags = channelFlags;
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public int getChannelLabel() {
|
||||
return this.channelLabel;
|
||||
}
|
||||
|
||||
public int getChannelFlags() {
|
||||
return this.channelFlags;
|
||||
}
|
||||
|
||||
public float[] getCoordinates() {
|
||||
return this.coordinates;
|
||||
}
|
||||
|
||||
public Label getLabel() {
|
||||
return Label.getByVal(this.channelLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "chan";
|
||||
}
|
||||
|
||||
public static ChannelBox createChannelBox() {
|
||||
return new ChannelBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.channelLayout = input.getInt();
|
||||
this.channelBitmap = input.getInt();
|
||||
int numDescriptions = input.getInt();
|
||||
this.descriptions = new ChannelDescription[numDescriptions];
|
||||
for (int i = 0; i < numDescriptions; i++) {
|
||||
this.descriptions[i] = new ChannelDescription(input.getInt(), input.getInt(), new float[] { Float.intBitsToFloat(input.getInt()), Float.intBitsToFloat(input.getInt()),
|
||||
Float.intBitsToFloat(input.getInt()) });
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.channelLayout);
|
||||
out.putInt(this.channelBitmap);
|
||||
out.putInt(this.descriptions.length);
|
||||
for (int i = 0; i < this.descriptions.length; i++) {
|
||||
ChannelDescription channelDescription = this.descriptions[i];
|
||||
out.putInt(channelDescription.getChannelLabel());
|
||||
out.putInt(channelDescription.getChannelFlags());
|
||||
out.putFloat(channelDescription.getCoordinates()[0]);
|
||||
out.putFloat(channelDescription.getCoordinates()[1]);
|
||||
out.putFloat(channelDescription.getCoordinates()[2]);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 24 + this.descriptions.length * 20;
|
||||
}
|
||||
|
||||
public int getChannelLayout() {
|
||||
return this.channelLayout;
|
||||
}
|
||||
|
||||
public int getChannelBitmap() {
|
||||
return this.channelBitmap;
|
||||
}
|
||||
|
||||
public ChannelDescription[] getDescriptions() {
|
||||
return this.descriptions;
|
||||
}
|
||||
|
||||
public void setChannelLayout(int channelLayout) {
|
||||
this.channelLayout = channelLayout;
|
||||
}
|
||||
|
||||
public void setDescriptions(ChannelDescription[] descriptions) {
|
||||
this.descriptions = descriptions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ChunkOffsets64Box extends FullBox {
|
||||
private long[] chunkOffsets;
|
||||
|
||||
public static String fourcc() {
|
||||
return "co64";
|
||||
}
|
||||
|
||||
public static ChunkOffsets64Box createChunkOffsets64Box(long[] offsets) {
|
||||
ChunkOffsets64Box co64 = new ChunkOffsets64Box(Header.createHeader(fourcc(), 0L));
|
||||
co64.chunkOffsets = offsets;
|
||||
return co64;
|
||||
}
|
||||
|
||||
public ChunkOffsets64Box(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int length = input.getInt();
|
||||
this.chunkOffsets = new long[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
this.chunkOffsets[i] = input.getLong();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.chunkOffsets.length);
|
||||
for (int i = 0; i < this.chunkOffsets.length; i++) {
|
||||
long offset = this.chunkOffsets[i];
|
||||
out.putLong(offset);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.chunkOffsets.length * 8;
|
||||
}
|
||||
|
||||
public long[] getChunkOffsets() {
|
||||
return this.chunkOffsets;
|
||||
}
|
||||
|
||||
public void setChunkOffsets(long[] chunkOffsets) {
|
||||
this.chunkOffsets = chunkOffsets;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class ChunkOffsetsBox extends FullBox {
|
||||
private long[] chunkOffsets;
|
||||
|
||||
public ChunkOffsetsBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "stco";
|
||||
}
|
||||
|
||||
public static ChunkOffsetsBox createChunkOffsetsBox(long[] chunkOffsets) {
|
||||
ChunkOffsetsBox stco = new ChunkOffsetsBox(new Header(fourcc()));
|
||||
stco.chunkOffsets = chunkOffsets;
|
||||
return stco;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int length = input.getInt();
|
||||
this.chunkOffsets = new long[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
this.chunkOffsets[i] = Platform.unsignedInt(input.getInt());
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.chunkOffsets.length);
|
||||
for (int i = 0; i < this.chunkOffsets.length; i++) {
|
||||
long offset = this.chunkOffsets[i];
|
||||
out.putInt((int)offset);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.chunkOffsets.length * 4;
|
||||
}
|
||||
|
||||
public long[] getChunkOffsets() {
|
||||
return this.chunkOffsets;
|
||||
}
|
||||
|
||||
public void setChunkOffsets(long[] chunkOffsets) {
|
||||
this.chunkOffsets = chunkOffsets;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class CleanApertureExtension extends Box {
|
||||
private int vertOffsetDenominator;
|
||||
|
||||
private int vertOffsetNumerator;
|
||||
|
||||
private int horizOffsetDenominator;
|
||||
|
||||
private int horizOffsetNumerator;
|
||||
|
||||
private int apertureHeightDenominator;
|
||||
|
||||
private int apertureHeightNumerator;
|
||||
|
||||
private int apertureWidthDenominator;
|
||||
|
||||
private int apertureWidthNumerator;
|
||||
|
||||
public CleanApertureExtension(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static CleanApertureExtension createCleanApertureExtension(int apertureWidthN, int apertureWidthD, int apertureHeightN, int apertureHeightD, int horizOffN, int horizOffD, int vertOffN, int vertOffD) {
|
||||
CleanApertureExtension clap = new CleanApertureExtension(new Header(fourcc()));
|
||||
clap.apertureWidthNumerator = apertureWidthN;
|
||||
clap.apertureWidthDenominator = apertureWidthD;
|
||||
clap.apertureHeightNumerator = apertureHeightN;
|
||||
clap.apertureHeightDenominator = apertureHeightD;
|
||||
clap.horizOffsetNumerator = horizOffN;
|
||||
clap.horizOffsetDenominator = horizOffD;
|
||||
clap.vertOffsetNumerator = vertOffN;
|
||||
clap.vertOffsetDenominator = vertOffD;
|
||||
return clap;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer is) {
|
||||
this.apertureWidthNumerator = is.getInt();
|
||||
this.apertureWidthDenominator = is.getInt();
|
||||
this.apertureHeightNumerator = is.getInt();
|
||||
this.apertureHeightDenominator = is.getInt();
|
||||
this.horizOffsetNumerator = is.getInt();
|
||||
this.horizOffsetDenominator = is.getInt();
|
||||
this.vertOffsetNumerator = is.getInt();
|
||||
this.vertOffsetDenominator = is.getInt();
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "clap";
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.apertureWidthNumerator);
|
||||
out.putInt(this.apertureWidthDenominator);
|
||||
out.putInt(this.apertureHeightNumerator);
|
||||
out.putInt(this.apertureHeightDenominator);
|
||||
out.putInt(this.horizOffsetNumerator);
|
||||
out.putInt(this.horizOffsetDenominator);
|
||||
out.putInt(this.vertOffsetNumerator);
|
||||
out.putInt(this.vertOffsetDenominator);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ClearApertureBox extends FullBox {
|
||||
public static final String CLEF = "clef";
|
||||
|
||||
protected float width;
|
||||
|
||||
protected float height;
|
||||
|
||||
public static ClearApertureBox createClearApertureBox(int width, int height) {
|
||||
ClearApertureBox clef = new ClearApertureBox(new Header("clef"));
|
||||
clef.width = (float)width;
|
||||
clef.height = (float)height;
|
||||
return clef;
|
||||
}
|
||||
|
||||
public ClearApertureBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.width = (float)input.getInt() / 65536.0F;
|
||||
this.height = (float)input.getInt() / 65536.0F;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt((int)(this.width * 65536.0F));
|
||||
out.putInt((int)(this.height * 65536.0F));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ClipRegionBox extends Box {
|
||||
private short rgnSize;
|
||||
|
||||
private short y;
|
||||
|
||||
private short x;
|
||||
|
||||
private short height;
|
||||
|
||||
private short width;
|
||||
|
||||
public static String fourcc() {
|
||||
return "crgn";
|
||||
}
|
||||
|
||||
public static ClipRegionBox createClipRegionBox(short x, short y, short width, short height) {
|
||||
ClipRegionBox b = new ClipRegionBox(new Header(fourcc()));
|
||||
b.rgnSize = 10;
|
||||
b.x = x;
|
||||
b.y = y;
|
||||
b.width = width;
|
||||
b.height = height;
|
||||
return b;
|
||||
}
|
||||
|
||||
public ClipRegionBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.rgnSize = input.getShort();
|
||||
this.y = input.getShort();
|
||||
this.x = input.getShort();
|
||||
this.height = input.getShort();
|
||||
this.width = input.getShort();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putShort(this.rgnSize);
|
||||
out.putShort(this.y);
|
||||
out.putShort(this.x);
|
||||
out.putShort(this.height);
|
||||
out.putShort(this.width);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 18;
|
||||
}
|
||||
|
||||
public short getRgnSize() {
|
||||
return this.rgnSize;
|
||||
}
|
||||
|
||||
public short getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public short getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public short getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public short getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class ColorExtension extends Box {
|
||||
private short primariesIndex;
|
||||
|
||||
private short transferFunctionIndex;
|
||||
|
||||
private short matrixIndex;
|
||||
|
||||
private String type = "nclc";
|
||||
|
||||
static final byte RANGE_UNSPECIFIED = 0;
|
||||
|
||||
static final byte AVCOL_RANGE_MPEG = 1;
|
||||
|
||||
static final byte AVCOL_RANGE_JPEG = 2;
|
||||
|
||||
private Byte colorRange = null;
|
||||
|
||||
public ColorExtension(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void setColorRange(Byte colorRange) {
|
||||
this.colorRange = colorRange;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
byte[] dst = new byte[4];
|
||||
input.get(dst);
|
||||
this.type = Platform.stringFromBytes(dst);
|
||||
this.primariesIndex = input.getShort();
|
||||
this.transferFunctionIndex = input.getShort();
|
||||
this.matrixIndex = input.getShort();
|
||||
if (input.hasRemaining())
|
||||
this.colorRange = input.get();
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.put(JCodecUtil2.asciiString(this.type));
|
||||
out.putShort(this.primariesIndex);
|
||||
out.putShort(this.transferFunctionIndex);
|
||||
out.putShort(this.matrixIndex);
|
||||
if (this.colorRange != null)
|
||||
out.put(this.colorRange.byteValue());
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "colr";
|
||||
}
|
||||
|
||||
public static ColorExtension createColorExtension(short primariesIndex, short transferFunctionIndex, short matrixIndex) {
|
||||
ColorExtension c = new ColorExtension(new Header(fourcc()));
|
||||
c.primariesIndex = primariesIndex;
|
||||
c.transferFunctionIndex = transferFunctionIndex;
|
||||
c.matrixIndex = matrixIndex;
|
||||
return c;
|
||||
}
|
||||
|
||||
public static ColorExtension createColr() {
|
||||
return new ColorExtension(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public short getPrimariesIndex() {
|
||||
return this.primariesIndex;
|
||||
}
|
||||
|
||||
public short getTransferFunctionIndex() {
|
||||
return this.transferFunctionIndex;
|
||||
}
|
||||
|
||||
public short getMatrixIndex() {
|
||||
return this.matrixIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class CompositionOffsetsBox extends FullBox {
|
||||
private Entry[] entries;
|
||||
|
||||
public static class Entry {
|
||||
public int count;
|
||||
|
||||
public int offset;
|
||||
|
||||
public Entry(int count, int offset) {
|
||||
this.count = count;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LongEntry {
|
||||
public long count;
|
||||
|
||||
public long offset;
|
||||
|
||||
public LongEntry(long count, long offset) {
|
||||
this.count = count;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
}
|
||||
|
||||
public CompositionOffsetsBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "ctts";
|
||||
}
|
||||
|
||||
public static CompositionOffsetsBox createCompositionOffsetsBox(Entry[] entries) {
|
||||
CompositionOffsetsBox ctts = new CompositionOffsetsBox(new Header(fourcc()));
|
||||
ctts.entries = entries;
|
||||
return ctts;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int num = input.getInt();
|
||||
this.entries = new Entry[num];
|
||||
for (int i = 0; i < num; i++)
|
||||
this.entries[i] = new Entry(input.getInt(), input.getInt());
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.entries.length);
|
||||
for (int i = 0; i < this.entries.length; i++) {
|
||||
out.putInt((this.entries[i]).count);
|
||||
out.putInt((this.entries[i]).offset);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.entries.length * 8;
|
||||
}
|
||||
|
||||
public Entry[] getEntries() {
|
||||
return this.entries;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class DataBox extends Box {
|
||||
private static final String FOURCC = "data";
|
||||
|
||||
private int type;
|
||||
|
||||
private int locale;
|
||||
|
||||
private byte[] data;
|
||||
|
||||
public DataBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static DataBox createDataBox(int type, int locale, byte[] data) {
|
||||
DataBox box = new DataBox(Header.createHeader("data", 0L));
|
||||
box.type = type;
|
||||
box.locale = locale;
|
||||
box.data = data;
|
||||
return box;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
this.type = buf.getInt();
|
||||
this.locale = buf.getInt();
|
||||
this.data = NIOUtils.toArray(NIOUtils.readBuf(buf));
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public int getLocale() {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.type);
|
||||
out.putInt(this.locale);
|
||||
out.put(this.data);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.data.length;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "data";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class DataInfoBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "dinf";
|
||||
}
|
||||
|
||||
public static DataInfoBox createDataInfoBox() {
|
||||
return new DataInfoBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public DataInfoBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public DataRefBox getDref() {
|
||||
return NodeBox.<DataRefBox>findFirst(this, DataRefBox.class, "dref");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DataRefBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "dref";
|
||||
}
|
||||
|
||||
public static DataRefBox createDataRefBox() {
|
||||
return new DataRefBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public DataRefBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
input.getInt();
|
||||
input.getInt();
|
||||
super.parse(input);
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.putInt(0);
|
||||
out.putInt(this.boxes.size());
|
||||
super.doWrite(out);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 8 + super.estimateSize();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class Edit {
|
||||
private long duration;
|
||||
|
||||
private long mediaTime;
|
||||
|
||||
private float rate;
|
||||
|
||||
public static Edit createEdit(Edit edit) {
|
||||
return new Edit(edit.duration, edit.mediaTime, edit.rate);
|
||||
}
|
||||
|
||||
public Edit(long duration, long mediaTime, float rate) {
|
||||
this.duration = duration;
|
||||
this.mediaTime = mediaTime;
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public long getMediaTime() {
|
||||
return this.mediaTime;
|
||||
}
|
||||
|
||||
public float getRate() {
|
||||
return this.rate;
|
||||
}
|
||||
|
||||
public void shift(long shift) {
|
||||
this.mediaTime += shift;
|
||||
}
|
||||
|
||||
public void setMediaTime(long l) {
|
||||
this.mediaTime = l;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EditListBox extends FullBox {
|
||||
private List<Edit> edits;
|
||||
|
||||
public static String fourcc() {
|
||||
return "elst";
|
||||
}
|
||||
|
||||
public static EditListBox createEditListBox(List<Edit> edits) {
|
||||
EditListBox elst = new EditListBox(new Header(fourcc()));
|
||||
elst.edits = edits;
|
||||
return elst;
|
||||
}
|
||||
|
||||
public EditListBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.edits = new ArrayList<>();
|
||||
long num = (long)input.getInt();
|
||||
for (int i = 0; (long)i < num; i++) {
|
||||
int duration = input.getInt();
|
||||
int mediaTime = input.getInt();
|
||||
float rate = (float)input.getInt() / 65536.0F;
|
||||
this.edits.add(new Edit((long)duration, (long)mediaTime, rate));
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.edits.size());
|
||||
for (Edit edit : this.edits) {
|
||||
out.putInt((int)edit.getDuration());
|
||||
out.putInt((int)edit.getMediaTime());
|
||||
out.putInt((int)(edit.getRate() * 65536.0F));
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.edits.size() * 12;
|
||||
}
|
||||
|
||||
public List<Edit> getEdits() {
|
||||
return this.edits;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class EncodedPixelBox extends ClearApertureBox {
|
||||
public static final String ENOF = "enof";
|
||||
|
||||
public static EncodedPixelBox createEncodedPixelBox(int width, int height) {
|
||||
EncodedPixelBox enof = new EncodedPixelBox(new Header("enof"));
|
||||
enof.width = (float)width;
|
||||
enof.height = (float)height;
|
||||
return enof;
|
||||
}
|
||||
|
||||
public EncodedPixelBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class EndianBox extends Box {
|
||||
private ByteOrder endian;
|
||||
|
||||
public static String fourcc() {
|
||||
return "enda";
|
||||
}
|
||||
|
||||
public static EndianBox createEndianBox(ByteOrder endian) {
|
||||
EndianBox endianBox = new EndianBox(new Header(fourcc()));
|
||||
endianBox.endian = endian;
|
||||
return endianBox;
|
||||
}
|
||||
|
||||
public EndianBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
long end = (long)input.getShort();
|
||||
if (end == 1L) {
|
||||
this.endian = ByteOrder.LITTLE_ENDIAN;
|
||||
} else {
|
||||
this.endian = ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putShort((short)((this.endian == ByteOrder.LITTLE_ENDIAN) ? 1 : 0));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public ByteOrder getEndian() {
|
||||
return this.endian;
|
||||
}
|
||||
|
||||
protected int calcSize() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class FielExtension extends Box {
|
||||
private int type;
|
||||
|
||||
private int order;
|
||||
|
||||
public FielExtension(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "fiel";
|
||||
}
|
||||
|
||||
public boolean isInterlaced() {
|
||||
return (this.type == 2);
|
||||
}
|
||||
|
||||
public boolean topFieldFirst() {
|
||||
return (this.order == 1 || this.order == 6);
|
||||
}
|
||||
|
||||
public String getOrderInterpretation() {
|
||||
if (isInterlaced())
|
||||
switch (this.order) {
|
||||
case 1:
|
||||
return "top";
|
||||
case 6:
|
||||
return "bottom";
|
||||
case 9:
|
||||
return "bottomtop";
|
||||
case 14:
|
||||
return "topbottom";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.type = input.get() & 0xFF;
|
||||
if (isInterlaced())
|
||||
this.order = input.get() & 0xFF;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.put((byte)this.type);
|
||||
out.put((byte)this.order);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class FileTypeBox extends Box {
|
||||
private String majorBrand;
|
||||
|
||||
private int minorVersion;
|
||||
|
||||
private Collection<String> compBrands;
|
||||
|
||||
public FileTypeBox(Header header) {
|
||||
super(header);
|
||||
this.compBrands = new LinkedList<>();
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "ftyp";
|
||||
}
|
||||
|
||||
public static FileTypeBox createFileTypeBox(String majorBrand, int minorVersion, Collection<String> compBrands) {
|
||||
FileTypeBox ftyp = new FileTypeBox(new Header(fourcc()));
|
||||
ftyp.majorBrand = majorBrand;
|
||||
ftyp.minorVersion = minorVersion;
|
||||
ftyp.compBrands = compBrands;
|
||||
return ftyp;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.majorBrand = NIOUtils.readString(input, 4);
|
||||
this.minorVersion = input.getInt();
|
||||
String brand;
|
||||
while (input.hasRemaining() && (brand = NIOUtils.readString(input, 4)) != null)
|
||||
this.compBrands.add(brand);
|
||||
}
|
||||
|
||||
public String getMajorBrand() {
|
||||
return this.majorBrand;
|
||||
}
|
||||
|
||||
public Collection<String> getCompBrands() {
|
||||
return this.compBrands;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.put(JCodecUtil2.asciiString(this.majorBrand));
|
||||
out.putInt(this.minorVersion);
|
||||
for (String string : this.compBrands)
|
||||
out.put(JCodecUtil2.asciiString(string));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int size = 13;
|
||||
for (String string : this.compBrands)
|
||||
size += (JCodecUtil2.asciiString(string)).length;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class FormatBox extends Box {
|
||||
private String fmt;
|
||||
|
||||
public FormatBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "frma";
|
||||
}
|
||||
|
||||
public static FormatBox createFormatBox(String fmt) {
|
||||
FormatBox frma = new FormatBox(new Header(fourcc()));
|
||||
frma.fmt = fmt;
|
||||
return frma;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.fmt = NIOUtils.readString(input, 4);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.put(JCodecUtil2.asciiString(this.fmt));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return (JCodecUtil2.asciiString(this.fmt)).length + 8;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class FullBox extends Box {
|
||||
protected byte version;
|
||||
|
||||
protected int flags;
|
||||
|
||||
public FullBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
int vf = input.getInt();
|
||||
this.version = (byte)(vf >> 24 & 0xFF);
|
||||
this.flags = vf & 0xFFFFFF;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.version << 24 | this.flags & 0xFFFFFF);
|
||||
}
|
||||
|
||||
public byte getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
public void setVersion(byte version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public void setFlags(int flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GamaExtension extends Box {
|
||||
private float gamma;
|
||||
|
||||
public static GamaExtension createGamaExtension(float gamma) {
|
||||
GamaExtension gamaExtension = new GamaExtension(new Header(fourcc()));
|
||||
gamaExtension.gamma = gamma;
|
||||
return gamaExtension;
|
||||
}
|
||||
|
||||
public GamaExtension(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
float g = (float)input.getInt();
|
||||
this.gamma = g / 65536.0F;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt((int)(this.gamma * 65536.0F));
|
||||
}
|
||||
|
||||
public float getGamma() {
|
||||
return this.gamma;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "gama";
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GenericMediaInfoBox extends FullBox {
|
||||
private short graphicsMode;
|
||||
|
||||
private short rOpColor;
|
||||
|
||||
private short gOpColor;
|
||||
|
||||
private short bOpColor;
|
||||
|
||||
private short balance;
|
||||
|
||||
public static String fourcc() {
|
||||
return "gmin";
|
||||
}
|
||||
|
||||
public static GenericMediaInfoBox createGenericMediaInfoBox() {
|
||||
return new GenericMediaInfoBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public GenericMediaInfoBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.graphicsMode = input.getShort();
|
||||
this.rOpColor = input.getShort();
|
||||
this.gOpColor = input.getShort();
|
||||
this.bOpColor = input.getShort();
|
||||
this.balance = input.getShort();
|
||||
input.getShort();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort(this.graphicsMode);
|
||||
out.putShort(this.rOpColor);
|
||||
out.putShort(this.gOpColor);
|
||||
out.putShort(this.bOpColor);
|
||||
out.putShort(this.balance);
|
||||
out.putShort((short)0);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class HandlerBox extends FullBox {
|
||||
private String componentType;
|
||||
|
||||
private String componentSubType;
|
||||
|
||||
private String componentManufacturer;
|
||||
|
||||
private int componentFlags;
|
||||
|
||||
private int componentFlagsMask;
|
||||
|
||||
private String componentName;
|
||||
|
||||
public HandlerBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "hdlr";
|
||||
}
|
||||
|
||||
public static HandlerBox createHandlerBox(String componentType, String componentSubType, String componentManufacturer, int componentFlags, int componentFlagsMask) {
|
||||
HandlerBox hdlr = new HandlerBox(new Header(fourcc()));
|
||||
hdlr.componentType = componentType;
|
||||
hdlr.componentSubType = componentSubType;
|
||||
hdlr.componentManufacturer = componentManufacturer;
|
||||
hdlr.componentFlags = componentFlags;
|
||||
hdlr.componentFlagsMask = componentFlagsMask;
|
||||
hdlr.componentName = "";
|
||||
return hdlr;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.componentType = NIOUtils.readString(input, 4);
|
||||
this.componentSubType = NIOUtils.readString(input, 4);
|
||||
this.componentManufacturer = NIOUtils.readString(input, 4);
|
||||
this.componentFlags = input.getInt();
|
||||
this.componentFlagsMask = input.getInt();
|
||||
this.componentName = NIOUtils.readString(input, input.remaining());
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.put(JCodecUtil2.asciiString(this.componentType));
|
||||
out.put(JCodecUtil2.asciiString(this.componentSubType));
|
||||
out.put(JCodecUtil2.asciiString(this.componentManufacturer));
|
||||
out.putInt(this.componentFlags);
|
||||
out.putInt(this.componentFlagsMask);
|
||||
if (this.componentName != null)
|
||||
out.put(JCodecUtil2.asciiString(this.componentName));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 12 + (JCodecUtil2.asciiString(this.componentType)).length + (JCodecUtil2.asciiString(this.componentSubType)).length + (
|
||||
JCodecUtil2.asciiString(this.componentManufacturer)).length + 9;
|
||||
}
|
||||
|
||||
public String getComponentType() {
|
||||
return this.componentType;
|
||||
}
|
||||
|
||||
public String getComponentSubType() {
|
||||
return this.componentSubType;
|
||||
}
|
||||
|
||||
public String getComponentManufacturer() {
|
||||
return this.componentManufacturer;
|
||||
}
|
||||
|
||||
public int getComponentFlags() {
|
||||
return this.componentFlags;
|
||||
}
|
||||
|
||||
public int getComponentFlagsMask() {
|
||||
return this.componentFlagsMask;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.io.StringReader;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class Header {
|
||||
public static final byte[] FOURCC_FREE = new byte[] { 102, 114, 101, 101 };
|
||||
|
||||
private static final long MAX_UNSIGNED_INT = 4294967296L;
|
||||
|
||||
private String fourcc;
|
||||
|
||||
private long size;
|
||||
|
||||
private boolean lng;
|
||||
|
||||
public Header(String fourcc) {
|
||||
this.fourcc = fourcc;
|
||||
}
|
||||
|
||||
public static Header createHeader(String fourcc, long size) {
|
||||
Header header = new Header(fourcc);
|
||||
header.size = size;
|
||||
return header;
|
||||
}
|
||||
|
||||
public static Header newHeader(String fourcc, long size, boolean lng) {
|
||||
Header header = new Header(fourcc);
|
||||
header.size = size;
|
||||
header.lng = lng;
|
||||
return header;
|
||||
}
|
||||
|
||||
public static Header read(ByteBuffer input) {
|
||||
long size = 0L;
|
||||
while (input.remaining() >= 4 && (size = Platform.unsignedInt(input.getInt())) == 0L);
|
||||
if (input.remaining() < 4 || (size < 8L && size != 1L)) {
|
||||
Logger.error("Broken atom of size " + size);
|
||||
return null;
|
||||
}
|
||||
String fourcc = NIOUtils.readString(input, 4);
|
||||
boolean lng = false;
|
||||
if (size == 1L)
|
||||
if (input.remaining() >= 8) {
|
||||
lng = true;
|
||||
size = input.getLong();
|
||||
} else {
|
||||
Logger.error("Broken atom of size " + size);
|
||||
return null;
|
||||
}
|
||||
return newHeader(fourcc, size, lng);
|
||||
}
|
||||
|
||||
public void skip(InputStream di) throws IOException {
|
||||
StringReader.sureSkip(di, this.size - headerSize());
|
||||
}
|
||||
|
||||
public long headerSize() {
|
||||
return (this.lng || this.size > 4294967296L) ? 16L : 8L;
|
||||
}
|
||||
|
||||
public static int estimateHeaderSize(int size) {
|
||||
return ((long)(size + 8) > 4294967296L) ? 16 : 8;
|
||||
}
|
||||
|
||||
public byte[] readContents(InputStream di) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for (int i = 0; (long)i < this.size - headerSize(); i++)
|
||||
baos.write(di.read());
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public String getFourcc() {
|
||||
return this.fourcc;
|
||||
}
|
||||
|
||||
public long getBodySize() {
|
||||
return this.size - headerSize();
|
||||
}
|
||||
|
||||
public void setBodySize(int length) {
|
||||
this.size = (long)length + headerSize();
|
||||
}
|
||||
|
||||
public void write(ByteBuffer out) {
|
||||
if (this.size > 4294967296L) {
|
||||
out.putInt(1);
|
||||
} else {
|
||||
out.putInt((int)this.size);
|
||||
}
|
||||
byte[] bt = JCodecUtil2.asciiString(this.fourcc);
|
||||
if (bt != null && bt.length == 4) {
|
||||
out.put(bt);
|
||||
} else {
|
||||
out.put(FOURCC_FREE);
|
||||
}
|
||||
if (this.size > 4294967296L)
|
||||
out.putLong(this.size);
|
||||
}
|
||||
|
||||
public void writeChannel(SeekableByteChannel output) throws IOException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(16);
|
||||
write(bb);
|
||||
bb.flip();
|
||||
output.write(bb);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int prime = 31;
|
||||
int result = 1;
|
||||
result = 31 * result + ((this.fourcc == null) ? 0 : this.fourcc.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Header other = (Header)obj;
|
||||
if (this.fourcc == null) {
|
||||
if (other.fourcc != null)
|
||||
return false;
|
||||
} else if (!this.fourcc.equals(other.fourcc)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mp4.Boxes;
|
||||
import org.jcodec.containers.mp4.IBoxFactory;
|
||||
|
||||
public class IListBox extends Box {
|
||||
private static final String FOURCC = "ilst";
|
||||
|
||||
private Map<Integer, List<Box>> values;
|
||||
|
||||
private IBoxFactory factory;
|
||||
|
||||
private static class LocalBoxes extends Boxes {
|
||||
LocalBoxes() {
|
||||
this.mappings.put(DataBox.fourcc(), DataBox.class);
|
||||
}
|
||||
}
|
||||
|
||||
public IListBox(Header atom) {
|
||||
super(atom);
|
||||
this.factory = new SimpleBoxFactory(new LocalBoxes());
|
||||
this.values = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static IListBox createIListBox(Map<Integer, List<Box>> values) {
|
||||
IListBox box = new IListBox(Header.createHeader("ilst", 0L));
|
||||
box.values = values;
|
||||
return box;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
while (input.remaining() >= 4) {
|
||||
int size = input.getInt();
|
||||
ByteBuffer local = NIOUtils.read(input, size - 4);
|
||||
int index = local.getInt();
|
||||
List<Box> children = new ArrayList<>();
|
||||
this.values.put(Integer.valueOf(index), children);
|
||||
while (local.hasRemaining()) {
|
||||
Header childAtom = Header.read(local);
|
||||
if (childAtom != null && (long)local.remaining() >= childAtom.getBodySize()) {
|
||||
Box box = Box.parseBox(NIOUtils.read(local, (int)childAtom.getBodySize()), childAtom, this.factory);
|
||||
children.add(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, List<Box>> getValues() {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
for (Map.Entry<Integer, List<Box>> entry : this.values.entrySet()) {
|
||||
ByteBuffer fork = out.duplicate();
|
||||
out.putInt(0);
|
||||
out.putInt(entry.getKey().intValue());
|
||||
for (Box box : entry.getValue())
|
||||
box.write(out);
|
||||
fork.putInt(out.position() - fork.position());
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int sz = 8;
|
||||
for (Map.Entry<Integer, List<Box>> entry : this.values.entrySet()) {
|
||||
for (Box box : entry.getValue())
|
||||
sz += 8 + box.estimateSize();
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "ilst";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.containers.mp4.Boxes;
|
||||
|
||||
public class KeysBox extends NodeBox {
|
||||
private static final String FOURCC = "keys";
|
||||
|
||||
private static class LocalBoxes extends Boxes {
|
||||
LocalBoxes() {
|
||||
this.mappings.put(MdtaBox.fourcc(), MdtaBox.class);
|
||||
}
|
||||
}
|
||||
|
||||
public KeysBox(Header atom) {
|
||||
super(atom);
|
||||
this.factory = new SimpleBoxFactory(new LocalBoxes());
|
||||
}
|
||||
|
||||
public static KeysBox createKeysBox() {
|
||||
return new KeysBox(Header.createHeader("keys", 0L));
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
int vf = input.getInt();
|
||||
int cnt = input.getInt();
|
||||
super.parse(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(0);
|
||||
out.putInt(this.boxes.size());
|
||||
super.doWrite(out);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "keys";
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 8 + super.estimateSize();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class LoadSettingsBox extends Box {
|
||||
private int preloadStartTime;
|
||||
|
||||
private int preloadDuration;
|
||||
|
||||
private int preloadFlags;
|
||||
|
||||
private int defaultHints;
|
||||
|
||||
public static String fourcc() {
|
||||
return "load";
|
||||
}
|
||||
|
||||
public LoadSettingsBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.preloadStartTime = input.getInt();
|
||||
this.preloadDuration = input.getInt();
|
||||
this.preloadFlags = input.getInt();
|
||||
this.defaultHints = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.preloadStartTime);
|
||||
out.putInt(this.preloadDuration);
|
||||
out.putInt(this.preloadFlags);
|
||||
out.putInt(this.defaultHints);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
public int getPreloadStartTime() {
|
||||
return this.preloadStartTime;
|
||||
}
|
||||
|
||||
public int getPreloadDuration() {
|
||||
return this.preloadDuration;
|
||||
}
|
||||
|
||||
public int getPreloadFlags() {
|
||||
return this.preloadFlags;
|
||||
}
|
||||
|
||||
public int getDefaultHints() {
|
||||
return this.defaultHints;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MP4ABox extends Box {
|
||||
private int val;
|
||||
|
||||
public MP4ABox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.val);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.val = input.getInt();
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MdtaBox extends Box {
|
||||
private static final String FOURCC = "mdta";
|
||||
|
||||
private String key;
|
||||
|
||||
public MdtaBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static MdtaBox createMdtaBox(String key) {
|
||||
MdtaBox box = new MdtaBox(Header.createHeader("mdta", 0L));
|
||||
box.key = key;
|
||||
return box;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
this.key = Platform.stringFromBytes(NIOUtils.toArray(NIOUtils.readBuf(buf)));
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.put(this.key.getBytes());
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return (this.key.getBytes()).length;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "mdta";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class MediaBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "mdia";
|
||||
}
|
||||
|
||||
public static MediaBox createMediaBox() {
|
||||
return new MediaBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public MediaBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public MediaInfoBox getMinf() {
|
||||
return NodeBox.<MediaInfoBox>findFirst(this, MediaInfoBox.class, "minf");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.containers.mp4.TimeUtil;
|
||||
|
||||
public class MediaHeaderBox extends FullBox {
|
||||
private long created;
|
||||
|
||||
private long modified;
|
||||
|
||||
private int timescale;
|
||||
|
||||
private long duration;
|
||||
|
||||
private int language;
|
||||
|
||||
private int quality;
|
||||
|
||||
public MediaHeaderBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "mdhd";
|
||||
}
|
||||
|
||||
public static MediaHeaderBox createMediaHeaderBox(int timescale, long duration, int language, long created, long modified, int quality) {
|
||||
MediaHeaderBox mdhd = new MediaHeaderBox(new Header(fourcc()));
|
||||
mdhd.timescale = timescale;
|
||||
mdhd.duration = duration;
|
||||
mdhd.language = language;
|
||||
mdhd.created = created;
|
||||
mdhd.modified = modified;
|
||||
mdhd.quality = quality;
|
||||
return mdhd;
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return this.timescale;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return this.created;
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return this.modified;
|
||||
}
|
||||
|
||||
public int getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
public int getQuality() {
|
||||
return this.quality;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public void setTimescale(int timescale) {
|
||||
this.timescale = timescale;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if (this.version == 0) {
|
||||
this.created = TimeUtil.fromMovTime(input.getInt());
|
||||
this.modified = TimeUtil.fromMovTime(input.getInt());
|
||||
this.timescale = input.getInt();
|
||||
this.duration = (long)input.getInt();
|
||||
} else if (this.version == 1) {
|
||||
this.created = TimeUtil.fromMovTime((int)input.getLong());
|
||||
this.modified = TimeUtil.fromMovTime((int)input.getLong());
|
||||
this.timescale = input.getInt();
|
||||
this.duration = input.getLong();
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(TimeUtil.toMovTime(this.created));
|
||||
out.putInt(TimeUtil.toMovTime(this.modified));
|
||||
out.putInt(this.timescale);
|
||||
out.putInt((int)this.duration);
|
||||
out.putShort((short)this.language);
|
||||
out.putShort((short)this.quality);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class MediaInfoBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "minf";
|
||||
}
|
||||
|
||||
public static MediaInfoBox createMediaInfoBox() {
|
||||
return new MediaInfoBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public MediaInfoBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public DataInfoBox getDinf() {
|
||||
return NodeBox.<DataInfoBox>findFirst(this, DataInfoBox.class, "dinf");
|
||||
}
|
||||
|
||||
public NodeBox getStbl() {
|
||||
return NodeBox.<NodeBox>findFirst(this, NodeBox.class, "stbl");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class MetaBox extends NodeBox {
|
||||
private static final String FOURCC = "meta";
|
||||
|
||||
public MetaBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static MetaBox createMetaBox() {
|
||||
return new MetaBox(Header.createHeader(fourcc(), 0L));
|
||||
}
|
||||
|
||||
public Map<String, MetaValue> getKeyedMeta() {
|
||||
Map<String, MetaValue> result = new LinkedHashMap<>();
|
||||
IListBox ilst = NodeBox.<IListBox>findFirst(this, IListBox.class, IListBox.fourcc());
|
||||
MdtaBox[] keys = NodeBox.<MdtaBox>findAllPath(this, MdtaBox.class, new String[] { KeysBox.fourcc(), MdtaBox.fourcc() });
|
||||
if (ilst == null || keys.length == 0)
|
||||
return result;
|
||||
for (Map.Entry<Integer, List<Box>> entry : ilst.getValues().entrySet()) {
|
||||
Integer index = entry.getKey();
|
||||
if (index == null)
|
||||
continue;
|
||||
DataBox db = getDataBox(entry.getValue());
|
||||
if (db == null)
|
||||
continue;
|
||||
MetaValue value = MetaValue.createOtherWithLocale(db.getType(), db.getLocale(), db.getData());
|
||||
if (index > 0 && index <= keys.length)
|
||||
result.put(keys[index - 1].getKey(), value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DataBox getDataBox(List<Box> value) {
|
||||
for (Box box : value) {
|
||||
if (box instanceof DataBox)
|
||||
return (DataBox)box;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<Integer, MetaValue> getItunesMeta() {
|
||||
Map<Integer, MetaValue> result = new LinkedHashMap<>();
|
||||
IListBox ilst = NodeBox.<IListBox>findFirst(this, IListBox.class, IListBox.fourcc());
|
||||
if (ilst == null)
|
||||
return result;
|
||||
for (Map.Entry<Integer, List<Box>> entry : ilst.getValues().entrySet()) {
|
||||
Integer index = entry.getKey();
|
||||
if (index == null)
|
||||
continue;
|
||||
DataBox db = getDataBox(entry.getValue());
|
||||
if (db == null)
|
||||
continue;
|
||||
MetaValue value = MetaValue.createOtherWithLocale(db.getType(), db.getLocale(), db.getData());
|
||||
result.put(index, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setKeyedMeta(Map<String, MetaValue> map) {
|
||||
if (map.isEmpty())
|
||||
return;
|
||||
KeysBox keys = KeysBox.createKeysBox();
|
||||
Map<Integer, List<Box>> data = new LinkedHashMap<>();
|
||||
int i = 1;
|
||||
for (Map.Entry<String, MetaValue> entry : map.entrySet()) {
|
||||
keys.add(MdtaBox.createMdtaBox(entry.getKey()));
|
||||
MetaValue v = entry.getValue();
|
||||
List<Box> children = new ArrayList<>();
|
||||
children.add(DataBox.createDataBox(v.getType(), v.getLocale(), v.getData()));
|
||||
data.put(Integer.valueOf(i), children);
|
||||
i++;
|
||||
}
|
||||
IListBox ilst = IListBox.createIListBox(data);
|
||||
replaceBox(keys);
|
||||
replaceBox(ilst);
|
||||
}
|
||||
|
||||
public void setItunesMeta(Map<Integer, MetaValue> map) {
|
||||
Map<Integer, List<Box>> data;
|
||||
if (map.isEmpty())
|
||||
return;
|
||||
Map<Integer, MetaValue> copy = new LinkedHashMap<>();
|
||||
copy.putAll(map);
|
||||
IListBox ilst = NodeBox.<IListBox>findFirst(this, IListBox.class, IListBox.fourcc());
|
||||
if (ilst == null) {
|
||||
data = new LinkedHashMap<>();
|
||||
} else {
|
||||
data = ilst.getValues();
|
||||
for (Map.Entry<Integer, List<Box>> entry : data.entrySet()) {
|
||||
int index = entry.getKey();
|
||||
MetaValue v = copy.get(Integer.valueOf(index));
|
||||
if (v != null) {
|
||||
DataBox dataBox = DataBox.createDataBox(v.getType(), v.getLocale(), v.getData());
|
||||
dropChildBox(entry.getValue(), DataBox.fourcc());
|
||||
((List<DataBox>)entry.getValue()).add(dataBox);
|
||||
copy.remove(Integer.valueOf(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Integer, MetaValue> entry : copy.entrySet()) {
|
||||
int index = entry.getKey();
|
||||
MetaValue v = entry.getValue();
|
||||
DataBox dataBox = DataBox.createDataBox(v.getType(), v.getLocale(), v.getData());
|
||||
List<Box> children = new ArrayList<>();
|
||||
data.put(Integer.valueOf(index), children);
|
||||
children.add(dataBox);
|
||||
}
|
||||
Set<Integer> keySet = new HashSet<>(data.keySet());
|
||||
keySet.removeAll(map.keySet());
|
||||
for (Integer dropped : keySet)
|
||||
data.remove(dropped);
|
||||
replaceBox(IListBox.createIListBox(data));
|
||||
}
|
||||
|
||||
private void dropChildBox(List<Box> children, String fourcc2) {
|
||||
ListIterator<Box> listIterator = children.listIterator();
|
||||
while (listIterator.hasNext()) {
|
||||
Box next = listIterator.next();
|
||||
if (fourcc2.equals(next.getFourcc()))
|
||||
listIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "meta";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MetaValue {
|
||||
public static final int TYPE_STRING_UTF16 = 2;
|
||||
|
||||
public static final int TYPE_STRING_UTF8 = 1;
|
||||
|
||||
public static final int TYPE_FLOAT_64 = 24;
|
||||
|
||||
public static final int TYPE_FLOAT_32 = 23;
|
||||
|
||||
public static final int TYPE_INT_32 = 67;
|
||||
|
||||
public static final int TYPE_INT_16 = 66;
|
||||
|
||||
public static final int TYPE_INT_8 = 65;
|
||||
|
||||
public static final int TYPE_INT_V = 22;
|
||||
|
||||
public static final int TYPE_UINT_V = 21;
|
||||
|
||||
public static final int TYPE_JPEG = 13;
|
||||
|
||||
public static final int TYPE_PNG = 13;
|
||||
|
||||
public static final int TYPE_BMP = 27;
|
||||
|
||||
private int type;
|
||||
|
||||
private int locale;
|
||||
|
||||
private byte[] data;
|
||||
|
||||
private MetaValue(int type, int locale, byte[] data) {
|
||||
this.type = type;
|
||||
this.locale = locale;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static MetaValue createInt(int value) {
|
||||
return new MetaValue(21, 0, fromInt(value));
|
||||
}
|
||||
|
||||
public static MetaValue createFloat(float value) {
|
||||
return new MetaValue(23, 0, fromFloat(value));
|
||||
}
|
||||
|
||||
public static MetaValue createString(String value) {
|
||||
return new MetaValue(1, 0, Platform.getBytesForCharset(value, "UTF-8"));
|
||||
}
|
||||
|
||||
public static MetaValue createOther(int type, byte[] data) {
|
||||
return new MetaValue(type, 0, data);
|
||||
}
|
||||
|
||||
public static MetaValue createOtherWithLocale(int type, int locale, byte[] data) {
|
||||
return new MetaValue(type, locale, data);
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
if (this.type == 21 || this.type == 22)
|
||||
switch (this.data.length) {
|
||||
case 1:
|
||||
return this.data[0];
|
||||
case 2:
|
||||
return toInt16(this.data);
|
||||
case 3:
|
||||
return toInt24(this.data);
|
||||
case 4:
|
||||
return toInt32(this.data);
|
||||
}
|
||||
if (this.type == 65)
|
||||
return this.data[0];
|
||||
if (this.type == 66)
|
||||
return toInt16(this.data);
|
||||
if (this.type == 67)
|
||||
return toInt32(this.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public double getFloat() {
|
||||
if (this.type == 23)
|
||||
return (double)toFloat(this.data);
|
||||
if (this.type == 24)
|
||||
return toDouble(this.data);
|
||||
return 0.0D;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
if (this.type == 1)
|
||||
return Platform.stringFromCharset(this.data, "UTF-8");
|
||||
if (this.type == 2)
|
||||
return Platform.stringFromCharset(this.data, "UTF-16BE");
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInt() {
|
||||
return (this.type == 21 || this.type == 22 || this.type == 65 || this.type == 66 || this.type == 67);
|
||||
}
|
||||
|
||||
public boolean isString() {
|
||||
return (this.type == 1 || this.type == 2);
|
||||
}
|
||||
|
||||
public boolean isFloat() {
|
||||
return (this.type == 23 || this.type == 24);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (isInt())
|
||||
return String.valueOf(getInt());
|
||||
if (isFloat())
|
||||
return String.valueOf(getFloat());
|
||||
if (isString())
|
||||
return String.valueOf(getString());
|
||||
return "BLOB";
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public int getLocale() {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private static byte[] fromFloat(float floatValue) {
|
||||
byte[] bytes = new byte[4];
|
||||
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
bb.putFloat(floatValue);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] fromInt(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
bb.putInt(value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private int toInt16(byte[] data) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
return bb.getShort();
|
||||
}
|
||||
|
||||
private int toInt24(byte[] data) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
return (bb.getShort() & 0xFFFF) << 8 | bb.get() & 0xFF;
|
||||
}
|
||||
|
||||
private int toInt32(byte[] data) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
return bb.getInt();
|
||||
}
|
||||
|
||||
private float toFloat(byte[] data) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
return bb.getFloat();
|
||||
}
|
||||
|
||||
private double toDouble(byte[] data) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
return bb.getDouble();
|
||||
}
|
||||
|
||||
public boolean isBlob() {
|
||||
return (!isFloat() && !isInt() && !isString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import org.jcodec.common.model.Rational;
|
||||
import org.jcodec.common.model.Size;
|
||||
|
||||
public class MovieBox extends NodeBox {
|
||||
public MovieBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "moov";
|
||||
}
|
||||
|
||||
public static MovieBox createMovieBox() {
|
||||
return new MovieBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public TrakBox[] getTracks() {
|
||||
return NodeBox.<TrakBox>findAll(this, TrakBox.class, "trak");
|
||||
}
|
||||
|
||||
public TrakBox getVideoTrack() {
|
||||
TrakBox[] tracks = getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
if (trakBox.isVideo())
|
||||
return trakBox;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TrakBox getTimecodeTrack() {
|
||||
TrakBox[] tracks = getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
if (trakBox.isTimecode())
|
||||
return trakBox;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return getMovieHeader().getTimescale();
|
||||
}
|
||||
|
||||
public long rescale(long tv, long ts) {
|
||||
return tv * (long)getTimescale() / ts;
|
||||
}
|
||||
|
||||
public void fixTimescale(int newTs) {
|
||||
int oldTs = getTimescale();
|
||||
setTimescale(newTs);
|
||||
TrakBox[] tracks = getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
trakBox.setDuration(rescale(trakBox.getDuration(), (long)oldTs));
|
||||
List<Edit> edits = trakBox.getEdits();
|
||||
if (edits != null) {
|
||||
ListIterator<Edit> lit = edits.listIterator();
|
||||
while (lit.hasNext()) {
|
||||
Edit edit = lit.next();
|
||||
lit.set(new Edit(rescale(edit.getDuration(), (long)oldTs), edit.getMediaTime(), edit.getRate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
setDuration(rescale(getDuration(), (long)oldTs));
|
||||
}
|
||||
|
||||
private void setTimescale(int newTs) {
|
||||
NodeBox.<MovieHeaderBox>findFirst(this, MovieHeaderBox.class, "mvhd").setTimescale(newTs);
|
||||
}
|
||||
|
||||
public void setDuration(long movDuration) {
|
||||
getMovieHeader().setDuration(movDuration);
|
||||
}
|
||||
|
||||
private MovieHeaderBox getMovieHeader() {
|
||||
return NodeBox.<MovieHeaderBox>findFirst(this, MovieHeaderBox.class, "mvhd");
|
||||
}
|
||||
|
||||
public List<TrakBox> getAudioTracks() {
|
||||
ArrayList<TrakBox> result = new ArrayList<>();
|
||||
TrakBox[] tracks = getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
if (trakBox.isAudio())
|
||||
result.add(trakBox);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return getMovieHeader().getDuration();
|
||||
}
|
||||
|
||||
public TrakBox importTrack(MovieBox movie, TrakBox track) {
|
||||
TrakBox newTrack = (TrakBox)NodeBox.cloneBox(track, 1048576, this.factory);
|
||||
List<Edit> edits = newTrack.getEdits();
|
||||
ArrayList<Edit> result = new ArrayList<>();
|
||||
if (edits != null)
|
||||
for (Edit edit : edits)
|
||||
result.add(new Edit(rescale(edit.getDuration(), (long)movie.getTimescale()), edit.getMediaTime(),
|
||||
edit.getRate()));
|
||||
newTrack.setEdits(result);
|
||||
return newTrack;
|
||||
}
|
||||
|
||||
public void appendTrack(TrakBox newTrack) {
|
||||
newTrack.getTrackHeader().setNo(getMovieHeader().getNextTrackId());
|
||||
getMovieHeader().setNextTrackId(getMovieHeader().getNextTrackId() + 1);
|
||||
this.boxes.add(newTrack);
|
||||
}
|
||||
|
||||
public boolean isPureRefMovie() {
|
||||
boolean pureRef = true;
|
||||
TrakBox[] tracks = getTracks();
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
pureRef &= trakBox.isPureRef();
|
||||
}
|
||||
return pureRef;
|
||||
}
|
||||
|
||||
public void updateDuration() {
|
||||
long l;
|
||||
TrakBox[] tracks = getTracks();
|
||||
int j = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
TrakBox trakBox = tracks[i];
|
||||
if (trakBox.getDuration() < j)
|
||||
l = trakBox.getDuration();
|
||||
}
|
||||
getMovieHeader().setDuration(l);
|
||||
}
|
||||
|
||||
public Size getDisplaySize() {
|
||||
TrakBox videoTrack = getVideoTrack();
|
||||
if (videoTrack == null)
|
||||
return null;
|
||||
ClearApertureBox clef = NodeBox.<ClearApertureBox>findFirstPath(videoTrack, ClearApertureBox.class, Box.path("tapt.clef"));
|
||||
if (clef != null)
|
||||
return applyMatrix(videoTrack, new Size((int)clef.getWidth(), (int)clef.getHeight()));
|
||||
Box box = NodeBox.<SampleDescriptionBox>findFirstPath(videoTrack, SampleDescriptionBox.class, Box.path("mdia.minf.stbl.stsd")).getBoxes()
|
||||
.get(0);
|
||||
if (box == null || !(box instanceof VideoSampleEntry))
|
||||
return null;
|
||||
VideoSampleEntry vs = (VideoSampleEntry)box;
|
||||
Rational par = videoTrack.getPAR();
|
||||
return applyMatrix(videoTrack, new Size(
|
||||
vs.getWidth() * par.getNum() / par.getDen(), vs.getHeight()));
|
||||
}
|
||||
|
||||
private Size applyMatrix(TrakBox videoTrack, Size size) {
|
||||
int[] matrix = videoTrack.getTrackHeader().getMatrix();
|
||||
return new Size((int)((double)size.getWidth() * (double)matrix[0] / 65536.0D), (int)((double)size.getHeight() * (double)matrix[4] / 65536.0D));
|
||||
}
|
||||
|
||||
public Size getStoredSize() {
|
||||
TrakBox videoTrack = getVideoTrack();
|
||||
if (videoTrack == null)
|
||||
return null;
|
||||
EncodedPixelBox enof = NodeBox.<EncodedPixelBox>findFirstPath(videoTrack, EncodedPixelBox.class, Box.path("tapt.enof"));
|
||||
if (enof != null)
|
||||
return new Size((int)enof.getWidth(), (int)enof.getHeight());
|
||||
Box box = NodeBox.<SampleDescriptionBox>findFirstPath(videoTrack, SampleDescriptionBox.class, Box.path("mdia.minf.stbl.stsd")).getBoxes()
|
||||
.get(0);
|
||||
if (box == null || !(box instanceof VideoSampleEntry))
|
||||
return null;
|
||||
VideoSampleEntry vs = (VideoSampleEntry)box;
|
||||
return new Size(vs.getWidth(), vs.getHeight());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class MovieExtendsBox extends NodeBox {
|
||||
public MovieExtendsBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "mvex";
|
||||
}
|
||||
|
||||
public static MovieExtendsBox createMovieExtendsBox() {
|
||||
return new MovieExtendsBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MovieExtendsHeaderBox extends FullBox {
|
||||
private int fragmentDuration;
|
||||
|
||||
public MovieExtendsHeaderBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "mehd";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.fragmentDuration = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.fragmentDuration);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public int getFragmentDuration() {
|
||||
return this.fragmentDuration;
|
||||
}
|
||||
|
||||
public void setFragmentDuration(int fragmentDuration) {
|
||||
this.fragmentDuration = fragmentDuration;
|
||||
}
|
||||
|
||||
public static MovieExtendsHeaderBox createMovieExtendsHeaderBox() {
|
||||
return new MovieExtendsHeaderBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class MovieFragmentBox extends NodeBox {
|
||||
private MovieBox moov;
|
||||
|
||||
public MovieFragmentBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "moof";
|
||||
}
|
||||
|
||||
public MovieBox getMovie() {
|
||||
return this.moov;
|
||||
}
|
||||
|
||||
public void setMovie(MovieBox moov) {
|
||||
this.moov = moov;
|
||||
}
|
||||
|
||||
public TrackFragmentBox[] getTracks() {
|
||||
return NodeBox.<TrackFragmentBox>findAll(this, TrackFragmentBox.class, TrackFragmentBox.fourcc());
|
||||
}
|
||||
|
||||
public int getSequenceNumber() {
|
||||
MovieFragmentHeaderBox mfhd = NodeBox.<MovieFragmentHeaderBox>findFirst(this, MovieFragmentHeaderBox.class, MovieFragmentHeaderBox.fourcc());
|
||||
if (mfhd == null)
|
||||
throw new RuntimeException("Corrupt movie fragment, no header atom found");
|
||||
return mfhd.getSequenceNumber();
|
||||
}
|
||||
|
||||
public static MovieFragmentBox createMovieFragmentBox() {
|
||||
return new MovieFragmentBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MovieFragmentHeaderBox extends FullBox {
|
||||
private int sequenceNumber;
|
||||
|
||||
public MovieFragmentHeaderBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "mfhd";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.sequenceNumber = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.sequenceNumber);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public int getSequenceNumber() {
|
||||
return this.sequenceNumber;
|
||||
}
|
||||
|
||||
public void setSequenceNumber(int sequenceNumber) {
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
}
|
||||
|
||||
public static MovieFragmentHeaderBox createMovieFragmentHeaderBox() {
|
||||
return new MovieFragmentHeaderBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mp4.TimeUtil;
|
||||
|
||||
public class MovieHeaderBox extends FullBox {
|
||||
private int timescale;
|
||||
|
||||
private long duration;
|
||||
|
||||
private float rate;
|
||||
|
||||
private float volume;
|
||||
|
||||
private long created;
|
||||
|
||||
private long modified;
|
||||
|
||||
private int[] matrix;
|
||||
|
||||
private int nextTrackId;
|
||||
|
||||
public static String fourcc() {
|
||||
return "mvhd";
|
||||
}
|
||||
|
||||
public static MovieHeaderBox createMovieHeaderBox(int timescale, long duration, float rate, float volume, long created, long modified, int[] matrix, int nextTrackId) {
|
||||
MovieHeaderBox mvhd = new MovieHeaderBox(new Header(fourcc()));
|
||||
mvhd.timescale = timescale;
|
||||
mvhd.duration = duration;
|
||||
mvhd.rate = rate;
|
||||
mvhd.volume = volume;
|
||||
mvhd.created = created;
|
||||
mvhd.modified = modified;
|
||||
mvhd.matrix = matrix;
|
||||
mvhd.nextTrackId = nextTrackId;
|
||||
return mvhd;
|
||||
}
|
||||
|
||||
public MovieHeaderBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return this.timescale;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public int getNextTrackId() {
|
||||
return this.nextTrackId;
|
||||
}
|
||||
|
||||
public float getRate() {
|
||||
return this.rate;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return this.created;
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return this.modified;
|
||||
}
|
||||
|
||||
public int[] getMatrix() {
|
||||
return this.matrix;
|
||||
}
|
||||
|
||||
public void setTimescale(int newTs) {
|
||||
this.timescale = newTs;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public void setNextTrackId(int nextTrackId) {
|
||||
this.nextTrackId = nextTrackId;
|
||||
}
|
||||
|
||||
private int[] readMatrix(ByteBuffer input) {
|
||||
int[] matrix = new int[9];
|
||||
for (int i = 0; i < 9; i++)
|
||||
matrix[i] = input.getInt();
|
||||
return matrix;
|
||||
}
|
||||
|
||||
private float readVolume(ByteBuffer input) {
|
||||
return (float)input.getShort() / 256.0F;
|
||||
}
|
||||
|
||||
private float readRate(ByteBuffer input) {
|
||||
return (float)input.getInt() / 65536.0F;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if (this.version == 0) {
|
||||
this.created = TimeUtil.fromMovTime(input.getInt());
|
||||
this.modified = TimeUtil.fromMovTime(input.getInt());
|
||||
this.timescale = input.getInt();
|
||||
this.duration = (long)input.getInt();
|
||||
} else if (this.version == 1) {
|
||||
this.created = TimeUtil.fromMovTime((int)input.getLong());
|
||||
this.modified = TimeUtil.fromMovTime((int)input.getLong());
|
||||
this.timescale = input.getInt();
|
||||
this.duration = input.getLong();
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported version");
|
||||
}
|
||||
this.rate = readRate(input);
|
||||
this.volume = readVolume(input);
|
||||
NIOUtils.skip(input, 10);
|
||||
this.matrix = readMatrix(input);
|
||||
NIOUtils.skip(input, 24);
|
||||
this.nextTrackId = input.getInt();
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(TimeUtil.toMovTime(this.created));
|
||||
out.putInt(TimeUtil.toMovTime(this.modified));
|
||||
out.putInt(this.timescale);
|
||||
out.putInt((int)this.duration);
|
||||
writeFixed1616(out, this.rate);
|
||||
writeFixed88(out, this.volume);
|
||||
out.put(new byte[10]);
|
||||
writeMatrix(out);
|
||||
out.put(new byte[24]);
|
||||
out.putInt(this.nextTrackId);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 144;
|
||||
}
|
||||
|
||||
private void writeMatrix(ByteBuffer out) {
|
||||
for (int j = 0; j < Math.min(9, this.matrix.length); j++)
|
||||
out.putInt(this.matrix[j]);
|
||||
for (int i = Math.min(9, this.matrix.length); i < 9; i++)
|
||||
out.putInt(0);
|
||||
}
|
||||
|
||||
private void writeFixed88(ByteBuffer out, float volume) {
|
||||
out.putShort((short)(int)((double)volume * 256.0D));
|
||||
}
|
||||
|
||||
private void writeFixed1616(ByteBuffer out, float rate) {
|
||||
out.putInt((int)((double)rate * 65536.0D));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class NameBox extends Box {
|
||||
private String name;
|
||||
|
||||
public static String fourcc() {
|
||||
return "name";
|
||||
}
|
||||
|
||||
public static NameBox createNameBox(String name) {
|
||||
NameBox box = new NameBox(new Header(fourcc()));
|
||||
box.name = name;
|
||||
return box;
|
||||
}
|
||||
|
||||
public NameBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.name = NIOUtils.readNullTermString(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.put(JCodecUtil2.asciiString(this.name));
|
||||
out.putInt(0);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 12 + (JCodecUtil2.asciiString(this.name)).length;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.containers.mp4.IBoxFactory;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class NodeBox extends Box {
|
||||
protected List<Box> boxes;
|
||||
|
||||
protected IBoxFactory factory;
|
||||
|
||||
public NodeBox(Header atom) {
|
||||
super(atom);
|
||||
this.boxes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void setFactory(IBoxFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
while (input.remaining() >= 8) {
|
||||
Box child = parseChildBox(input, this.factory);
|
||||
if (child != null)
|
||||
this.boxes.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
public static Box parseChildBox(ByteBuffer input, IBoxFactory factory) {
|
||||
ByteBuffer fork = input.duplicate();
|
||||
while (input.remaining() >= 4 && fork.getInt() == 0)
|
||||
input.getInt();
|
||||
if (input.remaining() < 4)
|
||||
return null;
|
||||
Box ret = null;
|
||||
Header childAtom = Header.read(input);
|
||||
if (childAtom != null && (long)input.remaining() >= childAtom.getBodySize())
|
||||
ret = Box.parseBox(NIOUtils.read(input, (int)childAtom.getBodySize()), childAtom, factory);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public List<Box> getBoxes() {
|
||||
return this.boxes;
|
||||
}
|
||||
|
||||
public void add(Box box) {
|
||||
this.boxes.add(box);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
for (Box box : this.boxes)
|
||||
box.write(out);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int total = 0;
|
||||
for (Box box : this.boxes)
|
||||
total += box.estimateSize();
|
||||
return total + Header.estimateHeaderSize(total);
|
||||
}
|
||||
|
||||
public void addFirst(MovieHeaderBox box) {
|
||||
this.boxes.add(0, box);
|
||||
}
|
||||
|
||||
public void replace(String fourcc, Box box) {
|
||||
removeChildren(new String[] { fourcc });
|
||||
add(box);
|
||||
}
|
||||
|
||||
public void replaceBox(Box box) {
|
||||
removeChildren(new String[] { box.getFourcc() });
|
||||
add(box);
|
||||
}
|
||||
|
||||
protected void dump(StringBuilder sb) {
|
||||
sb.append("{\"tag\":\"" + this.header.getFourcc() + "\",");
|
||||
sb.append("\"boxes\": [");
|
||||
dumpBoxes(sb);
|
||||
sb.append("]");
|
||||
sb.append("}");
|
||||
}
|
||||
|
||||
protected void dumpBoxes(StringBuilder sb) {
|
||||
for (int i = 0; i < this.boxes.size(); i++) {
|
||||
this.boxes.get(i).dump(sb);
|
||||
if (i < this.boxes.size() - 1)
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
public void removeChildren(String[] fourcc) {
|
||||
for (Iterator<Box> it = this.boxes.iterator(); it.hasNext(); ) {
|
||||
Box box = it.next();
|
||||
String fcc = box.getFourcc();
|
||||
for (int i = 0; i < fourcc.length; i++) {
|
||||
String cand = fourcc[i];
|
||||
if (cand.equals(fcc)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Box doCloneBox(Box box, int approxSize, IBoxFactory bf) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(approxSize);
|
||||
box.write(buf);
|
||||
buf.flip();
|
||||
return parseChildBox(buf, bf);
|
||||
}
|
||||
|
||||
public static Box cloneBox(Box box, int approxSize, IBoxFactory bf) {
|
||||
return doCloneBox(box, approxSize, bf);
|
||||
}
|
||||
|
||||
public static <T extends Box> T[] findDeep(Box box, Class<T> class1, String name) {
|
||||
List<T> storage = new ArrayList<>();
|
||||
findDeepInner(box, class1, name, storage);
|
||||
return storage.<T>toArray((T[])Array.newInstance(class1, 0));
|
||||
}
|
||||
|
||||
public static <T extends Box> void findDeepInner(Box box, Class<T> class1, String name, List<T> storage) {
|
||||
if (box == null)
|
||||
return;
|
||||
if (name.equals(box.getHeader().getFourcc())) {
|
||||
storage.add((T)box);
|
||||
return;
|
||||
}
|
||||
if (box instanceof NodeBox) {
|
||||
NodeBox nb = (NodeBox)box;
|
||||
for (Box candidate : nb.getBoxes())
|
||||
findDeepInner(candidate, class1, name, storage);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends Box> T[] findAll(Box box, Class<T> class1, String path) {
|
||||
return findAllPath(box, class1, new String[] { path });
|
||||
}
|
||||
|
||||
public static <T extends Box> T findFirst(NodeBox box, Class<T> clazz, String path) {
|
||||
return findFirstPath(box, clazz, new String[] { path });
|
||||
}
|
||||
|
||||
public static <T extends Box> T findFirstPath(NodeBox box, Class<T> clazz, String[] path) {
|
||||
T[] result = findAllPath(box, clazz, path);
|
||||
return (result.length > 0) ? result[0] : null;
|
||||
}
|
||||
|
||||
public static <T extends Box> T[] findAllPath(Box box, Class<T> class1, String[] path) {
|
||||
List<Box> result = new LinkedList<>();
|
||||
findBox(box, new ArrayList<>(Arrays.asList(path)), result);
|
||||
for (ListIterator<Box> it = result.listIterator(); it.hasNext(); ) {
|
||||
Box next = it.next();
|
||||
if (next == null) {
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
if (!Platform.isAssignableFrom(class1, next.getClass()))
|
||||
try {
|
||||
it.set(Box.asBox(class1, next));
|
||||
} catch (Exception e) {
|
||||
Logger.warn("Failed to reinterpret box: " + next.getFourcc() + " as: " + class1.getName() + "." +
|
||||
e.getMessage());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return result.<T>toArray((T[])Array.newInstance(class1, 0));
|
||||
}
|
||||
|
||||
public static void findBox(Box root, List<String> path, Collection<Box> result) {
|
||||
if (path.size() > 0) {
|
||||
String head = (String)path.remove(0);
|
||||
if (root instanceof NodeBox) {
|
||||
NodeBox nb = (NodeBox)root;
|
||||
for (Box candidate : nb.getBoxes()) {
|
||||
if (head == null || head.equals(candidate.header.getFourcc()))
|
||||
findBox(candidate, path, result);
|
||||
}
|
||||
}
|
||||
path.add(0, head);
|
||||
} else {
|
||||
result.add(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class PartialSyncSamplesBox extends SyncSamplesBox {
|
||||
public static final String STPS = "stps";
|
||||
|
||||
public PartialSyncSamplesBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.model.Rational;
|
||||
|
||||
public class PixelAspectExt extends Box {
|
||||
private int hSpacing;
|
||||
|
||||
private int vSpacing;
|
||||
|
||||
public PixelAspectExt(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static PixelAspectExt createPixelAspectExt(Rational par) {
|
||||
PixelAspectExt pasp = new PixelAspectExt(new Header(fourcc()));
|
||||
pasp.hSpacing = par.getNum();
|
||||
pasp.vSpacing = par.getDen();
|
||||
return pasp;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.hSpacing = input.getInt();
|
||||
this.vSpacing = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(this.hSpacing);
|
||||
out.putInt(this.vSpacing);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public int gethSpacing() {
|
||||
return this.hSpacing;
|
||||
}
|
||||
|
||||
public int getvSpacing() {
|
||||
return this.vSpacing;
|
||||
}
|
||||
|
||||
public Rational getRational() {
|
||||
return new Rational(this.hSpacing, this.vSpacing);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "pasp";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class ProductionApertureBox extends ClearApertureBox {
|
||||
public static final String PROF = "prof";
|
||||
|
||||
public static ProductionApertureBox createProductionApertureBox(int width, int height) {
|
||||
ProductionApertureBox prof = new ProductionApertureBox(new Header("prof"));
|
||||
prof.width = (float)width;
|
||||
prof.height = (float)height;
|
||||
return prof;
|
||||
}
|
||||
|
||||
public ProductionApertureBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SampleDescriptionBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "stsd";
|
||||
}
|
||||
|
||||
public static SampleDescriptionBox createSampleDescriptionBox(SampleEntry[] entries) {
|
||||
SampleDescriptionBox box = new SampleDescriptionBox(new Header(fourcc()));
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
SampleEntry e = entries[i];
|
||||
box.boxes.add(e);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
public SampleDescriptionBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
input.getInt();
|
||||
input.getInt();
|
||||
super.parse(input);
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.putInt(0);
|
||||
out.putInt(Math.max(1, this.boxes.size()));
|
||||
super.doWrite(out);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 8 + super.estimateSize();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SampleEntry extends NodeBox {
|
||||
protected short drefInd;
|
||||
|
||||
public SampleEntry(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
input.getInt();
|
||||
input.getShort();
|
||||
this.drefInd = input.getShort();
|
||||
}
|
||||
|
||||
protected void parseExtensions(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.put(new byte[] { (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0 });
|
||||
out.putShort(this.drefInd);
|
||||
}
|
||||
|
||||
protected void writeExtensions(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
}
|
||||
|
||||
public short getDrefInd() {
|
||||
return this.drefInd;
|
||||
}
|
||||
|
||||
public void setDrefInd(short ind) {
|
||||
this.drefInd = ind;
|
||||
}
|
||||
|
||||
public void setMediaType(String mediaType) {
|
||||
this.header = new Header(mediaType);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 8 + super.estimateSize();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SampleSizesBox extends FullBox {
|
||||
private int defaultSize;
|
||||
|
||||
private int count;
|
||||
|
||||
private int[] sizes;
|
||||
|
||||
public SampleSizesBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "stsz";
|
||||
}
|
||||
|
||||
public static SampleSizesBox createSampleSizesBox(int defaultSize, int count) {
|
||||
SampleSizesBox stsz = new SampleSizesBox(new Header(fourcc()));
|
||||
stsz.defaultSize = defaultSize;
|
||||
stsz.count = count;
|
||||
return stsz;
|
||||
}
|
||||
|
||||
public static SampleSizesBox createSampleSizesBox2(int[] sizes) {
|
||||
SampleSizesBox stsz = new SampleSizesBox(new Header(fourcc()));
|
||||
stsz.sizes = sizes;
|
||||
stsz.count = sizes.length;
|
||||
return stsz;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.defaultSize = input.getInt();
|
||||
this.count = input.getInt();
|
||||
if (this.defaultSize == 0) {
|
||||
this.sizes = new int[this.count];
|
||||
for (int i = 0; i < this.count; i++)
|
||||
this.sizes[i] = input.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
public int getDefaultSize() {
|
||||
return this.defaultSize;
|
||||
}
|
||||
|
||||
public int[] getSizes() {
|
||||
return this.sizes;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.defaultSize);
|
||||
if (this.defaultSize == 0) {
|
||||
out.putInt(this.count);
|
||||
for (int i = 0; i < this.sizes.length; i++) {
|
||||
long size = (long)this.sizes[i];
|
||||
out.putInt((int)size);
|
||||
}
|
||||
} else {
|
||||
out.putInt(this.count);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return ((this.defaultSize == 0) ? (this.sizes.length * 4) : 0) + 20;
|
||||
}
|
||||
|
||||
public void setSizes(int[] sizes) {
|
||||
this.sizes = sizes;
|
||||
this.count = sizes.length;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SampleToChunkBox extends FullBox {
|
||||
private SampleToChunkEntry[] sampleToChunk;
|
||||
|
||||
public SampleToChunkBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static class SampleToChunkEntry {
|
||||
private long first;
|
||||
|
||||
private int count;
|
||||
|
||||
private int entry;
|
||||
|
||||
public SampleToChunkEntry(long first, int count, int entry) {
|
||||
this.first = first;
|
||||
this.count = count;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public long getFirst() {
|
||||
return this.first;
|
||||
}
|
||||
|
||||
public void setFirst(long first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public int getEntry() {
|
||||
return this.entry;
|
||||
}
|
||||
|
||||
public void setEntry(int entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "stsc";
|
||||
}
|
||||
|
||||
public static SampleToChunkBox createSampleToChunkBox(SampleToChunkEntry[] sampleToChunk) {
|
||||
SampleToChunkBox box = new SampleToChunkBox(new Header(fourcc()));
|
||||
box.sampleToChunk = sampleToChunk;
|
||||
return box;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int size = input.getInt();
|
||||
this.sampleToChunk = new SampleToChunkEntry[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
this.sampleToChunk[i] = new SampleToChunkEntry((long)input.getInt(), input.getInt(),
|
||||
input.getInt());
|
||||
}
|
||||
|
||||
public SampleToChunkEntry[] getSampleToChunk() {
|
||||
return this.sampleToChunk;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.sampleToChunk.length);
|
||||
for (int i = 0; i < this.sampleToChunk.length; i++) {
|
||||
SampleToChunkEntry stc = this.sampleToChunk[i];
|
||||
out.putInt((int)stc.getFirst());
|
||||
out.putInt(stc.getCount());
|
||||
out.putInt(stc.getEntry());
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.sampleToChunk.length * 12;
|
||||
}
|
||||
|
||||
public void setSampleToChunk(SampleToChunkEntry[] sampleToChunk) {
|
||||
this.sampleToChunk = sampleToChunk;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class SegmentIndexBox extends FullBox {
|
||||
public long reference_ID;
|
||||
|
||||
public long timescale;
|
||||
|
||||
public long earliest_presentation_time;
|
||||
|
||||
public long first_offset;
|
||||
|
||||
public int reserved;
|
||||
|
||||
public int reference_count;
|
||||
|
||||
public Reference[] references;
|
||||
|
||||
public SegmentIndexBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static SegmentIndexBox createSegmentIndexBox() {
|
||||
return new SegmentIndexBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public static class Reference {
|
||||
public boolean reference_type;
|
||||
|
||||
public long referenced_size;
|
||||
|
||||
public long subsegment_duration;
|
||||
|
||||
public boolean starts_with_SAP;
|
||||
|
||||
public int SAP_type;
|
||||
|
||||
public long SAP_delta_time;
|
||||
|
||||
public String toString() {
|
||||
return "Reference [reference_type=" + this.reference_type + ", referenced_size=" + this.referenced_size + ", subsegment_duration=" + this.subsegment_duration + ", starts_with_SAP=" + this.starts_with_SAP + ", SAP_type=" + this.SAP_type + ", SAP_delta_time=" + this.SAP_delta_time + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "sidx";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.reference_ID = Platform.unsignedInt(input.getInt());
|
||||
this.timescale = Platform.unsignedInt(input.getInt());
|
||||
if (this.version == 0) {
|
||||
this.earliest_presentation_time = Platform.unsignedInt(input.getInt());
|
||||
this.first_offset = Platform.unsignedInt(input.getInt());
|
||||
} else {
|
||||
this.earliest_presentation_time = input.getLong();
|
||||
this.first_offset = input.getLong();
|
||||
}
|
||||
this.reserved = input.getShort();
|
||||
this.reference_count = input.getShort() & 0xFFFF;
|
||||
this.references = new Reference[this.reference_count];
|
||||
for (int i = 0; i < this.reference_count; i++) {
|
||||
long i0 = Platform.unsignedInt(input.getInt());
|
||||
long i1 = Platform.unsignedInt(input.getInt());
|
||||
long i2 = Platform.unsignedInt(input.getInt());
|
||||
Reference ref = new Reference();
|
||||
ref.reference_type = ((i0 >>> 31L & 0x1L) == 1L);
|
||||
ref.referenced_size = i0 & Integer.MAX_VALUE;
|
||||
ref.subsegment_duration = i1;
|
||||
ref.starts_with_SAP = ((i2 >>> 31L & 0x1L) == 1L);
|
||||
ref.SAP_type = (int)(i2 >>> 28L & 0x7L);
|
||||
ref.SAP_delta_time = i2 & 0xFFFFFFFL;
|
||||
this.references[i] = ref;
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt((int)this.reference_ID);
|
||||
out.putInt((int)this.timescale);
|
||||
if (this.version == 0) {
|
||||
out.putInt((int)this.earliest_presentation_time);
|
||||
out.putInt((int)this.first_offset);
|
||||
} else {
|
||||
out.putLong(this.earliest_presentation_time);
|
||||
out.putLong(this.first_offset);
|
||||
}
|
||||
out.putShort((short)this.reserved);
|
||||
out.putShort((short)this.reference_count);
|
||||
for (int i = 0; i < this.reference_count; i++) {
|
||||
Reference ref = this.references[i];
|
||||
int i0 = (int)((long)((ref.reference_type ? 1 : 0) << 31) | ref.referenced_size);
|
||||
int i1 = (int)ref.subsegment_duration;
|
||||
int i2 = 0;
|
||||
if (ref.starts_with_SAP)
|
||||
i2 |= Integer.MIN_VALUE;
|
||||
i2 |= (ref.SAP_type & 0x7) << 28;
|
||||
i2 = (int)((long)i2 | ref.SAP_delta_time & 0xFFFFFFFL);
|
||||
out.putInt(i0);
|
||||
out.putInt(i1);
|
||||
out.putInt(i2);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 40 + this.reference_count * 12;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "SegmentIndexBox [reference_ID=" + this.reference_ID + ", timescale=" + this.timescale + ", earliest_presentation_time=" + this.earliest_presentation_time + ", first_offset=" + this.first_offset + ", reserved=" + this.reserved + ", reference_count=" + this.reference_count + ", references=" +
|
||||
|
||||
|
||||
Platform.arrayToString(this.references) + ", version=" + this.version + ", flags=" + this.flags + ", header=" + String.valueOf(this.header) + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class SegmentTypeBox extends Box {
|
||||
private String majorBrand;
|
||||
|
||||
private int minorVersion;
|
||||
|
||||
private Collection<String> compBrands;
|
||||
|
||||
public SegmentTypeBox(Header header) {
|
||||
super(header);
|
||||
this.compBrands = new LinkedList<>();
|
||||
}
|
||||
|
||||
public static SegmentTypeBox createSegmentTypeBox(String majorBrand, int minorVersion, Collection<String> compBrands) {
|
||||
SegmentTypeBox styp = new SegmentTypeBox(new Header(fourcc()));
|
||||
styp.majorBrand = majorBrand;
|
||||
styp.minorVersion = minorVersion;
|
||||
styp.compBrands = compBrands;
|
||||
return styp;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "styp";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
this.majorBrand = NIOUtils.readString(input, 4);
|
||||
this.minorVersion = input.getInt();
|
||||
String brand;
|
||||
while (input.hasRemaining() && (brand = NIOUtils.readString(input, 4)) != null)
|
||||
this.compBrands.add(brand);
|
||||
}
|
||||
|
||||
public String getMajorBrand() {
|
||||
return this.majorBrand;
|
||||
}
|
||||
|
||||
public Collection<String> getCompBrands() {
|
||||
return this.compBrands;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
out.put(JCodecUtil2.asciiString(this.majorBrand));
|
||||
out.putInt(this.minorVersion);
|
||||
for (String string : this.compBrands)
|
||||
out.put(JCodecUtil2.asciiString(string));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int sz = 13;
|
||||
for (String string : this.compBrands)
|
||||
sz += (JCodecUtil2.asciiString(string)).length;
|
||||
return sz;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import org.jcodec.containers.mp4.Boxes;
|
||||
import org.jcodec.containers.mp4.IBoxFactory;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class SimpleBoxFactory implements IBoxFactory {
|
||||
private Boxes boxes;
|
||||
|
||||
public SimpleBoxFactory(Boxes boxes) {
|
||||
this.boxes = boxes;
|
||||
}
|
||||
|
||||
public Box newBox(Header header) {
|
||||
Class<? extends Box> claz = this.boxes.toClass(header.getFourcc());
|
||||
if (claz == null)
|
||||
return new Box.LeafBox(header);
|
||||
Box box = Platform.newInstance(claz, new Object[] { header });
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SoundMediaHeaderBox extends FullBox {
|
||||
private short balance;
|
||||
|
||||
public static String fourcc() {
|
||||
return "smhd";
|
||||
}
|
||||
|
||||
public static SoundMediaHeaderBox createSoundMediaHeaderBox() {
|
||||
return new SoundMediaHeaderBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public SoundMediaHeaderBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.balance = input.getShort();
|
||||
input.getShort();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort(this.balance);
|
||||
out.putShort((short)0);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public short getBalance() {
|
||||
return this.balance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SyncSamplesBox extends FullBox {
|
||||
public static final String STSS = "stss";
|
||||
|
||||
protected int[] syncSamples;
|
||||
|
||||
public static SyncSamplesBox createSyncSamplesBox(int[] array) {
|
||||
SyncSamplesBox stss = new SyncSamplesBox(new Header("stss"));
|
||||
stss.syncSamples = array;
|
||||
return stss;
|
||||
}
|
||||
|
||||
public SyncSamplesBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int len = input.getInt();
|
||||
this.syncSamples = new int[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
this.syncSamples[i] = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.syncSamples.length);
|
||||
for (int i = 0; i < this.syncSamples.length; i++)
|
||||
out.putInt(this.syncSamples[i]);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.syncSamples.length * 4;
|
||||
}
|
||||
|
||||
public int[] getSyncSamples() {
|
||||
return this.syncSamples;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TimeToSampleBox extends FullBox {
|
||||
private TimeToSampleEntry[] entries;
|
||||
|
||||
public TimeToSampleBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static class TimeToSampleEntry {
|
||||
int sampleCount;
|
||||
|
||||
int sampleDuration;
|
||||
|
||||
public TimeToSampleEntry(int sampleCount, int sampleDuration) {
|
||||
this.sampleCount = sampleCount;
|
||||
this.sampleDuration = sampleDuration;
|
||||
}
|
||||
|
||||
public int getSampleCount() {
|
||||
return this.sampleCount;
|
||||
}
|
||||
|
||||
public int getSampleDuration() {
|
||||
return this.sampleDuration;
|
||||
}
|
||||
|
||||
public void setSampleDuration(int sampleDuration) {
|
||||
this.sampleDuration = sampleDuration;
|
||||
}
|
||||
|
||||
public void setSampleCount(int sampleCount) {
|
||||
this.sampleCount = sampleCount;
|
||||
}
|
||||
|
||||
public long getSegmentDuration() {
|
||||
return (long)(this.sampleCount * this.sampleDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "stts";
|
||||
}
|
||||
|
||||
public static TimeToSampleBox createTimeToSampleBox(TimeToSampleEntry[] timeToSamples) {
|
||||
TimeToSampleBox box = new TimeToSampleBox(new Header(fourcc()));
|
||||
box.entries = timeToSamples;
|
||||
return box;
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
int foo = input.getInt();
|
||||
this.entries = new TimeToSampleEntry[foo];
|
||||
for (int i = 0; i < foo; i++)
|
||||
this.entries[i] = new TimeToSampleEntry(input.getInt(), input.getInt());
|
||||
}
|
||||
|
||||
public TimeToSampleEntry[] getEntries() {
|
||||
return this.entries;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.entries.length);
|
||||
for (int i = 0; i < this.entries.length; i++) {
|
||||
TimeToSampleEntry timeToSampleEntry = this.entries[i];
|
||||
out.putInt(timeToSampleEntry.getSampleCount());
|
||||
out.putInt(timeToSampleEntry.getSampleDuration());
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 16 + this.entries.length * 8;
|
||||
}
|
||||
|
||||
public void setEntries(TimeToSampleEntry[] entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class TimecodeMediaInfoBox extends FullBox {
|
||||
private short font;
|
||||
|
||||
private short face;
|
||||
|
||||
private short size;
|
||||
|
||||
private short[] color;
|
||||
|
||||
private short[] bgcolor;
|
||||
|
||||
private String name;
|
||||
|
||||
public static String fourcc() {
|
||||
return "tcmi";
|
||||
}
|
||||
|
||||
public static TimecodeMediaInfoBox createTimecodeMediaInfoBox(short font, short face, short size, short[] color, short[] bgcolor, String name) {
|
||||
TimecodeMediaInfoBox box = new TimecodeMediaInfoBox(new Header(fourcc()));
|
||||
box.font = font;
|
||||
box.face = face;
|
||||
box.size = size;
|
||||
box.color = color;
|
||||
box.bgcolor = bgcolor;
|
||||
box.name = name;
|
||||
return box;
|
||||
}
|
||||
|
||||
public TimecodeMediaInfoBox(Header atom) {
|
||||
super(atom);
|
||||
this.color = new short[3];
|
||||
this.bgcolor = new short[3];
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.font = input.getShort();
|
||||
this.face = input.getShort();
|
||||
this.size = input.getShort();
|
||||
input.getShort();
|
||||
this.color[0] = input.getShort();
|
||||
this.color[1] = input.getShort();
|
||||
this.color[2] = input.getShort();
|
||||
this.bgcolor[0] = input.getShort();
|
||||
this.bgcolor[1] = input.getShort();
|
||||
this.bgcolor[2] = input.getShort();
|
||||
this.name = NIOUtils.readPascalString(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort(this.font);
|
||||
out.putShort(this.face);
|
||||
out.putShort(this.size);
|
||||
out.putShort((short)0);
|
||||
out.putShort(this.color[0]);
|
||||
out.putShort(this.color[1]);
|
||||
out.putShort(this.color[2]);
|
||||
out.putShort(this.bgcolor[0]);
|
||||
out.putShort(this.bgcolor[1]);
|
||||
out.putShort(this.bgcolor[2]);
|
||||
NIOUtils.writePascalString(out, this.name);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 33 + (NIOUtils.asciiString(this.name)).length;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class TimecodeSampleEntry extends SampleEntry {
|
||||
private static final String TMCD = "tmcd";
|
||||
|
||||
public static final int FLAG_DROPFRAME = 1;
|
||||
|
||||
public static final int FLAG_24HOURMAX = 2;
|
||||
|
||||
public static final int FLAG_NEGATIVETIMEOK = 4;
|
||||
|
||||
public static final int FLAG_COUNTER = 8;
|
||||
|
||||
private int flags;
|
||||
|
||||
private int timescale;
|
||||
|
||||
private int frameDuration;
|
||||
|
||||
private byte numFrames;
|
||||
|
||||
public static TimecodeSampleEntry createTimecodeSampleEntry(int flags, int timescale, int frameDuration, int numFrames) {
|
||||
TimecodeSampleEntry tmcd = new TimecodeSampleEntry(new Header("tmcd"));
|
||||
tmcd.flags = flags;
|
||||
tmcd.timescale = timescale;
|
||||
tmcd.frameDuration = frameDuration;
|
||||
tmcd.numFrames = (byte)numFrames;
|
||||
return tmcd;
|
||||
}
|
||||
|
||||
public TimecodeSampleEntry(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
NIOUtils.skip(input, 4);
|
||||
this.flags = input.getInt();
|
||||
this.timescale = input.getInt();
|
||||
this.frameDuration = input.getInt();
|
||||
this.numFrames = input.get();
|
||||
NIOUtils.skip(input, 1);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(0);
|
||||
out.putInt(this.flags);
|
||||
out.putInt(this.timescale);
|
||||
out.putInt(this.frameDuration);
|
||||
out.put(this.numFrames);
|
||||
out.put((byte)-49);
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return this.timescale;
|
||||
}
|
||||
|
||||
public int getFrameDuration() {
|
||||
return this.frameDuration;
|
||||
}
|
||||
|
||||
public byte getNumFrames() {
|
||||
return this.numFrames;
|
||||
}
|
||||
|
||||
public boolean isDropFrame() {
|
||||
return ((this.flags & 0x1) != 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TrackExtendsBox extends FullBox {
|
||||
private int trackId;
|
||||
|
||||
private int defaultSampleDescriptionIndex;
|
||||
|
||||
private int defaultSampleDuration;
|
||||
|
||||
private int defaultSampleBytes;
|
||||
|
||||
private int defaultSampleFlags;
|
||||
|
||||
public TrackExtendsBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "trex";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.trackId = input.getInt();
|
||||
this.defaultSampleDescriptionIndex = input.getInt();
|
||||
this.defaultSampleDuration = input.getInt();
|
||||
this.defaultSampleBytes = input.getInt();
|
||||
this.defaultSampleFlags = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.trackId);
|
||||
out.putInt(this.defaultSampleDescriptionIndex);
|
||||
out.putInt(this.defaultSampleDuration);
|
||||
out.putInt(this.defaultSampleBytes);
|
||||
out.putInt(this.defaultSampleFlags);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public int getTrackId() {
|
||||
return this.trackId;
|
||||
}
|
||||
|
||||
public void setTrackId(int trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public int getDefaultSampleDescriptionIndex() {
|
||||
return this.defaultSampleDescriptionIndex;
|
||||
}
|
||||
|
||||
public void setDefaultSampleDescriptionIndex(int defaultSampleDescriptionIndex) {
|
||||
this.defaultSampleDescriptionIndex = defaultSampleDescriptionIndex;
|
||||
}
|
||||
|
||||
public int getDefaultSampleDuration() {
|
||||
return this.defaultSampleDuration;
|
||||
}
|
||||
|
||||
public void setDefaultSampleDuration(int defaultSampleDuration) {
|
||||
this.defaultSampleDuration = defaultSampleDuration;
|
||||
}
|
||||
|
||||
public int getDefaultSampleBytes() {
|
||||
return this.defaultSampleBytes;
|
||||
}
|
||||
|
||||
public void setDefaultSampleBytes(int defaultSampleBytes) {
|
||||
this.defaultSampleBytes = defaultSampleBytes;
|
||||
}
|
||||
|
||||
public int getDefaultSampleFlags() {
|
||||
return this.defaultSampleFlags;
|
||||
}
|
||||
|
||||
public void setDefaultSampleFlags(int defaultSampleFlags) {
|
||||
this.defaultSampleFlags = defaultSampleFlags;
|
||||
}
|
||||
|
||||
public static TrackExtendsBox createTrackExtendsBox() {
|
||||
return new TrackExtendsBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TrackFragmentBaseMediaDecodeTimeBox extends FullBox {
|
||||
private long baseMediaDecodeTime;
|
||||
|
||||
public TrackFragmentBaseMediaDecodeTimeBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static TrackFragmentBaseMediaDecodeTimeBox createTrackFragmentBaseMediaDecodeTimeBox(long baseMediaDecodeTime) {
|
||||
TrackFragmentBaseMediaDecodeTimeBox box = new TrackFragmentBaseMediaDecodeTimeBox(new Header(fourcc()));
|
||||
box.baseMediaDecodeTime = baseMediaDecodeTime;
|
||||
if (box.baseMediaDecodeTime > Integer.MAX_VALUE)
|
||||
box.version = 1;
|
||||
return box;
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "tfdt";
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if (this.version == 0) {
|
||||
this.baseMediaDecodeTime = (long)input.getInt();
|
||||
} else if (this.version == 1) {
|
||||
this.baseMediaDecodeTime = input.getLong();
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported tfdt version");
|
||||
}
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
if (this.version == 0) {
|
||||
out.putInt((int)this.baseMediaDecodeTime);
|
||||
} else if (this.version == 1) {
|
||||
out.putLong(this.baseMediaDecodeTime);
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported tfdt version");
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public long getBaseMediaDecodeTime() {
|
||||
return this.baseMediaDecodeTime;
|
||||
}
|
||||
|
||||
public void setBaseMediaDecodeTime(long baseMediaDecodeTime) {
|
||||
this.baseMediaDecodeTime = baseMediaDecodeTime;
|
||||
}
|
||||
|
||||
public static Factory copy(TrackFragmentBaseMediaDecodeTimeBox other) {
|
||||
return new Factory(other);
|
||||
}
|
||||
|
||||
public static class Factory {
|
||||
private TrackFragmentBaseMediaDecodeTimeBox box;
|
||||
|
||||
protected Factory(TrackFragmentBaseMediaDecodeTimeBox other) {
|
||||
this
|
||||
.box = TrackFragmentBaseMediaDecodeTimeBox.createTrackFragmentBaseMediaDecodeTimeBox(other.baseMediaDecodeTime);
|
||||
this.box.version = other.version;
|
||||
this.box.flags = other.flags;
|
||||
}
|
||||
|
||||
public Factory baseMediaDecodeTime(long val) {
|
||||
this.box.baseMediaDecodeTime = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrackFragmentBaseMediaDecodeTimeBox create() {
|
||||
try {
|
||||
return this.box;
|
||||
} finally {
|
||||
this.box = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class TrackFragmentBox extends NodeBox {
|
||||
public TrackFragmentBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "traf";
|
||||
}
|
||||
|
||||
public int getTrackId() {
|
||||
TrackFragmentHeaderBox tfhd = NodeBox.<TrackFragmentHeaderBox>findFirst(this, TrackFragmentHeaderBox.class, TrackFragmentHeaderBox.fourcc());
|
||||
if (tfhd == null)
|
||||
throw new RuntimeException("Corrupt track fragment, no header atom found");
|
||||
return tfhd.getTrackId();
|
||||
}
|
||||
|
||||
public static TrackFragmentBox createTrackFragmentBox() {
|
||||
return new TrackFragmentBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TrackFragmentHeaderBox extends FullBox {
|
||||
public static final int FLAG_BASE_DATA_OFFSET = 1;
|
||||
|
||||
public static final int FLAG_SAMPLE_DESCRIPTION_INDEX = 2;
|
||||
|
||||
public static final int FLAG_DEFAILT_SAMPLE_DURATION = 8;
|
||||
|
||||
public static final int FLAG_DEFAULT_SAMPLE_SIZE = 16;
|
||||
|
||||
public static final int FLAG_DEFAILT_SAMPLE_FLAGS = 32;
|
||||
|
||||
private int trackId;
|
||||
|
||||
private long baseDataOffset;
|
||||
|
||||
private int sampleDescriptionIndex;
|
||||
|
||||
private int defaultSampleDuration;
|
||||
|
||||
private int defaultSampleSize;
|
||||
|
||||
private int defaultSampleFlags;
|
||||
|
||||
public TrackFragmentHeaderBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "tfhd";
|
||||
}
|
||||
|
||||
public static TrackFragmentHeaderBox tfhd(int trackId, long baseDataOffset, int sampleDescriptionIndex, int defaultSampleDuration, int defaultSampleSize, int defaultSampleFlags) {
|
||||
TrackFragmentHeaderBox box = new TrackFragmentHeaderBox(new Header(fourcc()));
|
||||
box.trackId = trackId;
|
||||
box.baseDataOffset = baseDataOffset;
|
||||
box.sampleDescriptionIndex = sampleDescriptionIndex;
|
||||
box.defaultSampleDuration = defaultSampleDuration;
|
||||
box.defaultSampleSize = defaultSampleSize;
|
||||
box.defaultSampleFlags = defaultSampleFlags;
|
||||
return box;
|
||||
}
|
||||
|
||||
public static Factory create(int trackId) {
|
||||
return new Factory(createTrackFragmentHeaderBoxWithId(trackId));
|
||||
}
|
||||
|
||||
public static Factory copy(TrackFragmentHeaderBox other) {
|
||||
TrackFragmentHeaderBox box = tfhd(other.trackId, other.baseDataOffset, other.sampleDescriptionIndex, other.defaultSampleDuration, other.defaultSampleSize, other.defaultSampleFlags);
|
||||
box.setFlags(other.getFlags());
|
||||
box.setVersion(other.getVersion());
|
||||
return new Factory(box);
|
||||
}
|
||||
|
||||
public static TrackFragmentHeaderBox createTrackFragmentHeaderBoxWithId(int trackId) {
|
||||
TrackFragmentHeaderBox box = new TrackFragmentHeaderBox(new Header(fourcc()));
|
||||
box.trackId = trackId;
|
||||
return box;
|
||||
}
|
||||
|
||||
public static class Factory {
|
||||
private TrackFragmentHeaderBox box;
|
||||
|
||||
public Factory(TrackFragmentHeaderBox box) {
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
public Factory baseDataOffset(long baseDataOffset) {
|
||||
this.box.flags |= 0x1;
|
||||
this.box.baseDataOffset = (long)(int)baseDataOffset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory sampleDescriptionIndex(long sampleDescriptionIndex) {
|
||||
this.box.flags |= 0x2;
|
||||
this.box.sampleDescriptionIndex = (int)sampleDescriptionIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory defaultSampleDuration(long defaultSampleDuration) {
|
||||
this.box.flags |= 0x8;
|
||||
this.box.defaultSampleDuration = (int)defaultSampleDuration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory defaultSampleSize(long defaultSampleSize) {
|
||||
this.box.flags |= 0x10;
|
||||
this.box.defaultSampleSize = (int)defaultSampleSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory defaultSampleFlags(long defaultSampleFlags) {
|
||||
this.box.flags |= 0x20;
|
||||
this.box.defaultSampleFlags = (int)defaultSampleFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrackFragmentHeaderBox create() {
|
||||
try {
|
||||
return this.box;
|
||||
} finally {
|
||||
this.box = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.trackId = input.getInt();
|
||||
if (isBaseDataOffsetAvailable())
|
||||
this.baseDataOffset = input.getLong();
|
||||
if (isSampleDescriptionIndexAvailable())
|
||||
this.sampleDescriptionIndex = input.getInt();
|
||||
if (isDefaultSampleDurationAvailable())
|
||||
this.defaultSampleDuration = input.getInt();
|
||||
if (isDefaultSampleSizeAvailable())
|
||||
this.defaultSampleSize = input.getInt();
|
||||
if (isDefaultSampleFlagsAvailable())
|
||||
this.defaultSampleFlags = input.getInt();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.trackId);
|
||||
if (isBaseDataOffsetAvailable())
|
||||
out.putLong(this.baseDataOffset);
|
||||
if (isSampleDescriptionIndexAvailable())
|
||||
out.putInt(this.sampleDescriptionIndex);
|
||||
if (isDefaultSampleDurationAvailable())
|
||||
out.putInt(this.defaultSampleDuration);
|
||||
if (isDefaultSampleSizeAvailable())
|
||||
out.putInt(this.defaultSampleSize);
|
||||
if (isDefaultSampleFlagsAvailable())
|
||||
out.putInt(this.defaultSampleFlags);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
public int getTrackId() {
|
||||
return this.trackId;
|
||||
}
|
||||
|
||||
public long getBaseDataOffset() {
|
||||
return this.baseDataOffset;
|
||||
}
|
||||
|
||||
public int getSampleDescriptionIndex() {
|
||||
return this.sampleDescriptionIndex;
|
||||
}
|
||||
|
||||
public int getDefaultSampleDuration() {
|
||||
return this.defaultSampleDuration;
|
||||
}
|
||||
|
||||
public int getDefaultSampleSize() {
|
||||
return this.defaultSampleSize;
|
||||
}
|
||||
|
||||
public int getDefaultSampleFlags() {
|
||||
return this.defaultSampleFlags;
|
||||
}
|
||||
|
||||
public boolean isBaseDataOffsetAvailable() {
|
||||
return ((this.flags & 0x1) != 0);
|
||||
}
|
||||
|
||||
public boolean isSampleDescriptionIndexAvailable() {
|
||||
return ((this.flags & 0x2) != 0);
|
||||
}
|
||||
|
||||
public boolean isDefaultSampleDurationAvailable() {
|
||||
return ((this.flags & 0x8) != 0);
|
||||
}
|
||||
|
||||
public boolean isDefaultSampleSizeAvailable() {
|
||||
return ((this.flags & 0x10) != 0);
|
||||
}
|
||||
|
||||
public boolean isDefaultSampleFlagsAvailable() {
|
||||
return ((this.flags & 0x20) != 0);
|
||||
}
|
||||
|
||||
public void setTrackId(int trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public void setDefaultSampleFlags(int defaultSampleFlags) {
|
||||
this.defaultSampleFlags = defaultSampleFlags;
|
||||
}
|
||||
|
||||
public static TrackFragmentHeaderBox createTrackFragmentHeaderBox() {
|
||||
return new TrackFragmentHeaderBox(new Header(fourcc()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.containers.mp4.TimeUtil;
|
||||
|
||||
public class TrackHeaderBox extends FullBox {
|
||||
private int trackId;
|
||||
|
||||
private long duration;
|
||||
|
||||
private float width;
|
||||
|
||||
private float height;
|
||||
|
||||
private long created;
|
||||
|
||||
private long modified;
|
||||
|
||||
private float volume;
|
||||
|
||||
private short layer;
|
||||
|
||||
private long altGroup;
|
||||
|
||||
private int[] matrix;
|
||||
|
||||
public static String fourcc() {
|
||||
return "tkhd";
|
||||
}
|
||||
|
||||
public static TrackHeaderBox createTrackHeaderBox(int trackId, long duration, float width, float height, long created, long modified, float volume, short layer, long altGroup, int[] matrix) {
|
||||
TrackHeaderBox box = new TrackHeaderBox(new Header(fourcc()));
|
||||
box.trackId = trackId;
|
||||
box.duration = duration;
|
||||
box.width = width;
|
||||
box.height = height;
|
||||
box.created = created;
|
||||
box.modified = modified;
|
||||
box.volume = volume;
|
||||
box.layer = layer;
|
||||
box.altGroup = altGroup;
|
||||
box.matrix = matrix;
|
||||
return box;
|
||||
}
|
||||
|
||||
public TrackHeaderBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if (this.version == 0) {
|
||||
this.created = TimeUtil.fromMovTime(input.getInt());
|
||||
this.modified = TimeUtil.fromMovTime(input.getInt());
|
||||
} else {
|
||||
this.created = TimeUtil.fromMovTime((int)input.getLong());
|
||||
this.modified = TimeUtil.fromMovTime((int)input.getLong());
|
||||
}
|
||||
this.trackId = input.getInt();
|
||||
input.getInt();
|
||||
if (this.version == 0) {
|
||||
this.duration = (long)input.getInt();
|
||||
} else {
|
||||
this.duration = input.getLong();
|
||||
}
|
||||
input.getInt();
|
||||
input.getInt();
|
||||
this.layer = input.getShort();
|
||||
this.altGroup = (long)input.getShort();
|
||||
this.volume = readVolume(input);
|
||||
input.getShort();
|
||||
readMatrix(input);
|
||||
this.width = (float)input.getInt() / 65536.0F;
|
||||
this.height = (float)input.getInt() / 65536.0F;
|
||||
}
|
||||
|
||||
private void readMatrix(ByteBuffer input) {
|
||||
this.matrix = new int[9];
|
||||
for (int i = 0; i < 9; i++)
|
||||
this.matrix[i] = input.getInt();
|
||||
}
|
||||
|
||||
private float readVolume(ByteBuffer input) {
|
||||
return (float)((double)input.getShort() / 256.0D);
|
||||
}
|
||||
|
||||
public int getNo() {
|
||||
return this.trackId;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(TimeUtil.toMovTime(this.created));
|
||||
out.putInt(TimeUtil.toMovTime(this.modified));
|
||||
out.putInt(this.trackId);
|
||||
out.putInt(0);
|
||||
out.putInt((int)this.duration);
|
||||
out.putInt(0);
|
||||
out.putInt(0);
|
||||
out.putShort(this.layer);
|
||||
out.putShort((short)(int)this.altGroup);
|
||||
writeVolume(out);
|
||||
out.putShort((short)0);
|
||||
writeMatrix(out);
|
||||
out.putInt((int)(this.width * 65536.0F));
|
||||
out.putInt((int)(this.height * 65536.0F));
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 92;
|
||||
}
|
||||
|
||||
private void writeMatrix(ByteBuffer out) {
|
||||
for (int j = 0; j < Math.min(9, this.matrix.length); j++)
|
||||
out.putInt(this.matrix[j]);
|
||||
for (int i = Math.min(9, this.matrix.length); i < 9; i++)
|
||||
out.putInt(0);
|
||||
}
|
||||
|
||||
private void writeVolume(ByteBuffer out) {
|
||||
out.putShort((short)(int)((double)this.volume * 256.0D));
|
||||
}
|
||||
|
||||
public int getTrackId() {
|
||||
return this.trackId;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return this.created;
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return this.modified;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
public short getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
public long getAltGroup() {
|
||||
return this.altGroup;
|
||||
}
|
||||
|
||||
public int[] getMatrix() {
|
||||
return this.matrix;
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public void setHeight(float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public void setNo(int no) {
|
||||
this.trackId = no;
|
||||
}
|
||||
|
||||
public boolean isOrientation0() {
|
||||
return (this.matrix != null && this.matrix[0] == 65536 && this.matrix[4] == 65536);
|
||||
}
|
||||
|
||||
public boolean isOrientation90() {
|
||||
return (this.matrix != null && this.matrix[1] == 65536 && this.matrix[3] == -65536);
|
||||
}
|
||||
|
||||
public boolean isOrientation180() {
|
||||
return (this.matrix != null && this.matrix[0] == -65536 && this.matrix[4] == -65536);
|
||||
}
|
||||
|
||||
public boolean isOrientation270() {
|
||||
return (this.matrix != null && this.matrix[1] == -65536 && this.matrix[3] == 65536);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import org.jcodec.common.model.Rational;
|
||||
import org.jcodec.common.model.Size;
|
||||
import org.jcodec.containers.mp4.MP4TrackType;
|
||||
|
||||
public class TrakBox extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "trak";
|
||||
}
|
||||
|
||||
public static TrakBox createTrakBox() {
|
||||
return new TrakBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public TrakBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void setDataRef(String url) {
|
||||
MediaInfoBox minf = getMdia().getMinf();
|
||||
DataInfoBox dinf = minf.getDinf();
|
||||
if (dinf == null) {
|
||||
dinf = DataInfoBox.createDataInfoBox();
|
||||
minf.add(dinf);
|
||||
}
|
||||
DataRefBox dref = dinf.getDref();
|
||||
UrlBox urlBox = UrlBox.createUrlBox(url);
|
||||
if (dref == null) {
|
||||
dref = DataRefBox.createDataRefBox();
|
||||
dinf.add(dref);
|
||||
dref.add(urlBox);
|
||||
} else {
|
||||
ListIterator<Box> lit = dref.boxes.listIterator();
|
||||
while (lit.hasNext()) {
|
||||
FullBox box = (FullBox)lit.next();
|
||||
if ((box.getFlags() & 0x1) != 0)
|
||||
lit.set(urlBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MediaBox getMdia() {
|
||||
return NodeBox.<MediaBox>findFirst(this, MediaBox.class, "mdia");
|
||||
}
|
||||
|
||||
public TrackHeaderBox getTrackHeader() {
|
||||
return NodeBox.<TrackHeaderBox>findFirst(this, TrackHeaderBox.class, "tkhd");
|
||||
}
|
||||
|
||||
public List<Edit> getEdits() {
|
||||
EditListBox elst = NodeBox.<EditListBox>findFirstPath(this, EditListBox.class, Box.path("edts.elst"));
|
||||
if (elst == null)
|
||||
return null;
|
||||
return elst.getEdits();
|
||||
}
|
||||
|
||||
public void setEdits(List<Edit> edits) {
|
||||
NodeBox edts = NodeBox.<NodeBox>findFirst(this, NodeBox.class, "edts");
|
||||
if (edts == null) {
|
||||
edts = new NodeBox(new Header("edts"));
|
||||
add(edts);
|
||||
}
|
||||
edts.removeChildren(new String[] { "elst" });
|
||||
edts.add(EditListBox.createEditListBox(edits));
|
||||
getTrackHeader().setDuration(getEditedDuration(this));
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return "vide".equals(getHandlerType());
|
||||
}
|
||||
|
||||
public boolean isTimecode() {
|
||||
return "tmcd".equals(getHandlerType());
|
||||
}
|
||||
|
||||
public String getHandlerType() {
|
||||
HandlerBox handlerBox = NodeBox.<HandlerBox>findFirstPath(this, HandlerBox.class, Box.path("mdia.hdlr"));
|
||||
if (handlerBox == null)
|
||||
return null;
|
||||
String type = handlerBox.getComponentSubType();
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isAudio() {
|
||||
return "soun".equals(getHandlerType());
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return NodeBox.<MediaHeaderBox>findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getTimescale();
|
||||
}
|
||||
|
||||
public void setTimescale(int timescale) {
|
||||
NodeBox.<MediaHeaderBox>findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).setTimescale(timescale);
|
||||
}
|
||||
|
||||
public long rescale(long tv, long ts) {
|
||||
return tv * (long)getTimescale() / ts;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
getTrackHeader().setDuration(duration);
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return getTrackHeader().getDuration();
|
||||
}
|
||||
|
||||
public long getMediaDuration() {
|
||||
return NodeBox.<MediaHeaderBox>findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getDuration();
|
||||
}
|
||||
|
||||
public boolean isPureRef() {
|
||||
MediaInfoBox minf = getMdia().getMinf();
|
||||
DataInfoBox dinf = minf.getDinf();
|
||||
if (dinf == null)
|
||||
return false;
|
||||
DataRefBox dref = dinf.getDref();
|
||||
if (dref == null)
|
||||
return false;
|
||||
for (Box box : dref.boxes) {
|
||||
if ((((FullBox)box).getFlags() & 0x1) != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasDataRef() {
|
||||
DataInfoBox dinf = getMdia().getMinf().getDinf();
|
||||
if (dinf == null)
|
||||
return false;
|
||||
DataRefBox dref = dinf.getDref();
|
||||
if (dref == null)
|
||||
return false;
|
||||
boolean result = false;
|
||||
for (Box box : dref.boxes)
|
||||
result |= ((((FullBox)box).getFlags() & 0x1) != 1) ? true : false;
|
||||
return result;
|
||||
}
|
||||
|
||||
public Rational getPAR() {
|
||||
PixelAspectExt pasp = NodeBox.<PixelAspectExt>findFirstPath(this, PixelAspectExt.class, new String[] { "mdia", "minf", "stbl", "stsd", null, "pasp" });
|
||||
return (pasp == null) ? new Rational(1, 1) : pasp.getRational();
|
||||
}
|
||||
|
||||
public void setPAR(Rational par) {
|
||||
SampleEntry[] sampleEntries = getSampleEntries();
|
||||
for (int i = 0; i < sampleEntries.length; i++) {
|
||||
SampleEntry sampleEntry = sampleEntries[i];
|
||||
sampleEntry.removeChildren(new String[] { "pasp" });
|
||||
sampleEntry.add(PixelAspectExt.createPixelAspectExt(par));
|
||||
}
|
||||
}
|
||||
|
||||
public SampleEntry[] getSampleEntries() {
|
||||
return NodeBox.<SampleEntry>findAllPath(this, SampleEntry.class, new String[] { "mdia", "minf", "stbl", "stsd", null });
|
||||
}
|
||||
|
||||
public void setClipRect(short x, short y, short width, short height) {
|
||||
NodeBox clip = NodeBox.<NodeBox>findFirst(this, NodeBox.class, "clip");
|
||||
if (clip == null) {
|
||||
clip = new NodeBox(new Header("clip"));
|
||||
add(clip);
|
||||
}
|
||||
clip.replace("crgn", ClipRegionBox.createClipRegionBox(x, y, width, height));
|
||||
}
|
||||
|
||||
public long getSampleCount() {
|
||||
return (long)NodeBox.<SampleSizesBox>findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")).getCount();
|
||||
}
|
||||
|
||||
public void setAperture(Size sar, Size dar) {
|
||||
removeChildren(new String[] { "tapt" });
|
||||
NodeBox tapt = new NodeBox(new Header("tapt"));
|
||||
tapt.add(ClearApertureBox.createClearApertureBox(dar.getWidth(), dar.getHeight()));
|
||||
tapt.add(ProductionApertureBox.createProductionApertureBox(dar.getWidth(), dar.getHeight()));
|
||||
tapt.add(EncodedPixelBox.createEncodedPixelBox(sar.getWidth(), sar.getHeight()));
|
||||
add(tapt);
|
||||
}
|
||||
|
||||
public void setDimensions(Size dd) {
|
||||
getTrackHeader().setWidth((float)dd.getWidth());
|
||||
getTrackHeader().setHeight((float)dd.getHeight());
|
||||
}
|
||||
|
||||
public int getFrameCount() {
|
||||
SampleSizesBox stsz = NodeBox.<SampleSizesBox>findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz"));
|
||||
return (stsz.getDefaultSize() != 0) ? stsz.getCount() : (stsz.getSizes()).length;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
NameBox nb = NodeBox.<NameBox>findFirstPath(this, NameBox.class, Box.path("udta.name"));
|
||||
return (nb == null) ? null : nb.getName();
|
||||
}
|
||||
|
||||
public void fixMediaTimescale(int ts) {
|
||||
MediaHeaderBox mdhd = NodeBox.<MediaHeaderBox>findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd"));
|
||||
int oldTs = mdhd.getTimescale();
|
||||
mdhd.setTimescale(ts);
|
||||
mdhd.setDuration((long)ts * mdhd.getDuration() / (long)oldTs);
|
||||
List<Edit> edits = getEdits();
|
||||
if (edits != null)
|
||||
for (Edit edit : edits)
|
||||
edit.setMediaTime((long)ts * edit.getMediaTime() / (long)oldTs);
|
||||
TimeToSampleBox tts = NodeBox.<TimeToSampleBox>findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
|
||||
TimeToSampleBox.TimeToSampleEntry[] entries = tts.getEntries();
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
TimeToSampleBox.TimeToSampleEntry tte = entries[i];
|
||||
tte.setSampleDuration(ts * tte.getSampleDuration() / oldTs);
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(String string) {
|
||||
NodeBox udta = NodeBox.<NodeBox>findFirst(this, NodeBox.class, "udta");
|
||||
if (udta == null) {
|
||||
udta = new NodeBox(new Header("udta"));
|
||||
add(udta);
|
||||
}
|
||||
udta.removeChildren(new String[] { "name" });
|
||||
udta.add(NameBox.createNameBox(string));
|
||||
}
|
||||
|
||||
public Size getCodedSize() {
|
||||
SampleEntry se = getSampleEntries()[0];
|
||||
if (!(se instanceof VideoSampleEntry))
|
||||
throw new IllegalArgumentException("Not a video track");
|
||||
VideoSampleEntry vse = (VideoSampleEntry)se;
|
||||
return new Size(vse.getWidth(), vse.getHeight());
|
||||
}
|
||||
|
||||
public TimeToSampleBox getStts() {
|
||||
return NodeBox.<TimeToSampleBox>findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts"));
|
||||
}
|
||||
|
||||
public ChunkOffsetsBox getStco() {
|
||||
return NodeBox.<ChunkOffsetsBox>findFirstPath(this, ChunkOffsetsBox.class, Box.path("mdia.minf.stbl.stco"));
|
||||
}
|
||||
|
||||
public ChunkOffsets64Box getCo64() {
|
||||
return NodeBox.<ChunkOffsets64Box>findFirstPath(this, ChunkOffsets64Box.class, Box.path("mdia.minf.stbl.co64"));
|
||||
}
|
||||
|
||||
public SampleSizesBox getStsz() {
|
||||
return NodeBox.<SampleSizesBox>findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz"));
|
||||
}
|
||||
|
||||
public SampleToChunkBox getStsc() {
|
||||
return NodeBox.<SampleToChunkBox>findFirstPath(this, SampleToChunkBox.class, Box.path("mdia.minf.stbl.stsc"));
|
||||
}
|
||||
|
||||
public SampleDescriptionBox getStsd() {
|
||||
return NodeBox.<SampleDescriptionBox>findFirstPath(this, SampleDescriptionBox.class, Box.path("mdia.minf.stbl.stsd"));
|
||||
}
|
||||
|
||||
public SyncSamplesBox getStss() {
|
||||
return NodeBox.<SyncSamplesBox>findFirstPath(this, SyncSamplesBox.class, Box.path("mdia.minf.stbl.stss"));
|
||||
}
|
||||
|
||||
public CompositionOffsetsBox getCtts() {
|
||||
return NodeBox.<CompositionOffsetsBox>findFirstPath(this, CompositionOffsetsBox.class, Box.path("mdia.minf.stbl.ctts"));
|
||||
}
|
||||
|
||||
public static MP4TrackType getTrackType(TrakBox trak) {
|
||||
HandlerBox handler = NodeBox.<HandlerBox>findFirstPath(trak, HandlerBox.class, Box.path("mdia.hdlr"));
|
||||
return (handler == null) ? null : MP4TrackType.fromHandler(handler.getComponentSubType());
|
||||
}
|
||||
|
||||
public static long getEditedDuration(TrakBox track) {
|
||||
List<Edit> edits = track.getEdits();
|
||||
if (edits == null)
|
||||
return track.getDuration();
|
||||
long duration = 0L;
|
||||
for (Edit edit : edits)
|
||||
duration += edit.getDuration();
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class TrunBox extends FullBox {
|
||||
private static final int DATA_OFFSET_AVAILABLE = 1;
|
||||
|
||||
private static final int FIRST_SAMPLE_FLAGS_AVAILABLE = 4;
|
||||
|
||||
private static final int SAMPLE_DURATION_AVAILABLE = 256;
|
||||
|
||||
private static final int SAMPLE_SIZE_AVAILABLE = 512;
|
||||
|
||||
private static final int SAMPLE_FLAGS_AVAILABLE = 1024;
|
||||
|
||||
private static final int SAMPLE_COMPOSITION_OFFSET_AVAILABLE = 2048;
|
||||
|
||||
private int sampleCount;
|
||||
|
||||
private int dataOffset;
|
||||
|
||||
private int firstSampleFlags;
|
||||
|
||||
private int[] sampleDuration;
|
||||
|
||||
private int[] sampleSize;
|
||||
|
||||
private int[] sampleFlags;
|
||||
|
||||
private int[] sampleCompositionOffset;
|
||||
|
||||
public static String fourcc() {
|
||||
return "trun";
|
||||
}
|
||||
|
||||
public void setDataOffset(int dataOffset) {
|
||||
this.dataOffset = dataOffset;
|
||||
}
|
||||
|
||||
public static Factory create(int sampleCount) {
|
||||
return new Factory(createTrunBox1(sampleCount));
|
||||
}
|
||||
|
||||
public static Factory copy(TrunBox other) {
|
||||
TrunBox box = createTrunBox2(other.sampleCount, other.dataOffset, other.firstSampleFlags, other.sampleDuration, other.sampleSize, other.sampleFlags, other.sampleCompositionOffset);
|
||||
box.setFlags(other.getFlags());
|
||||
box.setVersion(other.getVersion());
|
||||
return new Factory(box);
|
||||
}
|
||||
|
||||
public TrunBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public static TrunBox createTrunBox1(int sampleCount) {
|
||||
TrunBox trun = new TrunBox(new Header(fourcc()));
|
||||
trun.sampleCount = sampleCount;
|
||||
return trun;
|
||||
}
|
||||
|
||||
public static TrunBox createTrunBox2(int sampleCount, int dataOffset, int firstSampleFlags, int[] sampleDuration, int[] sampleSize, int[] sampleFlags, int[] sampleCompositionOffset) {
|
||||
TrunBox trun = new TrunBox(new Header(fourcc()));
|
||||
trun.sampleCount = sampleCount;
|
||||
trun.dataOffset = dataOffset;
|
||||
trun.firstSampleFlags = firstSampleFlags;
|
||||
trun.sampleDuration = sampleDuration;
|
||||
trun.sampleSize = sampleSize;
|
||||
trun.sampleFlags = sampleFlags;
|
||||
trun.sampleCompositionOffset = sampleCompositionOffset;
|
||||
return trun;
|
||||
}
|
||||
|
||||
public static class Factory {
|
||||
private TrunBox box;
|
||||
|
||||
protected Factory(TrunBox box) {
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
public Factory dataOffset(long dataOffset) {
|
||||
this.box.flags |= 0x1;
|
||||
this.box.dataOffset = (int)dataOffset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory firstSampleFlags(int firstSampleFlags) {
|
||||
if (this.box.isSampleFlagsAvailable())
|
||||
throw new IllegalStateException("Sample flags already set on this object");
|
||||
this.box.flags |= 0x4;
|
||||
this.box.firstSampleFlags = firstSampleFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory sampleDuration(int[] sampleDuration) {
|
||||
if (sampleDuration.length != this.box.sampleCount)
|
||||
throw new IllegalArgumentException("Argument array length not equal to sampleCount");
|
||||
this.box.flags |= 0x100;
|
||||
this.box.sampleDuration = sampleDuration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory sampleSize(int[] sampleSize) {
|
||||
if (sampleSize.length != this.box.sampleCount)
|
||||
throw new IllegalArgumentException("Argument array length not equal to sampleCount");
|
||||
this.box.flags |= 0x200;
|
||||
this.box.sampleSize = sampleSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory sampleFlags(int[] sampleFlags) {
|
||||
if (sampleFlags.length != this.box.sampleCount)
|
||||
throw new IllegalArgumentException("Argument array length not equal to sampleCount");
|
||||
if (this.box.isFirstSampleFlagsAvailable())
|
||||
throw new IllegalStateException("First sample flags already set on this object");
|
||||
this.box.flags |= 0x400;
|
||||
this.box.sampleFlags = sampleFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory sampleCompositionOffset(int[] sampleCompositionOffset) {
|
||||
if (sampleCompositionOffset.length != this.box.sampleCount)
|
||||
throw new IllegalArgumentException("Argument array length not equal to sampleCount");
|
||||
this.box.flags |= 0x800;
|
||||
this.box.sampleCompositionOffset = sampleCompositionOffset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrunBox create() {
|
||||
try {
|
||||
return this.box;
|
||||
} finally {
|
||||
this.box = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getSampleCount() {
|
||||
return Platform.unsignedInt(this.sampleCount);
|
||||
}
|
||||
|
||||
public int getDataOffset() {
|
||||
return this.dataOffset;
|
||||
}
|
||||
|
||||
public int getFirstSampleFlags() {
|
||||
return this.firstSampleFlags;
|
||||
}
|
||||
|
||||
public int[] getSampleDurations() {
|
||||
return this.sampleDuration;
|
||||
}
|
||||
|
||||
public int[] getSampleSizes() {
|
||||
return this.sampleSize;
|
||||
}
|
||||
|
||||
public int[] getSamplesFlags() {
|
||||
return this.sampleFlags;
|
||||
}
|
||||
|
||||
public int[] getSampleCompositionOffsets() {
|
||||
return this.sampleCompositionOffset;
|
||||
}
|
||||
|
||||
public long getSampleDuration(int i) {
|
||||
return Platform.unsignedInt(this.sampleDuration[i]);
|
||||
}
|
||||
|
||||
public long getSampleSize(int i) {
|
||||
return Platform.unsignedInt(this.sampleSize[i]);
|
||||
}
|
||||
|
||||
public int getSampleFlags(int i) {
|
||||
return this.sampleFlags[i];
|
||||
}
|
||||
|
||||
public long getSampleCompositionOffset(int i) {
|
||||
return Platform.unsignedInt(this.sampleCompositionOffset[i]);
|
||||
}
|
||||
|
||||
public boolean isDataOffsetAvailable() {
|
||||
return ((this.flags & 0x1) != 0);
|
||||
}
|
||||
|
||||
public boolean isSampleCompositionOffsetAvailable() {
|
||||
return ((this.flags & 0x800) != 0);
|
||||
}
|
||||
|
||||
public boolean isSampleFlagsAvailable() {
|
||||
return ((this.flags & 0x400) != 0);
|
||||
}
|
||||
|
||||
public boolean isSampleSizeAvailable() {
|
||||
return ((this.flags & 0x200) != 0);
|
||||
}
|
||||
|
||||
public boolean isSampleDurationAvailable() {
|
||||
return ((this.flags & 0x100) != 0);
|
||||
}
|
||||
|
||||
public boolean isFirstSampleFlagsAvailable() {
|
||||
return ((this.flags & 0x4) != 0);
|
||||
}
|
||||
|
||||
public static int flagsGetSampleDependsOn(int flags) {
|
||||
return flags >> 6 & 0x3;
|
||||
}
|
||||
|
||||
public static int flagsGetSampleIsDependedOn(int flags) {
|
||||
return flags >> 8 & 0x3;
|
||||
}
|
||||
|
||||
public static int flagsGetSampleHasRedundancy(int flags) {
|
||||
return flags >> 10 & 0x3;
|
||||
}
|
||||
|
||||
public static int flagsGetSamplePaddingValue(int flags) {
|
||||
return flags >> 12 & 0x7;
|
||||
}
|
||||
|
||||
public static int flagsGetSampleIsDifferentSample(int flags) {
|
||||
return flags >> 15 & 0x1;
|
||||
}
|
||||
|
||||
public static int flagsGetSampleDegradationPriority(int flags) {
|
||||
return flags >> 16 & 0xFFFF;
|
||||
}
|
||||
|
||||
public static TrunBox createTrunBox() {
|
||||
return new TrunBox(new Header(fourcc()));
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if (isSampleFlagsAvailable() && isFirstSampleFlagsAvailable())
|
||||
throw new RuntimeException("Broken stream");
|
||||
this.sampleCount = input.getInt();
|
||||
if (isDataOffsetAvailable())
|
||||
this.dataOffset = input.getInt();
|
||||
if (isFirstSampleFlagsAvailable())
|
||||
this.firstSampleFlags = input.getInt();
|
||||
if (isSampleDurationAvailable())
|
||||
this.sampleDuration = new int[this.sampleCount];
|
||||
if (isSampleSizeAvailable())
|
||||
this.sampleSize = new int[this.sampleCount];
|
||||
if (isSampleFlagsAvailable())
|
||||
this.sampleFlags = new int[this.sampleCount];
|
||||
if (isSampleCompositionOffsetAvailable())
|
||||
this.sampleCompositionOffset = new int[this.sampleCount];
|
||||
for (int i = 0; i < this.sampleCount; i++) {
|
||||
if (isSampleDurationAvailable())
|
||||
this.sampleDuration[i] = input.getInt();
|
||||
if (isSampleSizeAvailable())
|
||||
this.sampleSize[i] = input.getInt();
|
||||
if (isSampleFlagsAvailable())
|
||||
this.sampleFlags[i] = input.getInt();
|
||||
if (isSampleCompositionOffsetAvailable())
|
||||
this.sampleCompositionOffset[i] = input.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putInt(this.sampleCount);
|
||||
if (isDataOffsetAvailable())
|
||||
out.putInt(this.dataOffset);
|
||||
if (isFirstSampleFlagsAvailable())
|
||||
out.putInt(this.firstSampleFlags);
|
||||
for (int i = 0; i < this.sampleCount; i++) {
|
||||
if (isSampleDurationAvailable())
|
||||
out.putInt(this.sampleDuration[i]);
|
||||
if (isSampleSizeAvailable())
|
||||
out.putInt(this.sampleSize[i]);
|
||||
if (isSampleFlagsAvailable())
|
||||
out.putInt(this.sampleFlags[i]);
|
||||
if (isSampleCompositionOffsetAvailable())
|
||||
out.putInt(this.sampleCompositionOffset[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 24 + this.sampleCount * 16;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.jcodec.containers.mp4.IBoxFactory;
|
||||
|
||||
public class UdtaBox extends NodeBox {
|
||||
private static final String FOURCC = "udta";
|
||||
|
||||
public static UdtaBox createUdtaBox() {
|
||||
return new UdtaBox(Header.createHeader(fourcc(), 0L));
|
||||
}
|
||||
|
||||
public void setFactory(final IBoxFactory _factory) {
|
||||
this.factory = new IBoxFactory() {
|
||||
public Box newBox(Header header) {
|
||||
if (header.getFourcc().equals(UdtaMetaBox.fourcc())) {
|
||||
UdtaMetaBox box = new UdtaMetaBox(header);
|
||||
box.setFactory(_factory);
|
||||
return box;
|
||||
}
|
||||
return _factory.newBox(header);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public UdtaBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public MetaBox meta() {
|
||||
return NodeBox.<MetaBox>findFirst(this, MetaBox.class, MetaBox.fourcc());
|
||||
}
|
||||
|
||||
public static String fourcc() {
|
||||
return "udta";
|
||||
}
|
||||
|
||||
public String latlng() {
|
||||
Box gps = findGps(this);
|
||||
if (gps == null)
|
||||
return null;
|
||||
ByteBuffer data = getData(gps);
|
||||
if (data == null)
|
||||
return null;
|
||||
if (data.remaining() < 4)
|
||||
return null;
|
||||
data.getInt();
|
||||
byte[] coordsBytes = new byte[data.remaining()];
|
||||
data.get(coordsBytes);
|
||||
String latlng = new String(coordsBytes);
|
||||
return latlng;
|
||||
}
|
||||
|
||||
static Box findGps(UdtaBox udta) {
|
||||
List<Box> boxes1 = udta.getBoxes();
|
||||
for (Box box : boxes1) {
|
||||
if (box.getFourcc().endsWith("xyz"))
|
||||
return box;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static ByteBuffer getData(Box box) {
|
||||
if (box instanceof Box.LeafBox) {
|
||||
Box.LeafBox leaf = (Box.LeafBox)box;
|
||||
return leaf.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class UdtaMetaBox extends MetaBox {
|
||||
public UdtaMetaBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public static UdtaMetaBox createUdtaMetaBox() {
|
||||
return new UdtaMetaBox(Header.createHeader(fourcc(), 0L));
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
input.getInt();
|
||||
super.parse(input);
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
out.putInt(0);
|
||||
super.doWrite(out);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class UrlBox extends FullBox {
|
||||
private String url;
|
||||
|
||||
public static String fourcc() {
|
||||
return "url ";
|
||||
}
|
||||
|
||||
public static UrlBox createUrlBox(String url) {
|
||||
UrlBox urlBox = new UrlBox(new Header(fourcc()));
|
||||
urlBox.url = url;
|
||||
return urlBox;
|
||||
}
|
||||
|
||||
public UrlBox(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
if ((this.flags & 0x1) != 0)
|
||||
return;
|
||||
this.url = NIOUtils.readNullTermStringCharset(input, "UTF-8");
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
if (this.url != null) {
|
||||
NIOUtils.write(out, ByteBuffer.wrap(Platform.getBytesForCharset(this.url, "UTF-8")));
|
||||
out.put((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int sz = 13;
|
||||
if (this.url != null)
|
||||
sz += (Platform.getBytesForCharset(this.url, "UTF-8")).length;
|
||||
return sz;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class VideoMediaHeaderBox extends FullBox {
|
||||
int graphicsMode;
|
||||
|
||||
int rOpColor;
|
||||
|
||||
int gOpColor;
|
||||
|
||||
int bOpColor;
|
||||
|
||||
public static String fourcc() {
|
||||
return "vmhd";
|
||||
}
|
||||
|
||||
public static VideoMediaHeaderBox createVideoMediaHeaderBox(int graphicsMode, int rOpColor, int gOpColor, int bOpColor) {
|
||||
VideoMediaHeaderBox vmhd = new VideoMediaHeaderBox(new Header(fourcc()));
|
||||
vmhd.graphicsMode = graphicsMode;
|
||||
vmhd.rOpColor = rOpColor;
|
||||
vmhd.gOpColor = gOpColor;
|
||||
vmhd.bOpColor = bOpColor;
|
||||
return vmhd;
|
||||
}
|
||||
|
||||
public VideoMediaHeaderBox(Header header) {
|
||||
super(header);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.graphicsMode = input.getShort();
|
||||
this.rOpColor = input.getShort();
|
||||
this.gOpColor = input.getShort();
|
||||
this.bOpColor = input.getShort();
|
||||
}
|
||||
|
||||
protected void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort((short)this.graphicsMode);
|
||||
out.putShort((short)this.rOpColor);
|
||||
out.putShort((short)this.gOpColor);
|
||||
out.putShort((short)this.bOpColor);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public int getGraphicsMode() {
|
||||
return this.graphicsMode;
|
||||
}
|
||||
|
||||
public int getrOpColor() {
|
||||
return this.rOpColor;
|
||||
}
|
||||
|
||||
public int getgOpColor() {
|
||||
return this.gOpColor;
|
||||
}
|
||||
|
||||
public int getbOpColor() {
|
||||
return this.bOpColor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.JCodecUtil2;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.model.Size;
|
||||
|
||||
public class VideoSampleEntry extends SampleEntry {
|
||||
private short version;
|
||||
|
||||
private short revision;
|
||||
|
||||
private String vendor;
|
||||
|
||||
private int temporalQual;
|
||||
|
||||
private int spacialQual;
|
||||
|
||||
private short width;
|
||||
|
||||
private short height;
|
||||
|
||||
private float hRes;
|
||||
|
||||
private float vRes;
|
||||
|
||||
private short frameCount;
|
||||
|
||||
private String compressorName;
|
||||
|
||||
private short depth;
|
||||
|
||||
private short clrTbl;
|
||||
|
||||
public static VideoSampleEntry videoSampleEntry(String fourcc, Size size, String encoderName) {
|
||||
return createVideoSampleEntry(new Header(fourcc), (short)0, (short)0, "jcod", 0, 768,
|
||||
(short)size.getWidth(), (short)size.getHeight(), 72L, 72L, (short)1,
|
||||
(encoderName != null) ? encoderName : "jcodec", (short)24, (short)1, (short)-1);
|
||||
}
|
||||
|
||||
public static VideoSampleEntry createVideoSampleEntry(Header atom, short version, short revision, String vendor, int temporalQual, int spacialQual, short width, short height, long hRes, long vRes, short frameCount, String compressorName, short depth, short drefInd, short clrTbl) {
|
||||
VideoSampleEntry e = new VideoSampleEntry(atom);
|
||||
e.drefInd = drefInd;
|
||||
e.version = version;
|
||||
e.revision = revision;
|
||||
e.vendor = vendor;
|
||||
e.temporalQual = temporalQual;
|
||||
e.spacialQual = spacialQual;
|
||||
e.width = width;
|
||||
e.height = height;
|
||||
e.hRes = (float)hRes;
|
||||
e.vRes = (float)vRes;
|
||||
e.frameCount = frameCount;
|
||||
e.compressorName = compressorName;
|
||||
e.depth = depth;
|
||||
e.clrTbl = clrTbl;
|
||||
return e;
|
||||
}
|
||||
|
||||
public VideoSampleEntry(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer input) {
|
||||
super.parse(input);
|
||||
this.version = input.getShort();
|
||||
this.revision = input.getShort();
|
||||
this.vendor = NIOUtils.readString(input, 4);
|
||||
this.temporalQual = input.getInt();
|
||||
this.spacialQual = input.getInt();
|
||||
this.width = input.getShort();
|
||||
this.height = input.getShort();
|
||||
this.hRes = (float)input.getInt() / 65536.0F;
|
||||
this.vRes = (float)input.getInt() / 65536.0F;
|
||||
input.getInt();
|
||||
this.frameCount = input.getShort();
|
||||
this.compressorName = NIOUtils.readPascalStringL(input, 31);
|
||||
this.depth = input.getShort();
|
||||
this.clrTbl = input.getShort();
|
||||
parseExtensions(input);
|
||||
}
|
||||
|
||||
public void doWrite(ByteBuffer out) {
|
||||
super.doWrite(out);
|
||||
out.putShort(this.version);
|
||||
out.putShort(this.revision);
|
||||
out.put(JCodecUtil2.asciiString(this.vendor), 0, 4);
|
||||
out.putInt(this.temporalQual);
|
||||
out.putInt(this.spacialQual);
|
||||
out.putShort(this.width);
|
||||
out.putShort(this.height);
|
||||
out.putInt((int)(this.hRes * 65536.0F));
|
||||
out.putInt((int)(this.vRes * 65536.0F));
|
||||
out.putInt(0);
|
||||
out.putShort(this.frameCount);
|
||||
NIOUtils.writePascalStringL(out, this.compressorName, 31);
|
||||
out.putShort(this.depth);
|
||||
out.putShort(this.clrTbl);
|
||||
writeExtensions(out);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public float gethRes() {
|
||||
return this.hRes;
|
||||
}
|
||||
|
||||
public float getvRes() {
|
||||
return this.vRes;
|
||||
}
|
||||
|
||||
public long getFrameCount() {
|
||||
return (long)this.frameCount;
|
||||
}
|
||||
|
||||
public String getCompressorName() {
|
||||
return this.compressorName;
|
||||
}
|
||||
|
||||
public long getDepth() {
|
||||
return (long)this.depth;
|
||||
}
|
||||
|
||||
public String getVendor() {
|
||||
return this.vendor;
|
||||
}
|
||||
|
||||
public short getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public short getRevision() {
|
||||
return this.revision;
|
||||
}
|
||||
|
||||
public int getTemporalQual() {
|
||||
return this.temporalQual;
|
||||
}
|
||||
|
||||
public int getSpacialQual() {
|
||||
return this.spacialQual;
|
||||
}
|
||||
|
||||
public short getClrTbl() {
|
||||
return this.clrTbl;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.jcodec.containers.mp4.boxes;
|
||||
|
||||
public class WaveExtension extends NodeBox {
|
||||
public static String fourcc() {
|
||||
return "wave";
|
||||
}
|
||||
|
||||
public WaveExtension(Header atom) {
|
||||
super(atom);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package org.jcodec.containers.mp4.boxes.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.model.Label;
|
||||
|
||||
public final class ChannelLayout {
|
||||
private static final List<ChannelLayout> _values = new ArrayList<>();
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_UseChannelDescriptions = new ChannelLayout(0, new Label[0]);
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_UseChannelBitmap = new ChannelLayout(65536, new Label[0]);
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Mono = new ChannelLayout(6553601, new Label[] { Label.Mono });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Stereo = new ChannelLayout(6619138, new Label[] { Label.Left, Label.Right });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_StereoHeadphones = new ChannelLayout(6684674, new Label[] { Label.HeadphonesLeft, Label.HeadphonesRight });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MatrixStereo = new ChannelLayout(6750210, new Label[] { Label.LeftTotal, Label.RightTotal });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MidSide = new ChannelLayout(6815746, new Label[] { Label.MS_Mid, Label.MS_Side });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_XY = new ChannelLayout(6881282, new Label[] { Label.XY_X, Label.XY_Y });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Binaural = new ChannelLayout(6946818, new Label[] { Label.HeadphonesLeft, Label.HeadphonesRight });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Ambisonic_B_Format = new ChannelLayout(7012356, new Label[] { Label.Ambisonic_W, Label.Ambisonic_X, Label.Ambisonic_Y, Label.Ambisonic_Z });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Quadraphonic = new ChannelLayout(7077892, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Pentagonal = new ChannelLayout(7143429, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Hexagonal = new ChannelLayout(7208966, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Octagonal = new ChannelLayout(7274504, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.CenterSurround, Label.LeftCenter, Label.RightCenter });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Cube = new ChannelLayout(7340040, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.TopBackLeft, Label.TopBackRight, Label.TopBackCenter, Label.TopCenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_3_0_A = new ChannelLayout(7405571, new Label[] { Label.Left, Label.Right, Label.Center });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_3_0_B = new ChannelLayout(7471107, new Label[] { Label.Center, Label.Left, Label.Right });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_4_0_A = new ChannelLayout(7536644, new Label[] { Label.Left, Label.Right, Label.Center, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_4_0_B = new ChannelLayout(7602180, new Label[] { Label.Center, Label.Left, Label.Right, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_0_A = new ChannelLayout(7667717, new Label[] { Label.Left, Label.Right, Label.Center, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_0_B = new ChannelLayout(7733253, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_0_C = new ChannelLayout(7798789, new Label[] { Label.Left, Label.Center, Label.Right, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_0_D = new ChannelLayout(7864325, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_1_A = new ChannelLayout(7929862, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_1_B = new ChannelLayout(7995398, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_1_C = new ChannelLayout(8060934, new Label[] { Label.Left, Label.Center, Label.Right, Label.LeftSurround, Label.RightSurround, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_5_1_D = new ChannelLayout(8126470, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_6_1_A = new ChannelLayout(8192007, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround, Label.Right });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_7_1_A = new ChannelLayout(8257544, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround, Label.LeftCenter, Label.RightCenter });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_7_1_B = new ChannelLayout(8323080, new Label[] { Label.Center, Label.LeftCenter, Label.RightCenter, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_MPEG_7_1_C = new ChannelLayout(8388616, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround, Label.RearSurroundLeft, Label.RearSurroundRight });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_Emagic_Default_7_1 = new ChannelLayout(8454152, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.LFEScreen, Label.LeftCenter, Label.RightCenter });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_SMPTE_DTV = new ChannelLayout(8519688, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.LeftSurround, Label.RightSurround, Label.LeftTotal, Label.RightTotal });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_ITU_2_1 = new ChannelLayout(8585219, new Label[] { Label.Left, Label.Right, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_ITU_2_2 = new ChannelLayout(8650756, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_4 = new ChannelLayout(8716291, new Label[] { Label.Left, Label.Right, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_5 = new ChannelLayout(8781828, new Label[] { Label.Left, Label.Right, Label.LFEScreen, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_6 = new ChannelLayout(8847365, new Label[] { Label.Left, Label.Right, Label.LFEScreen, Label.LeftSurround, Label.RightSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_10 = new ChannelLayout(8912900, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_11 = new ChannelLayout(8978437, new Label[] { Label.Left, Label.Right, Label.Center, Label.LFEScreen, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_DVD_18 = new ChannelLayout(9043973, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AudioUnit_6_0 = new ChannelLayout(9109510, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AudioUnit_7_0 = new ChannelLayout(9175047, new Label[] { Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.Center, Label.RearSurroundLeft, Label.RearSurroundRight });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AAC_6_0 = new ChannelLayout(9240582, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AAC_6_1 = new ChannelLayout(9306119, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.CenterSurround, Label.LFEScreen });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AAC_7_0 = new ChannelLayout(9371655, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.RearSurroundLeft, Label.RearSurroundRight });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_AAC_Octagonal = new ChannelLayout(9437192, new Label[] { Label.Center, Label.Left, Label.Right, Label.LeftSurround, Label.RightSurround, Label.RearSurroundLeft, Label.RearSurroundRight, Label.CenterSurround });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_TMH_10_2_std = new ChannelLayout(9502736, new Label[] {
|
||||
Label.Left, Label.Right, Label.Center, Label.Mono, Label.Mono, Label.Mono, Label.LeftSurround, Label.RightSurround, Label.Mono, Label.Mono,
|
||||
Label.Mono, Label.Mono, Label.Mono, Label.CenterSurround, Label.LFEScreen, Label.LFE2 });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_TMH_10_2_full = new ChannelLayout(9568277, new Label[] { Label.LeftCenter, Label.RightCenter, Label.Mono, Label.Mono, Label.Mono });
|
||||
|
||||
public static final ChannelLayout kCAFChannelLayoutTag_RESERVED_DO_NOT_USE = new ChannelLayout(9633792, new Label[0]);
|
||||
|
||||
private int code;
|
||||
|
||||
private Label[] labels;
|
||||
|
||||
private ChannelLayout(int code, Label[] labels) {
|
||||
this.code = code;
|
||||
this.labels = labels;
|
||||
_values.add(this);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Label[] getLabels() {
|
||||
return this.labels;
|
||||
}
|
||||
|
||||
public static ChannelLayout[] values() {
|
||||
return _values.<ChannelLayout>toArray(new ChannelLayout[0]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package org.jcodec.containers.mp4.boxes.channel;
|
||||
|
||||
public class ChannelUtils {}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package org.jcodec.containers.mp4.demuxer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.SeekableDemuxerTrack;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.model.RationalLarge;
|
||||
import org.jcodec.containers.mp4.MP4Packet;
|
||||
import org.jcodec.containers.mp4.MP4TrackType;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
|
||||
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
|
||||
import org.jcodec.containers.mp4.boxes.Edit;
|
||||
import org.jcodec.containers.mp4.boxes.EditListBox;
|
||||
import org.jcodec.containers.mp4.boxes.NameBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
|
||||
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
|
||||
public abstract class AbstractMP4DemuxerTrack implements SeekableDemuxerTrack {
|
||||
protected TrakBox box;
|
||||
|
||||
private MP4TrackType type;
|
||||
|
||||
private int no;
|
||||
|
||||
protected SampleEntry[] sampleEntries;
|
||||
|
||||
protected TimeToSampleBox.TimeToSampleEntry[] timeToSamples;
|
||||
|
||||
protected SampleToChunkBox.SampleToChunkEntry[] sampleToChunks;
|
||||
|
||||
protected long[] chunkOffsets;
|
||||
|
||||
protected long duration;
|
||||
|
||||
protected int sttsInd;
|
||||
|
||||
protected int sttsSubInd;
|
||||
|
||||
protected int stcoInd;
|
||||
|
||||
protected int stscInd;
|
||||
|
||||
protected long pts;
|
||||
|
||||
protected long curFrame;
|
||||
|
||||
protected int timescale;
|
||||
|
||||
public AbstractMP4DemuxerTrack(TrakBox trak) {
|
||||
this.no = trak.getTrackHeader().getNo();
|
||||
this.type = TrakBox.getTrackType(trak);
|
||||
this.sampleEntries = NodeBox.<SampleEntry>findAllPath(trak, SampleEntry.class, new String[] { "mdia", "minf", "stbl", "stsd", null });
|
||||
NodeBox stbl = trak.getMdia().getMinf().getStbl();
|
||||
TimeToSampleBox stts = NodeBox.<TimeToSampleBox>findFirst(stbl, TimeToSampleBox.class, "stts");
|
||||
SampleToChunkBox stsc = NodeBox.<SampleToChunkBox>findFirst(stbl, SampleToChunkBox.class, "stsc");
|
||||
ChunkOffsetsBox stco = NodeBox.<ChunkOffsetsBox>findFirst(stbl, ChunkOffsetsBox.class, "stco");
|
||||
ChunkOffsets64Box co64 = NodeBox.<ChunkOffsets64Box>findFirst(stbl, ChunkOffsets64Box.class, "co64");
|
||||
this.timeToSamples = stts.getEntries();
|
||||
this.sampleToChunks = stsc.getSampleToChunk();
|
||||
this.chunkOffsets = (stco != null) ? stco.getChunkOffsets() : co64.getChunkOffsets();
|
||||
for (int i = 0; i < this.timeToSamples.length; i++) {
|
||||
TimeToSampleBox.TimeToSampleEntry ttse = this.timeToSamples[i];
|
||||
this.duration += (long)(ttse.getSampleCount() * ttse.getSampleDuration());
|
||||
}
|
||||
this.box = trak;
|
||||
this.timescale = trak.getTimescale();
|
||||
}
|
||||
|
||||
public int pts2Sample(long _tv, int _timescale) {
|
||||
long tv = _tv * (long)this.timescale / (long)_timescale;
|
||||
int sample = 0;
|
||||
int ttsInd;
|
||||
for (ttsInd = 0; ttsInd < this.timeToSamples.length - 1; ttsInd++) {
|
||||
int a = this.timeToSamples[ttsInd].getSampleCount() * this.timeToSamples[ttsInd].getSampleDuration();
|
||||
if (tv < (long)a)
|
||||
break;
|
||||
tv -= (long)a;
|
||||
sample += this.timeToSamples[ttsInd].getSampleCount();
|
||||
}
|
||||
return sample + (int)(tv / (long)this.timeToSamples[ttsInd].getSampleDuration());
|
||||
}
|
||||
|
||||
public MP4TrackType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public int getNo() {
|
||||
return this.no;
|
||||
}
|
||||
|
||||
public SampleEntry[] getSampleEntries() {
|
||||
return this.sampleEntries;
|
||||
}
|
||||
|
||||
public TrakBox getBox() {
|
||||
return this.box;
|
||||
}
|
||||
|
||||
public long getTimescale() {
|
||||
return (long)this.timescale;
|
||||
}
|
||||
|
||||
protected abstract void seekPointer(long paramLong);
|
||||
|
||||
public boolean canSeek(long pts) {
|
||||
return (pts >= 0L && pts < this.duration);
|
||||
}
|
||||
|
||||
public synchronized boolean seekPts(long pts) {
|
||||
if (pts < 0L)
|
||||
throw new IllegalArgumentException("Seeking to negative pts");
|
||||
if (pts >= this.duration)
|
||||
return false;
|
||||
long prevDur = 0L;
|
||||
int frameNo = 0;
|
||||
this.sttsInd = 0;
|
||||
for (; pts > prevDur + (long)(this.timeToSamples[this.sttsInd].getSampleCount() * this.timeToSamples[this.sttsInd].getSampleDuration()) && this.sttsInd < this.timeToSamples.length - 1; this.sttsInd++) {
|
||||
prevDur += (long)(this.timeToSamples[this.sttsInd].getSampleCount() * this.timeToSamples[this.sttsInd].getSampleDuration());
|
||||
frameNo += this.timeToSamples[this.sttsInd].getSampleCount();
|
||||
}
|
||||
this.sttsSubInd = (int)((pts - prevDur) / (long)this.timeToSamples[this.sttsInd].getSampleDuration());
|
||||
frameNo += this.sttsSubInd;
|
||||
this.pts = prevDur + (long)(this.timeToSamples[this.sttsInd].getSampleDuration() * this.sttsSubInd);
|
||||
seekPointer((long)frameNo);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void shiftPts(long frames) {
|
||||
this.pts -= (long)(this.sttsSubInd * this.timeToSamples[this.sttsInd].getSampleDuration());
|
||||
this.sttsSubInd = (int)((long)this.sttsSubInd + frames);
|
||||
while (this.sttsInd < this.timeToSamples.length - 1 && this.sttsSubInd >= this.timeToSamples[this.sttsInd].getSampleCount()) {
|
||||
this.pts += this.timeToSamples[this.sttsInd].getSegmentDuration();
|
||||
this.sttsSubInd -= this.timeToSamples[this.sttsInd].getSampleCount();
|
||||
this.sttsInd++;
|
||||
}
|
||||
this.pts += (long)(this.sttsSubInd * this.timeToSamples[this.sttsInd].getSampleDuration());
|
||||
}
|
||||
|
||||
protected void nextChunk() {
|
||||
if (this.stcoInd >= this.chunkOffsets.length)
|
||||
return;
|
||||
this.stcoInd++;
|
||||
if (this.stscInd + 1 < this.sampleToChunks.length && (long)(this.stcoInd + 1) == this.sampleToChunks[this.stscInd + 1].getFirst())
|
||||
this.stscInd++;
|
||||
}
|
||||
|
||||
public synchronized boolean gotoFrame(long frameNo) {
|
||||
if (frameNo < 0L)
|
||||
throw new IllegalArgumentException("negative frame number");
|
||||
if (frameNo >= getFrameCount())
|
||||
return false;
|
||||
if (frameNo == this.curFrame)
|
||||
return true;
|
||||
seekPointer(frameNo);
|
||||
seekFrame(frameNo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void seek(double second) {
|
||||
seekPts((long)(second * (double)this.timescale));
|
||||
}
|
||||
|
||||
private void seekFrame(long frameNo) {
|
||||
this.pts = (long)(this.sttsInd = this.sttsSubInd = 0);
|
||||
shiftPts(frameNo);
|
||||
}
|
||||
|
||||
public RationalLarge getDuration() {
|
||||
return new RationalLarge(this.box.getMediaDuration(), (long)this.box.getTimescale());
|
||||
}
|
||||
|
||||
public abstract long getFrameCount();
|
||||
|
||||
public long getCurFrame() {
|
||||
return this.curFrame;
|
||||
}
|
||||
|
||||
public List<Edit> getEdits() {
|
||||
EditListBox editListBox = NodeBox.<EditListBox>findFirstPath(this.box, EditListBox.class, Box.path("edts.elst"));
|
||||
if (editListBox != null)
|
||||
return editListBox.getEdits();
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
NameBox nameBox = NodeBox.<NameBox>findFirstPath(this.box, NameBox.class, Box.path("udta.name"));
|
||||
return (nameBox != null) ? nameBox.getName() : null;
|
||||
}
|
||||
|
||||
public String getFourcc() {
|
||||
SampleEntry[] entries = getSampleEntries();
|
||||
SampleEntry se = (entries == null || entries.length == 0) ? null : entries[0];
|
||||
String fourcc = (se == null) ? null : se.getHeader().getFourcc();
|
||||
return fourcc;
|
||||
}
|
||||
|
||||
protected ByteBuffer readPacketData(SeekableByteChannel input, ByteBuffer buffer, long offset, int size) throws IOException {
|
||||
ByteBuffer result = buffer.duplicate();
|
||||
synchronized (input) {
|
||||
input.setPosition(offset);
|
||||
NIOUtils.readL(input, result, size);
|
||||
}
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
public abstract MP4Packet getNextFrame(ByteBuffer paramByteBuffer) throws IOException;
|
||||
|
||||
public ByteBuffer convertPacket(ByteBuffer _in) {
|
||||
return _in;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return MP4DemuxerTrackMeta.fromTrack(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.jcodec.containers.mp4.demuxer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import org.jcodec.codecs.aac.AACUtils;
|
||||
import org.jcodec.codecs.aac.ADTSParser;
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
import org.jcodec.codecs.h264.mp4.AvcCBox;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
|
||||
|
||||
public class CodecMP4DemuxerTrack extends MP4DemuxerTrack {
|
||||
private ByteBuffer codecPrivate;
|
||||
|
||||
private AvcCBox avcC;
|
||||
|
||||
public CodecMP4DemuxerTrack(MovieBox mov, TrakBox trak, SeekableByteChannel input) {
|
||||
super(mov, trak, input);
|
||||
if (Codec.codecByFourcc(getFourcc()) == Codec.H264)
|
||||
this.avcC = H264Utils.parseAVCC((VideoSampleEntry)getSampleEntries()[0]);
|
||||
this.codecPrivate = MP4DemuxerTrackMeta.getCodecPrivate(this);
|
||||
}
|
||||
|
||||
public ByteBuffer convertPacket(ByteBuffer result) {
|
||||
if (this.codecPrivate != null) {
|
||||
if (Codec.codecByFourcc(getFourcc()) == Codec.H264) {
|
||||
ByteBuffer annexbCoded = H264Utils.decodeMOVPacket(result, this.avcC);
|
||||
if (H264Utils.isByteBufferIDRSlice(annexbCoded))
|
||||
return NIOUtils.combineBuffers(Arrays.asList(this.codecPrivate, annexbCoded));
|
||||
return annexbCoded;
|
||||
}
|
||||
if (Codec.codecByFourcc(getFourcc()) == Codec.AAC) {
|
||||
ADTSParser.Header adts = AACUtils.streamInfoToADTS(this.codecPrivate, true, 1, result.remaining());
|
||||
ByteBuffer adtsRaw = ByteBuffer.allocate(7);
|
||||
ADTSParser.write(adts, adtsRaw);
|
||||
return NIOUtils.combineBuffers(Arrays.asList(adtsRaw, result));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
package org.jcodec.containers.mp4.demuxer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.Demuxer;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.Fourcc;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mp4.MP4TrackType;
|
||||
import org.jcodec.containers.mp4.MP4Util;
|
||||
import org.jcodec.containers.mp4.boxes.Box;
|
||||
import org.jcodec.containers.mp4.boxes.HandlerBox;
|
||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
||||
import org.jcodec.containers.mp4.boxes.NodeBox;
|
||||
import org.jcodec.containers.mp4.boxes.SampleEntry;
|
||||
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
|
||||
import org.jcodec.containers.mp4.boxes.TrakBox;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MP4Demuxer implements Demuxer {
|
||||
private List<AbstractMP4DemuxerTrack> tracks;
|
||||
|
||||
private TimecodeMP4DemuxerTrack timecodeTrack;
|
||||
|
||||
MovieBox movie;
|
||||
|
||||
protected SeekableByteChannel input;
|
||||
|
||||
public static MP4Demuxer createMP4Demuxer(SeekableByteChannel input) throws IOException {
|
||||
return new MP4Demuxer(input);
|
||||
}
|
||||
|
||||
public static MP4Demuxer createRawMP4Demuxer(SeekableByteChannel input) throws IOException {
|
||||
return new MP4Demuxer(input) {
|
||||
protected AbstractMP4DemuxerTrack newTrack(TrakBox trak) {
|
||||
return new MP4DemuxerTrack(this.movie, trak, this.input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private AbstractMP4DemuxerTrack fromTrakBox(TrakBox trak) {
|
||||
SampleSizesBox stsz = NodeBox.<SampleSizesBox>findFirstPath(trak, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz"));
|
||||
if (stsz.getDefaultSize() == 0)
|
||||
return newTrack(trak);
|
||||
return new PCMMP4DemuxerTrack(this.movie, trak, this.input);
|
||||
}
|
||||
|
||||
protected AbstractMP4DemuxerTrack newTrack(TrakBox trak) {
|
||||
return new CodecMP4DemuxerTrack(this.movie, trak, this.input);
|
||||
}
|
||||
|
||||
MP4Demuxer(SeekableByteChannel input) throws IOException {
|
||||
this.input = input;
|
||||
this.tracks = new LinkedList<>();
|
||||
findMovieBox(input);
|
||||
}
|
||||
|
||||
private void findMovieBox(SeekableByteChannel input) throws IOException {
|
||||
MP4Util.Movie mv = MP4Util.parseFullMovieChannel(input);
|
||||
if (mv == null || mv.getMoov() == null)
|
||||
throw new IOException("Could not find movie meta information box");
|
||||
this.movie = mv.getMoov();
|
||||
processHeader(this.movie);
|
||||
}
|
||||
|
||||
private void processHeader(NodeBox moov) throws IOException {
|
||||
TrakBox tt = null;
|
||||
TrakBox[] trakBoxs = NodeBox.<TrakBox>findAll(moov, TrakBox.class, "trak");
|
||||
for (int i = 0; i < trakBoxs.length; i++) {
|
||||
TrakBox trak = trakBoxs[i];
|
||||
SampleEntry se = NodeBox.<SampleEntry>findFirstPath(trak, SampleEntry.class, new String[] { "mdia", "minf", "stbl", "stsd", null });
|
||||
if (se != null && "tmcd".equals(se.getFourcc())) {
|
||||
tt = trak;
|
||||
} else {
|
||||
this.tracks.add(fromTrakBox(trak));
|
||||
}
|
||||
}
|
||||
if (tt != null) {
|
||||
DemuxerTrack video = getVideoTrack();
|
||||
if (video != null)
|
||||
this.timecodeTrack = new TimecodeMP4DemuxerTrack(this.movie, tt, this.input);
|
||||
}
|
||||
}
|
||||
|
||||
public static MP4TrackType getTrackType(TrakBox trak) {
|
||||
HandlerBox handler = NodeBox.<HandlerBox>findFirstPath(trak, HandlerBox.class, Box.path("mdia.hdlr"));
|
||||
return MP4TrackType.fromHandler(handler.getComponentSubType());
|
||||
}
|
||||
|
||||
public DemuxerTrack getVideoTrack() {
|
||||
for (AbstractMP4DemuxerTrack demuxerTrack : this.tracks) {
|
||||
if (demuxerTrack.box.isVideo())
|
||||
return demuxerTrack;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MovieBox getMovie() {
|
||||
return this.movie;
|
||||
}
|
||||
|
||||
public AbstractMP4DemuxerTrack getTrack(int no) {
|
||||
for (AbstractMP4DemuxerTrack track : this.tracks) {
|
||||
if (track.getNo() == no)
|
||||
return track;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AbstractMP4DemuxerTrack> getTracks() {
|
||||
return new ArrayList<>(this.tracks);
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getVideoTracks() {
|
||||
ArrayList<DemuxerTrack> result = new ArrayList<>();
|
||||
for (AbstractMP4DemuxerTrack demuxerTrack : this.tracks) {
|
||||
if (demuxerTrack.box.isVideo())
|
||||
result.add(demuxerTrack);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getAudioTracks() {
|
||||
ArrayList<DemuxerTrack> result = new ArrayList<>();
|
||||
for (AbstractMP4DemuxerTrack demuxerTrack : this.tracks) {
|
||||
if (demuxerTrack.box.isAudio())
|
||||
result.add(demuxerTrack);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public TimecodeMP4DemuxerTrack getTimecodeTrack() {
|
||||
return this.timecodeTrack;
|
||||
}
|
||||
|
||||
@UsedViaReflection
|
||||
public static int probe(ByteBuffer b) {
|
||||
ByteBuffer fork = b.duplicate();
|
||||
int success = 0;
|
||||
int total = 0;
|
||||
while (fork.remaining() >= 8) {
|
||||
long len = Platform.unsignedInt(fork.getInt());
|
||||
int fcc = fork.getInt();
|
||||
int hdrLen = 8;
|
||||
if (len == 1L) {
|
||||
len = fork.getLong();
|
||||
hdrLen = 16;
|
||||
} else if (len < 8L) {
|
||||
break;
|
||||
}
|
||||
if ((fcc == Fourcc.ftyp && len < 64L) || (fcc == Fourcc.moov && len < 104857600L) || fcc == Fourcc.free || fcc == Fourcc.mdat || fcc == Fourcc.wide)
|
||||
success++;
|
||||
total++;
|
||||
if (len >= Integer.MAX_VALUE)
|
||||
break;
|
||||
NIOUtils.skip(fork, (int)(len - (long)hdrLen));
|
||||
}
|
||||
return (total == 0) ? 0 : (success * 100 / total);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.input.close();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue