/*
* ScreenVideoPacket.java
* Transform
*
* Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.video;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.coder.Copyable;
import com.flagstone.transform.coder.SWFDecoder;
import com.flagstone.transform.coder.SWFEncoder;
/**
* The ScreenVideoPacket class is used to encode or decode a frame of video data
* using Macromedia's ScreenVideo format.
*/
public final class ScreenPacket implements Copyable<ScreenPacket> {
/** Multiplier for the encoded value representing the block width. */
private static final int PIXELS_PER_BLOCK = 16;
/** Is this frame a key frame with blocks for the entire image. */
private boolean keyFrame;
/** The width of each block. */
private int blockWidth;
/** The height of each block. */
private int blockHeight;
/** The width of the image. */
private int imageWidth;
/** The height of the image. */
private int imageHeight;
/** List of blocks that make up the image. */
private List<ImageBlock> imageBlocks;
/**
* Decode a screen packet from a block of data.
* @param data the encoded screen packet data.
* @throws IOException if the data cannot be decoded.
*/
public ScreenPacket(final byte[] data) throws IOException {
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final SWFDecoder coder = new SWFDecoder(stream);
int info = coder.readByte();
keyFrame = (info & Coder.NIB1) != 0;
info = (coder.readByte() << Coder.TO_UPPER_BYTE) + coder.readByte();
blockWidth = (((info & Coder.NIB3) >> Coder.ALIGN_NIB3) + 1)
* PIXELS_PER_BLOCK;
imageWidth = info & Coder.LOWEST12;
info = (coder.readByte() << Coder.TO_UPPER_BYTE) + coder.readByte();
blockHeight = (((info & Coder.NIB3) >> Coder.ALIGN_NIB3) + 1)
* PIXELS_PER_BLOCK;
imageHeight = info & Coder.LOWEST12;
final int columns = imageWidth / blockWidth
+ ((imageWidth % blockWidth > 0) ? 1 : 0);
final int rows = imageHeight / blockHeight
+ ((imageHeight % blockHeight > 0) ? 1 : 0);
int height = imageHeight;
int width = imageWidth;
imageBlocks = new ArrayList<ImageBlock>();
ImageBlock block;
int length;
for (int i = 0; i < rows; i++, height -= blockHeight) {
for (int j = 0; j < columns; j++, width -= blockWidth) {
length = (coder.readByte() << Coder.TO_UPPER_BYTE)
+ coder.readByte();
if (length == 0) {
block = new ImageBlock(0, 0, new byte[]{});
} else {
final int dataHeight = (height < blockHeight) ? height
: blockHeight;
final int dataWidth = (width < blockWidth) ? width
: blockWidth;
block = new ImageBlock(dataWidth, dataHeight, coder
.readBytes(new byte[length]));
}
imageBlocks.add(block);
}
}
}
/**
* Create a ScreenPacket with no image blocks.
*/
public ScreenPacket() {
imageBlocks = new ArrayList<ImageBlock>();
}
/**
* Creates a ScreenVideoPacket.
*
* @param key
* indicates whether the packet contains a key frame.
* @param imgWidth
* the width of the frame.
* @param imgHeight
* the height of the frame.
* @param blkWidth
* the width of the blocks that make up the frame.
* @param blkHeight
* the height of the blocks that make up the frame.
* @param blocks
* the array of ImageBlocks that make up the frame.
*/
public ScreenPacket(final boolean key, final int imgWidth,
final int imgHeight, final int blkWidth, final int blkHeight,
final List<ImageBlock> blocks) {
setKeyFrame(key);
setImageWidth(imgWidth);
setImageHeight(imgHeight);
setBlockWidth(blkWidth);
setBlockHeight(blkHeight);
setImageBlocks(blocks);
}
/**
* Creates and initialises a ScreenPacket object using the values copied
* from another ScreenPacket object.
*
* @param object
* a ScreenPacket object from which the values will be
* copied.
*/
public ScreenPacket(final ScreenPacket object) {
keyFrame = object.keyFrame;
blockWidth = object.blockWidth;
blockHeight = object.blockHeight;
imageWidth = object.imageWidth;
imageHeight = object.imageHeight;
imageBlocks = new ArrayList<ImageBlock>(object.imageBlocks.size());
for (final ImageBlock block : object.imageBlocks) {
imageBlocks.add(block.copy());
}
}
/**
* Add an image block to the array that make up the frame.
*
* @param block
* an ImageBlock. Must not be null.
* @return this object.
*/
public ScreenPacket add(final ImageBlock block) {
imageBlocks.add(block);
return this;
}
/**
* Does the packet contains a key frame.
*
* @return true if the packet is a key frame.
*/
public boolean isKeyFrame() {
return keyFrame;
}
/**
* Sets whether the frame is a key frame (true) or normal one (false).
*
* @param key
* a boolean value indicating whether the frame is key (true) or
* normal (false.
*/
public void setKeyFrame(final boolean key) {
keyFrame = key;
}
/**
* Get the width of the frame in pixels.
*
* @return the frame width.
*/
public int getImageWidth() {
return imageWidth;
}
/**
* Sets the width of the frame.
*
* @param width
* the width of the frame in pixels.
*/
public void setImageWidth(final int width) {
imageWidth = width;
}
/**
* Get the height of the frame in pixels.
*
* @return the image height.
*/
public int getImageHeight() {
return imageHeight;
}
/**
* Set the height of the frame in pixels.
*
* @param height the image height.
*/
public void setImageHeight(final int height) {
imageHeight = height;
}
/**
* Get the width of the blocks in pixels.
*
* @return the block width.
*/
public int getBlockWidth() {
return blockWidth;
}
/**
* Sets the width of the image blocks.
*
* @param width
* the width of the blocks in pixels.
*/
public void setBlockWidth(final int width) {
blockWidth = width;
}
/**
* Get the height of the blocks in pixels.
*
* @return the block width.
*/
public int getBlockHeight() {
return blockHeight;
}
/**
* Sets the height of the image blocks.
*
* @param height
* the height of the blocks in pixels.
*/
public void setBlockHeight(final int height) {
blockHeight = height;
}
/**
* Get the image blocks that have changed in this frame.
*
* @return the list of image blocks that make up the frame.
*/
public List<ImageBlock> getImageBlocks() {
return imageBlocks;
}
/**
* Set the image blocks that have changed in this frame. If this is a key
* frame then all image blocks are displayed.
*
* @param blocks
* the array of image blocks. Must not be null.
*/
public void setImageBlocks(final List<ImageBlock> blocks) {
imageBlocks = new ArrayList<ImageBlock>(blocks);
}
/** {@inheritDoc} */
@Override
public ScreenPacket copy() {
return new ScreenPacket(this);
}
/**
* Encode this ScreenPacket.
* @return the data representing the encoded image blocks.
* @throws IOException if there is an error encoding the blocks.
*/
public byte[] encode() throws IOException {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final SWFEncoder coder = new SWFEncoder(stream);
int bits = keyFrame ? Coder.BIT4 : Coder.BIT5;
bits |= Coder.BIT0 | Coder.BIT1;
coder.writeByte(bits);
int word = ((blockWidth / PIXELS_PER_BLOCK) - 1) << Coder.TO_UPPER_NIB;
word |= imageWidth & Coder.LOWEST12;
coder.writeByte(word >> Coder.TO_LOWER_BYTE);
coder.writeByte(word);
word = ((blockHeight / PIXELS_PER_BLOCK) - 1) << Coder.TO_UPPER_NIB;
word |= imageHeight & Coder.LOWEST12;
coder.writeByte(word >> Coder.TO_LOWER_BYTE);
coder.writeByte(word);
byte[] blockData;
for (final ImageBlock block : imageBlocks) {
if (block.isEmpty()) {
coder.writeShort(0);
} else {
blockData = block.getBlock();
coder.writeByte(blockData.length >> Coder.TO_LOWER_BYTE);
coder.writeByte(blockData.length);
coder.writeBytes(blockData);
}
}
return stream.toByteArray();
}
}