From e039870adc78ef6d99299861f8ef1103bc9c4797 Mon Sep 17 00:00:00 2001 From: Knut Forkalsrud Date: Sun, 31 Jul 2011 13:40:27 -0700 Subject: [PATCH] Finally cracked the nut on the FLV metadata processing. --- .../org/forkalsrud/album/video/FlvFilter.java | 53 ++- .../album/video/FlvFormatException.java | 21 ++ .../forkalsrud/album/video/FlvMetadata.java | 356 +++++++++++++++--- .../forkalsrud/album/video/MovieCoder.java | 29 +- .../forkalsrud/album/video/FlvFilterTest.java | 16 +- .../album/video/FlvMetadataTest.java | 85 +++++ 6 files changed, 453 insertions(+), 107 deletions(-) create mode 100644 src/main/java/org/forkalsrud/album/video/FlvFormatException.java diff --git a/src/main/java/org/forkalsrud/album/video/FlvFilter.java b/src/main/java/org/forkalsrud/album/video/FlvFilter.java index 88f7e17..63b58ef 100644 --- a/src/main/java/org/forkalsrud/album/video/FlvFilter.java +++ b/src/main/java/org/forkalsrud/album/video/FlvFilter.java @@ -1,5 +1,6 @@ package org.forkalsrud.album.video; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -14,7 +15,7 @@ public class FlvFilter extends OutputStream { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FlvFilter.class); public interface FlvReceiver { - public void writeHeader(byte[]... data); + public void writeHeader(byte[] data); public void writeBody(byte[] data); } @@ -50,8 +51,8 @@ public class FlvFilter extends OutputStream { public FlvFilter(FlvReceiver receiver, FlvMetadata metadata) { this.receiver = receiver; - this.metadata = metadata; - this.extraMetadata = null; + this.metadata = new FlvMetadata(); + this.extraMetadata = metadata; } /** @@ -141,22 +142,21 @@ public class FlvFilter extends OutputStream { // Then only audio and video if (currentTagType == FLV_TAG_SCRIPTDATA) { if (!headerWritten) { - incomingMetadataLength = currentTagSize; - /* - TODO (knut 09 JUL 2011) generate (extra-)metadata here to enable user injection of metadata - metadata.read(currentBox, FLV_SIZE_TAGHEADER, currentTagSize - FLV_SIZE_TAGFOOTER); + incomingMetadataLength = currentTagSize - (FLV_SIZE_TAGHEADER + FLV_SIZE_TAGFOOTER); + ByteArrayInputStream scriptData = new ByteArrayInputStream(currentBox, FLV_SIZE_TAGHEADER, incomingMetadataLength); + metadata.read(scriptData); metadata.merge(extraMetadata); - generateHeader(); - */ - receiver.writeHeader(fileHeader, currentBox); + generateHeaderChunk(); +// receiver.writeHeader(fileHeader, currentBox); // copy the input directly headerWritten = true; + metadata.clearKeyframes(); } else { log.warn("SCRIPTDATA out of order"); writeBody(); } } else if (currentTagType == FLV_TAG_VIDEO || currentTagType == FLV_TAG_AUDIO) { if (!headerWritten) { - log.error("SCRIPTDATA out of order"); + log.error("Missing SCRIPTDATA"); receiver.writeHeader(fileHeader); headerWritten = true; } @@ -190,16 +190,29 @@ public class FlvFilter extends OutputStream { } } - public void generateHeader() throws IOException { + /** + * Writes out the header chunk which consists of a file header and a script data tag. + * The tag body comes from the metadata object. The file header is a copy of what we read in at the start. + * @throws IOException + */ + public void generateHeaderChunk() throws IOException { - metadata.setFileSize(fileHeader.length + incomingMetadataLength + bodyLen); + metadata.setFileSize(fileHeader.length + FLV_SIZE_TAGHEADER + incomingMetadataLength + FLV_SIZE_TAGFOOTER + bodyLen); int metadataLength = metadata.calculateLength(); - ByteArrayOutputStream out = new ByteArrayOutputStream(fileHeader.length + metadataLength); + ByteArrayOutputStream out = new ByteArrayOutputStream(fileHeader.length + FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER); + // file header out.write(fileHeader); - metadata.setFileSize(fileHeader.length + metadataLength + bodyLen); + metadata.setFileSize(fileHeader.length + FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER + bodyLen); metadata.setFileOffsetDelta(metadataLength - incomingMetadataLength); - metadata.writeOnMetadata(out, metadataLength); - + // tag header + out.write(FLV_TAG_SCRIPTDATA); // Script data tag 1 byte + metadata.writeUint24(out, metadataLength); //data size 3 bytes + metadata.writeTimestamp(out, 0); // timestamp 4 bytes + metadata.writeUint24(out, 0); // Stream ID 3 bytes + // body + metadata.writeScriptDataObject(out); + // tag footer + metadata.writeUint32(out, FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER); // size of tag receiver.writeHeader(out.toByteArray()); } @@ -219,6 +232,12 @@ public class FlvFilter extends OutputStream { return ((ch1 << 16) + (ch2 << 8) + (ch3 << 0)); } + int decodeUint16(byte[] buf, int offset) { + int ch1 = buf[offset + 0] & 0xff; + int ch2 = buf[offset + 1] & 0xff; + return ((ch1 << 8) + (ch2 << 0)); + } + int decodeUint8(byte[] buf, int offset) { return buf[offset] & 0xff; } diff --git a/src/main/java/org/forkalsrud/album/video/FlvFormatException.java b/src/main/java/org/forkalsrud/album/video/FlvFormatException.java new file mode 100644 index 0000000..f3dd484 --- /dev/null +++ b/src/main/java/org/forkalsrud/album/video/FlvFormatException.java @@ -0,0 +1,21 @@ +package org.forkalsrud.album.video; + +public class FlvFormatException extends Exception { + + public FlvFormatException() { + super(); + } + + public FlvFormatException(String message) { + super(message); + } + + public FlvFormatException(Throwable cause) { + super(cause); + } + + public FlvFormatException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/forkalsrud/album/video/FlvMetadata.java b/src/main/java/org/forkalsrud/album/video/FlvMetadata.java index cfcc390..3d5944e 100644 --- a/src/main/java/org/forkalsrud/album/video/FlvMetadata.java +++ b/src/main/java/org/forkalsrud/album/video/FlvMetadata.java @@ -2,12 +2,14 @@ package org.forkalsrud.album.video; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.forkalsrud.album.exif.Dimension; +import org.slf4j.LoggerFactory; public class FlvMetadata { @@ -27,15 +29,13 @@ public class FlvMetadata { this.value = value; } - public void write(OutputStream out) throws IOException { - if (name != null) { - writeFlvString(out, name); - } - } + public abstract void write(OutputStream out) throws IOException; public boolean isPresent() { return present; } + + public abstract void read(InputStream in) throws FlvFormatException, IOException; } private class StringAttr extends Attr { @@ -46,10 +46,18 @@ public class FlvMetadata { @Override public void write(OutputStream out) throws IOException { - super.write(out); out.write(2); // DataString writeFlvString(out, value); } + + @Override + public void read(InputStream in) throws FlvFormatException, IOException { + int type = in.read(); + if (type != 2) { + throw new FlvFormatException("Not a string: " + type); + } + set(readString(in)); + } } private class BooleanAttr extends Attr { @@ -60,10 +68,18 @@ public class FlvMetadata { @Override public void write(OutputStream out) throws IOException { - super.write(out); out.write(1); // Bool out.write(value ? 1 : 0); } + + @Override + public void read(InputStream in) throws FlvFormatException, IOException { + int type = in.read(); + if (type != 1) { + throw new FlvFormatException("Not a boolean: " + type); + } + set(readBoolean(in)); + } } private class DoubleAttr extends Attr { @@ -74,10 +90,17 @@ public class FlvMetadata { @Override public void write(OutputStream out) throws IOException { - super.write(out); out.write(0); // NUMBER - long x = Double.doubleToLongBits(value); - writeUint64(out, x); + writeDouble(out, value); + } + + @Override + public void read(InputStream in) throws FlvFormatException, IOException { + int type = in.read(); + if (type != 0) { + throw new FlvFormatException("Not a boolean: " + type); + } + set(readDouble(in)); } } @@ -89,10 +112,17 @@ public class FlvMetadata { @Override public void write(OutputStream out) throws IOException { - super.write(out); out.write(0); // NUMBER - long x = Double.doubleToLongBits(value + filePositionDelta); - writeUint64(out, x); + writeDouble(out, value + filePositionDelta); + } + + @Override + public void read(InputStream in) throws FlvFormatException, IOException { + int type = in.read(); + if (type != 0) { + throw new FlvFormatException("Not a boolean: " + type); + } + set((int)readDouble(in) - filePositionDelta); } } @@ -109,7 +139,6 @@ public class FlvMetadata { FileOffsetAttr offset = new FileOffsetAttr(null); DoubleAttr timestamp = new DoubleAttr(null); - super.write(out); out.write(3); // Variable Array @@ -127,6 +156,43 @@ public class FlvMetadata { writeFlvVariableArrayEnd(out); } + @Override + public void read(InputStream in) throws FlvFormatException, IOException { + + FileOffsetAttr offset = new FileOffsetAttr(null); + DoubleAttr timestamp = new DoubleAttr(null); + + int type = in.read(); + if (type != 3) { + throw new FlvFormatException("Not an object: " + type); + } + if (!in.markSupported()) { + throw new FlvFormatException("Need redahead"); + } + List filePositions = null; + List times = null; + while (!isLookingAtEnd(in)) { + String propName = readString(in); + if ("filepositions".equals(propName)) { + filePositions = readValueArray(in, offset); + } else if ("times".equals(propName)) { + times = readValueArray(in, timestamp); + } + } + readObjectEnd(in); + if (times != null && filePositions != null) { + if (times.size() != filePositions.size()) { + throw new FlvFormatException("positions and times need to match up " + times.size() + " v.s. " + filePositions.size()); + } + keyframes.clear(); + for (int i = 0; i < times.size(); i++) { + addVideoFrame(filePositions.get(i), Math.round(times.get(i).floatValue()), true); + } + } else { + System.out.println("Missing filepositions (" + filePositions + ") or times (" + times + ")"); + } + } + @Override public boolean isPresent() { return !value.isEmpty(); @@ -157,6 +223,7 @@ public class FlvMetadata { private DoubleAttr audiosize = new DoubleAttr("audiosize"); private DoubleAttr audiodatarate = new DoubleAttr("audiodatarate"); + private DoubleAttr audiodelay = new DoubleAttr("audiodelay"); private DoubleAttr audiocodecid = new DoubleAttr("audiocodecid"); private DoubleAttr audiosamplerate = new DoubleAttr("audiosamplerate"); @@ -198,6 +265,7 @@ public class FlvMetadata { attrs.add(audiosize); attrs.add(audiodatarate); + attrs.add(audiodelay); attrs.add(audiocodecid); attrs.add(audiosamplerate); @@ -224,6 +292,10 @@ public class FlvMetadata { return i; } + public void clearKeyframes() { + keyframes.clear(); + } + public void addVideoFrame(int filePos, int timestamp, boolean isKeyframe) { hasVideo.set(true); @@ -243,8 +315,9 @@ public class FlvMetadata { } public int calculateLength() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeOnMetadata(baos, 0); + writeScriptDataObject(baos); return baos.size(); } @@ -253,47 +326,28 @@ public class FlvMetadata { this.filePositionDelta = fileOffsetDelta; } - public void writeOnMetadata(OutputStream out, int size) throws IOException { - - out.write(18); // Script data tag - writeUint24(out, size - 15); //data size - writeUint32(out, 0); // timestamp - + /** + * @param out + * @throws IOException + */ + public void writeScriptDataObject(OutputStream out) throws IOException { // ScriptDataObject - out.write(2); - writeFlvEcmaArray(out, "onMetaData", length()); + out.write(2); // string + writeFlvString(out, "onMetaData"); + writeFlvEcmaArray(out, length()); for (Attr a : attrs) { if (a.isPresent()) { + if (a.name != null) { + writeFlvString(out, a.name); + } a.write(out); } } writeFlvVariableArrayEnd(out); } - void writeStringAttr(OutputStream out, String name, String value) throws IOException { + void writeDouble(OutputStream out, double value) throws IOException { - if (name != null) { - writeFlvString(out, name); - } - out.write(2); // DataString - writeFlvString(out, value); - } - - void writeBoolAttr(OutputStream out, String name, boolean value) throws IOException { - - if (name != null) { - writeFlvString(out, name); - } - out.write(1); // Bool - out.write(value ? 1 : 0); - } - - void writeDoubleAttr(OutputStream out, String name, double value) throws IOException { - - if (name != null) { - writeFlvString(out, name); - } - out.write(0); // NUMBER long x = Double.doubleToLongBits(value); writeUint64(out, x); } @@ -305,19 +359,11 @@ public class FlvMetadata { out.write(bytes); } - private void writeFlvEcmaArray(OutputStream out, String name, int len) throws IOException { - - writeFlvString(out, name); + private void writeFlvEcmaArray(OutputStream out, int len) throws IOException { out.write(8); // ECMAArray writeUint32(out, len); } -/* - private void writeFlvVariableArrayStart(OutputStream out, String name) throws IOException { - writeFlvString(out, name); - out.write(3); // Variable Array - } -*/ private void writeFlvVariableArrayEnd(OutputStream out) throws IOException { out.write(0); out.write(0); @@ -364,6 +410,14 @@ public class FlvMetadata { out.write((value >> 0) & 0xff); } + void writeTimestamp(OutputStream out, int value) throws IOException { + + out.write((value >> 16) & 0xff); + out.write((value >> 8) & 0xff); + out.write((value >> 0) & 0xff); + out.write((value >> 24) & 0xff); + } + private static class Keyframe { private int fileOffset; @@ -393,6 +447,14 @@ public class FlvMetadata { } + /** + * Takes all the values from the parameter "theirs". + * Corresponding values in this are overwritten. + * Other values in this are kept. + * The typical purpose is to fill in some missing metadata. + * + * @param other + */ public void merge(FlvMetadata other) { for (int i = 0; i < attrs.size(); i++) { @@ -423,12 +485,147 @@ public class FlvMetadata { } - public void read(byte[] buf, int offset, int len) { - if (buf[offset] != 2) { //not string - return; + public void read(InputStream in) throws IOException { + + try { + int type = in.read(); + if (type != 2) { + throw new FlvFormatException("Not a string: " + type); + } + String name = readString(in); + if (!"onMetaData".equalsIgnoreCase(name)) { + throw new FlvFormatException("Not \"onMetaData\": " + name); + } + type = in.read(); + if (type != 8) { + throw new FlvFormatException("Not an ECMA array: " + type); + } + int arrayLen = readEcmaArrayHeader(in); + for (int i = 0; i < arrayLen; i++) { + readProperty(in); + } + readEcmaArrayFooter(in); + } catch (FlvFormatException e) { + LoggerFactory.getLogger(getClass()).error("invalid", e); } } + protected String readString(InputStream in) throws IOException { + int len = readUint16(in); + byte[] s = new byte[len]; + in.read(s); + return new String(s, "UTF-8"); + } + + protected boolean readBoolean(InputStream in) throws IOException { + int val = in.read(); + return val != 0; + } + + protected double readDouble(InputStream in) throws IOException { + long val = readUint64(in); + return Double.longBitsToDouble(val); + } + + protected long readUint64(InputStream in) throws IOException { + long ch1 = in.read(); + long ch2 = in.read(); + long ch3 = in.read(); + long ch4 = in.read(); + long ch5 = in.read(); + long ch6 = in.read(); + long ch7 = in.read(); + long ch8 = in.read(); + return ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0)); + } + + protected int readUint32(InputStream in) throws IOException { + int ch1 = in.read(); + int ch2 = in.read(); + int ch3 = in.read(); + int ch4 = in.read(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + protected int readUint24(InputStream in) throws IOException { + int ch1 = in.read(); + int ch2 = in.read(); + int ch3 = in.read(); + return ((ch1 << 16) + (ch2 << 8) + (ch3 << 0)); + } + + protected int readUint16(InputStream in) throws IOException { + int ch1 = in.read(); + int ch2 = in.read(); + return ((ch1 << 8) + (ch2 << 0)); + } + + protected int readTimestamp(InputStream in) throws IOException { + int ch1 = in.read(); + int ch2 = in.read(); + int ch3 = in.read(); + int ch4 = in.read(); + return ((ch4 << 24) + (ch1 << 16) + (ch2 << 8) + (ch3 << 0)); + } + + protected int readEcmaArrayHeader(InputStream in) throws IOException { + int len = readUint32(in); + return len; + } + + protected void readEcmaArrayFooter(InputStream in) throws FlvFormatException, IOException { + readObjectEnd(in); + } + + protected void readObjectEnd(InputStream in) throws FlvFormatException, IOException { + byte[] word = new byte[3]; + in.read(word); + if (word[0] != 0 || word[1] != 0 || word[2] != 9) { + throw new FlvFormatException("Not at end: " + word[0] + ", " + word[1] + ", " + word[2]); + } + } + + protected boolean isLookingAtEnd(InputStream in) throws IOException { + byte[] word = new byte[3]; + in.mark(word.length); + in.read(word); + in.reset(); + return word[0] == 0 && word[1] == 0 && word[2] == 9; + } + + protected void readProperty(InputStream in) throws FlvFormatException, IOException { + + String name = readString(in); + Attr attr = findAttrByName(name); + if (attr == null) { + throw new FlvFormatException("Unknown attribute: " + name); + } + attr.read(in); + } + + private Attr findAttrByName(String name) { + for (Attr a : this.attrs) { + if (name.equals(a.name)) { + return a; + } + } + return null; + } + + protected List readValueArray(InputStream in, Attr elementAttr) throws FlvFormatException, IOException { + int type = in.read(); + if (type != 10) { + throw new FlvFormatException("Not a value array: " + type); + } + int len = readUint32(in); + ArrayList list = new ArrayList(len); + for (int i = 0; i < len; i++) { + elementAttr.read(in); + list.add(elementAttr.value); + } + return list; + } + public void setVideoCodecId(int videocodecid) { this.videocodecid.set(Double.valueOf(videocodecid)); @@ -444,5 +641,46 @@ public class FlvMetadata { this.framerate.set(framerate); } - + + + public int getWidth() { + return width.isPresent() ? width.value.intValue() : 0; + } + + public int getHeight() { + return height.isPresent() ? height.value.intValue() : 0; + } + + + @Override + public boolean equals(Object o) { + if (o == null || !this.getClass().equals(o.getClass())) { + return false; + } + FlvMetadata other = (FlvMetadata)o; + for (int i = 0; i < attrs.size(); i++) { + Attr their = other.attrs.get(i); + Attr our = this.attrs.get(i); + if (our.isPresent() && their.isPresent()) { + if (!our.value.equals(their.value)) { + return false; + } + } + if (our.isPresent() != their.isPresent()) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int x = 0; + for (Attr a : attrs) { + x *= 13; + x += a.present ? a.value.hashCode() : 7; + } + return x; + } + } diff --git a/src/main/java/org/forkalsrud/album/video/MovieCoder.java b/src/main/java/org/forkalsrud/album/video/MovieCoder.java index 84c5819..133d663 100644 --- a/src/main/java/org/forkalsrud/album/video/MovieCoder.java +++ b/src/main/java/org/forkalsrud/album/video/MovieCoder.java @@ -151,7 +151,6 @@ public class MovieCoder { private final int chunkSize = 4 * 65536; private File file; - private Thumbnail thumbnail; private Dimension targetSize; private ArrayList listeners = new ArrayList(); private Chunk currentChunk = null; @@ -163,15 +162,11 @@ public class MovieCoder { public EncodingProcess(File file, Thumbnail thumbnail, Dimension size) { this.file = file; - this.thumbnail = thumbnail; this.targetSize = size; this.dbKey = key(file, targetSize); - FlvMetadata meta = new FlvMetadata(); - meta.setDuration(thumbnail.getDuration()); - meta.setVideoCodecId(2); - meta.setAudioCodecId(2); - meta.setFramerate(15); // TODO (knut 10 JUL 2011) Need to make sure the ffmpeg args match - this.filter = new FlvFilter(this, meta); + FlvMetadata extraMeta = new FlvMetadata(); + extraMeta.setDuration(thumbnail.getDuration()); + this.filter = new FlvFilter(this, extraMeta); } /* @@ -233,7 +228,10 @@ public class MovieCoder { endLastChunk(); // Generate header again to get updated metadata // TODO (knut 09 JUL 2011) Figure out why the generated header doesn't work - filter.generateHeader(); + // TODO (knut 30 JUL 2011) Add some metadata about the file in the first chunk of the file + // size of first chunk, size of last chunk, number number of chunks between first-last, size of chunks between first-last + // This will be used to determine the right chunk number from a given byte offset in a range oritented HTTP request + filter.generateHeaderChunk(); } catch (Exception e) { log.error("uh?", e); movieDb.delete(dbKey); @@ -246,17 +244,10 @@ public class MovieCoder { } @Override - public void writeHeader(byte[]... data) { - int len = 0; - for (byte[] ba : data) { - len += ba.length; - } + public void writeHeader(byte[] data) { + int len = data.length; Chunk chunk0 = new Chunk(len, 0); - int i = 0; - for (byte[] ba : data) { - System.arraycopy(ba, 0, chunk0.bits, i, ba.length); - i += ba.length; - } + chunk0.bits = data; movieDb.store(dbKey, 0, chunk0); notifyListeners(0, false); } diff --git a/src/test/java/org/forkalsrud/album/video/FlvFilterTest.java b/src/test/java/org/forkalsrud/album/video/FlvFilterTest.java index 9272ef6..db20f99 100644 --- a/src/test/java/org/forkalsrud/album/video/FlvFilterTest.java +++ b/src/test/java/org/forkalsrud/album/video/FlvFilterTest.java @@ -18,12 +18,8 @@ public class FlvFilterTest extends TestCase { FlvFilter.FlvReceiver receiver = new FlvFilter.FlvReceiver() { @Override - public void writeHeader(byte[]... data) { - int len = 0; - for (byte[] ba : data) { - len += ba.length; - } - buf.append("HEADER[" + len + "]BODY[" + bodyLen + "]"); + public void writeHeader(byte[] data) { + buf.append("HEADER[" + data.length + "]BODY[" + bodyLen + "]"); } @Override @@ -33,8 +29,8 @@ public class FlvFilterTest extends TestCase { }; FlvFilter os = new FlvFilter(receiver, new FlvMetadata()); IOUtils.copy(is, os); - os.generateHeader(); - assertEquals("HEADER[252]BODY[0]HEADER[579]BODY[911684]", buf.toString()); + os.generateHeaderChunk(); + assertEquals("HEADER[320]BODY[0]HEADER[797]BODY[911684]", buf.toString()); } public void testFoo() { @@ -44,8 +40,4 @@ public class FlvFilterTest extends TestCase { System.out.println(Long.toHexString(Double.doubleToLongBits(150d))); } - - - - } diff --git a/src/test/java/org/forkalsrud/album/video/FlvMetadataTest.java b/src/test/java/org/forkalsrud/album/video/FlvMetadataTest.java index 0f8abab..5380e7d 100644 --- a/src/test/java/org/forkalsrud/album/video/FlvMetadataTest.java +++ b/src/test/java/org/forkalsrud/album/video/FlvMetadataTest.java @@ -1,11 +1,24 @@ package org.forkalsrud.album.video; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + import org.forkalsrud.album.exif.Dimension; import junit.framework.TestCase; public class FlvMetadataTest extends TestCase { + private ByteArrayOutputStream out = new ByteArrayOutputStream(); + private ByteArrayInputStream in; + FlvMetadata md0 = new FlvMetadata(); + + private void flip() { + in = new ByteArrayInputStream(out.toByteArray()); + out.reset(); + } + public void testMerge() throws Exception { FlvMetadata md1 = new FlvMetadata(); @@ -19,4 +32,76 @@ public class FlvMetadataTest extends TestCase { assertEquals(Double.valueOf("6"), md2.get("height")); } + public void testReadWriteDouble() throws Exception { + + double value = 3.14; + md0.writeDouble(out, value); + flip(); + assertEquals(value, md0.readDouble(in)); + } + + public void testReadWriteInt() throws Exception { + int value = 45; + // uint16 + md0.writeUint16(out, value); + flip(); + assertEquals(value, md0.readUint16(in)); + + // uint24 + md0.writeUint24(out, value); + flip(); + assertEquals(value, md0.readUint24(in)); + + // uint32 + md0.writeUint32(out, value); + flip(); + assertEquals(value, md0.readUint32(in)); + + // timestamp + md0.writeTimestamp(out, value); + flip(); + assertEquals(value, md0.readTimestamp(in)); + } + + public void testReadWriteString() throws Exception { + String value = "awsome"; + md0.writeFlvString(out, value); + flip(); + assertEquals(value, md0.readString(in)); + } + + public void testWriteReadMetadata() throws Exception { + + FlvMetadata md1 = new FlvMetadata(); + md1.setDuration("3.14"); + md1.setDimension(new Dimension(5, 6)); + + md1.writeScriptDataObject(out); + flip(); + + FlvMetadata md2 = new FlvMetadata(); + md2.read(in); + + assertEquals("3.14", md2.get("duration").toString()); + assertEquals(5, md2.getWidth()); + assertEquals(6, md2.getHeight()); + } + + + public void testReadWriteMetadata() throws Exception { + + InputStream is = getClass().getResourceAsStream("/VideoAd.flv"); + is.read(new byte[24]); // offset to start of scriptdata + + FlvMetadata md1 = new FlvMetadata(); + md1.read(is); + + md1.writeScriptDataObject(out); + flip(); + + FlvMetadata md2 = new FlvMetadata(); + md2.read(in); + + assertEquals(md1, md2); + } }