/* * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/ * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package javaFlacEncoder; /** * Handles taking a set of audio samples, and splitting it into the proper * subframes, and returning the resulting encoded data. This object will do any calculations * needed for preparing the “channel configuration” used in encoding, such as mid-side or * left-right(based upon the given configuration). * @author Preston Lacey */ public class Frame { /** For debugging: Higher level equals more output(generally in increments * of 10 */ public static int DEBUG_LEV = 0; /* track the size of the last encoded frame */ private int lastEncodeSize; /* Current EncodingConfiguration used. This must NOT be changed while a * Frame is being encoded, but may be changed between frames */ EncodingConfiguration ec; /* Current stream configuration */ StreamConfiguration sc; /* Number of channels currently configured. This comes from setting the * StreamConfiguration(and so is slightly redundant) */ int channels; /* bits per sample as set by the StreamConfiguration...this is redundant */ int bitsPerSample; /* Object used to generate the headers needed for FLAC frames */ FrameHeader frameHeader; /* Used to calculate CRC16's of each frame */ CRC16 crc16; /* Used for calculation of verbatimSubframes */ Subframe verbatimSubframe; /* Used for calculation of fixedSubframes */ Subframe fixedSubframe; /* Used for calculation of lpcSubframes */ Subframe lpcSubframe; /* Used for calculation of constantSubframes */ Subframe constantSubframe; /* Flag tracking whether we need to test for a constant subframe */ boolean testConstant; /** * Constructor. Private to prevent it's use(if a StreamConfiguration isn't * set, then most methods will fail in an undefined fashion. */ private Frame(){} /** * Constructor. Sets the StreamConfiguration to use at creation of object. * If the StreamConfiguration needs to be changed, you *MUST* create a new * Frame object. * * @param sc StreamConfiguration to use for encoding with this frame. */ public Frame(StreamConfiguration sc) { lastEncodeSize = 0; channels = sc.getChannelCount(); this.sc = sc; frameHeader = new FrameHeader(); crc16 = new CRC16(); ec = null; verbatimSubframe = new Subframe_Verbatim(sc); fixedSubframe = new Subframe_Fixed(sc); lpcSubframe = new Subframe_LPC(sc); constantSubframe = new Subframe_Constant(sc); bitsPerSample = sc.getBitsPerSample(); testConstant = true; registerConfiguration(new EncodingConfiguration()); } /** * This method is used to set the encoding configuration. This * configuration can be altered throughout the stream, but cannot be called while an * encoding process is active. * * @param ec encoding configuration to use. * @return <code>true</code> if configuration was changed. * <code>false</code> otherwise(i.e, and encoding process was active * at the time of change) */ boolean registerConfiguration(EncodingConfiguration ec) { boolean changed = false; if(sc.getChannelCount() != 2) ec.setChannelConfig(EncodingConfiguration.ChannelConfig.INDEPENDENT); this.ec = new EncodingConfiguration(ec); verbatimSubframe.registerConfiguration(this.ec); fixedSubframe.registerConfiguration(this.ec); lpcSubframe.registerConfiguration(this.ec); constantSubframe.registerConfiguration(this.ec); changed = true; return changed; } /** * Encodes samples into the appropriate compressed format, saving the * result in the given “data” EncodedElement list. Encodes 'count' samples, * from index 'start', to index 'start' times 'skip', where “skip” is the * format that samples may be packed in an array. For example, 'samples' may * include both left and right samples of a stereo stream. Therefore, “skip” * would equal 2, resulting in the valid indices for the first channel being * even, and second being odd. * @param samples the audio samples to encode. This array may contain * samples for multiple channels, interleaved; only one of * these channels is encoded by a subframe. * @param count the number of samples to encode. * @param start the index to start at in the array. * @param skip the number of indices to skip between successive samples * (for use when channels are interleaved in the given * array). * @param data the EncodedElement to attach encoded data to. Data in * Encoded Element given is not altered. New data is * attached starting with “data.getNext()”. If “data” * already has a “next” set, it will be lost! * @return int Returns the number of inter-channel samples encoded; * i.e, if block-size is 4000, and it is stereo audio. * There are 8000 samples in this block, but the return * value is “4000”. There is always an equal number of * samples encoded from each channel. This exists primarily * to support dynamic block sizes in the future; * Pre-condition: none * Post-condition: Argument 'data' is the head of a list containing the resulting, * encoded data stream. */ int encodeSamples(int[] samples, int count, int start, int skip, EncodedElement result, long frameNumber) { //System.err.println("FRAME::encodeSamples: frame#:"+frameNumber); if(DEBUG_LEV > 0) { System.err.println("FRAME::encodeSamples(...)"); if(DEBUG_LEV > 10) { System.err.println("\tsamples.length:"+samples.length+":count:"+ count+":start:"+start+":skip:"+skip+":frameNumber:"+ frameNumber); } } //long frameNumber = 0; int samplesEncoded = count; testConstant = true; EncodedElement data = null; //choose correct channel configuration. Get data for that config EncodingConfiguration.ChannelConfig chConf = ec.getChannelConfig(); if(chConf == EncodingConfiguration.ChannelConfig.INDEPENDENT) { data = new EncodedElement(); int size = encodeIndependent(samples, count, start, skip, data, 0); //int size = encodeMidSide(samples, count, start, skip, data, 0); } else if(chConf == EncodingConfiguration.ChannelConfig.LEFT_SIDE) { //System.err.println("This option not implemented"); data = new EncodedElement(); int size = encodeLeftSide(samples, count, start, skip, data, 0); } else if(chConf == EncodingConfiguration.ChannelConfig.MID_SIDE) { //System.err.println("This option not implemented"); data = new EncodedElement(); int size = Frame.encodeMidSide(samples, count, start, skip, data, 0, this); } else if(chConf == EncodingConfiguration.ChannelConfig.RIGHT_SIDE) { data = new EncodedElement(); int size = encodeRightSide(samples, count, start, skip, data, 0); //System.err.println("This option not implemented"); } else if(chConf == EncodingConfiguration.ChannelConfig.ENCODER_CHOICE) { data = new EncodedElement(); int size = allChannelDecorrelation(samples, count, start, skip, data, 0, this); chConf = ec.channelConfig; ec.channelConfig = EncodingConfiguration.ChannelConfig.ENCODER_CHOICE; } else if(chConf == EncodingConfiguration.ChannelConfig.EXHAUSTIVE) { //System.err.println("This option not implemented"); //encode with all versions. EncodedElement dataLeftSide = new EncodedElement(); ec.channelConfig = EncodingConfiguration.ChannelConfig.LEFT_SIDE; int sizeLeft = encodeLeftSide(samples, count, start, skip, dataLeftSide, 0); ec.channelConfig = EncodingConfiguration.ChannelConfig.MID_SIDE; EncodedElement dataMidSide = new EncodedElement(); int sizeMid = Frame.encodeMidSide(samples, count, start, skip, dataMidSide, 0, this); ec.channelConfig = EncodingConfiguration.ChannelConfig.INDEPENDENT; EncodedElement dataIndependent = new EncodedElement(); int sizeInd = encodeIndependent(samples, count, start, skip, dataIndependent, 0); //choose best ec.channelConfig = chConf; if(sizeLeft <= sizeMid && sizeLeft <= sizeInd) { data = dataLeftSide; chConf = EncodingConfiguration.ChannelConfig.LEFT_SIDE; } else if(sizeMid <= sizeInd) { data = dataMidSide; chConf = EncodingConfiguration.ChannelConfig.MID_SIDE; } else { data = dataIndependent; chConf = EncodingConfiguration.ChannelConfig.INDEPENDENT; } //update header to reflect change } //create header element; attach to result EncodedElement header = frameHeader.createHeader(true, count, sc.getSampleRate(), chConf, sc.getBitsPerSample(), frameNumber, channels); result.setNext(header); //attach data to header header.attachEnd(data); //use "data" to zero-pad to byte boundary. //EncodedElement temp = data.getEnd(); data.padToByte(); /*while(temp.getNext() != null) { temp = temp.getNext(); //System.err.println("temp.getNext() != null)"); }*/ /*int tempVal = temp.getUsableBits(); //System.err.println("Usable bits: "+tempVal); if(tempVal % 8 != 0) { int toWrite = 8-tempVal%8; //System.err.println("Fixing frame to byte offset by : "+toWrite); temp.setUsableBits(tempVal+toWrite); byte[] tempData = temp.getData(); EncodedElement.addInt(0, toWrite, tempVal, tempData); /// FOR DEVEL ONLY: //// if(temp.getUsableBits() % 8 != 0) System.err.println("FRAME::EncodeSamples: SERIOUS ERROR! " + "Algorithm implemented is incorrect!!!"); }*/ //calculate CRC and affix footer EncodedElement crc16Ele = getCRC16(header); data.attachEnd(crc16Ele); //System.err.println("Frame::encodeSamples(...): End"); if(DEBUG_LEV > 0) System.err.println("Frame::encodeSamples(...): End"); return samplesEncoded; } EncodedElement getCRC16(EncodedElement header) { EncodedElement crc16Ele = new EncodedElement(2,0); short val = CRC16.getCRC16(header, crc16); crc16Ele.addInt(val, 16); return crc16Ele; } EncodedElement getCRC16OldWorking(EncodedElement header) { if(DEBUG_LEV > 0) System.err.println("Frame::getCRC16 : Begin"); EncodedElement crc16Ele = new EncodedElement(); crc16.reset(); byte crc16Data[] = new byte[2]; //calculate CRC16 int offset = 0; EncodedElement current = header; int currentByte = 0; byte[] unfullByte = {0}; byte[] eleData = null; int usableBits = 0; int lastByte = 0; while(current != null) { eleData = current.getData(); usableBits = current.getUsableBits(); currentByte = 0; //if offset is not zero, merge first byte with existing byte if(offset != 0) { unfullByte[0] = (byte)(unfullByte[0] | eleData[currentByte++]); CRC16.updateCRC16(unfullByte, 0, 1, crc16); } //checksum all full bytes of element. lastByte = usableBits/8; CRC16.updateCRC16(eleData, currentByte, lastByte, crc16); //save non-full byte(if present), and set "offset" for next element. //offset = usableBits - lastByte*8; offset = usableBits % 8; if(offset != 0) { //System.err.println("usablebits: " + usableBits); unfullByte[0] = eleData[lastByte]; } //update current. current = current.getNext(); } if(offset > 0) { System.err.println("ERROR: frame was not properly bit padded"); System.exit(0); } //get and write value to element data; short crc16Val = crc16.checksum(); EncodedElement.addInt(crc16Val, 16, 0, crc16Data); //attach data to element and return crc16Ele.setData(crc16Data); crc16Ele.setUsableBits(16); if(DEBUG_LEV > 0) { if(DEBUG_LEV > 10) System.err.println("Frame::getCRC16: crc16 : "+ Integer.toHexString(crc16Val)); System.err.println("Frame::getCRC16 : End"); } return crc16Ele; } int encodeIndependent(int[] samples, int count, int start, int skip, EncodedElement result, int offset) { if(DEBUG_LEV > 0) { System.err.println("Frame::encodeIndependent : Begin"); System.err.println("start:skip:offset::"+start+":"+skip+":"+offset); } //int startSize = result.getTotalBits(); int totalSize = 0; int channelLength = 0; int channelCount = skip+1; int inputOffset = offset; EncodedElement subframes[] = new EncodedElement[channelCount]; EncodingConfiguration.ChannelConfig chConf = ec.channelConfig; //encode each subframe, using prior offset in packing for(int i = 0; i < channelCount; i++) { int channelBitsPerSample = this.bitsPerSample; //System.err.println("independent: "+channelBitsPerSample); if(i == 1 && chConf == EncodingConfiguration.ChannelConfig.LEFT_SIDE) channelBitsPerSample++; else if(i == 1 && chConf == EncodingConfiguration.ChannelConfig.MID_SIDE) channelBitsPerSample++; else if(i == 0 && chConf == EncodingConfiguration.ChannelConfig.RIGHT_SIDE) channelBitsPerSample++; subframes[i] = new EncodedElement(); //System.err.println("Frame::subframe begin offset: "+offset); channelLength = encodeChannel(samples, count, start+i, skip, offset, subframes[i], channelBitsPerSample); totalSize += channelLength; offset = (inputOffset+totalSize)%8; } //attach first subframe to result result.attachEnd(subframes[0]); //attach all remaining channels together for(int i = 1; i < channelCount; i++) { subframes[i-1].attachEnd(subframes[i]); //result.attachEnd(subframes[i]); } if(DEBUG_LEV > 0) System.err.println("Frame::encodeIndependent : End"); //return total bit size(does not include given offset). return totalSize; } int encodeChannel(int[] samples, int count, int start, int skip, int offset, EncodedElement data, int channelBitsPerSample) { if(DEBUG_LEV > 0) System.err.println("Frame::encodeChannel : Begin"); int size = 0; //Calculate subframe using the methods requested. EncodingConfiguration.SubframeType subframeType = ec.getSubframeType(); if(subframeType == EncodingConfiguration.SubframeType.VERBATIM) { //use verbatim subframe to encode channel. //note size. //System.err.println("Using verbatim with count: "+count); this.verbatimSubframe.encodeSamples(samples, count, start, skip, data, offset, channelBitsPerSample); size = verbatimSubframe.getEncodedSize(); } else if(subframeType == EncodingConfiguration.SubframeType.FIXED) { //System.err.println("channelBitsPerSample: "+channelBitsPerSample); this.fixedSubframe.encodeSamples(samples, count, start, skip, data, offset, channelBitsPerSample); size = fixedSubframe.getEncodedSize(); } else if(subframeType == EncodingConfiguration.SubframeType.LPC) { this.lpcSubframe.encodeSamples(samples, count, start, skip, data, offset, channelBitsPerSample); size = lpcSubframe.getEncodedSize(); } else if(subframeType == EncodingConfiguration.SubframeType.EXHAUSTIVE) { int conCount = -1; if(testConstant) { //System.err.println("Testing constant"); conCount = constantSubframe.encodeSamples(samples, count, start, skip, data,offset, channelBitsPerSample); } //System.err.println("conCount: "+conCount); if(conCount == count) { //System.err.println("Using Constant!"); size = constantSubframe.getEncodedSize(); } else { ((Subframe_Fixed)fixedSubframe).encodeSamples(samples, count, start, skip, offset, channelBitsPerSample); int fixedSize = (int)((Subframe_Fixed)fixedSubframe).estimatedSize(); //int fixedSize = fixedSubframe.getEncodedSize(); ((Subframe_LPC)lpcSubframe).encodeSamples(samples, count, start, skip, offset, channelBitsPerSample); int lpcSize = (int)((Subframe_LPC)lpcSubframe).estimatedSize(); if(lpcSize < fixedSize) { //size = lpcSize; EncodedElement lpcEle = ((Subframe_LPC)lpcSubframe).getData(); data.data = lpcEle.data; data.next = lpcEle.next; data.usableBits = lpcEle.usableBits; data.offset = lpcEle.offset; size = lpcSubframe.getEncodedSize(); if(size > lpcSize) System.err.println("Lpc size wrong: exp:real : "+lpcSize+":"+size); } else { EncodedElement fixEle = ((Subframe_Fixed)fixedSubframe).getData(); size = fixedSize; data.data = fixEle.data; data.next = fixEle.next; data.usableBits = fixEle.usableBits; data.offset = fixEle.offset; size = fixedSubframe.getEncodedSize(); if(size > fixedSize) System.err.println("Fixed size wrong: exp:real : "+fixedSize+":"+size); } } } //return total bit size of encoded subframe. if(DEBUG_LEV > 0) System.err.println("Frame::encodeChannel : End"); return size; } /** * Returns the total number of valid bits used in the last encoding(i.e, the * number of compressed bits used). This is here for convenience, as the * calling object may also loop through the resulting EncodingElement from * the encoding process and sum the valid bits. * * @return an integer with value of the number of bits used in last encoding. * Pre-condition: none * Post-condition: none */ int getEncodedSize() { if(DEBUG_LEV > 0) System.err.println("Frame::getEncodedSize : Begin"); return lastEncodeSize; } private int encodeRightSide(int[] samples, int count, int start, int skip, EncodedElement data, int offset) { int[] rightSide = new int[samples.length]; for(int i = 0; i < count; i++) { rightSide[2*i] = samples[2*i]-samples[2*i+1]; rightSide[2*i+1] = samples[2*i+1]; } return encodeIndependent(rightSide, count, start, skip, data, offset); } private int encodeLeftSide(int[] samples, int count, int start, int skip, EncodedElement data, int offset) { int[] leftSide = new int[samples.length]; for(int i = 0; i < count; i++) { leftSide[2*i] = samples[2*i]; leftSide[2*i+1] = samples[2*i]-samples[2*i+1]; /*if(leftSide[2*i+1] >= 32767 || leftSide[2*i+1] < -32767) { System.err.println("Bound issue?: " + leftSide[2*i+1]); System.err.println("count:start:skip : "+count+":"+start+":"+skip); }*/ } return encodeIndependent(leftSide, count, start, skip, data, offset); } private static int encodeMidSide(int[] samples, int count, int start, int skip, EncodedElement data, int offset, Frame f) { int[] midSide = new int[samples.length]; for(int i = 0; i < count; i++) { int temp = (samples[2*i]+samples[2*i+1]) >> 1; // if(temp %2 != 0) temp++; midSide[2*i] = temp; midSide[2*i+1] = (samples[2*i]-samples[2*i+1]); //midSide[2*i+1] = 0; } return f.encodeIndependent(midSide, count, start, skip, data, offset); } private static double getVariance(double mean, int[] samples, int count, int start, int increment) { double var = 0; for(int i = 0; i < count; i++) { int loc = start+i*increment; double val = (mean-samples[loc]); var += val*val; } return var; } private static int allChannelDecorrelation(int[] samples, int count, int start, int skip, EncodedElement data, int offset, Frame f) { if(DEBUG_LEV > 0) { System.err.println("Frame::allChannelDecorrelation(...)"); } //calculate size for each type //int size = 0; /* * sums[0]: left * sums[1]: right * sums[2]: mid * sums[3]: side */ //long[] sums = new long[4]; long sums0 = 0; long sums1 = 0; long sums2 = 0; long sums3 = 0; boolean [] constantCandidate = new boolean[4]; for(int i = 0; i < 4; i++) { //sums[i] = 0; constantCandidate[i] = false; } int[] midSideSamples = new int[samples.length]; int index = 0; for(int i = 0; i < count; i++) { int temp = (samples[index]+samples[index+1]) >> 1; midSideSamples[index] = temp; midSideSamples[index+1] = (samples[index]-samples[index+1]); index += 2; } index = 0; for(int i = 0; i < count; i++ ) { long temp; temp = samples[index]; if(temp < 0) temp = -temp; sums0 += temp; temp = samples[index+1]; if(temp < 0) temp = -temp; sums1 += temp; temp = midSideSamples[index]; if(temp < 0) temp = -temp; sums2 += temp; temp = midSideSamples[index+1]; if(temp < 0) temp = -temp; sums3 += temp; index += 2; } //for(int i = 0; i < sums.length; i++) sums[i] =sums[i]/count; sums0 = sums0/count; sums1 = sums1/count; sums2 = sums2/count; sums3 = sums3/count; constantCandidate[0] = (samples[0] == sums0); constantCandidate[1] = (samples[1] == sums1); constantCandidate[2] = (midSideSamples[2] == sums2); constantCandidate[3] = (midSideSamples[3] == sums3); long[] results = new long[4]; results[0] = sums0+sums1;//independent results[1] = sums2+sums3;//midSide results[2] = sums0+sums3;//leftSide results[3] = sums3+sums1;//rightSide //choose the best int choice = 0; for(int i = 0; i < 4; i++) { if(results[choice] > results[i]) choice = i; } if(choice == 0) { f.testConstant = constantCandidate[0] || constantCandidate[1]; f.ec.channelConfig = EncodingConfiguration.ChannelConfig.INDEPENDENT; return f.encodeIndependent(samples, count, start, skip, data, offset); } else if(choice == 1) { f.testConstant = constantCandidate[2] || constantCandidate[3]; f.ec.channelConfig = EncodingConfiguration.ChannelConfig.MID_SIDE; return f.encodeIndependent(midSideSamples, count, start, skip, data, offset); } else if(choice == 2) { f.testConstant = constantCandidate[0] || constantCandidate[3]; f.ec.channelConfig = EncodingConfiguration.ChannelConfig.LEFT_SIDE; for(int i = 0; i < count; i++) midSideSamples[2*i] = samples[2*i]; return f.encodeIndependent(midSideSamples, count, start, skip, data, offset); } else { f.testConstant = constantCandidate[1] || constantCandidate[3]; f.ec.channelConfig = EncodingConfiguration.ChannelConfig.RIGHT_SIDE; for(int i = 0; i < count; i++) { midSideSamples[2*i] = midSideSamples[2*i+1]; midSideSamples[2*i+1] = samples[2*i+1]; } return f.encodeIndependent(midSideSamples, count, start, skip, data, offset); } } }