package org.jaudiotagger.tag.id3; import org.jaudiotagger.audio.mp3.MPEGFrameHeader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.logging.Logger; import java.util.logging.Level; /** * Performs unsynchronization and synchronization tasks on a buffer. * <p/> * Is currently required for V23Tags and V24Frames */ public class ID3Unsynchronization { //Logger public static Logger logger = Logger.getLogger("org.jaudiotagger.tag.id3"); /** * Check if a byte array will require unsynchronization before being written as a tag. * If the byte array contains any $FF $E0 bytes, then it will require unsynchronization. * * @param abySource the byte array to be examined * @return true if unsynchronization is required, false otherwise */ public static boolean requiresUnsynchronization(byte[] abySource) { for (int i = 0; i < abySource.length - 1; i++) { if (((abySource[i] & MPEGFrameHeader.SYNC_BYTE1) == MPEGFrameHeader.SYNC_BYTE1) && ((abySource[i + 1] & MPEGFrameHeader.SYNC_BYTE2) == MPEGFrameHeader.SYNC_BYTE2)) { if (logger.isLoggable(Level.FINEST)) { //logger.finest("Unsynchronisation required found bit at:" + i); } return true; } } return false; } /** * Unsynchronize an array of bytes, this should only be called if the decision has already been made to * unsynchronize the byte array * <p/> * In order to prevent a media player from incorrectly interpreting the contents of a tag, all $FF bytes * followed by a byte with value >=224 must be followed by a $00 byte (thus, $FF $F0 sequences become $FF $00 $F0). * Additionally because unsynchronisation is being applied any existing $FF $00 have to be converted to * $FF $00 $00 * * @param abySource a byte array to be unsynchronized * @return a unsynchronized representation of the source */ public static byte[] unsynchronize(byte[] abySource) { ByteArrayInputStream input = new ByteArrayInputStream(abySource); ByteArrayOutputStream output = new ByteArrayOutputStream(abySource.length); int count = 0; while (input.available() > 0) { int firstByte = input.read(); count++; output.write(firstByte); if ((firstByte & MPEGFrameHeader.SYNC_BYTE1) == MPEGFrameHeader.SYNC_BYTE1) { // if byte is $FF, we must check the following byte if there is one if (input.available() > 0) { input.mark(1); // remember where we were, if we don't need to unsynchronize int secondByte = input.read(); if ((secondByte & MPEGFrameHeader.SYNC_BYTE2) == MPEGFrameHeader.SYNC_BYTE2) { // we need to unsynchronize here if (logger.isLoggable(Level.FINEST)) { //logger.finest("Writing unsynchronisation bit at:" + count); } output.write(0); } else if (secondByte == 0) { // we need to unsynchronize here if (logger.isLoggable(Level.FINEST)) { //logger.finest("Inserting zero unsynchronisation bit at:" + count); } output.write(0); } input.reset(); } } } // if we needed to unsynchronize anything, and this tag ends with 0xff, we have to append a zero byte, // which will be removed on de-unsynchronization later if ((abySource[abySource.length - 1] & MPEGFrameHeader.SYNC_BYTE1) == MPEGFrameHeader.SYNC_BYTE1) { //logger.finest("Adding unsynchronisation bit at end of stream"); output.write(0); } return output.toByteArray(); } /** * Synchronize an array of bytes, this should only be called if it has been determined the tag is unsynchronised * <p/> * Any patterns of the form $FF $00 should be replaced by $FF * * @param source a ByteBuffer to be unsynchronized * @return a synchronized representation of the source */ /* public static ByteBuffer synchronize(ByteBuffer source) { long start = System.nanoTime(); int bufferSize = source.limit(); ByteArrayOutputStream oBAOS = new ByteArrayOutputStream(bufferSize); int position = 0; while (position < bufferSize) { int byteValue = source.get(); position ++; oBAOS.write(byteValue); if ((byteValue & MPEGFrameHeader.SYNC_BYTE1) == MPEGFrameHeader.SYNC_BYTE1) { // we are skipping if $00 byte but check not an end of stream if (position < bufferSize) { int unsyncByteValue = source.get(); position++; //If its the null byte we just ignore it if (unsyncByteValue != 0) { oBAOS.write(unsyncByteValue); } } } } long time = System.nanoTime() - start; ByteBuffer bb = ByteBuffer.wrap(oBAOS.toByteArray()); System.out.printf("Took %6.3f ms, was %d bytes, now %,d bytes%n", time/1e6, source.limit(), bb.limit()); return bb; } */ /** * Synchronize an array of bytes, this should only be called if it has been determined the tag is unsynchronised * <p/> * Any patterns of the form $FF $00 should be replaced by $FF * * @param source a ByteBuffer to be unsynchronized * @return a synchronized representation of the source */ /* public static ByteBuffer synchronize(ByteBuffer source) { long start = System.nanoTime(); int bufferSize = source.limit(); ByteBuffer output = ByteBuffer.allocate(bufferSize); int position = 0; int offset = 0; int length = 0; while (position < bufferSize) { int byteValue = source.get(); position++; length++; if ((byteValue & MPEGFrameHeader.SYNC_BYTE1) == MPEGFrameHeader.SYNC_BYTE1) { // we are skipping if $00 byte but check not an end of stream if (position < bufferSize) { int unsyncByteValue = source.get(); position++; //If this is null byte, then write upto this point if (unsyncByteValue == 0) { output.put(source.array(), source.arrayOffset() + offset, length); offset = position; length = 0; } else { length++; } } } } if (length > 0) { output.put(source.array(), source.arrayOffset() + offset, length); } output.flip(); long time = System.nanoTime() - start; System.out.printf("Took %6.3f ms, was %d bytes, now %,d bytes%n", time/1e6, source.limit(), output.limit()); return output; } */ /** * Synchronize an array of bytes, this should only be called if it has been determined the tag is unsynchronised * <p/> * Any patterns of the form $FF $00 should be replaced by $FF * * @param source a ByteBuffer to be unsynchronized * @return a synchronized representation of the source */ public static ByteBuffer synchronize(ByteBuffer source) { //long start = System.nanoTime(); int len = source.remaining(); byte[] bytes = new byte[len + 1]; // an extra byte saves a check later. source.get(bytes, 0, len); int from = 0, to = 0; boolean copy = true; // whether to copy the byte, if false, check the byte != 0. while (from < len) { byte byteValue = bytes[from++]; if (copy || byteValue != 0) bytes[to++] = byteValue; copy = ((byteValue & MPEGFrameHeader.SYNC_BYTE1) != MPEGFrameHeader.SYNC_BYTE1); } ByteBuffer bb2 = ByteBuffer.wrap(bytes, 0, to); //long time = System.nanoTime() - start; //System.out.printf("Took %6.3f ms, was %d bytes, now %,d bytes%n", time/1e6, source.limit(), bb2.limit()); return bb2; } }