/* * 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; /** * Implements the Subframe abstract class, providing encoding support for the * FLAC LPC Subframe. * * @author Preston Lacey */ public class Subframe_LPC extends Subframe { public static long totalTime = 0; private class PartialResult { int[] samples; int start; int increment; int count; int subframeSampleSize; int lpcOrder; int lowOrderBits; int totalBits; int precision; int lastCount; } /* Following values used frequently, let's calculate just once */ private static final double LOGE_2 = Math.log(2); private static final double SQRT_2 = Math.sqrt(2); /** Maximum LPC order that is supported by this subframe */ public static final int MAX_LPC_ORDER = 32; /** For debugging: Higher values equals greater output, generally in * increments of 10 */ public static int DEBUG_LEV = 0; /** Subframe type implemented by this subframe. */ public static final EncodingConfiguration.SubframeType type = EncodingConfiguration.SubframeType.LPC; int sampleSize = 0; RiceEncoder rice = null; int _lpcOrder = 0; int _lowOrderBits = 0; long _totalBits = 0; int _precision = 15; int _lastCount = 0; int[] _errors = null; int[] _quantizedCoeffs = null; int _shift = 0; LPC[] lpcs = null; int[] _samples = null; int _offset = 0; int _frameSampleSize; int _start = 0; int _increment = 0; long[] correlations = null; int[] _windowedSamples = null; Subframe_LPC(StreamConfiguration sc) { super(sc); sampleSize = sc.getBitsPerSample(); rice = new RiceEncoder(); lpcs = new LPC[MAX_LPC_ORDER+1]; for(int i = 0; i < MAX_LPC_ORDER+1; i++) lpcs[i] = new LPC(i); _lastCount = -1; _quantizedCoeffs = new int[MAX_LPC_ORDER+1]; } /** * This method is used to set the encoding configuration. * @param ec encoding configuration to use. * @return true if configuration was changed, false otherwise */ @Override public boolean registerConfiguration(EncodingConfiguration ec) { return super.registerConfiguration(ec); } public int encodeSamples(int[] samples, int count, int start, int skip, int offset, int unencSampleSize) { int encodedSamples = count; if(DEBUG_LEV > 0) { System.err.println("Subframe_LPC::encodeSamples(...) : Begin"); if(DEBUG_LEV > 10) { System.err.println("--count : " +count); System.err.println("start:skip:offset:::"+start+":"+skip+":"+offset); } } int increment = skip+1; if(count != _lastCount) { _errors = new int[count]; _lastCount = count; _windowedSamples = new int[count]; } int minOrder = ec.getMinLPCOrder(); int maxOrder = ec.getMaxLPCOrder(); int frameSampleSize = unencSampleSize; int order = -1; long totalBits = 0; long[] R = null; if(correlations == null || correlations.length < maxOrder+1) { R = new long[maxOrder+1]; correlations = R; } else R = correlations; LPC.window(samples, count, start, increment, _windowedSamples); //LPC.createAutoCorrelation(R, samples, count, start, increment, maxOrder); LPC.createAutoCorrelation(R, _windowedSamples, count, 0, 1, maxOrder); int[] coefficients = new int[MAX_LPC_ORDER+1]; int[] errors = new int[count]; int lowOrderBits = 0; int precision = 0; int shift = 0; int watchCount = 2; for(int i = maxOrder; i >= minOrder; i--) { LPC.calculate(lpcs[i], R); int tempTotalBits = partialEncodeLPC(samples, count, start, increment, lpcs[i], this,frameSampleSize); //compare to current order: If last not set or size < last, replace if(tempTotalBits < totalBits || order == -1) { order = i; totalBits = tempTotalBits; lowOrderBits = _lowOrderBits; precision = _precision; shift = _shift; int[] temp = coefficients; coefficients = _quantizedCoeffs; _quantizedCoeffs = temp; temp = errors; errors = _errors; _errors = temp; //priorLPC = lpcs[i]; watchCount = 2; } else { if(--watchCount == 0) break; } } _lowOrderBits = lowOrderBits; _precision = precision; _shift = shift; _quantizedCoeffs = coefficients; _errors = errors; _samples = samples; _offset = offset; _frameSampleSize = unencSampleSize; _start = start; _increment = increment; _totalBits = totalBits; _lpcOrder = order; return encodedSamples; } /** * Return the estimated size of the previous encode attempt in bits. Since * returning the data from an encode is costly(due to the rice encoding and FLAC * compliant bit-packing), this allows us to estimate the size first, and * therefore choose another subframe type if this is larger. * * @return estimated size in bits of encoded subframe. */ public long estimatedSize() { return _totalBits; } /** * Get the data from the last encode attempt. Data is returned in an * EncodedElement, properly packed at the bit-level to be added directly to * a FLAC stream. * * @return EncodedElement containing encoded subframe */ public EncodedElement getData() { EncodedElement result = new EncodedElement(); result.clear((int)_totalBits+1, _offset); writeLPC(_samples, _lastCount, _start, _increment, result, _frameSampleSize, _lowOrderBits, _precision, _shift, _quantizedCoeffs, _errors, _lpcOrder,rice); int totalBits = result.getTotalBits(); this.lastEncodedSize = (int)totalBits; if(DEBUG_LEV > 0) { System.err.println("lastencodedSize set: "+this.lastEncodedSize); System.err.println("Subframe_LPC::getData(...): End"); } return result; } public int encodeSamples(int[] samples, int count, int start, int skip, EncodedElement dataEle, int offset, int unencSampleSize ) { encodeSamples(samples, count, start, skip, offset, unencSampleSize); EncodedElement result = getData(); int totalBits = result.getTotalBits(); dataEle.data = result.data; dataEle.usableBits = result.usableBits; dataEle.offset = result.offset; dataEle.previous = result.previous; dataEle.next = result.next; this.lastEncodedSize = (int)totalBits; return count; } private static void writeHeadersAndData(EncodedElement dataEle, int order, int[] coeff, int precision, int shift, int[] samples, int sampleSize, int start, int skip) { //write headers int encodedType = 1<<5 | (order-1); dataEle.addInt(0, 1); dataEle.addInt(encodedType, 6); dataEle.addInt(0, 1); if(order > 0) { dataEle.packInt(samples, sampleSize, start, skip, order); } dataEle.addInt(precision-1, 4); dataEle.addInt(shift, 5); //System.err.println("shift:order:type::"+shift+":"+order+":"+encodedType); for(int i = 1; i <= order; i++) { int val = (int)-coeff[i]; dataEle.addInt(val, precision); } } /** * Quantize coefficients to integer values of the given precision, and * calculate the shift needed. * @param coefficients values to quantize. These values will not be changed. * @param dest destination for quantized values. * @param order number of values to quantize. First value skipped, coefficients * array must be at least order+1 in length. * @param precision number of signed bits to use for coefficients * @return */ private static int quantizeCoefficients(double[] coefficients, int[] dest, int order, int precision) { int shiftApplied = 0; int maxValAllowed = 1<<(precision-1)-1; double maxVal = 0; for(int i = 1; i <= order; i++) { double temp = coefficients[i]; if(temp < 0) temp*= -1; if(temp > maxVal) maxVal = temp; } //find shift to use(by max value) //for(shiftApplied = precision-1; shiftApplied > 0; shiftApplied--) { for(shiftApplied = 15; shiftApplied > 0; shiftApplied--) { int temp = (int)(maxVal * (1<<shiftApplied)); if(temp <= maxValAllowed) break; } if(maxVal > maxValAllowed) {//no shift should have been applied //ensure max value is not too large, cap all necessary // for(int i = 1; i <= order; i++) { double temp = coefficients[i]; if(temp < 0) temp = temp * -1; if(temp > maxValAllowed) { //coefficients[i] = maxValAllowed; dest[i] = maxValAllowed; } else dest[i] = (int)coefficients[i]; //System.err.println("Quantizing with new dest"); } } else { //shift and quantize all values by found shift for(int i = 1; i <= order; i++) { //if(DEBUG_LEV > 20) // System.err.print("i:old:new::"+i+":"+coefficients[i]); //coefficients[i] = (int)(coefficients[i]*(1<<shiftApplied)); double temp = coefficients[i]*(1<<shiftApplied); temp = (temp > 0) ? temp+0.5:temp-0.5; //dest[i] = (int)(coefficients[i]*(1<<shiftApplied)); dest[i] = (int)temp; //if(DEBUG_LEV > 20) // System.err.println(":"+coefficients[i]); } } return shiftApplied; } private static void writeLPC(int[] samples, int count, int start, int increment, EncodedElement ele, int frameSampleSize, int riceParam, int precision, int shift, int[] coeffs, int[] errors, int order, RiceEncoder rice) { writeHeadersAndData(ele, order, coeffs, precision, shift, samples, frameSampleSize, start, increment-1); int paramSize = (riceParam > 14) ? 5:4; boolean fiveBitParam = (paramSize < 5) ? false:true; RiceEncoder.beginResidual(fiveBitParam, (byte)0, ele); EncodedElement_32 temp = new EncodedElement_32(ele.data.length/4+1, ele.getUsableBits()%8); rice.encodeRicePartition(errors, order,1, count-order, temp, riceParam, fiveBitParam); ele.attachEnd(temp.convertToEncodedElement()); } private static int getParam(int[] vals, int end, int start, int max) { long sum = 0; for(int i = start; i < end; i++) { int temp = vals[i]; temp = (temp < 0) ? -temp:temp; sum += temp; } float mean = (float)sum/(float)(end-start); double temp = LOGE_2*(mean); if(temp < 1) temp = 0; else temp = Math.ceil(Math.log(temp)/LOGE_2); int param = (int)temp; param++; if(param < 0) { System.err.println("end:start:sum:mean "+end+":"+start+":"+sum+":"+mean); param = 1; System.err.println("param negative?"); System.exit(0); } else if(param > max) param = max; return param; } private static int partialEncodeLPC(int[] samples, int count, int start, int increment, LPC lpc, Subframe_LPC lpcSubframe, int frameSampleSize) { //System.err.println("encodeLPC begin"); int order = lpc.order; //double error = (lpc.rawError < 0) ? -lpc.rawError:lpc.rawError; double tempLowOrderBits = 0; //following commented out because the getParam() method appears to be //more accurate for high-order lpc's, causing the search to end sooner //and resulting in smaller files. win-win. On second thought, that can't //be why it's quicker. The profile is showing *more* invocatiosn of this //function rather than fewer(by 3000!), which means it's something else //that's causing it to be quicker....strange. /*double deviation = Math.sqrt((int)error/count); double tempBits = LOGE_2*deviation/SQRT_2; tempLowOrderBits = (Math.ceil(Math.log(tempBits)/LOGE_2)); if(java.lang.Double.isNaN(tempLowOrderBits)) { System.err.println("tempLowOrderBits is NaN"); if(Double.isNaN(deviation)) System.err.println("deviation is NaN"); System.err.println("Error: "+(int)error/count); System.exit(0); } if(tempLowOrderBits < 1) tempLowOrderBits = 1; else if (tempLowOrderBits > frameSampleSize) { tempLowOrderBits = frameSampleSize; }*/ int precision = 15; //calculate total estimated size of frame int headerSize = order*frameSampleSize+precision*order+9+8; int[] coeffs = lpcSubframe._quantizedCoeffs; int shift = quantizeCoefficients(lpc.rawCoefficients, coeffs, order, precision); //use integer coefficients to predict samples //compare prediction to original, storing error. /** We save ~7% by accessing local vars instead of array in next loop */ int coeff1 = coeffs[1]; int coeff2 = coeffs[2]; int coeff3 = coeffs[3]; int coeff4 = coeffs[4]; int coeff5 = coeffs[5]; int coeff6 = coeffs[6]; int coeff7 = coeffs[7]; int coeff8 = coeffs[8]; int coeff9 = coeffs[9]; int coeff10 = coeffs[10]; int coeff11 = coeffs[11]; int coeff12 = coeffs[12]; int baseIndex = start; int targetSampleBase = start+order*increment-increment; int tempOrder = order; for(int i = order; i < count; i++) { int temp = 0; targetSampleBase += increment; int sampleIndex = baseIndex; baseIndex += increment; if(order > 12) { switch(order) { case 32: temp -= coeffs[32]*samples[sampleIndex]; sampleIndex+=increment; case 31: temp -= coeffs[31]*samples[sampleIndex]; sampleIndex+=increment; case 30: temp -= coeffs[30]*samples[sampleIndex]; sampleIndex+=increment; case 29: temp -= coeffs[29]*samples[sampleIndex]; sampleIndex+=increment; case 28: temp -= coeffs[28]*samples[sampleIndex]; sampleIndex+=increment; case 27: temp -= coeffs[27]*samples[sampleIndex]; sampleIndex+=increment; case 26: temp -= coeffs[26]*samples[sampleIndex]; sampleIndex+=increment; case 25: temp -= coeffs[25]*samples[sampleIndex]; sampleIndex+=increment; case 24: temp -= coeffs[24]*samples[sampleIndex]; sampleIndex+=increment; case 23: temp -= coeffs[23]*samples[sampleIndex]; sampleIndex+=increment; case 22: temp -= coeffs[22]*samples[sampleIndex]; sampleIndex+=increment; case 21: temp -= coeffs[21]*samples[sampleIndex]; sampleIndex+=increment; case 20: temp -= coeffs[20]*samples[sampleIndex]; sampleIndex+=increment; case 19: temp -= coeffs[19]*samples[sampleIndex]; sampleIndex+=increment; case 18: temp -= coeffs[18]*samples[sampleIndex]; sampleIndex+=increment; case 17: temp -= coeffs[17]*samples[sampleIndex]; sampleIndex+=increment; case 16: temp -= coeffs[16]*samples[sampleIndex]; sampleIndex+=increment; case 15: temp -= coeffs[15]*samples[sampleIndex]; sampleIndex+=increment; case 14: temp -= coeffs[14]*samples[sampleIndex]; sampleIndex+=increment; case 13: temp -= coeffs[13]*samples[sampleIndex]; sampleIndex+=increment; } tempOrder = 12; } switch(tempOrder) { case 12: temp -= coeff12*samples[sampleIndex]; sampleIndex+=increment; case 11: temp -= coeff11*samples[sampleIndex]; sampleIndex+=increment; case 10: temp -= coeff10*samples[sampleIndex]; sampleIndex+=increment; case 9: temp -= coeff9*samples[sampleIndex]; sampleIndex+=increment; case 8: temp -= coeff8*samples[sampleIndex]; sampleIndex+=increment; case 7: temp -= coeff7*samples[sampleIndex]; sampleIndex+=increment; case 6: temp -= coeff6*samples[sampleIndex]; sampleIndex+=increment; case 5: temp -= coeff5*samples[sampleIndex]; sampleIndex+=increment; case 4: temp -= coeff4*samples[sampleIndex]; sampleIndex+=increment; case 3: temp -= coeff3*samples[sampleIndex]; sampleIndex+=increment; case 2: temp -= coeff2*samples[sampleIndex]; sampleIndex+=increment; case 1: temp -= coeff1*samples[sampleIndex]; sampleIndex+=increment;break; default: } temp = temp >> shift; lpcSubframe._errors[i] = samples[targetSampleBase]-temp; } tempLowOrderBits = getParam(lpcSubframe._errors, count, order,frameSampleSize); int riceSize = RiceEncoder.calculateEncodeSize(lpcSubframe._errors, order, 1, count-order, (int)tempLowOrderBits); int totalSize = headerSize + riceSize; lpcSubframe._precision = precision; lpcSubframe._lowOrderBits = (int)tempLowOrderBits; lpcSubframe._shift = shift; lpcSubframe._totalBits = totalSize; return totalSize; } }