All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.session.mcap.encoding.LZ4FrameEncoder Maven / Gradle / Ivy

package us.ihmc.scs2.session.mcap.encoding;

import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.xxhash.XXHash32;
import net.jpountz.xxhash.XXHashFactory;
import us.ihmc.scs2.session.mcap.encoding.LZ4FrameDecoder.BD;
import us.ihmc.scs2.session.mcap.encoding.LZ4FrameDecoder.BLOCKSIZE;
import us.ihmc.scs2.session.mcap.encoding.LZ4FrameDecoder.FLG;
import us.ihmc.scs2.session.mcap.encoding.LZ4FrameDecoder.FrameInfo;

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

/**
 * This class is a modified version of the original LZ4FrameOutputStream from the lz4-java project.
 * 

* This version allows to decode a byte array into another byte array, without using any {@link OutputStream}. *

*/ public class LZ4FrameEncoder { static final FLG.Bits[] DEFAULT_FEATURES = new FLG.Bits[] {FLG.Bits.BLOCK_INDEPENDENCE}; static final String CLOSED_STREAM = "The stream is already closed"; private final LZ4Compressor compressor; private final XXHash32 checksum; private final ByteBuffer blockBuffer; // Buffer for uncompressed input data private final byte[] compressedBuffer; // Only allocated once so it can be reused private final int maxBlockSize; private final long knownSize; private final ByteBuffer intLEBuffer = ByteBuffer.allocate(LZ4FrameDecoder.INTEGER_BYTES).order(ByteOrder.LITTLE_ENDIAN); private FrameInfo frameInfo = null; /** * Creates a new encoder that will compress data of unknown size using the LZ4 algorithm. * * @param blockSize the BLOCKSIZE to use * @param bits a set of features to use * @see #LZ4FrameEncoder(BLOCKSIZE, long, FLG.Bits...) */ public LZ4FrameEncoder(BLOCKSIZE blockSize, FLG.Bits... bits) { this(blockSize, -1L, bits); } /** * Creates a new encoder that will compress data using using fastest instances of {@link LZ4Compressor} and {@link XXHash32}. * * @param blockSize the BLOCKSIZE to use * @param knownSize the size of the uncompressed data. A value less than zero means unknown. * @param bits a set of features to use */ public LZ4FrameEncoder(BLOCKSIZE blockSize, long knownSize, FLG.Bits... bits) { this(blockSize, knownSize, LZ4Factory.fastestInstance().fastCompressor(), XXHashFactory.fastestInstance().hash32(), bits); } /** * Creates a new encoder that will compress data using the specified instances of {@link LZ4Compressor} and {@link XXHash32}. * * @param blockSize the BLOCKSIZE to use * @param knownSize the size of the uncompressed data. A value less than zero means unknown. * @param compressor the {@link LZ4Compressor} instance to use to compress data * @param checksum the {@link XXHash32} instance to use to check data for integrity * @param bits a set of features to use */ public LZ4FrameEncoder(BLOCKSIZE blockSize, long knownSize, LZ4Compressor compressor, XXHash32 checksum, FLG.Bits... bits) { this.compressor = compressor; this.checksum = checksum; frameInfo = new FrameInfo(new FLG(FLG.DEFAULT_VERSION, bits), new BD(blockSize)); maxBlockSize = frameInfo.getBD().getBlockMaximumSize(); blockBuffer = ByteBuffer.allocate(maxBlockSize).order(ByteOrder.LITTLE_ENDIAN); compressedBuffer = new byte[this.compressor.maxCompressedLength(maxBlockSize)]; if (frameInfo.getFLG().isEnabled(FLG.Bits.CONTENT_SIZE) && knownSize < 0) { throw new IllegalArgumentException("Known size must be greater than zero in order to use the known size feature"); } this.knownSize = knownSize; } /** * Creates a new encoder that will compress data using the LZ4 algorithm. The block independence flag is set, and none of the other flags are * set. * * @param blockSize the BLOCKSIZE to use * @see #LZ4FrameEncoder(BLOCKSIZE, FLG.Bits...) */ public LZ4FrameEncoder(BLOCKSIZE blockSize) { this(blockSize, DEFAULT_FEATURES); } /** * Creates a new encoder that will compress data using the LZ4 algorithm with 4-MB blocks. * * @see #LZ4FrameEncoder(BLOCKSIZE) */ public LZ4FrameEncoder() { this(BLOCKSIZE.SIZE_4MB); } public byte[] encode(byte[] in, byte[] out) { return encode(in, 0, in.length, out, 0); } public byte[] encode(byte[] in, int inOffset, int inLength, byte[] out, int outOffset) { ByteBuffer resultBuffer = encode(ByteBuffer.wrap(in, inOffset, inLength), out == null ? null : ByteBuffer.wrap(out, outOffset, out.length - outOffset)); if (resultBuffer == null) return null; byte[] result = new byte[resultBuffer.remaining()]; resultBuffer.get(result); return result; } public ByteBuffer encode(ByteBuffer in, ByteBuffer out) { return encode(in, 0, in.remaining(), out, 0); } public ByteBuffer encode(ByteBuffer in, int inOffset, int inLength, ByteBuffer out, int outOffset) { int limitPrev = in.limit(); in.position(inOffset); in.limit(inOffset + inLength); if (out != null) { out.order(ByteOrder.LITTLE_ENDIAN); out.position(outOffset); } try { if (out != null) { writeHeader(out); ensureNotFinished(); // while b will fill the buffer while (in.remaining() > blockBuffer.remaining()) { int sizeWritten = blockBuffer.remaining(); // fill remaining space in buffer blockBuffer.put(in.slice(in.position(), sizeWritten)); in.position(in.position() + sizeWritten); writeBlock(out); } blockBuffer.put(in); writeBlock(out); writeEndMark(out); out.flip(); return out; } else { ByteBuffer whenOutIsNull = ByteBuffer.allocate(inLength + LZ4FrameDecoder.LZ4_MAX_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); writeHeader(whenOutIsNull); ensureNotFinished(); while (in.remaining() > blockBuffer.remaining()) { int sizeWritten = blockBuffer.remaining(); // fill remaining space in buffer blockBuffer.put(in.slice(in.position(), sizeWritten)); in.position(in.position() + sizeWritten); whenOutIsNull = ensureCapacity(whenOutIsNull, blockBuffer.limit()); writeBlock(whenOutIsNull); } blockBuffer.put(in); whenOutIsNull = ensureCapacity(whenOutIsNull, blockBuffer.limit()); writeBlock(whenOutIsNull); whenOutIsNull = ensureCapacity(whenOutIsNull, 2 * LZ4FrameDecoder.INTEGER_BYTES); writeEndMark(whenOutIsNull); whenOutIsNull.flip(); return whenOutIsNull; } } finally { in.limit(limitPrev); } } private static ByteBuffer ensureCapacity(ByteBuffer buffer, int remainingNeeded) { if (buffer.remaining() >= remainingNeeded) return buffer; ByteBuffer extended = ByteBuffer.allocate(buffer.capacity() + remainingNeeded); extended.order(ByteOrder.LITTLE_ENDIAN); buffer.flip(); extended.put(buffer); return extended; } /** * Writes the magic number and frame descriptor to the underlying {@link OutputStream}. */ private void writeHeader(ByteBuffer out) { if (out.remaining() < LZ4FrameDecoder.LZ4_MAX_HEADER_LENGTH) { throw new IllegalArgumentException("The provided buffer is too small to write the header"); } out.order(ByteOrder.LITTLE_ENDIAN); out.putInt(LZ4FrameDecoder.MAGIC); out.put(frameInfo.getFLG().toByte()); out.put(frameInfo.getBD().toByte()); if (frameInfo.isEnabled(FLG.Bits.CONTENT_SIZE)) { out.putLong(knownSize); } // compute checksum on all descriptor fields final int hash = (checksum.hash(out.array(), LZ4FrameDecoder.INTEGER_BYTES, out.position() - LZ4FrameDecoder.INTEGER_BYTES, 0) >> 8) & 0xFF; out.put((byte) hash); } /** * Compresses buffered data, optionally computes an XXHash32 checksum, and writes the result to the buffer. */ private void writeBlock(ByteBuffer out) { if (blockBuffer.position() == 0) { return; } // Make sure there's no stale data Arrays.fill(compressedBuffer, (byte) 0); if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { frameInfo.updateStreamHash(blockBuffer.array(), 0, blockBuffer.position()); } int compressedLength = compressor.compress(blockBuffer.array(), 0, blockBuffer.position(), compressedBuffer, 0); final byte[] bufferToWrite; final int compressMethod; // Store block uncompressed if compressed length is greater (incompressible) if (compressedLength >= blockBuffer.position()) { compressedLength = blockBuffer.position(); bufferToWrite = Arrays.copyOf(blockBuffer.array(), compressedLength); compressMethod = LZ4FrameDecoder.LZ4_FRAME_INCOMPRESSIBLE_MASK; } else { bufferToWrite = compressedBuffer; compressMethod = 0; } // Write content out.putInt(compressedLength | compressMethod); out.put(bufferToWrite, 0, compressedLength); // TODO bufferToWrite is a copy, we could avoid it // Calculate and write block checksum if (frameInfo.isEnabled(FLG.Bits.BLOCK_CHECKSUM)) { out.putInt(checksum.hash(bufferToWrite, 0, compressedLength, 0)); } blockBuffer.rewind(); } /** * Similar to the {@link #writeBlock(ByteBuffer)} method. Writes a 0-length block (without block checksum) to signal the end * of the block stream. */ private void writeEndMark(ByteBuffer out) { out.order(ByteOrder.LITTLE_ENDIAN); out.putInt(0); if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { out.putInt(0, frameInfo.currentStreamHash()); } frameInfo.finish(); } /** * A simple state check to ensure the stream is still open. */ private void ensureNotFinished() { if (frameInfo.isFinished()) { throw new IllegalStateException(CLOSED_STREAM); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy