mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-30 22:45:46 +00:00
trunk contents moved to root
This commit is contained in:
39
libsrc/avi/src/org/monte/media/AbortException.java
Normal file
39
libsrc/avi/src/org/monte/media/AbortException.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* @(#)AbortException.java
|
||||
*
|
||||
* Copyright (c) 1999-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
/**
|
||||
* This exception is thrown when the production of an image
|
||||
* has been aborted.
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
|
||||
*
|
||||
* @version $Id: AbortException.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class AbortException extends Exception {
|
||||
|
||||
public static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
Creates a new exception.
|
||||
*/
|
||||
public AbortException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new exception.
|
||||
|
||||
*/
|
||||
public AbortException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
99
libsrc/avi/src/org/monte/media/AbstractCodec.java
Normal file
99
libsrc/avi/src/org/monte/media/AbstractCodec.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.util.ArrayList;
|
||||
/**
|
||||
* {@code AbstractCodec}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-03-12 Created.
|
||||
*/
|
||||
public abstract class AbstractCodec implements Codec {
|
||||
|
||||
protected Format[] inputFormats;
|
||||
protected Format[] outputFormats;
|
||||
protected Format inputFormat;
|
||||
protected Format outputFormat;
|
||||
protected String name="unnamed codec";
|
||||
|
||||
public AbstractCodec(Format[] supportedInputFormats, Format[] supportedOutputFormats) {
|
||||
this.inputFormats = supportedInputFormats;
|
||||
this.outputFormats = supportedOutputFormats;
|
||||
}
|
||||
public AbstractCodec(Format[] supportedInputOutputFormats) {
|
||||
this.inputFormats = supportedInputOutputFormats;
|
||||
this.outputFormats = supportedInputOutputFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format[] getInputFormats() {
|
||||
return inputFormats.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format[] getOutputFormats(Format input) {
|
||||
ArrayList<Format>of=new ArrayList<Format>(outputFormats.length);
|
||||
for (Format f:outputFormats) {
|
||||
of.add(input==null?f:f.append(input));
|
||||
}
|
||||
return of.toArray(new Format[of.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format setInputFormat(Format f) {
|
||||
if (f!=null)
|
||||
for (Format sf : getInputFormats()) {
|
||||
if (sf.matches(f)) {
|
||||
this.inputFormat = sf.append(f);
|
||||
return inputFormat;
|
||||
}
|
||||
}
|
||||
this.inputFormat=null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format setOutputFormat(Format f) {
|
||||
for (Format sf : getOutputFormats(f)) {
|
||||
if (sf.matches(f)) {
|
||||
this.outputFormat = f;
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
this.outputFormat=null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getInputFormat() {
|
||||
return inputFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Empty implementation of the reset method. Don't call super. */
|
||||
@Override
|
||||
public void reset() {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String className=getClass().getName();
|
||||
int p=className.lastIndexOf('.');
|
||||
return className.substring(p+1)+"{" + "inputFormat=" + inputFormat + ", outputFormat=" + outputFormat+'}';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
226
libsrc/avi/src/org/monte/media/AbstractVideoCodec.java
Normal file
226
libsrc/avi/src/org/monte/media/AbstractVideoCodec.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* @(#)AbstractVideoCodec.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.DataBufferShort;
|
||||
import java.awt.image.DataBufferUShort;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
|
||||
/**
|
||||
* {@code AbstractVideoCodec}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: AbstractVideoCodec.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public abstract class AbstractVideoCodec extends AbstractCodec {
|
||||
|
||||
private BufferedImage imgConverter;
|
||||
|
||||
public AbstractVideoCodec(Format[] supportedInputFormats, Format[] supportedOutputFormats) {
|
||||
super(supportedInputFormats, supportedOutputFormats);
|
||||
}
|
||||
|
||||
/** Gets 8-bit indexed pixels from a buffer. Returns null if conversion failed. */
|
||||
protected byte[] getIndexed8(Buffer buf) {
|
||||
if (buf.data instanceof byte[]) {
|
||||
return (byte[]) buf.data;
|
||||
}
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) buf.data;
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferByte) {
|
||||
return ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Gets 15-bit RGB pixels from a buffer. Returns null if conversion failed. */
|
||||
protected short[] getRGB15(Buffer buf) {
|
||||
if (buf.data instanceof int[]) {
|
||||
return (short[]) buf.data;
|
||||
}
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) buf.data;
|
||||
if (image.getColorModel() instanceof DirectColorModel) {
|
||||
DirectColorModel dcm = (DirectColorModel) image.getColorModel();
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferShort) {
|
||||
// FIXME - Implement additional checks
|
||||
return ((DataBufferShort) image.getRaster().getDataBuffer()).getData();
|
||||
} else if (image.getRaster().getDataBuffer() instanceof DataBufferUShort) {
|
||||
// FIXME - Implement additional checks
|
||||
return ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
}
|
||||
if (imgConverter == null) {
|
||||
int width = outputFormat.get(WidthKey);
|
||||
int height = outputFormat.get(HeightKey);
|
||||
imgConverter = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_555_RGB);
|
||||
}
|
||||
Graphics2D g = imgConverter.createGraphics();
|
||||
g.drawImage(image, 0, 0, null);
|
||||
g.dispose();
|
||||
return ((DataBufferUShort) imgConverter.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/** Gets 16-bit RGB-5-6-5 pixels from a buffer. Returns null if conversion failed. */
|
||||
protected short[] getRGB16(Buffer buf) {
|
||||
if (buf.data instanceof int[]) {
|
||||
return (short[]) buf.data;
|
||||
}
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) buf.data;
|
||||
if (image.getColorModel() instanceof DirectColorModel) {
|
||||
DirectColorModel dcm = (DirectColorModel) image.getColorModel();
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferShort) {
|
||||
// FIXME - Implement additional checks
|
||||
return ((DataBufferShort) image.getRaster().getDataBuffer()).getData();
|
||||
} else if (image.getRaster().getDataBuffer() instanceof DataBufferUShort) {
|
||||
// FIXME - Implement additional checks
|
||||
return ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
}
|
||||
if (imgConverter == null) {
|
||||
int width = outputFormat.get(WidthKey);
|
||||
int height = outputFormat.get(HeightKey);
|
||||
imgConverter = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);
|
||||
}
|
||||
Graphics2D g = imgConverter.createGraphics();
|
||||
g.drawImage(image, 0, 0, null);
|
||||
g.dispose();
|
||||
return ((DataBufferUShort) imgConverter.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/** Gets 24-bit RGB pixels from a buffer. Returns null if conversion failed. */
|
||||
protected int[] getRGB24(Buffer buf) {
|
||||
if (buf.data instanceof int[]) {
|
||||
return (int[]) buf.data;
|
||||
}
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) buf.data;
|
||||
if (image.getColorModel() instanceof DirectColorModel) {
|
||||
DirectColorModel dcm = (DirectColorModel) image.getColorModel();
|
||||
if (dcm.getBlueMask() == 0xff && dcm.getGreenMask() == 0xff00 && dcm.getRedMask() == 0xff0000) {
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
||||
return ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
return image.getRGB(0, 0, //
|
||||
outputFormat.get(WidthKey), outputFormat.get(HeightKey), //
|
||||
null, 0, outputFormat.get(WidthKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Gets 32-bit ARGB pixels from a buffer. Returns null if conversion failed. */
|
||||
protected int[] getARGB32(Buffer buf) {
|
||||
if (buf.data instanceof int[]) {
|
||||
return (int[]) buf.data;
|
||||
}
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) buf.data;
|
||||
if (image.getColorModel() instanceof DirectColorModel) {
|
||||
DirectColorModel dcm = (DirectColorModel) image.getColorModel();
|
||||
if (dcm.getBlueMask() == 0xff && dcm.getGreenMask() == 0xff00 && dcm.getRedMask() == 0xff0000) {
|
||||
if (image.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
||||
return ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
return image.getRGB(0, 0, //
|
||||
outputFormat.get(WidthKey), outputFormat.get(HeightKey), //
|
||||
null, 0, outputFormat.get(WidthKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Gets a buffered image from a buffer. Returns null if conversion failed. */
|
||||
protected BufferedImage getBufferedImage(Buffer buf) {
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
return (BufferedImage) buf.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private byte[] byteBuf = new byte[4];
|
||||
|
||||
protected void writeInt24(ImageOutputStream out, int v) throws IOException {
|
||||
byteBuf[0] = (byte) (v >>> 16);
|
||||
byteBuf[1] = (byte) (v >>> 8);
|
||||
byteBuf[2] = (byte) (v >>> 0);
|
||||
out.write(byteBuf, 0, 3);
|
||||
}
|
||||
|
||||
protected void writeInt24LE(ImageOutputStream out, int v) throws IOException {
|
||||
byteBuf[2] = (byte) (v >>> 16);
|
||||
byteBuf[1] = (byte) (v >>> 8);
|
||||
byteBuf[0] = (byte) (v >>> 0);
|
||||
out.write(byteBuf, 0, 3);
|
||||
}
|
||||
|
||||
protected void writeInts24(ImageOutputStream out, int[] i, int off, int len) throws IOException {
|
||||
// Fix 4430357 - if off + len < 0, overflow occurred
|
||||
if (off < 0 || len < 0 || off + len > i.length || off + len < 0) {
|
||||
throw new IndexOutOfBoundsException("off < 0 || len < 0 || off + len > i.length!");
|
||||
}
|
||||
|
||||
byte[] b = new byte[len * 3];
|
||||
int boff = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
int v = i[off + j];
|
||||
//b[boff++] = (byte)(v >>> 24);
|
||||
b[boff++] = (byte) (v >>> 16);
|
||||
b[boff++] = (byte) (v >>> 8);
|
||||
b[boff++] = (byte) (v >>> 0);
|
||||
}
|
||||
|
||||
out.write(b, 0, len * 3);
|
||||
}
|
||||
|
||||
protected void writeInts24LE(ImageOutputStream out, int[] i, int off, int len) throws IOException {
|
||||
// Fix 4430357 - if off + len < 0, overflow occurred
|
||||
if (off < 0 || len < 0 || off + len > i.length || off + len < 0) {
|
||||
throw new IndexOutOfBoundsException("off < 0 || len < 0 || off + len > i.length!");
|
||||
}
|
||||
|
||||
byte[] b = new byte[len * 3];
|
||||
int boff = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
int v = i[off + j];
|
||||
b[boff++] = (byte) (v >>> 0);
|
||||
b[boff++] = (byte) (v >>> 8);
|
||||
b[boff++] = (byte) (v >>> 16);
|
||||
//b[boff++] = (byte)(v >>> 24);
|
||||
}
|
||||
|
||||
out.write(b, 0, len * 3);
|
||||
}
|
||||
|
||||
/** Copies a buffered image. */
|
||||
protected static BufferedImage copyImage(BufferedImage img) {
|
||||
ColorModel cm = img.getColorModel();
|
||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
||||
WritableRaster raster = img.copyData(null);
|
||||
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
}
|
||||
}
|
||||
126
libsrc/avi/src/org/monte/media/AudioFormatKeys.java
Normal file
126
libsrc/avi/src/org/monte/media/AudioFormatKeys.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* @(#)AudioFormatKeys.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance onlyWith the
|
||||
* license agreement you entered into onlyWith Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import org.monte.media.math.Rational;
|
||||
import java.nio.ByteOrder;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioFormat.Encoding;
|
||||
|
||||
/**
|
||||
* Defines common format keys for audio media.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: AudioFormatKeys.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class AudioFormatKeys extends FormatKeys {
|
||||
// Standard video EncodingKey strings for use onlyWith FormatKey.Encoding.
|
||||
|
||||
/**
|
||||
* Specifies SignedKey, linear PCM data.
|
||||
*/
|
||||
public static final String ENCODING_PCM_SIGNED = javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED.toString();
|
||||
/**
|
||||
* Specifies unsigned, linear PCM data.
|
||||
*/
|
||||
public static final String ENCODING_PCM_UNSIGNED = javax.sound.sampled.AudioFormat.Encoding.PCM_UNSIGNED.toString();
|
||||
/**
|
||||
* Specifies u-law encoded data.
|
||||
*/
|
||||
public static final String ENCODING_ULAW = javax.sound.sampled.AudioFormat.Encoding.ULAW.toString();
|
||||
/**
|
||||
* Specifies a-law encoded data.
|
||||
*/
|
||||
public static final String ENCODING_ALAW = javax.sound.sampled.AudioFormat.Encoding.ALAW.toString();
|
||||
/**
|
||||
* AVI PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_AVI_PCM = "\u0000\u0000\u0000\u0001";
|
||||
/**
|
||||
* QuickTime 16-bit big endian signed PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_QUICKTIME_TWOS_PCM = "twos";
|
||||
/**
|
||||
* QuickTime 16-bit little endian signed PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_QUICKTIME_SOWT_PCM = "sowt";
|
||||
/**
|
||||
* QuickTime 24-bit big endian signed PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_QUICKTIME_IN24_PCM = "in24";
|
||||
/**
|
||||
* QuickTime 32-bit big endian signed PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_QUICKTIME_IN32_PCM = "in32";
|
||||
/**
|
||||
* QuickTime 8-bit unsigned PCM encoding.
|
||||
*/
|
||||
public static final String ENCODING_QUICKTIME_RAW_PCM = "raw ";
|
||||
/**
|
||||
* Specifies MP3 encoded data.
|
||||
*/
|
||||
public static final String ENCODING_MP3 = "MP3";
|
||||
/**
|
||||
* The sample size in bits.
|
||||
*/
|
||||
public final static FormatKey<Integer> SampleSizeInBitsKey = new FormatKey<Integer>("sampleSizeInBits", Integer.class);
|
||||
/**
|
||||
* The numer of ChannelsKey.
|
||||
*/
|
||||
public final static FormatKey<Integer> ChannelsKey = new FormatKey<Integer>("channels", Integer.class);
|
||||
/**
|
||||
* The size of a frame.
|
||||
*/
|
||||
public final static FormatKey<Integer> FrameSizeKey = new FormatKey<Integer>("frameSize", Integer.class);
|
||||
/**
|
||||
* The compressor name.
|
||||
*/
|
||||
public final static FormatKey<ByteOrder> ByteOrderKey = new FormatKey<ByteOrder>("byteOrder", ByteOrder.class);
|
||||
/**
|
||||
* The number of frames per second.
|
||||
*/
|
||||
public final static FormatKey<Rational> SampleRateKey = new FormatKey<Rational>("sampleRate", Rational.class);
|
||||
/**
|
||||
* Whether values are signed.
|
||||
*/
|
||||
public final static FormatKey<Boolean> SignedKey = new FormatKey<Boolean>("signed", Boolean.class);
|
||||
/**
|
||||
* Whether silence is encoded as -128 instead of 0.
|
||||
*/
|
||||
public final static FormatKey<Boolean> SilenceBugKey = new FormatKey<Boolean>("silenceBug", Boolean.class);
|
||||
|
||||
public static Format fromAudioFormat(javax.sound.sampled.AudioFormat fmt) {
|
||||
return new Format(
|
||||
MediaTypeKey, MediaType.AUDIO,
|
||||
EncodingKey, fmt.getEncoding().toString(),
|
||||
SampleRateKey, Rational.valueOf(fmt.getSampleRate()),
|
||||
SampleSizeInBitsKey, fmt.getSampleSizeInBits(),
|
||||
ChannelsKey, fmt.getChannels(),
|
||||
FrameSizeKey, fmt.getFrameSize(),
|
||||
FrameRateKey, Rational.valueOf(fmt.getFrameRate()),
|
||||
ByteOrderKey, fmt.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN,
|
||||
SignedKey, AudioFormat.Encoding.PCM_SIGNED.equals(fmt.getEncoding())//,
|
||||
//
|
||||
);
|
||||
}
|
||||
|
||||
public static javax.sound.sampled.AudioFormat toAudioFormat(Format fmt) {
|
||||
// We always use PCM_SIGNED or PCM_UNSIGNED
|
||||
return new javax.sound.sampled.AudioFormat(
|
||||
!fmt.containsKey(SignedKey) || fmt.get(SignedKey) ? Encoding.PCM_SIGNED : Encoding.PCM_UNSIGNED,
|
||||
fmt.get(SampleRateKey).floatValue(),
|
||||
fmt.get(SampleSizeInBitsKey, 16),
|
||||
fmt.get(ChannelsKey, 1),
|
||||
fmt.containsKey(FrameSizeKey) ? fmt.get(FrameSizeKey) : (fmt.get(SampleSizeInBitsKey, 16) + 7) / 8 * fmt.get(ChannelsKey, 1),
|
||||
fmt.containsKey(FrameRateKey) ? fmt.get(FrameRateKey).floatValue() : fmt.get(SampleRateKey).floatValue(),
|
||||
fmt.containsKey(ByteOrderKey) ? fmt.get(ByteOrderKey) == ByteOrder.BIG_ENDIAN : true);
|
||||
}
|
||||
}
|
||||
164
libsrc/avi/src/org/monte/media/Buffer.java
Normal file
164
libsrc/avi/src/org/monte/media/Buffer.java
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.monte.media.math.Rational;
|
||||
import org.monte.media.util.Methods;
|
||||
|
||||
/**
|
||||
* A {@code Buffer} carries media data from one media processing unit to another.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-03-12 Created.
|
||||
*/
|
||||
public class Buffer {
|
||||
|
||||
/** A flag mask that describes the boolean attributes for this buffer.
|
||||
*/
|
||||
public EnumSet<BufferFlag> flags = EnumSet.noneOf(BufferFlag.class);
|
||||
/** Values which are not specified must have this value. */
|
||||
public static final int NOT_SPECIFIED = -1;
|
||||
/** The track number.
|
||||
* This can be set to NOT_SPECIFIED or to a number >= 0.
|
||||
*/
|
||||
public int track;
|
||||
/** Header information, such as RTP header for this chunk. */
|
||||
public Object header;
|
||||
/** The media data. */
|
||||
public Object data;
|
||||
/** The data offset. This field is only used if {@code data} is an array. */
|
||||
public int offset;
|
||||
/** The data length. This field is only used if {@code data} is an array. */
|
||||
public int length;
|
||||
/** Duration of a sample in seconds.
|
||||
* Multiply this with {@code sampleCount} to get the buffer duration.
|
||||
*/
|
||||
public Rational sampleDuration;
|
||||
/** The time stamp of this buffer in seconds. */
|
||||
public Rational timeStamp;
|
||||
/** The format of the data in this buffer. */
|
||||
public Format format;
|
||||
/** The number of samples in the data field. */
|
||||
public int sampleCount = 1;
|
||||
|
||||
/** Sequence number of the buffer. This can be used for debugging. */
|
||||
public long sequenceNumber;
|
||||
|
||||
/** Sets all variables of this buffer to that buffer except for {@code data},
|
||||
* {@code offset}, {@code length} and {@code header}.
|
||||
*/
|
||||
public void setMetaTo(Buffer that) {
|
||||
this.flags = EnumSet.copyOf(that.flags);
|
||||
//this.data=that.data;
|
||||
//this.offset=that.offset;
|
||||
//this.length=that.length;
|
||||
//this.header=that.header;
|
||||
this.track = that.track;
|
||||
this.sampleDuration = that.sampleDuration;
|
||||
this.timeStamp = that.timeStamp;
|
||||
this.format = that.format;
|
||||
this.sampleCount = that.sampleCount;
|
||||
this.format = that.format;
|
||||
this.sequenceNumber=that.sequenceNumber;
|
||||
}
|
||||
|
||||
/** Sets {@code data}, {@code offset}, {@code length} and {@code header}
|
||||
* of this buffer to that buffer.
|
||||
* Note that this method creates copies of the {@code data} and
|
||||
* {@code header}, so that these fields in that buffer can be discarded
|
||||
* without affecting the contents of this buffer.
|
||||
* <p>
|
||||
* FIXME - This method does not always create a copy!!
|
||||
*/
|
||||
public void setDataTo(Buffer that) {
|
||||
this.offset = that.offset;
|
||||
this.length = that.length;
|
||||
this.data = copy(that.data, this.data);
|
||||
this.header = copy(that.header, this.header);
|
||||
|
||||
}
|
||||
|
||||
private Object copy(Object from, Object into) {
|
||||
if (from instanceof byte[]) {
|
||||
byte[] b=(byte[])from;
|
||||
if (!(into instanceof byte[]) || ((byte[]) into).length < b.length) {
|
||||
into = new byte[b.length];
|
||||
}
|
||||
System.arraycopy(b, 0, (byte[])into, 0, b.length);
|
||||
} else if (from instanceof BufferedImage) {
|
||||
// FIXME - Try to reuse BufferedImage in output!
|
||||
BufferedImage img = (BufferedImage) from;
|
||||
ColorModel cm = img.getColorModel();
|
||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
||||
WritableRaster raster = img.copyData(null);
|
||||
into = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
} else if (from instanceof Cloneable) {
|
||||
try {
|
||||
into=Methods.invoke(from, "clone");
|
||||
} catch (NoSuchMethodException ex) {
|
||||
into=from;
|
||||
}
|
||||
} else {
|
||||
// FIXME - This is very fragile, since we do not know, if the
|
||||
// input data stays valid until the output data is processed!
|
||||
into = from;
|
||||
}
|
||||
|
||||
return into;
|
||||
}
|
||||
|
||||
/** Returns true if the specified flag is set. */
|
||||
public boolean isFlag(BufferFlag flag) {
|
||||
return flags.contains(flag);
|
||||
}
|
||||
|
||||
/** Convenience method for setting a flag. */
|
||||
public void setFlag(BufferFlag flag) {
|
||||
setFlag(flag, true);
|
||||
}
|
||||
|
||||
/** Convenience method for clearing a flag. */
|
||||
public void clearFlag(BufferFlag flag) {
|
||||
setFlag(flag, false);
|
||||
}
|
||||
|
||||
/** Sets or clears the specified flag. */
|
||||
public void setFlag(BufferFlag flag, boolean value) {
|
||||
if (value) {
|
||||
flags.add(flag);
|
||||
} else {
|
||||
flags.remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all flags, and then sets the specified flag. */
|
||||
public void setFlagsTo(BufferFlag... flags) {
|
||||
if (flags.length == 0) {
|
||||
this.flags = EnumSet.noneOf(BufferFlag.class);
|
||||
} else {
|
||||
this.flags = EnumSet.copyOf(Arrays.asList(flags));
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears all flags, and then sets the specified flag. */
|
||||
public void setFlagsTo(EnumSet<BufferFlag> flags) {
|
||||
if (flags == null) {
|
||||
this.flags = EnumSet.noneOf(BufferFlag.class);
|
||||
} else {
|
||||
this.flags = EnumSet.copyOf(flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearFlags() {
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
42
libsrc/avi/src/org/monte/media/BufferFlag.java
Normal file
42
libsrc/avi/src/org/monte/media/BufferFlag.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* @(#)BufferFlag.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
/**
|
||||
* {@code BufferFlag}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: BufferFlag.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public enum BufferFlag {
|
||||
|
||||
/** Indicates that the data in this buffer should be ignored. */
|
||||
DISCARD,
|
||||
/** Indicates that this Buffer holds an intra-coded picture, which can be
|
||||
* decoded independently. */
|
||||
KEYFRAME,
|
||||
/** Indicates that the data in this buffer is at the end of the media. */
|
||||
END_OF_MEDIA,
|
||||
/** Indicates that the data in this buffer is used for initializing the
|
||||
* decoding queue.
|
||||
* <p>
|
||||
* This flag is used when the media time of a track is set to a non-keyframe
|
||||
* sample. Thus decoding must start at a keyframe at an earlier time.
|
||||
* <p>
|
||||
* Decoders should decode the buffer.
|
||||
* Encoders and Multiplexers should discard the buffer.
|
||||
*/
|
||||
PREFETCH,
|
||||
/** Indicates that this buffer is known to have the same data as the
|
||||
* previous buffer. This may improve encoding performance.
|
||||
*/
|
||||
SAME_DATA;
|
||||
}
|
||||
73
libsrc/avi/src/org/monte/media/Codec.java
Normal file
73
libsrc/avi/src/org/monte/media/Codec.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
/**
|
||||
* A {@code Codec} processes a {@code Buffer} and stores the result in another
|
||||
* {@code Buffer}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-03-12 Created.
|
||||
*/
|
||||
public interface Codec {
|
||||
|
||||
/** The codec successfully converted the input to output. */
|
||||
public final static int CODEC_OK = 0;
|
||||
/** The codec could not handle the input. */
|
||||
public final static int CODEC_FAILED = 1;
|
||||
/** The codec did not fully consume the input buffer.
|
||||
* The codec has updated the input buffer to
|
||||
* reflect the amount of data that it has processed.
|
||||
* The codec must be called again with the same input buffer.
|
||||
*/
|
||||
public final static int CODEC_INPUT_NOT_CONSUMED = 2;
|
||||
/** The codec did not fully fill the output buffer.
|
||||
* The codec has updated the output buffer to
|
||||
* reflect the amount of data that it has processed.
|
||||
* The codec must be called again with the same output buffer.
|
||||
*/
|
||||
public final static int CODEC_OUTPUT_NOT_FILLED = 4;
|
||||
|
||||
/** Lists all of the input formats that this codec accepts. */
|
||||
public Format[] getInputFormats();
|
||||
|
||||
/** Lists all of the output formats that this codec can generate
|
||||
* with the provided input format. If the input format is null, returns
|
||||
* all supported output formats.
|
||||
*/
|
||||
public Format[] getOutputFormats(Format input);
|
||||
|
||||
/** Sets the input format.
|
||||
* Returns the format that was actually set. This is the closest format
|
||||
* that the Codec supports. Returns null if the specified format is not
|
||||
* supported and no reasonable match could be found.
|
||||
*/
|
||||
public Format setInputFormat(Format input);
|
||||
|
||||
public Format getInputFormat();
|
||||
|
||||
/** Sets the output format.
|
||||
* Returns the format that was actually set. This is the closest format
|
||||
* that the Codec supports. Returns null if the specified format is not
|
||||
* supported and no reasonable match could be found.
|
||||
*/
|
||||
public Format setOutputFormat(Format output);
|
||||
|
||||
public Format getOutputFormat();
|
||||
|
||||
/** Performs the media processing defined by this codec.
|
||||
* <p>
|
||||
* Copies the data from the input buffer into the output buffer.
|
||||
*
|
||||
* @return A combination of processing flags.
|
||||
*/
|
||||
public int process(Buffer in, Buffer out);
|
||||
|
||||
/** Returns a human readable name of the codec. */
|
||||
public String getName();
|
||||
|
||||
/** Resets the state of the codec. */
|
||||
public void reset();
|
||||
}
|
||||
395
libsrc/avi/src/org/monte/media/DefaultRegistry.java
Normal file
395
libsrc/avi/src/org/monte/media/DefaultRegistry.java
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* @(#)DefaultRegistry.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance onlyWith the
|
||||
* license agreement you entered into onlyWith Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
import static org.monte.media.AudioFormatKeys.*;
|
||||
|
||||
/**
|
||||
* {@code DefaultRegistry}.
|
||||
* <p>
|
||||
* FIXME - The registry should be read from a file.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: DefaultRegistry.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class DefaultRegistry extends Registry {
|
||||
|
||||
private HashMap<String, LinkedList<RegistryEntry>> codecMap;
|
||||
private HashMap<String, LinkedList<RegistryEntry>> readerMap;
|
||||
private HashMap<String, LinkedList<RegistryEntry>> writerMap;
|
||||
private HashMap<String, Format> fileFormatMap;
|
||||
|
||||
@Override
|
||||
public Format[] getReaderFormats() {
|
||||
return getFileFormats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format[] getWriterFormats() {
|
||||
return getFileFormats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format[] getFileFormats() {
|
||||
return fileFormatMap.values().toArray(new Format[fileFormatMap.size()]);
|
||||
}
|
||||
|
||||
private static class RegistryEntry {
|
||||
|
||||
Format inputFormat;
|
||||
Format outputFormat;
|
||||
String className;
|
||||
|
||||
public RegistryEntry(Format inputFormat, Format outputFormat, String className) {
|
||||
this.inputFormat = inputFormat;
|
||||
this.outputFormat = outputFormat;
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultRegistry() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
codecMap = new HashMap<String, LinkedList<RegistryEntry>>();
|
||||
readerMap = new HashMap<String, LinkedList<RegistryEntry>>();
|
||||
writerMap = new HashMap<String, LinkedList<RegistryEntry>>();
|
||||
fileFormatMap = new HashMap<String, Format>();
|
||||
|
||||
// IFF ANIM
|
||||
// --------
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_ANIM, EncodingKey, ENCODING_BITMAP_IMAGE),
|
||||
"org.monte.media.anim.BitmapCodec");
|
||||
|
||||
// AVI
|
||||
// --------
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_DIB),
|
||||
"org.monte.media.avi.DIBCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_MJPG),
|
||||
"org.monte.media.jpeg.JPEGCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_PNG),
|
||||
"org.monte.media.png.PNGCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_RLE),
|
||||
"org.monte.media.avi.RunLengthCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
"org.monte.media.avi.TechSmithCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_DOSBOX_SCREEN_CAPTURE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
"org.monte.media.avi.ZMBVCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_PCM),
|
||||
"org.monte.media.avi.AVIPCMAudioCodec");
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_PCM),
|
||||
"org.monte.media.avi.AVIPCMAudioCodec");
|
||||
|
||||
// QuickTime
|
||||
// --------
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_RAW),
|
||||
"org.monte.media.quicktime.RawCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_ANIMATION),
|
||||
"org.monte.media.quicktime.AnimationCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_JPEG),
|
||||
"org.monte.media.jpeg.JPEGCodec");
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI, EncodingKey, ENCODING_AVI_MJPG),
|
||||
"org.monte.media.jpeg.JPEGCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_PNG),
|
||||
"org.monte.media.png.PNGCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME,
|
||||
EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, CompressorNameKey, COMPRESSOR_NAME_AVI_TECHSMITH_SCREEN_CAPTURE),
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_BUFFERED_IMAGE),
|
||||
"org.monte.media.avi.TechSmithCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_TWOS_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_TWOS_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_SOWT_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
putCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_SOWT_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_IN24_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_IN24_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_IN32_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_IN32_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_SIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_RAW_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
putBidiCodec(
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_JAVA, EncodingKey, ENCODING_PCM_UNSIGNED),
|
||||
new Format(MediaTypeKey, MediaType.AUDIO, MimeTypeKey, MIME_QUICKTIME, EncodingKey, ENCODING_QUICKTIME_RAW_PCM),
|
||||
"org.monte.media.quicktime.QuickTimePCMAudioCodec");
|
||||
|
||||
|
||||
putReader(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_AVI), "org.monte.media.avi.AVIReader");
|
||||
putReader(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_QUICKTIME), "org.monte.media.quicktime.QuickTimeReader");
|
||||
putReader(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_ANIM), "org.monte.media.anim.ANIMReader");
|
||||
putWriter(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_AVI), "org.monte.media.avi.AVIWriter");
|
||||
putWriter(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_QUICKTIME), "org.monte.media.quicktime.QuickTimeWriter");
|
||||
putWriter(new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_ANIM), "org.monte.media.anim.ANIMWriter");
|
||||
|
||||
putFileFormat("avi", new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_AVI));
|
||||
putFileFormat("mov", new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_QUICKTIME));
|
||||
putFileFormat("anim", new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_ANIM));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inputFormat Must have {@code MediaTypeKey}, {@code EncodingKey}, {@code MimeTypeKey}.
|
||||
* @param outputFormat Must have {@code MediaTypeKey}, {@code EncodingKey}, {@code MimeTypeKey}.
|
||||
* @param codecClass
|
||||
*/
|
||||
public void putBidiCodec(Format inputFormat, Format outputFormat, String codecClass) {
|
||||
putCodec(inputFormat, outputFormat, codecClass);
|
||||
putCodec(outputFormat, inputFormat, codecClass);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inputFormat Must have {@code MediaTypeKey}, {@code EncodingKey}, {@code MimeTypeKey}.
|
||||
* @param outputFormat Must have {@code MediaTypeKey}, {@code EncodingKey}, {@code MimeTypeKey}.
|
||||
* @param codecClass
|
||||
*/
|
||||
@Override
|
||||
public void putCodec(Format inputFormat, Format outputFormat, String codecClass) {
|
||||
RegistryEntry entry = new RegistryEntry(inputFormat, outputFormat, codecClass);
|
||||
addCodecEntry(inputFormat.get(EncodingKey), entry);
|
||||
addCodecEntry(outputFormat.get(EncodingKey), entry);
|
||||
}
|
||||
|
||||
private void addCodecEntry(String key, RegistryEntry entry) {
|
||||
LinkedList<RegistryEntry> list = codecMap.get(key);
|
||||
if (list == null) {
|
||||
list = new LinkedList<RegistryEntry>();
|
||||
codecMap.put(key, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileFormat Must have {@code MediaTypeKey}, {@code MimeTypeKey}.
|
||||
* @param readerClass
|
||||
*/
|
||||
@Override
|
||||
public void putReader(Format fileFormat, String readerClass) {
|
||||
RegistryEntry entry = new RegistryEntry(null, fileFormat, readerClass);
|
||||
String key = fileFormat.get(MimeTypeKey);
|
||||
LinkedList<RegistryEntry> list = readerMap.get(key);
|
||||
if (list == null) {
|
||||
list = new LinkedList<RegistryEntry>();
|
||||
readerMap.put(key, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileFormat Must have {@code MediaTypeKey}, {@code MimeTypeKey}.
|
||||
* @param writerClass
|
||||
*/
|
||||
@Override
|
||||
public void putWriter(Format fileFormat, String writerClass) {
|
||||
RegistryEntry entry = new RegistryEntry(fileFormat, null, writerClass);
|
||||
String key = fileFormat.get(MimeTypeKey);
|
||||
LinkedList<RegistryEntry> list = writerMap.get(key);
|
||||
if (list == null) {
|
||||
list = new LinkedList<RegistryEntry>();
|
||||
writerMap.put(key, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCodecClasses(Format inputFormat, Format outputFormat) {
|
||||
HashSet<String> classNames = new HashSet<String>();
|
||||
HashSet<RegistryEntry> entries = new HashSet<RegistryEntry>();
|
||||
if (inputFormat != null) {
|
||||
LinkedList<RegistryEntry> re;
|
||||
if (inputFormat.get(EncodingKey) == null) {
|
||||
re = new LinkedList<RegistryEntry>();
|
||||
for (Map.Entry<String, LinkedList<RegistryEntry>> i : codecMap.entrySet()) {
|
||||
for (RegistryEntry j : i.getValue()) {
|
||||
if (inputFormat.matches(j.inputFormat)) {
|
||||
re.add(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
re = codecMap.get(inputFormat.get(EncodingKey));
|
||||
}
|
||||
if (re != null) {
|
||||
entries.addAll(re);
|
||||
}
|
||||
}
|
||||
if (outputFormat != null) {
|
||||
LinkedList<RegistryEntry> re;
|
||||
if (outputFormat.get(EncodingKey) == null) {
|
||||
re = new LinkedList<RegistryEntry>();
|
||||
for (Map.Entry<String, LinkedList<RegistryEntry>> i : codecMap.entrySet()) {
|
||||
for (RegistryEntry j : i.getValue()) {
|
||||
if (outputFormat.matches(j.outputFormat)) {
|
||||
re.add(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
re = codecMap.get(outputFormat.get(EncodingKey));
|
||||
}
|
||||
if (re != null) {
|
||||
entries.addAll(re);
|
||||
}
|
||||
}
|
||||
for (RegistryEntry e : entries) {
|
||||
if ((inputFormat == null || e.inputFormat == null || inputFormat.matches(e.inputFormat))
|
||||
&& (outputFormat == null || e.outputFormat == null || outputFormat.matches(e.outputFormat))) {
|
||||
classNames.add(e.className);
|
||||
}
|
||||
}
|
||||
return classNames.toArray(new String[classNames.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getReaderClasses(Format fileFormat) {
|
||||
LinkedList<RegistryEntry> rr = readerMap.get(fileFormat.get(MimeTypeKey));
|
||||
String[] names = new String[rr == null ? 0 : rr.size()];
|
||||
if (rr != null) {
|
||||
int i = 0;
|
||||
for (RegistryEntry e : rr) {
|
||||
names[i++] = e.className;
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getFileFormat(File file) {
|
||||
String ext = file.getName();
|
||||
int p = ext.lastIndexOf('.');
|
||||
if (p != -1) {
|
||||
ext = ext.substring(p + 1);
|
||||
}
|
||||
ext = ext.toLowerCase();
|
||||
return fileFormatMap.get(ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getWriterClasses(Format fileFormat) {
|
||||
LinkedList<RegistryEntry> rr = writerMap.get(fileFormat.get(MimeTypeKey));
|
||||
String[] names = new String[rr == null ? 0 : rr.size()];
|
||||
if (rr != null) {
|
||||
int i = 0;
|
||||
for (RegistryEntry e : rr) {
|
||||
names[i++] = e.className;
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putFileFormat(String extension, Format format) {
|
||||
fileFormatMap.put(extension.toLowerCase(), format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension(Format ff) {
|
||||
for (Map.Entry<String, Format> e : fileFormatMap.entrySet()) {
|
||||
if (e.getValue().get(MimeTypeKey).equals(ff.get(MimeTypeKey))) {
|
||||
return e.getKey();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterCodec(String codecClass) {
|
||||
for (Map.Entry<String, LinkedList<RegistryEntry>> i:codecMap.entrySet()) {
|
||||
LinkedList<RegistryEntry> ll=i.getValue();
|
||||
for (Iterator<RegistryEntry> j=ll.iterator();j.hasNext();) {
|
||||
RegistryEntry e=j.next();
|
||||
if (e.className.equals(codecClass)) {
|
||||
j.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
325
libsrc/avi/src/org/monte/media/Format.java
Normal file
325
libsrc/avi/src/org/monte/media/Format.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* @(#)Format.java
|
||||
*
|
||||
* Copyright (c) 2011-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance onlyWith the
|
||||
* license agreement you entered into onlyWith Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Specifies the format of a media, for example of audio and video.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: Format.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class Format {
|
||||
|
||||
/**
|
||||
* Holds the properties of the format.
|
||||
*/
|
||||
private HashMap<FormatKey, Object> properties;
|
||||
|
||||
/**
|
||||
* Creates a new format onlyWith the specified properties.
|
||||
*/
|
||||
public Format(Map<FormatKey, Object> properties) {
|
||||
this(properties, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format onlyWith the specified properties.
|
||||
*/
|
||||
private Format(Map<FormatKey, Object> properties, boolean copy) {
|
||||
if (copy || ! (properties instanceof HashMap)) {
|
||||
for (Map.Entry<FormatKey, Object> e : properties.entrySet()) {
|
||||
if (!e.getKey().isAssignable(e.getValue())) {
|
||||
throw new ClassCastException(e.getValue() + " must be of type " + e.getKey().getValueClass());
|
||||
}
|
||||
}
|
||||
this.properties = new HashMap< FormatKey, Object>(properties);
|
||||
} else {
|
||||
this.properties = (HashMap< FormatKey, Object>) properties;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format onlyWith the specified properties. The properties
|
||||
* must be given as key value pairs.
|
||||
*/
|
||||
public Format(Object... p) {
|
||||
this.properties = new HashMap< FormatKey, Object>();
|
||||
for (int i = 0; i < p.length; i += 2) {
|
||||
FormatKey key = (FormatKey) p[i];
|
||||
if (!key.isAssignable(p[i + 1])) {
|
||||
throw new ClassCastException(key + ": " + p[i + 1] + " must be of type " + key.getValueClass());
|
||||
}
|
||||
this.properties.put(key, p[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(FormatKey<T> key) {
|
||||
return (T) properties.get(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(FormatKey<T> key, T defaultValue) {
|
||||
return (properties.containsKey(key)) ? (T) properties.get(key) : defaultValue;
|
||||
}
|
||||
|
||||
public boolean containsKey(FormatKey key) {
|
||||
return properties.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties of the format as an unmodifiable map.
|
||||
*/
|
||||
public Map<FormatKey, Object> getProperties() {
|
||||
return Collections.unmodifiableMap(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keys of the format as an unmodifiable set.
|
||||
*/
|
||||
public Set<FormatKey> getKeys() {
|
||||
return Collections.unmodifiableSet(properties.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if that format matches this format. That is iff all
|
||||
* properties defined in both format objects are identical. Properties which
|
||||
* are only defined in one of the format objects are not considered.
|
||||
*
|
||||
* @param that Another format.
|
||||
* @return True if the other format matches this format.
|
||||
*/
|
||||
public boolean matches(Format that) {
|
||||
for (Map.Entry<FormatKey, Object> e : properties.entrySet()) {
|
||||
if (!e.getKey().isComment()) {
|
||||
if (that.properties.containsKey(e.getKey())) {
|
||||
Object a = e.getValue();
|
||||
Object b = that.properties.get(e.getKey());
|
||||
if (a != b && a == null || !a.equals(b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean matchesWithout(Format that, FormatKey... without) {
|
||||
OuterLoop:
|
||||
for (Map.Entry<FormatKey, Object> e : properties.entrySet()) {
|
||||
FormatKey k = e.getKey();
|
||||
if (!e.getKey().isComment()) {
|
||||
if (that.properties.containsKey(k)) {
|
||||
for (int i = 0; i < without.length; i++) {
|
||||
if (without[i] == k) {
|
||||
continue OuterLoop;
|
||||
}
|
||||
}
|
||||
Object a = e.getValue();
|
||||
Object b = that.properties.get(k);
|
||||
if (a != b && a == null || !a.equals(b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format which contains all properties from this format and
|
||||
* additional properties from that format. <p> If a property is specified in
|
||||
* both formats, then the property value from this format is used. It
|
||||
* overwrites that format. <p> If one of the format has more properties than
|
||||
* the other, then the new format is more specific than this format.
|
||||
*
|
||||
* @param that
|
||||
* @return That format with properties overwritten by this format.
|
||||
*/
|
||||
public Format append(Format that) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>(this.properties);
|
||||
for (Map.Entry<FormatKey, Object> e : that.properties.entrySet()) {
|
||||
if (!m.containsKey(e.getKey())) {
|
||||
m.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format which contains all properties from this format and
|
||||
* additional properties listed. <p> If a property is specified in both
|
||||
* formats, then the property value from this format is used. It overwrites
|
||||
* that format. <p> If one of the format has more properties than the other,
|
||||
* then the new format is more specific than this format.
|
||||
*
|
||||
* @param p The properties must be given as key value pairs.
|
||||
* @return That format with properties overwritten by this format.
|
||||
*/
|
||||
public Format append(Object... p) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>(this.properties);
|
||||
for (int i = 0; i < p.length; i += 2) {
|
||||
FormatKey key = (FormatKey) p[i];
|
||||
if (!key.isAssignable(p[i + 1])) {
|
||||
throw new ClassCastException(key + ": " + p[i + 1] + " must be of type " + key.getValueClass());
|
||||
}
|
||||
m.put(key, p[i + 1]);
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format which contains all properties from the specified
|
||||
* format and additional properties from this format.
|
||||
* <p> If a property is specified in both formats, then the property value
|
||||
* from this format is used. It overwrites that format.
|
||||
* <p> If one of the format has more properties than the other, then the new
|
||||
* format is more specific than this format.
|
||||
*
|
||||
* @param that
|
||||
* @return That format with properties overwritten by this format.
|
||||
*/
|
||||
public Format prepend(Format that) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>(that.properties);
|
||||
for (Map.Entry<FormatKey, Object> e : this.properties.entrySet()) {
|
||||
if (!m.containsKey(e.getKey())) {
|
||||
m.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format which contains all specified properties and
|
||||
* additional properties from this format.
|
||||
* <p> If a property is specified in both formats, then the property value
|
||||
* from this format is used. It overwrites that format.
|
||||
* <p> If one of the format has more properties than the other, then the new
|
||||
* format is more specific than this format.
|
||||
*
|
||||
* @param p The properties must be given as key value pairs.
|
||||
* @return That format with properties overwritten by this format.
|
||||
*/
|
||||
public Format prepend(Object... p) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>();
|
||||
for (int i = 0; i < p.length; i += 2) {
|
||||
FormatKey key = (FormatKey) p[i];
|
||||
if (!key.isAssignable(p[i + 1])) {
|
||||
throw new ClassCastException(key + ": " + p[i + 1] + " must be of type " + key.getValueClass());
|
||||
}
|
||||
m.put(key, p[i + 1]);
|
||||
}
|
||||
for (Map.Entry<FormatKey, Object> e : this.properties.entrySet()) {
|
||||
if (!m.containsKey(e.getKey())) {
|
||||
m.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
/**
|
||||
* Creates a new format which only has the specified keys (or less). <p> If
|
||||
* the keys are reduced, then the new format is less specific than this
|
||||
* format.
|
||||
*/
|
||||
public Format intersectKeys(FormatKey... keys) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>();
|
||||
for (FormatKey k : keys) {
|
||||
if (properties.containsKey(k)) {
|
||||
m.put(k, properties.get(k));
|
||||
}
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new format without the specified keys. <p> If the keys are
|
||||
* reduced, then the new format is less specific than this format.
|
||||
*/
|
||||
public Format removeKeys(FormatKey... keys) {
|
||||
boolean needsRemoval = false;
|
||||
for (FormatKey k : keys) {
|
||||
if (properties.containsKey(k)) {
|
||||
needsRemoval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!needsRemoval) {
|
||||
return this;
|
||||
}
|
||||
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>(properties);
|
||||
for (FormatKey k : keys) {
|
||||
m.remove(k);
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the format has the specified keys.
|
||||
*/
|
||||
public Format containsKeys(FormatKey... keys) {
|
||||
HashMap<FormatKey, Object> m = new HashMap<FormatKey, Object>(properties);
|
||||
for (FormatKey k : keys) {
|
||||
m.remove(k);
|
||||
}
|
||||
return new Format(m,false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder("Format{");
|
||||
boolean isFirst = true;
|
||||
for (Map.Entry<FormatKey, Object> e : properties.entrySet()) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
buf.append(',');
|
||||
}
|
||||
buf.append(e.getKey().toString());
|
||||
buf.append(':');
|
||||
appendStuffedString(e.getValue(), buf);
|
||||
}
|
||||
buf.append('}');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used by #toString.
|
||||
*/
|
||||
private static void appendStuffedString(Object value, StringBuilder stuffed) {
|
||||
if (value == null) {
|
||||
stuffed.append("null");
|
||||
}
|
||||
value = value.toString();
|
||||
if (value instanceof String) {
|
||||
for (char ch : ((String) value).toCharArray()) {
|
||||
if (ch >= ' ') {
|
||||
stuffed.append(ch);
|
||||
} else {
|
||||
String hex = Integer.toHexString(ch);
|
||||
stuffed.append("\\u");
|
||||
for (int i = hex.length(); i < 4; i++) {
|
||||
stuffed.append('0');
|
||||
}
|
||||
stuffed.append(hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
libsrc/avi/src/org/monte/media/FormatKey.java
Normal file
112
libsrc/avi/src/org/monte/media/FormatKey.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* @(#)FormatKey.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A <em>FormatKey</em> provides type-safe access to an attribute of
|
||||
* a {@link Format}.
|
||||
* <p>
|
||||
* A format key has a name, a type and a value.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: FormatKey.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class FormatKey<T> implements Serializable, Comparable {
|
||||
|
||||
public static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* Holds a String representation of the attribute key.
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* Holds a pretty name. This can be null, if the value is self-explaining.
|
||||
*/
|
||||
private String name;
|
||||
/** This variable is used as a "type token" so that we can check for
|
||||
* assignability of attribute values at runtime.
|
||||
*/
|
||||
private Class<T> clazz;
|
||||
|
||||
/** Comment keys are ignored when matching two media formats with each other. */
|
||||
private boolean comment;
|
||||
|
||||
/** Creates a new instance with the specified attribute key, type token class,
|
||||
* default value null, and allowing null values. */
|
||||
public FormatKey(String key, Class<T> clazz) {
|
||||
this(key, key, clazz);
|
||||
}
|
||||
|
||||
/** Creates a new instance with the specified attribute key, type token class,
|
||||
* default value null, and allowing null values. */
|
||||
public FormatKey(String key, String name, Class<T> clazz) {
|
||||
this(key,name,clazz,false);
|
||||
}
|
||||
/** Creates a new instance with the specified attribute key, type token class,
|
||||
* default value null, and allowing null values. */
|
||||
public FormatKey(String key, String name, Class<T> clazz, boolean comment) {
|
||||
this.key = key;
|
||||
this.name = name;
|
||||
this.clazz = clazz;
|
||||
this.comment=comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key string.
|
||||
* @return key string.
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pretty name string.
|
||||
* @return name string.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Returns the key string. */
|
||||
@Override
|
||||
public String toString() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is assignable with this key.
|
||||
*
|
||||
* @param value
|
||||
* @return True if assignable.
|
||||
*/
|
||||
public boolean isAssignable(Object value) {
|
||||
return clazz.isInstance(value);
|
||||
}
|
||||
|
||||
public boolean isComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
|
||||
public Class getValueClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
return compareTo((FormatKey) o);
|
||||
}
|
||||
|
||||
public int compareTo(FormatKey that) {
|
||||
return this.key.compareTo(that.key);
|
||||
}
|
||||
}
|
||||
61
libsrc/avi/src/org/monte/media/FormatKeys.java
Normal file
61
libsrc/avi/src/org/monte/media/FormatKeys.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* @(#)FormatKeys.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import org.monte.media.math.Rational;
|
||||
|
||||
/**
|
||||
* Defines common {@code FormatKey}'s.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: FormatKeys.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class FormatKeys {
|
||||
public static enum MediaType {
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
MIDI,
|
||||
TEXT,
|
||||
META,
|
||||
FILE
|
||||
}
|
||||
/**
|
||||
* The media MediaTypeKey.
|
||||
*/
|
||||
public final static FormatKey<MediaType> MediaTypeKey = new FormatKey<MediaType>("mediaType", MediaType.class);
|
||||
/**
|
||||
* The EncodingKey.
|
||||
*/
|
||||
public final static FormatKey<String> EncodingKey = new FormatKey<String>("encoding", String.class);
|
||||
|
||||
//
|
||||
public final static String MIME_AVI = "video/avi";
|
||||
public final static String MIME_QUICKTIME = "video/quicktime";
|
||||
public final static String MIME_MP4 = "video/mp4";
|
||||
public final static String MIME_JAVA = "Java";
|
||||
public final static String MIME_ANIM = "x-iff/anim";
|
||||
public final static String MIME_IMAGE_SEQUENCE = "ImageSequence";
|
||||
/**
|
||||
* The mime type.
|
||||
*/
|
||||
public final static FormatKey<String> MimeTypeKey = new FormatKey<String>("mimeType", String.class);
|
||||
/**
|
||||
* The number of frames per second.
|
||||
*/
|
||||
public final static FormatKey<Rational> FrameRateKey = new FormatKey<Rational>("frameRate", Rational.class);
|
||||
|
||||
/**
|
||||
* The interval between key frames.
|
||||
* If this value is not specified, most codecs will use {@code FrameRateKey}
|
||||
* as a hint and try to produce one key frame per second.
|
||||
*/
|
||||
public final static FormatKey<Integer> KeyFrameIntervalKey = new FormatKey<Integer>("keyFrameInterval", Integer.class);
|
||||
}
|
||||
93
libsrc/avi/src/org/monte/media/MovieReader.java
Normal file
93
libsrc/avi/src/org/monte/media/MovieReader.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* @(#)MovieReader.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import org.monte.media.math.Rational;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A simple API for reading movie data (audio and video) from a file.
|
||||
*
|
||||
* <p>
|
||||
* FIXME - MovieReader should extend Demultiplexer
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: MovieReader.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public interface MovieReader {
|
||||
/** Returns the number of tracks. */
|
||||
public int getTrackCount() throws IOException;
|
||||
|
||||
/** Finds a track with the specified format.
|
||||
*
|
||||
* @param fromTrack the start track number.
|
||||
* @param format A format specification.
|
||||
* @return The track number >= fromTrack or -1 if no track has been found.
|
||||
*/
|
||||
public int findTrack(int fromTrack, Format format) throws IOException;
|
||||
|
||||
/** Returns the total duration of the movie . */
|
||||
public Rational getDuration() throws IOException;
|
||||
/** Returns the duration of the specified track. */
|
||||
public Rational getDuration(int track) throws IOException;
|
||||
|
||||
/** Returns the sample number for the specified time. */
|
||||
public long timeToSample(int track, Rational seconds) throws IOException;
|
||||
/** Returns the time for the specified sample number. */
|
||||
public Rational sampleToTime(int track, long sample) throws IOException;
|
||||
|
||||
/** Returns the file format. */
|
||||
public Format getFileFormat() throws IOException;
|
||||
|
||||
/** Returns the media format of the specified track.
|
||||
*
|
||||
* @param track Track number.
|
||||
* @return The media format of the track.
|
||||
*/
|
||||
public Format getFormat(int track) throws IOException;
|
||||
|
||||
/** Returns the number of media data chunks in the specified track.
|
||||
* A chunk contains one or more samples.
|
||||
*/
|
||||
public long getChunkCount(int track) throws IOException;
|
||||
|
||||
/** Reads the next sample chunk from the specified track.
|
||||
*
|
||||
* @param track Track number.
|
||||
* @param buffer The buffer into which to store the sample data.
|
||||
*/
|
||||
public void read(int track, Buffer buffer) throws IOException;
|
||||
/** Reads the next sample chunk from the next track in playback sequence.
|
||||
* The variable buffer.track contains the track number.
|
||||
*
|
||||
* @param buf The buffer into which to store the sample data.
|
||||
*/
|
||||
//public void read(Buffer buffer) throws IOException;
|
||||
|
||||
/** Returns the index of the next track in playback sequence.
|
||||
*
|
||||
* @return Index of next track or -1 if end of media reached.
|
||||
*/
|
||||
public int nextTrack() throws IOException;
|
||||
|
||||
public void close() throws IOException;
|
||||
|
||||
/** Sets the read time of all tracks to the closest sync sample before or
|
||||
* at the specified time.
|
||||
*
|
||||
* @param newValue Time in seconds.
|
||||
*/
|
||||
public void setMovieReadTime(Rational newValue) throws IOException;
|
||||
|
||||
/** Returns the current time of the track. */
|
||||
public Rational getReadTime(int track) throws IOException;
|
||||
|
||||
}
|
||||
82
libsrc/avi/src/org/monte/media/MovieWriter.java
Normal file
82
libsrc/avi/src/org/monte/media/MovieWriter.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* @(#)MovieWriter.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import org.monte.media.math.Rational;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A simple API for writing movie data (audio and video) into a file.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: MovieWriter.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public interface MovieWriter extends Multiplexer {
|
||||
/** Returns the file format. */
|
||||
public Format getFileFormat() throws IOException;
|
||||
|
||||
/** Adds a track to the writer for a suggested input format.
|
||||
* <p>
|
||||
* The format should at least specify the desired {@link FormatKeys.MediaType}.
|
||||
* The actual input format is a refined version of the suggested format. For
|
||||
* example, if a MovieWriter only supports fixed frame rate video, then the
|
||||
* MovieWriter will extend the format with that information.
|
||||
* <p>
|
||||
* If the suggested input format is not compatible, then an IOException is
|
||||
* thrown. For example, if a MovieWriter only supports fixed frame rate video,
|
||||
* but a format with variable frame rate was requested.
|
||||
*
|
||||
* @param format The desired input format of the track. The actual input
|
||||
* format may be a refined version of the specified format.
|
||||
* @return The track number.
|
||||
*/
|
||||
public int addTrack(Format format) throws IOException;
|
||||
|
||||
/** Returns the media format of the specified track.
|
||||
* This is a refined version of the format that was requested when the
|
||||
* track was added. See {@link #addTrack}.
|
||||
*
|
||||
* @param track Track number.
|
||||
* @return The media format of the track.
|
||||
*/
|
||||
public Format getFormat(int track);
|
||||
|
||||
/** Returns the number of tracks. */
|
||||
public int getTrackCount();
|
||||
|
||||
/** Writes a sample into the specified track.
|
||||
* Does nothing if the discard-flag in the buffer is set to true.
|
||||
*
|
||||
* @param track The track number.
|
||||
* @param buf The buffer containing the sample data.
|
||||
*/
|
||||
@Override
|
||||
public void write(int track, Buffer buf) throws IOException;
|
||||
|
||||
/** Closes the writer. */
|
||||
@Override
|
||||
public void close() throws IOException;
|
||||
|
||||
/** Returns true if the limit for media data has been reached.
|
||||
* If this limit is reached, no more samples should be added to the movie.
|
||||
* <p>
|
||||
* This limit is imposed by data structures of the movie file
|
||||
* which will overflow if more samples are added to the movie.
|
||||
* <p>
|
||||
* FIXME - Maybe replace by getCapacity():long.
|
||||
*/
|
||||
public boolean isDataLimitReached();
|
||||
|
||||
/** Returns the duration of the track in seconds. */
|
||||
public Rational getDuration(int track);
|
||||
/** Returns true if the specified track has no samples. */
|
||||
public boolean isEmpty(int track);
|
||||
}
|
||||
34
libsrc/avi/src/org/monte/media/Multiplexer.java
Normal file
34
libsrc/avi/src/org/monte/media/Multiplexer.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* @(#)Multiplexer.java 1.0 2011-02-19
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package org.monte.media;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@code Multiplexer} can write multiple media tracks into a
|
||||
* single output stream.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-02-19 Created.
|
||||
*/
|
||||
public interface Multiplexer {
|
||||
/** Writes a sample.
|
||||
* Does nothing if the discard-flag in the buffer is set to true.
|
||||
*
|
||||
* @param track The track number.
|
||||
* @param buf The buffer containing the sample data.
|
||||
*/
|
||||
public void write(int track, Buffer buf) throws IOException;
|
||||
|
||||
/** Closes the Multiplexer. */
|
||||
public void close() throws IOException;
|
||||
}
|
||||
30
libsrc/avi/src/org/monte/media/ParseException.java
Normal file
30
libsrc/avi/src/org/monte/media/ParseException.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* @(#)ParseException.java
|
||||
*
|
||||
* Copyright (c) 1999-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
/**
|
||||
* Exception thrown by IFFParse.
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
|
||||
* @version $Id: ParseException.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class ParseException extends Exception {
|
||||
|
||||
public static final long serialVersionUID = 1L;
|
||||
|
||||
public ParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
310
libsrc/avi/src/org/monte/media/Registry.java
Normal file
310
libsrc/avi/src/org/monte/media/Registry.java
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* @(#)Registry.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import static org.monte.media.FormatKeys.*;
|
||||
|
||||
/**
|
||||
* The {@code Registry} for audio and video codecs.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: Registry.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public abstract class Registry {
|
||||
|
||||
private static Registry instance;
|
||||
|
||||
public static Registry getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DefaultRegistry();
|
||||
instance.init();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the registry.
|
||||
*/
|
||||
protected abstract void init();
|
||||
|
||||
/**
|
||||
* Puts a codec into the registry.
|
||||
*
|
||||
* @param inputFormat The input format. Must not be null.
|
||||
* @param outputFormat The output format. Must not be null.
|
||||
* @param codecClass The codec class name. Must not be null.
|
||||
*/
|
||||
public abstract void putCodec(Format inputFormat, Format outputFormat, String codecClass);
|
||||
|
||||
/**
|
||||
* Gets all codecs which can decode the specified format.
|
||||
*
|
||||
* @param format The format.
|
||||
* @return An array of codec class names. If no codec was found, an empty
|
||||
* array is returned.
|
||||
*/
|
||||
public final String[] getDecoderClasses(Format format) {
|
||||
return getCodecClasses(format, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all codecs which can decode the specified format.
|
||||
*
|
||||
* @param format The format.
|
||||
* @return An array of codec class names. If no codec was found, an empty
|
||||
* array is returned.
|
||||
*/
|
||||
public final String[] getEncoderClasses(Format format) {
|
||||
return getCodecClasses(null, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all codecs which can transcode from the specified input format to
|
||||
* the specified output format.
|
||||
*
|
||||
* @param inputFormat The input format.
|
||||
* @param outputFormat The output format.
|
||||
* @return An array of codec class names. If no codec was found, an empty
|
||||
* array is returned.
|
||||
*/
|
||||
public abstract String[] getCodecClasses(//
|
||||
Format inputFormat,
|
||||
Format outputFormat);
|
||||
|
||||
/**
|
||||
* Gets all codecs which can decode the specified format.
|
||||
*
|
||||
* @param inputFormat The input format.
|
||||
* @return An array of codec class names. If no codec was found, an empty
|
||||
* array is returned.
|
||||
*/
|
||||
public final Codec[] getDecoders(Format inputFormat) {
|
||||
return getCodecs(inputFormat, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first codec which can decode the specified format.
|
||||
*
|
||||
* @param inputFormat The output format.
|
||||
* @return A codec. Returns null if no codec was found.
|
||||
*/
|
||||
public Codec getDecoder(Format inputFormat) {
|
||||
return getCodec(inputFormat, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all codecs which can encode the specified format.
|
||||
*
|
||||
* @param outputFormat The output format.
|
||||
* @return An array of codecs. If no codec was found, an empty array is
|
||||
* returned.
|
||||
*/
|
||||
public final Codec[] getEncoders(Format outputFormat) {
|
||||
return getCodecs(null, outputFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first codec which can encode the specified foramt.
|
||||
*
|
||||
* @param outputFormat The output format.
|
||||
* @return A codec. Returns null if no codec was found.
|
||||
*/
|
||||
public Codec getEncoder(Format outputFormat) {
|
||||
return getCodec(null, outputFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all codecs which can transcode from the specified input format to
|
||||
* the specified output format.
|
||||
*
|
||||
* @param inputFormat The input format.
|
||||
* @param outputFormat The output format.
|
||||
* @return An array of codec class names. If no codec was found, an empty
|
||||
* array is returned.
|
||||
*/
|
||||
public Codec[] getCodecs(Format inputFormat, Format outputFormat) {
|
||||
String[] clazz = getCodecClasses(inputFormat, outputFormat);
|
||||
ArrayList<Codec> codecs = new ArrayList<Codec>(clazz.length);
|
||||
for (int i = 0; i < clazz.length; i++) {
|
||||
try {
|
||||
codecs.add((Codec) Class.forName(clazz[i]).newInstance());
|
||||
} catch (Exception ex) {
|
||||
//ex.printStackTrace();
|
||||
System.err.println("Monte Registry. Codec class not found: " + clazz[i]);
|
||||
unregisterCodec(clazz[i]);
|
||||
}
|
||||
}
|
||||
return codecs.toArray(new Codec[codecs.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a codec which can transcode from the specified input format to the
|
||||
* specified output format.
|
||||
*
|
||||
* @param inputFormat The input format.
|
||||
* @param outputFormat The output format.
|
||||
* @return A codec or null.
|
||||
*/
|
||||
public Codec getCodec(Format inputFormat, Format outputFormat) {
|
||||
String[] clazz = getCodecClasses(inputFormat, outputFormat);
|
||||
for (int i = 0; i < clazz.length; i++) {
|
||||
try {
|
||||
Codec codec = ((Codec) Class.forName(clazz[i]).newInstance());
|
||||
codec.setInputFormat(inputFormat);
|
||||
if (outputFormat != null) {
|
||||
codec.setOutputFormat(outputFormat);
|
||||
}
|
||||
return codec;
|
||||
} catch (Exception ex) {
|
||||
//ex.printStackTrace();
|
||||
System.err.println("Monte Registry. Codec class not found: " + clazz[i]);
|
||||
unregisterCodec(clazz[i]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a reader into the registry.
|
||||
*
|
||||
* @param fileFormat The file format, e.g."video/avi", "video/quicktime".
|
||||
* Use "Java" for formats which are not tied to a file format. Must not be
|
||||
* null.
|
||||
* @param readerClass The reader class name. Must not be null.
|
||||
*/
|
||||
public abstract void putReader(Format fileFormat, String readerClass);
|
||||
|
||||
/**
|
||||
* Puts a writer into the registry.
|
||||
*
|
||||
* @param fileFormat The file format, e.g."video/avi", "video/quicktime".
|
||||
* Use "Java" for formats which are not tied to a file format. Must not be
|
||||
* null.
|
||||
* @param writerClass The writer class name. Must not be null.
|
||||
*/
|
||||
public abstract void putWriter(Format fileFormat, String writerClass);
|
||||
|
||||
/**
|
||||
* Gets all reader class names from the registry for the specified file
|
||||
* format.
|
||||
*
|
||||
* @param fileFormat The file format, e.g."AVI", "QuickTime".
|
||||
* @return The reader class names.
|
||||
*/
|
||||
public abstract String[] getReaderClasses(Format fileFormat);
|
||||
|
||||
/**
|
||||
* Gets all writer class names from the registry for the specified file
|
||||
* format.
|
||||
*
|
||||
* @param fileFormat The file format, e.g."AVI", "QuickTime".
|
||||
* @return The writer class names.
|
||||
*/
|
||||
public abstract String[] getWriterClasses(Format fileFormat);
|
||||
|
||||
public MovieReader getReader(Format fileFormat, File file) {
|
||||
String[] clazz = getReaderClasses(fileFormat);
|
||||
for (int i = 0; i < clazz.length; i++) {
|
||||
try {
|
||||
return ((MovieReader) Class.forName(clazz[i]).getConstructor(File.class).newInstance(file));
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MovieWriter getWriter(File file) {
|
||||
Format format = getFileFormat(file);
|
||||
return format == null ? null : getWriter(format, file);
|
||||
}
|
||||
|
||||
public MovieWriter getWriter(Format fileFormat, File file) {
|
||||
String[] clazz = getWriterClasses(fileFormat);
|
||||
for (int i = 0; i < clazz.length; i++) {
|
||||
try {
|
||||
return ((MovieWriter) Class.forName(clazz[i]).getConstructor(File.class).newInstance(file));
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MovieReader getReader(File file) {
|
||||
Format format = getFileFormat(file);
|
||||
return format == null ? null : getReader(format, file);
|
||||
}
|
||||
|
||||
public abstract void putFileFormat(String extension, Format format);
|
||||
|
||||
public abstract Format getFileFormat(File file);
|
||||
|
||||
public abstract Format[] getReaderFormats();
|
||||
|
||||
public abstract Format[] getWriterFormats();
|
||||
|
||||
public abstract Format[] getFileFormats();
|
||||
|
||||
public abstract String getExtension(Format ff);
|
||||
|
||||
/**
|
||||
* Suggests output formats for the given input media format and specified
|
||||
* file format.
|
||||
*
|
||||
* @param inputMediaFormat
|
||||
* @param outputFileFormat
|
||||
* @return List of output media formats.
|
||||
*/
|
||||
public ArrayList<Format> suggestOutputFormats(Format inputMediaFormat, Format outputFileFormat) {
|
||||
ArrayList<Format> formats = new ArrayList<Format>();
|
||||
Format matchFormat = new Format(//
|
||||
MimeTypeKey, outputFileFormat.get(MimeTypeKey),//
|
||||
MediaTypeKey, inputMediaFormat.get(MediaTypeKey));
|
||||
Codec[] codecs = getEncoders(matchFormat);
|
||||
int matchingCount = 0;
|
||||
for (Codec c : codecs) {
|
||||
for (Format mf : c.getOutputFormats(null)) {
|
||||
if (mf.matches(matchFormat)) {
|
||||
if (inputMediaFormat.matchesWithout(mf, MimeTypeKey)) {
|
||||
// add matching formats first
|
||||
formats.add(0, mf.append(inputMediaFormat));
|
||||
matchingCount++;
|
||||
} else if (inputMediaFormat.matchesWithout(mf, MimeTypeKey, EncodingKey)) {
|
||||
// add formats which match everything but the encoding second
|
||||
formats.add(matchingCount, mf.append(inputMediaFormat));
|
||||
} else {
|
||||
// add remaining formats last
|
||||
formats.add(mf.append(inputMediaFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
for (int i = formats.size() - 1; i >= 0; i--) {
|
||||
Format fi = formats.get(i);
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
Format fj = formats.get(j);
|
||||
if (fi.matches(fj)) {
|
||||
formats.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
public abstract void unregisterCodec(String codecClass);
|
||||
}
|
||||
77
libsrc/avi/src/org/monte/media/VideoFormatKeys.java
Normal file
77
libsrc/avi/src/org/monte/media/VideoFormatKeys.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* @(#)VideoFormatKeys.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media;
|
||||
|
||||
import org.monte.media.math.Rational;
|
||||
|
||||
/**
|
||||
* Defines common format keys for video media.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: VideoFormatKeys.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class VideoFormatKeys extends FormatKeys {
|
||||
// Standard video ENCODING strings for use with FormatKey.Encoding.
|
||||
public static final String ENCODING_BUFFERED_IMAGE = "image";
|
||||
/** Cinepak format. */
|
||||
public static final String ENCODING_QUICKTIME_CINEPAK = "cvid";
|
||||
public static final String COMPRESSOR_NAME_QUICKTIME_CINEPAK = "Cinepak";
|
||||
/** JPEG format. */
|
||||
public static final String ENCODING_QUICKTIME_JPEG = "jpeg";
|
||||
public static final String COMPRESSOR_NAME_QUICKTIME_JPEG = "Photo - JPEG";
|
||||
/** PNG format. */
|
||||
public static final String ENCODING_QUICKTIME_PNG = "png ";
|
||||
public static final String COMPRESSOR_NAME_QUICKTIME_PNG = "PNG";
|
||||
/** Animation format. */
|
||||
public static final String ENCODING_QUICKTIME_ANIMATION = "rle ";
|
||||
public static final String COMPRESSOR_NAME_QUICKTIME_ANIMATION = "Animation";
|
||||
/** Raw format. */
|
||||
public static final String ENCODING_QUICKTIME_RAW = "raw ";
|
||||
public static final String COMPRESSOR_NAME_QUICKTIME_RAW = "NONE";
|
||||
// AVI Formats
|
||||
/** Microsoft Device Independent Bitmap (DIB) format. */
|
||||
public static final String ENCODING_AVI_DIB = "DIB ";
|
||||
/** Microsoft Run Length format. */
|
||||
public static final String ENCODING_AVI_RLE = "RLE ";
|
||||
/** Techsmith Screen Capture format. */
|
||||
public static final String ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE = "tscc";
|
||||
public static final String COMPRESSOR_NAME_AVI_TECHSMITH_SCREEN_CAPTURE = "Techsmith Screen Capture";
|
||||
/** DosBox Screen Capture format. */
|
||||
public static final String ENCODING_AVI_DOSBOX_SCREEN_CAPTURE = "ZMBV";
|
||||
/** JPEG format. */
|
||||
public static final String ENCODING_AVI_MJPG = "MJPG";
|
||||
/** PNG format. */
|
||||
public static final String ENCODING_AVI_PNG = "png ";
|
||||
/** Interleaved planar bitmap format. */
|
||||
public static final String ENCODING_BITMAP_IMAGE = "ILBM";
|
||||
|
||||
//
|
||||
|
||||
/** The WidthKey of a video frame. */
|
||||
public final static FormatKey<Integer> WidthKey = new FormatKey<Integer>("dimX","width", Integer.class);
|
||||
/** The HeightKey of a video frame. */
|
||||
public final static FormatKey<Integer> HeightKey = new FormatKey<Integer>("dimY","height", Integer.class);
|
||||
/** The number of bits per pixel. */
|
||||
public final static FormatKey<Integer> DepthKey = new FormatKey<Integer>("dimZ","depth", Integer.class);
|
||||
/** The data class. */
|
||||
public final static FormatKey<Class> DataClassKey = new FormatKey<Class>("dataClass", Class.class);
|
||||
/** The compressor name. */
|
||||
public final static FormatKey<String> CompressorNameKey = new FormatKey<String>("compressorName", "compressorName",String.class, true);
|
||||
/** The pixel aspect ratio WidthKey : HeightKey;
|
||||
*/
|
||||
public final static FormatKey<Rational> PixelAspectRatioKey = new FormatKey<Rational>("pixelAspectRatio", Rational.class);
|
||||
/** Whether the frame rate must be fixed. False means variable frame rate. */
|
||||
public final static FormatKey<Boolean> FixedFrameRateKey = new FormatKey<Boolean>("fixedFrameRate", Boolean.class);
|
||||
/** Whether the video is interlaced. */
|
||||
public final static FormatKey<Boolean> InterlaceKey = new FormatKey<Boolean>("interlace", Boolean.class);
|
||||
/** Encoding quality. Value between 0 and 1. */
|
||||
public final static FormatKey<Float> QualityKey = new FormatKey<Float>("quality", Float.class);
|
||||
}
|
||||
106
libsrc/avi/src/org/monte/media/avi/AVIBMPDIB.java
Normal file
106
libsrc/avi/src/org/monte/media/avi/AVIBMPDIB.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* @(#)AVIBMPDIB.java 1.0 2009-12-30
|
||||
*
|
||||
* Copyright (c) 2009 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.avi;
|
||||
|
||||
import org.monte.media.io.ImageInputStreamAdapter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.*;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
|
||||
/**
|
||||
* This class defines the JPEG Huffman table, which is omitted in AVI MJPEG
|
||||
* files.
|
||||
* <p>
|
||||
* Source:
|
||||
* Microsoft Windows Bitmap Format.
|
||||
* Multimedia Technical Note: JPEG DIB Format.
|
||||
* (c) 1993 Microsoft Corporation. All rights reserved.
|
||||
* <a href="http://www.fileformat.info/format/bmp/spec/b7c72ebab8064da48ae5ed0c053c67a4/BMPDIB.TXT">BMPDIB.txt</a>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2009-12-30 Created.
|
||||
*/
|
||||
public class AVIBMPDIB {
|
||||
|
||||
/** MJPG DHT Segment */
|
||||
private static byte[] MJPGDHTSeg = {
|
||||
/* JPEG DHT Segment for YCrCb omitted from MJPG data */
|
||||
(byte) 0xFF, (byte) 0xC4, (byte) 0x01, (byte) 0xA2,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x01,
|
||||
(byte) 0x00, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x10, (byte) 0x00,
|
||||
(byte) 0x02, (byte) 0x01, (byte) 0x03, (byte) 0x03, (byte) 0x02, (byte) 0x04, (byte) 0x03, (byte) 0x05, (byte) 0x05, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x7D,
|
||||
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x00, (byte) 0x04, (byte) 0x11, (byte) 0x05, (byte) 0x12, (byte) 0x21, (byte) 0x31, (byte) 0x41, (byte) 0x06, (byte) 0x13, (byte) 0x51, (byte) 0x61,
|
||||
(byte) 0x07, (byte) 0x22, (byte) 0x71, (byte) 0x14, (byte) 0x32, (byte) 0x81, (byte) 0x91, (byte) 0xA1, (byte) 0x08, (byte) 0x23, (byte) 0x42, (byte) 0xB1, (byte) 0xC1, (byte) 0x15, (byte) 0x52,
|
||||
(byte) 0xD1, (byte) 0xF0, (byte) 0x24, (byte) 0x33, (byte) 0x62, (byte) 0x72, (byte) 0x82, (byte) 0x09, (byte) 0x0A, (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1A, (byte) 0x25,
|
||||
(byte) 0x26, (byte) 0x27, (byte) 0x28, (byte) 0x29, (byte) 0x2A, (byte) 0x34, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0x3A, (byte) 0x43, (byte) 0x44, (byte) 0x45,
|
||||
(byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x4A, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x5A, (byte) 0x63, (byte) 0x64,
|
||||
(byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x83,
|
||||
(byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99,
|
||||
(byte) 0x9A, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0xB6,
|
||||
(byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xD2, (byte) 0xD3,
|
||||
(byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8,
|
||||
(byte) 0xE9, (byte) 0xEA, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0x11, (byte) 0x00, (byte) 0x02,
|
||||
(byte) 0x01, (byte) 0x02, (byte) 0x04, (byte) 0x04, (byte) 0x03, (byte) 0x04, (byte) 0x07, (byte) 0x05, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x77, (byte) 0x00,
|
||||
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x11, (byte) 0x04, (byte) 0x05, (byte) 0x21, (byte) 0x31, (byte) 0x06, (byte) 0x12, (byte) 0x41, (byte) 0x51, (byte) 0x07, (byte) 0x61, (byte) 0x71,
|
||||
(byte) 0x13, (byte) 0x22, (byte) 0x32, (byte) 0x81, (byte) 0x08, (byte) 0x14, (byte) 0x42, (byte) 0x91, (byte) 0xA1, (byte) 0xB1, (byte) 0xC1, (byte) 0x09, (byte) 0x23, (byte) 0x33, (byte) 0x52,
|
||||
(byte) 0xF0, (byte) 0x15, (byte) 0x62, (byte) 0x72, (byte) 0xD1, (byte) 0x0A, (byte) 0x16, (byte) 0x24, (byte) 0x34, (byte) 0xE1, (byte) 0x25, (byte) 0xF1, (byte) 0x17, (byte) 0x18, (byte) 0x19,
|
||||
(byte) 0x1A, (byte) 0x26, (byte) 0x27, (byte) 0x28, (byte) 0x29, (byte) 0x2A, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0x3A, (byte) 0x43, (byte) 0x44, (byte) 0x45,
|
||||
(byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x4A, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x5A, (byte) 0x63, (byte) 0x64,
|
||||
(byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x82,
|
||||
(byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98,
|
||||
(byte) 0x99, (byte) 0x9A, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5,
|
||||
(byte) 0xB6, (byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xD2,
|
||||
(byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8,
|
||||
(byte) 0xE9, (byte) 0xEA, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA
|
||||
};
|
||||
/** JFIF Start of Image (SOI) segment. */
|
||||
private static byte[] JFIFSOISeg = {
|
||||
(byte) 0xff,
|
||||
(byte) 0xd8
|
||||
};
|
||||
|
||||
public static InputStream prependDHTSeg(byte[] jpgWithoutDHT) {
|
||||
return prependDHTSeg(jpgWithoutDHT, 0, jpgWithoutDHT.length);
|
||||
}
|
||||
public static InputStream prependDHTSeg(byte[] jpgWithoutDHT, int offset, int length) {
|
||||
|
||||
// FIXME - Only add DHT Segment if none is present
|
||||
|
||||
Vector<InputStream> v = new Vector<InputStream>();
|
||||
v.add(new ByteArrayInputStream(JFIFSOISeg));
|
||||
v.add(new ByteArrayInputStream(MJPGDHTSeg));
|
||||
v.add(new ByteArrayInputStream(jpgWithoutDHT,offset+JFIFSOISeg.length,length-JFIFSOISeg.length));
|
||||
return new SequenceInputStream(v.elements());
|
||||
}
|
||||
|
||||
public static ImageInputStream prependDHTSeg(ImageInputStream iisWithoutDHT) throws IOException {
|
||||
Vector<InputStream> v = new Vector<InputStream>();
|
||||
v.add(new ByteArrayInputStream(JFIFSOISeg));
|
||||
v.add(new ByteArrayInputStream(MJPGDHTSeg));
|
||||
iisWithoutDHT.seek(2);// skip JFIF SOI
|
||||
v.add(new ImageInputStreamAdapter(iisWithoutDHT));
|
||||
return new MemoryCacheImageInputStream(new SequenceInputStream(v.elements()));
|
||||
}
|
||||
public static ImageInputStream prependDHTSeg(InputStream inWithoutDHT) throws IOException {
|
||||
Vector<InputStream> v = new Vector<InputStream>();
|
||||
v.add(new ByteArrayInputStream(JFIFSOISeg));
|
||||
v.add(new ByteArrayInputStream(MJPGDHTSeg));
|
||||
inWithoutDHT.skip(2);// skip JFIF SOI
|
||||
v.add(inWithoutDHT);
|
||||
return new MemoryCacheImageInputStream(new SequenceInputStream(v.elements()));
|
||||
}
|
||||
}
|
||||
1117
libsrc/avi/src/org/monte/media/avi/AVIOutputStream.java
Normal file
1117
libsrc/avi/src/org/monte/media/avi/AVIOutputStream.java
Normal file
File diff suppressed because it is too large
Load Diff
517
libsrc/avi/src/org/monte/media/avi/AVIWriter.java
Normal file
517
libsrc/avi/src/org/monte/media/avi/AVIWriter.java
Normal file
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* @(#)AVIWriter.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland. All rights
|
||||
* reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance onlyWith the
|
||||
* license agreement you entered into onlyWith Werner Randelshofer. For details
|
||||
* see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.avi;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import org.monte.media.math.Rational;
|
||||
import org.monte.media.Format;
|
||||
import org.monte.media.Codec;
|
||||
import org.monte.media.Buffer;
|
||||
import org.monte.media.MovieWriter;
|
||||
import org.monte.media.Registry;
|
||||
import org.monte.media.io.ByteArrayImageOutputStream;
|
||||
import org.monte.media.riff.RIFFParser;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.*;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import javax.imageio.stream.*;
|
||||
import static org.monte.media.AudioFormatKeys.*;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
import org.monte.media.BufferFlag;
|
||||
import static org.monte.media.BufferFlag.*;
|
||||
|
||||
/**
|
||||
* Provides high-level support for encoding and writing audio and video samples
|
||||
* into an AVI 1.0 file.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: AVIWriter.java 306 2013-01-04 16:19:29Z werner $
|
||||
*/
|
||||
public class AVIWriter extends AVIOutputStream implements MovieWriter {
|
||||
|
||||
public final static Format AVI = new Format(MediaTypeKey, MediaType.FILE, MimeTypeKey, MIME_AVI);
|
||||
public final static Format VIDEO_RAW = new Format(
|
||||
MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_RAW);
|
||||
public final static Format VIDEO_JPEG = new Format(
|
||||
MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_MJPG, CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_RAW);
|
||||
public final static Format VIDEO_PNG = new Format(
|
||||
MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_PNG, CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_RAW);
|
||||
public final static Format VIDEO_RLE = new Format(
|
||||
MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_RLE, CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_RAW);
|
||||
public final static Format VIDEO_SCREEN_CAPTURE = new Format(
|
||||
MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_RAW);
|
||||
|
||||
/**
|
||||
* Creates a new AVI writer.
|
||||
*
|
||||
* @param file the output file
|
||||
*/
|
||||
public AVIWriter(File file) throws IOException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AVI writer.
|
||||
*
|
||||
* @param out the output stream.
|
||||
*/
|
||||
public AVIWriter(ImageOutputStream out) throws IOException {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getFileFormat() throws IOException {
|
||||
return AVI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getFormat(int track) {
|
||||
return tracks.get(track).format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media duration of the track in seconds.
|
||||
*/
|
||||
@Override
|
||||
public Rational getDuration(int track) {
|
||||
Track tr = tracks.get(track);
|
||||
long duration = getMediaDuration(track);
|
||||
return new Rational(duration * tr.scale, tr.rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a track.
|
||||
*
|
||||
* @param format The format of the track.
|
||||
* @return The track number.
|
||||
*/
|
||||
@Override
|
||||
public int addTrack(Format format) throws IOException {
|
||||
if (format.get(MediaTypeKey) == MediaType.VIDEO) {
|
||||
return addVideoTrack(format);
|
||||
} else {
|
||||
return addAudioTrack(format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a video track.
|
||||
*
|
||||
* @param format The format of the track.
|
||||
* @return The track number.
|
||||
*/
|
||||
private int addVideoTrack(Format vf) throws IOException {
|
||||
if (!vf.containsKey(EncodingKey)) {
|
||||
throw new IllegalArgumentException("EncodingKey missing in " + vf);
|
||||
}
|
||||
if (!vf.containsKey(FrameRateKey)) {
|
||||
throw new IllegalArgumentException("FrameRateKey missing in " + vf);
|
||||
}
|
||||
if (!vf.containsKey(WidthKey)) {
|
||||
throw new IllegalArgumentException("WidthKey missing in " + vf);
|
||||
}
|
||||
if (!vf.containsKey(HeightKey)) {
|
||||
throw new IllegalArgumentException("HeightKey missing in " + vf);
|
||||
}
|
||||
if (!vf.containsKey(DepthKey)) {
|
||||
throw new IllegalArgumentException("DepthKey missing in " + vf);
|
||||
}
|
||||
int tr = addVideoTrack(vf.get(EncodingKey),
|
||||
vf.get(FrameRateKey).getDenominator(), vf.get(FrameRateKey).getNumerator(),
|
||||
vf.get(WidthKey), vf.get(HeightKey), vf.get(DepthKey),
|
||||
vf.get(FrameRateKey).floor(1).intValue());
|
||||
setCompressionQuality(tr, vf.get(QualityKey, 1.0f));
|
||||
return tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an audio track.
|
||||
*
|
||||
* @param format The format of the track.
|
||||
* @return The track number.
|
||||
*/
|
||||
private int addAudioTrack(Format format) throws IOException {
|
||||
int waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
|
||||
|
||||
long timeScale = 1;
|
||||
long sampleRate = format.get(SampleRateKey, new Rational(41000, 0)).longValue();
|
||||
int numberOfChannels = format.get(ChannelsKey, 1);
|
||||
int sampleSizeInBits = format.get(SampleSizeInBitsKey, 16); //
|
||||
boolean isCompressed = false; // FIXME
|
||||
int frameDuration = 1;
|
||||
int frameSize = format.get(FrameSizeKey, (sampleSizeInBits + 7) / 8 * numberOfChannels);
|
||||
|
||||
|
||||
String enc = format.get(EncodingKey);
|
||||
if (enc == null) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
} else if (enc.equals(ENCODING_ALAW)) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
} else if (enc.equals(ENCODING_PCM_SIGNED)) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
} else if (enc.equals(ENCODING_PCM_UNSIGNED)) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
} else if (enc.equals(ENCODING_ULAW)) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM
|
||||
} else if (enc.equals(ENCODING_MP3)) {
|
||||
waveFormatTag = 0x0001; // WAVE_FORMAT_PCM - FIXME
|
||||
} else {
|
||||
waveFormatTag = RIFFParser.stringToID(format.get(EncodingKey)) & 0xffff;
|
||||
}
|
||||
|
||||
return addAudioTrack(waveFormatTag, //
|
||||
timeScale, sampleRate, //
|
||||
numberOfChannels, sampleSizeInBits, //
|
||||
isCompressed, //
|
||||
frameDuration, frameSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec of the specified track.
|
||||
*/
|
||||
public Codec getCodec(int track) {
|
||||
return tracks.get(track).codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the codec for the specified track.
|
||||
*/
|
||||
public void setCodec(int track, Codec codec) {
|
||||
tracks.get(track).codec = codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackCount() {
|
||||
return tracks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the provided image and writes its sample data into the specified
|
||||
* track.
|
||||
*
|
||||
* @param track The track index.
|
||||
* @param image The image of the video frame.
|
||||
* @param duration Duration given in media time units.
|
||||
*
|
||||
* @throws IndexOutofBoundsException if the track index is out of bounds.
|
||||
* @throws if the duration is less than 1, or if the dimension of the frame
|
||||
* does not match the dimension of the video.
|
||||
* @throws UnsupportedOperationException if the {@code MovieWriter} does not
|
||||
* have a built-in encoder for this video format.
|
||||
* @throws IOException if writing the sample data failed.
|
||||
*/
|
||||
public void write(int track, BufferedImage image, long duration) throws IOException {
|
||||
ensureStarted();
|
||||
|
||||
VideoTrack vt = (VideoTrack) tracks.get(track);
|
||||
if (vt.codec == null) {
|
||||
createCodec(track);
|
||||
}
|
||||
if (vt.codec == null) {
|
||||
throw new UnsupportedOperationException("No codec for this format: " + vt.format);
|
||||
}
|
||||
|
||||
// The dimension of the image must match the dimension of the video track
|
||||
Format fmt = vt.format;
|
||||
if (fmt.get(WidthKey) != image.getWidth() || fmt.get(HeightKey) != image.getHeight()) {
|
||||
throw new IllegalArgumentException("Dimensions of image[" + vt.samples.size()
|
||||
+ "] (width=" + image.getWidth() + ", height=" + image.getHeight()
|
||||
+ ") differs from video format of track: " + fmt);
|
||||
}
|
||||
|
||||
// Encode pixel data
|
||||
{
|
||||
if (vt.outputBuffer == null) {
|
||||
vt.outputBuffer = new Buffer();
|
||||
}
|
||||
|
||||
boolean isKeyframe = vt.syncInterval == 0 ? false : vt.samples.size() % vt.syncInterval == 0;
|
||||
|
||||
Buffer inputBuffer = new Buffer();
|
||||
inputBuffer.flags = (isKeyframe) ? EnumSet.of(KEYFRAME) : EnumSet.noneOf(BufferFlag.class);
|
||||
inputBuffer.data = image;
|
||||
vt.codec.process(inputBuffer, vt.outputBuffer);
|
||||
if (vt.outputBuffer.flags.contains(DISCARD)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Encode palette data
|
||||
isKeyframe = vt.outputBuffer.flags.contains(KEYFRAME);
|
||||
boolean paletteChange = writePalette(track, image, isKeyframe);
|
||||
writeSample(track, (byte[]) vt.outputBuffer.data, vt.outputBuffer.offset, vt.outputBuffer.length, isKeyframe && !paletteChange);
|
||||
/*
|
||||
long offset = getRelativeStreamPosition();
|
||||
|
||||
DataChunk videoFrameChunk = new DataChunk(vt.getSampleChunkFourCC(isKeyframe));
|
||||
moviChunk.add(videoFrameChunk);
|
||||
videoFrameChunk.getOutputStream().write((byte[]) vt.outputBuffer.data, vt.outputBuffer.offset, vt.outputBuffer.length);
|
||||
videoFrameChunk.finish();
|
||||
long length = getRelativeStreamPosition() - offset;
|
||||
|
||||
Sample s=new Sample(videoFrameChunk.chunkType, 1, offset, length, isKeyframe&&!paletteChange);
|
||||
vt.addSample(s);
|
||||
idx1.add(s);
|
||||
|
||||
if (getRelativeStreamPosition() > 1L << 32) {
|
||||
throw new IOException("AVI file is larger than 4 GB");
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the data provided in the buffer and then writes it into the
|
||||
* specified track. <p> Does nothing if the discard-flag in the buffer is
|
||||
* set to true.
|
||||
*
|
||||
* @param track The track number.
|
||||
* @param buf The buffer containing a data sample.
|
||||
*/
|
||||
@Override
|
||||
public void write(int track, Buffer buf) throws IOException {
|
||||
ensureStarted();
|
||||
if (buf.flags.contains(DISCARD)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Track tr = tracks.get(track);
|
||||
|
||||
boolean isKeyframe = buf.flags.contains(KEYFRAME);
|
||||
if (buf.data instanceof BufferedImage) {
|
||||
if (tr.syncInterval != 0) {
|
||||
isKeyframe = buf.flags.contains(KEYFRAME) | (tr.samples.size() % tr.syncInterval == 0);
|
||||
}
|
||||
}
|
||||
// Encode palette data
|
||||
boolean paletteChange = false;
|
||||
if (buf.data instanceof BufferedImage && tr instanceof VideoTrack) {
|
||||
paletteChange = writePalette(track, (BufferedImage) buf.data, isKeyframe);
|
||||
} else if (buf.header instanceof IndexColorModel) {
|
||||
paletteChange = writePalette(track, (IndexColorModel) buf.header, isKeyframe);
|
||||
}
|
||||
// Encode sample data
|
||||
{
|
||||
if (buf.format == null) {
|
||||
throw new IllegalArgumentException("Buffer.format must not be null");
|
||||
}
|
||||
if (buf.format.matchesWithout(tr.format, FrameRateKey) && buf.data instanceof byte[]) {
|
||||
writeSamples(track, buf.sampleCount, (byte[]) buf.data, buf.offset, buf.length,
|
||||
buf.isFlag(KEYFRAME) && !paletteChange);
|
||||
return;
|
||||
}
|
||||
|
||||
// We got here, because the buffer format does not match the track
|
||||
// format. Lets see if we can create a codec which can perform the
|
||||
// encoding for us.
|
||||
|
||||
if (tr.codec == null) {
|
||||
createCodec(track);
|
||||
if (tr.codec == null) {
|
||||
throw new UnsupportedOperationException("No codec for this format " + tr.format);
|
||||
}
|
||||
}
|
||||
|
||||
if (tr.outputBuffer == null) {
|
||||
tr.outputBuffer = new Buffer();
|
||||
}
|
||||
Buffer outBuf = tr.outputBuffer;
|
||||
if (tr.codec.process(buf, outBuf) != Codec.CODEC_OK) {
|
||||
throw new IOException("Codec failed or could not encode the sample in a single step.");
|
||||
}
|
||||
if (outBuf.isFlag(DISCARD)) {
|
||||
return;
|
||||
}
|
||||
writeSamples(track, outBuf.sampleCount, (byte[]) outBuf.data, outBuf.offset, outBuf.length,
|
||||
isKeyframe && !paletteChange);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writePalette(int track, BufferedImage image, boolean isKeyframe) throws IOException {
|
||||
if ((image.getColorModel() instanceof IndexColorModel)) {
|
||||
return writePalette(track, (IndexColorModel) image.getColorModel(), isKeyframe);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean writePalette(int track, IndexColorModel imgPalette, boolean isKeyframe) throws IOException {
|
||||
ensureStarted();
|
||||
|
||||
VideoTrack vt = (VideoTrack) tracks.get(track);
|
||||
int imgDepth = vt.bitCount;
|
||||
ByteArrayImageOutputStream tmp = null;
|
||||
boolean paletteChange = false;
|
||||
switch (imgDepth) {
|
||||
case 4: {
|
||||
//IndexColorModel imgPalette = (IndexColorModel) image.getColorModel();
|
||||
int[] imgRGBs = new int[16];
|
||||
imgPalette.getRGBs(imgRGBs);
|
||||
int[] previousRGBs = new int[16];
|
||||
if (vt.previousPalette == null) {
|
||||
vt.previousPalette = vt.palette;
|
||||
}
|
||||
vt.previousPalette.getRGBs(previousRGBs);
|
||||
if (isKeyframe || !Arrays.equals(imgRGBs, previousRGBs)) {
|
||||
paletteChange = true;
|
||||
vt.previousPalette = imgPalette;
|
||||
/*
|
||||
int first = imgPalette.getMapSize();
|
||||
int last = -1;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (previousRGBs[i] != imgRGBs[i] && i < first) {
|
||||
first = i;
|
||||
}
|
||||
if (previousRGBs[i] != imgRGBs[i] && i > last) {
|
||||
last = i;
|
||||
}
|
||||
}*/
|
||||
int first = 0;
|
||||
int last = imgPalette.getMapSize() - 1;
|
||||
/*
|
||||
* typedef struct {
|
||||
BYTE bFirstEntry;
|
||||
BYTE bNumEntries;
|
||||
WORD wFlags;
|
||||
PALETTEENTRY peNew[];
|
||||
} AVIPALCHANGE;
|
||||
*
|
||||
* typedef struct tagPALETTEENTRY {
|
||||
BYTE peRed;
|
||||
BYTE peGreen;
|
||||
BYTE peBlue;
|
||||
BYTE peFlags;
|
||||
} PALETTEENTRY;
|
||||
*/
|
||||
tmp = new ByteArrayImageOutputStream(ByteOrder.LITTLE_ENDIAN);
|
||||
tmp.writeByte(first);//bFirstEntry
|
||||
tmp.writeByte(last - first + 1);//bNumEntries
|
||||
tmp.writeShort(0);//wFlags
|
||||
|
||||
for (int i = first; i <= last; i++) {
|
||||
tmp.writeByte((imgRGBs[i] >>> 16) & 0xff); // red
|
||||
tmp.writeByte((imgRGBs[i] >>> 8) & 0xff); // green
|
||||
tmp.writeByte(imgRGBs[i] & 0xff); // blue
|
||||
tmp.writeByte(0); // reserved*/
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
//IndexColorModel imgPalette = (IndexColorModel) image.getColorModel();
|
||||
int[] imgRGBs = new int[256];
|
||||
imgPalette.getRGBs(imgRGBs);
|
||||
int[] previousRGBs = new int[256];
|
||||
if (vt.previousPalette != null) {
|
||||
vt.previousPalette.getRGBs(previousRGBs);
|
||||
}
|
||||
if (isKeyframe || !Arrays.equals(imgRGBs, previousRGBs)) {
|
||||
paletteChange = true;
|
||||
vt.previousPalette = imgPalette;
|
||||
/*
|
||||
int first = imgPalette.getMapSize();
|
||||
int last = -1;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (previousRGBs[i] != imgRGBs[i] && i < first) {
|
||||
first = i;
|
||||
}
|
||||
if (previousRGBs[i] != imgRGBs[i] && i > last) {
|
||||
last = i;
|
||||
}
|
||||
}*/
|
||||
int first = 0;
|
||||
int last = imgPalette.getMapSize() - 1;
|
||||
/*
|
||||
* typedef struct {
|
||||
BYTE bFirstEntry;
|
||||
BYTE bNumEntries;
|
||||
WORD wFlags;
|
||||
PALETTEENTRY peNew[];
|
||||
} AVIPALCHANGE;
|
||||
*
|
||||
* typedef struct tagPALETTEENTRY {
|
||||
BYTE peRed;
|
||||
BYTE peGreen;
|
||||
BYTE peBlue;
|
||||
BYTE peFlags;
|
||||
} PALETTEENTRY;
|
||||
*/
|
||||
tmp = new ByteArrayImageOutputStream(ByteOrder.LITTLE_ENDIAN);
|
||||
tmp.writeByte(first);//bFirstEntry
|
||||
tmp.writeByte(last - first + 1);//bNumEntries
|
||||
tmp.writeShort(0);//wFlags
|
||||
for (int i = first; i <= last; i++) {
|
||||
tmp.writeByte((imgRGBs[i] >>> 16) & 0xff); // red
|
||||
tmp.writeByte((imgRGBs[i] >>> 8) & 0xff); // green
|
||||
tmp.writeByte(imgRGBs[i] & 0xff); // blue
|
||||
tmp.writeByte(0); // reserved*/
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tmp != null) {
|
||||
tmp.close();
|
||||
writePalette(track, tmp.toByteArray(), 0, (int) tmp.length(), isKeyframe);
|
||||
}
|
||||
return paletteChange;
|
||||
}
|
||||
|
||||
private Codec createCodec(Format fmt) {
|
||||
return Registry.getInstance().getEncoder(fmt.prepend(MimeTypeKey, MIME_AVI));
|
||||
}
|
||||
|
||||
private void createCodec(int track) {
|
||||
Track tr = tracks.get(track);
|
||||
Format fmt = tr.format;
|
||||
tr.codec = createCodec(fmt);
|
||||
String enc = fmt.get(EncodingKey);
|
||||
if (tr.codec != null) {
|
||||
if (fmt.get(MediaTypeKey) == MediaType.VIDEO) {
|
||||
tr.codec.setInputFormat(fmt.prepend(
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE,
|
||||
DataClassKey, BufferedImage.class));
|
||||
if (null == tr.codec.setOutputFormat(
|
||||
fmt.prepend(FixedFrameRateKey, true,
|
||||
QualityKey, getCompressionQuality(track),
|
||||
MimeTypeKey, MIME_AVI,
|
||||
DataClassKey, byte[].class))) {
|
||||
throw new UnsupportedOperationException("Track " + tr + " codec does not support format " + fmt + ". codec=" + tr.codec);
|
||||
}
|
||||
} else {
|
||||
tr.codec.setInputFormat(null);
|
||||
if (null == tr.codec.setOutputFormat(
|
||||
fmt.prepend(FixedFrameRateKey, true,
|
||||
QualityKey, getCompressionQuality(track),
|
||||
MimeTypeKey, MIME_AVI,
|
||||
DataClassKey, byte[].class))) {
|
||||
throw new UnsupportedOperationException("Track " + tr + " codec " + tr.codec + " does not support format. " + fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVFRSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty(int track) {
|
||||
return tracks.get(track).samples.isEmpty();
|
||||
}
|
||||
}
|
||||
1734
libsrc/avi/src/org/monte/media/avi/AbstractAVIStream.java
Normal file
1734
libsrc/avi/src/org/monte/media/avi/AbstractAVIStream.java
Normal file
File diff suppressed because it is too large
Load Diff
343
libsrc/avi/src/org/monte/media/avi/DIBCodec.java
Normal file
343
libsrc/avi/src/org/monte/media/avi/DIBCodec.java
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* @(#)DIBCodec.java
|
||||
*
|
||||
* Copyright © 2011-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.avi;
|
||||
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import org.monte.media.AbstractVideoCodec;
|
||||
import org.monte.media.Buffer;
|
||||
import org.monte.media.Format;
|
||||
import org.monte.media.io.SeekableByteArrayOutputStream;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
import static org.monte.media.BufferFlag.*;
|
||||
|
||||
/**
|
||||
* {@code DIBCodec} encodes a BufferedImage as a Microsoft Device Independent
|
||||
* Bitmap (DIB) into a byte array.
|
||||
* <p>
|
||||
* The DIB codec only works with the AVI file format. Other file formats, such
|
||||
* as QuickTime, use a different encoding for uncompressed video.
|
||||
* <p>
|
||||
* This codec currently only supports encoding from a {@code BufferedImage} into
|
||||
* the file format. Decoding support may be added in the future.
|
||||
* <p>
|
||||
* This codec does not encode the color palette of an image. This must be done
|
||||
* separately.
|
||||
* <p>
|
||||
* The pixels of a frame are written row by row from bottom to top and from
|
||||
* the left to the right. 24-bit pixels are encoded as BGR.
|
||||
* <p>
|
||||
* Supported input formats:
|
||||
* <ul>
|
||||
* {@code Format} with {@code BufferedImage.class}, any width, any height,
|
||||
* depth=4.
|
||||
* </ul>
|
||||
* Supported output formats:
|
||||
* <ul>
|
||||
* {@code Format} with {@code byte[].class}, same width and height as input
|
||||
* format, depth=4.
|
||||
* </ul>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: DIBCodec.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class DIBCodec extends AbstractVideoCodec {
|
||||
|
||||
public DIBCodec() {
|
||||
super(new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE, FixedFrameRateKey, true), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 4), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 8), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 24), //
|
||||
},
|
||||
new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE, FixedFrameRateKey, true), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 4), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 8), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
|
||||
FixedFrameRateKey, true, DepthKey, 24), //
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int process(Buffer in, Buffer out) {
|
||||
if (outputFormat.get(EncodingKey) == ENCODING_BUFFERED_IMAGE) {
|
||||
return decode(in, out);
|
||||
} else {
|
||||
return encode(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
public int decode(Buffer in, Buffer out) {
|
||||
out.setMetaTo(in);
|
||||
out.format = outputFormat;
|
||||
if (in.isFlag(DISCARD)) {
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
out.sampleCount = 1;
|
||||
BufferedImage img = null;
|
||||
|
||||
int imgType;
|
||||
switch (outputFormat.get(DepthKey)) {
|
||||
case 4:
|
||||
imgType = BufferedImage.TYPE_BYTE_INDEXED;
|
||||
break;
|
||||
case 8:
|
||||
imgType = BufferedImage.TYPE_BYTE_INDEXED;
|
||||
break;
|
||||
case 24:
|
||||
imgType = BufferedImage.TYPE_INT_RGB;
|
||||
break;
|
||||
default:
|
||||
imgType = BufferedImage.TYPE_INT_RGB;
|
||||
break;
|
||||
}
|
||||
|
||||
if (out.data instanceof BufferedImage) {
|
||||
img = (BufferedImage) out.data;
|
||||
// Fixme: Handle sub-image
|
||||
if (img.getWidth() != outputFormat.get(WidthKey)
|
||||
|| img.getHeight() != outputFormat.get(HeightKey)
|
||||
|| img.getType() != imgType) {
|
||||
img = null;
|
||||
}
|
||||
}
|
||||
if (img == null) {
|
||||
img = new BufferedImage(outputFormat.get(WidthKey), outputFormat.get(HeightKey), imgType);
|
||||
}
|
||||
out.data = img;
|
||||
|
||||
switch (outputFormat.get(DepthKey)) {
|
||||
case 4:
|
||||
readKey4((byte[]) in.data, in.offset, in.length, img);
|
||||
break;
|
||||
case 8:
|
||||
readKey8((byte[]) in.data, in.offset, in.length, img);
|
||||
break;
|
||||
case 24:
|
||||
default:
|
||||
readKey24((int[]) in.data, in.offset, in.length, img);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
public int encode(Buffer in, Buffer out) {
|
||||
out.setMetaTo(in);
|
||||
out.format = outputFormat;
|
||||
if (in.isFlag(DISCARD)) {
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
SeekableByteArrayOutputStream tmp;
|
||||
if (out.data instanceof byte[]) {
|
||||
tmp = new SeekableByteArrayOutputStream((byte[]) out.data);
|
||||
} else {
|
||||
tmp = new SeekableByteArrayOutputStream();
|
||||
}
|
||||
|
||||
// Handle sub-image
|
||||
// FIXME - Scanline stride must be a multiple of four.
|
||||
Rectangle r;
|
||||
int scanlineStride;
|
||||
if (in.data instanceof BufferedImage) {
|
||||
BufferedImage image = (BufferedImage) in.data;
|
||||
WritableRaster raster = image.getRaster();
|
||||
scanlineStride = raster.getSampleModel().getWidth();
|
||||
r = raster.getBounds();
|
||||
r.x -= raster.getSampleModelTranslateX();
|
||||
r.y -= raster.getSampleModelTranslateY();
|
||||
out.header = image.getColorModel();
|
||||
} else {
|
||||
r = new Rectangle(0, 0, outputFormat.get(WidthKey), outputFormat.get(HeightKey));
|
||||
scanlineStride = outputFormat.get(WidthKey);
|
||||
out.header = null;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (outputFormat.get(DepthKey)) {
|
||||
case 4: {
|
||||
byte[] pixels = getIndexed8(in);
|
||||
if (pixels == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_OK;
|
||||
}
|
||||
writeKey4(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
byte[] pixels = getIndexed8(in);
|
||||
if (pixels == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_OK;
|
||||
}
|
||||
writeKey8(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
int[] pixels = getRGB24(in);
|
||||
if (pixels == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_OK;
|
||||
}
|
||||
writeKey24(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
out.setFlag(KEYFRAME);
|
||||
out.data = tmp.getBuffer();
|
||||
out.sampleCount = 1;
|
||||
out.offset = 0;
|
||||
out.length = (int) tmp.getStreamPosition();
|
||||
return CODEC_OK;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
public void readKey4(byte[] in, int offset, int length, BufferedImage img) {
|
||||
DataBufferByte buf = (DataBufferByte) img.getRaster().getDataBuffer();
|
||||
WritableRaster raster = img.getRaster();
|
||||
int scanlineStride = raster.getSampleModel().getWidth();
|
||||
Rectangle r = raster.getBounds();
|
||||
r.x -= raster.getSampleModelTranslateX();
|
||||
r.y -= raster.getSampleModelTranslateY();
|
||||
|
||||
throw new UnsupportedOperationException("readKey4 not yet implemented");
|
||||
}
|
||||
|
||||
public void readKey8(byte[] in, int offset, int length, BufferedImage img) {
|
||||
DataBufferByte buf = (DataBufferByte) img.getRaster().getDataBuffer();
|
||||
WritableRaster raster = img.getRaster();
|
||||
int scanlineStride = raster.getSampleModel().getWidth();
|
||||
Rectangle r = raster.getBounds();
|
||||
r.x -= raster.getSampleModelTranslateX();
|
||||
r.y -= raster.getSampleModelTranslateY();
|
||||
|
||||
int h=img.getHeight();
|
||||
int w=img.getWidth();
|
||||
int i=offset;
|
||||
int j=r.x+r.y*scanlineStride+(h-1)*scanlineStride;
|
||||
byte[] out=buf.getData();
|
||||
for (int y=0;y<h;y++) {
|
||||
System.arraycopy(in,i,out,j,w);
|
||||
i+=w;
|
||||
j-=scanlineStride;
|
||||
}
|
||||
}
|
||||
|
||||
public void readKey24(int[] in, int offset, int length, BufferedImage img) {
|
||||
DataBufferInt buf = (DataBufferInt) img.getRaster().getDataBuffer();
|
||||
WritableRaster raster = img.getRaster();
|
||||
int scanlineStride = raster.getSampleModel().getWidth();
|
||||
Rectangle r = raster.getBounds();
|
||||
r.x -= raster.getSampleModelTranslateX();
|
||||
r.y -= raster.getSampleModelTranslateY();
|
||||
|
||||
int h=img.getHeight();
|
||||
int w=img.getWidth();
|
||||
int i=offset;
|
||||
int j=r.x+r.y*scanlineStride+(h-1)*scanlineStride;
|
||||
int[] out=buf.getData();
|
||||
for (int y=0;y<h;y++) {
|
||||
System.arraycopy(in,i,out,j,w);
|
||||
i+=w;
|
||||
j-=scanlineStride;
|
||||
}
|
||||
}
|
||||
|
||||
/** Encodes a 4-bit key frame.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param pixels The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to append to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey4(OutputStream out, byte[] pixels, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
|
||||
byte[] bytes = new byte[width];
|
||||
for (int y = (height - 1) * scanlineStride; y >= 0; y -= scanlineStride) { // Upside down
|
||||
for (int x = offset, xx = 0, n = offset + width; x < n; x += 2, ++xx) {
|
||||
bytes[xx] = (byte) (((pixels[y + x] & 0xf) << 4) | (pixels[y + x + 1] & 0xf));
|
||||
}
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Encodes an 8-bit key frame.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param pixels The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to append to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey8(OutputStream out, byte[] pixels, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
|
||||
for (int y = (height - 1) * scanlineStride; y >= 0; y -= scanlineStride) { // Upside down
|
||||
out.write(pixels, y + offset, width);
|
||||
}
|
||||
}
|
||||
|
||||
/** Encodes a 24-bit key frame.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param pixels The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to append to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey24(OutputStream out, int[] pixels, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
int w3 = width * 3;
|
||||
byte[] bytes = new byte[w3]; // holds a scanline of raw image data with 3 channels of 8 bit data
|
||||
for (int xy = (height - 1) * scanlineStride + offset; xy >= offset; xy -= scanlineStride) { // Upside down
|
||||
for (int x = 0, xp = 0; x < w3; x += 3, ++xp) {
|
||||
int p = pixels[xy + xp];
|
||||
bytes[x] = (byte) (p); // Blue
|
||||
bytes[x + 1] = (byte) (p >> 8); // Green
|
||||
bytes[x + 2] = (byte) (p >> 16); // Red
|
||||
}
|
||||
out.write(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
213
libsrc/avi/src/org/monte/media/color/Colors.java
Normal file
213
libsrc/avi/src/org/monte/media/color/Colors.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* @(#)Colors.java 1.0 2011-03-13
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.color;
|
||||
|
||||
import java.awt.image.IndexColorModel;
|
||||
import static java.lang.Math.*;
|
||||
|
||||
/**
|
||||
* {@code Colors}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-03-13 Created.
|
||||
*/
|
||||
public class Colors {
|
||||
|
||||
/** Prevent instance creation. */
|
||||
private Colors() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The macintosh palette is arranged as follows: there are 256 colours to
|
||||
* allocate, an even distribution of colors through the color cube might be
|
||||
* desirable but 256 is not the cube of an integer. 6x6x6 is 216 and so the
|
||||
* first 216 colors are an equal 6x6x6 sampling of the color cube.
|
||||
* This leaves 40 colours to allocate, this has been done by choosing a ramp of
|
||||
* 10 shades each for red, green, blue and grey.
|
||||
*
|
||||
* <p>
|
||||
* References:<br>
|
||||
* <a href="http://paulbourke.net/texture_colour/colourramp/">http://paulbourke.net/texture_colour/colourramp/</a>
|
||||
*
|
||||
* @return The Macintosh color palette.
|
||||
*/
|
||||
public static IndexColorModel createMacColors() {
|
||||
byte[] r = new byte[256];
|
||||
byte[] g = new byte[256];
|
||||
byte[] b = new byte[256];
|
||||
|
||||
// Generate color cube with 216 colors
|
||||
int index = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < 6; j++) {
|
||||
for (int k = 0; k < 6; k++) {
|
||||
r[index] = (byte) (255 - 51 * i);
|
||||
g[index] = (byte) (255 - 51 * j);
|
||||
b[index] = (byte) (255 - 51 * k);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index--; // overwrite last color (black) with color ramp
|
||||
|
||||
// Generate red ramp
|
||||
byte[] ramp = {(byte) 238, (byte) 221, (byte) 187, (byte) 170, (byte) 136, (byte) 119, 85, 68, 34, 17};
|
||||
for (int i = 0; i < 10; i++) {
|
||||
r[index] = ramp[i];
|
||||
g[index] = (byte) (0);
|
||||
b[index] = (byte) (0);
|
||||
index++;
|
||||
}
|
||||
// Generate green ramp
|
||||
for (int j = 0; j < 10; j++) {
|
||||
r[index] = (byte) (0);
|
||||
g[index] = ramp[j];
|
||||
b[index] = (byte) (0);
|
||||
index++;
|
||||
}
|
||||
// Generate blue ramp
|
||||
for (int k = 0; k < 10; k++) {
|
||||
r[index] = (byte) (0);
|
||||
g[index] = (byte) (0);
|
||||
b[index] = ramp[k];
|
||||
index++;
|
||||
}
|
||||
// Generate gray ramp
|
||||
for (int ijk = 0; ijk < 10; ijk++) {
|
||||
r[index] = ramp[ijk];
|
||||
g[index] = ramp[ijk];
|
||||
b[index] = ramp[ijk];
|
||||
index++;
|
||||
}
|
||||
// last color is black (nothing to do)
|
||||
|
||||
/*
|
||||
for (int i=0;i<256;i++) {
|
||||
if (i%6==0) System.out.println(); else System.out.print(" ");
|
||||
System.out.print(Integer.toHexString(r[i]&0xff)+","+Integer.toHexString(g[i]&0xff)+","+Integer.toHexString(b[i]&0xff));
|
||||
}*/
|
||||
|
||||
IndexColorModel icm = new IndexColorModel(8, 256, r, g, b);
|
||||
return icm;
|
||||
}
|
||||
|
||||
private static void RGBtoYCC(float[] rgb, float[] ycc) {
|
||||
float R = rgb[0];
|
||||
float G = rgb[1];
|
||||
float B = rgb[2];
|
||||
float Y = 0.3f * R + 0.6f * G + 0.1f * B;
|
||||
float V = R - Y;
|
||||
float U = B - Y;
|
||||
float Cb = (U / 2f) + 0.5f;
|
||||
float Cr = (V / 1.6f) + 0.5f;
|
||||
ycc[0] = Y;
|
||||
ycc[1] = Cb;
|
||||
ycc[2] = Cr;
|
||||
}
|
||||
|
||||
private static void YCCtoRGB(float[] ycc, float[] rgb) {
|
||||
float Y = ycc[0];
|
||||
float Cb = ycc[1];
|
||||
float Cr = ycc[2];
|
||||
float U = (Cb - 0.5f) * 2f;
|
||||
float V = (Cr - 0.5f) * 1.6f;
|
||||
float R = V + Y;
|
||||
float B = U + Y;
|
||||
float G = (Y - 0.3f * R - 0.1f * B) / 0.6f;
|
||||
rgb[0] = R;
|
||||
rgb[1] = G;
|
||||
rgb[2] = B;
|
||||
}
|
||||
|
||||
/** RGB 8-bit per channel to YCC 16-bit per channel. */
|
||||
private static void RGB8toYCC16(int[] rgb, int[] ycc) {
|
||||
int R = rgb[0];
|
||||
int G = rgb[1];
|
||||
int B = rgb[2];
|
||||
int Y = 77 * R + 153 * G + 26 * B;
|
||||
int V = R * 256 - Y;
|
||||
int U = B * 256 - Y;
|
||||
int Cb = (U / 2) + 128 * 256;
|
||||
int Cr = (V * 5 / 8) + 128 * 256;
|
||||
ycc[0] = Y;
|
||||
ycc[1] = Cb;
|
||||
ycc[2] = Cr;
|
||||
}
|
||||
|
||||
/** RGB 8-bit per channel to YCC 16-bit per channel. */
|
||||
private static void RGB8toYCC16(int rgb, int[] ycc) {
|
||||
int R = (rgb & 0xff0000) >>> 16;
|
||||
int G = (rgb & 0xff00) >>> 8;
|
||||
int B = rgb & 0xff;
|
||||
int Y = 77 * R + 153 * G + 26 * B;
|
||||
int V = R * 256 - Y;
|
||||
int U = B * 256 - Y;
|
||||
int Cb = (U / 2) + 128 * 256;
|
||||
int Cr = (V * 5 / 8) + 128 * 256;
|
||||
ycc[0] = Y;
|
||||
ycc[1] = Cb;
|
||||
ycc[2] = Cr;
|
||||
}
|
||||
|
||||
/** YCC 16-bit per channel to RGB 8-bit per channel. */
|
||||
private static void YCC16toRGB8(int[] ycc, int[] rgb) {
|
||||
int Y = ycc[0];
|
||||
int Cb = ycc[1];
|
||||
int Cr = ycc[2];
|
||||
int U = (Cb - 128 * 256) * 2;
|
||||
int V = (Cr - 128 * 256) * 8 / 5;
|
||||
int R = min(255, max(0, (V + Y) / 256));
|
||||
int B = min(255, max(0, (U + Y) / 256));
|
||||
int G = min(255, max(0, (Y - 77 * R - 26 * B) / 153));
|
||||
rgb[0] = R;
|
||||
rgb[1] = G;
|
||||
rgb[2] = B;
|
||||
}
|
||||
|
||||
/** YCC 8-bit per channel to RGB 8-bit per channel.
|
||||
*/
|
||||
private static void YCC8toRGB8(int[] ycc, int[] rgb) {
|
||||
int Y = ycc[0];
|
||||
int Cb = ycc[1];
|
||||
int Cr = ycc[2];
|
||||
// Source: JPEG File Interchange Format Version 1.02, September 1, 1992
|
||||
//RGB can be computed directly from YCbCr (256 levels) as follows:
|
||||
//R = Y + 1.402 (Cr-128)
|
||||
//G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
|
||||
//B = Y + 1.772 (Cb-128)
|
||||
int R = (1000 * Y + 1402 * (Cr - 128)) / 1000;
|
||||
int G = (100000 * Y - 34414 * (Cb - 128) - 71414 * (Cr - 128)) / 100000;
|
||||
int B = (1000 * Y + 1772 * (Cb - 128)) / 1000;
|
||||
rgb[0] = min(255, max(0, R));
|
||||
rgb[1] = min(255, max(0, G));
|
||||
rgb[2] = min(255, max(0, B));
|
||||
}
|
||||
|
||||
/** YCC 8-bit per channel to RGB 8-bit per channel.
|
||||
*/
|
||||
private static void RGB8toYCC8(int[] rgb, int[] ycc) {
|
||||
int R = rgb[0];
|
||||
int G = rgb[1];
|
||||
int B = rgb[2];
|
||||
// Source: JPEG File Interchange Format Version 1.02, September 1, 1992
|
||||
//YCbCr (256 levels) can be computed directly from 8-bit RGB as follows:
|
||||
//Y = 0.299R +0.587G +0.114B
|
||||
//Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
|
||||
//Cr = 0.5 R - 0.4187 G - 0.0813 B + 128
|
||||
int Y = (299 * R + 587 * G + 114 * B) / 1000;
|
||||
int Cb = (-1687 * R - 3313 * G + 5000 * B) / 10000 + 128;
|
||||
int Cr = (5000 * R - 4187 * G - 813 * B) / 10000 + 128;
|
||||
ycc[0] = min(255, max(0, Y));
|
||||
ycc[1] = min(255, max(0, Cb));
|
||||
ycc[2] = min(255, max(0, Cr));
|
||||
}
|
||||
}
|
||||
216
libsrc/avi/src/org/monte/media/io/ByteArrayImageInputStream.java
Normal file
216
libsrc/avi/src/org/monte/media/io/ByteArrayImageInputStream.java
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* @(#)ByteArrayImageInputStream.java
|
||||
*
|
||||
* Copyright (c) 2008-2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* A {@code ByteArrayImageInputStream} contains
|
||||
* an internal buffer that contains bytes that
|
||||
* may be read from the stream. An internal
|
||||
* counter keeps track of the next byte to
|
||||
* be supplied by the {@code read} method.
|
||||
* <p>
|
||||
* Closing a {@code ByteArrayImageInputStream} has no effect. The methods in
|
||||
* this class can be called after the stream has been closed without
|
||||
* generating an {@code IOException}.
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau
|
||||
* @version $Id: ByteArrayImageInputStream.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class ByteArrayImageInputStream extends ImageInputStreamImpl2 {
|
||||
/**
|
||||
* An array of bytes that was provided
|
||||
* by the creator of the stream. Elements <code>buf[0]</code>
|
||||
* through <code>buf[count-1]</code> are the
|
||||
* only bytes that can ever be read from the
|
||||
* stream; element <code>buf[streamPos]</code> is
|
||||
* the next byte to be read.
|
||||
*/
|
||||
protected byte buf[];
|
||||
|
||||
/**
|
||||
* The index one greater than the last valid character in the input
|
||||
* stream buffer.
|
||||
* This value should always be nonnegative
|
||||
* and not larger than the length of <code>buf</code>.
|
||||
* It is one greater than the position of
|
||||
* the last byte within <code>buf</code> that
|
||||
* can ever be read from the input stream buffer.
|
||||
*/
|
||||
protected int count;
|
||||
|
||||
/** The offset to the start of the array. */
|
||||
private final int arrayOffset;
|
||||
|
||||
public ByteArrayImageInputStream(byte[] buf) {
|
||||
this(buf, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
public ByteArrayImageInputStream(byte[] buf, ByteOrder byteOrder) {
|
||||
this(buf, 0, buf.length, byteOrder);
|
||||
}
|
||||
|
||||
public ByteArrayImageInputStream(byte[] buf, int offset, int length, ByteOrder byteOrder) {
|
||||
this.buf = buf;
|
||||
this.streamPos = offset;
|
||||
this.count = Math.min(offset + length, buf.length);
|
||||
this.arrayOffset = offset;
|
||||
this.byteOrder = byteOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next byte of data from this input stream. The value
|
||||
* byte is returned as an <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method
|
||||
* cannot block.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> if the end of the
|
||||
* stream has been reached.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read() {
|
||||
flushBits();
|
||||
return (streamPos < count) ? (buf[(int)(streamPos++)] & 0xff) : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to <code>len</code> bytes of data into an array of bytes
|
||||
* from this input stream.
|
||||
* If <code>streamPos</code> equals <code>count</code>,
|
||||
* then <code>-1</code> is returned to indicate
|
||||
* end of file. Otherwise, the number <code>k</code>
|
||||
* of bytes read is equal to the smaller of
|
||||
* <code>len</code> and <code>count-streamPos</code>.
|
||||
* If <code>k</code> is positive, then bytes
|
||||
* <code>buf[streamPos]</code> through <code>buf[streamPos+k-1]</code>
|
||||
* are copied into <code>b[off]</code> through
|
||||
* <code>b[off+k-1]</code> in the manner performed
|
||||
* by <code>System.arraycopy</code>. The
|
||||
* value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method cannot block.
|
||||
*
|
||||
* @param b the buffer into which the data is read.
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes read.
|
||||
* @return the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> if there is no more data because the end of
|
||||
* the stream has been reached.
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read(byte b[], int off, int len) {
|
||||
flushBits();
|
||||
if (b == null) {
|
||||
throw new NullPointerException();
|
||||
} else if (off < 0 || len < 0 || len > b.length - off) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (streamPos >= count) {
|
||||
return -1;
|
||||
}
|
||||
if (streamPos + len > count) {
|
||||
len = (int)(count - streamPos);
|
||||
}
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
System.arraycopy(buf, (int)streamPos, b, off, len);
|
||||
streamPos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips <code>n</code> bytes of input from this input stream. Fewer
|
||||
* bytes might be skipped if the end of the input stream is reached.
|
||||
* The actual number <code>k</code>
|
||||
* of bytes to be skipped is equal to the smaller
|
||||
* of <code>n</code> and <code>count-streamPos</code>.
|
||||
* The value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> is returned.
|
||||
*
|
||||
* @param n the number of bytes to be skipped.
|
||||
* @return the actual number of bytes skipped.
|
||||
*/
|
||||
public synchronized long skip(long n) {
|
||||
if (streamPos + n > count) {
|
||||
n = count - streamPos;
|
||||
}
|
||||
if (n < 0) {
|
||||
return 0;
|
||||
}
|
||||
streamPos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of remaining bytes that can be read (or skipped over)
|
||||
* from this input stream.
|
||||
* <p>
|
||||
* The value returned is <code>count - streamPos</code>,
|
||||
* which is the number of bytes remaining to be read from the input buffer.
|
||||
*
|
||||
* @return the number of remaining bytes that can be read (or skipped
|
||||
* over) from this input stream without blocking.
|
||||
*/
|
||||
public synchronized int available() {
|
||||
return (int)(count - streamPos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in
|
||||
* this class can be called after the stream has been closed without
|
||||
* generating an <tt>IOException</tt>.
|
||||
* <p>
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
// does nothing!!
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamPosition() throws IOException {
|
||||
checkClosed();
|
||||
return streamPos-arrayOffset;
|
||||
}
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
checkClosed();
|
||||
flushBits();
|
||||
|
||||
// This test also covers pos < 0
|
||||
if (pos < flushedPos) {
|
||||
throw new IndexOutOfBoundsException("pos < flushedPos!");
|
||||
}
|
||||
|
||||
this.streamPos = pos+arrayOffset;
|
||||
}
|
||||
|
||||
private void flushBits() {
|
||||
bitOffset=0;
|
||||
}
|
||||
@Override
|
||||
public long length() {
|
||||
return count-arrayOffset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* @(#)ByteArrayImageOutputStream.java 1.0.1 2011-01-23
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer
|
||||
* Staldenmattweg 2, Goldau, CH-6405, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* The copyright of this software is owned by Werner Randelshofer.
|
||||
* You may not use, copy or modify this software, except in
|
||||
* accordance with the license agreement you entered into with
|
||||
* Werner Randelshofer. For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.nio.ByteOrder;
|
||||
import static java.lang.Math.*;
|
||||
|
||||
/**
|
||||
* This class implements an image output stream in which the data is
|
||||
* written into a byte array. The buffer automatically grows as data
|
||||
* is written to it.
|
||||
* The data can be retrieved using {@code toByteArray()}, {@code toImageOutputStream()}
|
||||
* and {@code toOutputStream()}.
|
||||
* <p>
|
||||
* Closing a {@code ByteArrayImageOutputStream} has no effect. The methods in
|
||||
* this class can be called after the stream has been closed without
|
||||
* generating an {@code IOException}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0.1 2011-01-23 Implements length method.
|
||||
* <br>1.0 2011-01-18 Created.
|
||||
*/
|
||||
public class ByteArrayImageOutputStream extends ImageOutputStreamImpl {
|
||||
|
||||
|
||||
/**
|
||||
* An array of bytes that was provided
|
||||
* by the creator of the stream. Elements <code>buf[0]</code>
|
||||
* through <code>buf[count-1]</code> are the
|
||||
* only bytes that can ever be read from the
|
||||
* stream; element <code>buf[streamPos]</code> is
|
||||
* the next byte to be read.
|
||||
*/
|
||||
protected byte buf[];
|
||||
/**
|
||||
* The index one greater than the last valid character in the input
|
||||
* stream buffer.
|
||||
* This value should always be nonnegative
|
||||
* and not larger than the length of <code>buf</code>.
|
||||
* It is one greater than the position of
|
||||
* the last byte within <code>buf</code> that
|
||||
* can ever be read from the input stream buffer.
|
||||
*/
|
||||
protected int count;
|
||||
/** The offset to the start of the array. */
|
||||
private final int arrayOffset;
|
||||
|
||||
public ByteArrayImageOutputStream() {
|
||||
this(16);
|
||||
}
|
||||
|
||||
public ByteArrayImageOutputStream(int initialCapacity) {
|
||||
this(new byte[initialCapacity]);
|
||||
}
|
||||
|
||||
public ByteArrayImageOutputStream(byte[] buf) {
|
||||
this(buf, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
public ByteArrayImageOutputStream(byte[] buf, ByteOrder byteOrder) {
|
||||
this(buf, 0, buf.length, byteOrder);
|
||||
}
|
||||
|
||||
public ByteArrayImageOutputStream(byte[] buf, int offset, int length, ByteOrder byteOrder) {
|
||||
this.buf = buf;
|
||||
this.streamPos = offset;
|
||||
this.count = Math.min(offset + length, buf.length);
|
||||
this.arrayOffset = offset;
|
||||
this.byteOrder = byteOrder;
|
||||
}
|
||||
|
||||
public ByteArrayImageOutputStream(ByteOrder byteOrder) {
|
||||
this(new byte[16],byteOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next byte of data from this input stream. The value
|
||||
* byte is returned as an <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method
|
||||
* cannot block.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> if the end of the
|
||||
* stream has been reached.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
flushBits();
|
||||
return (streamPos < count) ? (buf[(int) (streamPos++)] & 0xff) : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to <code>len</code> bytes of data into an array of bytes
|
||||
* from this input stream.
|
||||
* If <code>streamPos</code> equals <code>count</code>,
|
||||
* then <code>-1</code> is returned to indicate
|
||||
* end of file. Otherwise, the number <code>k</code>
|
||||
* of bytes read is equal to the smaller of
|
||||
* <code>len</code> and <code>count-streamPos</code>.
|
||||
* If <code>k</code> is positive, then bytes
|
||||
* <code>buf[streamPos]</code> through <code>buf[streamPos+k-1]</code>
|
||||
* are copied into <code>b[off]</code> through
|
||||
* <code>b[off+k-1]</code> in the manner performed
|
||||
* by <code>System.arraycopy</code>. The
|
||||
* value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method cannot block.
|
||||
*
|
||||
* @param b the buffer into which the data is read.
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes read.
|
||||
* @return the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> if there is no more data because the end of
|
||||
* the stream has been reached.
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read(byte b[], int off, int len) throws IOException {
|
||||
flushBits();
|
||||
if (b == null) {
|
||||
throw new NullPointerException();
|
||||
} else if (off < 0 || len < 0 || len > b.length - off) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (streamPos >= count) {
|
||||
return -1;
|
||||
}
|
||||
if (streamPos + len > count) {
|
||||
len = (int) (count - streamPos);
|
||||
}
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
System.arraycopy(buf, (int) streamPos, b, off, len);
|
||||
streamPos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips <code>n</code> bytes of input from this input stream. Fewer
|
||||
* bytes might be skipped if the end of the input stream is reached.
|
||||
* The actual number <code>k</code>
|
||||
* of bytes to be skipped is equal to the smaller
|
||||
* of <code>n</code> and <code>count-streamPos</code>.
|
||||
* The value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> is returned.
|
||||
*
|
||||
* @param n the number of bytes to be skipped.
|
||||
* @return the actual number of bytes skipped.
|
||||
*/
|
||||
public synchronized long skip(long n) {
|
||||
if (streamPos + n > count) {
|
||||
n = count - streamPos;
|
||||
}
|
||||
if (n < 0) {
|
||||
return 0;
|
||||
}
|
||||
streamPos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of remaining bytes that can be read (or skipped over)
|
||||
* from this input stream.
|
||||
* <p>
|
||||
* The value returned is <code>count - streamPos</code>,
|
||||
* which is the number of bytes remaining to be read from the input buffer.
|
||||
*
|
||||
* @return the number of remaining bytes that can be read (or skipped
|
||||
* over) from this input stream without blocking.
|
||||
*/
|
||||
public synchronized int available() {
|
||||
return (int) (count - streamPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in
|
||||
* this class can be called after the stream has been closed without
|
||||
* generating an <tt>IOException</tt>.
|
||||
* <p>
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
// does nothing!!
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamPosition() throws IOException {
|
||||
checkClosed();
|
||||
return streamPos - arrayOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
checkClosed();
|
||||
flushBits();
|
||||
|
||||
// This test also covers pos < 0
|
||||
if (pos < flushedPos) {
|
||||
throw new IndexOutOfBoundsException("pos < flushedPos!");
|
||||
}
|
||||
|
||||
this.streamPos = pos + arrayOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this output stream.
|
||||
*
|
||||
* @param b the byte to be written.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(int b) throws IOException {
|
||||
flushBits();
|
||||
long newcount = max(streamPos + 1, count);
|
||||
if (newcount> Integer.MAX_VALUE) {
|
||||
throw new IndexOutOfBoundsException(newcount+" > max array size");
|
||||
}
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, max(buf.length << 1, (int) newcount));
|
||||
}
|
||||
buf[(int) streamPos++] = (byte) b;
|
||||
count = (int)newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte array to this output stream.
|
||||
*
|
||||
* @param b the data.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(byte b[]) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified byte array
|
||||
* starting at offset <code>off</code> to this output stream.
|
||||
*
|
||||
* @param b the data.
|
||||
* @param off the start offset in the data.
|
||||
* @param len the number of bytes to write.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(byte b[], int off, int len) throws IOException {
|
||||
flushBits();
|
||||
if ((off < 0) || (off > b.length) || (len < 0)
|
||||
|| ((off + len) > b.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException("off="+off+", len="+len+", b.length="+b.length);
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
int newcount = max((int) streamPos + len, count);
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
|
||||
}
|
||||
System.arraycopy(b, off, buf, (int) streamPos, len);
|
||||
streamPos += len;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/** Writes the contents of the byte array into the specified output
|
||||
* stream.
|
||||
* @param out
|
||||
*/
|
||||
public void toOutputStream(OutputStream out) throws IOException {
|
||||
out.write(buf, arrayOffset, count);
|
||||
}
|
||||
|
||||
/** Writes the contents of the byte array into the specified image output
|
||||
* stream.
|
||||
* @param out
|
||||
*/
|
||||
public void toImageOutputStream(ImageOutputStream out) throws IOException {
|
||||
out.write(buf, arrayOffset, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a newly allocated byte array. Its size is the current
|
||||
* size of this output stream and the valid contents of the buffer
|
||||
* have been copied into it.
|
||||
*
|
||||
* @return the current contents of this output stream, as a byte array.
|
||||
* @see java.io.ByteArrayOutputStream#size()
|
||||
*/
|
||||
public synchronized byte[] toByteArray() {
|
||||
byte[] copy = new byte[count - arrayOffset];
|
||||
System.arraycopy(buf, arrayOffset, copy, 0, count);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Returns the internally used byte buffer. */
|
||||
public byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return count-arrayOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the <code>count</code> field of this byte array output
|
||||
* stream to zero, so that all currently accumulated output in the
|
||||
* output stream is discarded. The output stream can be used again,
|
||||
* reusing the already allocated buffer space.
|
||||
*
|
||||
* @see java.io.ByteArrayInputStream#count
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
count = arrayOffset;
|
||||
streamPos=arrayOffset;
|
||||
}
|
||||
}
|
||||
189
libsrc/avi/src/org/monte/media/io/ImageInputStreamAdapter.java
Normal file
189
libsrc/avi/src/org/monte/media/io/ImageInputStreamAdapter.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* @(#)ImageInputStreamAdapter.java 1.0 2009-12-17
|
||||
*
|
||||
* Copyright (c) 2009 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
/**
|
||||
* ImageInputStreamAdapter.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2009-12-17 Created.
|
||||
*/
|
||||
public class ImageInputStreamAdapter extends FilterInputStream {
|
||||
private ImageInputStream iis;
|
||||
public ImageInputStreamAdapter(ImageInputStream iis) {
|
||||
super(null);
|
||||
this.iis=iis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next byte of data from this input stream. The value
|
||||
* byte is returned as an <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned. This method blocks until input data
|
||||
* is available, the end of the stream is detected, or an exception
|
||||
* is thrown.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.read()</code> and returns the result.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> if the end of the
|
||||
* stream is reached.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return iis.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to <code>len</code> bytes of data from this input stream
|
||||
* into an array of bytes. If <code>len</code> is not zero, the method
|
||||
* blocks until some input is available; otherwise, no
|
||||
* bytes are read and <code>0</code> is returned.
|
||||
* <p>
|
||||
* This method simply performs <code>in.read(b, off, len)</code>
|
||||
* and returns the result.
|
||||
*
|
||||
* @param b the buffer into which the data is read.
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes read.
|
||||
* @return the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> if there is no more data because the end of
|
||||
* the stream has been reached.
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
@Override
|
||||
public int read(byte b[], int off, int len) throws IOException {
|
||||
return iis.read(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This method simply performs <code>in.skip(n)</code>.
|
||||
*/
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return iis.skipBytes(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an estimate of the number of bytes that can be read (or
|
||||
* skipped over) from this input stream without blocking by the next
|
||||
* caller of a method for this input stream. The next caller might be
|
||||
* the same thread or another thread. A single read or skip of this
|
||||
* many bytes will not block, but may read or skip fewer bytes.
|
||||
* <p>
|
||||
* This method returns the result of {@link #in in}.available().
|
||||
*
|
||||
* @return an estimate of the number of bytes that can be read (or skipped
|
||||
* over) from this input stream without blocking.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (iis.isCached()) ? //
|
||||
(int)Math.min(Integer.MAX_VALUE, iis.length() - iis.getStreamPosition()) :
|
||||
0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this input stream and releases any system resources
|
||||
* associated with the stream.
|
||||
* This
|
||||
* method simply performs <code>in.close()</code>.
|
||||
*
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
iis.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current position in this input stream. A subsequent
|
||||
* call to the <code>reset</code> method repositions this stream at
|
||||
* the last marked position so that subsequent reads re-read the same bytes.
|
||||
* <p>
|
||||
* The <code>readlimit</code> argument tells this input stream to
|
||||
* allow that many bytes to be read before the mark position gets
|
||||
* invalidated.
|
||||
* <p>
|
||||
* This method simply performs <code>in.mark(readlimit)</code>.
|
||||
*
|
||||
* @param readlimit the maximum limit of bytes that can be read before
|
||||
* the mark position becomes invalid.
|
||||
* @see java.io.FilterInputStream#in
|
||||
* @see java.io.FilterInputStream#reset()
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
iis.mark();
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions this stream to the position at the time the
|
||||
* <code>mark</code> method was last called on this input stream.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.reset()</code>.
|
||||
* <p>
|
||||
* Stream marks are intended to be used in
|
||||
* situations where you need to read ahead a little to see what's in
|
||||
* the stream. Often this is most easily done by invoking some
|
||||
* general parser. If the stream is of the type handled by the
|
||||
* parse, it just chugs along happily. If the stream is not of
|
||||
* that type, the parser should toss an exception when it fails.
|
||||
* If this happens within readlimit bytes, it allows the outer
|
||||
* code to reset the stream and try another parser.
|
||||
*
|
||||
* @exception IOException if the stream has not been marked or if the
|
||||
* mark has been invalidated.
|
||||
* @see java.io.FilterInputStream#in
|
||||
* @see java.io.FilterInputStream#mark(int)
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
iis.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this input stream supports the <code>mark</code>
|
||||
* and <code>reset</code> methods.
|
||||
* This method
|
||||
* simply performs <code>in.markSupported()</code>.
|
||||
*
|
||||
* @return <code>true</code> if this stream type supports the
|
||||
* <code>mark</code> and <code>reset</code> method;
|
||||
* <code>false</code> otherwise.
|
||||
* @see java.io.FilterInputStream#in
|
||||
* @see java.io.InputStream#mark(int)
|
||||
* @see java.io.InputStream#reset()
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
65
libsrc/avi/src/org/monte/media/io/ImageInputStreamImpl2.java
Normal file
65
libsrc/avi/src/org/monte/media/io/ImageInputStreamImpl2.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* @(#)ImageInputStreamImpl2.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
|
||||
/**
|
||||
* {@code ImageInputStreamImpl2} fixes bugs in ImageInputStreamImpl.
|
||||
* <p>
|
||||
* ImageInputStreamImpl uses read(byte[]) instead of readFully(byte[]) inside of
|
||||
* readShort. This results in corrupt data input if the underlying stream can
|
||||
* not fulfill the read operation in a single step.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: ImageInputStreamImpl2.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public abstract class ImageInputStreamImpl2 extends ImageInputStreamImpl {
|
||||
// Length of the buffer used for readFully(type[], int, int)
|
||||
private static final int BYTE_BUF_LENGTH = 8192;
|
||||
/**
|
||||
* Byte buffer used for readFully(type[], int, int). Note that this
|
||||
* array is also used for bulk reads in readShort(), readInt(), etc, so
|
||||
* it should be large enough to hold a primitive value (i.e. >= 8 bytes).
|
||||
* Also note that this array is package protected, so that it can be
|
||||
* used by ImageOutputStreamImpl in a similar manner.
|
||||
*/
|
||||
byte[] byteBuf = new byte[BYTE_BUF_LENGTH];
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
readFully(byteBuf, 0, 2);
|
||||
|
||||
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||
return (short)
|
||||
(((byteBuf[0] & 0xff) << 8) | ((byteBuf[1] & 0xff) << 0));
|
||||
} else {
|
||||
return (short)
|
||||
(((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0));
|
||||
}
|
||||
}
|
||||
public int readInt() throws IOException {
|
||||
readFully(byteBuf, 0, 4);
|
||||
|
||||
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||
return
|
||||
(((byteBuf[0] & 0xff) << 24) | ((byteBuf[1] & 0xff) << 16) |
|
||||
((byteBuf[2] & 0xff) << 8) | ((byteBuf[3] & 0xff) << 0));
|
||||
} else {
|
||||
return
|
||||
(((byteBuf[3] & 0xff) << 24) | ((byteBuf[2] & 0xff) << 16) |
|
||||
((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* @(#)SeekableByteArrayOutputStream.java
|
||||
*
|
||||
* Copyright © 2010-2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import static java.lang.Math.*;
|
||||
/**
|
||||
* {@code SeekableByteArrayOutputStream}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: SeekableByteArrayOutputStream.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class SeekableByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
/**
|
||||
* The current stream position.
|
||||
*/
|
||||
private int pos;
|
||||
|
||||
/**
|
||||
* Creates a new byte array output stream. The buffer capacity is
|
||||
* initially 32 bytes, though its size increases if necessary.
|
||||
*/
|
||||
public SeekableByteArrayOutputStream() {
|
||||
this(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new byte array output stream, with a buffer capacity of
|
||||
* the specified size, in bytes.
|
||||
*
|
||||
* @param size the initial size.
|
||||
* @exception IllegalArgumentException if size is negative.
|
||||
*/
|
||||
public SeekableByteArrayOutputStream(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("Negative initial size: "
|
||||
+ size);
|
||||
}
|
||||
buf = new byte[size];
|
||||
}
|
||||
/**
|
||||
* Creates a new byte array output stream, which reuses the supplied buffer.
|
||||
*/
|
||||
public SeekableByteArrayOutputStream(byte[] buf) {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this byte array output stream.
|
||||
*
|
||||
* @param b the byte to be written.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(int b) {
|
||||
int newcount = max(pos + 1, count);
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
|
||||
}
|
||||
buf[pos++] = (byte)b;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified byte array
|
||||
* starting at offset <code>off</code> to this byte array output stream.
|
||||
*
|
||||
* @param b the data.
|
||||
* @param off the start offset in the data.
|
||||
* @param len the number of bytes to write.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(byte b[], int off, int len) {
|
||||
if ((off < 0) || (off > b.length) || (len < 0) ||
|
||||
((off + len) > b.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
int newcount = max(pos+len,count);
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
|
||||
}
|
||||
System.arraycopy(b, off, buf, pos, len);
|
||||
pos+=len;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the <code>count</code> field of this byte array output
|
||||
* stream to zero, so that all currently accumulated output in the
|
||||
* output stream is discarded. The output stream can be used again,
|
||||
* reusing the already allocated buffer space.
|
||||
*
|
||||
* @see java.io.ByteArrayInputStream#count
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() {
|
||||
count = 0;
|
||||
pos=0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current stream position to the desired location. The
|
||||
* next read will occur at this location. The bit offset is set
|
||||
* to 0.
|
||||
*
|
||||
* <p> An <code>IndexOutOfBoundsException</code> will be thrown if
|
||||
* <code>pos</code> is smaller than the flushed position (as
|
||||
* returned by <code>getflushedPosition</code>).
|
||||
*
|
||||
* <p> It is legal to seek past the end of the file; an
|
||||
* <code>EOFException</code> will be thrown only if a read is
|
||||
* performed.
|
||||
*
|
||||
* @param pos a <code>long</code> containing the desired file
|
||||
* pointer position.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if <code>pos</code> is smaller
|
||||
* than the flushed position.
|
||||
* @exception IOException if any other I/O error occurs.
|
||||
*/
|
||||
public void seek(long pos) throws IOException {
|
||||
this.pos = (int)pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte position of the stream. The next write
|
||||
* will take place starting at this offset.
|
||||
*
|
||||
* @return a long containing the position of the stream.
|
||||
*
|
||||
* @exception IOException if an I/O error occurs.
|
||||
*/
|
||||
public long getStreamPosition() throws IOException {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Writes the contents of the byte array into the specified output
|
||||
* stream.
|
||||
* @param out
|
||||
*/
|
||||
public void toOutputStream(OutputStream out) throws IOException {
|
||||
out.write(buf, 0, count);
|
||||
}
|
||||
|
||||
/** Returns the underlying byte buffer. */
|
||||
public byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
151
libsrc/avi/src/org/monte/media/io/SubImageOutputStream.java
Normal file
151
libsrc/avi/src/org/monte/media/io/SubImageOutputStream.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* @(#)SubImageOutputStream.java 1.0 2011-07-20
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
|
||||
/**
|
||||
* {@code SubImageOutputStream}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-07-20 Created.
|
||||
*/
|
||||
public class SubImageOutputStream extends ImageOutputStreamImpl {
|
||||
|
||||
private ImageOutputStream out;
|
||||
private long offset;
|
||||
private long length;
|
||||
|
||||
/** Whether flush and close request shall be forwarded to underlying stream.*/
|
||||
private boolean forwardFlushAndClose;
|
||||
|
||||
public SubImageOutputStream(ImageOutputStream out, ByteOrder bo,boolean forwardFlushAndClose) throws IOException {
|
||||
this(out, out.getStreamPosition(),bo,forwardFlushAndClose);
|
||||
}
|
||||
|
||||
public SubImageOutputStream(ImageOutputStream out, long offset, ByteOrder bo,boolean forwardFlushAndClose) throws IOException {
|
||||
this.out = out;
|
||||
this.offset = offset;
|
||||
this.forwardFlushAndClose=forwardFlushAndClose;
|
||||
setByteOrder(bo);
|
||||
out.seek(offset);
|
||||
}
|
||||
|
||||
private long available() throws IOException {
|
||||
checkClosed();
|
||||
long pos = out.getStreamPosition();
|
||||
if (pos < offset) {
|
||||
out.seek(offset);
|
||||
pos = offset;
|
||||
}
|
||||
return offset + out.length() - pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (available() <= 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return out.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
long av = available();
|
||||
if (av <= 0) {
|
||||
return -1;
|
||||
} else {
|
||||
int result = out.read(b, off, (int) Math.min(len, av));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamPosition() throws IOException {
|
||||
return out.getStreamPosition() - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
out.seek(pos + offset);
|
||||
length=Math.max(pos-offset+1,length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (forwardFlushAndClose) {
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (forwardFlushAndClose) {
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFlushedPosition() {
|
||||
return out.getFlushedPosition() - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation returns false. Subclasses should
|
||||
* override this if they cache data.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCached() {
|
||||
return out.isCached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation returns false. Subclasses should
|
||||
* override this if they cache data in main memory.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCachedMemory() {
|
||||
return out.isCachedMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachedFile() {
|
||||
return out.isCachedFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
length = Math.max(out.getStreamPosition()-offset,length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
out.write(b,off,len);
|
||||
length = Math.max(out.getStreamPosition()-offset,length);
|
||||
}
|
||||
|
||||
public void dispose() throws IOException {
|
||||
if (forwardFlushAndClose) {
|
||||
checkClosed();
|
||||
}
|
||||
out=null;
|
||||
}
|
||||
}
|
||||
155
libsrc/avi/src/org/monte/media/jpeg/JPEGCodec.java
Normal file
155
libsrc/avi/src/org/monte/media/jpeg/JPEGCodec.java
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* @(#)JPGCodec.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.jpeg;
|
||||
|
||||
import org.monte.media.io.ByteArrayImageInputStream;
|
||||
import javax.imageio.ImageReader;
|
||||
import org.monte.media.Format;
|
||||
import org.monte.media.AbstractVideoCodec;
|
||||
import org.monte.media.Buffer;
|
||||
import org.monte.media.io.ByteArrayImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
import static org.monte.media.BufferFlag.*;
|
||||
|
||||
/**
|
||||
* {@code JPEGCodec} encodes a BufferedImage as a byte[] array.
|
||||
* <p>
|
||||
* Supported input formats:
|
||||
* <ul>
|
||||
* {@code VideoFormat} with {@code BufferedImage.class}, any width, any height,
|
||||
* any depth.
|
||||
* </ul>
|
||||
* Supported output formats:
|
||||
* <ul>
|
||||
* {@code VideoFormat} with {@code byte[].class}, same width and height as input
|
||||
* format, depth=24.
|
||||
* </ul>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: JPEGCodec.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class JPEGCodec extends AbstractVideoCodec {
|
||||
|
||||
public JPEGCodec() {
|
||||
super(new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME,
|
||||
EncodingKey, ENCODING_QUICKTIME_JPEG,//
|
||||
CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_JPEG, //
|
||||
DataClassKey, byte[].class, DepthKey, 24), //
|
||||
//
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_MJPG, DataClassKey, byte[].class, DepthKey, 24), //
|
||||
},
|
||||
new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE), //
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME,//
|
||||
EncodingKey, ENCODING_QUICKTIME_JPEG,//
|
||||
CompressorNameKey, COMPRESSOR_NAME_QUICKTIME_JPEG, //
|
||||
DataClassKey, byte[].class, DepthKey, 24), //
|
||||
//
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_MJPG, DataClassKey, byte[].class, DepthKey, 24), //
|
||||
}//
|
||||
);
|
||||
name = "JPEG Codec";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int process(Buffer in, Buffer out) {
|
||||
if (outputFormat.get(EncodingKey).equals(ENCODING_BUFFERED_IMAGE)) {
|
||||
return decode(in, out);
|
||||
} else {
|
||||
return encode(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
public int encode(Buffer in, Buffer out) {
|
||||
out.setMetaTo(in);
|
||||
out.format = outputFormat;
|
||||
if (in.isFlag(DISCARD)) {
|
||||
return CODEC_OK;
|
||||
}
|
||||
BufferedImage image = getBufferedImage(in);
|
||||
if (image == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
ByteArrayImageOutputStream tmp;
|
||||
if (out.data instanceof byte[]) {
|
||||
tmp = new ByteArrayImageOutputStream((byte[]) out.data);
|
||||
} else {
|
||||
tmp = new ByteArrayImageOutputStream();
|
||||
}
|
||||
|
||||
try {
|
||||
ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/jpeg").next();
|
||||
ImageWriteParam iwParam = iw.getDefaultWriteParam();
|
||||
iwParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
float quality = outputFormat.get(QualityKey, 1f);
|
||||
iwParam.setCompressionQuality(quality);
|
||||
iw.setOutput(tmp);
|
||||
IIOImage img = new IIOImage(image, null, null);
|
||||
iw.write(null, img, iwParam);
|
||||
iw.dispose();
|
||||
|
||||
out.sampleCount = 1;
|
||||
out.setFlag(KEYFRAME);
|
||||
out.data = tmp.getBuffer();
|
||||
out.offset = 0;
|
||||
out.length = (int) tmp.getStreamPosition();
|
||||
return CODEC_OK;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
public int decode(Buffer in, Buffer out) {
|
||||
out.setMetaTo(in);
|
||||
out.format = outputFormat;
|
||||
if (in.isFlag(DISCARD)) {
|
||||
return CODEC_OK;
|
||||
}
|
||||
byte[] data = (byte[]) in.data;
|
||||
if (data == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
ByteArrayImageInputStream tmp = new ByteArrayImageInputStream(data);
|
||||
|
||||
try {
|
||||
// ImageReader ir = (ImageReader) ImageIO.getImageReadersByMIMEType("image/jpeg").next();
|
||||
ImageReader ir = new MJPGImageReader(new MJPGImageReaderSpi());
|
||||
ir.setInput(tmp);
|
||||
out.data = ir.read(0);
|
||||
ir.dispose();
|
||||
|
||||
out.sampleCount = 1;
|
||||
out.offset = 0;
|
||||
out.length = (int) tmp.getStreamPosition();
|
||||
return CODEC_OK;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
libsrc/avi/src/org/monte/media/jpeg/MJPGImageReader.java
Normal file
119
libsrc/avi/src/org/monte/media/jpeg/MJPGImageReader.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* @(#)MJPGImageReader.java
|
||||
*
|
||||
* Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.jpeg;
|
||||
|
||||
import org.monte.media.avi.AVIBMPDIB;
|
||||
import com.sun.imageio.plugins.jpeg.JPEGImageReader;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
|
||||
/**
|
||||
* Reads an image in the Motion JPEG (MJPG) format.
|
||||
* <p>.
|
||||
* This class can read Motion JPEG files with omitted Huffmann table.
|
||||
* <p>
|
||||
* For more information see:
|
||||
* Microsoft Windows Bitmap Format.
|
||||
* Multimedia Technical Note: JPEG DIB Format.
|
||||
* (c) 1993 Microsoft Corporation. All rights reserved.
|
||||
* <a href="http://www.fileformat.info/format/bmp/spec/b7c72ebab8064da48ae5ed0c053c67a4/BMPDIB.TXT">BMPDIB.txt</a>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: MJPGImageReader.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class MJPGImageReader extends ImageReader {
|
||||
|
||||
private static DirectColorModel RGB = new DirectColorModel(24, 0xff0000, 0xff00, 0xff, 0x0);
|
||||
/** When we read the header, we read the whole image. */
|
||||
private BufferedImage image;
|
||||
|
||||
public MJPGImageReader(ImageReaderSpi originatingProvider) {
|
||||
super(originatingProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumImages(boolean allowSearch) throws IOException {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(int imageIndex) throws IOException {
|
||||
readHeader();
|
||||
return image.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(int imageIndex) throws IOException {
|
||||
readHeader();
|
||||
return image.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
readHeader();
|
||||
LinkedList<ImageTypeSpecifier> l = new LinkedList<ImageTypeSpecifier>();
|
||||
l.add(new ImageTypeSpecifier(RGB, RGB.createCompatibleSampleModel(image.getWidth(), image.getHeight())));
|
||||
return l.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getStreamMetadata() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||
if (imageIndex > 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
readHeader();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/** Reads the image header.
|
||||
* Does nothing if the header has already been loaded.
|
||||
*/
|
||||
private void readHeader() throws IOException {
|
||||
if (image == null) {
|
||||
ImageReader r = new JPEGImageReader(getOriginatingProvider());
|
||||
Object in = getInput();
|
||||
/*if (in instanceof Buffer) {
|
||||
Buffer buffer = (Buffer) in;
|
||||
in=buffer.getData();
|
||||
}*/
|
||||
if (in instanceof byte[]) {
|
||||
r.setInput(new MemoryCacheImageInputStream(AVIBMPDIB.prependDHTSeg((byte[]) in)));
|
||||
} else if (in instanceof ImageInputStream) {
|
||||
r.setInput(AVIBMPDIB.prependDHTSeg((ImageInputStream) in));
|
||||
} else {
|
||||
r.setInput(AVIBMPDIB.prependDHTSeg((InputStream) in));
|
||||
}
|
||||
image = r.read(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
libsrc/avi/src/org/monte/media/jpeg/MJPGImageReaderSpi.java
Normal file
88
libsrc/avi/src/org/monte/media/jpeg/MJPGImageReaderSpi.java
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* @(#)MJPGImageReaderSpi.java
|
||||
*
|
||||
* Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.jpeg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
|
||||
/**
|
||||
* ImageIO service provider interface for images in the Motion JPEG (MJPG)
|
||||
* format.
|
||||
* <p>
|
||||
* The reader described by this class can read Motion JPEG files with omitted
|
||||
* Huffmann table.
|
||||
* <p>
|
||||
* For more information see:
|
||||
* Microsoft Windows Bitmap Format.
|
||||
* Multimedia Technical Note: JPEG DIB Format.
|
||||
* (c) 1993 Microsoft Corporation. All rights reserved.
|
||||
* <a href="http://www.fileformat.info/format/bmp/spec/b7c72ebab8064da48ae5ed0c053c67a4/BMPDIB.TXT">BMPDIB.txt</a>
|
||||
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: MJPGImageReaderSpi.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class MJPGImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
public MJPGImageReaderSpi() {
|
||||
super("Werner Randelshofer",//vendor name
|
||||
"1.0",//version
|
||||
new String[]{"MJPG"},//names
|
||||
new String[]{"mjpg"},//suffixes,
|
||||
new String[]{"image/mjpg"},// MIMETypes,
|
||||
"org.monte.media.jmf.renderer.video.MJPGImageReader",// readerClassName,
|
||||
new Class[]{ImageInputStream.class,InputStream.class,byte[].class/*,javax.media.Buffer.class*/},// inputTypes,
|
||||
null,// writerSpiNames,
|
||||
false,// supportsStandardStreamMetadataFormat,
|
||||
null,// nativeStreamMetadataFormatName,
|
||||
null,// nativeStreamMetadataFormatClassName,
|
||||
null,// extraStreamMetadataFormatNames,
|
||||
null,// extraStreamMetadataFormatClassNames,
|
||||
false,// supportsStandardImageMetadataFormat,
|
||||
null,// nativeImageMetadataFormatName,
|
||||
null,// nativeImageMetadataFormatClassName,
|
||||
null,// extraImageMetadataFormatNames,
|
||||
null// extraImageMetadataFormatClassNames
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeInput(Object source) throws IOException {
|
||||
if (source instanceof ImageInputStream) {
|
||||
ImageInputStream in = (ImageInputStream) source;
|
||||
in.mark();
|
||||
|
||||
// Check if file starts with a JFIF SOI magic (0xffd8=-40)
|
||||
if (in.readShort() != -40) {
|
||||
in.reset();
|
||||
return false;
|
||||
}
|
||||
in.reset();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
return new MJPGImageReader(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale locale) {
|
||||
return "MJPG Image Reader";
|
||||
}
|
||||
}
|
||||
250
libsrc/avi/src/org/monte/media/math/IntMath.java
Normal file
250
libsrc/avi/src/org/monte/media/math/IntMath.java
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* @(#)IntMath.java
|
||||
*
|
||||
* Copyright (c) 2002-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package org.monte.media.math;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Utility class for integer arithmetic.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: IntMath.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class IntMath {
|
||||
|
||||
/** Creates a new instance of IntMath */
|
||||
public IntMath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an int whose value is the greatest common divisor of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 && b==0</tt>.
|
||||
*
|
||||
* @param a value with with the GCD is to be computed.
|
||||
* @param b value with with the GCD is to be computed.
|
||||
* @return <tt>GCD(a, b)</tt>
|
||||
*/
|
||||
public static int gcd(int a, int b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// ggt6, Seite 63
|
||||
|
||||
a = Math.abs(a);
|
||||
b = Math.abs(b);
|
||||
|
||||
while (a > 0 && b > 0) {
|
||||
a = a % b;
|
||||
if (a > 0) b = b % a;
|
||||
}
|
||||
return a + b;
|
||||
}
|
||||
/**
|
||||
* Returns a long whose value is the greatest common divisor of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 && b==0</tt>.
|
||||
*
|
||||
* @param a value with with the GCD is to be computed.
|
||||
* @param b value with with the GCD is to be computed.
|
||||
* @return <tt>GCD(a, b)</tt>
|
||||
*/
|
||||
public static long gcd(long a, long b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// ggt6, Seite 63
|
||||
|
||||
a = Math.abs(a);
|
||||
b = Math.abs(b);
|
||||
|
||||
while (a > 0 && b > 0) {
|
||||
a = a % b;
|
||||
if (a > 0) b = b % a;
|
||||
}
|
||||
return a + b;
|
||||
}
|
||||
/**
|
||||
* Returns a long whose value is the greatest common divisor of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 && b==0</tt>.
|
||||
*
|
||||
* @param a value with with the GCD is to be computed.
|
||||
* @param b value with with the GCD is to be computed.
|
||||
* @return <tt>GCD(a, b)</tt>
|
||||
*/
|
||||
public static BigInteger gcd(BigInteger a, BigInteger b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// ggt6, Seite 63
|
||||
|
||||
a = a.abs();
|
||||
b = b.abs();
|
||||
|
||||
while (a.compareTo(BigInteger.ZERO) > 0 && b.compareTo(BigInteger.ZERO) > 0) {
|
||||
a = a.mod(b);
|
||||
if (a.compareTo(BigInteger.ZERO) > 0) b = b.mod(a);
|
||||
}
|
||||
return a.add(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an int whose value is the smallest common multiple of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 || b==0</tt>.
|
||||
*
|
||||
* @param a value with with the SCM is to be computed.
|
||||
* @param b value with with the SCM is to be computed.
|
||||
* @return <tt>SCM(a, b)</tt>
|
||||
*/
|
||||
public static int scm(int a, int b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// gill, Seite 141
|
||||
|
||||
if (a == 0 || b == 0) return 0;
|
||||
|
||||
a = Math.abs(a);
|
||||
b = Math.abs(b);
|
||||
|
||||
int u = a;
|
||||
int v = b;
|
||||
|
||||
while (a != b) {
|
||||
if (a < b) {
|
||||
b -= a;
|
||||
v += u;
|
||||
} else {
|
||||
a -= b;
|
||||
u += v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//return a; // gcd
|
||||
return (u + v) / 2; // scm
|
||||
}
|
||||
/**
|
||||
* Returns an int whose value is the smallest common multiple of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 || b==0</tt>.
|
||||
*
|
||||
* @param a value with with the SCM is to be computed.
|
||||
* @param b value with with the SCM is to be computed.
|
||||
* @return <tt>SCM(a, b)</tt>
|
||||
*/
|
||||
public static long scm(long a, long b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// gill, Seite 141
|
||||
|
||||
if (a == 0 || b == 0) return 0;
|
||||
|
||||
a = Math.abs(a);
|
||||
b = Math.abs(b);
|
||||
if (b==1)return a;
|
||||
if (a==1)return b;
|
||||
|
||||
long u = a;
|
||||
long v = b;
|
||||
|
||||
// FIXME - Handle overflow
|
||||
while (a != b) {
|
||||
if (a < b) {
|
||||
b -= a;
|
||||
v += u;
|
||||
} else {
|
||||
a -= b;
|
||||
u += v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//return a; // gcd
|
||||
return (u + v) / 2; // scm
|
||||
}
|
||||
/**
|
||||
* Returns an int whose value is the smallest common multiple of
|
||||
* <tt>abs(a)</tt> and <tt>abs(b)</tt>. Returns 0 if
|
||||
* <tt>a==0 || b==0</tt>.
|
||||
*
|
||||
* @param a value with with the SCM is to be computed.
|
||||
* @param b value with with the SCM is to be computed.
|
||||
* @return <tt>SCM(a, b)</tt>
|
||||
*/
|
||||
public static BigInteger scm(BigInteger a, BigInteger b) {
|
||||
// Quelle:
|
||||
// Herrmann, D. (1992). Algorithmen Arbeitsbuch.
|
||||
// Bonn, München Paris: Addison Wesley.
|
||||
// gill, Seite 141
|
||||
|
||||
if (a.compareTo(BigInteger.ZERO) == 0 || b.compareTo(BigInteger.ZERO) == 0) {
|
||||
return BigInteger.ZERO;
|
||||
}
|
||||
|
||||
a = a.abs();
|
||||
b = b.abs();
|
||||
if (b.compareTo(BigInteger.ONE)==0)return a;
|
||||
if (a.compareTo(BigInteger.ONE)==0)return b;
|
||||
|
||||
BigInteger u = a;
|
||||
BigInteger v = b;
|
||||
|
||||
// FIXME - Handle overflow
|
||||
while (a.compareTo(b) != 0) {
|
||||
if (a .compareTo( b)<0) {
|
||||
b = b.subtract(a);
|
||||
v = v.add(u);
|
||||
} else {
|
||||
a = a.subtract(b);
|
||||
u = u.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//return a; // gcd
|
||||
return (u.add(v)).divide(BigInteger.valueOf(2)); // scm
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses all 32 bits of the provided integer value.
|
||||
*/
|
||||
public static int reverseBits(int a) {
|
||||
return reverseBits(a, 32);
|
||||
}
|
||||
/**
|
||||
* Reverses specified number of bits of the provided integer value.
|
||||
* @param a The number.
|
||||
* @param numBits The number of bits (must be between 1 and 32).
|
||||
*/
|
||||
public static int reverseBits(int a, int numBits) {
|
||||
int b = 0;
|
||||
for (int i=0; i < numBits; i++) {
|
||||
b <<= 1;
|
||||
b |= (a & 1);
|
||||
a >>>= 1;
|
||||
}
|
||||
return b;
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i=0; i < 8; i++) {
|
||||
int a = 1<<i;
|
||||
int b = reverseBits(a, 3);
|
||||
System.out.println(a+" - "+b);
|
||||
}
|
||||
}
|
||||
}
|
||||
492
libsrc/avi/src/org/monte/media/math/Rational.java
Normal file
492
libsrc/avi/src/org/monte/media/math/Rational.java
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* @(#)Rational.java
|
||||
*
|
||||
* Copyright (c) 2009-2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.math;
|
||||
|
||||
import static java.lang.Math.*;
|
||||
import java.math.BigInteger;
|
||||
import static org.monte.media.math.IntMath.*;
|
||||
|
||||
/**
|
||||
* Represents a TIFF RATIONAL number. <p> Two LONGs 32-bit (4-byte) unsigned
|
||||
* integer: the first represents the numerator of a fraction; the second, the
|
||||
* denominator. </p> <p> Invariants: </p> <ul> <li>denominator>=0, the
|
||||
* denominator is always a positive integer</li> <li>0/1 is the unique
|
||||
* representation of 0.</li> <li>1/0,-1/0 are the unique representations of
|
||||
* infinity.</li> </ul>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: Rational.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class Rational extends Number {
|
||||
|
||||
public static final Rational ONE = new Rational(1, 1,false);
|
||||
public static final Rational ZERO = new Rational(0, 1,false);
|
||||
public static final long serialVersionUID = 1L;
|
||||
private final long num;
|
||||
private final long den;
|
||||
|
||||
public Rational(long numerator) {
|
||||
this(numerator, 1);
|
||||
}
|
||||
|
||||
public Rational(long numerator, long denominator) {
|
||||
this(numerator, denominator, true);
|
||||
}
|
||||
|
||||
private Rational(long numerator, long denominator, boolean reduceFraction) {
|
||||
if (numerator == 0) {
|
||||
// Invariant: 0/1 is unique representation of 0
|
||||
denominator = 1;
|
||||
}
|
||||
|
||||
if (denominator == 0) {
|
||||
// Invariant: 1/0, -1/0 are unique representations of infinity
|
||||
numerator = (numerator > 0) ? 1 : -1;
|
||||
} else if (denominator < 0) {
|
||||
// Invariant: denominator is always positive
|
||||
denominator = -denominator;
|
||||
numerator = -numerator;
|
||||
}
|
||||
|
||||
if (reduceFraction) {
|
||||
long g = gcd(numerator, denominator);
|
||||
num = numerator / g;
|
||||
den = denominator / g;
|
||||
} else {
|
||||
num = numerator;
|
||||
den = denominator;
|
||||
}
|
||||
}
|
||||
|
||||
private Rational(BigInteger numerator, BigInteger denominator, boolean reduceFraction) {
|
||||
if (numerator.equals(BigInteger.ZERO)) {
|
||||
// Invariant: 0/1 is unique representation of 0
|
||||
denominator = BigInteger.ONE;
|
||||
}
|
||||
|
||||
if (denominator.equals(BigInteger.ZERO)) {
|
||||
// Invariant: 1/0, -1/0 are unique representations of infinity
|
||||
numerator = (numerator.compareTo(BigInteger.ZERO) > 0) ? BigInteger.ONE : BigInteger.ONE.negate();
|
||||
} else if (denominator.compareTo(BigInteger.ZERO) < 0) {
|
||||
// Invariant: denominator is always positive
|
||||
denominator = denominator.negate();
|
||||
numerator = numerator.negate();
|
||||
}
|
||||
|
||||
BigInteger numB, denB;
|
||||
if (reduceFraction) {
|
||||
BigInteger g = gcd(numerator, denominator);
|
||||
numB = numerator.divide(g);
|
||||
denB = denominator.divide(g);
|
||||
} else {
|
||||
numB = numerator;
|
||||
denB = denominator;
|
||||
}
|
||||
int bitLength = Math.max(numB.bitLength(), denB.bitLength());
|
||||
if (bitLength > 63) {
|
||||
numB = numB.shiftRight(bitLength - 63);
|
||||
denB = denB.shiftRight(bitLength - 63);
|
||||
if (numB.equals(BigInteger.ZERO)) {
|
||||
// Invariant: 0/1 is unique representation of 0
|
||||
denB = BigInteger.ONE;
|
||||
}
|
||||
|
||||
if (denB.equals(BigInteger.ZERO)) {
|
||||
// Invariant: 1/0, -1/0 are unique representations of infinity
|
||||
numB = (numB.compareTo(BigInteger.ZERO) > 0) ? BigInteger.ONE : BigInteger.ONE.negate();
|
||||
|
||||
}
|
||||
}
|
||||
num = numB.longValue();
|
||||
den = denB.longValue();
|
||||
}
|
||||
|
||||
public Rational(Rational r) {
|
||||
this(r.num, r.den);
|
||||
}
|
||||
|
||||
public long getNumerator() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public long getDenominator() {
|
||||
return den;
|
||||
}
|
||||
|
||||
public Rational add(Rational that) {
|
||||
return add(that, true);
|
||||
}
|
||||
|
||||
private Rational add(Rational that, boolean reduceFraction) {
|
||||
if (this.den == that.den) {
|
||||
// => same denominator: add numerators
|
||||
return new Rational(this.num + that.num, this.den, reduceFraction);
|
||||
}
|
||||
|
||||
// FIXME - handle overflow
|
||||
long s = scm(this.den, that.den);
|
||||
Rational result = new Rational(
|
||||
this.num * (s / this.den) + that.num * (s / that.den),
|
||||
s, reduceFraction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning. Rational is supposed to be immutable. *
|
||||
*
|
||||
* private Rational addAssign(Rational that) { if (this.den == that.den) {
|
||||
* // => same denominator: add numerators this.num += that.num; return this;
|
||||
* }
|
||||
*
|
||||
* // FIXME - handle overflow long s = scm(this.den, that.den); this.num =
|
||||
* this.num * (s / this.den) + that.num * (s / that.den); this.den = s;
|
||||
*
|
||||
*
|
||||
* return reduceAssign(); }
|
||||
*/
|
||||
public Rational subtract(Rational that) {
|
||||
return add(that.negate());
|
||||
}
|
||||
|
||||
public Rational negate() {
|
||||
return valueOf(-num, den);
|
||||
}
|
||||
|
||||
public Rational inverse() {
|
||||
return valueOf(den, num, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest rational with the specified denominator which is
|
||||
* smaller or equal than this number.
|
||||
*/
|
||||
public Rational floor(long d) {
|
||||
if (d == den) {
|
||||
return valueOf(num, den);
|
||||
}
|
||||
long s = scm(this.den, d);
|
||||
|
||||
if (s == d) {
|
||||
return valueOf(num * s / den, d);
|
||||
} else if (s == den) {
|
||||
return valueOf(num * d / den, d);
|
||||
} else {
|
||||
return valueOf(num * d / den, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest rational with the specified denominator which is
|
||||
* greater or equal than this number.
|
||||
*/
|
||||
public Rational ceil(long d) {
|
||||
if (d == den) {
|
||||
return valueOf(num, den);
|
||||
}
|
||||
long s = scm(this.den, d);
|
||||
|
||||
if (s == d) {
|
||||
return valueOf((num * s + den - 1) / den, d);
|
||||
} else if (s == den) {
|
||||
return valueOf((num * d + den - 1) / den, d);
|
||||
} else {
|
||||
return valueOf((num * d + den - 1) / den, d);
|
||||
}
|
||||
}
|
||||
|
||||
public Rational multiply(Rational that) {
|
||||
if (abs(this.num) < Integer.MAX_VALUE
|
||||
&& abs(this.den) < Integer.MAX_VALUE
|
||||
&& abs(that.num) < Integer.MAX_VALUE
|
||||
&& abs(that.den) < Integer.MAX_VALUE) {
|
||||
return valueOf(this.num * that.num,
|
||||
this.den * that.den);
|
||||
} else {
|
||||
return new Rational(
|
||||
BigInteger.valueOf(this.num).multiply(BigInteger.valueOf(that.num)),
|
||||
BigInteger.valueOf(this.den).multiply(BigInteger.valueOf(that.den)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
public Rational multiply(long integer) {
|
||||
if (integer==0) {
|
||||
return ZERO;
|
||||
} else if (this.den % integer == 0) {
|
||||
return valueOf(
|
||||
this.num,
|
||||
this.den / integer);
|
||||
} else if (abs(this.num) < Integer.MAX_VALUE
|
||||
&& abs(integer) < Integer.MAX_VALUE) {
|
||||
return valueOf(
|
||||
this.num * integer,
|
||||
this.den);
|
||||
} else {
|
||||
return new Rational(
|
||||
BigInteger.valueOf(this.num).multiply(BigInteger.valueOf(integer)),
|
||||
BigInteger.valueOf(this.den), true);
|
||||
}
|
||||
}
|
||||
|
||||
public Rational divide(Rational that) {
|
||||
if (abs(this.num) < Integer.MAX_VALUE
|
||||
&& abs(this.den) < Integer.MAX_VALUE
|
||||
&& abs(that.num) < Integer.MAX_VALUE
|
||||
&& abs(that.den) < Integer.MAX_VALUE) {
|
||||
return valueOf(this.num * that.den,
|
||||
this.den * that.num);
|
||||
} else {
|
||||
return valueOf(
|
||||
BigInteger.valueOf(this.num).multiply(BigInteger.valueOf(that.den)),
|
||||
BigInteger.valueOf(this.den).multiply(BigInteger.valueOf(that.num)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
//long gcd = IntMath.gcd(num, den);
|
||||
if (num == 0) {
|
||||
return "0";
|
||||
} else if (den == 1) {
|
||||
return Long.toString(num);
|
||||
} else {
|
||||
return num + "/" + den;
|
||||
/*
|
||||
} else {
|
||||
return Float.toString((float) num / den);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public String toDescriptiveString() {
|
||||
long gcd = IntMath.gcd(num, den);
|
||||
if (gcd == 0 || num == 0) {
|
||||
return num + "/" + den + " = " + 0;
|
||||
} else if (gcd == den) {
|
||||
return num + "/" + den + " = " + Long.toString(num / den);
|
||||
} else {
|
||||
return num + "/" + den + " ≈ " + ((float) num / den);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) (num / den);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return num / den;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) num / (float) den;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) num / (double) den;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Rational that = (Rational) obj;
|
||||
|
||||
return compareTo(that) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* return { -1, 0, +1 } if a < b, a = b, or a > b.
|
||||
*/
|
||||
public int compareTo(Rational that) {
|
||||
// The following code avoids BigInteger allocation if the denominators
|
||||
// are equal
|
||||
if (this.den == that.den) {
|
||||
if (this.num < that.num) {
|
||||
return -1;
|
||||
} else if (this.num > that.num) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Work with longs if overflow can not occur
|
||||
if (abs(this.num) < Integer.MAX_VALUE
|
||||
&& abs(this.den) < Integer.MAX_VALUE
|
||||
&& abs(that.num) < Integer.MAX_VALUE
|
||||
&& abs(that.den) < Integer.MAX_VALUE) {
|
||||
long lhs = this.num * that.den;
|
||||
long rhs = this.den * that.num;
|
||||
if (lhs < rhs) {
|
||||
return -1;
|
||||
} else if (lhs > rhs) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Use big integers to avoid overflows
|
||||
BigInteger lhs;
|
||||
BigInteger rhs;
|
||||
lhs = BigInteger.valueOf(this.num).multiply(BigInteger.valueOf(that.den));
|
||||
rhs = BigInteger.valueOf(this.den).multiply(BigInteger.valueOf(that.num));
|
||||
|
||||
return lhs.compareTo(rhs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) ((num ^ (num >>> 32))
|
||||
^ (den ^ (den >>> 32)));
|
||||
|
||||
}
|
||||
|
||||
public static Rational max(Rational a, Rational b) {
|
||||
return (a.compareTo(b) >= 0) ? a : b;
|
||||
}
|
||||
|
||||
public static Rational min(Rational a, Rational b) {
|
||||
return (a.compareTo(b) <= 0) ? a : b;
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return num == 0;
|
||||
}
|
||||
|
||||
public boolean isLessOrEqualZero() {
|
||||
return num <= 0;
|
||||
}
|
||||
|
||||
public static Rational valueOf(double d) {
|
||||
if (d == 0) {
|
||||
return valueOf(0, 1);
|
||||
}
|
||||
if (abs(d) > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Value " + d + " is too big.");
|
||||
}
|
||||
if (Double.isInfinite(d)) {
|
||||
return valueOf((long) signum(d), 0);
|
||||
}
|
||||
if (Double.isNaN(d)) {
|
||||
return valueOf(0, 1); // no way to express a NaN :-(
|
||||
}
|
||||
return toRational(d, Integer.MAX_VALUE, 100);
|
||||
}
|
||||
|
||||
public static Rational valueOf(long num, long den) {
|
||||
return valueOf(num, den, true);
|
||||
}
|
||||
|
||||
private static Rational valueOf(long num, long den, boolean reduceFraction) {
|
||||
if (num == den) {
|
||||
return ONE;
|
||||
}
|
||||
if (num == 0) {
|
||||
return ZERO;
|
||||
}
|
||||
return new Rational(num, den, reduceFraction);
|
||||
}
|
||||
|
||||
public static Rational valueOf(BigInteger num, BigInteger den) {
|
||||
return valueOf(num, den, true);
|
||||
}
|
||||
|
||||
private static Rational valueOf(BigInteger num, BigInteger den, boolean reduceFraction) {
|
||||
if (num.equals(den)) {
|
||||
return ONE;
|
||||
}
|
||||
if (num.equals(BigInteger.ZERO)) {
|
||||
return ZERO;
|
||||
}
|
||||
return new Rational(num, den, reduceFraction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iteratively computes rational from double. <p>Reference:<br> <a
|
||||
* href="http://www2.fz-juelich.de/video/cpp/html/exercises/exercise/Rational_cpp.html">
|
||||
* http://www2.fz-juelich.de/video/cpp/html/exercises/exercise/Rational_cpp.html</a>
|
||||
* </p>
|
||||
*/
|
||||
private static Rational toRational(double x, double limit, int iterations) {
|
||||
double intpart = Math.floor(x);
|
||||
double fractpart = x - intpart;
|
||||
double d = 1.0 / fractpart;
|
||||
long left = (long) intpart;
|
||||
if (d > limit || iterations == 0) {
|
||||
return valueOf(left, 1, false);
|
||||
} else {
|
||||
return valueOf(left, 1, false).add(toRational(d, limit * 0.1, iterations - 1).inverse(), false);
|
||||
}
|
||||
}
|
||||
|
||||
public Rational round(long d) {
|
||||
if (d == den) {
|
||||
return valueOf(num, den);
|
||||
}
|
||||
|
||||
Rational fl = floor(d);
|
||||
Rational diffFl = subtract(fl);
|
||||
|
||||
if (diffFl.isZero()) {
|
||||
return fl;
|
||||
}
|
||||
|
||||
Rational cl = ceil(d);
|
||||
Rational diffCl = subtract(cl);
|
||||
if (diffCl.isZero()) {
|
||||
return cl;
|
||||
}
|
||||
|
||||
if (diffFl.isNegative()) {
|
||||
diffFl = diffFl.negate();
|
||||
}
|
||||
if (diffCl.isNegative()) {
|
||||
diffCl = diffCl.negate();
|
||||
}
|
||||
return diffFl.compareTo(diffCl) <= 0 ? fl : cl;
|
||||
}
|
||||
|
||||
private boolean isNegative() {
|
||||
return num < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string.
|
||||
*
|
||||
* A rational can be represented in the following ways: <li>As a long
|
||||
* number</li> <li>As a double number</li> <li>As an integer/integer
|
||||
* rational number</li>
|
||||
*
|
||||
* @throws NumberFormatException if str can not be parsed.
|
||||
*/
|
||||
public static Rational valueOf(String str) {
|
||||
int p = str.indexOf('/');
|
||||
if (p != -1) {
|
||||
return valueOf(Long.valueOf(str.substring(0, p)), Long.valueOf(str.substring(p + 1)));
|
||||
}
|
||||
try {
|
||||
return valueOf(Long.valueOf(str));
|
||||
} catch (NumberFormatException e) {
|
||||
return valueOf(Double.valueOf(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
118
libsrc/avi/src/org/monte/media/png/PNGCodec.java
Normal file
118
libsrc/avi/src/org/monte/media/png/PNGCodec.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* @(#)PNGCodec.java
|
||||
*
|
||||
* Copyright (c) 2011-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.png;
|
||||
|
||||
import org.monte.media.Format;
|
||||
import org.monte.media.AbstractVideoCodec;
|
||||
import org.monte.media.Buffer;
|
||||
import org.monte.media.io.ByteArrayImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import static org.monte.media.VideoFormatKeys.*;
|
||||
import static org.monte.media.BufferFlag.*;
|
||||
|
||||
/**
|
||||
* {@code PNGCodec} encodes a BufferedImage as a byte[] array..
|
||||
* <p>
|
||||
* Supported input formats:
|
||||
* <ul>
|
||||
* {@code VideoFormat} with {@code BufferedImage.class}, any width, any height,
|
||||
* any depth.
|
||||
* </ul>
|
||||
* Supported output formats:
|
||||
* <ul>
|
||||
* {@code VideoFormat} with {@code byte[].class}, same width and height as input
|
||||
* format, depth=24.
|
||||
* </ul>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: PNGCodec.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class PNGCodec extends AbstractVideoCodec {
|
||||
|
||||
public PNGCodec() {
|
||||
super(new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
|
||||
EncodingKey, ENCODING_BUFFERED_IMAGE), //
|
||||
},
|
||||
new Format[]{
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME,
|
||||
DepthKey, 24,
|
||||
EncodingKey, ENCODING_QUICKTIME_PNG, DataClassKey, byte[].class), //
|
||||
//
|
||||
new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
DepthKey, 24,
|
||||
EncodingKey, ENCODING_AVI_PNG, DataClassKey, byte[].class), //
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format setOutputFormat(Format f) {
|
||||
String mimeType = f.get(MimeTypeKey, MIME_QUICKTIME);
|
||||
if (mimeType != null && !mimeType.equals(MIME_AVI)) {
|
||||
return super.setOutputFormat(
|
||||
f.prepend(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_QUICKTIME,
|
||||
EncodingKey, ENCODING_QUICKTIME_PNG, DataClassKey,
|
||||
byte[].class, DepthKey, 24));
|
||||
} else {
|
||||
return super.setOutputFormat(
|
||||
f.prepend(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
|
||||
EncodingKey, ENCODING_AVI_PNG, DataClassKey,
|
||||
byte[].class, DepthKey, 24));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int process(Buffer in, Buffer out) {
|
||||
out.setMetaTo(in);
|
||||
out.format = outputFormat;
|
||||
if (in.isFlag(DISCARD)) {
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
BufferedImage image = getBufferedImage(in);
|
||||
if (image == null) {
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
|
||||
ByteArrayImageOutputStream tmp;
|
||||
if (out.data instanceof byte[]) {
|
||||
tmp = new ByteArrayImageOutputStream((byte[]) out.data);
|
||||
} else {
|
||||
tmp = new ByteArrayImageOutputStream();
|
||||
}
|
||||
|
||||
try {
|
||||
ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/png").next();
|
||||
ImageWriteParam iwParam = iw.getDefaultWriteParam();
|
||||
iw.setOutput(tmp);
|
||||
IIOImage img = new IIOImage(image, null, null);
|
||||
iw.write(null, img, iwParam);
|
||||
iw.dispose();
|
||||
|
||||
out.setFlag(KEYFRAME);
|
||||
out.header = null;
|
||||
out.data = tmp.getBuffer();
|
||||
out.offset = 0;
|
||||
out.length = (int) tmp.getStreamPosition();
|
||||
return CODEC_OK;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
out.setFlag(DISCARD);
|
||||
return CODEC_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
libsrc/avi/src/org/monte/media/riff/RIFFChunk.java
Normal file
188
libsrc/avi/src/org/monte/media/riff/RIFFChunk.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* @(#)RIFFChunk.java
|
||||
*
|
||||
* Copyright (c) 2005-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.riff;
|
||||
|
||||
import java.util.*;
|
||||
/**
|
||||
* RIFF Chunks form the building blocks of a RIFF file.
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
|
||||
* @version $Id: RIFFChunk.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
public class RIFFChunk {
|
||||
private int id;
|
||||
private int type;
|
||||
private long size;
|
||||
private long scan;
|
||||
private byte[] data;
|
||||
private Hashtable<RIFFChunk,RIFFChunk> propertyChunks;
|
||||
private ArrayList<RIFFChunk> collectionChunks;
|
||||
/**
|
||||
* This is used to display parser messages, when the parser encounters and
|
||||
* error while parsing the chunk.
|
||||
*/
|
||||
private String parserMessage;
|
||||
|
||||
public RIFFChunk(int type, int id) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
size = -1;
|
||||
scan = -1;
|
||||
}
|
||||
public RIFFChunk(int type, int id, long size, long scan) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
this.scan = scan;
|
||||
}
|
||||
public RIFFChunk(int type, int id, long size, long scan, RIFFChunk propGroup) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
this.scan = scan;
|
||||
if (propGroup != null) {
|
||||
if (propGroup.propertyChunks != null) {
|
||||
propertyChunks = new Hashtable<RIFFChunk,RIFFChunk>(propGroup.propertyChunks);
|
||||
}
|
||||
if (propGroup.collectionChunks != null) {
|
||||
collectionChunks = new ArrayList<RIFFChunk>(propGroup.collectionChunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ID of chunk.
|
||||
*/
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type of chunk.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Size of chunk.
|
||||
*/
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Scan position of chunk within the file.
|
||||
*/
|
||||
public long getScan() {
|
||||
return scan;
|
||||
}
|
||||
|
||||
public void putPropertyChunk(RIFFChunk chunk) {
|
||||
if (propertyChunks == null) {
|
||||
propertyChunks = new Hashtable<RIFFChunk,RIFFChunk> ();
|
||||
}
|
||||
propertyChunks.put(chunk,chunk);
|
||||
}
|
||||
|
||||
public RIFFChunk getPropertyChunk(int id) {
|
||||
if (propertyChunks == null) {
|
||||
return null;
|
||||
}
|
||||
RIFFChunk chunk = new RIFFChunk(type, id);
|
||||
return propertyChunks.get(chunk);
|
||||
}
|
||||
|
||||
public Enumeration propertyChunks() {
|
||||
if (propertyChunks == null) {
|
||||
propertyChunks = new Hashtable<RIFFChunk,RIFFChunk> ();
|
||||
}
|
||||
return propertyChunks.keys();
|
||||
}
|
||||
|
||||
public void addCollectionChunk(RIFFChunk chunk) {
|
||||
if (collectionChunks == null) {
|
||||
collectionChunks = new ArrayList<RIFFChunk>();
|
||||
}
|
||||
collectionChunks.add(chunk);
|
||||
}
|
||||
|
||||
public RIFFChunk[] getCollectionChunks(int id) {
|
||||
if (collectionChunks == null) {
|
||||
return new RIFFChunk[0];
|
||||
}
|
||||
Iterator<RIFFChunk> enm = collectionChunks.iterator();
|
||||
int i = 0;
|
||||
while ( enm.hasNext() ) {
|
||||
RIFFChunk chunk = enm.next();
|
||||
if (chunk.id==id) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
RIFFChunk[] array = new RIFFChunk[i];
|
||||
i = 0;
|
||||
enm = collectionChunks.iterator();
|
||||
while ( enm.hasNext() ) {
|
||||
RIFFChunk chunk = enm.next();
|
||||
if (chunk.id==id) {
|
||||
array[i++] = chunk;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
public Iterator collectionChunks() {
|
||||
if (collectionChunks == null) {
|
||||
collectionChunks = new ArrayList<RIFFChunk>();
|
||||
}
|
||||
return collectionChunks.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data.
|
||||
* Note: The array will not be cloned.
|
||||
*/
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
/**
|
||||
* Gets the data.
|
||||
* Note: The array will not be cloned.
|
||||
*/
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object another) {
|
||||
if (another instanceof RIFFChunk) {
|
||||
RIFFChunk that = (RIFFChunk) another;
|
||||
return (that.id==this.id) && (that.type==this.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setParserMessage(String newValue) {
|
||||
this.parserMessage = newValue;
|
||||
}
|
||||
public String getParserMessage() {
|
||||
return this.parserMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+"{"+RIFFParser.idToString(getType())+","+RIFFParser.idToString(getID())+"}";
|
||||
}
|
||||
}
|
||||
841
libsrc/avi/src/org/monte/media/riff/RIFFParser.java
Normal file
841
libsrc/avi/src/org/monte/media/riff/RIFFParser.java
Normal file
@@ -0,0 +1,841 @@
|
||||
/*
|
||||
* @(#)RIFFParser.java 1.5 2012-08-03
|
||||
*
|
||||
* Copyright (c) 2005-2012 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.riff;
|
||||
|
||||
import org.monte.media.AbortException;
|
||||
import org.monte.media.ParseException;
|
||||
import org.monte.media.io.ImageInputStreamAdapter;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.WeakHashMap;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
/**
|
||||
* Interprets Resource Interchange File Format (RIFF) streams.
|
||||
*
|
||||
* <p><b>Abstract</b>
|
||||
* <p>
|
||||
* <b>RIFF File Format</b>
|
||||
* A RIFF file consists of a RIFF header followed by zero or more lists and chunks.
|
||||
* <p>
|
||||
* The RIFF header has the following form:
|
||||
* <pre>
|
||||
* 'RIFF' fileSize fileType (data)
|
||||
* </pre>
|
||||
* where 'RIFF' is the literal FOURCC code 'RIFF', fileSize is a 4-byte value
|
||||
* giving the size of the data in the file, and fileType is a FOURCC that
|
||||
* identifies the specific file type. The value of fileSize includes the size
|
||||
* of the fileType FOURCC plus the size of the data that follows, but does not
|
||||
* include the size of the 'RIFF' FOURCC or the size of fileSize. The file data
|
||||
* consists of chunks and lists, in any order.
|
||||
* <p>
|
||||
* <b>FOURCCs</b><br>
|
||||
* A FOURCC (four-character code) is a 32-bit unsigned integer created by
|
||||
* concatenating four ASCII characters. For example, the FOURCC 'abcd' is
|
||||
* represented on a Little-Endian system as 0x64636261. FOURCCs can contain
|
||||
* space characters, so ' abc' is a valid FOURCC. The RIFF file format uses
|
||||
* FOURCC codes to identify stream types, data chunks, index entries, and other
|
||||
* information.
|
||||
* <p>
|
||||
* A chunk has the following form:
|
||||
* <pre>
|
||||
* ckID ckSize ckData
|
||||
* </pre>
|
||||
* where ckID is a FOURCC that identifies the data contained in the chunk,
|
||||
* ckData is a 4-byte value giving the size of the data in ckData, and ckData is
|
||||
* zero or more bytes of data. The data is always padded to nearest WORD boundary.
|
||||
* ckSize gives the size of the valid data in the chunk; it does not include
|
||||
* the padding, the size of ckID, or the size of ckSize.
|
||||
* <p>
|
||||
* A list has the following form:
|
||||
* <pre>
|
||||
* 'LIST' listSize listType listData
|
||||
* </pre>
|
||||
* where 'LIST' is the literal FOURCC code 'LIST', listSize is a 4-byte value
|
||||
* giving the size of the list, listType is a FOURCC code, and listData consists
|
||||
* of chunks or lists, in any order. The value of listSize includes the size of
|
||||
* listType plus the size of listData; it does not include the 'LIST' FOURCC or
|
||||
* the size of listSize.
|
||||
* <p>
|
||||
* For more information see:
|
||||
* http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c/directx/htm/avirifffilereference.asp
|
||||
* http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c/directx/htm/aboutriff.asp
|
||||
* <p>
|
||||
* <p><b>Grammar for RIFF streams used by this parser</b>
|
||||
* <pre>
|
||||
* RIFFFile ::= 'RIFF' FormGroup
|
||||
* <br>
|
||||
* GroupChunk ::= FormGroup | ListGroup
|
||||
* FormGroup ::= size GroupType [ ChunkID LocalChunk [pad] | 'LIST ' ListGroup [pad] }
|
||||
* ListGroup ::= size GroupType [ ChunkID LocalChunk [pad] | 'LIST ' ListGroup [pad] }
|
||||
* <br>
|
||||
* LocalChunk ::= DataChunk | CollectionChunk | PropertyChunk
|
||||
* DataChunk ::= size [ struct ]
|
||||
* CollectionChunk ::= size [ struct ]
|
||||
* PropertyChunk ::= size [ struct ]
|
||||
* <br>
|
||||
* size ::= ULONG
|
||||
* GroupType ::= FourCC
|
||||
* ChunkID ::= FourCC
|
||||
* pad ::= (BYTE)0
|
||||
* struct ::= any C language struct built with primitive data types.
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>Examples</b>
|
||||
*
|
||||
* <p><b>Traversing the raw structure of a RIFF file</b>
|
||||
* <p>To traverse the file structure you must first set up a RIFFVisitor object
|
||||
* that does something useful at each call to the visit method. Then create an
|
||||
* instance of a RIFFParser and invoke the #interpret method.
|
||||
*
|
||||
* <pre>
|
||||
* class RIFFRawTraversal
|
||||
* . {
|
||||
* . static class Visitor
|
||||
* . implements RIFFVisitor
|
||||
* . {
|
||||
* . ...implement the visitor interface here...
|
||||
* . }
|
||||
* .
|
||||
* . public static void main(String[] args)
|
||||
* . {
|
||||
* . try {
|
||||
* . Visitor visitor = new Visitor();
|
||||
* . FileInputStream stream = new FileInputStream(args[0]);
|
||||
* . RIFFParser p = new RIFFParser();
|
||||
* . p.interpret(stream,visitor);
|
||||
* . stream.close();
|
||||
* . }
|
||||
* . catch (IOException e) { System.out.println(e); }
|
||||
* . catch (InterpreterException e) { System.out.println(e); }
|
||||
* . catch (AbortedException e) { System.out.println(e); }
|
||||
* . }
|
||||
* . }
|
||||
* </pre>
|
||||
*
|
||||
* <p><b>Traversing the RIFF file and interpreting its content.</b>
|
||||
* <p>Since RIFF files are not completely self describing (there is no information
|
||||
* that helps differentiate between data chunks, property chunks and collection
|
||||
* chunks) a reader must set up the interpreter with some contextual information
|
||||
* before starting the interpreter.
|
||||
* <p>
|
||||
* Once at least one chunk has been declared, the interpreter will only call the
|
||||
* visitor for occurences of the declared group chunks and data chunks. The property
|
||||
* chunks and the collection chunks can be obtained from the current group chunk
|
||||
* by calling #getProperty or #getCollection.
|
||||
* <br>Note: All information the visitor can obtain during interpretation is only
|
||||
* valid during the actual #visit... call. Dont try to get information about properties
|
||||
* or collections for chunks that the visitor is not visiting right now.
|
||||
*
|
||||
* <pre>
|
||||
* class InterpretingAnILBMFile
|
||||
* . {
|
||||
* . static class Visitor
|
||||
* . implements RIFFVisitor
|
||||
* . {
|
||||
* . ...
|
||||
* . }
|
||||
* .
|
||||
* . public static void main(String[] args)
|
||||
* . {
|
||||
* . try {
|
||||
* . Visitor visitor = new Visitor();
|
||||
* . FileInputStream stream = new FileInputStream(args[0]);
|
||||
* . RIFFParser p = new RIFFParser();
|
||||
* . p.declareGroupChunk('FORM','ILBM');
|
||||
* . p.declarePropertyChunk('ILBM','BMHD');
|
||||
* . p.declarePropertyChunk('ILBM','CMAP');
|
||||
* . p.declareCollectionChunk('ILBM','CRNG');
|
||||
* . p.declareDataChunk('ILBM','BODY');
|
||||
* . p.interpret(stream,visitor);
|
||||
* . stream.close();
|
||||
* . }
|
||||
* . catch (IOException e) { System.out.println(e); }
|
||||
* . catch (InterpreterException e) { System.out.println(e); }
|
||||
* . catch (AbortedException e) { System.out.println(e); }
|
||||
* . }
|
||||
* . }
|
||||
* </pre>
|
||||
*
|
||||
* @see RIFFVisitor
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
|
||||
* @version 1.5 2012-08-03 Adds offset property.
|
||||
* <br>1.4 2011-08-26 Adds HashMap for stop chunks.
|
||||
* <br>1.3 2011-01-23 Use HashMap instead of Hashtable.
|
||||
* <br>1.2 2010-07-06 Use integer IDs for efficiency. Added support for
|
||||
* stop chunks.
|
||||
* <br>1.0 2005-01-16 Created.
|
||||
*/
|
||||
public class RIFFParser extends Object {
|
||||
private final static boolean DEBUG =false;
|
||||
/** ID for FormGroupExpression. */
|
||||
public final static int RIFF_ID = stringToID("RIFF");
|
||||
/** ID for ListGroupExpression. */
|
||||
public final static int LIST_ID = stringToID("LIST");
|
||||
/** ID for NULL chunks. */
|
||||
public final static int NULL_ID = stringToID(" ");
|
||||
/** ID for NULL chunks. */
|
||||
public final static int NULL_NUL_ID = stringToID("\0\0\0\0");
|
||||
/** ID for JUNK chunks. */
|
||||
public final static int JUNK_ID = stringToID("JUNK");
|
||||
/** The visitor traverses the parse tree. */
|
||||
private RIFFVisitor visitor;
|
||||
/** List of data chunks the visitor is interested in. */
|
||||
private HashSet<RIFFChunk> dataChunks;
|
||||
/** List of property chunks the visitor is interested in. */
|
||||
private HashSet<RIFFChunk> propertyChunks;
|
||||
/** List of collection chunks the visitor is interested in. */
|
||||
private HashSet<RIFFChunk> collectionChunks;
|
||||
/** List of stop chunks the visitor is interested in. */
|
||||
private HashSet<Integer> stopChunkTypes;
|
||||
/** List of group chunks the visitor is interested in. */
|
||||
private HashSet<RIFFChunk> groupChunks;
|
||||
/** Reference to the input stream. */
|
||||
private RIFFPrimitivesInputStream in;
|
||||
/** Reference to the image input stream. */
|
||||
private ImageInputStream iin;
|
||||
|
||||
/** Whether we stop at all chunks. */
|
||||
private boolean isStopChunks;
|
||||
|
||||
/** Stream offset. */
|
||||
private long streamOffset;
|
||||
|
||||
/* ---- constructors ---- */
|
||||
/**
|
||||
* Constructs a new RIFF parser.
|
||||
*/
|
||||
public RIFFParser() {
|
||||
}
|
||||
|
||||
public long getStreamOffset() {
|
||||
return streamOffset;
|
||||
}
|
||||
|
||||
public void setStreamOffset(long offset) {
|
||||
this.streamOffset = offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ---- accessor methods ---- */
|
||||
/* ---- action methods ---- */
|
||||
/**
|
||||
* Interprets the RIFFFile expression located at the
|
||||
* current position of the indicated InputStream.
|
||||
* Lets the visitor traverse the RIFF parse tree during
|
||||
* interpretation.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> Data-, property- and collection chunks must have been
|
||||
* declared prior to this call.
|
||||
* <li> When the client never declared chunks, then all local
|
||||
* chunks will be interpreted as data chunks.
|
||||
* <li> The stream must be positioned at the beginning of the
|
||||
* RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> When no exception was thrown then the stream is positioned
|
||||
* after the RIFFFile expression.
|
||||
*
|
||||
* <p>Obligation
|
||||
* The visitor may throw an ParseException or an
|
||||
* AbortException during tree traversal.
|
||||
*
|
||||
* @exception ParseException
|
||||
* Is thrown when an interpretation error occured.
|
||||
* The stream is positioned where the error occured.
|
||||
* @exception AbortException
|
||||
* Is thrown when the visitor decided to abort the
|
||||
* interpretation.
|
||||
*/
|
||||
public long parse(InputStream in, RIFFVisitor v)
|
||||
throws ParseException, AbortException, IOException {
|
||||
this.in = new RIFFPrimitivesInputStream(in);
|
||||
visitor = v;
|
||||
parseFile();
|
||||
return getScan(this.in);
|
||||
}
|
||||
|
||||
public long parse(ImageInputStream in, RIFFVisitor v)
|
||||
throws ParseException, AbortException, IOException {
|
||||
return parse(new ImageInputStreamAdapter(in), v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a RIFF file.
|
||||
*
|
||||
* <pre>
|
||||
* RIFF = 'RIFF' FormGroup
|
||||
* </pre>
|
||||
*/
|
||||
private void parseFile()
|
||||
throws ParseException, AbortException, IOException {
|
||||
int id = in.readFourCC();
|
||||
|
||||
if (id == RIFF_ID) {
|
||||
parseFORM(null);
|
||||
} else if (id == JUNK_ID) {
|
||||
parseLocalChunk(null,id);
|
||||
} else {
|
||||
if (iin!=null) {
|
||||
throw new ParseException("Invalid RIFF File ID: \"" + idToString(id) +" 0x"+Integer.toHexString(id)+" near "+iin.getStreamPosition()+" 0x"+Long.toHexString(iin.getStreamPosition()));
|
||||
} else {
|
||||
throw new ParseException("Invalid RIFF File ID: \"" + idToString(id) +" 0x"+Integer.toHexString(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getScan(RIFFPrimitivesInputStream in) {
|
||||
return in.getScan()+streamOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a FORM group.
|
||||
* <pre>
|
||||
* FormGroup ::= size GroupType { ChunkID LocalChunk [pad]
|
||||
* | 'FORM' FormGroup [pad] }
|
||||
* | 'LIST' ListGroup [pad] }
|
||||
* </pre>
|
||||
*/
|
||||
private void parseFORM(HashMap props)
|
||||
throws ParseException, AbortException, IOException {
|
||||
long size = in.readULONG();
|
||||
long offset = getScan(in);
|
||||
int type = in.readFourCC();
|
||||
if (DEBUG)System.out.println("RIFFParser.parseForm "+idToString(type));
|
||||
if (!isGroupType(type)) {
|
||||
throw new ParseException("Invalid FORM Type: \"" + idToString(type) + "\"");
|
||||
}
|
||||
|
||||
RIFFChunk propGroup = (props == null) ? null : (RIFFChunk) props.get(type);
|
||||
RIFFChunk chunk = new RIFFChunk(type, RIFF_ID, size, offset, propGroup);
|
||||
|
||||
boolean visitorWantsToEnterGroup = false;
|
||||
if (isGroupChunk(chunk) && (visitorWantsToEnterGroup = visitor.enteringGroup(chunk))) {
|
||||
visitor.enterGroup(chunk);
|
||||
}
|
||||
|
||||
try {
|
||||
long finish = offset + size;
|
||||
while (getScan(in) < finish) {
|
||||
long idscan = getScan(in);
|
||||
int id = in.readFourCC();
|
||||
|
||||
if (id == RIFF_ID) {
|
||||
parseFORM(props);
|
||||
} else if (id == LIST_ID) {
|
||||
parseLIST(props);
|
||||
} else if (isLocalChunkID(id)) {
|
||||
parseLocalChunk(chunk, id);
|
||||
} else {
|
||||
ParseException pex = new ParseException("Invalid Chunk: \"" + id + "\" at offset:" + idscan);
|
||||
chunk.setParserMessage(pex.getMessage());
|
||||
throw pex;
|
||||
}
|
||||
|
||||
in.align();
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
e.printStackTrace();
|
||||
chunk.setParserMessage(
|
||||
"Unexpected EOF after "
|
||||
+ NumberFormat.getInstance().format(getScan(in) - offset)
|
||||
+ " bytes");
|
||||
} finally {
|
||||
if (visitorWantsToEnterGroup) {
|
||||
visitor.leaveGroup(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a LIST group.
|
||||
* <pre>
|
||||
* ListGroup ::= size GroupType { ChunkID LocalChunk [pad] | 'LIST ' ListGroup [pad] }
|
||||
* </pre>
|
||||
*/
|
||||
private void parseLIST(HashMap props)
|
||||
throws ParseException, AbortException, IOException {
|
||||
long size = in.readULONG();
|
||||
long scan = getScan(in);
|
||||
int type = in.readFourCC();
|
||||
if (DEBUG)System.out.println("RIFFParser.parseLIST "+idToString(type));
|
||||
|
||||
if (!isGroupType(type)) {
|
||||
throw new ParseException("Invalid LIST Type: \"" + type + "\"");
|
||||
}
|
||||
|
||||
RIFFChunk propGroup = (props == null) ? null : (RIFFChunk) props.get(type);
|
||||
RIFFChunk chunk = new RIFFChunk(type, LIST_ID, size, scan, propGroup);
|
||||
|
||||
boolean visitorWantsToEnterGroup = false;
|
||||
if (isGroupChunk(chunk) && (visitorWantsToEnterGroup = visitor.enteringGroup(chunk))) {
|
||||
visitor.enterGroup(chunk);
|
||||
}
|
||||
try {
|
||||
if (visitorWantsToEnterGroup) {
|
||||
long finish = scan + size;
|
||||
while (getScan(in) < finish) {
|
||||
long idscan = getScan(in);
|
||||
int id = in.readFourCC();
|
||||
if (id == LIST_ID) {
|
||||
parseLIST(props);
|
||||
} else if (isLocalChunkID(id)) {
|
||||
parseLocalChunk(chunk, id);
|
||||
} else {
|
||||
parseGarbage(chunk, id, finish-getScan(in), getScan(in));
|
||||
ParseException pex = new ParseException("Invalid Chunk: \"" + id + "\" at offset:" + idscan);
|
||||
chunk.setParserMessage(pex.getMessage());
|
||||
//throw pex;
|
||||
}
|
||||
|
||||
in.align();
|
||||
}
|
||||
} else {
|
||||
in.skipFully(size-4);
|
||||
in.align();
|
||||
}
|
||||
} finally {
|
||||
if (visitorWantsToEnterGroup) {
|
||||
visitor.leaveGroup(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a local chunk.
|
||||
* <pre>
|
||||
* LocalChunk ::= size { DataChunk | PropertyChunk | CollectionChunk }
|
||||
* DataChunk = PropertyChunk = CollectionChunk ::= { byte }...*size
|
||||
* </pre>
|
||||
*/
|
||||
private void parseLocalChunk(RIFFChunk parent, int id)
|
||||
throws ParseException, AbortException, IOException {
|
||||
long size = in.readULONG();
|
||||
long scan = getScan(in);
|
||||
if (DEBUG)System.out.println("RIFFParser.parseLocalChunk "+idToString(id));
|
||||
RIFFChunk chunk = new RIFFChunk(parent==null?0:parent.getType(), id, size, scan);
|
||||
|
||||
if (isDataChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
visitor.visitChunk(parent, chunk);
|
||||
} else if (isPropertyChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
parent.putPropertyChunk(chunk);
|
||||
} else if (isCollectionChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
parent.addCollectionChunk(chunk);
|
||||
} else {
|
||||
in.skipFully((int) size);
|
||||
if (isStopChunks) {
|
||||
visitor.visitChunk(parent, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This method is invoked when we encounter a parsing problem.
|
||||
* <pre>
|
||||
* LocalChunk ::= size { DataChunk | PropertyChunk | CollectionChunk }
|
||||
* DataChunk = PropertyChunk = CollectionChunk ::= { byte }...*size
|
||||
* </pre>
|
||||
*/
|
||||
private void parseGarbage(RIFFChunk parent, int id, long size, long scan)
|
||||
throws ParseException, AbortException, IOException {
|
||||
//long size = in.readULONG();
|
||||
//long scan = getScan(in);
|
||||
|
||||
RIFFChunk chunk = new RIFFChunk(parent.getType(), id, size, scan);
|
||||
|
||||
if (isDataChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
visitor.visitChunk(parent, chunk);
|
||||
} else if (isPropertyChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
parent.putPropertyChunk(chunk);
|
||||
} else if (isCollectionChunk(chunk)) {
|
||||
byte[] data = new byte[(int) size];
|
||||
in.read(data, 0, (int) size);
|
||||
chunk.setData(data);
|
||||
parent.addCollectionChunk(chunk);
|
||||
} else {
|
||||
in.skipFully((int) size);
|
||||
if (isStopChunk(chunk)) {
|
||||
visitor.visitChunk(parent, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the ID of the chunk has been declared as a
|
||||
* data chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> Data chunks must have been declared before the
|
||||
* interpretation has been started.
|
||||
* <li> This method will always return true when neither
|
||||
* data chunks, property chunks nor collection chunks
|
||||
* have been declared,
|
||||
*
|
||||
* @param chunk Chunk to be verified.
|
||||
* @return True when the parameter is a data chunk.
|
||||
*/
|
||||
protected boolean isDataChunk(RIFFChunk chunk) {
|
||||
if (dataChunks == null) {
|
||||
if (collectionChunks == null && propertyChunks == null && (stopChunkTypes==null||!stopChunkTypes.contains(chunk.getType()))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return dataChunks.contains(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the ID of the chunk has been declared as
|
||||
* a group chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> Group chunks must have been declared before the
|
||||
* interpretation has been started.
|
||||
* (Otherwise the response is always true).
|
||||
*
|
||||
* @param chunk Chunk to be verified.
|
||||
* @return True when the visitor is interested in this is a group chunk.
|
||||
*/
|
||||
protected boolean isGroupChunk(RIFFChunk chunk) {
|
||||
if (groupChunks == null) {
|
||||
return true;
|
||||
} else {
|
||||
return groupChunks.contains(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the ID of the chunk has been declared as a
|
||||
* property chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> Property chunks must have been declared before the
|
||||
* interpretation has been started.
|
||||
* <li> This method will always return false when neither
|
||||
* data chunks, property chunks nor collection chunks
|
||||
* have been declared,
|
||||
*/
|
||||
protected boolean isPropertyChunk(RIFFChunk chunk) {
|
||||
if (propertyChunks == null) {
|
||||
return false;
|
||||
} else {
|
||||
return propertyChunks.contains(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the ID of the chunk has been declared as a
|
||||
* collection chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> Collection chunks must have been declared before the
|
||||
* interpretation has been started.
|
||||
* <li> This method will always return true when neither
|
||||
* data chunks, property chunks nor collection chunks
|
||||
* have been declared,
|
||||
*
|
||||
* @param chunk Chunk to be verified.
|
||||
* @return True when the parameter is a collection chunk.
|
||||
*/
|
||||
protected boolean isCollectionChunk(RIFFChunk chunk) {
|
||||
if (collectionChunks == null) {
|
||||
return false;
|
||||
} else {
|
||||
return collectionChunks.contains(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a data chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> The chunk must not have already been declared as of a
|
||||
* different type.
|
||||
* <li> Declarations may not be done during interpretation
|
||||
* of an RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> Data chunk declared
|
||||
*
|
||||
* @param type
|
||||
* Type of the chunk. Must be formulated as a TypeID conforming
|
||||
* to the method #isFormType.
|
||||
* @param id
|
||||
* ID of the chunk. Must be formulated as a ChunkID conforming
|
||||
* to the method #isLocalChunkID.
|
||||
*/
|
||||
public void declareDataChunk(int type, int id) {
|
||||
RIFFChunk chunk = new RIFFChunk(type, id);
|
||||
if (dataChunks == null) {
|
||||
dataChunks = new HashSet<RIFFChunk>();
|
||||
}
|
||||
dataChunks.add(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a FORM group chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> The chunk must not have already been declared as of a
|
||||
* different type.
|
||||
* <li> Declarations may not be done during interpretation
|
||||
* of an RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> Group chunk declared
|
||||
*
|
||||
* @param type
|
||||
* Type of the chunk. Must be formulated as a TypeID conforming
|
||||
* to the method #isFormType.
|
||||
* @param id
|
||||
* ID of the chunk. Must be formulated as a ChunkID conforming
|
||||
* to the method #isContentsType.
|
||||
*/
|
||||
public void declareGroupChunk(int type, int id) {
|
||||
RIFFChunk chunk = new RIFFChunk(type, id);
|
||||
if (groupChunks == null) {
|
||||
groupChunks = new HashSet<RIFFChunk>();
|
||||
}
|
||||
groupChunks.add(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a property chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> The chunk must not have already been declared as of a
|
||||
* different type.
|
||||
* <li> Declarations may not be done during interpretation
|
||||
* of an RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> Group chunk declared
|
||||
*
|
||||
*
|
||||
* @param type
|
||||
* Type of the chunk. Must be formulated as a TypeID conforming
|
||||
* to the method #isFormType.
|
||||
* @param id
|
||||
* ID of the chunk. Must be formulated as a ChunkID conforming
|
||||
* to the method #isLocalChunkID.
|
||||
*/
|
||||
public void declarePropertyChunk(int type, int id) {
|
||||
RIFFChunk chunk = new RIFFChunk(type, id);
|
||||
if (propertyChunks == null) {
|
||||
propertyChunks = new HashSet<RIFFChunk>();
|
||||
}
|
||||
propertyChunks.add(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a collection chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> The chunk must not have already been declared as of a
|
||||
* different type.
|
||||
* <li> Declarations may not be done during interpretation
|
||||
* of an RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> Collection chunk declared
|
||||
*
|
||||
* @param type
|
||||
* Type of the chunk. Must be formulated as a TypeID conforming
|
||||
* to the method #isFormType.
|
||||
* @param id
|
||||
* ID of the chunk. Must be formulated as a ChunkID conforming
|
||||
* to the method #isLocalChunkID.
|
||||
*/
|
||||
public void declareCollectionChunk(int type, int id) {
|
||||
RIFFChunk chunk = new RIFFChunk(type, id);
|
||||
if (collectionChunks == null) {
|
||||
collectionChunks = new HashSet<RIFFChunk>();
|
||||
}
|
||||
collectionChunks.add(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a stop chunk.
|
||||
*
|
||||
* <p>Pre condition
|
||||
* <li> The chunk must not have already been declared as of a
|
||||
* different type.
|
||||
* <li> Declarations may not be done during interpretation
|
||||
* of an RIFFFileExpression.
|
||||
*
|
||||
* <p>Post condition
|
||||
* <li> Stop chunk declared
|
||||
*
|
||||
* @param type
|
||||
* Type of the chunk. Must be formulated as a TypeID conforming
|
||||
* to the method #isFormType.
|
||||
*/
|
||||
public void declareStopChunkType(int type) {
|
||||
if (stopChunkTypes == null) {
|
||||
stopChunkTypes = new HashSet<Integer>();
|
||||
}
|
||||
stopChunkTypes.add(type);
|
||||
}
|
||||
|
||||
/** Whether the parse should stop at all chunks.
|
||||
* <p>
|
||||
* The parser does not read the data body of stop chunks.
|
||||
* <p>
|
||||
* By declaring stop chunks, and not declaring any data, group or
|
||||
* property chunks, the file structure of a RIFF file can be quickly
|
||||
* scanned through.
|
||||
*/
|
||||
public void declareStopChunks() {
|
||||
isStopChunks = true;
|
||||
}
|
||||
|
||||
private boolean isStopChunk(RIFFChunk chunk) {
|
||||
return isStopChunks||stopChunkTypes!=null&&stopChunkTypes.contains(chunk.getType());
|
||||
}
|
||||
|
||||
/* ---- Class methods ---- */
|
||||
/**
|
||||
* Checks wether the argument represents a valid RIFF GroupID.
|
||||
*
|
||||
* <p>Validation
|
||||
* <ul>
|
||||
* <li> Group ID must be one of RIFF_ID, LIST_ID.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param id Chunk ID to be checked.
|
||||
* @return True when the chunk ID is a valid Group ID.
|
||||
*/
|
||||
public static boolean isGroupID(int id) {
|
||||
return id == LIST_ID || id == RIFF_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the argument represents a valid RIFF Group Type.
|
||||
*
|
||||
* <p>Validation
|
||||
* <ul>
|
||||
* <li> Must be a valid ID.</li>
|
||||
* <li> Must not be a group ID.</li>
|
||||
* <li> Must not be a NULL_ID.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param id Chunk ID to be checked.
|
||||
* @return True when the chunk ID is a valid Group ID.
|
||||
*/
|
||||
public static boolean isGroupType(int id) {
|
||||
return isID(id) && !isGroupID(id) && id != NULL_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the argument represents a valid RIFF ID.
|
||||
*
|
||||
* <p>Validation
|
||||
* <li> Every byte of an ID must be in the range of 0x20..0x7e
|
||||
* <li> The id may not have leading spaces (unless the id is a NULL_ID).
|
||||
*
|
||||
* @param id Chunk ID to be checked.
|
||||
* @return True when the ID is a valid IFF chunk ID.
|
||||
*/
|
||||
public static boolean isID(int id) {
|
||||
int c0 = id >> 24;
|
||||
int c1 = (id >> 16) & 0xff;
|
||||
int c2 = (id >> 8) & 0xff;
|
||||
int c3 = id & 0xff;
|
||||
|
||||
return id == NULL_NUL_ID
|
||||
|| c0 >= 0x20 && c0 <= 0x7e
|
||||
&& c1 >= 0x20 && c1 <= 0x7e
|
||||
&& c2 >= 0x20 && c2 <= 0x7e
|
||||
&& c3 >= 0x20 && c3 <= 0x7e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the argument is a valid Local Chunk ID.
|
||||
*
|
||||
* <p>Validation
|
||||
* <ud>
|
||||
* <li> Must be valid ID.</li>
|
||||
* <li> Local Chunk IDs may not collide with GroupIDs.</id>
|
||||
* <li> Must not be a NULL_ID.</li>
|
||||
* </ud>
|
||||
*
|
||||
* @param id Chunk ID to be checked.
|
||||
* @return True when the chunk ID is a Local Chunk ID.
|
||||
*/
|
||||
public static boolean isLocalChunkID(int id) {
|
||||
if (isGroupID(id)) {
|
||||
return false;
|
||||
}
|
||||
return id != NULL_ID && isID(id);
|
||||
}
|
||||
private WeakHashMap<String, String> ids;
|
||||
|
||||
/**
|
||||
* Convert an integer IFF identifier to String.
|
||||
*
|
||||
* @param anInt ID to be converted.
|
||||
* @return String representation of the ID.
|
||||
*/
|
||||
public static String idToString(int anInt) {
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
bytes[0] = (byte) (anInt >>> 24);
|
||||
bytes[1] = (byte) (anInt >>> 16);
|
||||
bytes[2] = (byte) (anInt >>> 8);
|
||||
bytes[3] = (byte) (anInt >>> 0);
|
||||
|
||||
try {
|
||||
return new String(bytes, "ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the first four letters of the
|
||||
* String into an IFF Identifier.
|
||||
*
|
||||
* @param aString String to be converted.
|
||||
* @return ID representation of the String.
|
||||
*/
|
||||
public static int stringToID(String aString) {
|
||||
byte[] bytes = aString.getBytes();
|
||||
|
||||
return ((int) bytes[0]) << 24
|
||||
| ((int) bytes[1]) << 16
|
||||
| ((int) bytes[2]) << 8
|
||||
| ((int) bytes[3]) << 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* @(#)RIFFPrimitivesInputStream.java 1.0 2005-01-15
|
||||
*
|
||||
* Copyright (c) 2005 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.riff;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A RIFF primitives input stream lets an application read primitive data
|
||||
* types in the Microsoft Resource Interfache File Format (RIFF) format from an
|
||||
* underlying input stream.
|
||||
*
|
||||
* Reference:
|
||||
* AVI RIFF File Reference
|
||||
* http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c/directx/htm/avirifffilereference.asp
|
||||
*
|
||||
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
|
||||
* @version 1.0 2005-01-15 Created.
|
||||
*/
|
||||
public class RIFFPrimitivesInputStream extends FilterInputStream {
|
||||
private long scan, mark;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param in the input stream.
|
||||
*/
|
||||
public RIFFPrimitivesInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 1 byte from the input stream and interpret
|
||||
* them as an 8 Bit unsigned UBYTE value.
|
||||
*/
|
||||
public int readUBYTE()
|
||||
throws IOException {
|
||||
int b0 = in.read();
|
||||
|
||||
if (b0 == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
scan += 1;
|
||||
return b0 & 0xff;
|
||||
}
|
||||
/**
|
||||
* Read 2 bytes from the input stream and interpret
|
||||
* them as a 16 Bit signed WORD value.
|
||||
*/
|
||||
public short readWORD()
|
||||
throws IOException {
|
||||
int b0 = in.read();
|
||||
int b1 = in.read();
|
||||
|
||||
if (b1 == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
scan += 2;
|
||||
|
||||
return (short) (((b0 & 0xff) << 0) | ((b1 & 0xff) << 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 2 bytes from the input stream and interpret
|
||||
* them as a 16 Bit unsigned UWORD value.
|
||||
*/
|
||||
public int readUWORD()
|
||||
throws IOException {
|
||||
return readWORD() & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 4 bytes from the input stream and interpret
|
||||
* them as a 32 Bit signed LONG value.
|
||||
*/
|
||||
public int readLONG()
|
||||
throws IOException {
|
||||
int b0 = in.read();
|
||||
int b1 = in.read();
|
||||
int b2 = in.read();
|
||||
int b3 = in.read();
|
||||
|
||||
if (b3 == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
scan += 4;
|
||||
|
||||
return ((b0&0xff) << 0) +
|
||||
((b1&0xff) << 8) +
|
||||
((b2&0xff) << 16) +
|
||||
((b3&0xff) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 4 bytes from the input stream and interpret
|
||||
* them as a four byte character code.
|
||||
*
|
||||
* Cited from Referenced "AVI RIFF File Reference":
|
||||
* "A FOURCC (four-character code) is a 32-bit unsigned integer created by
|
||||
* concatenating four ASCII characters. For example, the FOURCC 'abcd' is
|
||||
* represented on a Little-Endian system as 0x64636261. FOURCCs can contain
|
||||
* space characters, so ' abc' is a valid FOURCC. The AVI file format uses
|
||||
* FOURCC codes to identify stream types, data chunks, index entries, and
|
||||
* other information."
|
||||
*/
|
||||
public int readFourCC()
|
||||
throws IOException {
|
||||
int b3 = in.read();
|
||||
int b2 = in.read();
|
||||
int b1 = in.read();
|
||||
int b0 = in.read();
|
||||
|
||||
if (b0 == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
scan += 4;
|
||||
|
||||
return ((b0&0xff) << 0) +
|
||||
((b1&0xff) << 8) +
|
||||
((b2&0xff) << 16) +
|
||||
((b3&0xff) << 24);
|
||||
}
|
||||
/**
|
||||
* Read 4 bytes from the input stream and interpret
|
||||
* them as a four byte character code.
|
||||
*
|
||||
* Cited from Referenced "AVI RIFF File Reference":
|
||||
* "A FOURCC (four-character code) is a 32-bit unsigned integer created by
|
||||
* concatenating four ASCII characters. For example, the FOURCC 'abcd' is
|
||||
* represented on a Little-Endian system as 0x64636261. FOURCCs can contain
|
||||
* space characters, so ' abc' is a valid FOURCC. The AVI file format uses
|
||||
* FOURCC codes to identify stream types, data chunks, index entries, and
|
||||
* other information."
|
||||
*/
|
||||
public String readFourCCString()
|
||||
throws IOException {
|
||||
byte[] buf = new byte[4];
|
||||
readFully(buf, 0, 4);
|
||||
//scan += 4; <- scan is updated by method readFully
|
||||
return new String(buf, "ASCII");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 4 Bytes from the input Stream and interpret
|
||||
* them as an unsigned Integer value of type ULONG.
|
||||
*/
|
||||
public long readULONG()
|
||||
throws IOException {
|
||||
return (long)(readLONG()) & 0x00ffffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Align to an even byte position in the input stream.
|
||||
* This will skip one byte in the stream if the current
|
||||
* read position is not even.
|
||||
*/
|
||||
public void align()
|
||||
throws IOException {
|
||||
if (scan % 2 == 1) {
|
||||
in.skip(1);
|
||||
scan++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current read position within the file (as seen
|
||||
* by this input stream filter).
|
||||
*/
|
||||
public long getScan()
|
||||
{ return scan; }
|
||||
|
||||
/**
|
||||
* Reads one byte.
|
||||
*/
|
||||
public int read()
|
||||
throws IOException {
|
||||
int data = in.read();
|
||||
if (data != -1) scan++;
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Reads a sequence of bytes.
|
||||
*/
|
||||
public int readFully(byte[] b,int offset, int length)
|
||||
throws IOException {
|
||||
int count = read(b, offset, length);
|
||||
if (count != length) {
|
||||
throw new EOFException("readFully for "+length+" bytes, unexpected EOF after "+count+" bytes.");
|
||||
}
|
||||
//scan += count; <- scan is already counted by read method
|
||||
return count;
|
||||
}
|
||||
/**
|
||||
* Reads a sequence of bytes.
|
||||
*/
|
||||
public int read(byte[] b,int offset, int length)
|
||||
throws IOException {
|
||||
int count = 0;
|
||||
while (count < length) {
|
||||
int result = in.read(b,offset+count,length-count);
|
||||
if (result == -1) break;
|
||||
count += result;
|
||||
}
|
||||
scan += count;
|
||||
return count;
|
||||
}
|
||||
/**
|
||||
* Marks the input stream.
|
||||
* @param readlimit The maximum limit of bytes that can be read before
|
||||
* the mark position becomes invalid.
|
||||
*/
|
||||
public void mark(int readlimit) {
|
||||
in.mark(readlimit);
|
||||
mark = scan;
|
||||
}
|
||||
/**
|
||||
* Repositions the stream at the previously marked position.
|
||||
*
|
||||
* @exception IOException If the stream has not been marked or if the
|
||||
* mark has been invalidated.
|
||||
*/
|
||||
public void reset()
|
||||
throws IOException {
|
||||
in.reset();
|
||||
scan = mark;
|
||||
}
|
||||
/**
|
||||
* Skips over and discards n bytes of data from this input stream. This skip
|
||||
* method tries to skip the provided number of bytes.
|
||||
*/
|
||||
public long skip(long n)
|
||||
throws IOException {
|
||||
long skipped = in.skip(n);
|
||||
scan += skipped;
|
||||
return skipped;
|
||||
}
|
||||
/**
|
||||
* Skips over and discards n bytes of data from this input stream. Throws
|
||||
*
|
||||
* @param n the number of bytes to be skipped.
|
||||
* @exception EOFException if this input stream reaches the end before
|
||||
* skipping all the bytes.
|
||||
*/
|
||||
public void skipFully(long n)
|
||||
throws IOException {
|
||||
if (n==0) return;
|
||||
|
||||
int total = 0;
|
||||
int cur = 0;
|
||||
|
||||
while ((total<n) && ((cur = (int) in.skip(n-total)) > 0)) {
|
||||
total += cur;
|
||||
}
|
||||
if (cur == 0) throw new EOFException();
|
||||
scan += total;
|
||||
}
|
||||
}
|
||||
43
libsrc/avi/src/org/monte/media/riff/RIFFVisitor.java
Normal file
43
libsrc/avi/src/org/monte/media/riff/RIFFVisitor.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @(#)RIFFVIsitor.java 1.0 2005-01-09
|
||||
*
|
||||
* Copyright (c) 2005 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package org.monte.media.riff;
|
||||
|
||||
import org.monte.media.AbortException;
|
||||
import org.monte.media.ParseException;
|
||||
|
||||
/**
|
||||
* RIFFVIsitor is notified each time the RIFFParser visits
|
||||
* a data chunk and when a group is entered or leaved.
|
||||
*
|
||||
* @version 1.0 2005-01-09 Created.
|
||||
*/
|
||||
public interface RIFFVisitor {
|
||||
/** This method is invoked when the parser attempts to enter a group.
|
||||
* The visitor can return false, if the parse shall skip the group contents.
|
||||
*
|
||||
* @param group
|
||||
* @return True to enter the group, false to skip over the group.
|
||||
*/
|
||||
public boolean enteringGroup(RIFFChunk group);
|
||||
|
||||
/** This method is invoked when the parser enters a group chunk.*/
|
||||
public void enterGroup(RIFFChunk group)
|
||||
throws ParseException, AbortException;
|
||||
|
||||
/** This method is invoked when the parser leaves a group chunk.*/
|
||||
public void leaveGroup(RIFFChunk group)
|
||||
throws ParseException, AbortException;
|
||||
|
||||
/** This method is invoked when the parser has read a data chunk or
|
||||
* has skipped a stop chunk.*/
|
||||
public void visitChunk(RIFFChunk group, RIFFChunk chunk)
|
||||
throws ParseException, AbortException;
|
||||
}
|
||||
463
libsrc/avi/src/org/monte/media/util/Methods.java
Normal file
463
libsrc/avi/src/org/monte/media/util/Methods.java
Normal file
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* @(#)Methods.java
|
||||
*
|
||||
* Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package org.monte.media.util;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
/**
|
||||
* Methods contains convenience methods for method invocations using
|
||||
* java.lang.reflect.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version $Id: Methods.java 299 2013-01-03 07:40:18Z werner $
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Methods {
|
||||
/**
|
||||
* Prevent instance creation.
|
||||
*/
|
||||
private Methods() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified accessible parameterless method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @return The return value of the method.
|
||||
* @return NoSuchMethodException if the method does not exist or is not
|
||||
* accessible.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(obj, new Object[0]);
|
||||
return result;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified accessible method with a string parameter if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param stringParameter The String parameter
|
||||
* @return The return value of the method or METHOD_NOT_FOUND.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, String stringParameter)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[] { String.class });
|
||||
Object result = method.invoke(obj, new Object[] { stringParameter });
|
||||
return result;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified accessible parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @return The return value of the method or METHOD_NOT_FOUND.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(Class clazz, String methodName)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = clazz.getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(null, new Object[0]);
|
||||
return result;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified accessible parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @return The return value of the method.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(String clazz, String methodName)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
return invokeStatic(Class.forName(clazz), methodName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new NoSuchMethodException("class "+clazz+" not found");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param type The parameter type.
|
||||
* @param value The parameter value.
|
||||
* @return The return value of the method.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(Class clazz, String methodName, Class type, Object value)
|
||||
throws NoSuchMethodException {
|
||||
return invokeStatic(clazz,methodName,new Class[]{type},new Object[]{value});
|
||||
}
|
||||
/**
|
||||
* Invokes the specified parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param types The parameter types.
|
||||
* @param values The parameter values.
|
||||
* @return The return value of the method.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(Class clazz, String methodName, Class[] types, Object[] values)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = clazz.getMethod(methodName, types);
|
||||
Object result = method.invoke(null, values);
|
||||
return result;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param types The parameter types.
|
||||
* @param values The parameter values.
|
||||
* @return The return value of the method.
|
||||
* @return NoSuchMethodException if the method does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(String clazz, String methodName,
|
||||
Class[] types, Object[] values)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
return invokeStatic(Class.forName(clazz), methodName, types, values);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new NoSuchMethodException("class "+clazz+" not found");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified parameterless method if it exists.
|
||||
*
|
||||
* @param clazz The class on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param types The parameter types.
|
||||
* @param values The parameter values.
|
||||
* @param defaultValue The default value.
|
||||
* @return The return value of the method or the default value if the method
|
||||
* does not exist or is not accessible.
|
||||
*/
|
||||
public static Object invokeStatic(String clazz, String methodName,
|
||||
Class[] types, Object[] values, Object defaultValue) {
|
||||
try {
|
||||
return invokeStatic(Class.forName(clazz), methodName, types, values);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return defaultValue;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified getter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param defaultValue This value is returned, if the method does not exist.
|
||||
* @return The value returned by the getter method or the default value.
|
||||
*/
|
||||
public static int invokeGetter(Object obj, String methodName, int defaultValue) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(obj, new Object[0]);
|
||||
return ((Integer) result).intValue();
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
} catch (IllegalAccessException e) {
|
||||
return defaultValue;
|
||||
} catch (InvocationTargetException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified getter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param defaultValue This value is returned, if the method does not exist.
|
||||
* @return The value returned by the getter method or the default value.
|
||||
*/
|
||||
public static long invokeGetter(Object obj, String methodName, long defaultValue) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(obj, new Object[0]);
|
||||
return ((Long) result).longValue();
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
} catch (IllegalAccessException e) {
|
||||
return defaultValue;
|
||||
} catch (InvocationTargetException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified getter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param defaultValue This value is returned, if the method does not exist.
|
||||
* @return The value returned by the getter method or the default value.
|
||||
*/
|
||||
public static boolean invokeGetter(Object obj, String methodName, boolean defaultValue) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(obj, new Object[0]);
|
||||
return ((Boolean) result).booleanValue();
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
} catch (IllegalAccessException e) {
|
||||
return defaultValue;
|
||||
} catch (InvocationTargetException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified getter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param defaultValue This value is returned, if the method does not exist.
|
||||
* @return The value returned by the getter method or the default value.
|
||||
*/
|
||||
public static Object invokeGetter(Object obj, String methodName, Object defaultValue) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(obj, new Object[0]);
|
||||
return result;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
} catch (IllegalAccessException e) {
|
||||
return defaultValue;
|
||||
} catch (InvocationTargetException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified getter method if it exists.
|
||||
*
|
||||
* @param clazz The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param defaultValue This value is returned, if the method does not exist.
|
||||
* @return The value returned by the getter method or the default value.
|
||||
*/
|
||||
public static boolean invokeStaticGetter(Class clazz, String methodName, boolean defaultValue) {
|
||||
try {
|
||||
Method method = clazz.getMethod(methodName, new Class[0]);
|
||||
Object result = method.invoke(null, new Object[0]);
|
||||
return ((Boolean) result).booleanValue();
|
||||
} catch (NoSuchMethodException e) {
|
||||
return defaultValue;
|
||||
} catch (IllegalAccessException e) {
|
||||
return defaultValue;
|
||||
} catch (InvocationTargetException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, boolean newValue)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[] { Boolean.TYPE} );
|
||||
return method.invoke(obj, new Object[] { newValue});
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, int newValue)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[] { Integer.TYPE} );
|
||||
return method.invoke(obj, new Object[] { newValue});
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, float newValue)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[] { Float.TYPE} );
|
||||
return method.invoke(obj, new Object[] { new Float(newValue)});
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, Class clazz, Object newValue)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, new Class[] { clazz } );
|
||||
return method.invoke(obj, new Object[] { newValue});
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
throw new InternalError(e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static Object invoke(Object obj, String methodName, Class[] clazz, Object... newValue)
|
||||
throws NoSuchMethodException {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName, clazz );
|
||||
return method.invoke(obj, newValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoSuchMethodException(methodName+" is not accessible");
|
||||
} catch (InvocationTargetException e) {
|
||||
// The method is not supposed to throw exceptions
|
||||
InternalError error = new InternalError(e.getMessage());
|
||||
error.initCause((e.getCause() != null) ? e.getCause() : e);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static void invokeIfExists(Object obj, String methodName) {
|
||||
try {
|
||||
invoke(obj, methodName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static void invokeIfExists(Object obj, String methodName, float newValue) {
|
||||
try {
|
||||
invoke(obj, methodName, newValue);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static void invokeIfExists(Object obj, String methodName, boolean newValue) {
|
||||
try {
|
||||
invoke(obj, methodName, newValue);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static void invokeIfExists(Object obj, String methodName, Class clazz, Object newValue) {
|
||||
try {
|
||||
invoke(obj, methodName, clazz, newValue);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified setter method if it exists.
|
||||
*
|
||||
* @param obj The object on which to invoke the method.
|
||||
* @param methodName The name of the method.
|
||||
*/
|
||||
public static void invokeIfExistsWithEnum(Object obj, String methodName, String enumClassName, String enumValueName) {
|
||||
try {
|
||||
Class enumClass = Class.forName(enumClassName);
|
||||
Object enumValue = invokeStatic("java.lang.Enum", "valueOf", new Class[] {Class.class, String.class},
|
||||
new Object[] {enumClass, enumValueName}
|
||||
);
|
||||
invoke(obj, methodName, enumClass, enumValue);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user