www in docker support
This commit is contained in:
parent
539a848e95
commit
c227fce036
2145 changed files with 399596 additions and 58 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,44 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
import org.jcodec.common.StringUtils;
|
||||
|
||||
public class DPXMetadata {
|
||||
public static final String V2 = "V2.0";
|
||||
|
||||
public static final String V1 = "V1.0";
|
||||
|
||||
public FileHeader file;
|
||||
|
||||
public ImageHeader image;
|
||||
|
||||
public ImageSourceHeader imageSource;
|
||||
|
||||
public FilmHeader film;
|
||||
|
||||
public TelevisionHeader television;
|
||||
|
||||
public String userId;
|
||||
|
||||
private static String smpteTC(int tcsmpte, boolean prevent_dropframe) {
|
||||
int ff = bcd2uint(tcsmpte & 0x3F);
|
||||
int ss = bcd2uint(tcsmpte >> 8 & 0x7F);
|
||||
int mm = bcd2uint(tcsmpte >> 16 & 0x7F);
|
||||
int hh = bcd2uint(tcsmpte >> 24 & 0x3F);
|
||||
boolean drop = ((long)(tcsmpte & 0x40000000) > 0L && !prevent_dropframe);
|
||||
return StringUtils.zeroPad2(hh) + ":" + StringUtils.zeroPad2(hh) + ":" +
|
||||
StringUtils.zeroPad2(mm) +
|
||||
StringUtils.zeroPad2(ss) + (drop ? ";" : ":");
|
||||
}
|
||||
|
||||
private static int bcd2uint(int bcd) {
|
||||
int low = bcd & 0xF;
|
||||
int high = bcd >> 4;
|
||||
if (low > 9 || high > 9)
|
||||
return 0;
|
||||
return low + 10 * high;
|
||||
}
|
||||
|
||||
public String getTimecodeString() {
|
||||
return smpteTC(this.television.timecode, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import org.jcodec.common.StringUtils;
|
||||
import org.jcodec.common.io.IOUtils;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
|
||||
public class DPXReader {
|
||||
private static final int READ_BUFFER_SIZE = 3072;
|
||||
|
||||
static final int IMAGEINFO_OFFSET = 768;
|
||||
|
||||
static final int IMAGESOURCE_OFFSET = 1408;
|
||||
|
||||
static final int FILM_OFFSET = 1664;
|
||||
|
||||
static final int TVINFO_OFFSET = 1920;
|
||||
|
||||
public static final int SDPX = 1396985944;
|
||||
|
||||
private final ByteBuffer readBuf;
|
||||
|
||||
private final int magic;
|
||||
|
||||
private boolean eof;
|
||||
|
||||
public DPXReader(SeekableByteChannel ch) throws IOException {
|
||||
this.readBuf = ByteBuffer.allocate(3072);
|
||||
initialRead(ch);
|
||||
this.magic = this.readBuf.getInt();
|
||||
if (this.magic == 1396985944) {
|
||||
this.readBuf.order(ByteOrder.BIG_ENDIAN);
|
||||
} else {
|
||||
this.readBuf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
}
|
||||
|
||||
public DPXMetadata parseMetadata() {
|
||||
DPXMetadata dpx = new DPXMetadata();
|
||||
dpx.file = readFileInfo(this.readBuf);
|
||||
dpx.file.magic = this.magic;
|
||||
this.readBuf.position(768);
|
||||
dpx.image = readImageInfoHeader(this.readBuf);
|
||||
this.readBuf.position(1408);
|
||||
dpx.imageSource = readImageSourceHeader(this.readBuf);
|
||||
this.readBuf.position(1664);
|
||||
dpx.film = readFilmInformationHeader(this.readBuf);
|
||||
this.readBuf.position(1920);
|
||||
dpx.television = readTelevisionInfoHeader(this.readBuf);
|
||||
dpx.userId = readNullTermString(this.readBuf, 32);
|
||||
return dpx;
|
||||
}
|
||||
|
||||
private void initialRead(ReadableByteChannel ch) throws IOException {
|
||||
this.readBuf.clear();
|
||||
if (ch.read(this.readBuf) == -1)
|
||||
this.eof = true;
|
||||
this.readBuf.flip();
|
||||
}
|
||||
|
||||
private static FileHeader readFileInfo(ByteBuffer bb) {
|
||||
FileHeader h = new FileHeader();
|
||||
h.imageOffset = bb.getInt();
|
||||
h.version = readNullTermString(bb, 8);
|
||||
h.filesize = bb.getInt();
|
||||
h.ditto = bb.getInt();
|
||||
h.genericHeaderLength = bb.getInt();
|
||||
h.industryHeaderLength = bb.getInt();
|
||||
h.userHeaderLength = bb.getInt();
|
||||
h.filename = readNullTermString(bb, 100);
|
||||
h.created = tryParseISO8601Date(readNullTermString(bb, 24));
|
||||
h.creator = readNullTermString(bb, 100);
|
||||
h.projectName = readNullTermString(bb, 200);
|
||||
h.copyright = readNullTermString(bb, 200);
|
||||
h.encKey = bb.getInt();
|
||||
return h;
|
||||
}
|
||||
|
||||
static Date tryParseISO8601Date(String dateString) {
|
||||
if (StringUtils.isEmpty(dateString))
|
||||
return null;
|
||||
String noTZ = "yyyy:MM:dd:HH:mm:ss";
|
||||
if (dateString.length() == noTZ.length())
|
||||
return date(dateString, noTZ);
|
||||
if (dateString.length() == noTZ.length() + 4)
|
||||
dateString = dateString + "00";
|
||||
return date(dateString, "yyyy:MM:dd:HH:mm:ss:Z");
|
||||
}
|
||||
|
||||
private static Date date(String dateString, String dateFormat) {
|
||||
SimpleDateFormat format = new SimpleDateFormat(dateFormat, Locale.US);
|
||||
try {
|
||||
return format.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String readNullTermString(ByteBuffer bb, int length) {
|
||||
ByteBuffer b = ByteBuffer.allocate(length);
|
||||
bb.get(b.array(), 0, length);
|
||||
return NIOUtils.readNullTermString(b);
|
||||
}
|
||||
|
||||
public static DPXReader readFile(File file) throws IOException {
|
||||
SeekableByteChannel _in = NIOUtils.readableChannel(file);
|
||||
try {
|
||||
return new DPXReader(_in);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(_in);
|
||||
}
|
||||
}
|
||||
|
||||
private static TelevisionHeader readTelevisionInfoHeader(ByteBuffer r) {
|
||||
TelevisionHeader h = new TelevisionHeader();
|
||||
h.timecode = r.getInt();
|
||||
h.userBits = r.getInt();
|
||||
h.interlace = r.get();
|
||||
h.filedNumber = r.get();
|
||||
h.videoSignalStarted = r.get();
|
||||
h.zero = r.get();
|
||||
h.horSamplingRateHz = r.getInt();
|
||||
h.vertSampleRateHz = r.getInt();
|
||||
h.frameRate = r.getInt();
|
||||
h.timeOffset = r.getInt();
|
||||
h.gamma = r.getInt();
|
||||
h.blackLevel = r.getInt();
|
||||
h.blackGain = r.getInt();
|
||||
h.breakpoint = r.getInt();
|
||||
h.referenceWhiteLevel = r.getInt();
|
||||
h.integrationTime = r.getInt();
|
||||
return h;
|
||||
}
|
||||
|
||||
private static FilmHeader readFilmInformationHeader(ByteBuffer r) {
|
||||
FilmHeader h = new FilmHeader();
|
||||
h.idCode = readNullTermString(r, 2);
|
||||
h.type = readNullTermString(r, 2);
|
||||
h.offset = readNullTermString(r, 2);
|
||||
h.prefix = readNullTermString(r, 6);
|
||||
h.count = readNullTermString(r, 4);
|
||||
h.format = readNullTermString(r, 32);
|
||||
return h;
|
||||
}
|
||||
|
||||
private static ImageSourceHeader readImageSourceHeader(ByteBuffer r) {
|
||||
ImageSourceHeader h = new ImageSourceHeader();
|
||||
h.xOffset = r.getInt();
|
||||
h.yOffset = r.getInt();
|
||||
h.xCenter = r.getFloat();
|
||||
h.yCenter = r.getFloat();
|
||||
h.xOriginal = r.getInt();
|
||||
h.yOriginal = r.getInt();
|
||||
h.sourceImageFilename = readNullTermString(r, 100);
|
||||
h.sourceImageDate = tryParseISO8601Date(readNullTermString(r, 24));
|
||||
h.deviceName = readNullTermString(r, 32);
|
||||
h.deviceSerial = readNullTermString(r, 32);
|
||||
h.borderValidity = new short[] { r.getShort(), r.getShort(), r.getShort(), r.getShort() };
|
||||
h.aspectRatio = new int[] { r.getInt(), r.getInt() };
|
||||
return h;
|
||||
}
|
||||
|
||||
private static ImageHeader readImageInfoHeader(ByteBuffer r) {
|
||||
ImageHeader h = new ImageHeader();
|
||||
h.orientation = r.getShort();
|
||||
h.numberOfImageElements = r.getShort();
|
||||
h.pixelsPerLine = r.getInt();
|
||||
h.linesPerImageElement = r.getInt();
|
||||
h.imageElement1 = new ImageElement();
|
||||
h.imageElement1.dataSign = r.getInt();
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FileHeader {
|
||||
public int magic;
|
||||
|
||||
public int imageOffset;
|
||||
|
||||
public String version;
|
||||
|
||||
public int ditto;
|
||||
|
||||
public String filename;
|
||||
|
||||
public Date created;
|
||||
|
||||
public int filesize;
|
||||
|
||||
public String creator;
|
||||
|
||||
public String projectName;
|
||||
|
||||
public String copyright;
|
||||
|
||||
public int encKey;
|
||||
|
||||
public int genericHeaderLength;
|
||||
|
||||
public int industryHeaderLength;
|
||||
|
||||
public int userHeaderLength;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
public class FilmHeader {
|
||||
public String idCode;
|
||||
|
||||
public String type;
|
||||
|
||||
public String offset;
|
||||
|
||||
public String prefix;
|
||||
|
||||
public String count;
|
||||
|
||||
public String format;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
public class ImageElement {
|
||||
public int dataSign;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
public class ImageHeader {
|
||||
public short orientation;
|
||||
|
||||
public short numberOfImageElements;
|
||||
|
||||
public int linesPerImageElement;
|
||||
|
||||
public int pixelsPerLine;
|
||||
|
||||
public ImageElement imageElement1;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ImageSourceHeader {
|
||||
public int xOffset;
|
||||
|
||||
public int yOffset;
|
||||
|
||||
public float xCenter;
|
||||
|
||||
public float yCenter;
|
||||
|
||||
public int xOriginal;
|
||||
|
||||
public int yOriginal;
|
||||
|
||||
public String sourceImageFilename;
|
||||
|
||||
public Date sourceImageDate;
|
||||
|
||||
public String deviceName;
|
||||
|
||||
public String deviceSerial;
|
||||
|
||||
public short[] borderValidity;
|
||||
|
||||
public int[] aspectRatio;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.jcodec.containers.dpx;
|
||||
|
||||
public class TelevisionHeader {
|
||||
public int timecode;
|
||||
|
||||
public int userBits;
|
||||
|
||||
public byte interlace;
|
||||
|
||||
public byte filedNumber;
|
||||
|
||||
public byte videoSignalStarted;
|
||||
|
||||
public byte zero;
|
||||
|
||||
public int horSamplingRateHz;
|
||||
|
||||
public int vertSampleRateHz;
|
||||
|
||||
public int frameRate;
|
||||
|
||||
public int timeOffset;
|
||||
|
||||
public int gamma;
|
||||
|
||||
public int blackLevel;
|
||||
|
||||
public int blackGain;
|
||||
|
||||
public int breakpoint;
|
||||
|
||||
public int referenceWhiteLevel;
|
||||
|
||||
public int integrationTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class FLVMetadata {
|
||||
private double duration;
|
||||
|
||||
private double width;
|
||||
|
||||
private double height;
|
||||
|
||||
private double framerate;
|
||||
|
||||
private String audiocodecid;
|
||||
|
||||
private double videokeyframe_frequency;
|
||||
|
||||
private String videodevice;
|
||||
|
||||
private double avclevel;
|
||||
|
||||
private double audiosamplerate;
|
||||
|
||||
private double audiochannels;
|
||||
|
||||
private String presetname;
|
||||
|
||||
private double videodatarate;
|
||||
|
||||
private double audioinputvolume;
|
||||
|
||||
private Date creationdate;
|
||||
|
||||
private String videocodecid;
|
||||
|
||||
private double avcprofile;
|
||||
|
||||
private String audiodevice;
|
||||
|
||||
private double audiodatarate;
|
||||
|
||||
public FLVMetadata(Map<String, Object> md) {
|
||||
Field[] declaredFields = Platform.getDeclaredFields(getClass());
|
||||
for (int i = 0; i < declaredFields.length; i++) {
|
||||
Field field = declaredFields[i];
|
||||
Object object = md.get(field.getName());
|
||||
try {
|
||||
if (object instanceof Double) {
|
||||
field.setDouble(this, ((Double)object).doubleValue());
|
||||
} else if (object instanceof Boolean) {
|
||||
field.setBoolean(this, ((Boolean)object).booleanValue());
|
||||
} else {
|
||||
field.set(this, object);
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public double getFramerate() {
|
||||
return this.framerate;
|
||||
}
|
||||
|
||||
public String getAudiocodecid() {
|
||||
return this.audiocodecid;
|
||||
}
|
||||
|
||||
public double getVideokeyframe_frequency() {
|
||||
return this.videokeyframe_frequency;
|
||||
}
|
||||
|
||||
public String getVideodevice() {
|
||||
return this.videodevice;
|
||||
}
|
||||
|
||||
public double getAvclevel() {
|
||||
return this.avclevel;
|
||||
}
|
||||
|
||||
public double getAudiosamplerate() {
|
||||
return this.audiosamplerate;
|
||||
}
|
||||
|
||||
public double getAudiochannels() {
|
||||
return this.audiochannels;
|
||||
}
|
||||
|
||||
public String getPresetname() {
|
||||
return this.presetname;
|
||||
}
|
||||
|
||||
public double getVideodatarate() {
|
||||
return this.videodatarate;
|
||||
}
|
||||
|
||||
public double getAudioinputvolume() {
|
||||
return this.audioinputvolume;
|
||||
}
|
||||
|
||||
public Date getCreationdate() {
|
||||
return this.creationdate;
|
||||
}
|
||||
|
||||
public String getVideocodecid() {
|
||||
return this.videocodecid;
|
||||
}
|
||||
|
||||
public double getAvcprofile() {
|
||||
return this.avcprofile;
|
||||
}
|
||||
|
||||
public String getAudiodevice() {
|
||||
return this.audiodevice;
|
||||
}
|
||||
|
||||
public double getAudiodatarate() {
|
||||
return this.audiodatarate;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jcodec.common.AudioFormat;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.common.tools.MathUtil;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class FLVReader {
|
||||
private static final int REPOSITION_BUFFER_READS = 10;
|
||||
|
||||
private static final int TAG_HEADER_SIZE = 15;
|
||||
|
||||
private static final int READ_BUFFER_SIZE = 1024;
|
||||
|
||||
private int frameNo;
|
||||
|
||||
private ByteBuffer readBuf;
|
||||
|
||||
private SeekableByteChannel ch;
|
||||
|
||||
private boolean eof;
|
||||
|
||||
private static boolean platformBigEndian = (ByteBuffer.allocate(0).order() == ByteOrder.BIG_ENDIAN);
|
||||
|
||||
public static Codec[] audioCodecMapping = new Codec[] {
|
||||
Codec.PCM, Codec.ADPCM, Codec.MP3, Codec.PCM, Codec.NELLYMOSER, Codec.NELLYMOSER, Codec.NELLYMOSER, Codec.G711, Codec.G711, null,
|
||||
Codec.AAC, Codec.SPEEX, Codec.MP3, null };
|
||||
|
||||
public static Codec[] videoCodecMapping = new Codec[] { null, null, Codec.SORENSON, Codec.FLASH_SCREEN_VIDEO, Codec.VP6, Codec.VP6, Codec.FLASH_SCREEN_V2, Codec.H264 };
|
||||
|
||||
public static int[] sampleRates = new int[] { 5500, 11000, 22000, 44100 };
|
||||
|
||||
public FLVReader(SeekableByteChannel ch) throws IOException {
|
||||
this.ch = ch;
|
||||
this.readBuf = ByteBuffer.allocate(1024);
|
||||
this.readBuf.order(ByteOrder.BIG_ENDIAN);
|
||||
initialRead(ch);
|
||||
if (!readHeader(this.readBuf)) {
|
||||
this.readBuf.position(0);
|
||||
if (!repositionFile())
|
||||
throw new RuntimeException("Invalid FLV file");
|
||||
Logger.warn(String.format("Parsing a corrupt FLV file, first tag found at %d. %s", this.readBuf.position(),
|
||||
(this.readBuf.position() == 0) ? "Did you forget the FLV 9-byte header?" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
private void initialRead(ReadableByteChannel ch) throws IOException {
|
||||
this.readBuf.clear();
|
||||
if (ch.read(this.readBuf) == -1)
|
||||
this.eof = true;
|
||||
this.readBuf.flip();
|
||||
}
|
||||
|
||||
public FLVTag readNextPacket() throws IOException {
|
||||
if (this.eof)
|
||||
return null;
|
||||
FLVTag pkt = parsePacket(this.readBuf);
|
||||
if (pkt == null && !this.eof) {
|
||||
moveRemainderToTheStart(this.readBuf);
|
||||
if (this.ch.read(this.readBuf) == -1) {
|
||||
this.eof = true;
|
||||
return null;
|
||||
}
|
||||
while (MathUtil.log2(this.readBuf.capacity()) <= 22) {
|
||||
this.readBuf.flip();
|
||||
pkt = parsePacket(this.readBuf);
|
||||
if (pkt != null || this.readBuf.position() > 0)
|
||||
break;
|
||||
ByteBuffer newBuf = ByteBuffer.allocate(this.readBuf.capacity() << 2);
|
||||
newBuf.put(this.readBuf);
|
||||
this.readBuf = newBuf;
|
||||
if (this.ch.read(this.readBuf) == -1) {
|
||||
this.eof = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
|
||||
public FLVTag readPrevPacket() throws IOException {
|
||||
int startOfLastPacket = this.readBuf.getInt();
|
||||
this.readBuf.position(this.readBuf.position() - 4);
|
||||
if (this.readBuf.position() > startOfLastPacket) {
|
||||
this.readBuf.position(this.readBuf.position() - startOfLastPacket);
|
||||
return parsePacket(this.readBuf);
|
||||
}
|
||||
long oldPos = this.ch.position() - (long)this.readBuf.remaining();
|
||||
if (oldPos <= 9L)
|
||||
return null;
|
||||
long newPos = Math.max(0L, oldPos - (long)(this.readBuf.capacity() / 2));
|
||||
this.ch.setPosition(newPos);
|
||||
this.readBuf.clear();
|
||||
this.ch.read(this.readBuf);
|
||||
this.readBuf.flip();
|
||||
this.readBuf.position((int)(oldPos - newPos));
|
||||
return readPrevPacket();
|
||||
}
|
||||
|
||||
private static void moveRemainderToTheStart(ByteBuffer readBuf) {
|
||||
int rem = readBuf.remaining();
|
||||
for (int i = 0; i < rem; i++)
|
||||
readBuf.put(i, readBuf.get());
|
||||
readBuf.clear();
|
||||
readBuf.position(rem);
|
||||
}
|
||||
|
||||
public FLVTag parsePacket(ByteBuffer readBuf) throws IOException {
|
||||
long packetPos;
|
||||
int prevPacketSize, packetType, timestamp, streamId;
|
||||
ByteBuffer payload;
|
||||
FLVTag.Type type;
|
||||
FLVTag.TagHeader tagHeader;
|
||||
while (true) {
|
||||
if (readBuf.remaining() < 15)
|
||||
return null;
|
||||
int pos = readBuf.position();
|
||||
packetPos = this.ch.position() - (long)readBuf.remaining();
|
||||
prevPacketSize = readBuf.getInt();
|
||||
packetType = readBuf.get() & 0xFF;
|
||||
int payloadSize = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF;
|
||||
timestamp = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF | (readBuf.get() & 0xFF) << 24;
|
||||
streamId = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF;
|
||||
if (readBuf.remaining() >= payloadSize + 4) {
|
||||
int thisPacketSize = readBuf.getInt(readBuf.position() + payloadSize);
|
||||
if (thisPacketSize != payloadSize + 11) {
|
||||
readBuf.position(readBuf.position() - 15);
|
||||
if (!repositionFile()) {
|
||||
Logger.error(String.format("Corrupt FLV stream at %d, failed to reposition!", packetPos));
|
||||
this.ch.setPosition(this.ch.size());
|
||||
this.eof = true;
|
||||
return null;
|
||||
}
|
||||
Logger.warn(String.format("Corrupt FLV stream at %d, repositioned to %d.", packetPos, this.ch.position() -
|
||||
(long)readBuf.remaining()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (readBuf.remaining() < payloadSize) {
|
||||
readBuf.position(pos);
|
||||
return null;
|
||||
}
|
||||
if (packetType != 8 && packetType != 9 && packetType != 18) {
|
||||
NIOUtils.skip(readBuf, payloadSize);
|
||||
continue;
|
||||
}
|
||||
payload = NIOUtils.clone(NIOUtils.read(readBuf, payloadSize));
|
||||
if (packetType == 8) {
|
||||
type = FLVTag.Type.AUDIO;
|
||||
tagHeader = parseAudioTagHeader(payload.duplicate());
|
||||
break;
|
||||
}
|
||||
if (packetType == 9) {
|
||||
type = FLVTag.Type.VIDEO;
|
||||
tagHeader = parseVideoTagHeader(payload.duplicate());
|
||||
break;
|
||||
}
|
||||
if (packetType == 18) {
|
||||
type = FLVTag.Type.SCRIPT;
|
||||
tagHeader = null;
|
||||
break;
|
||||
}
|
||||
System.out.println("NON AV packet");
|
||||
}
|
||||
boolean keyFrame = false;
|
||||
if (tagHeader != null && tagHeader instanceof FLVTag.VideoTagHeader) {
|
||||
FLVTag.VideoTagHeader vth = (FLVTag.VideoTagHeader)tagHeader;
|
||||
keyFrame &= (vth.getFrameType() == 1) ? true : false;
|
||||
}
|
||||
keyFrame &= (packetType == 8 || packetType == 9) ? true : false;
|
||||
return new FLVTag(type, packetPos, tagHeader, timestamp, payload, keyFrame, (long)this.frameNo++, streamId, prevPacketSize);
|
||||
}
|
||||
|
||||
public static boolean readHeader(ByteBuffer readBuf) {
|
||||
if (readBuf.remaining() < 9 || readBuf.get() != 70 || readBuf.get() != 76 || readBuf.get() != 86 ||
|
||||
readBuf.get() != 1 || (readBuf.get() & 0x5) == 0 || readBuf.getInt() != 9)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static FLVMetadata parseMetadata(ByteBuffer bb) {
|
||||
if ("onMetaData".equals(readAMFData(bb, -1)))
|
||||
return new FLVMetadata((Map<String, Object>)readAMFData(bb, -1));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Object readAMFData(ByteBuffer input, int type) {
|
||||
Date date;
|
||||
if (type == -1)
|
||||
type = input.get() & 0xFF;
|
||||
switch (type) {
|
||||
case 0:
|
||||
return input.getDouble();
|
||||
case 1:
|
||||
return (input.get() == 1);
|
||||
case 2:
|
||||
return readAMFString(input);
|
||||
case 3:
|
||||
return readAMFObject(input);
|
||||
case 8:
|
||||
return readAMFEcmaArray(input);
|
||||
case 10:
|
||||
return readAMFStrictArray(input);
|
||||
case 11:
|
||||
date = new Date((long)input.getDouble());
|
||||
input.getShort();
|
||||
return date;
|
||||
case 13:
|
||||
return "UNDEFINED";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Object readAMFStrictArray(ByteBuffer input) {
|
||||
int count = input.getInt();
|
||||
Object[] result = new Object[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
result[i] = readAMFData(input, -1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String readAMFString(ByteBuffer input) {
|
||||
int size = input.getShort() & 0xFFFF;
|
||||
return Platform.stringFromCharset(NIOUtils.toArray(NIOUtils.read(input, size)), "UTF-8");
|
||||
}
|
||||
|
||||
private static Object readAMFObject(ByteBuffer input) {
|
||||
Map<String, Object> array = new HashMap<>();
|
||||
while (true) {
|
||||
String key = readAMFString(input);
|
||||
int dataType = input.get() & 0xFF;
|
||||
if (dataType == 9)
|
||||
break;
|
||||
array.put(key, readAMFData(input, dataType));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static Object readAMFEcmaArray(ByteBuffer input) {
|
||||
long size = (long)input.getInt();
|
||||
Map<String, Object> array = new HashMap<>();
|
||||
for (int i = 0; (long)i < size; i++) {
|
||||
String key = readAMFString(input);
|
||||
int dataType = input.get() & 0xFF;
|
||||
array.put(key, readAMFData(input, dataType));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static FLVTag.VideoTagHeader parseVideoTagHeader(ByteBuffer dup) {
|
||||
byte b0 = dup.get();
|
||||
int frameType = (b0 & 0xFF) >> 4;
|
||||
int codecId = b0 & 0xF;
|
||||
Codec codec = videoCodecMapping[codecId];
|
||||
if (codecId == 7) {
|
||||
byte avcPacketType = dup.get();
|
||||
int compOffset = dup.getShort() << 8 | dup.get() & 0xFF;
|
||||
return new FLVTag.AvcVideoTagHeader(codec, frameType, avcPacketType, compOffset);
|
||||
}
|
||||
return new FLVTag.VideoTagHeader(codec, frameType);
|
||||
}
|
||||
|
||||
public static FLVTag.TagHeader parseAudioTagHeader(ByteBuffer dup) {
|
||||
byte b = dup.get();
|
||||
int codecId = (b & 0xFF) >> 4;
|
||||
int sampleRate = sampleRates[b >> 2 & 0x3];
|
||||
if (codecId == 4 || codecId == 11)
|
||||
sampleRate = 16000;
|
||||
if (codecId == 5 || codecId == 14)
|
||||
sampleRate = 8000;
|
||||
int sampleSizeInBits = ((b & 0x2) == 0) ? 8 : 16;
|
||||
boolean signed = ((codecId != 3 && codecId != 0) || sampleSizeInBits == 16);
|
||||
int channelCount = 1 + (b & 0x1);
|
||||
if (codecId == 11)
|
||||
channelCount = 1;
|
||||
AudioFormat audioFormat = new AudioFormat(sampleRate, sampleSizeInBits, channelCount, signed,
|
||||
(codecId == 3) ? false : platformBigEndian);
|
||||
Codec codec = audioCodecMapping[codecId];
|
||||
if (codecId == 10) {
|
||||
byte packetType = dup.get();
|
||||
return new FLVTag.AacAudioTagHeader(codec, audioFormat, packetType);
|
||||
}
|
||||
return new FLVTag.AudioTagHeader(codec, audioFormat);
|
||||
}
|
||||
|
||||
public static int probe(ByteBuffer buf) {
|
||||
try {
|
||||
readHeader(buf);
|
||||
return 100;
|
||||
} catch (RuntimeException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
initialRead(this.ch);
|
||||
}
|
||||
|
||||
public void reposition() throws IOException {
|
||||
reset();
|
||||
if (!positionAtPacket(this.readBuf))
|
||||
throw new RuntimeException("Could not find at FLV tag start");
|
||||
}
|
||||
|
||||
public static boolean positionAtPacket(ByteBuffer readBuf) {
|
||||
ByteBuffer dup = readBuf.duplicate();
|
||||
int payloadSize = 0;
|
||||
NIOUtils.skip(dup, 5);
|
||||
while (dup.hasRemaining()) {
|
||||
payloadSize = (payloadSize & 0xFFFF) << 8 | dup.get() & 0xFF;
|
||||
int pointerPos = dup.position() + 7 + payloadSize;
|
||||
if (dup.position() >= 8 && pointerPos < dup.limit() - 4 && dup.getInt(pointerPos) - payloadSize == 11) {
|
||||
readBuf.position(dup.position() - 8);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean repositionFile() throws IOException {
|
||||
int payloadSize = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
while (this.readBuf.hasRemaining()) {
|
||||
payloadSize = (payloadSize & 0xFFFF) << 8 | this.readBuf.get() & 0xFF;
|
||||
int pointerPos = this.readBuf.position() + 7 + payloadSize;
|
||||
if (this.readBuf.position() >= 8 && pointerPos < this.readBuf.limit() - 4 &&
|
||||
this.readBuf.getInt(pointerPos) - payloadSize == 11) {
|
||||
this.readBuf.position(this.readBuf.position() - 8);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
initialRead(this.ch);
|
||||
if (!this.readBuf.hasRemaining())
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.AudioFormat;
|
||||
import org.jcodec.common.Codec;
|
||||
|
||||
public class FLVTag {
|
||||
private Type type;
|
||||
|
||||
private long position;
|
||||
|
||||
private TagHeader tagHeader;
|
||||
|
||||
private int pts;
|
||||
|
||||
private ByteBuffer data;
|
||||
|
||||
private boolean keyFrame;
|
||||
|
||||
private long frameNo;
|
||||
|
||||
private int streamId;
|
||||
|
||||
private int prevPacketSize;
|
||||
|
||||
public FLVTag(Type type, long position, TagHeader tagHeader, int pts, ByteBuffer data, boolean keyFrame, long frameNo, int streamId, int prevPacketSize) {
|
||||
this.type = type;
|
||||
this.position = position;
|
||||
this.tagHeader = tagHeader;
|
||||
this.pts = pts;
|
||||
this.data = data;
|
||||
this.keyFrame = keyFrame;
|
||||
this.frameNo = frameNo;
|
||||
this.streamId = streamId;
|
||||
this.prevPacketSize = prevPacketSize;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
VIDEO, AUDIO, SCRIPT;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public TagHeader getTagHeader() {
|
||||
return this.tagHeader;
|
||||
}
|
||||
|
||||
public int getPts() {
|
||||
return this.pts;
|
||||
}
|
||||
|
||||
public void setPts(int pts) {
|
||||
this.pts = pts;
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
return this.streamId;
|
||||
}
|
||||
|
||||
public void setStreamId(int streamId) {
|
||||
this.streamId = streamId;
|
||||
}
|
||||
|
||||
public int getPrevPacketSize() {
|
||||
return this.prevPacketSize;
|
||||
}
|
||||
|
||||
public void setPrevPacketSize(int prevPacketSize) {
|
||||
this.prevPacketSize = prevPacketSize;
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public double getPtsD() {
|
||||
return (double)this.pts / 1000.0D;
|
||||
}
|
||||
|
||||
public boolean isKeyFrame() {
|
||||
return this.keyFrame;
|
||||
}
|
||||
|
||||
public long getFrameNo() {
|
||||
return this.frameNo;
|
||||
}
|
||||
|
||||
public static class TagHeader {
|
||||
private Codec codec;
|
||||
|
||||
public TagHeader(Codec codec) {
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
public Codec getCodec() {
|
||||
return this.codec;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VideoTagHeader extends TagHeader {
|
||||
private int frameType;
|
||||
|
||||
public VideoTagHeader(Codec codec, int frameType) {
|
||||
super(codec);
|
||||
this.frameType = frameType;
|
||||
}
|
||||
|
||||
public int getFrameType() {
|
||||
return this.frameType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AvcVideoTagHeader extends VideoTagHeader {
|
||||
private int compOffset;
|
||||
|
||||
private byte avcPacketType;
|
||||
|
||||
public AvcVideoTagHeader(Codec codec, int frameType, byte avcPacketType, int compOffset) {
|
||||
super(codec, frameType);
|
||||
this.avcPacketType = avcPacketType;
|
||||
this.compOffset = compOffset;
|
||||
}
|
||||
|
||||
public int getCompOffset() {
|
||||
return this.compOffset;
|
||||
}
|
||||
|
||||
public byte getAvcPacketType() {
|
||||
return this.avcPacketType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AudioTagHeader extends TagHeader {
|
||||
private AudioFormat audioFormat;
|
||||
|
||||
public AudioTagHeader(Codec codec, AudioFormat audioFormat) {
|
||||
super(codec);
|
||||
this.audioFormat = audioFormat;
|
||||
}
|
||||
|
||||
public AudioFormat getAudioFormat() {
|
||||
return this.audioFormat;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AacAudioTagHeader extends AudioTagHeader {
|
||||
private int packetType;
|
||||
|
||||
public AacAudioTagHeader(Codec codec, AudioFormat audioFormat, int packetType) {
|
||||
super(codec, audioFormat);
|
||||
this.packetType = packetType;
|
||||
}
|
||||
|
||||
public int getPacketType() {
|
||||
return this.packetType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
|
||||
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
||||
import org.jcodec.codecs.h264.mp4.AvcCBox;
|
||||
import org.jcodec.common.AudioFormat;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.StringUtils;
|
||||
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.common.tools.MainUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class FLVTool {
|
||||
private static Map<String, PacketProcessorFactory> processors = new HashMap<>();
|
||||
|
||||
static {
|
||||
processors.put("clip", new ClipPacketProcessor.Factory());
|
||||
processors.put("fix_pts", new FixPtsProcessor.Factory());
|
||||
processors.put("info", new InfoPacketProcessor.Factory());
|
||||
processors.put("shift_pts", new ShiftPtsProcessor.Factory());
|
||||
}
|
||||
|
||||
private static final MainUtils.Flag FLAG_MAX_PACKETS = MainUtils.Flag.flag("max-packets", "m", "Maximum number of packets to process");
|
||||
|
||||
public static void main1(String[] args) throws IOException {
|
||||
if (args.length < 1) {
|
||||
printGenericHelp();
|
||||
return;
|
||||
}
|
||||
String command = args[0];
|
||||
PacketProcessorFactory processorFactory = processors.get(command);
|
||||
if (processorFactory == null) {
|
||||
System.err.println("Unknown command: " + command);
|
||||
printGenericHelp();
|
||||
return;
|
||||
}
|
||||
MainUtils.Cmd cmd = MainUtils.parseArguments(Platform.<String>copyOfRangeO(args, 1, args.length), processorFactory.getFlags());
|
||||
if (cmd.args.length < 1) {
|
||||
MainUtils.printHelpCmd(command, processorFactory.getFlags(), Arrays.asList("file in", "?file out"));
|
||||
return;
|
||||
}
|
||||
PacketProcessor processor = processorFactory.newPacketProcessor(cmd);
|
||||
int maxPackets = cmd.getIntegerFlagD(FLAG_MAX_PACKETS, Integer.valueOf(Integer.MAX_VALUE));
|
||||
SeekableByteChannel _in = null;
|
||||
SeekableByteChannel out = null;
|
||||
try {
|
||||
_in = NIOUtils.readableChannel(new File(cmd.getArg(0)));
|
||||
if (processor.hasOutput())
|
||||
out = NIOUtils.writableChannel(new File(cmd.getArg(1)));
|
||||
FLVReader demuxer = new FLVReader(_in);
|
||||
FLVWriter muxer = new FLVWriter(out);
|
||||
FLVTag pkt = null;
|
||||
for (int i = 0; i < maxPackets && (pkt = demuxer.readNextPacket()) != null &&
|
||||
processor.processPacket(pkt, muxer); i++);
|
||||
processor.finish(muxer);
|
||||
if (processor.hasOutput())
|
||||
muxer.finish();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(_in);
|
||||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printGenericHelp() {
|
||||
System.err.println("Syntax: <command> [flags] <file in> [file out]\nWhere command is: [" +
|
||||
StringUtils.joinS(processors.keySet().toArray(new String[0]), ", ") + "].");
|
||||
}
|
||||
|
||||
private static PacketProcessor getProcessor(String command, MainUtils.Cmd cmd) {
|
||||
PacketProcessorFactory factory = processors.get(command);
|
||||
if (factory == null)
|
||||
return null;
|
||||
return factory.newPacketProcessor(cmd);
|
||||
}
|
||||
|
||||
public static class ClipPacketProcessor implements PacketProcessor {
|
||||
private static FLVTag h264Config;
|
||||
|
||||
private boolean copying = false;
|
||||
|
||||
private Double from;
|
||||
|
||||
private Double to;
|
||||
|
||||
private static final MainUtils.Flag FLAG_FROM = MainUtils.Flag.flag("from", null, "From timestamp (in seconds, i.e 67.49)");
|
||||
|
||||
private static final MainUtils.Flag FLAG_TO = MainUtils.Flag.flag("to", null, "To timestamp");
|
||||
|
||||
public static class Factory implements FLVTool.PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.ClipPacketProcessor(flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_FROM), flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_TO));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLVTool.ClipPacketProcessor.FLAG_FROM, FLVTool.ClipPacketProcessor.FLAG_TO };
|
||||
}
|
||||
}
|
||||
|
||||
public ClipPacketProcessor(Double from, Double to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
|
||||
if (pkt.getType() == FLVTag.Type.VIDEO && pkt.getTagHeader().getCodec() == Codec.H264 && (
|
||||
(FLVTag.AvcVideoTagHeader)pkt.getTagHeader()).getAvcPacketType() == 0) {
|
||||
h264Config = pkt;
|
||||
System.out.println("GOT AVCC");
|
||||
}
|
||||
if (!this.copying && (this.from == null || pkt.getPtsD() > this.from) && pkt.getType() == FLVTag.Type.VIDEO && pkt.isKeyFrame() && h264Config != null) {
|
||||
System.out.println("Starting at packet: " + Platform.toJSON(pkt));
|
||||
this.copying = true;
|
||||
h264Config.setPts(pkt.getPts());
|
||||
writer.addPacket(h264Config);
|
||||
}
|
||||
if (this.to != null && pkt.getPtsD() >= this.to) {
|
||||
System.out.println("Stopping at packet: " + Platform.toJSON(pkt));
|
||||
return false;
|
||||
}
|
||||
if (this.copying)
|
||||
writer.addPacket(pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void finish(FLVWriter muxer) {}
|
||||
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.ClipPacketProcessor(flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_FROM), flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_TO));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLVTool.ClipPacketProcessor.FLAG_FROM, FLVTool.ClipPacketProcessor.FLAG_TO };
|
||||
}
|
||||
}
|
||||
|
||||
public static class FixPtsProcessor implements PacketProcessor {
|
||||
private double lastPtsAudio = 0.0D;
|
||||
|
||||
private double lastPtsVideo = 0.0D;
|
||||
|
||||
private List<FLVTag> tags;
|
||||
|
||||
private int audioTagsInQueue;
|
||||
|
||||
private int videoTagsInQueue;
|
||||
|
||||
private static final double CORRECTION_PACE = 0.33D;
|
||||
|
||||
public static class Factory implements FLVTool.PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.FixPtsProcessor();
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[0];
|
||||
}
|
||||
}
|
||||
|
||||
public FixPtsProcessor() {
|
||||
this.tags = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
|
||||
this.tags.add(pkt);
|
||||
if (pkt.getType() == FLVTag.Type.AUDIO) {
|
||||
this.audioTagsInQueue++;
|
||||
} else if (pkt.getType() == FLVTag.Type.VIDEO) {
|
||||
this.videoTagsInQueue++;
|
||||
}
|
||||
if (this.tags.size() < 600)
|
||||
return true;
|
||||
processOneTag(writer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processOneTag(FLVWriter writer) throws IOException {
|
||||
FLVTag tag = (FLVTag)this.tags.remove(0);
|
||||
if (tag.getType() == FLVTag.Type.AUDIO) {
|
||||
tag.setPts((int)Math.round(this.lastPtsAudio * 1000.0D));
|
||||
this.lastPtsAudio += audioFrameDuration((FLVTag.AudioTagHeader)tag.getTagHeader());
|
||||
this.audioTagsInQueue--;
|
||||
} else if (tag.getType() == FLVTag.Type.VIDEO) {
|
||||
double duration = 1024.0D * (double)this.audioTagsInQueue / (double)(48000 * this.videoTagsInQueue);
|
||||
tag.setPts((int)Math.round(this.lastPtsVideo * 1000.0D));
|
||||
this.lastPtsVideo += Math.min(1.33D * duration, Math.max(0.6699999999999999D * duration, duration +
|
||||
Math.min(1.0D, Math.abs(this.lastPtsAudio - this.lastPtsVideo)) * (this.lastPtsAudio - this.lastPtsVideo)));
|
||||
this.videoTagsInQueue--;
|
||||
System.out.println("" + this.lastPtsVideo + " - " + this.lastPtsVideo);
|
||||
} else {
|
||||
tag.setPts((int)Math.round(this.lastPtsVideo * 1000.0D));
|
||||
}
|
||||
writer.addPacket(tag);
|
||||
}
|
||||
|
||||
private double audioFrameDuration(FLVTag.AudioTagHeader audioTagHeader) {
|
||||
if (Codec.AAC == audioTagHeader.getCodec())
|
||||
return 1024.0D / (double)audioTagHeader.getAudioFormat().getSampleRate();
|
||||
if (Codec.MP3 == audioTagHeader.getCodec())
|
||||
return 1152.0D / (double)audioTagHeader.getAudioFormat().getSampleRate();
|
||||
throw new RuntimeException("Audio codec:" + String.valueOf(audioTagHeader.getCodec()) + " is not supported.");
|
||||
}
|
||||
|
||||
public void finish(FLVWriter muxer) throws IOException {
|
||||
while (this.tags.size() > 0)
|
||||
processOneTag(muxer);
|
||||
}
|
||||
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.FixPtsProcessor();
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static class InfoPacketProcessor implements PacketProcessor {
|
||||
private FLVTag prevVideoTag;
|
||||
|
||||
private FLVTag prevAudioTag;
|
||||
|
||||
private boolean checkOnly;
|
||||
|
||||
private FLVTag.Type streamType;
|
||||
|
||||
public static class Factory implements FLVTool.PacketProcessorFactory {
|
||||
private static final MainUtils.Flag FLAG_CHECK = MainUtils.Flag.flag("check", null, "Check sanity and report errors only, no packet dump will be generated.");
|
||||
|
||||
private static final MainUtils.Flag FLAG_STREAM = MainUtils.Flag.flag("stream", null, "Stream selector, can be one of: ['video', 'audio', 'script'].");
|
||||
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.InfoPacketProcessor(flags.getBooleanFlagD(FLAG_CHECK, Boolean.valueOf(false)),
|
||||
flags.<FLVTag.Type>getEnumFlagD(FLAG_STREAM, null, FLVTag.Type.class));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLAG_CHECK, FLAG_STREAM };
|
||||
}
|
||||
}
|
||||
|
||||
public InfoPacketProcessor(boolean checkOnly, FLVTag.Type streamType) {
|
||||
this.checkOnly = checkOnly;
|
||||
this.streamType = streamType;
|
||||
}
|
||||
|
||||
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
|
||||
if (this.checkOnly)
|
||||
return true;
|
||||
if (pkt.getType() == FLVTag.Type.VIDEO) {
|
||||
if (this.streamType == FLVTag.Type.VIDEO || this.streamType == null) {
|
||||
if (this.prevVideoTag != null)
|
||||
dumpOnePacket(this.prevVideoTag, pkt.getPts() - this.prevVideoTag.getPts());
|
||||
this.prevVideoTag = pkt;
|
||||
}
|
||||
} else if (pkt.getType() == FLVTag.Type.AUDIO) {
|
||||
if (this.streamType == FLVTag.Type.AUDIO || this.streamType == null) {
|
||||
if (this.prevAudioTag != null)
|
||||
dumpOnePacket(this.prevAudioTag, pkt.getPts() - this.prevAudioTag.getPts());
|
||||
this.prevAudioTag = pkt;
|
||||
}
|
||||
} else {
|
||||
dumpOnePacket(pkt, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dumpOnePacket(FLVTag pkt, int duration) {
|
||||
System.out.print("T=" + typeString(pkt.getType()) + "|PTS=" + pkt.getPts() + "|DUR=" + duration + "|" + (
|
||||
pkt.isKeyFrame() ? "K" : " ") + "|POS=" + pkt.getPosition());
|
||||
if (pkt.getTagHeader() instanceof FLVTag.VideoTagHeader) {
|
||||
FLVTag.VideoTagHeader vt = (FLVTag.VideoTagHeader)pkt.getTagHeader();
|
||||
System.out.print("|C=" + String.valueOf(vt.getCodec()) + "|FT=" + vt.getFrameType());
|
||||
if (vt instanceof FLVTag.AvcVideoTagHeader) {
|
||||
FLVTag.AvcVideoTagHeader avct = (FLVTag.AvcVideoTagHeader)vt;
|
||||
System.out.print("|PKT_TYPE=" + avct.getAvcPacketType() + "|COMP_OFF=" + avct.getCompOffset());
|
||||
if (avct.getAvcPacketType() == 0) {
|
||||
ByteBuffer frameData = pkt.getData().duplicate();
|
||||
FLVReader.parseVideoTagHeader(frameData);
|
||||
AvcCBox avcc = H264Utils.parseAVCCFromBuffer(frameData);
|
||||
for (SeqParameterSet sps : H264Utils.readSPSFromBufferList(avcc.getSpsList())) {
|
||||
System.out.println();
|
||||
System.out.print(" SPS[" + sps.getSeqParameterSetId() + "]:" + Platform.toJSON(sps));
|
||||
}
|
||||
for (PictureParameterSet pps : H264Utils.readPPSFromBufferList(avcc.getPpsList())) {
|
||||
System.out.println();
|
||||
System.out.print(" PPS[" + pps.getPicParameterSetId() + "]:" + Platform.toJSON(pps));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (pkt.getTagHeader() instanceof FLVTag.AudioTagHeader) {
|
||||
FLVTag.AudioTagHeader at = (FLVTag.AudioTagHeader)pkt.getTagHeader();
|
||||
AudioFormat format = at.getAudioFormat();
|
||||
System.out.print("|C=" + String.valueOf(at.getCodec()) + "|SR=" + format.getSampleRate() + "|SS=" + (
|
||||
format.getSampleSizeInBits() >> 3) + "|CH=" + format.getChannels());
|
||||
} else if (pkt.getType() == FLVTag.Type.SCRIPT) {
|
||||
FLVMetadata metadata = FLVReader.parseMetadata(pkt.getData().duplicate());
|
||||
if (metadata != null) {
|
||||
System.out.println();
|
||||
System.out.print(" Metadata:" + Platform.toJSON(metadata));
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private String typeString(FLVTag.Type type) {
|
||||
return type.toString().substring(0, 1);
|
||||
}
|
||||
|
||||
public void finish(FLVWriter muxer) throws IOException {
|
||||
if (this.prevVideoTag != null)
|
||||
dumpOnePacket(this.prevVideoTag, 0);
|
||||
if (this.prevAudioTag != null)
|
||||
dumpOnePacket(this.prevAudioTag, 0);
|
||||
}
|
||||
|
||||
public boolean hasOutput() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements PacketProcessorFactory {
|
||||
private static final MainUtils.Flag FLAG_CHECK = MainUtils.Flag.flag("check", null, "Check sanity and report errors only, no packet dump will be generated.");
|
||||
|
||||
private static final MainUtils.Flag FLAG_STREAM = MainUtils.Flag.flag("stream", null, "Stream selector, can be one of: ['video', 'audio', 'script'].");
|
||||
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.InfoPacketProcessor(flags.getBooleanFlagD(FLAG_CHECK, Boolean.valueOf(false)), flags.<FLVTag.Type>getEnumFlagD(FLAG_STREAM, null, FLVTag.Type.class));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLAG_CHECK, FLAG_STREAM };
|
||||
}
|
||||
}
|
||||
|
||||
public static class ShiftPtsProcessor implements PacketProcessor {
|
||||
private static final long WRAP_AROUND_VALUE = 2147483648L;
|
||||
|
||||
private static final int HALF_WRAP_AROUND_VALUE = 1073741824;
|
||||
|
||||
private static final MainUtils.Flag FLAG_TO = MainUtils.Flag.flag("to", null, "Shift first pts to this value, and all subsequent pts accordingly.");
|
||||
|
||||
private static final MainUtils.Flag FLAG_BY = MainUtils.Flag.flag("by", null, "Shift all pts by this value.");
|
||||
|
||||
private static final MainUtils.Flag FLAG_WRAP_AROUND = MainUtils.Flag.flag("wrap-around", null, "Expect wrap around of timestamps.");
|
||||
|
||||
private int shiftTo;
|
||||
|
||||
private Integer shiftBy;
|
||||
|
||||
private long ptsDelta;
|
||||
|
||||
private boolean firstPtsSeen;
|
||||
|
||||
private List<FLVTag> savedTags;
|
||||
|
||||
private boolean expectWrapAround;
|
||||
|
||||
private int prevPts;
|
||||
|
||||
public static class Factory implements FLVTool.PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.ShiftPtsProcessor(flags.getIntegerFlagD(FLVTool.ShiftPtsProcessor.FLAG_TO, Integer.valueOf(0)), flags.getIntegerFlag(FLVTool.ShiftPtsProcessor.FLAG_BY),
|
||||
flags.getBooleanFlagD(FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND, Boolean.valueOf(false)));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLVTool.ShiftPtsProcessor.FLAG_TO, FLVTool.ShiftPtsProcessor.FLAG_BY, FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND };
|
||||
}
|
||||
}
|
||||
|
||||
public ShiftPtsProcessor(int shiftTo, Integer shiftBy, boolean expectWrapAround) {
|
||||
this.savedTags = new LinkedList<>();
|
||||
this.shiftTo = shiftTo;
|
||||
this.shiftBy = shiftBy;
|
||||
this.expectWrapAround = true;
|
||||
}
|
||||
|
||||
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
|
||||
boolean avcPrivatePacket = (pkt.getType() == FLVTag.Type.VIDEO && ((FLVTag.VideoTagHeader)
|
||||
pkt.getTagHeader()).getCodec() == Codec.H264 && ((FLVTag.AvcVideoTagHeader)
|
||||
pkt.getTagHeader()).getAvcPacketType() == 0);
|
||||
boolean aacPrivatePacket = (pkt.getType() == FLVTag.Type.AUDIO && ((FLVTag.AudioTagHeader)
|
||||
pkt.getTagHeader()).getCodec() == Codec.AAC && ((FLVTag.AacAudioTagHeader)
|
||||
pkt.getTagHeader()).getPacketType() == 0);
|
||||
boolean validPkt = (pkt.getType() != FLVTag.Type.SCRIPT && !avcPrivatePacket && !aacPrivatePacket);
|
||||
if (this.expectWrapAround && validPkt && pkt.getPts() < this.prevPts && (long)this.prevPts -
|
||||
(long)pkt.getPts() > 1073741824L) {
|
||||
Logger.warn("Wrap around detected: " + this.prevPts + " -> " + pkt.getPts());
|
||||
if (pkt.getPts() < -1073741824) {
|
||||
this.ptsDelta += 4294967296L;
|
||||
} else if (pkt.getPts() >= 0) {
|
||||
this.ptsDelta += 2147483648L;
|
||||
}
|
||||
}
|
||||
if (validPkt)
|
||||
this.prevPts = pkt.getPts();
|
||||
if (this.firstPtsSeen) {
|
||||
writePacket(pkt, writer);
|
||||
} else if (!validPkt) {
|
||||
this.savedTags.add(pkt);
|
||||
} else {
|
||||
if (this.shiftBy != null) {
|
||||
this.ptsDelta = (long)this.shiftBy;
|
||||
if (this.ptsDelta + (long)pkt.getPts() < 0L)
|
||||
this.ptsDelta = (long)-pkt.getPts();
|
||||
} else {
|
||||
this.ptsDelta = (long)(this.shiftTo - pkt.getPts());
|
||||
}
|
||||
this.firstPtsSeen = true;
|
||||
emptySavedTags(writer);
|
||||
writePacket(pkt, writer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writePacket(FLVTag pkt, FLVWriter writer) throws IOException {
|
||||
long newPts = (long)pkt.getPts() + this.ptsDelta;
|
||||
if (newPts < 0L) {
|
||||
Logger.warn("Preventing negative pts for tag @" + pkt.getPosition());
|
||||
if (this.shiftBy != null) {
|
||||
newPts = 0L;
|
||||
} else {
|
||||
newPts = (long)this.shiftTo;
|
||||
}
|
||||
} else if (newPts >= 2147483648L) {
|
||||
Logger.warn("PTS wrap around @" + pkt.getPosition());
|
||||
newPts -= 2147483648L;
|
||||
this.ptsDelta = newPts - (long)pkt.getPts();
|
||||
}
|
||||
pkt.setPts((int)newPts);
|
||||
writer.addPacket(pkt);
|
||||
}
|
||||
|
||||
private void emptySavedTags(FLVWriter muxer) throws IOException {
|
||||
while (this.savedTags.size() > 0)
|
||||
writePacket((FLVTag)this.savedTags.remove(0), muxer);
|
||||
}
|
||||
|
||||
public void finish(FLVWriter muxer) throws IOException {
|
||||
emptySavedTags(muxer);
|
||||
}
|
||||
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements PacketProcessorFactory {
|
||||
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
|
||||
return new FLVTool.ShiftPtsProcessor(flags.getIntegerFlagD(FLVTool.ShiftPtsProcessor.FLAG_TO, Integer.valueOf(0)), flags.getIntegerFlag(FLVTool.ShiftPtsProcessor.FLAG_BY), flags.getBooleanFlagD(FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND, Boolean.valueOf(false)));
|
||||
}
|
||||
|
||||
public MainUtils.Flag[] getFlags() {
|
||||
return new MainUtils.Flag[] { FLVTool.ShiftPtsProcessor.FLAG_TO, FLVTool.ShiftPtsProcessor.FLAG_BY, FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND };
|
||||
}
|
||||
}
|
||||
|
||||
public static interface PacketProcessorFactory {
|
||||
FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd param1Cmd);
|
||||
|
||||
MainUtils.Flag[] getFlags();
|
||||
}
|
||||
|
||||
public static interface PacketProcessor {
|
||||
boolean processPacket(FLVTag param1FLVTag, FLVWriter param1FLVWriter) throws IOException;
|
||||
|
||||
boolean hasOutput();
|
||||
|
||||
void finish(FLVWriter param1FLVWriter) throws IOException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.LongArrayList;
|
||||
import org.jcodec.common.SeekableDemuxerTrack;
|
||||
import org.jcodec.common.TrackType;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.model.Packet;
|
||||
|
||||
public class FLVTrackDemuxer {
|
||||
private static final int MAX_CRAWL_DISTANCE_SEC = 10;
|
||||
|
||||
private FLVReader demuxer;
|
||||
|
||||
private FLVDemuxerTrack video;
|
||||
|
||||
private FLVDemuxerTrack audio;
|
||||
|
||||
private LinkedList<FLVTag> packets;
|
||||
|
||||
private SeekableByteChannel _in;
|
||||
|
||||
public static class FLVDemuxerTrack implements SeekableDemuxerTrack {
|
||||
private FLVTag.Type type;
|
||||
|
||||
private int curFrame;
|
||||
|
||||
private Codec codec;
|
||||
|
||||
private LongArrayList framePositions;
|
||||
|
||||
private byte[] codecPrivate;
|
||||
|
||||
private FLVTrackDemuxer demuxer;
|
||||
|
||||
public FLVDemuxerTrack(FLVTrackDemuxer demuxer, FLVTag.Type type) throws IOException {
|
||||
this.framePositions = LongArrayList.createLongArrayList();
|
||||
this.demuxer = demuxer;
|
||||
this.type = type;
|
||||
FLVTag frame = demuxer.nextFrameI(type, false);
|
||||
this.codec = frame.getTagHeader().getCodec();
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
FLVTag frame = this.demuxer.nextFrameI(this.type, true);
|
||||
this.framePositions.add(frame.getPosition());
|
||||
return toPacket(frame);
|
||||
}
|
||||
|
||||
public Packet prevFrame() throws IOException {
|
||||
FLVTag frame = this.demuxer.prevFrameI(this.type, true);
|
||||
return toPacket(frame);
|
||||
}
|
||||
|
||||
public Packet pickFrame() throws IOException {
|
||||
FLVTag frame = this.demuxer.nextFrameI(this.type, false);
|
||||
return toPacket(frame);
|
||||
}
|
||||
|
||||
private Packet toPacket(FLVTag frame) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
TrackType t = (this.type == FLVTag.Type.VIDEO) ? TrackType.VIDEO : TrackType.AUDIO;
|
||||
return new DemuxerTrackMeta(t, this.codec, 0.0D, null, 0, ByteBuffer.wrap(this.codecPrivate), null, null);
|
||||
}
|
||||
|
||||
public boolean gotoFrame(long i) throws IOException {
|
||||
if (i >= (long)this.framePositions.size())
|
||||
return false;
|
||||
this.demuxer.resetToPosition(this.framePositions.get((int)i));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean gotoSyncFrame(long i) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public long getCurFrame() {
|
||||
return (long)this.curFrame;
|
||||
}
|
||||
|
||||
public void seek(double second) throws IOException {
|
||||
this.demuxer.seekI(second);
|
||||
}
|
||||
}
|
||||
|
||||
public FLVTrackDemuxer(SeekableByteChannel _in) throws IOException {
|
||||
this.packets = new LinkedList<>();
|
||||
this._in = _in;
|
||||
_in.setPosition(0L);
|
||||
this.demuxer = new FLVReader(_in);
|
||||
this.video = new FLVDemuxerTrack(this, FLVTag.Type.VIDEO);
|
||||
this.audio = new FLVDemuxerTrack(this, FLVTag.Type.AUDIO);
|
||||
}
|
||||
|
||||
private void resetToPosition(long position) throws IOException {
|
||||
this._in.setPosition(position);
|
||||
this.demuxer.reset();
|
||||
this.packets.clear();
|
||||
}
|
||||
|
||||
private void seekI(double second) throws IOException {
|
||||
this.packets.clear();
|
||||
FLVTag base;
|
||||
while ((base = this.demuxer.readNextPacket()) != null && base.getPtsD() == 0.0D);
|
||||
if (base == null)
|
||||
return;
|
||||
this._in.setPosition(base.getPosition() + 1048576L);
|
||||
this.demuxer.reposition();
|
||||
FLVTag off = this.demuxer.readNextPacket();
|
||||
int byteRate = (int)((double)(off.getPosition() - base.getPosition()) / (off.getPtsD() - base.getPtsD()));
|
||||
long offset = base.getPosition() + (long)((second - base.getPtsD()) * (double)byteRate);
|
||||
this._in.setPosition(offset);
|
||||
this.demuxer.reposition();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
FLVTag pkt = this.demuxer.readNextPacket();
|
||||
double distance = second - pkt.getPtsD();
|
||||
if (distance > 0.0D && distance < 10.0D) {
|
||||
System.out.println("Crawling forward: " + distance);
|
||||
FLVTag testPkt;
|
||||
while ((testPkt = this.demuxer.readNextPacket()) != null && testPkt.getPtsD() < second);
|
||||
if (testPkt != null)
|
||||
this.packets.add(pkt);
|
||||
return;
|
||||
}
|
||||
if (distance < 0.0D && distance > -10.0D) {
|
||||
System.out.println("Overshoot by: " + -distance);
|
||||
this._in.setPosition(pkt.getPosition() + (long)((distance - 1.0D) * (double)byteRate));
|
||||
this.demuxer.reposition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FLVTag nextFrameI(FLVTag.Type type, boolean remove) throws IOException {
|
||||
for (Iterator<FLVTag> it = this.packets.iterator(); it.hasNext(); ) {
|
||||
FLVTag fLVTag = it.next();
|
||||
if (fLVTag.getType() == type) {
|
||||
if (remove)
|
||||
it.remove();
|
||||
return fLVTag;
|
||||
}
|
||||
}
|
||||
FLVTag pkt;
|
||||
while ((pkt = this.demuxer.readNextPacket()) != null && pkt.getType() != type)
|
||||
this.packets.add(pkt);
|
||||
if (!remove)
|
||||
this.packets.add(pkt);
|
||||
return pkt;
|
||||
}
|
||||
|
||||
private FLVTag prevFrameI(FLVTag.Type type, boolean remove) throws IOException {
|
||||
for (ListIterator<FLVTag> it = this.packets.listIterator(); it.hasPrevious(); ) {
|
||||
FLVTag fLVTag = it.previous();
|
||||
if (fLVTag.getType() == type) {
|
||||
if (remove)
|
||||
it.remove();
|
||||
return fLVTag;
|
||||
}
|
||||
}
|
||||
FLVTag pkt;
|
||||
while ((pkt = this.demuxer.readPrevPacket()) != null && pkt.getType() != type)
|
||||
this.packets.add(0, pkt);
|
||||
if (!remove)
|
||||
this.packets.add(0, pkt);
|
||||
return pkt;
|
||||
}
|
||||
|
||||
public DemuxerTrack[] getTracks() {
|
||||
return new DemuxerTrack[] { this.video, this.audio };
|
||||
}
|
||||
|
||||
public DemuxerTrack getVideoTrack() {
|
||||
return this.video;
|
||||
}
|
||||
|
||||
public DemuxerTrack getAudioTrack() {
|
||||
return this.video;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package org.jcodec.containers.flv;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
|
||||
public class FLVWriter {
|
||||
private static final int WRITE_BUFFER_SIZE = 1048576;
|
||||
|
||||
private int startOfLastPacket = 9;
|
||||
|
||||
private SeekableByteChannel out;
|
||||
|
||||
private ByteBuffer writeBuf;
|
||||
|
||||
public FLVWriter(SeekableByteChannel out) {
|
||||
this.out = out;
|
||||
this.writeBuf = ByteBuffer.allocate(1048576);
|
||||
writeHeader(this.writeBuf);
|
||||
}
|
||||
|
||||
public void addPacket(FLVTag pkt) throws IOException {
|
||||
if (!writePacket(this.writeBuf, pkt)) {
|
||||
this.writeBuf.flip();
|
||||
this.startOfLastPacket -= this.out.write(this.writeBuf);
|
||||
this.writeBuf.clear();
|
||||
if (!writePacket(this.writeBuf, pkt))
|
||||
throw new RuntimeException("Unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
this.writeBuf.flip();
|
||||
this.out.write(this.writeBuf);
|
||||
}
|
||||
|
||||
private boolean writePacket(ByteBuffer writeBuf, FLVTag pkt) {
|
||||
int pktType = (pkt.getType() == FLVTag.Type.VIDEO) ? 9 : ((pkt.getType() == FLVTag.Type.SCRIPT) ? 18 : 8);
|
||||
int dataLen = pkt.getData().remaining();
|
||||
if (writeBuf.remaining() < 15 + dataLen)
|
||||
return false;
|
||||
writeBuf.putInt(writeBuf.position() - this.startOfLastPacket);
|
||||
this.startOfLastPacket = writeBuf.position();
|
||||
writeBuf.put((byte)pktType);
|
||||
writeBuf.putShort((short)(dataLen >> 8));
|
||||
writeBuf.put((byte)(dataLen & 0xFF));
|
||||
writeBuf.putShort((short)(pkt.getPts() >> 8 & 0xFFFF));
|
||||
writeBuf.put((byte)(pkt.getPts() & 0xFF));
|
||||
writeBuf.put((byte)(pkt.getPts() >> 24 & 0xFF));
|
||||
writeBuf.putShort((short)0);
|
||||
writeBuf.put((byte)0);
|
||||
NIOUtils.write(writeBuf, pkt.getData().duplicate());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void writeHeader(ByteBuffer writeBuf) {
|
||||
writeBuf.put((byte)70);
|
||||
writeBuf.put((byte)76);
|
||||
writeBuf.put((byte)86);
|
||||
writeBuf.put((byte)1);
|
||||
writeBuf.put((byte)5);
|
||||
writeBuf.putInt(9);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package org.jcodec.containers.imgseq;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.Demuxer;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.TrackType;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.common.model.Packet;
|
||||
|
||||
public class ImageSequenceDemuxer implements Demuxer, DemuxerTrack {
|
||||
private static final int VIDEO_FPS = 25;
|
||||
|
||||
private String namePattern;
|
||||
|
||||
private int frameNo;
|
||||
|
||||
private Packet curFrame;
|
||||
|
||||
private Codec codec;
|
||||
|
||||
private int maxAvailableFrame;
|
||||
|
||||
private int maxFrames;
|
||||
|
||||
private String prevName;
|
||||
|
||||
private static final int MAX_MAX = 5184000;
|
||||
|
||||
public ImageSequenceDemuxer(String namePattern, int maxFrames) throws IOException {
|
||||
this.namePattern = namePattern;
|
||||
this.maxFrames = maxFrames;
|
||||
this.maxAvailableFrame = -1;
|
||||
this.curFrame = loadFrame();
|
||||
String lowerCase = namePattern.toLowerCase();
|
||||
if (lowerCase.endsWith(".png")) {
|
||||
this.codec = Codec.PNG;
|
||||
} else if (lowerCase.endsWith(".jpg") || lowerCase.endsWith(".jpeg")) {
|
||||
this.codec = Codec.JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {}
|
||||
|
||||
public List<? extends DemuxerTrack> getTracks() {
|
||||
ArrayList<DemuxerTrack> tracks = new ArrayList<>();
|
||||
tracks.add(this);
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<? extends DemuxerTrack> getVideoTracks() {
|
||||
return getTracks();
|
||||
}
|
||||
|
||||
public List<? extends DemuxerTrack> getAudioTracks() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
try {
|
||||
return this.curFrame;
|
||||
} finally {
|
||||
this.curFrame = loadFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private Packet loadFrame() throws IOException {
|
||||
if (this.frameNo > this.maxFrames)
|
||||
return null;
|
||||
File file = null;
|
||||
do {
|
||||
String name = String.format(this.namePattern, this.frameNo);
|
||||
if (name.equals(this.prevName))
|
||||
return null;
|
||||
this.prevName = name;
|
||||
file = new File(name);
|
||||
if (file.exists() || this.frameNo > 0)
|
||||
break;
|
||||
this.frameNo++;
|
||||
} while (this.frameNo < 2);
|
||||
if (file == null || !file.exists())
|
||||
return null;
|
||||
Packet ret = new Packet(NIOUtils.fetchFromFile(file), (long)this.frameNo, 25, 1L, (long)this.frameNo, Packet.FrameType.KEY, null, this.frameNo);
|
||||
this.frameNo++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public int getMaxAvailableFrame() {
|
||||
if (this.maxAvailableFrame == -1) {
|
||||
int firstPoint = 0;
|
||||
for (int i = 5184000; i > 0; i /= 2) {
|
||||
if (new File(String.format(this.namePattern, i)).exists()) {
|
||||
firstPoint = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int pos = firstPoint;
|
||||
for (int interv = firstPoint / 2; interv > 1; interv /= 2) {
|
||||
if (new File(String.format(this.namePattern, pos + interv)).exists())
|
||||
pos += interv;
|
||||
}
|
||||
this.maxAvailableFrame = pos;
|
||||
Logger.info("Max frame found: " + this.maxAvailableFrame);
|
||||
}
|
||||
return Math.min(this.maxAvailableFrame, this.maxFrames);
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
int durationFrames = getMaxAvailableFrame();
|
||||
return new DemuxerTrackMeta(TrackType.VIDEO, this.codec, (double)((durationFrames + 1) * 25), null, durationFrames + 1, null, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package org.jcodec.containers.imgseq;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jcodec.common.AudioCodecMeta;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.Muxer;
|
||||
import org.jcodec.common.MuxerTrack;
|
||||
import org.jcodec.common.VideoCodecMeta;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.common.tools.MainUtils;
|
||||
|
||||
public class ImageSequenceMuxer implements Muxer, MuxerTrack {
|
||||
private String fileNamePattern;
|
||||
|
||||
private int frameNo;
|
||||
|
||||
public ImageSequenceMuxer(String fileNamePattern) {
|
||||
this.fileNamePattern = fileNamePattern;
|
||||
}
|
||||
|
||||
public void addFrame(Packet packet) throws IOException {
|
||||
NIOUtils.writeTo(packet.getData(), MainUtils.tildeExpand(String.format(this.fileNamePattern, this.frameNo++)));
|
||||
}
|
||||
|
||||
public MuxerTrack addVideoTrack(Codec codec, VideoCodecMeta meta) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public MuxerTrack addAudioTrack(Codec codec, AudioCodecMeta meta) {
|
||||
Logger.warn("Audio is not supported for image sequence muxer.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public void finish() throws IOException {}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package org.jcodec.containers.mkv;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlUint;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class CuesFactory {
|
||||
List<CuePointMock> a;
|
||||
|
||||
private final long offsetBase;
|
||||
|
||||
private long currentDataOffset = 0L;
|
||||
|
||||
private long videoTrackNr;
|
||||
|
||||
public CuesFactory(long offset, long videoTrack) {
|
||||
this.a = new ArrayList<>();
|
||||
this.offsetBase = offset;
|
||||
this.videoTrackNr = videoTrack;
|
||||
this.currentDataOffset += this.offsetBase;
|
||||
}
|
||||
|
||||
public void addFixedSize(CuePointMock z) {
|
||||
z.elementOffset = this.currentDataOffset;
|
||||
z.cueClusterPositionSize = 8;
|
||||
this.currentDataOffset += z.size;
|
||||
this.a.add(z);
|
||||
}
|
||||
|
||||
public void add(CuePointMock z) {
|
||||
z.elementOffset = this.currentDataOffset;
|
||||
z.cueClusterPositionSize = EbmlUint.calculatePayloadSize(z.elementOffset);
|
||||
this.currentDataOffset += z.size;
|
||||
this.a.add(z);
|
||||
}
|
||||
|
||||
public EbmlMaster createCues() {
|
||||
int estimatedSize = computeCuesSize();
|
||||
EbmlMaster cues = MKVType.<EbmlMaster>createByType(MKVType.Cues);
|
||||
for (CuePointMock cpm : this.a) {
|
||||
EbmlMaster cuePoint = MKVType.<EbmlMaster>createByType(MKVType.CuePoint);
|
||||
EbmlUint cueTime = MKVType.<EbmlUint>createByType(MKVType.CueTime);
|
||||
cueTime.setUint(cpm.timecode);
|
||||
cuePoint.add(cueTime);
|
||||
EbmlMaster cueTrackPositions = MKVType.<EbmlMaster>createByType(MKVType.CueTrackPositions);
|
||||
EbmlUint cueTrack = MKVType.<EbmlUint>createByType(MKVType.CueTrack);
|
||||
cueTrack.setUint(this.videoTrackNr);
|
||||
cueTrackPositions.add(cueTrack);
|
||||
EbmlUint cueClusterPosition = MKVType.<EbmlUint>createByType(MKVType.CueClusterPosition);
|
||||
cueClusterPosition.setUint(cpm.elementOffset + (long)estimatedSize);
|
||||
if (cueClusterPosition.data.limit() != cpm.cueClusterPositionSize)
|
||||
System.err.println("estimated size of CueClusterPosition differs from the one actually used. ElementId: " + EbmlUtil.toHexString(cpm.id) + " " + cueClusterPosition.getData().limit() + " vs " + cpm.cueClusterPositionSize);
|
||||
cueTrackPositions.add(cueClusterPosition);
|
||||
cuePoint.add(cueTrackPositions);
|
||||
cues.add(cuePoint);
|
||||
}
|
||||
return cues;
|
||||
}
|
||||
|
||||
public int computeCuesSize() {
|
||||
int cuesSize = estimateSize();
|
||||
boolean reindex = false;
|
||||
while (true) {
|
||||
reindex = false;
|
||||
for (CuePointMock z : this.a) {
|
||||
int minByteSize = EbmlUint.calculatePayloadSize(z.elementOffset + (long)cuesSize);
|
||||
if (minByteSize > z.cueClusterPositionSize) {
|
||||
System.out.println("" + minByteSize + ">" + minByteSize);
|
||||
System.err.println("Size " + cuesSize + " seems too small for element " + EbmlUtil.toHexString(z.id) + " increasing size by one.");
|
||||
z.cueClusterPositionSize++;
|
||||
cuesSize++;
|
||||
reindex = true;
|
||||
break;
|
||||
}
|
||||
if (minByteSize < z.cueClusterPositionSize)
|
||||
throw new RuntimeException("Downsizing the index is not well thought through");
|
||||
}
|
||||
if (!reindex)
|
||||
return cuesSize;
|
||||
}
|
||||
}
|
||||
|
||||
public int estimateFixedSize(int numberOfClusters) {
|
||||
int s = 34 * numberOfClusters;
|
||||
s += MKVType.Cues.id.length + EbmlUtil.ebmlLength((long)s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int s = 0;
|
||||
for (CuePointMock cpm : this.a)
|
||||
s += estimateCuePointSize(EbmlUint.calculatePayloadSize(cpm.timecode), EbmlUint.calculatePayloadSize(this.videoTrackNr), EbmlUint.calculatePayloadSize(cpm.elementOffset));
|
||||
s += MKVType.Cues.id.length + EbmlUtil.ebmlLength((long)s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static int estimateCuePointSize(int timecodeSizeInBytes, int trackNrSizeInBytes, int clusterPositionSizeInBytes) {
|
||||
int cueTimeSize = MKVType.CueTime.id.length + EbmlUtil.ebmlLength((long)timecodeSizeInBytes) + timecodeSizeInBytes;
|
||||
int cueTrackPositionSize = MKVType.CueTrack.id.length + EbmlUtil.ebmlLength((long)trackNrSizeInBytes) + trackNrSizeInBytes + MKVType.CueClusterPosition.id.length +
|
||||
EbmlUtil.ebmlLength((long)clusterPositionSizeInBytes) + clusterPositionSizeInBytes;
|
||||
cueTrackPositionSize += MKVType.CueTrackPositions.id.length + EbmlUtil.ebmlLength((long)cueTrackPositionSize);
|
||||
int cuePointSize = MKVType.CuePoint.id.length + EbmlUtil.ebmlLength((long)(cueTimeSize + cueTrackPositionSize)) + cueTimeSize + cueTrackPositionSize;
|
||||
return cuePointSize;
|
||||
}
|
||||
|
||||
public static class CuePointMock {
|
||||
public int cueClusterPositionSize;
|
||||
|
||||
public long elementOffset;
|
||||
|
||||
private long timecode;
|
||||
|
||||
private long size;
|
||||
|
||||
private byte[] id;
|
||||
|
||||
public static CuePointMock make(EbmlMaster c) {
|
||||
MKVType[] path = { MKVType.Cluster, MKVType.Timecode };
|
||||
EbmlUint tc = (EbmlUint)MKVType.findFirst(c, path);
|
||||
return doMake(c.id, tc.getUint(), c.size());
|
||||
}
|
||||
|
||||
public static CuePointMock doMake(byte[] id, long timecode, long size) {
|
||||
CuePointMock mock = new CuePointMock();
|
||||
mock.id = id;
|
||||
mock.timecode = timecode;
|
||||
mock.size = size;
|
||||
return mock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package org.jcodec.containers.mkv;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBase;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBin;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlVoid;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class MKVParser {
|
||||
private SeekableByteChannel channel;
|
||||
|
||||
private LinkedList<EbmlMaster> trace;
|
||||
|
||||
public MKVParser(SeekableByteChannel channel) {
|
||||
this.channel = channel;
|
||||
this.trace = new LinkedList<>();
|
||||
}
|
||||
|
||||
public List<EbmlMaster> parse() throws IOException {
|
||||
List<EbmlMaster> tree = new ArrayList<>();
|
||||
EbmlBase e = null;
|
||||
while ((e = nextElement()) != null) {
|
||||
if (!isKnownType(e.id))
|
||||
System.err.println("Unspecified header: " + EbmlUtil.toHexString(e.id) + " at " + e.offset);
|
||||
while (!possibleChild(this.trace.peekFirst(), e))
|
||||
closeElem(this.trace.removeFirst(), tree);
|
||||
openElem(e);
|
||||
if (e instanceof EbmlMaster) {
|
||||
this.trace.push((EbmlMaster)e);
|
||||
continue;
|
||||
}
|
||||
if (e instanceof EbmlBin) {
|
||||
EbmlBin bin = (EbmlBin)e;
|
||||
EbmlMaster traceTop = this.trace.peekFirst();
|
||||
if (traceTop.dataOffset + (long)traceTop.dataLen < e.dataOffset + (long)e.dataLen) {
|
||||
this.channel.setPosition(traceTop.dataOffset + (long)traceTop.dataLen);
|
||||
} else {
|
||||
try {
|
||||
bin.readChannel(this.channel);
|
||||
} catch (OutOfMemoryError oome) {
|
||||
throw new RuntimeException(String.valueOf(e.type) + " 0x" + String.valueOf(e.type) + " size: " + EbmlUtil.toHexString(bin.id) + " offset: 0x" + Long.toHexString((long)bin.dataLen), oome);
|
||||
}
|
||||
}
|
||||
this.trace.peekFirst().add(e);
|
||||
continue;
|
||||
}
|
||||
if (e instanceof EbmlVoid) {
|
||||
((EbmlVoid)e).skip(this.channel);
|
||||
continue;
|
||||
}
|
||||
throw new RuntimeException("Currently there are no elements that are neither Master nor Binary, should never actually get here");
|
||||
}
|
||||
while (this.trace.peekFirst() != null)
|
||||
closeElem(this.trace.removeFirst(), tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
private boolean possibleChild(EbmlMaster parent, EbmlBase child) {
|
||||
if (parent != null && MKVType.Cluster.equals(parent.type) && child != null && !MKVType.Cluster.equals(child.type) && !MKVType.Info.equals(child.type) && !MKVType.SeekHead.equals(child.type) && !MKVType.Tracks.equals(child.type) &&
|
||||
!MKVType.Cues.equals(child.type) && !MKVType.Attachments.equals(child.type) && !MKVType.Tags.equals(child.type) && !MKVType.Chapters.equals(child.type))
|
||||
return true;
|
||||
return MKVType.possibleChild(parent, child);
|
||||
}
|
||||
|
||||
private void openElem(EbmlBase e) {}
|
||||
|
||||
private void closeElem(EbmlMaster e, List<EbmlMaster> tree) {
|
||||
if (this.trace.peekFirst() == null) {
|
||||
tree.add(e);
|
||||
} else {
|
||||
this.trace.peekFirst().add(e);
|
||||
}
|
||||
}
|
||||
|
||||
private EbmlBase nextElement() throws IOException {
|
||||
long offset = this.channel.position();
|
||||
if (offset >= this.channel.size())
|
||||
return null;
|
||||
byte[] typeId = readEbmlId(this.channel);
|
||||
while (typeId == null && !isKnownType(typeId) && offset < this.channel.size()) {
|
||||
offset++;
|
||||
this.channel.setPosition(offset);
|
||||
typeId = readEbmlId(this.channel);
|
||||
}
|
||||
long dataLen = readEbmlInt(this.channel);
|
||||
EbmlBase elem = MKVType.createById(typeId, offset);
|
||||
elem.offset = offset;
|
||||
elem.typeSizeLength = (int)(this.channel.position() - offset);
|
||||
elem.dataOffset = this.channel.position();
|
||||
elem.dataLen = (int)dataLen;
|
||||
return elem;
|
||||
}
|
||||
|
||||
public boolean isKnownType(byte[] b) {
|
||||
if (!this.trace.isEmpty() && MKVType.Cluster.equals(((EbmlMaster)this.trace.peekFirst()).type))
|
||||
return true;
|
||||
return MKVType.isSpecifiedHeader(b);
|
||||
}
|
||||
|
||||
public static byte[] readEbmlId(SeekableByteChannel source) throws IOException {
|
||||
if (source.position() == source.size())
|
||||
return null;
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8);
|
||||
buffer.limit(1);
|
||||
source.read(buffer);
|
||||
buffer.flip();
|
||||
byte firstByte = buffer.get();
|
||||
int numBytes = EbmlUtil.computeLength(firstByte);
|
||||
if (numBytes == 0)
|
||||
return null;
|
||||
if (numBytes > 1) {
|
||||
buffer.limit(numBytes);
|
||||
source.read(buffer);
|
||||
}
|
||||
buffer.flip();
|
||||
ByteBuffer val = ByteBuffer.allocate(buffer.remaining());
|
||||
val.put(buffer);
|
||||
return val.array();
|
||||
}
|
||||
|
||||
public static long readEbmlInt(SeekableByteChannel source) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8);
|
||||
buffer.limit(1);
|
||||
source.read(buffer);
|
||||
buffer.flip();
|
||||
byte firstByte = buffer.get();
|
||||
int length = EbmlUtil.computeLength(firstByte);
|
||||
if (length == 0)
|
||||
throw new RuntimeException("Invalid ebml integer size.");
|
||||
buffer.limit(length);
|
||||
source.read(buffer);
|
||||
buffer.position(1);
|
||||
long value = (long)(firstByte & 255 >>> length);
|
||||
length--;
|
||||
while (length > 0) {
|
||||
value = value << 8L | (long)(buffer.get() & 0xFF);
|
||||
length--;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
package org.jcodec.containers.mkv;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBase;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBin;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlDate;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlFloat;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlSint;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlString;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlUint;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlVoid;
|
||||
import org.jcodec.containers.mkv.boxes.MkvBlock;
|
||||
import org.jcodec.containers.mkv.boxes.MkvSegment;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public final class MKVType {
|
||||
private static final List<MKVType> _values = new ArrayList<>();
|
||||
|
||||
public static final MKVType Void = new MKVType("Void", new byte[] { (byte)-20 }, EbmlVoid.class);
|
||||
|
||||
public static final MKVType CRC32 = new MKVType("CRC32", new byte[] { (byte)-65 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType EBML = new MKVType("EBML", new byte[] { (byte)26, (byte)69, (byte)-33, (byte)-93 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType EBMLVersion = new MKVType("EBMLVersion", new byte[] { (byte)66, (byte)-122 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EBMLReadVersion = new MKVType("EBMLReadVersion", new byte[] { (byte)66, (byte)-9 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EBMLMaxIDLength = new MKVType("EBMLMaxIDLength", new byte[] { (byte)66, (byte)-14 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EBMLMaxSizeLength = new MKVType("EBMLMaxSizeLength", new byte[] { (byte)66, (byte)-13 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DocType = new MKVType("DocType", new byte[] { (byte)66, (byte)-126 }, EbmlString.class);
|
||||
|
||||
public static final MKVType DocTypeVersion = new MKVType("DocTypeVersion", new byte[] { (byte)66, (byte)-121 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DocTypeReadVersion = new MKVType("DocTypeReadVersion", new byte[] { (byte)66, (byte)-123 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Segment = new MKVType("Segment", MkvSegment.SEGMENT_ID, MkvSegment.class);
|
||||
|
||||
public static final MKVType SeekHead = new MKVType("SeekHead", new byte[] { (byte)17, (byte)77, (byte)-101, (byte)116 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType Seek = new MKVType("Seek", new byte[] { (byte)77, (byte)-69 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType SeekID = new MKVType("SeekID", new byte[] { (byte)83, (byte)-85 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType SeekPosition = new MKVType("SeekPosition", new byte[] { (byte)83, (byte)-84 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Info = new MKVType("Info", new byte[] { (byte)21, (byte)73, (byte)-87, (byte)102 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType SegmentUID = new MKVType("SegmentUID", new byte[] { (byte)115, (byte)-92 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType SegmentFilename = new MKVType("SegmentFilename", new byte[] { (byte)115, (byte)-124 }, EbmlString.class);
|
||||
|
||||
public static final MKVType PrevUID = new MKVType("PrevUID", new byte[] { (byte)60, (byte)-71, (byte)35 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType PrevFilename = new MKVType("PrevFilename", new byte[] { (byte)60, (byte)-125, (byte)-85 }, EbmlString.class);
|
||||
|
||||
public static final MKVType NextUID = new MKVType("NextUID", new byte[] { (byte)62, (byte)-71, (byte)35 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType NextFilenam = new MKVType("NextFilenam", new byte[] { (byte)62, (byte)-125, (byte)-69 }, EbmlString.class);
|
||||
|
||||
public static final MKVType SegmentFamily = new MKVType("SegmentFamily", new byte[] { (byte)68, (byte)68 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ChapterTranslate = new MKVType("ChapterTranslate", new byte[] { (byte)105, (byte)36 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapterTranslateEditionUID = new MKVType("ChapterTranslateEditionUID", new byte[] { (byte)105, (byte)-4 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterTranslateCodec = new MKVType("ChapterTranslateCodec", new byte[] { (byte)105, (byte)-65 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterTranslateID = new MKVType("ChapterTranslateID", new byte[] { (byte)105, (byte)-91 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType TimecodeScale = new MKVType("TimecodeScale", new byte[] { (byte)42, (byte)-41, (byte)-79 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Duration = new MKVType("Duration", new byte[] { (byte)68, (byte)-119 }, EbmlFloat.class);
|
||||
|
||||
public static final MKVType DateUTC = new MKVType("DateUTC", new byte[] { (byte)68, (byte)97 }, EbmlDate.class);
|
||||
|
||||
public static final MKVType Title = new MKVType("Title", new byte[] { (byte)123, (byte)-87 }, EbmlString.class);
|
||||
|
||||
public static final MKVType MuxingApp = new MKVType("MuxingApp", new byte[] { (byte)77, (byte)Byte.MIN_VALUE }, EbmlString.class);
|
||||
|
||||
public static final MKVType WritingApp = new MKVType("WritingApp", new byte[] { (byte)87, (byte)65 }, EbmlString.class);
|
||||
|
||||
public static final MKVType Cluster = new MKVType("Cluster", EbmlMaster.CLUSTER_ID, EbmlMaster.class);
|
||||
|
||||
public static final MKVType Timecode = new MKVType("Timecode", new byte[] { (byte)-25 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType SilentTracks = new MKVType("SilentTracks", new byte[] { (byte)88, (byte)84 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType SilentTrackNumber = new MKVType("SilentTrackNumber", new byte[] { (byte)88, (byte)-41 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Position = new MKVType("Position", new byte[] { (byte)-89 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PrevSize = new MKVType("PrevSize", new byte[] { (byte)-85 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType SimpleBlock = new MKVType("SimpleBlock", MkvBlock.SIMPLEBLOCK_ID, MkvBlock.class);
|
||||
|
||||
public static final MKVType BlockGroup = new MKVType("BlockGroup", new byte[] { (byte)-96 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType Block = new MKVType("Block", MkvBlock.BLOCK_ID, MkvBlock.class);
|
||||
|
||||
public static final MKVType BlockAdditions = new MKVType("BlockAdditions", new byte[] { (byte)117, (byte)-95 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType BlockMore = new MKVType("BlockMore", new byte[] { (byte)-90 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType BlockAddID = new MKVType("BlockAddID", new byte[] { (byte)-18 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType BlockAdditional = new MKVType("BlockAdditional", new byte[] { (byte)-91 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType BlockDuration = new MKVType("BlockDuration", new byte[] { (byte)-101 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ReferencePriority = new MKVType("ReferencePriority", new byte[] { (byte)-6 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ReferenceBlock = new MKVType("ReferenceBlock", new byte[] { (byte)-5 }, EbmlSint.class);
|
||||
|
||||
public static final MKVType CodecState = new MKVType("CodecState", new byte[] { (byte)-92 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType Slices = new MKVType("Slices", new byte[] { (byte)-114 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TimeSlice = new MKVType("TimeSlice", new byte[] { (byte)-24 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType LaceNumber = new MKVType("LaceNumber", new byte[] { (byte)-52 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Tracks = new MKVType("Tracks", new byte[] { (byte)22, (byte)84, (byte)-82, (byte)107 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackEntry = new MKVType("TrackEntry", new byte[] { (byte)-82 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackNumber = new MKVType("TrackNumber", new byte[] { (byte)-41 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackUID = new MKVType("TrackUID", new byte[] { (byte)115, (byte)-59 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackType = new MKVType("TrackType", new byte[] { (byte)-125 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType FlagEnabled = new MKVType("FlagEnabled", new byte[] { (byte)-71 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType FlagDefault = new MKVType("FlagDefault", new byte[] { (byte)-120 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType FlagForced = new MKVType("FlagForced", new byte[] { (byte)85, (byte)-86 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType FlagLacing = new MKVType("FlagLacing", new byte[] { (byte)-100 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType MinCache = new MKVType("MinCache", new byte[] { (byte)109, (byte)-25 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType MaxCache = new MKVType("MaxCache", new byte[] { (byte)109, (byte)-8 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DefaultDuration = new MKVType("DefaultDuration", new byte[] { (byte)35, (byte)-29, (byte)-125 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType MaxBlockAdditionID = new MKVType("MaxBlockAdditionID", new byte[] { (byte)85, (byte)-18 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Name = new MKVType("Name", new byte[] { (byte)83, (byte)110 }, EbmlString.class);
|
||||
|
||||
public static final MKVType Language = new MKVType("Language", new byte[] { (byte)34, (byte)-75, (byte)-100 }, EbmlString.class);
|
||||
|
||||
public static final MKVType CodecID = new MKVType("CodecID", new byte[] { (byte)-122 }, EbmlString.class);
|
||||
|
||||
public static final MKVType CodecPrivate = new MKVType("CodecPrivate", new byte[] { (byte)99, (byte)-94 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType CodecName = new MKVType("CodecName", new byte[] { (byte)37, (byte)-122, (byte)-120 }, EbmlString.class);
|
||||
|
||||
public static final MKVType AttachmentLink = new MKVType("AttachmentLink", new byte[] { (byte)116, (byte)70 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CodecDecodeAll = new MKVType("CodecDecodeAll", new byte[] { (byte)-86 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackOverlay = new MKVType("TrackOverlay", new byte[] { (byte)111, (byte)-85 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackTranslate = new MKVType("TrackTranslate", new byte[] { (byte)102, (byte)36 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackTranslateEditionUID = new MKVType("TrackTranslateEditionUID", new byte[] { (byte)102, (byte)-4 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackTranslateCodec = new MKVType("TrackTranslateCodec", new byte[] { (byte)102, (byte)-65 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackTranslateTrackID = new MKVType("TrackTranslateTrackID", new byte[] { (byte)102, (byte)-91 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType Video = new MKVType("Video", new byte[] { (byte)-32 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType FlagInterlaced = new MKVType("FlagInterlaced", new byte[] { (byte)-102 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType StereoMode = new MKVType("StereoMode", new byte[] { (byte)83, (byte)-72 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType AlphaMode = new MKVType("AlphaMode", new byte[] { (byte)83, (byte)-64 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelWidth = new MKVType("PixelWidth", new byte[] { (byte)-80 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelHeight = new MKVType("PixelHeight", new byte[] { (byte)-70 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelCropBottom = new MKVType("PixelCropBottom", new byte[] { (byte)84, (byte)-86 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelCropTop = new MKVType("PixelCropTop", new byte[] { (byte)84, (byte)-69 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelCropLeft = new MKVType("PixelCropLeft", new byte[] { (byte)84, (byte)-52 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType PixelCropRight = new MKVType("PixelCropRight", new byte[] { (byte)84, (byte)-35 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DisplayWidth = new MKVType("DisplayWidth", new byte[] { (byte)84, (byte)-80 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DisplayHeight = new MKVType("DisplayHeight", new byte[] { (byte)84, (byte)-70 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType DisplayUnit = new MKVType("DisplayUnit", new byte[] { (byte)84, (byte)-78 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType AspectRatioType = new MKVType("AspectRatioType", new byte[] { (byte)84, (byte)-77 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ColourSpace = new MKVType("ColourSpace", new byte[] { (byte)46, (byte)-75, (byte)36 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType Audio = new MKVType("Audio", new byte[] { (byte)-31 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType SamplingFrequency = new MKVType("SamplingFrequency", new byte[] { (byte)-75 }, EbmlFloat.class);
|
||||
|
||||
public static final MKVType OutputSamplingFrequency = new MKVType("OutputSamplingFrequency", new byte[] { (byte)120, (byte)-75 }, EbmlFloat.class);
|
||||
|
||||
public static final MKVType Channels = new MKVType("Channels", new byte[] { (byte)-97 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType BitDepth = new MKVType("BitDepth", new byte[] { (byte)98, (byte)100 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackOperation = new MKVType("TrackOperation", new byte[] { (byte)-30 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackCombinePlanes = new MKVType("TrackCombinePlanes", new byte[] { (byte)-29 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackPlane = new MKVType("TrackPlane", new byte[] { (byte)-28 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackPlaneUID = new MKVType("TrackPlaneUID", new byte[] { (byte)-27 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackPlaneType = new MKVType("TrackPlaneType", new byte[] { (byte)-26 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TrackJoinBlocks = new MKVType("TrackJoinBlocks", new byte[] { (byte)-23 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TrackJoinUID = new MKVType("TrackJoinUID", new byte[] { (byte)-19 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentEncodings = new MKVType("ContentEncodings", new byte[] { (byte)109, (byte)Byte.MIN_VALUE }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ContentEncoding = new MKVType("ContentEncoding", new byte[] { (byte)98, (byte)64 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ContentEncodingOrder = new MKVType("ContentEncodingOrder", new byte[] { (byte)80, (byte)49 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentEncodingScope = new MKVType("ContentEncodingScope", new byte[] { (byte)80, (byte)50 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentEncodingType = new MKVType("ContentEncodingType", new byte[] { (byte)80, (byte)51 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentCompression = new MKVType("ContentCompression", new byte[] { (byte)80, (byte)52 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ContentCompAlgo = new MKVType("ContentCompAlgo", new byte[] { (byte)66, (byte)84 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentCompSettings = new MKVType("ContentCompSettings", new byte[] { (byte)66, (byte)85 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ContentEncryption = new MKVType("ContentEncryption", new byte[] { (byte)80, (byte)53 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ContentEncAlgo = new MKVType("ContentEncAlgo", new byte[] { (byte)71, (byte)-31 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentEncKeyID = new MKVType("ContentEncKeyID", new byte[] { (byte)71, (byte)-30 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ContentSignature = new MKVType("ContentSignature", new byte[] { (byte)71, (byte)-29 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ContentSigKeyID = new MKVType("ContentSigKeyID", new byte[] { (byte)71, (byte)-28 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ContentSigAlgo = new MKVType("ContentSigAlgo", new byte[] { (byte)71, (byte)-27 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ContentSigHashAlgo = new MKVType("ContentSigHashAlgo", new byte[] { (byte)71, (byte)-26 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Cues = new MKVType("Cues", new byte[] { (byte)28, (byte)83, (byte)-69, (byte)107 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType CuePoint = new MKVType("CuePoint", new byte[] { (byte)-69 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType CueTime = new MKVType("CueTime", new byte[] { (byte)-77 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueTrackPositions = new MKVType("CueTrackPositions", new byte[] { (byte)-73 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType CueTrack = new MKVType("CueTrack", new byte[] { (byte)-9 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueClusterPosition = new MKVType("CueClusterPosition", new byte[] { (byte)-15 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueRelativePosition = new MKVType("CueRelativePosition", new byte[] { (byte)-16 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueDuration = new MKVType("CueDuration", new byte[] { (byte)-78 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueBlockNumber = new MKVType("CueBlockNumber", new byte[] { (byte)83, (byte)120 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueCodecState = new MKVType("CueCodecState", new byte[] { (byte)-22 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType CueReference = new MKVType("CueReference", new byte[] { (byte)-37 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType CueRefTime = new MKVType("CueRefTime", new byte[] { (byte)-106 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Attachments = new MKVType("Attachments", new byte[] { (byte)25, (byte)65, (byte)-92, (byte)105 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType AttachedFile = new MKVType("AttachedFile", new byte[] { (byte)97, (byte)-89 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType FileDescription = new MKVType("FileDescription", new byte[] { (byte)70, (byte)126 }, EbmlString.class);
|
||||
|
||||
public static final MKVType FileName = new MKVType("FileName", new byte[] { (byte)70, (byte)110 }, EbmlString.class);
|
||||
|
||||
public static final MKVType FileMimeType = new MKVType("FileMimeType", new byte[] { (byte)70, (byte)96 }, EbmlString.class);
|
||||
|
||||
public static final MKVType FileData = new MKVType("FileData", new byte[] { (byte)70, (byte)92 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType FileUID = new MKVType("FileUID", new byte[] { (byte)70, (byte)-82 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType Chapters = new MKVType("Chapters", new byte[] { (byte)16, (byte)67, (byte)-89, (byte)112 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType EditionEntry = new MKVType("EditionEntry", new byte[] { (byte)69, (byte)-71 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType EditionUID = new MKVType("EditionUID", new byte[] { (byte)69, (byte)-68 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EditionFlagHidden = new MKVType("EditionFlagHidden", new byte[] { (byte)69, (byte)-67 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EditionFlagDefault = new MKVType("EditionFlagDefault", new byte[] { (byte)69, (byte)-37 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType EditionFlagOrdered = new MKVType("EditionFlagOrdered", new byte[] { (byte)69, (byte)-35 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterAtom = new MKVType("ChapterAtom", new byte[] { (byte)-74 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapterUID = new MKVType("ChapterUID", new byte[] { (byte)115, (byte)-60 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterStringUID = new MKVType("ChapterStringUID", new byte[] { (byte)86, (byte)84 }, EbmlString.class);
|
||||
|
||||
public static final MKVType ChapterTimeStart = new MKVType("ChapterTimeStart", new byte[] { (byte)-111 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterTimeEnd = new MKVType("ChapterTimeEnd", new byte[] { (byte)-110 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterFlagHidden = new MKVType("ChapterFlagHidden", new byte[] { (byte)-104 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterFlagEnabled = new MKVType("ChapterFlagEnabled", new byte[] { (byte)69, (byte)-104 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterSegmentUID = new MKVType("ChapterSegmentUID", new byte[] { (byte)110, (byte)103 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ChapterSegmentEditionUID = new MKVType("ChapterSegmentEditionUID", new byte[] { (byte)110, (byte)-68 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterPhysicalEquiv = new MKVType("ChapterPhysicalEquiv", new byte[] { (byte)99, (byte)-61 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterTrack = new MKVType("ChapterTrack", new byte[] { (byte)-113 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapterTrackNumber = new MKVType("ChapterTrackNumber", new byte[] { (byte)-119 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapterDisplay = new MKVType("ChapterDisplay", new byte[] { (byte)Byte.MIN_VALUE }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapString = new MKVType("ChapString", new byte[] { (byte)-123 }, EbmlString.class);
|
||||
|
||||
public static final MKVType ChapLanguage = new MKVType("ChapLanguage", new byte[] { (byte)67, (byte)124 }, EbmlString.class);
|
||||
|
||||
public static final MKVType ChapCountry = new MKVType("ChapCountry", new byte[] { (byte)67, (byte)126 }, EbmlString.class);
|
||||
|
||||
public static final MKVType ChapProcess = new MKVType("ChapProcess", new byte[] { (byte)105, (byte)68 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapProcessCodecID = new MKVType("ChapProcessCodecID", new byte[] { (byte)105, (byte)85 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapProcessPrivate = new MKVType("ChapProcessPrivate", new byte[] { (byte)69, (byte)13 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType ChapProcessCommand = new MKVType("ChapProcessCommand", new byte[] { (byte)105, (byte)17 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType ChapProcessTime = new MKVType("ChapProcessTime", new byte[] { (byte)105, (byte)34 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType ChapProcessData = new MKVType("ChapProcessData", new byte[] { (byte)105, (byte)51 }, EbmlBin.class);
|
||||
|
||||
public static final MKVType Tags = new MKVType("Tags", new byte[] { (byte)18, (byte)84, (byte)-61, (byte)103 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType Tag = new MKVType("Tag", new byte[] { (byte)115, (byte)115 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType Targets = new MKVType("Targets", new byte[] { (byte)99, (byte)-64 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TargetTypeValue = new MKVType("TargetTypeValue", new byte[] { (byte)104, (byte)-54 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TargetType = new MKVType("TargetType", new byte[] { (byte)99, (byte)-54 }, EbmlString.class);
|
||||
|
||||
public static final MKVType TagTrackUID = new MKVType("TagTrackUID", new byte[] { (byte)99, (byte)-59 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TagEditionUID = new MKVType("TagEditionUID", new byte[] { (byte)99, (byte)-55 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TagChapterUID = new MKVType("TagChapterUID", new byte[] { (byte)99, (byte)-60 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TagAttachmentUID = new MKVType("TagAttachmentUID", new byte[] { (byte)99, (byte)-58 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType SimpleTag = new MKVType("SimpleTag", new byte[] { (byte)103, (byte)-56 }, EbmlMaster.class);
|
||||
|
||||
public static final MKVType TagName = new MKVType("TagName", new byte[] { (byte)69, (byte)-93 }, EbmlString.class);
|
||||
|
||||
public static final MKVType TagLanguage = new MKVType("TagLanguage", new byte[] { (byte)68, (byte)122 }, EbmlString.class);
|
||||
|
||||
public static final MKVType TagDefault = new MKVType("TagDefault", new byte[] { (byte)68, (byte)-124 }, EbmlUint.class);
|
||||
|
||||
public static final MKVType TagString = new MKVType("TagString", new byte[] { (byte)68, (byte)-121 }, EbmlString.class);
|
||||
|
||||
public static final MKVType TagBinary = new MKVType("TagBinary", new byte[] { (byte)68, (byte)-123 }, EbmlBin.class);
|
||||
|
||||
public static MKVType[] firstLevelHeaders = new MKVType[] {
|
||||
SeekHead, Info, Cluster, Tracks, Cues, Attachments, Chapters, Tags, EBMLVersion, EBMLReadVersion,
|
||||
EBMLMaxIDLength, EBMLMaxSizeLength, DocType, DocTypeVersion, DocTypeReadVersion };
|
||||
|
||||
public final byte[] id;
|
||||
|
||||
public final Class<? extends EbmlBase> clazz;
|
||||
|
||||
private String _name;
|
||||
|
||||
private MKVType(String name, byte[] id, Class<? extends EbmlBase> clazz) {
|
||||
this._name = name;
|
||||
this.id = id;
|
||||
this.clazz = clazz;
|
||||
_values.add(this);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public static MKVType[] values() {
|
||||
return _values.<MKVType>toArray(new MKVType[0]);
|
||||
}
|
||||
|
||||
public static <T extends EbmlBase> T createByType(MKVType g) {
|
||||
try {
|
||||
T elem = Platform.<T>newInstance((Class<T>)g.clazz, new Object[] { g.id });
|
||||
elem.type = g;
|
||||
return elem;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return (T)new EbmlBin(g.id);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends EbmlBase> T createById(byte[] id, long offset) {
|
||||
MKVType[] values = values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
MKVType t = values[i];
|
||||
if (Platform.arrayEqualsByte(t.id, id))
|
||||
return createByType(t);
|
||||
}
|
||||
System.err.println("WARNING: unspecified ebml ID (" + EbmlUtil.toHexString(id) + ") encountered at position 0x" +
|
||||
Long.toHexString(offset).toUpperCase());
|
||||
EbmlVoid ebmlVoid = new EbmlVoid(id);
|
||||
ebmlVoid.type = Void;
|
||||
return (T)ebmlVoid;
|
||||
}
|
||||
|
||||
public static boolean isHeaderFirstByte(byte b) {
|
||||
MKVType[] values = values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
MKVType t = values[i];
|
||||
if (t.id[0] == b)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isSpecifiedHeader(byte[] b) {
|
||||
MKVType[] values = values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
MKVType firstLevelHeader = values[i];
|
||||
if (Platform.arrayEqualsByte(firstLevelHeader.id, b))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isFirstLevelHeader(byte[] b) {
|
||||
for (MKVType firstLevelHeader : firstLevelHeaders) {
|
||||
if (Platform.arrayEqualsByte(firstLevelHeader.id, b))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final Map<MKVType, Set<MKVType>> children = new HashMap<>();
|
||||
|
||||
static {
|
||||
children.put(EBML, new HashSet<>(Arrays.asList(EBMLVersion, EBMLReadVersion, EBMLMaxIDLength, EBMLMaxSizeLength, DocType, DocTypeVersion, DocTypeReadVersion)));
|
||||
children.put(Segment, new HashSet<>(Arrays.asList(SeekHead, Info, Cluster, Tracks, Cues, Attachments, Chapters, Tags)));
|
||||
children.put(SeekHead, new HashSet<>(Arrays.asList(Seek)));
|
||||
children.put(Seek, new HashSet<>(Arrays.asList(SeekID, SeekPosition)));
|
||||
children.put(Info, new HashSet<>(Arrays.asList(SegmentUID, SegmentFilename, PrevUID, PrevFilename, NextUID, NextFilenam, SegmentFamily, ChapterTranslate, TimecodeScale, Duration, DateUTC, Title, MuxingApp, WritingApp)));
|
||||
children.put(ChapterTranslate, new HashSet<>(Arrays.asList(ChapterTranslateEditionUID, ChapterTranslateCodec, ChapterTranslateID)));
|
||||
children.put(Cluster, new HashSet<>(Arrays.asList(Timecode, SilentTracks, Position, PrevSize, SimpleBlock, BlockGroup)));
|
||||
children.put(SilentTracks, new HashSet<>(Arrays.asList(SilentTrackNumber)));
|
||||
children.put(BlockGroup, new HashSet<>(Arrays.asList(Block, BlockAdditions, BlockDuration, ReferencePriority, ReferenceBlock, CodecState, Slices)));
|
||||
children.put(BlockAdditions, new HashSet<>(Arrays.asList(BlockMore)));
|
||||
children.put(BlockMore, new HashSet<>(Arrays.asList(BlockAddID, BlockAdditional)));
|
||||
children.put(Slices, new HashSet<>(Arrays.asList(TimeSlice)));
|
||||
children.put(TimeSlice, new HashSet<>(Arrays.asList(LaceNumber)));
|
||||
children.put(Tracks, new HashSet<>(Arrays.asList(TrackEntry)));
|
||||
children.put(TrackEntry, new HashSet<>(Arrays.asList(TrackNumber, TrackUID, TrackType, TrackType, FlagDefault, FlagForced, FlagLacing, MinCache, MaxCache, DefaultDuration, MaxBlockAdditionID, Name, Language, CodecID, CodecPrivate, CodecName, AttachmentLink, CodecDecodeAll, TrackOverlay, TrackTranslate, Video, Audio, TrackOperation, ContentEncodings)));
|
||||
children.put(TrackTranslate, new HashSet<>(Arrays.asList(TrackTranslateEditionUID, TrackTranslateCodec, TrackTranslateTrackID)));
|
||||
children.put(Video, new HashSet<>(Arrays.asList(FlagInterlaced, StereoMode, AlphaMode, PixelWidth, PixelHeight, PixelCropBottom, PixelCropTop, PixelCropLeft, PixelCropRight, DisplayWidth, DisplayHeight, DisplayUnit, AspectRatioType, ColourSpace)));
|
||||
children.put(Audio, new HashSet<>(Arrays.asList(SamplingFrequency, OutputSamplingFrequency, Channels, BitDepth)));
|
||||
children.put(TrackOperation, new HashSet<>(Arrays.asList(TrackCombinePlanes, TrackJoinBlocks)));
|
||||
children.put(TrackCombinePlanes, new HashSet<>(Arrays.asList(TrackPlane)));
|
||||
children.put(TrackPlane, new HashSet<>(Arrays.asList(TrackPlaneUID, TrackPlaneType)));
|
||||
children.put(TrackJoinBlocks, new HashSet<>(Arrays.asList(TrackJoinUID)));
|
||||
children.put(ContentEncodings, new HashSet<>(Arrays.asList(ContentEncoding)));
|
||||
children.put(ContentEncoding, new HashSet<>(Arrays.asList(ContentEncodingOrder, ContentEncodingScope, ContentEncodingType, ContentCompression, ContentEncryption)));
|
||||
children.put(ContentCompression, new HashSet<>(Arrays.asList(ContentCompAlgo, ContentCompSettings)));
|
||||
children.put(ContentEncryption, new HashSet<>(Arrays.asList(ContentEncAlgo, ContentEncKeyID, ContentSignature, ContentSigKeyID, ContentSigAlgo, ContentSigHashAlgo)));
|
||||
children.put(Cues, new HashSet<>(Arrays.asList(CuePoint)));
|
||||
children.put(CuePoint, new HashSet<>(Arrays.asList(CueTime, CueTrackPositions)));
|
||||
children.put(CueTrackPositions, new HashSet<>(Arrays.asList(CueTrack, CueClusterPosition, CueRelativePosition, CueDuration, CueBlockNumber, CueCodecState, CueReference)));
|
||||
children.put(CueReference, new HashSet<>(Arrays.asList(CueRefTime)));
|
||||
children.put(Attachments, new HashSet<>(Arrays.asList(AttachedFile)));
|
||||
children.put(AttachedFile, new HashSet<>(Arrays.asList(FileDescription, FileName, FileMimeType, FileData, FileUID)));
|
||||
children.put(Chapters, new HashSet<>(Arrays.asList(EditionEntry)));
|
||||
children.put(EditionEntry, new HashSet<>(Arrays.asList(EditionUID, EditionFlagHidden, EditionFlagDefault, EditionFlagOrdered, ChapterAtom)));
|
||||
children.put(ChapterAtom, new HashSet<>(Arrays.asList(ChapterUID, ChapterStringUID, ChapterTimeStart, ChapterTimeEnd, ChapterFlagHidden, ChapterFlagEnabled, ChapterSegmentUID, ChapterSegmentEditionUID, ChapterPhysicalEquiv, ChapterTrack, ChapterDisplay, ChapProcess)));
|
||||
children.put(ChapterTrack, new HashSet<>(Arrays.asList(ChapterTrackNumber)));
|
||||
children.put(ChapterDisplay, new HashSet<>(Arrays.asList(ChapString, ChapLanguage, ChapCountry)));
|
||||
children.put(ChapProcess, new HashSet<>(Arrays.asList(ChapProcessCodecID, ChapProcessPrivate, ChapProcessCommand)));
|
||||
children.put(ChapProcessCommand, new HashSet<>(Arrays.asList(ChapProcessTime, ChapProcessData)));
|
||||
children.put(Tags, new HashSet<>(Arrays.asList(Tag)));
|
||||
children.put(Tag, new HashSet<>(Arrays.asList(Targets, SimpleTag)));
|
||||
children.put(Targets, new HashSet<>(Arrays.asList(TargetTypeValue, TargetType, TagTrackUID, TagEditionUID, TagChapterUID, TagAttachmentUID)));
|
||||
children.put(SimpleTag, new HashSet<>(Arrays.asList(TagName, TagLanguage, TagDefault, TagString, TagBinary)));
|
||||
}
|
||||
|
||||
public static MKVType getParent(MKVType t) {
|
||||
for (Map.Entry<MKVType, Set<MKVType>> ent : children.entrySet()) {
|
||||
if (ent.getValue().contains(t))
|
||||
return ent.getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean possibleChild(EbmlMaster parent, EbmlBase child) {
|
||||
if (parent == null) {
|
||||
if (child.type == EBML || child.type == Segment)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (Platform.arrayEqualsByte(child.id, Void.id) || Platform.arrayEqualsByte(child.id, CRC32.id))
|
||||
return (child.offset != parent.dataOffset + (long)parent.dataLen);
|
||||
if (child.type == Void || child.type == CRC32)
|
||||
return true;
|
||||
Set<MKVType> candidates = children.get(parent.type);
|
||||
return (candidates != null && candidates.contains(child.type));
|
||||
}
|
||||
|
||||
public static boolean possibleChildById(EbmlMaster parent, byte[] typeId) {
|
||||
if (parent == null && (Platform.arrayEqualsByte(EBML.id, typeId) || Platform.arrayEqualsByte(Segment.id, typeId)))
|
||||
return true;
|
||||
if (parent == null)
|
||||
return false;
|
||||
if (Platform.arrayEqualsByte(Void.id, typeId) || Platform.arrayEqualsByte(CRC32.id, typeId))
|
||||
return true;
|
||||
for (MKVType aCandidate : children.get(parent.type)) {
|
||||
if (Platform.arrayEqualsByte(aCandidate.id, typeId))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static EbmlBase findFirst(EbmlBase master, MKVType[] path) {
|
||||
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
|
||||
return findFirstSub(master, tlist);
|
||||
}
|
||||
|
||||
public static <T> T findFirstTree(List<? extends EbmlBase> tree, MKVType[] path) {
|
||||
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
|
||||
for (EbmlBase e : tree) {
|
||||
EbmlBase z = findFirstSub(e, tlist);
|
||||
if (z != null)
|
||||
return (T)z;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static EbmlBase findFirstSub(EbmlBase elem, List<MKVType> path) {
|
||||
if (path.size() == 0)
|
||||
return null;
|
||||
if (!elem.type.equals(path.get(0)))
|
||||
return null;
|
||||
if (path.size() == 1)
|
||||
return elem;
|
||||
MKVType head = (MKVType)path.remove(0);
|
||||
EbmlBase result = null;
|
||||
if (elem instanceof EbmlMaster) {
|
||||
Iterator<EbmlBase> iter = ((EbmlMaster)elem).children.iterator();
|
||||
while (iter.hasNext() && result == null)
|
||||
result = findFirstSub(iter.next(), path);
|
||||
}
|
||||
path.add(0, head);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> findList(List<? extends EbmlBase> tree, Class<T> class1, MKVType[] path) {
|
||||
List<T> result = new LinkedList<>();
|
||||
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
|
||||
if (tlist.size() > 0)
|
||||
for (EbmlBase node : tree) {
|
||||
MKVType head = (MKVType)tlist.remove(0);
|
||||
if (head == null || head.equals(node.type))
|
||||
findSubList(node, tlist, result);
|
||||
tlist.add(0, head);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T> void findSubList(EbmlBase element, List<MKVType> path, Collection<T> result) {
|
||||
if (path.size() > 0) {
|
||||
MKVType head = (MKVType)path.remove(0);
|
||||
if (element instanceof EbmlMaster) {
|
||||
EbmlMaster nb = (EbmlMaster)element;
|
||||
for (EbmlBase candidate : nb.children) {
|
||||
if (head == null || head.equals(candidate.type))
|
||||
findSubList(candidate, path, result);
|
||||
}
|
||||
}
|
||||
path.add(0, head);
|
||||
} else {
|
||||
result.add((T)element);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T[] findAllTree(List<? extends EbmlBase> tree, Class<T> class1, MKVType[] path) {
|
||||
List<EbmlBase> result = new LinkedList<>();
|
||||
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
|
||||
if (tlist.size() > 0)
|
||||
for (EbmlBase node : tree) {
|
||||
MKVType head = (MKVType)tlist.remove(0);
|
||||
if (head == null || head.equals(node.type))
|
||||
findSub(node, tlist, result);
|
||||
tlist.add(0, head);
|
||||
}
|
||||
return result.toArray((T[])Array.newInstance(class1, 0));
|
||||
}
|
||||
|
||||
public static <T> T[] findAll(EbmlBase master, Class<T> class1, boolean ga, MKVType[] path) {
|
||||
List<EbmlBase> result = new LinkedList<>();
|
||||
List<MKVType> tlist = new LinkedList<>(Arrays.asList(path));
|
||||
if (!master.type.equals(tlist.get(0)))
|
||||
return result.toArray((T[])Array.newInstance(class1, 0));
|
||||
tlist.remove(0);
|
||||
findSub(master, tlist, result);
|
||||
return result.toArray((T[])Array.newInstance(class1, 0));
|
||||
}
|
||||
|
||||
private static void findSub(EbmlBase master, List<MKVType> path, Collection<EbmlBase> result) {
|
||||
if (path.size() > 0) {
|
||||
MKVType head = (MKVType)path.remove(0);
|
||||
if (master instanceof EbmlMaster) {
|
||||
EbmlMaster nb = (EbmlMaster)master;
|
||||
for (EbmlBase candidate : nb.children) {
|
||||
if (head == null || head.equals(candidate.type))
|
||||
findSub(candidate, path, result);
|
||||
}
|
||||
}
|
||||
path.add(0, head);
|
||||
} else {
|
||||
result.add(master);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package org.jcodec.containers.mkv;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBase;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBin;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlUint;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class SeekHeadFactory {
|
||||
List<SeekMock> a;
|
||||
|
||||
long currentDataOffset = 0L;
|
||||
|
||||
public SeekHeadFactory() {
|
||||
this.a = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void add(EbmlBase e) {
|
||||
SeekMock z = SeekMock.make(e);
|
||||
z.dataOffset = this.currentDataOffset;
|
||||
z.seekPointerSize = EbmlUint.calculatePayloadSize(z.dataOffset);
|
||||
this.currentDataOffset += (long)z.size;
|
||||
this.a.add(z);
|
||||
}
|
||||
|
||||
public EbmlMaster indexSeekHead() {
|
||||
int seekHeadSize = computeSeekHeadSize();
|
||||
EbmlMaster seekHead = MKVType.<EbmlMaster>createByType(MKVType.SeekHead);
|
||||
for (SeekMock z : this.a) {
|
||||
EbmlMaster seek = MKVType.<EbmlMaster>createByType(MKVType.Seek);
|
||||
EbmlBin seekId = MKVType.<EbmlBin>createByType(MKVType.SeekID);
|
||||
seekId.setBuf(ByteBuffer.wrap(z.id));
|
||||
seek.add(seekId);
|
||||
EbmlUint seekPosition = MKVType.<EbmlUint>createByType(MKVType.SeekPosition);
|
||||
seekPosition.setUint(z.dataOffset + (long)seekHeadSize);
|
||||
if (seekPosition.data.limit() != z.seekPointerSize)
|
||||
System.err.println("estimated size of seekPosition differs from the one actually used. ElementId: " + EbmlUtil.toHexString(z.id) + " " + seekPosition.getData().limit() + " vs " + z.seekPointerSize);
|
||||
seek.add(seekPosition);
|
||||
seekHead.add(seek);
|
||||
}
|
||||
ByteBuffer mux = seekHead.getData();
|
||||
if (mux.limit() != seekHeadSize)
|
||||
System.err.println("estimated size of seekHead differs from the one actually used. " + mux.limit() + " vs " + seekHeadSize);
|
||||
return seekHead;
|
||||
}
|
||||
|
||||
public int computeSeekHeadSize() {
|
||||
int seekHeadSize = estimateSize();
|
||||
boolean reindex = false;
|
||||
while (true) {
|
||||
reindex = false;
|
||||
for (SeekMock z : this.a) {
|
||||
int minSize = EbmlUint.calculatePayloadSize(z.dataOffset + (long)seekHeadSize);
|
||||
if (minSize > z.seekPointerSize) {
|
||||
System.out.println("Size " + seekHeadSize + " seems too small for element " + EbmlUtil.toHexString(z.id) + " increasing size by one.");
|
||||
z.seekPointerSize++;
|
||||
seekHeadSize++;
|
||||
reindex = true;
|
||||
break;
|
||||
}
|
||||
if (minSize < z.seekPointerSize)
|
||||
throw new RuntimeException("Downsizing the index is not well thought through.");
|
||||
}
|
||||
if (!reindex)
|
||||
return seekHeadSize;
|
||||
}
|
||||
}
|
||||
|
||||
int estimateSize() {
|
||||
int s = MKVType.SeekHead.id.length + 1;
|
||||
s += estimeteSeekSize(((SeekMock)this.a.get(0)).id.length, 1);
|
||||
for (int i = 1; i < this.a.size(); i++)
|
||||
s += estimeteSeekSize(((SeekMock)this.a.get(i)).id.length, ((SeekMock)this.a.get(i)).seekPointerSize);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static int estimeteSeekSize(int idLength, int offsetSizeInBytes) {
|
||||
int seekIdSize = MKVType.SeekID.id.length + EbmlUtil.ebmlLength((long)idLength) + idLength;
|
||||
int seekPositionSize = MKVType.SeekPosition.id.length + EbmlUtil.ebmlLength((long)offsetSizeInBytes) + offsetSizeInBytes;
|
||||
int seekSize = MKVType.Seek.id.length + EbmlUtil.ebmlLength((long)(seekIdSize + seekPositionSize)) + seekIdSize + seekPositionSize;
|
||||
return seekSize;
|
||||
}
|
||||
|
||||
public static class SeekMock {
|
||||
public long dataOffset;
|
||||
|
||||
byte[] id;
|
||||
|
||||
int size;
|
||||
|
||||
int seekPointerSize;
|
||||
|
||||
public static SeekMock make(EbmlBase e) {
|
||||
SeekMock z = new SeekMock();
|
||||
z.id = e.id;
|
||||
z.size = (int)e.size();
|
||||
return z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mkv.MKVType;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public abstract class EbmlBase {
|
||||
protected EbmlMaster parent;
|
||||
|
||||
public MKVType type;
|
||||
|
||||
public byte[] id;
|
||||
|
||||
public int dataLen = 0;
|
||||
|
||||
public long offset;
|
||||
|
||||
public long dataOffset;
|
||||
|
||||
public int typeSizeLength;
|
||||
|
||||
@UsedViaReflection
|
||||
public EbmlBase(byte[] id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean equalId(byte[] typeId) {
|
||||
return Platform.arrayEqualsByte(this.id, typeId);
|
||||
}
|
||||
|
||||
public abstract ByteBuffer getData();
|
||||
|
||||
public long size() {
|
||||
return (long)(this.dataLen + EbmlUtil.ebmlLength((long)this.dataLen) + this.id.length);
|
||||
}
|
||||
|
||||
public long mux(SeekableByteChannel os) throws IOException {
|
||||
ByteBuffer bb = getData();
|
||||
return (long)os.write(bb);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class EbmlBin extends EbmlBase {
|
||||
public ByteBuffer data;
|
||||
|
||||
protected boolean dataRead = false;
|
||||
|
||||
public EbmlBin(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void readChannel(SeekableByteChannel is) throws IOException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(this.dataLen);
|
||||
is.read(bb);
|
||||
bb.flip();
|
||||
read(bb);
|
||||
}
|
||||
|
||||
public void read(ByteBuffer source) {
|
||||
this.data = source.slice();
|
||||
this.data.limit(this.dataLen);
|
||||
this.dataRead = true;
|
||||
}
|
||||
|
||||
public void skip(ByteBuffer source) {
|
||||
if (!this.dataRead) {
|
||||
source.position((int)(this.dataOffset + (long)this.dataLen));
|
||||
this.dataRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
public long size() {
|
||||
if (this.data == null || this.data.limit() == 0)
|
||||
return super.size();
|
||||
long totalSize = (long)this.data.limit();
|
||||
totalSize += (long)EbmlUtil.ebmlLength((long)this.data.limit());
|
||||
totalSize += (long)this.id.length;
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public void setBuf(ByteBuffer data) {
|
||||
this.data = data.slice();
|
||||
this.dataLen = this.data.limit();
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
int sizeSize = EbmlUtil.ebmlLength((long)this.data.limit());
|
||||
byte[] size = EbmlUtil.ebmlEncodeLen((long)this.data.limit(), sizeSize);
|
||||
ByteBuffer bb = ByteBuffer.allocate(this.id.length + sizeSize + this.data.limit());
|
||||
bb.put(this.id);
|
||||
bb.put(size);
|
||||
bb.put(this.data);
|
||||
bb.flip();
|
||||
this.data.flip();
|
||||
return bb;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
|
||||
public class EbmlDate extends EbmlSint {
|
||||
private static final int NANOSECONDS_IN_A_SECOND = 1000000000;
|
||||
|
||||
private static final int MILISECONDS_IN_A_SECOND = 1000;
|
||||
|
||||
private static final int NANOSECONDS_IN_A_MILISECOND = 1000000;
|
||||
|
||||
public static long MILISECONDS_SINCE_UNIX_EPOCH_START = 978307200L;
|
||||
|
||||
public EbmlDate(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void setDate(Date value) {
|
||||
setMiliseconds(value.getTime());
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
long val = getLong();
|
||||
val = val / 1000000L + MILISECONDS_SINCE_UNIX_EPOCH_START;
|
||||
return new Date(val);
|
||||
}
|
||||
|
||||
private void setMiliseconds(long milliseconds) {
|
||||
setLong((milliseconds - MILISECONDS_SINCE_UNIX_EPOCH_START) * 1000000L);
|
||||
}
|
||||
|
||||
public void setLong(long value) {
|
||||
this.data = ByteBuffer.allocate(8);
|
||||
this.data.putLong(value);
|
||||
this.data.flip();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class EbmlFloat extends EbmlBin {
|
||||
public EbmlFloat(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void setDouble(double value) {
|
||||
if (value < Float.MAX_VALUE) {
|
||||
ByteBuffer bb = ByteBuffer.allocate(4);
|
||||
bb.putFloat((float)value);
|
||||
bb.flip();
|
||||
this.data = bb;
|
||||
} else if (value < Double.MAX_VALUE) {
|
||||
ByteBuffer bb = ByteBuffer.allocate(8);
|
||||
bb.putDouble(value);
|
||||
bb.flip();
|
||||
this.data = bb;
|
||||
}
|
||||
}
|
||||
|
||||
public double getDouble() {
|
||||
if (this.data.limit() == 4)
|
||||
return (double)this.data.duplicate().getFloat();
|
||||
return this.data.duplicate().getDouble();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class EbmlMaster extends EbmlBase {
|
||||
protected long usedSize;
|
||||
|
||||
public final ArrayList<EbmlBase> children;
|
||||
|
||||
public static final byte[] CLUSTER_ID = new byte[] { 31, 67, -74, 117 };
|
||||
|
||||
public EbmlMaster(byte[] id) {
|
||||
super(id);
|
||||
this.children = new ArrayList<>();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void add(EbmlBase elem) {
|
||||
if (elem == null)
|
||||
return;
|
||||
elem.parent = this;
|
||||
this.children.add(elem);
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
long size = getDataLen();
|
||||
if (size > Integer.MAX_VALUE)
|
||||
System.out.println("EbmlMaster.getData: id.length " + this.id.length + " EbmlUtil.ebmlLength(" + size + "): " + EbmlUtil.ebmlLength(size) + " size: " + size);
|
||||
ByteBuffer bb = ByteBuffer.allocate((int)((long)(this.id.length + EbmlUtil.ebmlLength(size)) + size));
|
||||
bb.put(this.id);
|
||||
bb.put(EbmlUtil.ebmlEncode(size));
|
||||
for (int i = 0; i < this.children.size(); i++)
|
||||
bb.put(this.children.get(i).getData());
|
||||
bb.flip();
|
||||
return bb;
|
||||
}
|
||||
|
||||
protected long getDataLen() {
|
||||
if (this.children == null || this.children.isEmpty())
|
||||
return (long)this.dataLen;
|
||||
long dataLength = 0L;
|
||||
for (EbmlBase e : this.children)
|
||||
dataLength += e.size();
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
long size = getDataLen();
|
||||
size += (long)EbmlUtil.ebmlLength(size);
|
||||
size += (long)this.id.length;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
|
||||
public class EbmlSint extends EbmlBin {
|
||||
public EbmlSint(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void setLong(long value) {
|
||||
this.data = ByteBuffer.wrap(convertToBytes(value));
|
||||
}
|
||||
|
||||
public long getLong() {
|
||||
if (this.data.limit() - this.data.position() == 8)
|
||||
return this.data.duplicate().getLong();
|
||||
byte[] b = this.data.array();
|
||||
long l = 0L;
|
||||
for (int i = b.length - 1; i >= 0; i--)
|
||||
l |= ((long)b[i] & 0xFFL) << 8 * (b.length - 1 - i);
|
||||
return l;
|
||||
}
|
||||
|
||||
public static int ebmlSignedLength(long val) {
|
||||
if (val <= 64L && val >= -63L)
|
||||
return 1;
|
||||
if (val <= 8192L && val >= -8191L)
|
||||
return 2;
|
||||
if (val <= 1048576L && val >= -1048575L)
|
||||
return 3;
|
||||
if (val <= 134217728L && val >= -134217727L)
|
||||
return 4;
|
||||
if (val <= 17179869184L && val >= -17179869183L)
|
||||
return 5;
|
||||
if (val <= 2199023255552L && val >= -2199023255551L)
|
||||
return 6;
|
||||
if (val <= 281474976710656L && val >= -281474976710655L)
|
||||
return 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static final long[] signedComplement = new long[] { 0L, 63L, 8191L, 1048575L, 134217727L, 17179869183L, 2199023255551L, 281474976710655L, 36028797018963967L };
|
||||
|
||||
public static byte[] convertToBytes(long val) {
|
||||
int num = ebmlSignedLength(val);
|
||||
val += signedComplement[num];
|
||||
return EbmlUtil.ebmlEncodeLen(val, num);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class EbmlString extends EbmlBin {
|
||||
public String charset = "UTF-8";
|
||||
|
||||
public EbmlString(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public static EbmlString createEbmlString(byte[] id, String value) {
|
||||
EbmlString e = new EbmlString(id);
|
||||
e.setString(value);
|
||||
return e;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
try {
|
||||
return new String(this.data.array(), this.charset);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void setString(String value) {
|
||||
try {
|
||||
this.data = ByteBuffer.wrap(value.getBytes(this.charset));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class EbmlUint extends EbmlBin {
|
||||
public EbmlUint(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public static EbmlUint createEbmlUint(byte[] id, long value) {
|
||||
EbmlUint e = new EbmlUint(id);
|
||||
e.setUint(value);
|
||||
return e;
|
||||
}
|
||||
|
||||
public void setUint(long value) {
|
||||
this.data = ByteBuffer.wrap(longToBytes(value));
|
||||
this.dataLen = this.data.limit();
|
||||
}
|
||||
|
||||
public long getUint() {
|
||||
long l = 0L;
|
||||
long tmp = 0L;
|
||||
for (int i = 0; i < this.data.limit(); i++) {
|
||||
tmp = (long)this.data.get(this.data.limit() - 1 - i) << 56L;
|
||||
tmp >>>= 56 - i * 8;
|
||||
l |= tmp;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
public static byte[] longToBytes(long value) {
|
||||
byte[] b = new byte[calculatePayloadSize(value)];
|
||||
for (int i = b.length - 1; i >= 0; i--)
|
||||
b[i] = (byte)(int)(value >>> 8 * (b.length - i - 1));
|
||||
return b;
|
||||
}
|
||||
|
||||
public static int calculatePayloadSize(long value) {
|
||||
if (value == 0L)
|
||||
return 1;
|
||||
if (value <= Integer.MAX_VALUE)
|
||||
return 4 - (Integer.numberOfLeadingZeros((int)value) >> 3);
|
||||
return 8 - (Long.numberOfLeadingZeros(value) >> 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class EbmlUlong extends EbmlBin {
|
||||
public EbmlUlong(byte[] id) {
|
||||
super(id);
|
||||
this.data = ByteBuffer.allocate(8);
|
||||
}
|
||||
|
||||
public static EbmlUlong createEbmlUlong(byte[] id, long value) {
|
||||
EbmlUlong e = new EbmlUlong(id);
|
||||
e.setUlong(value);
|
||||
return e;
|
||||
}
|
||||
|
||||
public void setUlong(long value) {
|
||||
this.data.putLong(value);
|
||||
this.data.flip();
|
||||
}
|
||||
|
||||
public long getUlong() {
|
||||
return this.data.duplicate().getLong();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
|
||||
public class EbmlVoid extends EbmlBase {
|
||||
public EbmlVoid(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void skip(SeekableByteChannel is) throws IOException {
|
||||
is.setPosition(this.dataOffset + (long)this.dataLen);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Arrays;
|
||||
import org.jcodec.common.ByteArrayList;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MkvBlock extends EbmlBin {
|
||||
private static final String XIPH = "Xiph";
|
||||
|
||||
private static final String EBML = "EBML";
|
||||
|
||||
private static final String FIXED = "Fixed";
|
||||
|
||||
private static final int MAX_BLOCK_HEADER_SIZE = 512;
|
||||
|
||||
public int[] frameOffsets;
|
||||
|
||||
public int[] frameSizes;
|
||||
|
||||
public long trackNumber;
|
||||
|
||||
public int timecode;
|
||||
|
||||
public long absoluteTimecode;
|
||||
|
||||
public boolean _keyFrame;
|
||||
|
||||
public int headerSize;
|
||||
|
||||
public String lacing;
|
||||
|
||||
public boolean discardable;
|
||||
|
||||
public boolean lacingPresent;
|
||||
|
||||
public ByteBuffer[] frames;
|
||||
|
||||
public static final byte[] BLOCK_ID = new byte[] { -95 };
|
||||
|
||||
public static final byte[] SIMPLEBLOCK_ID = new byte[] { -93 };
|
||||
|
||||
public static MkvBlock copy(MkvBlock old) {
|
||||
MkvBlock be = new MkvBlock(old.id);
|
||||
be.trackNumber = old.trackNumber;
|
||||
be.timecode = old.timecode;
|
||||
be.absoluteTimecode = old.absoluteTimecode;
|
||||
be._keyFrame = old._keyFrame;
|
||||
be.headerSize = old.headerSize;
|
||||
be.lacing = old.lacing;
|
||||
be.discardable = old.discardable;
|
||||
be.lacingPresent = old.lacingPresent;
|
||||
be.frameOffsets = new int[old.frameOffsets.length];
|
||||
be.frameSizes = new int[old.frameSizes.length];
|
||||
be.dataOffset = old.dataOffset;
|
||||
be.offset = old.offset;
|
||||
be.type = old.type;
|
||||
System.arraycopy(old.frameOffsets, 0, be.frameOffsets, 0, be.frameOffsets.length);
|
||||
System.arraycopy(old.frameSizes, 0, be.frameSizes, 0, be.frameSizes.length);
|
||||
return be;
|
||||
}
|
||||
|
||||
public static MkvBlock keyFrame(long trackNumber, int timecode, ByteBuffer frame) {
|
||||
MkvBlock be = new MkvBlock(SIMPLEBLOCK_ID);
|
||||
be.frames = new ByteBuffer[] { frame };
|
||||
be.frameSizes = new int[] { frame.limit() };
|
||||
be._keyFrame = true;
|
||||
be.trackNumber = trackNumber;
|
||||
be.timecode = timecode;
|
||||
return be;
|
||||
}
|
||||
|
||||
public MkvBlock(byte[] type) {
|
||||
super(type);
|
||||
if (!Platform.arrayEqualsByte(SIMPLEBLOCK_ID, type) && !Platform.arrayEqualsByte(BLOCK_ID, type))
|
||||
throw new IllegalArgumentException("Block initiated with invalid id: " + EbmlUtil.toHexString(type));
|
||||
}
|
||||
|
||||
public void readChannel(SeekableByteChannel is) throws IOException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(100);
|
||||
is.read(bb);
|
||||
bb.flip();
|
||||
read(bb);
|
||||
is.setPosition(this.dataOffset + (long)this.dataLen);
|
||||
}
|
||||
|
||||
public void read(ByteBuffer source) {
|
||||
ByteBuffer bb = source.slice();
|
||||
this.trackNumber = ebmlDecode(bb);
|
||||
int tcPart1 = bb.get() & 0xFF;
|
||||
int tcPart2 = bb.get() & 0xFF;
|
||||
this.timecode = (short)((short)tcPart1 << 8 | (short)tcPart2);
|
||||
int flags = bb.get() & 0xFF;
|
||||
this._keyFrame = ((flags & 0x80) > 0);
|
||||
this.discardable = ((flags & 0x1) > 0);
|
||||
int laceFlags = flags & 0x6;
|
||||
this.lacingPresent = (laceFlags != 0);
|
||||
if (this.lacingPresent) {
|
||||
int lacesCount = bb.get() & 0xFF;
|
||||
this.frameSizes = new int[lacesCount + 1];
|
||||
if (laceFlags == 2) {
|
||||
this.lacing = "Xiph";
|
||||
this.headerSize = readXiphLaceSizes(bb, this.frameSizes, this.dataLen, bb.position());
|
||||
} else if (laceFlags == 6) {
|
||||
this.lacing = "EBML";
|
||||
this.headerSize = readEBMLLaceSizes(bb, this.frameSizes, this.dataLen, bb.position());
|
||||
} else if (laceFlags == 4) {
|
||||
this.lacing = "Fixed";
|
||||
this.headerSize = bb.position();
|
||||
int aLaceSize = (this.dataLen - this.headerSize) / (lacesCount + 1);
|
||||
Arrays.fill(this.frameSizes, aLaceSize);
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported lacing type flag.");
|
||||
}
|
||||
turnSizesToFrameOffsets(this.frameSizes);
|
||||
} else {
|
||||
this.lacing = "";
|
||||
int frameOffset = bb.position();
|
||||
this.frameOffsets = new int[1];
|
||||
this.frameOffsets[0] = frameOffset;
|
||||
this.headerSize = bb.position();
|
||||
this.frameSizes = new int[1];
|
||||
this.frameSizes[0] = this.dataLen - this.headerSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void turnSizesToFrameOffsets(int[] sizes) {
|
||||
this.frameOffsets = new int[sizes.length];
|
||||
this.frameOffsets[0] = this.headerSize;
|
||||
for (int i = 1; i < sizes.length; i++)
|
||||
this.frameOffsets[i] = this.frameOffsets[i - 1] + sizes[i - 1];
|
||||
}
|
||||
|
||||
public static int readXiphLaceSizes(ByteBuffer bb, int[] sizes, int size, int preLacingHeaderSize) {
|
||||
int startPos = bb.position();
|
||||
int lastIndex = sizes.length - 1;
|
||||
sizes[lastIndex] = size;
|
||||
for (int l = 0; l < lastIndex; l++) {
|
||||
int laceSize = 255;
|
||||
while (laceSize == 255) {
|
||||
laceSize = bb.get() & 0xFF;
|
||||
sizes[l] = sizes[l] + laceSize;
|
||||
}
|
||||
sizes[lastIndex] = sizes[lastIndex] - sizes[l];
|
||||
}
|
||||
int headerSize = bb.position() - startPos + preLacingHeaderSize;
|
||||
sizes[lastIndex] = sizes[lastIndex] - headerSize;
|
||||
return headerSize;
|
||||
}
|
||||
|
||||
public static int readEBMLLaceSizes(ByteBuffer source, int[] sizes, int size, int preLacingHeaderSize) {
|
||||
int lastIndex = sizes.length - 1;
|
||||
sizes[lastIndex] = size;
|
||||
int startPos = source.position();
|
||||
sizes[0] = (int)ebmlDecode(source);
|
||||
sizes[lastIndex] = sizes[lastIndex] - sizes[0];
|
||||
int laceSize = sizes[0];
|
||||
long laceSizeDiff = 0L;
|
||||
for (int l = 1; l < lastIndex; l++) {
|
||||
laceSizeDiff = ebmlDecodeSigned(source);
|
||||
laceSize = (int)((long)laceSize + laceSizeDiff);
|
||||
sizes[l] = laceSize;
|
||||
sizes[lastIndex] = sizes[lastIndex] - sizes[l];
|
||||
}
|
||||
int headerSize = source.position() - startPos + preLacingHeaderSize;
|
||||
sizes[lastIndex] = sizes[lastIndex] - headerSize;
|
||||
return headerSize;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{dataOffset: ").append(this.dataOffset);
|
||||
sb.append(", trackNumber: ").append(this.trackNumber);
|
||||
sb.append(", timecode: ").append(this.timecode);
|
||||
sb.append(", keyFrame: ").append(this._keyFrame);
|
||||
sb.append(", headerSize: ").append(this.headerSize);
|
||||
sb.append(", lacing: ").append(this.lacing);
|
||||
for (int i = 0; i < this.frameSizes.length; i++)
|
||||
sb.append(", frame[").append(i).append("] offset ").append(this.frameOffsets[i]).append(" size ").append(this.frameSizes[i]);
|
||||
sb.append(" }");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public ByteBuffer[] getFrames(ByteBuffer source) throws IOException {
|
||||
ByteBuffer[] frames = new ByteBuffer[this.frameSizes.length];
|
||||
for (int i = 0; i < this.frameSizes.length; i++) {
|
||||
if (this.frameOffsets[i] > source.limit())
|
||||
System.err.println("frame offset: " + this.frameOffsets[i] + " limit: " + source.limit());
|
||||
source.position(this.frameOffsets[i]);
|
||||
ByteBuffer bb = source.slice();
|
||||
bb.limit(this.frameSizes[i]);
|
||||
frames[i] = bb;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
public void readFrames(ByteBuffer source) throws IOException {
|
||||
this.frames = getFrames(source);
|
||||
}
|
||||
|
||||
public ByteBuffer getData() {
|
||||
int dataSize = getDataSize();
|
||||
ByteBuffer bb = ByteBuffer.allocate(dataSize + EbmlUtil.ebmlLength((long)dataSize) + this.id.length);
|
||||
bb.put(this.id);
|
||||
bb.put(EbmlUtil.ebmlEncode((long)dataSize));
|
||||
bb.put(EbmlUtil.ebmlEncode(this.trackNumber));
|
||||
bb.put((byte)(this.timecode >>> 8 & 0xFF));
|
||||
bb.put((byte)(this.timecode & 0xFF));
|
||||
byte flags = 0;
|
||||
if ("Xiph".equals(this.lacing)) {
|
||||
flags = 2;
|
||||
} else if ("EBML".equals(this.lacing)) {
|
||||
flags = 6;
|
||||
} else if ("Fixed".equals(this.lacing)) {
|
||||
flags = 4;
|
||||
}
|
||||
if (this.discardable)
|
||||
flags = (byte)(flags | 0x1);
|
||||
if (this._keyFrame)
|
||||
flags = (byte)(flags | 0x80);
|
||||
bb.put(flags);
|
||||
if ((flags & 0x6) != 0) {
|
||||
bb.put((byte)(this.frames.length - 1 & 0xFF));
|
||||
bb.put(muxLacingInfo());
|
||||
}
|
||||
for (int i = 0; i < this.frames.length; i++) {
|
||||
ByteBuffer frame = this.frames[i];
|
||||
bb.put(frame);
|
||||
}
|
||||
bb.flip();
|
||||
return bb;
|
||||
}
|
||||
|
||||
public void seekAndReadContent(FileChannel source) throws IOException {
|
||||
this.data = ByteBuffer.allocate(this.dataLen);
|
||||
source.position(this.dataOffset);
|
||||
source.read(this.data);
|
||||
this.data.flip();
|
||||
}
|
||||
|
||||
public long size() {
|
||||
long size = (long)getDataSize();
|
||||
size += (long)EbmlUtil.ebmlLength(size);
|
||||
size += (long)this.id.length;
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getDataSize() {
|
||||
int size = 0;
|
||||
int arrayOfInt[], i;
|
||||
int j;
|
||||
for (arrayOfInt = this.frameSizes, i = arrayOfInt.length, j = 0; j < i; ) {
|
||||
long fsize = (long)arrayOfInt[j];
|
||||
size = (int)((long)size + fsize);
|
||||
j++;
|
||||
}
|
||||
if (this.lacingPresent) {
|
||||
size += (muxLacingInfo()).length;
|
||||
size++;
|
||||
}
|
||||
size += 3;
|
||||
size += EbmlUtil.ebmlLength(this.trackNumber);
|
||||
return size;
|
||||
}
|
||||
|
||||
private byte[] muxLacingInfo() {
|
||||
if ("EBML".equals(this.lacing))
|
||||
return muxEbmlLacing(this.frameSizes);
|
||||
if ("Xiph".equals(this.lacing))
|
||||
return muxXiphLacing(this.frameSizes);
|
||||
if ("Fixed".equals(this.lacing))
|
||||
return new byte[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
public static long ebmlDecode(ByteBuffer bb) {
|
||||
byte firstByte = bb.get();
|
||||
int length = EbmlUtil.computeLength(firstByte);
|
||||
if (length == 0)
|
||||
throw new RuntimeException("Invalid ebml integer size.");
|
||||
long value = (long)(firstByte & 255 >>> length);
|
||||
length--;
|
||||
while (length > 0) {
|
||||
value = value << 8L | (long)(bb.get() & 0xFF);
|
||||
length--;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static long ebmlDecodeSigned(ByteBuffer source) {
|
||||
byte firstByte = source.get();
|
||||
int size = EbmlUtil.computeLength(firstByte);
|
||||
if (size == 0)
|
||||
throw new RuntimeException("Invalid ebml integer size.");
|
||||
long value = (long)(firstByte & 255 >>> size);
|
||||
int remaining = size - 1;
|
||||
while (remaining > 0) {
|
||||
value = value << 8L | (long)(source.get() & 0xFF);
|
||||
remaining--;
|
||||
}
|
||||
return value - EbmlSint.signedComplement[size];
|
||||
}
|
||||
|
||||
public static long[] calcEbmlLacingDiffs(int[] laceSizes) {
|
||||
int lacesCount = laceSizes.length - 1;
|
||||
long[] out = new long[lacesCount];
|
||||
out[0] = (long)laceSizes[0];
|
||||
for (int i = 1; i < lacesCount; i++)
|
||||
out[i] = (long)(laceSizes[i] - laceSizes[i - 1]);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] muxEbmlLacing(int[] laceSizes) {
|
||||
ByteArrayList bytes = ByteArrayList.createByteArrayList();
|
||||
long[] laceSizeDiffs = calcEbmlLacingDiffs(laceSizes);
|
||||
bytes.addAll(EbmlUtil.ebmlEncode(laceSizeDiffs[0]));
|
||||
for (int i = 1; i < laceSizeDiffs.length; i++)
|
||||
bytes.addAll(EbmlSint.convertToBytes(laceSizeDiffs[i]));
|
||||
return bytes.toArray();
|
||||
}
|
||||
|
||||
public static byte[] muxXiphLacing(int[] laceSizes) {
|
||||
ByteArrayList bytes = ByteArrayList.createByteArrayList();
|
||||
for (int i = 0; i < laceSizes.length - 1; i++) {
|
||||
long laceSize = (long)laceSizes[i];
|
||||
while (laceSize >= 255L) {
|
||||
bytes.add((byte)-1);
|
||||
laceSize -= 255L;
|
||||
}
|
||||
bytes.add((byte)(int)laceSize);
|
||||
}
|
||||
return bytes.toArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package org.jcodec.containers.mkv.boxes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.containers.mkv.util.EbmlUtil;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MkvSegment extends EbmlMaster {
|
||||
int headerSize = 0;
|
||||
|
||||
public static final byte[] SEGMENT_ID = new byte[] { 24, 83, Byte.MIN_VALUE, 103 };
|
||||
|
||||
public MkvSegment(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public static MkvSegment createMkvSegment() {
|
||||
return new MkvSegment(SEGMENT_ID);
|
||||
}
|
||||
|
||||
public ByteBuffer getHeader() {
|
||||
long headerSize = getHeaderSize();
|
||||
if (headerSize > Integer.MAX_VALUE)
|
||||
System.out.println("MkvSegment.getHeader: id.length " + this.id.length + " Element.getEbmlSize(" + this.dataLen + "): " + EbmlUtil.ebmlLength((long)this.dataLen) + " size: " + this.dataLen);
|
||||
ByteBuffer bb = ByteBuffer.allocate((int)headerSize);
|
||||
bb.put(this.id);
|
||||
bb.put(EbmlUtil.ebmlEncode(getDataLen()));
|
||||
if (this.children != null && !this.children.isEmpty())
|
||||
for (EbmlBase e : this.children) {
|
||||
if (Platform.arrayEqualsByte(CLUSTER_ID, e.type.id))
|
||||
continue;
|
||||
bb.put(e.getData());
|
||||
}
|
||||
bb.flip();
|
||||
return bb;
|
||||
}
|
||||
|
||||
public long getHeaderSize() {
|
||||
long returnValue = (long)this.id.length;
|
||||
returnValue += (long)EbmlUtil.ebmlLength(getDataLen());
|
||||
if (this.children != null && !this.children.isEmpty())
|
||||
for (EbmlBase e : this.children) {
|
||||
if (Platform.arrayEqualsByte(CLUSTER_ID, e.type.id))
|
||||
continue;
|
||||
returnValue += e.size();
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,489 @@
|
|||
package org.jcodec.containers.mkv.demuxer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
import org.jcodec.codecs.h264.mp4.AvcCBox;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.Demuxer;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.SeekableDemuxerTrack;
|
||||
import org.jcodec.common.TrackType;
|
||||
import org.jcodec.common.VideoCodecMeta;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.model.ColorSpace;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.common.model.Size;
|
||||
import org.jcodec.common.model.TapeTimecode;
|
||||
import org.jcodec.containers.mkv.MKVParser;
|
||||
import org.jcodec.containers.mkv.MKVType;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBase;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBin;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlFloat;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlString;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlUint;
|
||||
import org.jcodec.containers.mkv.boxes.MkvBlock;
|
||||
|
||||
public final class MKVDemuxer implements Demuxer {
|
||||
private VideoTrack vTrack = null;
|
||||
|
||||
private List<AudioTrack> aTracks;
|
||||
|
||||
private List<SubtitlesTrack> subsTracks;
|
||||
|
||||
private List<EbmlMaster> t;
|
||||
|
||||
private SeekableByteChannel channel;
|
||||
|
||||
int timescale = 1;
|
||||
|
||||
int pictureWidth;
|
||||
|
||||
int pictureHeight;
|
||||
|
||||
private static Map<String, Codec> codecMapping = new HashMap<>();
|
||||
|
||||
static {
|
||||
codecMapping.put("V_VP8", Codec.VP8);
|
||||
codecMapping.put("V_VP9", Codec.VP9);
|
||||
codecMapping.put("V_MPEG4/ISO/AVC", Codec.H264);
|
||||
}
|
||||
|
||||
public MKVDemuxer(SeekableByteChannel fileChannelWrapper) throws IOException {
|
||||
this.channel = fileChannelWrapper;
|
||||
this.aTracks = new ArrayList<>();
|
||||
this.subsTracks = new ArrayList<>();
|
||||
MKVParser parser = new MKVParser(this.channel);
|
||||
this.t = parser.parse();
|
||||
demux();
|
||||
}
|
||||
|
||||
private void demux() {
|
||||
MKVType[] path = { MKVType.Segment, MKVType.Info, MKVType.TimecodeScale };
|
||||
EbmlUint ts = MKVType.<EbmlUint>findFirstTree(this.t, path);
|
||||
if (ts != null)
|
||||
this.timescale = (int)ts.getUint();
|
||||
MKVType[] path9 = { MKVType.Segment, MKVType.Tracks, MKVType.TrackEntry };
|
||||
for (EbmlMaster aTrack : MKVType.<EbmlMaster>findList(this.t, EbmlMaster.class, path9)) {
|
||||
MKVType[] path1 = { MKVType.TrackEntry, MKVType.TrackType };
|
||||
long type = ((EbmlUint)MKVType.findFirst(aTrack, path1)).getUint();
|
||||
MKVType[] arrayOfMKVType1 = { MKVType.TrackEntry, MKVType.TrackNumber };
|
||||
long id = ((EbmlUint)MKVType.findFirst(aTrack, arrayOfMKVType1)).getUint();
|
||||
if (type == 1L) {
|
||||
if (this.vTrack != null)
|
||||
throw new RuntimeException("More then 1 video track, can not compute...");
|
||||
MKVType[] path3 = { MKVType.TrackEntry, MKVType.CodecPrivate };
|
||||
MKVType[] path10 = { MKVType.TrackEntry, MKVType.CodecID };
|
||||
EbmlString codecId = (EbmlString)MKVType.findFirst(aTrack, path10);
|
||||
Codec codec = codecMapping.get(codecId.getString());
|
||||
EbmlBin videoCodecState = (EbmlBin)MKVType.findFirst(aTrack, path3);
|
||||
ByteBuffer state = null;
|
||||
if (videoCodecState != null)
|
||||
state = videoCodecState.data;
|
||||
MKVType[] path4 = { MKVType.TrackEntry, MKVType.Video, MKVType.PixelWidth };
|
||||
EbmlUint width = (EbmlUint)MKVType.findFirst(aTrack, path4);
|
||||
MKVType[] path5 = { MKVType.TrackEntry, MKVType.Video, MKVType.PixelHeight };
|
||||
EbmlUint height = (EbmlUint)MKVType.findFirst(aTrack, path5);
|
||||
MKVType[] path6 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayWidth };
|
||||
EbmlUint dwidth = (EbmlUint)MKVType.findFirst(aTrack, path6);
|
||||
MKVType[] path7 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayHeight };
|
||||
EbmlUint dheight = (EbmlUint)MKVType.findFirst(aTrack, path7);
|
||||
MKVType[] path8 = { MKVType.TrackEntry, MKVType.Video, MKVType.DisplayUnit };
|
||||
EbmlUint unit = (EbmlUint)MKVType.findFirst(aTrack, path8);
|
||||
if (width != null && height != null) {
|
||||
this.pictureWidth = (int)width.getUint();
|
||||
this.pictureHeight = (int)height.getUint();
|
||||
} else if (dwidth != null && dheight != null) {
|
||||
if (unit == null || unit.getUint() == 0L) {
|
||||
this.pictureHeight = (int)dheight.getUint();
|
||||
this.pictureWidth = (int)dwidth.getUint();
|
||||
} else {
|
||||
throw new RuntimeException("DisplayUnits other then 0 are not implemented yet");
|
||||
}
|
||||
}
|
||||
this.vTrack = new VideoTrack(this, (int)id, state, codec);
|
||||
continue;
|
||||
}
|
||||
if (type == 2L) {
|
||||
AudioTrack audioTrack = new AudioTrack((int)id, this);
|
||||
MKVType[] path3 = { MKVType.TrackEntry, MKVType.Audio, MKVType.SamplingFrequency };
|
||||
EbmlFloat sf = (EbmlFloat)MKVType.findFirst(aTrack, path3);
|
||||
if (sf != null)
|
||||
audioTrack.samplingFrequency = sf.getDouble();
|
||||
this.aTracks.add(audioTrack);
|
||||
continue;
|
||||
}
|
||||
if (type == 17L) {
|
||||
SubtitlesTrack subsTrack = new SubtitlesTrack((int)id, this);
|
||||
this.subsTracks.add(subsTrack);
|
||||
}
|
||||
}
|
||||
MKVType[] path2 = { MKVType.Segment, MKVType.Cluster };
|
||||
for (EbmlMaster aCluster : MKVType.<EbmlMaster>findList(this.t, EbmlMaster.class, path2)) {
|
||||
MKVType[] path1 = { MKVType.Cluster, MKVType.Timecode };
|
||||
long baseTimecode = ((EbmlUint)MKVType.findFirst(aCluster, path1)).getUint();
|
||||
for (EbmlBase child : aCluster.children) {
|
||||
if (MKVType.SimpleBlock.equals(child.type)) {
|
||||
MkvBlock b = (MkvBlock)child;
|
||||
b.absoluteTimecode = (long)b.timecode + baseTimecode;
|
||||
putIntoRightBasket(b);
|
||||
continue;
|
||||
}
|
||||
if (MKVType.BlockGroup.equals(child.type)) {
|
||||
EbmlMaster group = (EbmlMaster)child;
|
||||
for (EbmlBase grandChild : group.children) {
|
||||
if (grandChild.type == MKVType.Block) {
|
||||
MkvBlock b = (MkvBlock)grandChild;
|
||||
b.absoluteTimecode = (long)b.timecode + baseTimecode;
|
||||
putIntoRightBasket(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void putIntoRightBasket(MkvBlock b) {
|
||||
if (this.vTrack != null && b.trackNumber == (long)this.vTrack.trackNo) {
|
||||
this.vTrack.blocks.add(b);
|
||||
} else {
|
||||
for (int j = 0; j < this.aTracks.size(); j++) {
|
||||
AudioTrack audio = this.aTracks.get(j);
|
||||
if (b.trackNumber == (long)audio.trackNo) {
|
||||
audio.blocks.add(IndexedBlock.make(audio.framesCount, b));
|
||||
audio.framesCount += b.frameSizes.length;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < this.subsTracks.size(); i++) {
|
||||
SubtitlesTrack subs = this.subsTracks.get(i);
|
||||
if (b.trackNumber == (long)subs.trackNo) {
|
||||
subs.blocks.add(IndexedBlock.make(subs.framesCount, b));
|
||||
subs.framesCount += b.frameSizes.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class VideoTrack implements SeekableDemuxerTrack {
|
||||
private ByteBuffer state;
|
||||
|
||||
public final int trackNo;
|
||||
|
||||
private int frameIdx = 0;
|
||||
|
||||
List<MkvBlock> blocks;
|
||||
|
||||
private MKVDemuxer demuxer;
|
||||
|
||||
private Codec codec;
|
||||
|
||||
private AvcCBox avcC;
|
||||
|
||||
public VideoTrack(MKVDemuxer demuxer, int trackNo, ByteBuffer state, Codec codec) {
|
||||
this.blocks = new ArrayList<>();
|
||||
this.demuxer = demuxer;
|
||||
this.trackNo = trackNo;
|
||||
this.codec = codec;
|
||||
if (codec == Codec.H264) {
|
||||
this.avcC = H264Utils.parseAVCCFromBuffer(state);
|
||||
this.state = H264Utils.avcCToAnnexB(this.avcC);
|
||||
} else {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
if (this.frameIdx >= this.blocks.size())
|
||||
return null;
|
||||
MkvBlock b = this.blocks.get(this.frameIdx);
|
||||
if (b == null)
|
||||
throw new RuntimeException("Something somewhere went wrong.");
|
||||
this.frameIdx++;
|
||||
this.demuxer.channel.setPosition(b.dataOffset);
|
||||
ByteBuffer data = ByteBuffer.allocate(b.dataLen);
|
||||
this.demuxer.channel.read(data);
|
||||
data.flip();
|
||||
b.readFrames(data.duplicate());
|
||||
long duration = 1L;
|
||||
if (this.frameIdx < this.blocks.size())
|
||||
duration = ((MkvBlock)this.blocks.get(this.frameIdx)).absoluteTimecode - b.absoluteTimecode;
|
||||
ByteBuffer result = b.frames[0].duplicate();
|
||||
if (this.codec == Codec.H264)
|
||||
result = H264Utils.decodeMOVPacket(result, this.avcC);
|
||||
return Packet.createPacket(result, b.absoluteTimecode, this.demuxer.timescale, duration, (long)(this.frameIdx - 1),
|
||||
b._keyFrame ? Packet.FrameType.KEY : Packet.FrameType.INTER, TapeTimecode.ZERO_TAPE_TIMECODE);
|
||||
}
|
||||
|
||||
public boolean gotoFrame(long i) {
|
||||
if (i > Integer.MAX_VALUE)
|
||||
return false;
|
||||
if (i > (long)this.blocks.size())
|
||||
return false;
|
||||
this.frameIdx = (int)i;
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getCurFrame() {
|
||||
return (long)this.frameIdx;
|
||||
}
|
||||
|
||||
public void seek(double second) {
|
||||
throw new RuntimeException("Not implemented yet");
|
||||
}
|
||||
|
||||
public int getFrameCount() {
|
||||
return this.blocks.size();
|
||||
}
|
||||
|
||||
public ByteBuffer getCodecState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return new DemuxerTrackMeta(TrackType.VIDEO, this.codec, 0.0D, null, 0, this.state,
|
||||
VideoCodecMeta.createSimpleVideoCodecMeta(new Size(this.demuxer.pictureWidth, this.demuxer.pictureHeight), ColorSpace.YUV420), null);
|
||||
}
|
||||
|
||||
public boolean gotoSyncFrame(long i) {
|
||||
throw new RuntimeException("Unsupported");
|
||||
}
|
||||
}
|
||||
|
||||
public static class IndexedBlock {
|
||||
public int firstFrameNo;
|
||||
|
||||
public MkvBlock block;
|
||||
|
||||
public static IndexedBlock make(int no, MkvBlock b) {
|
||||
IndexedBlock ib = new IndexedBlock();
|
||||
ib.firstFrameNo = no;
|
||||
ib.block = b;
|
||||
return ib;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubtitlesTrack extends MkvTrack {
|
||||
SubtitlesTrack(int trackNo, MKVDemuxer demuxer) {
|
||||
super(trackNo, demuxer);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MkvBlockData {
|
||||
final MkvBlock block;
|
||||
|
||||
final ByteBuffer data;
|
||||
|
||||
final int count;
|
||||
|
||||
MkvBlockData(MkvBlock block, ByteBuffer data, int count) {
|
||||
this.block = block;
|
||||
this.data = data;
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MkvTrack implements SeekableDemuxerTrack {
|
||||
public final int trackNo;
|
||||
|
||||
List<MKVDemuxer.IndexedBlock> blocks;
|
||||
|
||||
int framesCount = 0;
|
||||
|
||||
private int frameIdx = 0;
|
||||
|
||||
private int blockIdx = 0;
|
||||
|
||||
private int frameInBlockIdx = 0;
|
||||
|
||||
private MKVDemuxer demuxer;
|
||||
|
||||
public MkvTrack(int trackNo, MKVDemuxer demuxer) {
|
||||
this.blocks = new ArrayList<>();
|
||||
this.trackNo = trackNo;
|
||||
this.demuxer = demuxer;
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
MKVDemuxer.MkvBlockData bd = nextBlock();
|
||||
if (bd == null)
|
||||
return null;
|
||||
return Packet.createPacket(bd.data, bd.block.absoluteTimecode, this.demuxer.timescale, 1L, (long)(this.frameIdx - 1), Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
|
||||
}
|
||||
|
||||
protected MKVDemuxer.MkvBlockData nextBlock() throws IOException {
|
||||
if (this.frameIdx >= this.blocks.size() || this.blockIdx >= this.blocks.size())
|
||||
return null;
|
||||
MkvBlock b = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
|
||||
if (b == null)
|
||||
throw new RuntimeException("Something somewhere went wrong.");
|
||||
if (b.frames == null || b.frames.length == 0) {
|
||||
this.demuxer.channel.setPosition(b.dataOffset);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(b.dataLen);
|
||||
this.demuxer.channel.read(byteBuffer);
|
||||
b.readFrames(byteBuffer);
|
||||
}
|
||||
ByteBuffer data = b.frames[this.frameInBlockIdx].duplicate();
|
||||
this.frameInBlockIdx++;
|
||||
this.frameIdx++;
|
||||
if (this.frameInBlockIdx >= b.frames.length) {
|
||||
this.blockIdx++;
|
||||
this.frameInBlockIdx = 0;
|
||||
}
|
||||
return new MKVDemuxer.MkvBlockData(b, data, 1);
|
||||
}
|
||||
|
||||
public boolean gotoFrame(long i) {
|
||||
if (i > Integer.MAX_VALUE)
|
||||
return false;
|
||||
if (i > (long)this.framesCount)
|
||||
return false;
|
||||
int frameBlockIdx = findBlockIndex(i);
|
||||
if (frameBlockIdx == -1)
|
||||
return false;
|
||||
this.frameIdx = (int)i;
|
||||
this.blockIdx = frameBlockIdx;
|
||||
this.frameInBlockIdx = (int)i - ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).firstFrameNo;
|
||||
return true;
|
||||
}
|
||||
|
||||
private int findBlockIndex(long i) {
|
||||
for (int blockIndex = 0; blockIndex < this.blocks.size(); blockIndex++) {
|
||||
if (i < (long)((MKVDemuxer.IndexedBlock)this.blocks.get(blockIndex)).block.frameSizes.length)
|
||||
return blockIndex;
|
||||
i -= (long)((MKVDemuxer.IndexedBlock)this.blocks.get(blockIndex)).block.frameSizes.length;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getCurFrame() {
|
||||
return (long)this.frameIdx;
|
||||
}
|
||||
|
||||
public void seek(double second) {
|
||||
throw new RuntimeException("Not implemented yet");
|
||||
}
|
||||
|
||||
public Packet getFrames(int count) {
|
||||
MKVDemuxer.MkvBlockData frameBlock = getFrameBlock(count);
|
||||
if (frameBlock == null)
|
||||
return null;
|
||||
return Packet.createPacket(frameBlock.data, frameBlock.block.absoluteTimecode, this.demuxer.timescale, (long)frameBlock.count, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
|
||||
}
|
||||
|
||||
MKVDemuxer.MkvBlockData getFrameBlock(int count) {
|
||||
if (count + this.frameIdx >= this.framesCount)
|
||||
return null;
|
||||
List<ByteBuffer> packetFrames = new ArrayList<>();
|
||||
MkvBlock firstBlockInAPacket = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
|
||||
while (count > 0) {
|
||||
MkvBlock b = ((MKVDemuxer.IndexedBlock)this.blocks.get(this.blockIdx)).block;
|
||||
if (b.frames == null || b.frames.length == 0)
|
||||
try {
|
||||
this.demuxer.channel.setPosition(b.dataOffset);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(b.dataLen);
|
||||
this.demuxer.channel.read(byteBuffer);
|
||||
b.readFrames(byteBuffer);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("while reading frames of a Block at offset 0x" +
|
||||
Long.toHexString(b.dataOffset).toUpperCase() + ")", ioe);
|
||||
}
|
||||
packetFrames.add(b.frames[this.frameInBlockIdx].duplicate());
|
||||
this.frameIdx++;
|
||||
this.frameInBlockIdx++;
|
||||
if (this.frameInBlockIdx >= b.frames.length) {
|
||||
this.frameInBlockIdx = 0;
|
||||
this.blockIdx++;
|
||||
}
|
||||
count--;
|
||||
}
|
||||
int size = 0;
|
||||
for (ByteBuffer aFrame : packetFrames)
|
||||
size += aFrame.limit();
|
||||
ByteBuffer data = ByteBuffer.allocate(size);
|
||||
for (ByteBuffer aFrame : packetFrames)
|
||||
data.put(aFrame);
|
||||
return new MKVDemuxer.MkvBlockData(firstBlockInAPacket, data, packetFrames.size());
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean gotoSyncFrame(long frame) {
|
||||
return gotoFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AudioTrack extends MkvTrack {
|
||||
public double samplingFrequency;
|
||||
|
||||
public AudioTrack(int trackNo, MKVDemuxer demuxer) {
|
||||
super(trackNo, demuxer);
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
MKVDemuxer.MkvBlockData b = nextBlock();
|
||||
if (b == null)
|
||||
return null;
|
||||
return Packet.createPacket(b.data, b.block.absoluteTimecode, (int)Math.round(this.samplingFrequency), 1L, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
|
||||
}
|
||||
|
||||
public Packet getFrames(int count) {
|
||||
MKVDemuxer.MkvBlockData frameBlock = getFrameBlock(count);
|
||||
if (frameBlock == null)
|
||||
return null;
|
||||
return Packet.createPacket(frameBlock.data, frameBlock.block.absoluteTimecode, (int)Math.round(this.samplingFrequency), (long)frameBlock.count, 0L, Packet.FrameType.KEY, TapeTimecode.ZERO_TAPE_TIMECODE);
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean gotoSyncFrame(long frame) {
|
||||
return gotoFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
public int getPictureWidth() {
|
||||
return this.pictureWidth;
|
||||
}
|
||||
|
||||
public int getPictureHeight() {
|
||||
return this.pictureHeight;
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getAudioTracks() {
|
||||
return (List)this.aTracks;
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getTracks() {
|
||||
ArrayList<DemuxerTrack> tracks = new ArrayList<>(this.aTracks);
|
||||
tracks.add(this.vTrack);
|
||||
tracks.addAll(this.subsTracks);
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getVideoTracks() {
|
||||
ArrayList<DemuxerTrack> tracks = new ArrayList<>();
|
||||
tracks.add(this.vTrack);
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<DemuxerTrack> getSubtitleTracks() {
|
||||
return (List)this.subsTracks;
|
||||
}
|
||||
|
||||
public List<? extends EbmlBase> getTree() {
|
||||
return this.t;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.channel.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package org.jcodec.containers.mkv.muxer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jcodec.common.AudioCodecMeta;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.Muxer;
|
||||
import org.jcodec.common.MuxerTrack;
|
||||
import org.jcodec.common.VideoCodecMeta;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mkv.CuesFactory;
|
||||
import org.jcodec.containers.mkv.MKVType;
|
||||
import org.jcodec.containers.mkv.SeekHeadFactory;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBase;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlBin;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlDate;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlFloat;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlMaster;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlString;
|
||||
import org.jcodec.containers.mkv.boxes.EbmlUint;
|
||||
import org.jcodec.containers.mkv.boxes.MkvBlock;
|
||||
|
||||
public class MKVMuxer implements Muxer {
|
||||
private List<MKVMuxerTrack> tracks;
|
||||
|
||||
private MKVMuxerTrack audioTrack;
|
||||
|
||||
private MKVMuxerTrack videoTrack;
|
||||
|
||||
private EbmlMaster mkvInfo;
|
||||
|
||||
private EbmlMaster mkvTracks;
|
||||
|
||||
private EbmlMaster mkvCues;
|
||||
|
||||
private EbmlMaster mkvSeekHead;
|
||||
|
||||
private List<EbmlMaster> clusterList;
|
||||
|
||||
private SeekableByteChannel sink;
|
||||
|
||||
private static Map<Codec, String> codec2mkv = new HashMap<>();
|
||||
|
||||
static {
|
||||
codec2mkv.put(Codec.H264, "V_MPEG4/ISO/AVC");
|
||||
codec2mkv.put(Codec.VP8, "V_VP8");
|
||||
codec2mkv.put(Codec.VP9, "V_VP9");
|
||||
}
|
||||
|
||||
public MKVMuxer(SeekableByteChannel s) {
|
||||
this.sink = s;
|
||||
this.tracks = new ArrayList<>();
|
||||
this.clusterList = new LinkedList<>();
|
||||
}
|
||||
|
||||
public MKVMuxerTrack createVideoTrack(VideoCodecMeta meta, String codecId) {
|
||||
if (this.videoTrack == null) {
|
||||
this.videoTrack = new MKVMuxerTrack();
|
||||
this.tracks.add(this.videoTrack);
|
||||
this.videoTrack.codecId = codecId;
|
||||
this.videoTrack.videoMeta = meta;
|
||||
this.videoTrack.trackNo = this.tracks.size();
|
||||
}
|
||||
return this.videoTrack;
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
List<EbmlMaster> mkvFile = new ArrayList<>();
|
||||
EbmlMaster ebmlHeader = defaultEbmlHeader();
|
||||
mkvFile.add(ebmlHeader);
|
||||
EbmlMaster segmentElem = MKVType.<EbmlMaster>createByType(MKVType.Segment);
|
||||
this.mkvInfo = muxInfo();
|
||||
this.mkvTracks = muxTracks();
|
||||
this.mkvCues = MKVType.<EbmlMaster>createByType(MKVType.Cues);
|
||||
this.mkvSeekHead = muxSeekHead();
|
||||
muxCues();
|
||||
segmentElem.add(this.mkvSeekHead);
|
||||
segmentElem.add(this.mkvInfo);
|
||||
segmentElem.add(this.mkvTracks);
|
||||
segmentElem.add(this.mkvCues);
|
||||
for (EbmlMaster aCluster : this.clusterList)
|
||||
segmentElem.add(aCluster);
|
||||
mkvFile.add(segmentElem);
|
||||
for (EbmlMaster el : mkvFile)
|
||||
el.mux(this.sink);
|
||||
}
|
||||
|
||||
private EbmlMaster defaultEbmlHeader() {
|
||||
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.EBML);
|
||||
createLong(master, MKVType.EBMLVersion, 1L);
|
||||
createLong(master, MKVType.EBMLReadVersion, 1L);
|
||||
createLong(master, MKVType.EBMLMaxIDLength, 4L);
|
||||
createLong(master, MKVType.EBMLMaxSizeLength, 8L);
|
||||
createString(master, MKVType.DocType, "webm");
|
||||
createLong(master, MKVType.DocTypeVersion, 2L);
|
||||
createLong(master, MKVType.DocTypeReadVersion, 2L);
|
||||
return master;
|
||||
}
|
||||
|
||||
private EbmlMaster muxInfo() {
|
||||
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.Info);
|
||||
int frameDurationInNanoseconds = 40000000;
|
||||
createLong(master, MKVType.TimecodeScale, (long)frameDurationInNanoseconds);
|
||||
createString(master, MKVType.WritingApp, "JCodec");
|
||||
createString(master, MKVType.MuxingApp, "JCodec");
|
||||
List<MKVMuxerTrack> tracks2 = this.tracks;
|
||||
long max = 0L;
|
||||
for (MKVMuxerTrack track : tracks2) {
|
||||
MkvBlock lastBlock = track.trackBlocks.get(track.trackBlocks.size() - 1);
|
||||
if (lastBlock.absoluteTimecode > max)
|
||||
max = lastBlock.absoluteTimecode;
|
||||
}
|
||||
createDouble(master, MKVType.Duration, (double)((max + 1L) * (long)frameDurationInNanoseconds) * 1.0D);
|
||||
createDate(master, MKVType.DateUTC, new Date());
|
||||
return master;
|
||||
}
|
||||
|
||||
private EbmlMaster muxTracks() {
|
||||
EbmlMaster master = MKVType.<EbmlMaster>createByType(MKVType.Tracks);
|
||||
for (int i = 0; i < this.tracks.size(); i++) {
|
||||
MKVMuxerTrack track = this.tracks.get(i);
|
||||
EbmlMaster trackEntryElem = MKVType.<EbmlMaster>createByType(MKVType.TrackEntry);
|
||||
createLong(trackEntryElem, MKVType.TrackNumber, (long)track.trackNo);
|
||||
createLong(trackEntryElem, MKVType.TrackUID, (long)track.trackNo);
|
||||
if (MKVMuxerTrack.MKVMuxerTrackType.VIDEO.equals(track.type)) {
|
||||
createLong(trackEntryElem, MKVType.TrackType, 1L);
|
||||
createString(trackEntryElem, MKVType.Name, "Track " + i + 1 + " Video");
|
||||
createString(trackEntryElem, MKVType.CodecID, track.codecId);
|
||||
EbmlMaster trackVideoElem = MKVType.<EbmlMaster>createByType(MKVType.Video);
|
||||
createLong(trackVideoElem, MKVType.PixelWidth, (long)track.videoMeta.getSize().getWidth());
|
||||
createLong(trackVideoElem, MKVType.PixelHeight, (long)track.videoMeta.getSize().getHeight());
|
||||
trackEntryElem.add(trackVideoElem);
|
||||
} else {
|
||||
createLong(trackEntryElem, MKVType.TrackType, 2L);
|
||||
createString(trackEntryElem, MKVType.Name, "Track " + i + 1 + " Audio");
|
||||
createString(trackEntryElem, MKVType.CodecID, track.codecId);
|
||||
}
|
||||
master.add(trackEntryElem);
|
||||
}
|
||||
return master;
|
||||
}
|
||||
|
||||
private void muxCues() {
|
||||
CuesFactory cf = new CuesFactory(this.mkvSeekHead.size() + this.mkvInfo.size() + this.mkvTracks.size(), (long)this.videoTrack.trackNo);
|
||||
for (MkvBlock aBlock : this.videoTrack.trackBlocks) {
|
||||
EbmlMaster mkvCluster = singleBlockedCluster(aBlock);
|
||||
this.clusterList.add(mkvCluster);
|
||||
cf.add(CuesFactory.CuePointMock.make(mkvCluster));
|
||||
}
|
||||
EbmlMaster indexedCues = cf.createCues();
|
||||
for (EbmlBase aCuePoint : indexedCues.children)
|
||||
this.mkvCues.add(aCuePoint);
|
||||
}
|
||||
|
||||
private EbmlMaster singleBlockedCluster(MkvBlock aBlock) {
|
||||
EbmlMaster mkvCluster = MKVType.<EbmlMaster>createByType(MKVType.Cluster);
|
||||
createLong(mkvCluster, MKVType.Timecode, aBlock.absoluteTimecode - (long)aBlock.timecode);
|
||||
mkvCluster.add(aBlock);
|
||||
return mkvCluster;
|
||||
}
|
||||
|
||||
private EbmlMaster muxSeekHead() {
|
||||
SeekHeadFactory shi = new SeekHeadFactory();
|
||||
shi.add(this.mkvInfo);
|
||||
shi.add(this.mkvTracks);
|
||||
shi.add(this.mkvCues);
|
||||
return shi.indexSeekHead();
|
||||
}
|
||||
|
||||
public static void createLong(EbmlMaster parent, MKVType type, long value) {
|
||||
EbmlUint se = MKVType.<EbmlUint>createByType(type);
|
||||
se.setUint(value);
|
||||
parent.add(se);
|
||||
}
|
||||
|
||||
public static void createString(EbmlMaster parent, MKVType type, String value) {
|
||||
EbmlString se = MKVType.<EbmlString>createByType(type);
|
||||
se.setString(value);
|
||||
parent.add(se);
|
||||
}
|
||||
|
||||
public static void createDate(EbmlMaster parent, MKVType type, Date value) {
|
||||
EbmlDate se = MKVType.<EbmlDate>createByType(type);
|
||||
se.setDate(value);
|
||||
parent.add(se);
|
||||
}
|
||||
|
||||
public static void createBuffer(EbmlMaster parent, MKVType type, ByteBuffer value) {
|
||||
EbmlBin se = MKVType.<EbmlBin>createByType(type);
|
||||
se.setBuf(value);
|
||||
parent.add(se);
|
||||
}
|
||||
|
||||
public static void createDouble(EbmlMaster parent, MKVType type, double value) {
|
||||
try {
|
||||
EbmlFloat se = MKVType.<EbmlFloat>createByType(type);
|
||||
se.setDouble(value);
|
||||
parent.add(se);
|
||||
} catch (ClassCastException cce) {
|
||||
throw new RuntimeException("Element of type " + String.valueOf(type) + " can't be cast to EbmlFloat", cce);
|
||||
}
|
||||
}
|
||||
|
||||
public MuxerTrack addVideoTrack(Codec codec, VideoCodecMeta meta) {
|
||||
return createVideoTrack(meta, codec2mkv.get(codec));
|
||||
}
|
||||
|
||||
public MuxerTrack addAudioTrack(Codec codec, AudioCodecMeta meta) {
|
||||
this.audioTrack = new MKVMuxerTrack();
|
||||
this.tracks.add(this.audioTrack);
|
||||
this.audioTrack.codecId = codec2mkv.get(codec);
|
||||
this.audioTrack.trackNo = this.tracks.size();
|
||||
return this.audioTrack;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jcodec.containers.mkv.muxer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.MuxerTrack;
|
||||
import org.jcodec.common.VideoCodecMeta;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.containers.mkv.boxes.MkvBlock;
|
||||
|
||||
public class MKVMuxerTrack implements MuxerTrack {
|
||||
public MKVMuxerTrackType type;
|
||||
|
||||
public VideoCodecMeta videoMeta;
|
||||
|
||||
public String codecId;
|
||||
|
||||
public int trackNo;
|
||||
|
||||
private int frameDuration;
|
||||
|
||||
List<MkvBlock> trackBlocks;
|
||||
|
||||
static final int DEFAULT_TIMESCALE = 1000000000;
|
||||
|
||||
static final int NANOSECONDS_IN_A_MILISECOND = 1000000;
|
||||
|
||||
static final int MULTIPLIER = 1000;
|
||||
|
||||
public enum MKVMuxerTrackType {
|
||||
VIDEO;
|
||||
}
|
||||
|
||||
public MKVMuxerTrack() {
|
||||
this.trackBlocks = new ArrayList<>();
|
||||
this.type = MKVMuxerTrackType.VIDEO;
|
||||
}
|
||||
|
||||
public int getTimescale() {
|
||||
return 1000000;
|
||||
}
|
||||
|
||||
public void addFrame(Packet outPacket) {
|
||||
MkvBlock frame = MkvBlock.keyFrame((long)this.trackNo, 0, outPacket.getData());
|
||||
frame.absoluteTimecode = outPacket.getPts() - 1L;
|
||||
this.trackBlocks.add(frame);
|
||||
}
|
||||
|
||||
public long getTrackNo() {
|
||||
return (long)this.trackNo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.jcodec.containers.mkv.util;
|
||||
|
||||
public class EbmlUtil {
|
||||
public static byte[] ebmlEncodeLen(long value, int length) {
|
||||
byte[] b = new byte[length];
|
||||
for (int idx = 0; idx < length; idx++)
|
||||
b[length - idx - 1] = (byte)(int)(value >>> 8 * idx & 0xFFL);
|
||||
b[0] = (byte)(b[0] | 128 >>> length - 1);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static byte[] ebmlEncode(long value) {
|
||||
return ebmlEncodeLen(value, ebmlLength(value));
|
||||
}
|
||||
|
||||
public static final byte[] lengthOptions = new byte[] { 0, Byte.MIN_VALUE, 64, 32, 16, 8, 4, 2, 1 };
|
||||
|
||||
public static final long one = 127L;
|
||||
|
||||
public static final long two = 16256L;
|
||||
|
||||
public static final long three = 2080768L;
|
||||
|
||||
public static final long four = 266338304L;
|
||||
|
||||
public static final long five = 34091302912L;
|
||||
|
||||
public static final long six = 4363686772736L;
|
||||
|
||||
public static final long seven = 558551906910208L;
|
||||
|
||||
public static final long eight = 71494644084506624L;
|
||||
|
||||
public static int computeLength(byte b) {
|
||||
if (b == 0)
|
||||
throw new RuntimeException("Invalid head element for ebml sequence");
|
||||
int i = 1;
|
||||
while ((b & lengthOptions[i]) == 0)
|
||||
i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final long[] ebmlLengthMasks = new long[] { 0L, 127L, 16256L, 2080768L, 266338304L, 34091302912L, 4363686772736L, 558551906910208L, 71494644084506624L };
|
||||
|
||||
public static int ebmlLength(long v) {
|
||||
if (v == 0L)
|
||||
return 1;
|
||||
int length = 8;
|
||||
while (length > 0 && (v & ebmlLengthMasks[length]) == 0L)
|
||||
length--;
|
||||
return length;
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] a) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : a)
|
||||
sb.append(String.format("0x%02x ", Integer.valueOf(b & 0xFF)));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
package org.jcodec.containers.mp3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.AudioCodecMeta;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.Demuxer;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.TrackType;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.common.model.Packet;
|
||||
|
||||
public class MPEGAudioDemuxer implements Demuxer, DemuxerTrack {
|
||||
private static final int MAX_FRAME_SIZE = 1728;
|
||||
|
||||
private static final int MIN_FRAME_SIZE = 52;
|
||||
|
||||
private static int field(int off, int size) {
|
||||
return (1 << size) - 1 << 16 | off;
|
||||
}
|
||||
|
||||
private static final int CHANNELS = field(6, 2);
|
||||
|
||||
private static final int PADDING = field(9, 1);
|
||||
|
||||
private static final int SAMPLE_RATE = field(10, 2);
|
||||
|
||||
private static final int BITRATE = field(12, 4);
|
||||
|
||||
private static final int VERSION = field(19, 2);
|
||||
|
||||
private static final int LAYER = field(17, 2);
|
||||
|
||||
private static final int SYNC = field(21, 11);
|
||||
|
||||
private static final int MPEG1 = 3;
|
||||
|
||||
private static final int MPEG2 = 2;
|
||||
|
||||
private static final int MPEG25 = 0;
|
||||
|
||||
private static int[][][] bitrateTable = new int[][][] { new int[][] { new int[] {
|
||||
0, 32, 64, 96, 128, 160, 192, 224, 256, 288,
|
||||
320, 352, 384, 416, 448 }, new int[] {
|
||||
0, 32, 48, 56, 64, 80, 96, 112, 128, 160,
|
||||
192, 224, 256, 320, 384 }, new int[] {
|
||||
0, 32, 40, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320 } }, new int[][] { new int[] {
|
||||
0, 32, 48, 56, 64, 80, 96, 112, 128, 144,
|
||||
160, 176, 192, 224, 256 }, new int[] {
|
||||
0, 8, 16, 24, 32, 40, 48, 56, 64, 80,
|
||||
96, 112, 128, 144, 160 }, new int[] {
|
||||
0, 8, 16, 24, 32, 40, 48, 56, 64, 80,
|
||||
96, 112, 128, 144, 160 } } };
|
||||
|
||||
private static int[] freqTab = new int[] { 44100, 48000, 32000 };
|
||||
|
||||
private static int[] rateReductTab = new int[] { 2, 0, 1, 0 };
|
||||
|
||||
private SeekableByteChannel ch;
|
||||
|
||||
private List<DemuxerTrack> tracks;
|
||||
|
||||
private int frameNo;
|
||||
|
||||
private ByteBuffer readBuffer;
|
||||
|
||||
private int runningFour;
|
||||
|
||||
private boolean eof;
|
||||
|
||||
private DemuxerTrackMeta meta;
|
||||
|
||||
private int sampleRate;
|
||||
|
||||
private static int getField(int header, int field) {
|
||||
return header >> (field & 0xFFFF) & field >> 16;
|
||||
}
|
||||
|
||||
public MPEGAudioDemuxer(SeekableByteChannel ch) throws IOException {
|
||||
this.ch = ch;
|
||||
this.readBuffer = ByteBuffer.allocate(262144);
|
||||
readMoreData();
|
||||
if (this.readBuffer.remaining() < 4) {
|
||||
this.eof = true;
|
||||
} else {
|
||||
this.runningFour = this.readBuffer.getInt();
|
||||
if (!validHeader(this.runningFour))
|
||||
this.eof = skipJunk();
|
||||
extractMeta();
|
||||
}
|
||||
this.tracks = new ArrayList<>();
|
||||
this.tracks.add(this);
|
||||
}
|
||||
|
||||
private void extractMeta() {
|
||||
if (!validHeader(this.runningFour))
|
||||
return;
|
||||
int layer = 3 - getField(this.runningFour, LAYER);
|
||||
int channelCount = (getField(this.runningFour, CHANNELS) == 3) ? 1 : 2;
|
||||
int version = getField(this.runningFour, VERSION);
|
||||
this.sampleRate = freqTab[getField(this.runningFour, SAMPLE_RATE)] >> rateReductTab[version];
|
||||
AudioCodecMeta codecMeta = AudioCodecMeta.createAudioCodecMeta(".mp3", 16, channelCount, this.sampleRate, ByteOrder.LITTLE_ENDIAN, false, null, null);
|
||||
Codec codec = (layer == 2) ? Codec.MP3 : ((layer == 1) ? Codec.MP2 : Codec.MP1);
|
||||
this.meta = new DemuxerTrackMeta(TrackType.AUDIO, codec, 0.0D, null, 0, null, null, codecMeta);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.ch.close();
|
||||
}
|
||||
|
||||
public List<? extends DemuxerTrack> getTracks() {
|
||||
return this.tracks;
|
||||
}
|
||||
|
||||
public List<? extends DemuxerTrack> getVideoTracks() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<? extends DemuxerTrack> getAudioTracks() {
|
||||
return this.tracks;
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
if (this.eof)
|
||||
return null;
|
||||
if (!validHeader(this.runningFour))
|
||||
this.eof = skipJunk();
|
||||
int frameSize = calcFrameSize(this.runningFour);
|
||||
ByteBuffer frame = ByteBuffer.allocate(frameSize);
|
||||
this.eof = readFrame(frame);
|
||||
frame.flip();
|
||||
Packet pkt = new Packet(frame, (long)(this.frameNo * 1152), this.sampleRate, 1152L, (long)this.frameNo, Packet.FrameType.KEY, null, 0);
|
||||
this.frameNo++;
|
||||
return pkt;
|
||||
}
|
||||
|
||||
private static boolean validHeader(int four) {
|
||||
if (getField(four, SYNC) != 2047)
|
||||
return false;
|
||||
if (getField(four, LAYER) == 0)
|
||||
return false;
|
||||
if (getField(four, SAMPLE_RATE) == 3)
|
||||
return false;
|
||||
if (getField(four, BITRATE) == 15)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readMoreData() throws IOException {
|
||||
this.readBuffer.clear();
|
||||
this.ch.read(this.readBuffer);
|
||||
this.readBuffer.flip();
|
||||
}
|
||||
|
||||
private static int calcFrameSize(int header) {
|
||||
int bitrateIdx = getField(header, BITRATE);
|
||||
int layer = 3 - getField(header, LAYER);
|
||||
int version = getField(header, VERSION);
|
||||
int mpeg2 = (version != 3) ? 1 : 0;
|
||||
int bitRate = bitrateTable[mpeg2][layer][bitrateIdx] * 1000;
|
||||
int sampleRate = freqTab[getField(header, SAMPLE_RATE)] >> rateReductTab[version];
|
||||
int padding = getField(header, PADDING);
|
||||
int lsf = (version == 0 || version == 2) ? 1 : 0;
|
||||
switch (layer) {
|
||||
case 0:
|
||||
return (bitRate * 12 / sampleRate + padding) * 4;
|
||||
case 1:
|
||||
return bitRate * 144 / sampleRate + padding;
|
||||
}
|
||||
return bitRate * 144 / (sampleRate << lsf) + padding;
|
||||
}
|
||||
|
||||
private boolean readFrame(ByteBuffer frame) throws IOException {
|
||||
boolean eof = false;
|
||||
while (frame.hasRemaining()) {
|
||||
frame.put((byte)(this.runningFour >> 24));
|
||||
this.runningFour <<= 8;
|
||||
if (!this.readBuffer.hasRemaining())
|
||||
readMoreData();
|
||||
if (this.readBuffer.hasRemaining()) {
|
||||
this.runningFour |= this.readBuffer.get() & 0xFF;
|
||||
continue;
|
||||
}
|
||||
eof = true;
|
||||
}
|
||||
return eof;
|
||||
}
|
||||
|
||||
private boolean skipJunk() throws IOException {
|
||||
boolean eof = false;
|
||||
int total = 0;
|
||||
while (!validHeader(this.runningFour)) {
|
||||
if (!this.readBuffer.hasRemaining())
|
||||
readMoreData();
|
||||
if (!this.readBuffer.hasRemaining()) {
|
||||
eof = true;
|
||||
break;
|
||||
}
|
||||
this.runningFour <<= 8;
|
||||
this.runningFour |= this.readBuffer.get() & 0xFF;
|
||||
total++;
|
||||
}
|
||||
Logger.warn(String.format("[mp3demuxer] Skipped %d bytes of junk", total));
|
||||
return eof;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return this.meta;
|
||||
}
|
||||
|
||||
@UsedViaReflection
|
||||
public static int probe(ByteBuffer b) {
|
||||
ByteBuffer fork = b.duplicate();
|
||||
int valid = 0, total = 0;
|
||||
int header = fork.getInt();
|
||||
do {
|
||||
if (!validHeader(header))
|
||||
header = skipJunkBB(header, fork);
|
||||
int size = calcFrameSize(header);
|
||||
if (fork.remaining() < size)
|
||||
break;
|
||||
total++;
|
||||
if (size > 0) {
|
||||
NIOUtils.skip(fork, size - 4);
|
||||
} else {
|
||||
header = skipJunkBB(header, fork);
|
||||
}
|
||||
if (fork.remaining() < 4)
|
||||
continue;
|
||||
header = fork.getInt();
|
||||
if (size < 52 || size > 1728 || !validHeader(header))
|
||||
continue;
|
||||
valid++;
|
||||
} while (fork.remaining() >= 4);
|
||||
return 100 * valid / total;
|
||||
}
|
||||
|
||||
private static int skipJunkBB(int header, ByteBuffer fork) {
|
||||
while (!validHeader(header) && fork.hasRemaining()) {
|
||||
header <<= 8;
|
||||
header |= fork.get() & 0xFF;
|
||||
}
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
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