trunk contents moved to root

This commit is contained in:
Jindra Petřík
2014-05-10 20:50:57 +02:00
parent 1b851e66a8
commit 199a4d0c2b
2296 changed files with 0 additions and 0 deletions

73
libsrc/avi/build.xml Normal file
View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="avi" default="default" basedir=".">
<description>Builds, tests, and runs the project avi.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="avi-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
build.xml.data.CRC32=505ab359
build.xml.script.CRC32=fa68b8cb
build.xml.stylesheet.CRC32=8064a381@1.68.1.46
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=505ab359
nbproject/build-impl.xml.script.CRC32=3d5c3ae5
nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.1.48

View File

@@ -0,0 +1,72 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processor.options=
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.test.classpath=\
${run.test.classpath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=../../lib/avi.jar
dist.javadoc.dir=${dist.dir}/javadoc
excludes=
includes=**
jar.compress=false
javac.classpath=
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.processorpath=\
${javac.classpath}
javac.source=1.7
javac.target=1.7
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
main.class=org.monte.media.math.IntMath
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=true
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>avi</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View 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);
}
}

View 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+'}';
}
}

View 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);
}
}

View 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);
}
}

View 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 &gt;= 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();
}
}

View 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;
}

View 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();
}

View 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();
}
}
}
}
}

View 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);
}
}
}
}
}

View 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);
}
}

View 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);
}

View 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 &gt;= 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;
}

View 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);
}

View 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;
}

View 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);
}
}

View 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);
}

View 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);
}

View 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()));
}
}

File diff suppressed because it is too large Load Diff

View 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();
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}
}

View 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));
}
}

View 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&nbsp;- 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;
}
}

View File

@@ -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&nbsp;- 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;
}
}

View 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;
}
}

View 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));
}
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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";
}
}

View 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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);
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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())+"}";
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}

View 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();
}
}
}