package net.sourceforge.jaad.aac.ps;

import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.SampleFrequency;
import net.sourceforge.jaad.aac.syntax.IBitStream;

/**
 * This class is part of JAAD ( jaadec.sourceforge.net ) that is distributed
 * under the Public Domain license. Code changes provided by the JCodec project
 * are distributed under FreeBSD license.
 *
 * @author in-somnia
 */
public class PS implements PSConstants, PSTables, PSHuffmanTables {

	/* bitstream parameters */
	boolean enable_iid, enable_icc, enable_ext;
	int iid_mode;
	int icc_mode;
	int nr_iid_par;
	int nr_ipdopd_par;
	int nr_icc_par;
	int frame_class;
	int num_env;
	int[] border_position;
	boolean[] iid_dt;
	boolean[] icc_dt;
	boolean enable_ipdopd;
	int ipd_mode;
	boolean[] ipd_dt;
	boolean[] opd_dt;

	/* indices */
	int[] iid_index_prev;
	int[] icc_index_prev;
	int[] ipd_index_prev;
	int[] opd_index_prev;
	int[][] iid_index;
	int[][] icc_index;
	int[][] ipd_index;
	int[][] opd_index;

	int[] ipd_index_1;
	int[] opd_index_1;
	int[] ipd_index_2;
	int[] opd_index_2;
	/* ps data was correctly read */
	int ps_data_available;
	/* a header has been read */
	public boolean header_read;
	/* hybrid filterbank parameters */
	PSFilterbank hyb;
	boolean use34hybrid_bands;
	int numTimeSlotsRate;
	int num_groups;
	int num_hybrid_groups;
	int nr_par_bands;
	int nr_allpass_bands;
	int decay_cutoff;
	int[] group_border;
	int[] map_group2bk;
	/* filter delay handling */
	int saved_delay;
	int[] delay_buf_index_ser;
	int[] num_sample_delay_ser;
	int[] delay_D;
	int[] delay_buf_index_delay;
	float[][][] delay_Qmf; /* 14 samples delay max, 64 QMF channels */

	float[][][] delay_SubQmf; /* 2 samples delay max (SubQmf is always allpass filtered) */

	float[][][][] delay_Qmf_ser; /* 5 samples delay max (table 8.34), 64 QMF channels */

	float[][][][] delay_SubQmf_ser; /* 5 samples delay max (table 8.34) */
	/* transients */

	float alpha_decay;
	float alpha_smooth;
	float[] P_PeakDecayNrg;
	float[] P_prev;
	float[] P_SmoothPeakDecayDiffNrg_prev;
	/* mixing and phase */
	float[][] h11_prev;
	float[][] h12_prev;
	float[][] h21_prev;
	float[][] h22_prev;
	int phase_hist;
	float[][][] ipd_prev;
	float[][][] opd_prev;

	public PS(SampleFrequency sr, int numTimeSlotsRate) {
	    this.border_position = new int[MAX_PS_ENVELOPES+1];
        this.iid_dt = new boolean[MAX_PS_ENVELOPES];
        this.icc_dt = new boolean[MAX_PS_ENVELOPES];
        this.ipd_dt = new boolean[MAX_PS_ENVELOPES];
        this.opd_dt = new boolean[MAX_PS_ENVELOPES];
        this.iid_index_prev = new int[34];
        this.icc_index_prev = new int[34];
        this.ipd_index_prev = new int[17];
        this.opd_index_prev = new int[17];
        this.iid_index = new int[MAX_PS_ENVELOPES][34];
        this.icc_index = new int[MAX_PS_ENVELOPES][34];
        this.ipd_index = new int[MAX_PS_ENVELOPES][17];
        this.opd_index = new int[MAX_PS_ENVELOPES][17];
        this.ipd_index_1 = new int[17];
        this.opd_index_1 = new int[17];
        this.ipd_index_2 = new int[17];
        this.opd_index_2 = new int[17];
        this.delay_buf_index_ser = new int[NO_ALLPASS_LINKS];
        this.num_sample_delay_ser = new int[NO_ALLPASS_LINKS];
        this.delay_D = new int[64];
        this.delay_buf_index_delay = new int[64];
        this.delay_Qmf = new float[14][64][2]; /* 14 samples delay max, 64 QMF channels */
        this.delay_SubQmf = new float[2][32][2]; /* 2 samples delay max (SubQmf is always allpass filtered) */
        this.delay_Qmf_ser = new float[NO_ALLPASS_LINKS][5][64][2]; /* 5 samples delay max (table 8.34), 64 QMF channels */
        this.delay_SubQmf_ser = new float[NO_ALLPASS_LINKS][5][32][2]; /* 5 samples delay max (table 8.34) */
        this.P_PeakDecayNrg = new float[34];
        this.P_prev = new float[34];
        this.P_SmoothPeakDecayDiffNrg_prev = new float[34];
        this.h11_prev = new float[50][2];
        this.h12_prev = new float[50][2];
        this.h21_prev = new float[50][2];
        this.h22_prev = new float[50][2];
        this.ipd_prev = new float[20][2][2];
        this.opd_prev = new float[20][2][2];

		int i;
		int short_delay_band;

		hyb = new PSFilterbank(numTimeSlotsRate);
		this.numTimeSlotsRate = numTimeSlotsRate;

		this.ps_data_available = 0;

		/* delay stuff*/
		this.saved_delay = 0;

		for(i = 0; i<64; i++) {
			this.delay_buf_index_delay[i] = 0;
		}

		for(i = 0; i<NO_ALLPASS_LINKS; i++) {
			this.delay_buf_index_ser[i] = 0;
			/* THESE ARE CONSTANTS NOW */
			this.num_sample_delay_ser[i] = delay_length_d[i];
		}

		/* THESE ARE CONSTANTS NOW */
		short_delay_band = 35;
		this.nr_allpass_bands = 22;
		this.alpha_decay = 0.76592833836465f;
		this.alpha_smooth = 0.25f;

		/* THESE ARE CONSTANT NOW IF PS IS INDEPENDANT OF SAMPLERATE */
		for(i = 0; i<short_delay_band; i++) {
			this.delay_D[i] = 14;
		}
		for(i = short_delay_band; i<64; i++) {
			this.delay_D[i] = 1;
		}

		/* mixing and phase */
		for(i = 0; i<50; i++) {
			this.h11_prev[i][0] = 1;
			this.h12_prev[i][1] = 1;
			this.h11_prev[i][0] = 1;
			this.h12_prev[i][1] = 1;
		}

		this.phase_hist = 0;

		for(i = 0; i<20; i++) {
			this.ipd_prev[i][0][0] = 0;
			this.ipd_prev[i][0][1] = 0;
			this.ipd_prev[i][1][0] = 0;
			this.ipd_prev[i][1][1] = 0;
			this.opd_prev[i][0][0] = 0;
			this.opd_prev[i][0][1] = 0;
			this.opd_prev[i][1][0] = 0;
			this.opd_prev[i][1][1] = 0;
		}
	}

	public int decode(IBitStream ld) throws AACException {
		int tmp, n;
		long bits = ld.getPosition();

		/* check for new PS header */
		if(ld.readBool()) {
			this.header_read = true;

			this.use34hybrid_bands = false;

			/* Inter-channel Intensity Difference (IID) parameters enabled */
			this.enable_iid = ld.readBool();

			if(this.enable_iid) {
				this.iid_mode = ld.readBits(3);

				this.nr_iid_par = nr_iid_par_tab[this.iid_mode];
				this.nr_ipdopd_par = nr_ipdopd_par_tab[this.iid_mode];

				if(this.iid_mode==2||this.iid_mode==5)
					this.use34hybrid_bands = true;

				/* IPD freq res equal to IID freq res */
				this.ipd_mode = this.iid_mode;
			}

			/* Inter-channel Coherence (ICC) parameters enabled */
			this.enable_icc = ld.readBool();

			if(this.enable_icc) {
				this.icc_mode = ld.readBits(3);

				this.nr_icc_par = nr_icc_par_tab[this.icc_mode];

				if(this.icc_mode==2||this.icc_mode==5)
					this.use34hybrid_bands = true;
			}

			/* PS extension layer enabled */
			this.enable_ext = ld.readBool();
		}

		/* we are here, but no header has been read yet */
		if(this.header_read==false) {
			this.ps_data_available = 0;
			return 1;
		}

		this.frame_class = ld.readBit();
		tmp = ld.readBits(2);

		this.num_env = num_env_tab[this.frame_class][tmp];

		if(this.frame_class!=0) {
			for(n = 1; n<this.num_env+1; n++) {
				this.border_position[n] = ld.readBits(5)+1;
			}
		}

		if(this.enable_iid) {
			for(n = 0; n<this.num_env; n++) {
				this.iid_dt[n] = ld.readBool();

				/* iid_data */
				if(this.iid_mode<3) {
					huff_data(ld, this.iid_dt[n], this.nr_iid_par, t_huff_iid_def,
						f_huff_iid_def, this.iid_index[n]);
				}
				else {
					huff_data(ld, this.iid_dt[n], this.nr_iid_par, t_huff_iid_fine,
						f_huff_iid_fine, this.iid_index[n]);
				}
			}
		}

		if(this.enable_icc) {
			for(n = 0; n<this.num_env; n++) {
				this.icc_dt[n] = ld.readBool();

				/* icc_data */
				huff_data(ld, this.icc_dt[n], this.nr_icc_par, t_huff_icc,
					f_huff_icc, this.icc_index[n]);
			}
		}

		if(this.enable_ext) {
			int num_bits_left;
			int cnt = ld.readBits(4);
			if(cnt==15) {
				cnt += ld.readBits(8);
			}

			num_bits_left = 8*cnt;
			while(num_bits_left>7) {
				int ps_extension_id = ld.readBits(2);

				num_bits_left -= 2;
				num_bits_left -= ps_extension(ld, ps_extension_id, num_bits_left);
			}

			ld.skipBits(num_bits_left);
		}

		int bits2 = (int) (ld.getPosition()-bits);

		this.ps_data_available = 1;

		return bits2;
	}

	private int ps_extension(IBitStream ld,
		int ps_extension_id,
		int num_bits_left) throws AACException {
		int n;
		long bits = ld.getPosition();

		if(ps_extension_id==0) {
			this.enable_ipdopd = ld.readBool();

			if(this.enable_ipdopd) {
				for(n = 0; n<this.num_env; n++) {
					this.ipd_dt[n] = ld.readBool();

					/* ipd_data */
					huff_data(ld, this.ipd_dt[n], this.nr_ipdopd_par, t_huff_ipd,
						f_huff_ipd, this.ipd_index[n]);

					this.opd_dt[n] = ld.readBool();

					/* opd_data */
					huff_data(ld, this.opd_dt[n], this.nr_ipdopd_par, t_huff_opd,
						f_huff_opd, this.opd_index[n]);
				}
			}
			ld.readBit(); //reserved
		}

		/* return number of bits read */
		int bits2 = (int) (ld.getPosition()-bits);

		return bits2;
	}

	/* read huffman data coded in either the frequency or the time direction */
	private void huff_data(IBitStream ld, boolean dt, int nr_par,
		int[][] t_huff, int[][] f_huff, int[] par) throws AACException {
		int n;

		if(dt) {
			/* coded in time direction */
			for(n = 0; n<nr_par; n++) {
				par[n] = ps_huff_dec(ld, t_huff);
			}
		}
		else {
			/* coded in frequency direction */
			par[0] = ps_huff_dec(ld, f_huff);

			for(n = 1; n<nr_par; n++) {
				par[n] = ps_huff_dec(ld, f_huff);
			}
		}
	}

	/* binary search huffman decoding */
	private int ps_huff_dec(IBitStream ld, int[][] t_huff) throws AACException {
		int bit;
		int index = 0;

		while(index>=0) {
			bit = ld.readBit();
			index = t_huff[index][bit];
		}

		return index+31;
	}

	/* limits the value i to the range [min,max] */
	private int delta_clip(int i, int min, int max) {
		if(i<min) return min;
		else if(i>max) return max;
		else return i;
	}

	/* delta decode array */
	private void delta_decode(boolean enable, int[] index, int[] index_prev,
		boolean dt_flag, int nr_par, int stride,
		int min_index, int max_index) {
		int i;

		if(enable) {
			if(!dt_flag) {
				/* delta coded in frequency direction */
				index[0] = 0+index[0];
				index[0] = delta_clip(index[0], min_index, max_index);

				for(i = 1; i<nr_par; i++) {
					index[i] = index[i-1]+index[i];
					index[i] = delta_clip(index[i], min_index, max_index);
				}
			}
			else {
				/* delta coded in time direction */
				for(i = 0; i<nr_par; i++) {
                //int8_t tmp2;
					//int8_t tmp = index[i];

					//printf("%d %d\n", index_prev[i*stride], index[i]);
					//printf("%d\n", index[i]);
					index[i] = index_prev[i*stride]+index[i];
					//tmp2 = index[i];
					index[i] = delta_clip(index[i], min_index, max_index);

					//if (iid)
					//{
					//    if (index[i] == 7)
					//    {
					//        printf("%d %d %d\n", index_prev[i*stride], tmp, tmp2);
					//    }
					//}
				}
			}
		}
		else {
			/* set indices to zero */
			for(i = 0; i<nr_par; i++) {
				index[i] = 0;
			}
		}

		/* coarse */
		if(stride==2) {
			for(i = (nr_par<<1)-1; i>0; i--) {
				index[i] = index[i>>1];
			}
		}
	}

	/* delta modulo decode array */
	/* in: log2 value of the modulo value to allow using AND instead of MOD */
	private void delta_modulo_decode(boolean enable, int[] index, int[] index_prev,
		boolean dt_flag, int nr_par, int stride,
		int and_modulo) {
		int i;

		if(enable) {
			if(!dt_flag) {
				/* delta coded in frequency direction */
				index[0] = 0+index[0];
				index[0] &= and_modulo;

				for(i = 1; i<nr_par; i++) {
					index[i] = index[i-1]+index[i];
					index[i] &= and_modulo;
				}
			}
			else {
				/* delta coded in time direction */
				for(i = 0; i<nr_par; i++) {
					index[i] = index_prev[i*stride]+index[i];
					index[i] &= and_modulo;
				}
			}
		}
		else {
			/* set indices to zero */
			for(i = 0; i<nr_par; i++) {
				index[i] = 0;
			}
		}

		/* coarse */
		if(stride==2) {
			index[0] = 0;
			for(i = (nr_par<<1)-1; i>0; i--) {
				index[i] = index[i>>1];
			}
		}
	}

	private void map20indexto34(int[] index, int bins) {
		//index[0] = index[0];
		index[1] = (index[0]+index[1])/2;
		index[2] = index[1];
		index[3] = index[2];
		index[4] = (index[2]+index[3])/2;
		index[5] = index[3];
		index[6] = index[4];
		index[7] = index[4];
		index[8] = index[5];
		index[9] = index[5];
		index[10] = index[6];
		index[11] = index[7];
		index[12] = index[8];
		index[13] = index[8];
		index[14] = index[9];
		index[15] = index[9];
		index[16] = index[10];

		if(bins==34) {
			index[17] = index[11];
			index[18] = index[12];
			index[19] = index[13];
			index[20] = index[14];
			index[21] = index[14];
			index[22] = index[15];
			index[23] = index[15];
			index[24] = index[16];
			index[25] = index[16];
			index[26] = index[17];
			index[27] = index[17];
			index[28] = index[18];
			index[29] = index[18];
			index[30] = index[18];
			index[31] = index[18];
			index[32] = index[19];
			index[33] = index[19];
		}
	}

	/* parse the bitstream data decoded in ps_data() */
	private void ps_data_decode() {
		int env, bin;

		/* ps data not available, use data from previous frame */
		if(this.ps_data_available==0) {
			this.num_env = 0;
		}

		for(env = 0; env<this.num_env; env++) {
			int[] iid_index_prev;
			int[] icc_index_prev;
			int[] ipd_index_prev;
			int[] opd_index_prev;

			int num_iid_steps = (this.iid_mode<3) ? 7 : 15 /*fine quant*/;

			if(env==0) {
				/* take last envelope from previous frame */
				iid_index_prev = this.iid_index_prev;
				icc_index_prev = this.icc_index_prev;
				ipd_index_prev = this.ipd_index_prev;
				opd_index_prev = this.opd_index_prev;
			}
			else {
				/* take index values from previous envelope */
				iid_index_prev = this.iid_index[env-1];
				icc_index_prev = this.icc_index[env-1];
				ipd_index_prev = this.ipd_index[env-1];
				opd_index_prev = this.opd_index[env-1];
			}

//        iid = 1;
        /* delta decode iid parameters */
			delta_decode(this.enable_iid, this.iid_index[env], iid_index_prev,
				this.iid_dt[env], this.nr_iid_par,
				(this.iid_mode==0||this.iid_mode==3) ? 2 : 1,
				-num_iid_steps, num_iid_steps);
//        iid = 0;

			/* delta decode icc parameters */
			delta_decode(this.enable_icc, this.icc_index[env], icc_index_prev,
				this.icc_dt[env], this.nr_icc_par,
				(this.icc_mode==0||this.icc_mode==3) ? 2 : 1,
				0, 7);

			/* delta modulo decode ipd parameters */
			delta_modulo_decode(this.enable_ipdopd, this.ipd_index[env], ipd_index_prev,
				this.ipd_dt[env], this.nr_ipdopd_par, 1, 7);

			/* delta modulo decode opd parameters */
			delta_modulo_decode(this.enable_ipdopd, this.opd_index[env], opd_index_prev,
				this.opd_dt[env], this.nr_ipdopd_par, 1, 7);
		}

		/* handle error case */
		if(this.num_env==0) {
			/* force to 1 */
			this.num_env = 1;

			if(this.enable_iid) {
				for(bin = 0; bin<34; bin++) {
					this.iid_index[0][bin] = this.iid_index_prev[bin];
				}
			}
			else {
				for(bin = 0; bin<34; bin++) {
					this.iid_index[0][bin] = 0;
				}
			}

			if(this.enable_icc) {
				for(bin = 0; bin<34; bin++) {
					this.icc_index[0][bin] = this.icc_index_prev[bin];
				}
			}
			else {
				for(bin = 0; bin<34; bin++) {
					this.icc_index[0][bin] = 0;
				}
			}

			if(this.enable_ipdopd) {
				for(bin = 0; bin<17; bin++) {
					this.ipd_index[0][bin] = this.ipd_index_prev[bin];
					this.opd_index[0][bin] = this.opd_index_prev[bin];
				}
			}
			else {
				for(bin = 0; bin<17; bin++) {
					this.ipd_index[0][bin] = 0;
					this.opd_index[0][bin] = 0;
				}
			}
		}

		/* update previous indices */
		for(bin = 0; bin<34; bin++) {
			this.iid_index_prev[bin] = this.iid_index[this.num_env-1][bin];
		}
		for(bin = 0; bin<34; bin++) {
			this.icc_index_prev[bin] = this.icc_index[this.num_env-1][bin];
		}
		for(bin = 0; bin<17; bin++) {
			this.ipd_index_prev[bin] = this.ipd_index[this.num_env-1][bin];
			this.opd_index_prev[bin] = this.opd_index[this.num_env-1][bin];
		}

		this.ps_data_available = 0;

		if(this.frame_class==0) {
			this.border_position[0] = 0;
			for(env = 1; env<this.num_env; env++) {
				this.border_position[env] = (env*this.numTimeSlotsRate)/this.num_env;
			}
			this.border_position[this.num_env] = this.numTimeSlotsRate;
		}
		else {
			this.border_position[0] = 0;

			if(this.border_position[this.num_env]<this.numTimeSlotsRate) {
				for(bin = 0; bin<34; bin++) {
					this.iid_index[this.num_env][bin] = this.iid_index[this.num_env-1][bin];
					this.icc_index[this.num_env][bin] = this.icc_index[this.num_env-1][bin];
				}
				for(bin = 0; bin<17; bin++) {
					this.ipd_index[this.num_env][bin] = this.ipd_index[this.num_env-1][bin];
					this.opd_index[this.num_env][bin] = this.opd_index[this.num_env-1][bin];
				}
				this.num_env++;
				this.border_position[this.num_env] = this.numTimeSlotsRate;
			}

			for(env = 1; env<this.num_env; env++) {
				int thr = this.numTimeSlotsRate-(this.num_env-env);

				if(this.border_position[env]>thr) {
					this.border_position[env] = thr;
				}
				else {
					thr = this.border_position[env-1]+1;
					if(this.border_position[env]<thr) {
						this.border_position[env] = thr;
					}
				}
			}
		}

		/* make sure that the indices of all parameters can be mapped
		 * to the same hybrid synthesis filterbank
		 */
		if(this.use34hybrid_bands) {
			for(env = 0; env<this.num_env; env++) {
				if(this.iid_mode!=2&&this.iid_mode!=5)
					map20indexto34(this.iid_index[env], 34);
				if(this.icc_mode!=2&&this.icc_mode!=5)
					map20indexto34(this.icc_index[env], 34);
				if(this.ipd_mode!=2&&this.ipd_mode!=5) {
					map20indexto34(this.ipd_index[env], 17);
					map20indexto34(this.opd_index[env], 17);
				}
			}
		}
	}

	/* decorrelate the mono signal using an allpass filter */
	private void ps_decorrelate(float[][][] X_left, float[][][] X_right,
		float[][][] X_hybrid_left, float[][][] X_hybrid_right) {
		int gr, n, m, bk;
		int temp_delay = 0;
		int sb, maxsb;
		int[] temp_delay_ser = new int[NO_ALLPASS_LINKS];
		float P_SmoothPeakDecayDiffNrg, nrg;
		float[][] P = new float[32][34];
		float[][] G_TransientRatio = new float[32][34];
		float[] inputLeft = new float[2];

		/* chose hybrid filterbank: 20 or 34 band case */
		float[][] Phi_Fract_SubQmf;
		if(this.use34hybrid_bands) {
			Phi_Fract_SubQmf = Phi_Fract_SubQmf34;
		}
		else {
			Phi_Fract_SubQmf = Phi_Fract_SubQmf20;
		}

		/* clear the energy values */
		for(n = 0; n<32; n++) {
			for(bk = 0; bk<34; bk++) {
				P[n][bk] = 0;
			}
		}

		/* calculate the energy in each parameter band b(k) */
		for(gr = 0; gr<this.num_groups; gr++) {
			/* select the parameter index b(k) to which this group belongs */
			bk = (~NEGATE_IPD_MASK)&this.map_group2bk[gr];

			/* select the upper subband border for this group */
			maxsb = (gr<this.num_hybrid_groups) ? this.group_border[gr]+1 : this.group_border[gr+1];

			for(sb = this.group_border[gr]; sb<maxsb; sb++) {
				for(n = this.border_position[0]; n<this.border_position[this.num_env]; n++) {

					/* input from hybrid subbands or QMF subbands */
					if(gr<this.num_hybrid_groups) {
						inputLeft[0] = X_hybrid_left[n][sb][0];
						inputLeft[1] = X_hybrid_left[n][sb][1];
					}
					else {
						inputLeft[0] = X_left[n][sb][0];
						inputLeft[1] = X_left[n][sb][1];
					}

					/* accumulate energy */
					P[n][bk] += (inputLeft[0]*inputLeft[0])+(inputLeft[1]*inputLeft[1]);
				}
			}
		}

		/* calculate transient reduction ratio for each parameter band b(k) */
		for(bk = 0; bk<this.nr_par_bands; bk++) {
			for(n = this.border_position[0]; n<this.border_position[this.num_env]; n++) {
				float gamma = 1.5f;

				this.P_PeakDecayNrg[bk] = (this.P_PeakDecayNrg[bk]*this.alpha_decay);
				if(this.P_PeakDecayNrg[bk]<P[n][bk])
					this.P_PeakDecayNrg[bk] = P[n][bk];

				/* apply smoothing filter to peak decay energy */
				P_SmoothPeakDecayDiffNrg = this.P_SmoothPeakDecayDiffNrg_prev[bk];
				P_SmoothPeakDecayDiffNrg += ((this.P_PeakDecayNrg[bk]-P[n][bk]-this.P_SmoothPeakDecayDiffNrg_prev[bk])*alpha_smooth);
				this.P_SmoothPeakDecayDiffNrg_prev[bk] = P_SmoothPeakDecayDiffNrg;

				/* apply smoothing filter to energy */
				nrg = this.P_prev[bk];
				nrg += ((P[n][bk]-this.P_prev[bk])*this.alpha_smooth);
				this.P_prev[bk] = nrg;

				/* calculate transient ratio */
				if((P_SmoothPeakDecayDiffNrg*gamma)<=nrg) {
					G_TransientRatio[n][bk] = 1.0f;
				}
				else {
					G_TransientRatio[n][bk] = (nrg/(P_SmoothPeakDecayDiffNrg*gamma));
				}
			}
		}

		/* apply stereo decorrelation filter to the signal */
		for(gr = 0; gr<this.num_groups; gr++) {
			if(gr<this.num_hybrid_groups)
				maxsb = this.group_border[gr]+1;
			else
				maxsb = this.group_border[gr+1];

			/* QMF channel */
			for(sb = this.group_border[gr]; sb<maxsb; sb++) {
				float g_DecaySlope;
				float[] g_DecaySlope_filt = new float[NO_ALLPASS_LINKS];

				/* g_DecaySlope: [0..1] */
				if(gr<this.num_hybrid_groups||sb<=this.decay_cutoff) {
					g_DecaySlope = 1.0f;
				}
				else {
					int decay = this.decay_cutoff-sb;
					if(decay<=-20 /* -1/DECAY_SLOPE */) {
						g_DecaySlope = 0;
					}
					else {
						/* decay(int)*decay_slope(frac) = g_DecaySlope(frac) */
						g_DecaySlope = 1.0f+DECAY_SLOPE*decay;
					}
				}

				/* calculate g_DecaySlope_filt for every m multiplied by filter_a[m] */
				for(m = 0; m<NO_ALLPASS_LINKS; m++) {
					g_DecaySlope_filt[m] = g_DecaySlope*filter_a[m];
				}

				/* set delay indices */
				temp_delay = this.saved_delay;
				for(n = 0; n<NO_ALLPASS_LINKS; n++) {
					temp_delay_ser[n] = this.delay_buf_index_ser[n];
				}

				for(n = this.border_position[0]; n<this.border_position[this.num_env]; n++) {
					float[] tmp = new float[2], tmp0 = new float[2], R0 = new float[2];

					if(gr<this.num_hybrid_groups) {
						/* hybrid filterbank input */
						inputLeft[0] = X_hybrid_left[n][sb][0];
						inputLeft[1] = X_hybrid_left[n][sb][1];
					}
					else {
						/* QMF filterbank input */
						inputLeft[0] = X_left[n][sb][0];
						inputLeft[1] = X_left[n][sb][1];
					}

					if(sb>this.nr_allpass_bands&&gr>=this.num_hybrid_groups) {
						/* delay */

						/* never hybrid subbands here, always QMF subbands */
						tmp[0] = this.delay_Qmf[this.delay_buf_index_delay[sb]][sb][0];
						tmp[1] = this.delay_Qmf[this.delay_buf_index_delay[sb]][sb][1];
						R0[0] = tmp[0];
						R0[1] = tmp[1];
						this.delay_Qmf[this.delay_buf_index_delay[sb]][sb][0] = inputLeft[0];
						this.delay_Qmf[this.delay_buf_index_delay[sb]][sb][1] = inputLeft[1];
					}
					else {
						/* allpass filter */
						//int m;
						float[] Phi_Fract = new float[2];

						/* fetch parameters */
						if(gr<this.num_hybrid_groups) {
							/* select data from the hybrid subbands */
							tmp0[0] = this.delay_SubQmf[temp_delay][sb][0];
							tmp0[1] = this.delay_SubQmf[temp_delay][sb][1];

							this.delay_SubQmf[temp_delay][sb][0] = inputLeft[0];
							this.delay_SubQmf[temp_delay][sb][1] = inputLeft[1];

							Phi_Fract[0] = Phi_Fract_SubQmf[sb][0];
							Phi_Fract[1] = Phi_Fract_SubQmf[sb][1];
						}
						else {
							/* select data from the QMF subbands */
							tmp0[0] = this.delay_Qmf[temp_delay][sb][0];
							tmp0[1] = this.delay_Qmf[temp_delay][sb][1];

							this.delay_Qmf[temp_delay][sb][0] = inputLeft[0];
							this.delay_Qmf[temp_delay][sb][1] = inputLeft[1];

							Phi_Fract[0] = Phi_Fract_Qmf[sb][0];
							Phi_Fract[1] = Phi_Fract_Qmf[sb][1];
						}

						/* z^(-2) * Phi_Fract[k] */
						tmp[0] = (tmp[0]*Phi_Fract[0])+(tmp0[1]*Phi_Fract[1]);
						tmp[1] = (tmp0[1]*Phi_Fract[0])-(tmp0[0]*Phi_Fract[1]);

						R0[0] = tmp[0];
						R0[1] = tmp[1];
						for(m = 0; m<NO_ALLPASS_LINKS; m++) {
							float[] Q_Fract_allpass = new float[2], tmp2 = new float[2];

							/* fetch parameters */
							if(gr<this.num_hybrid_groups) {
								/* select data from the hybrid subbands */
								tmp0[0] = this.delay_SubQmf_ser[m][temp_delay_ser[m]][sb][0];
								tmp0[1] = this.delay_SubQmf_ser[m][temp_delay_ser[m]][sb][1];

								if(this.use34hybrid_bands) {
									Q_Fract_allpass[0] = Q_Fract_allpass_SubQmf34[sb][m][0];
									Q_Fract_allpass[1] = Q_Fract_allpass_SubQmf34[sb][m][1];
								}
								else {
									Q_Fract_allpass[0] = Q_Fract_allpass_SubQmf20[sb][m][0];
									Q_Fract_allpass[1] = Q_Fract_allpass_SubQmf20[sb][m][1];
								}
							}
							else {
								/* select data from the QMF subbands */
								tmp0[0] = this.delay_Qmf_ser[m][temp_delay_ser[m]][sb][0];
								tmp0[1] = this.delay_Qmf_ser[m][temp_delay_ser[m]][sb][1];

								Q_Fract_allpass[0] = Q_Fract_allpass_Qmf[sb][m][0];
								Q_Fract_allpass[1] = Q_Fract_allpass_Qmf[sb][m][1];
							}

							/* delay by a fraction */
							/* z^(-d(m)) * Q_Fract_allpass[k,m] */
							tmp[0] = (tmp0[0]*Q_Fract_allpass[0])+(tmp0[1]*Q_Fract_allpass[1]);
							tmp[1] = (tmp0[1]*Q_Fract_allpass[0])-(tmp0[0]*Q_Fract_allpass[1]);

							/* -a(m) * g_DecaySlope[k] */
							tmp[0] += -(g_DecaySlope_filt[m]*R0[0]);
							tmp[1] += -(g_DecaySlope_filt[m]*R0[1]);

							/* -a(m) * g_DecaySlope[k] * Q_Fract_allpass[k,m] * z^(-d(m)) */
							tmp2[0] = R0[0]+(g_DecaySlope_filt[m]*tmp[0]);
							tmp2[1] = R0[1]+(g_DecaySlope_filt[m]*tmp[1]);

							/* store sample */
							if(gr<this.num_hybrid_groups) {
								this.delay_SubQmf_ser[m][temp_delay_ser[m]][sb][0] = tmp2[0];
								this.delay_SubQmf_ser[m][temp_delay_ser[m]][sb][1] = tmp2[1];
							}
							else {
								this.delay_Qmf_ser[m][temp_delay_ser[m]][sb][0] = tmp2[0];
								this.delay_Qmf_ser[m][temp_delay_ser[m]][sb][1] = tmp2[1];
							}

							/* store for next iteration (or as output value if last iteration) */
							R0[0] = tmp[0];
							R0[1] = tmp[1];
						}
					}

					/* select b(k) for reading the transient ratio */
					bk = (~NEGATE_IPD_MASK)&this.map_group2bk[gr];

					/* duck if a past transient is found */
					R0[0] = (G_TransientRatio[n][bk]*R0[0]);
					R0[1] = (G_TransientRatio[n][bk]*R0[1]);

					if(gr<this.num_hybrid_groups) {
						/* hybrid */
						X_hybrid_right[n][sb][0] = R0[0];
						X_hybrid_right[n][sb][1] = R0[1];
					}
					else {
						/* QMF */
						X_right[n][sb][0] = R0[0];
						X_right[n][sb][1] = R0[1];
					}

					/* Update delay buffer index */
					if(++temp_delay>=2) {
						temp_delay = 0;
					}

					/* update delay indices */
					if(sb>this.nr_allpass_bands&&gr>=this.num_hybrid_groups) {
						/* delay_D depends on the samplerate, it can hold the values 14 and 1 */
						if(++this.delay_buf_index_delay[sb]>=this.delay_D[sb]) {
							this.delay_buf_index_delay[sb] = 0;
						}
					}

					for(m = 0; m<NO_ALLPASS_LINKS; m++) {
						if(++temp_delay_ser[m]>=this.num_sample_delay_ser[m]) {
							temp_delay_ser[m] = 0;
						}
					}
				}
			}
		}

		/* update delay indices */
		this.saved_delay = temp_delay;
		for(m = 0; m<NO_ALLPASS_LINKS; m++) {
			this.delay_buf_index_ser[m] = temp_delay_ser[m];
		}
	}

	private float magnitude_c(float[] c) {
		return (float) Math.sqrt(c[0]*c[0]+c[1]*c[1]);
	}

	private void ps_mix_phase(float[][][] X_left, float[][][] X_right,
		float[][][] X_hybrid_left, float[][][] X_hybrid_right) {
		int n;
		int gr;
		int bk = 0;
		int sb, maxsb;
		int env;
		int nr_ipdopd_par;
		float[] h11 = new float[2], h12 = new float[2], h21 = new float[2], h22 = new float[2];
		float[] H11 = new float[2], H12 = new float[2], H21 = new float[2], H22 = new float[2];
		float[] deltaH11 = new float[2], deltaH12 = new float[2], deltaH21 = new float[2], deltaH22 = new float[2];
		float[] tempLeft = new float[2];
		float[] tempRight = new float[2];
		float[] phaseLeft = new float[2];
		float[] phaseRight = new float[2];
		float L;
		float[] sf_iid;
		int no_iid_steps;

		if(this.iid_mode>=3) {
			no_iid_steps = 15;
			sf_iid = sf_iid_fine;
		}
		else {
			no_iid_steps = 7;
			sf_iid = sf_iid_normal;
		}

		if(this.ipd_mode==0||this.ipd_mode==3) {
			nr_ipdopd_par = 11; /* resolution */

		}
		else {
			nr_ipdopd_par = this.nr_ipdopd_par;
		}

		for(gr = 0; gr<this.num_groups; gr++) {
			bk = (~NEGATE_IPD_MASK)&this.map_group2bk[gr];

			/* use one channel per group in the subqmf domain */
			maxsb = (gr<this.num_hybrid_groups) ? this.group_border[gr]+1 : this.group_border[gr+1];

			for(env = 0; env<this.num_env; env++) {
				if(this.icc_mode<3) {
					/* type 'A' mixing as described in 8.6.4.6.2.1 */
					float c_1, c_2;
					float cosa, sina;
					float cosb, sinb;
					float ab1, ab2;
					float ab3, ab4;

					/*
					 c_1 = sqrt(2.0 / (1.0 + pow(10.0, quant_iid[no_iid_steps + iid_index] / 10.0)));
					 c_2 = sqrt(2.0 / (1.0 + pow(10.0, quant_iid[no_iid_steps - iid_index] / 10.0)));
					 alpha = 0.5 * acos(quant_rho[icc_index]);
					 beta = alpha * ( c_1 - c_2 ) / sqrt(2.0);
					 */
					//printf("%d\n", ps.iid_index[env][bk]);

					/* calculate the scalefactors c_1 and c_2 from the intensity differences */
					c_1 = sf_iid[no_iid_steps+this.iid_index[env][bk]];
					c_2 = sf_iid[no_iid_steps-this.iid_index[env][bk]];

					/* calculate alpha and beta using the ICC parameters */
					cosa = cos_alphas[this.icc_index[env][bk]];
					sina = sin_alphas[this.icc_index[env][bk]];

					if(this.iid_mode>=3) {
						if(this.iid_index[env][bk]<0) {
							cosb = cos_betas_fine[-this.iid_index[env][bk]][this.icc_index[env][bk]];
							sinb = -sin_betas_fine[-this.iid_index[env][bk]][this.icc_index[env][bk]];
						}
						else {
							cosb = cos_betas_fine[this.iid_index[env][bk]][this.icc_index[env][bk]];
							sinb = sin_betas_fine[this.iid_index[env][bk]][this.icc_index[env][bk]];
						}
					}
					else {
						if(this.iid_index[env][bk]<0) {
							cosb = cos_betas_normal[-this.iid_index[env][bk]][this.icc_index[env][bk]];
							sinb = -sin_betas_normal[-this.iid_index[env][bk]][this.icc_index[env][bk]];
						}
						else {
							cosb = cos_betas_normal[this.iid_index[env][bk]][this.icc_index[env][bk]];
							sinb = sin_betas_normal[this.iid_index[env][bk]][this.icc_index[env][bk]];
						}
					}

					ab1 = (cosb*cosa);
					ab2 = (sinb*sina);
					ab3 = (sinb*cosa);
					ab4 = (cosb*sina);

					/* h_xy: COEF */
					h11[0] = (c_2*(ab1-ab2));
					h12[0] = (c_1*(ab1+ab2));
					h21[0] = (c_2*(ab3+ab4));
					h22[0] = (c_1*(ab3-ab4));
				}
				else {
					/* type 'B' mixing as described in 8.6.4.6.2.2 */
					float sina, cosa;
					float cosg, sing;

					if(this.iid_mode>=3) {
						int abs_iid = Math.abs(this.iid_index[env][bk]);

						cosa = sincos_alphas_B_fine[no_iid_steps+this.iid_index[env][bk]][this.icc_index[env][bk]];
						sina = sincos_alphas_B_fine[30-(no_iid_steps+this.iid_index[env][bk])][this.icc_index[env][bk]];
						cosg = cos_gammas_fine[abs_iid][this.icc_index[env][bk]];
						sing = sin_gammas_fine[abs_iid][this.icc_index[env][bk]];
					}
					else {
						int abs_iid = Math.abs(this.iid_index[env][bk]);

						cosa = sincos_alphas_B_normal[no_iid_steps+this.iid_index[env][bk]][this.icc_index[env][bk]];
						sina = sincos_alphas_B_normal[14-(no_iid_steps+this.iid_index[env][bk])][this.icc_index[env][bk]];
						cosg = cos_gammas_normal[abs_iid][this.icc_index[env][bk]];
						sing = sin_gammas_normal[abs_iid][this.icc_index[env][bk]];
					}

					h11[0] = (COEF_SQRT2*(cosa*cosg));
					h12[0] = (COEF_SQRT2*(sina*cosg));
					h21[0] = (COEF_SQRT2*(-cosa*sing));
					h22[0] = (COEF_SQRT2*(sina*sing));
				}

				/* calculate phase rotation parameters H_xy */
				/* note that the imaginary part of these parameters are only calculated when
				 IPD and OPD are enabled
				 */
				if((this.enable_ipdopd)&&(bk<nr_ipdopd_par)) {
					float xy, pq, xypq;

					/* ringbuffer index */
					int i = this.phase_hist;

					/* previous value */
					tempLeft[0] = (this.ipd_prev[bk][i][0]*0.25f);
					tempLeft[1] = (this.ipd_prev[bk][i][1]*0.25f);
					tempRight[0] = (this.opd_prev[bk][i][0]*0.25f);
					tempRight[1] = (this.opd_prev[bk][i][1]*0.25f);

					/* save current value */
					this.ipd_prev[bk][i][0] = ipdopd_cos_tab[Math.abs(this.ipd_index[env][bk])];
					this.ipd_prev[bk][i][1] = ipdopd_sin_tab[Math.abs(this.ipd_index[env][bk])];
					this.opd_prev[bk][i][0] = ipdopd_cos_tab[Math.abs(this.opd_index[env][bk])];
					this.opd_prev[bk][i][1] = ipdopd_sin_tab[Math.abs(this.opd_index[env][bk])];

					/* add current value */
					tempLeft[0] += this.ipd_prev[bk][i][0];
					tempLeft[1] += this.ipd_prev[bk][i][1];
					tempRight[0] += this.opd_prev[bk][i][0];
					tempRight[1] += this.opd_prev[bk][i][1];

					/* ringbuffer index */
					if(i==0) {
						i = 2;
					}
					i--;

					/* get value before previous */
					tempLeft[0] += (this.ipd_prev[bk][i][0]*0.5f);
					tempLeft[1] += (this.ipd_prev[bk][i][1]*0.5f);
					tempRight[0] += (this.opd_prev[bk][i][0]*0.5f);
					tempRight[1] += (this.opd_prev[bk][i][1]*0.5f);

					xy = magnitude_c(tempRight);
					pq = magnitude_c(tempLeft);

					if(xy!=0) {
						phaseLeft[0] = (tempRight[0]/xy);
						phaseLeft[1] = (tempRight[1]/xy);
					}
					else {
						phaseLeft[0] = 0;
						phaseLeft[1] = 0;
					}

					xypq = (xy*pq);

					if(xypq!=0) {
						float tmp1 = (tempRight[0]*tempLeft[0])+(tempRight[1]*tempLeft[1]);
						float tmp2 = (tempRight[1]*tempLeft[0])-(tempRight[0]*tempLeft[1]);

						phaseRight[0] = (tmp1/xypq);
						phaseRight[1] = (tmp2/xypq);
					}
					else {
						phaseRight[0] = 0;
						phaseRight[1] = 0;
					}

					/* MUL_F(COEF, REAL) = COEF */
					h11[1] = (h11[0]*phaseLeft[1]);
					h12[1] = (h12[0]*phaseRight[1]);
					h21[1] = (h21[0]*phaseLeft[1]);
					h22[1] = (h22[0]*phaseRight[1]);

					h11[0] = (h11[0]*phaseLeft[0]);
					h12[0] = (h12[0]*phaseRight[0]);
					h21[0] = (h21[0]*phaseLeft[0]);
					h22[0] = (h22[0]*phaseRight[0]);
				}

				/* length of the envelope n_e+1 - n_e (in time samples) */
				/* 0 < L <= 32: integer */
				L = (float) (this.border_position[env+1]-this.border_position[env]);

				/* obtain final H_xy by means of linear interpolation */
				deltaH11[0] = (h11[0]-this.h11_prev[gr][0])/L;
				deltaH12[0] = (h12[0]-this.h12_prev[gr][0])/L;
				deltaH21[0] = (h21[0]-this.h21_prev[gr][0])/L;
				deltaH22[0] = (h22[0]-this.h22_prev[gr][0])/L;

				H11[0] = this.h11_prev[gr][0];
				H12[0] = this.h12_prev[gr][0];
				H21[0] = this.h21_prev[gr][0];
				H22[0] = this.h22_prev[gr][0];

				this.h11_prev[gr][0] = h11[0];
				this.h12_prev[gr][0] = h12[0];
				this.h21_prev[gr][0] = h21[0];
				this.h22_prev[gr][0] = h22[0];

				/* only calculate imaginary part when needed */
				if((this.enable_ipdopd)&&(bk<nr_ipdopd_par)) {
					/* obtain final H_xy by means of linear interpolation */
					deltaH11[1] = (h11[1]-this.h11_prev[gr][1])/L;
					deltaH12[1] = (h12[1]-this.h12_prev[gr][1])/L;
					deltaH21[1] = (h21[1]-this.h21_prev[gr][1])/L;
					deltaH22[1] = (h22[1]-this.h22_prev[gr][1])/L;

					H11[1] = this.h11_prev[gr][1];
					H12[1] = this.h12_prev[gr][1];
					H21[1] = this.h21_prev[gr][1];
					H22[1] = this.h22_prev[gr][1];

					if((NEGATE_IPD_MASK&this.map_group2bk[gr])!=0) {
						deltaH11[1] = -deltaH11[1];
						deltaH12[1] = -deltaH12[1];
						deltaH21[1] = -deltaH21[1];
						deltaH22[1] = -deltaH22[1];

						H11[1] = -H11[1];
						H12[1] = -H12[1];
						H21[1] = -H21[1];
						H22[1] = -H22[1];
					}

					this.h11_prev[gr][1] = h11[1];
					this.h12_prev[gr][1] = h12[1];
					this.h21_prev[gr][1] = h21[1];
					this.h22_prev[gr][1] = h22[1];
				}

				/* apply H_xy to the current envelope band of the decorrelated subband */
				for(n = this.border_position[env]; n<this.border_position[env+1]; n++) {
					/* addition finalises the interpolation over every n */
					H11[0] += deltaH11[0];
					H12[0] += deltaH12[0];
					H21[0] += deltaH21[0];
					H22[0] += deltaH22[0];
					if((this.enable_ipdopd)&&(bk<nr_ipdopd_par)) {
						H11[1] += deltaH11[1];
						H12[1] += deltaH12[1];
						H21[1] += deltaH21[1];
						H22[1] += deltaH22[1];
					}

					/* channel is an alias to the subband */
					for(sb = this.group_border[gr]; sb<maxsb; sb++) {
						float[] inLeft = new float[2], inRight = new float[2];

						/* load decorrelated samples */
						if(gr<this.num_hybrid_groups) {
							inLeft[0] = X_hybrid_left[n][sb][0];
							inLeft[1] = X_hybrid_left[n][sb][1];
							inRight[0] = X_hybrid_right[n][sb][0];
							inRight[1] = X_hybrid_right[n][sb][1];
						}
						else {
							inLeft[0] = X_left[n][sb][0];
							inLeft[1] = X_left[n][sb][1];
							inRight[0] = X_right[n][sb][0];
							inRight[1] = X_right[n][sb][1];
						}

						/* apply mixing */
						tempLeft[0] = (H11[0]*inLeft[0])+(H21[0]*inRight[0]);
						tempLeft[1] = (H11[0]*inLeft[1])+(H21[0]*inRight[1]);
						tempRight[0] = (H12[0]*inLeft[0])+(H22[0]*inRight[0]);
						tempRight[1] = (H12[0]*inLeft[1])+(H22[0]*inRight[1]);

						/* only perform imaginary operations when needed */
						if((this.enable_ipdopd)&&(bk<nr_ipdopd_par)) {
							/* apply rotation */
							tempLeft[0] -= (H11[1]*inLeft[1])+(H21[1]*inRight[1]);
							tempLeft[1] += (H11[1]*inLeft[0])+(H21[1]*inRight[0]);
							tempRight[0] -= (H12[1]*inLeft[1])+(H22[1]*inRight[1]);
							tempRight[1] += (H12[1]*inLeft[0])+(H22[1]*inRight[0]);
						}

						/* store final samples */
						if(gr<this.num_hybrid_groups) {
							X_hybrid_left[n][sb][0] = tempLeft[0];
							X_hybrid_left[n][sb][1] = tempLeft[1];
							X_hybrid_right[n][sb][0] = tempRight[0];
							X_hybrid_right[n][sb][1] = tempRight[1];
						}
						else {
							X_left[n][sb][0] = tempLeft[0];
							X_left[n][sb][1] = tempLeft[1];
							X_right[n][sb][0] = tempRight[0];
							X_right[n][sb][1] = tempRight[1];
						}
					}
				}

				/* shift phase smoother's circular buffer index */
				this.phase_hist++;
				if(this.phase_hist==2) {
					this.phase_hist = 0;
				}
			}
		}
	}

	/* main Parametric Stereo decoding function */
	public int process(float[][][] X_left, float[][][] X_right) {
		float[][][] X_hybrid_left = new float[32][32][2];
		float[][][] X_hybrid_right = new float[32][32][2];

		/* delta decoding of the bitstream data */
		ps_data_decode();

		/* set up some parameters depending on filterbank type */
		if(this.use34hybrid_bands) {
			this.group_border = group_border34;
			this.map_group2bk = map_group2bk34;
			this.num_groups = 32+18;
			this.num_hybrid_groups = 32;
			this.nr_par_bands = 34;
			this.decay_cutoff = 5;
		}
		else {
			this.group_border = group_border20;
			this.map_group2bk = map_group2bk20;
			this.num_groups = 10+12;
			this.num_hybrid_groups = 10;
			this.nr_par_bands = 20;
			this.decay_cutoff = 3;
		}

		/* Perform further analysis on the lowest subbands to get a higher
		 * frequency resolution
		 */
		hyb.hybrid_analysis(X_left, X_hybrid_left,
			this.use34hybrid_bands, this.numTimeSlotsRate);

		/* decorrelate mono signal */
		ps_decorrelate(X_left, X_right, X_hybrid_left, X_hybrid_right);

		/* apply mixing and phase parameters */
		ps_mix_phase(X_left, X_right, X_hybrid_left, X_hybrid_right);

		/* hybrid synthesis, to rebuild the SBR QMF matrices */
		hyb.hybrid_synthesis(X_left, X_hybrid_left,
			this.use34hybrid_bands, this.numTimeSlotsRate);

		hyb.hybrid_synthesis(X_right, X_hybrid_right,
			this.use34hybrid_bands, this.numTimeSlotsRate);

		return 0;
	}

}


package net.sourceforge.jaad.aac;

/**
 * This class is part of JAAD ( jaadec.sourceforge.net ) that is distributed
 * under the Public Domain license. Code changes provided by the JCodec project
 * are distributed under FreeBSD license.
 *
 * An enumeration that represents all possible sample frequencies AAC data can
 * have.
 *
 * @author in-somnia
 */
public final class SampleFrequency {

    public final static SampleFrequency SAMPLE_FREQUENCY_96000 = new SampleFrequency(0, 96000, new int[] { 33, 512 },
            new int[] { 31, 9 });
    public final static SampleFrequency SAMPLE_FREQUENCY_88200 = new SampleFrequency(1, 88200, new int[] { 33, 512 },
            new int[] { 31, 9 });
    public final static SampleFrequency SAMPLE_FREQUENCY_64000 = new SampleFrequency(2, 64000, new int[] { 38, 664 },
            new int[] { 34, 10 });
    public final static SampleFrequency SAMPLE_FREQUENCY_48000 = new SampleFrequency(3, 48000, new int[] { 40, 672 },
            new int[] { 40, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_44100 = new SampleFrequency(4, 44100, new int[] { 40, 672 },
            new int[] { 42, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_32000 = new SampleFrequency(5, 32000, new int[] { 40, 672 },
            new int[] { 51, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_24000 = new SampleFrequency(6, 24000, new int[] { 41, 652 },
            new int[] { 46, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_22050 = new SampleFrequency(7, 22050, new int[] { 41, 652 },
            new int[] { 46, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_16000 = new SampleFrequency(8, 16000, new int[] { 37, 664 },
            new int[] { 42, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_12000 = new SampleFrequency(9, 12000, new int[] { 37, 664 },
            new int[] { 42, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_11025 = new SampleFrequency(10, 11025, new int[] { 37, 664 },
            new int[] { 42, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_8000 = new SampleFrequency(11, 8000, new int[] { 34, 664 },
            new int[] { 39, 14 });
    public final static SampleFrequency SAMPLE_FREQUENCY_NONE = new SampleFrequency(-1, 0, new int[] { 0, 0 },
            new int[] { 0, 0 });

    private final static SampleFrequency[] _values = new SampleFrequency[] { SAMPLE_FREQUENCY_96000,
            SAMPLE_FREQUENCY_88200, SAMPLE_FREQUENCY_64000, SAMPLE_FREQUENCY_48000, SAMPLE_FREQUENCY_44100,
            SAMPLE_FREQUENCY_32000, SAMPLE_FREQUENCY_24000, SAMPLE_FREQUENCY_22050, SAMPLE_FREQUENCY_16000,
            SAMPLE_FREQUENCY_12000, SAMPLE_FREQUENCY_11025, SAMPLE_FREQUENCY_8000, SAMPLE_FREQUENCY_NONE };

    public static SampleFrequency[] values() {
        return _values;
    }

    /**
     * Returns a sample frequency instance for the given index. If the index is
     * not between 0 and 11 inclusive, SAMPLE_FREQUENCY_NONE is returned.
     *
     * @return a sample frequency with the given index
     */
    public static SampleFrequency forInt(int i) {
        final SampleFrequency freq;
        if (i >= 0 && i < 12)
            freq = values()[i];
        else
            freq = SAMPLE_FREQUENCY_NONE;
        return freq;
    }

    public static SampleFrequency forFrequency(int i) {
        final SampleFrequency[] all = values();

        SampleFrequency freq = null;
        for (int j = 0; freq == null && j < 12; j++) {
            if (i == all[j].frequency)
                freq = all[j];
        }

        if (freq == null)
            freq = SAMPLE_FREQUENCY_NONE;
        return freq;
    }

    private final int index, frequency;
    private final int[] prediction, maxTNS_SFB;

    private SampleFrequency(int index, int freqency, int[] prediction, int[] maxTNS_SFB) {
        this.index = index;
        this.frequency = freqency;
        this.prediction = prediction;
        this.maxTNS_SFB = maxTNS_SFB;
    }

    /**
     * Returns this sample frequency's index between 0 (96000) and 11 (8000) or
     * -1 if this is SAMPLE_FREQUENCY_NONE.
     *
     * @return the sample frequency's index
     */
    public int getIndex() {
        return index;
    }

    /**
     * Returns the sample frequency as integer value. This may be a value
     * between 96000 and 8000, or 0 if this is SAMPLE_FREQUENCY_NONE.
     *
     * @return the sample frequency
     */
    public int getFrequency() {
        return frequency;
    }

    /**
     * Returns the highest scale factor band allowed for ICPrediction at this
     * sample frequency. This method is mainly used internally.
     *
     * @return the highest prediction SFB
     */
    public int getMaximalPredictionSFB() {
        return prediction[0];
    }

    /**
     * Returns the number of predictors allowed for ICPrediction at this sample
     * frequency. This method is mainly used internally.
     *
     * @return the number of ICPredictors
     */
    public int getPredictorCount() {
        return prediction[1];
    }

    /**
     * Returns the highest scale factor band allowed for TNS at this sample
     * frequency. This method is mainly used internally.
     *
     * @return the highest SFB for TNS
     */
    public int getMaximalTNS_SFB(boolean shortWindow) {
        return maxTNS_SFB[shortWindow ? 1 : 0];
    }

    /**
     * Returns a string representation of this sample frequency. The method is
     * identical to getDescription().
     *
     * @return the sample frequency's description
     */
    @Override
    public String toString() {
        return Integer.toString(frequency);
    }
}


package org.jcodec.codecs.mpeg12;

public class MPEGPredDbl  extends MPEGPredOct {
    public MPEGPredDbl(MPEGPred other) {
        super(other);
    }

    // TODO: This interpolation uses sinc at the very lowest (half-pel -- 1/4) level as opposed to linear as specified by the standard.
    // this may be a result of color greening out in long GOPs.
    @Override
    public void predictPlane(byte[] ref, int refX, int refY, int refW, int refH, int refVertStep, int refVertOff,
            int[] tgt, int tgtY, int tgtW, int tgtH, int tgtVertStep) {
        super.predictPlane(ref, refX << 1, refY << 1, refW, refH, refVertStep, refVertOff, tgt, tgtY, tgtW << 2, tgtH << 2,
                tgtVertStep);
    }
}


package org.jcodec.common.model;

import static java.lang.System.arraycopy;
import static org.jcodec.common.model.ColorSpace.MAX_PLANES;

import java.util.Arrays;

import org.jcodec.common.tools.MathUtil;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * The data is -128 shifted, so 0 is represented by -128 and 255 is represented
 * by +127
 *
 * @author The JCodec project
 *
 */
public class Picture {
    private ColorSpace color;

    private int width;
    private int height;

    private byte[][] data;
    private byte[][] lowBits;
    private int lowBitsNum;

    private Rect crop;

    public static Picture createPicture(int width, int height, byte[][] data, ColorSpace color) {
        return new Picture(width, height, data, null, color, 0, new Rect(0, 0, width, height));
    }

    public static Picture createPictureHiBD(int width, int height, byte[][] data, byte[][] lowBits, ColorSpace color,
            int lowBitsNum) {
        return new Picture(width, height, data, lowBits, color, lowBitsNum, new Rect(0, 0, width, height));
    }

    public Picture(int width, int height, byte[][] data, byte[][] lowBits, ColorSpace color, int lowBitsNum, Rect crop) {
        this.width = width;
        this.height = height;
        this.data = data;
        this.lowBits = lowBits;
        this.color = color;
        this.lowBitsNum = lowBitsNum;
        this.crop = crop;

        if (color != null) {
            for (int i = 0; i < color.nComp; i++) {
                int mask = 0xff >> (8 - color.compWidth[i]);
                if ((width & mask) != 0)
                    throw new IllegalArgumentException("Component " + i + " width should be a multiple of "
                            + (1 << color.compWidth[i]) + " for colorspace: " + color);
                if (crop != null && (crop.getWidth() & mask) != 0)
                    throw new IllegalArgumentException("Component " + i + " cropped width should be a multiple of "
                            + (1 << color.compWidth[i]) + " for colorspace: " + color);
                mask = 0xff >> (8 - color.compHeight[i]);
                if ((height & mask) != 0)
                    throw new IllegalArgumentException("Component " + i + " height should be a multiple of "
                            + (1 << color.compHeight[i]) + " for colorspace: " + color);
                if (crop != null && (crop.getHeight() & mask) != 0)
                    throw new IllegalArgumentException("Component " + i + " cropped height should be a multiple of "
                            + (1 << color.compHeight[i]) + " for colorspace: " + color);
            }
        }
    }

    public static Picture copyPicture(Picture other) {
        return new Picture(other.width, other.height, other.data, other.lowBits, other.color, 0, other.crop);
    }

    public static Picture create(int width, int height, ColorSpace colorSpace) {
        return createCropped(width, height, colorSpace, null);
    }

    public static Picture createCropped(int width, int height, ColorSpace colorSpace, Rect crop) {
        int[] planeSizes = new int[MAX_PLANES];
        for (int i = 0; i < colorSpace.nComp; i++) {
            planeSizes[colorSpace.compPlane[i]] += (width >> colorSpace.compWidth[i])
                    * (height >> colorSpace.compHeight[i]);
        }
        int nPlanes = 0;
        for (int i = 0; i < MAX_PLANES; i++)
            nPlanes += planeSizes[i] != 0 ? 1 : 0;

        byte[][] data = new byte[nPlanes][];
        for (int i = 0, plane = 0; i < MAX_PLANES; i++) {
            if (planeSizes[i] != 0) {
                data[plane++] = new byte[planeSizes[i]];
            }
        }
        return new Picture(width, height, data, null, colorSpace, 0, crop);
    }

    public static Picture createCroppedHiBD(int width, int height, int lowBitsNum, ColorSpace colorSpace, Rect crop) {
        Picture result = createCropped(width, height, colorSpace, crop);
        if (lowBitsNum <= 0)
            return result;
        byte[][] data = result.getData();
        int nPlanes = data.length;

        byte[][] lowBits = new byte[nPlanes][];
        for (int i = 0, plane = 0; i < nPlanes; i++) {
            lowBits[plane++] = new byte[data[i].length];
        }
        result.setLowBits(lowBits);
        result.setLowBitsNum(lowBitsNum);

        return result;
    }

    private void setLowBitsNum(int lowBitsNum) {
        this.lowBitsNum = lowBitsNum;
    }

    private void setLowBits(byte[][] lowBits) {
        this.lowBits = lowBits;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public byte[] getPlaneData(int plane) {
        return data[plane];
    }

    public ColorSpace getColor() {
        return color;
    }

    public byte[][] getData() {
        return data;
    }

    public byte[][] getLowBits() {
        return lowBits;
    }

    public Rect getCrop() {
        return crop;
    }

    public int getPlaneWidth(int plane) {
        return width >> color.compWidth[plane];
    }

    public int getPlaneHeight(int plane) {
        return height >> color.compHeight[plane];
    }

    public boolean compatible(Picture src) {
        return src.color == color && src.width == width && src.height == height;
    }

    public Picture createCompatible() {
        return Picture.create(width, height, color);
    }

    public void copyFrom(Picture src) {
        if (!compatible(src))
            throw new IllegalArgumentException("Can not copy to incompatible picture");
        for (int plane = 0; plane < color.nComp; plane++) {
            if (data[plane] == null)
                continue;
            arraycopy(src.data[plane], 0, data[plane], 0, (width >> color.compWidth[plane])
                    * (height >> color.compHeight[plane]));
        }
    }

    /**
     * Creates a cropped clone of this picture.
     *
     * @return
     */
    public Picture cloneCropped() {
        if (cropNeeded()) {
            return cropped();
        } else {
            Picture clone = createCompatible();
            clone.copyFrom(this);
            return clone;
        }
    }

    public Picture cropped() {
        if (!cropNeeded())
            return this;
        Picture result = Picture.create(crop.getWidth(), crop.getHeight(), color);

        if(color.planar) {
            for (int plane = 0; plane < data.length; plane++) {
                if (data[plane] == null)
                    continue;
                cropSub(data[plane], crop.getX() >> color.compWidth[plane], crop.getY() >> color.compHeight[plane],
                        crop.getWidth() >> color.compWidth[plane], crop.getHeight() >> color.compHeight[plane],
                        width >> color.compWidth[plane], crop.getWidth() >> color.compWidth[plane], result.data[plane]);
            }
        } else {
           cropSub(data[0], crop.getX(), crop.getY(), crop.getWidth(),
            crop.getHeight(), width * color.nComp, crop.getWidth() * color.nComp, result.data[0]);
        }

        return result;
    }

    protected boolean cropNeeded() {
        return crop != null
                && (crop.getX() != 0 || crop.getY() != 0 || crop.getWidth() != width || crop.getHeight() != height);
    }

    private void cropSub(byte[] src, int x, int y, int w, int h, int srcStride, int dstStride, byte[] tgt) {
        int srcOff = y * srcStride + x, dstOff = 0;
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < dstStride; j++)
                tgt[dstOff + j] = src[srcOff + j];

            srcOff += srcStride;
            dstOff += dstStride;
        }
    }

    public void setCrop(Rect crop) {
        this.crop = crop;
    }

    public int getCroppedWidth() {
        return crop == null ? width : crop.getWidth();
    }

    public int getCroppedHeight() {
        return crop == null ? height : crop.getHeight();
    }

    public int getLowBitsNum() {
        return lowBitsNum;
    }

    public static Picture fromPictureHiBD(PictureHiBD pic) {
        int lowBitsNum = pic.getBitDepth() - 8;
        int lowBitsRound = (1 << lowBitsNum) >> 1;

        Picture result = Picture.createCroppedHiBD(pic.getWidth(), pic.getHeight(), lowBitsNum, pic.getColor(),
                pic.getCrop());

        for (int i = 0; i < Math.min(pic.getData().length, result.getData().length); i++) {
            for (int j = 0; j < Math.min(pic.getData()[i].length, result.getData()[i].length); j++) {
                int val = pic.getData()[i][j];
                int round = MathUtil.clip((val + lowBitsRound) >> lowBitsNum, 0, 255);
                result.getData()[i][j] = (byte) (round - 128);
            }
        }

        byte[][] lowBits = result.getLowBits();
        if (lowBits != null) {
            for (int i = 0; i < Math.min(pic.getData().length, result.getData().length); i++) {
                for (int j = 0; j < Math.min(pic.getData()[i].length, result.getData()[i].length); j++) {
                    int val = pic.getData()[i][j];
                    int round = MathUtil.clip((val + lowBitsRound) >> lowBitsNum, 0, 255);
                    lowBits[i][j] = (byte) (val - (round << 2));
                }
            }
        }

        return result;
    }

    public PictureHiBD toPictureHiBD() {
        PictureHiBD create = PictureHiBD.doCreate(width, height, color, lowBitsNum + 8, crop);

        return toPictureHiBDInternal(create);
    }

    public PictureHiBD toPictureHiBDWithBuffer(int[][] buffer) {
        PictureHiBD create = new PictureHiBD(width, height, buffer, color, lowBitsNum + 8, crop);

        return toPictureHiBDInternal(create);
    }

    private PictureHiBD toPictureHiBDInternal(PictureHiBD pic) {
        int[][] dstData = pic.getData();

        for (int i = 0; i < data.length; i++) {
            int planeSize = getPlaneWidth(i) * getPlaneHeight(i);
            for (int j = 0; j < planeSize; j++) {
                dstData[i][j] = (data[i][j] + 128) << lowBitsNum;
            }
        }

        if (lowBits != null) {
            for (int i = 0; i < lowBits.length; i++) {
                int planeSize = getPlaneWidth(i) * getPlaneHeight(i);
                for (int j = 0; j < planeSize; j++) {
                    dstData[i][j] += lowBits[i][j];
                }
            }
        }

        return pic;
    }

    public void fill(int val) {
        for (int i = 0; i < data.length; i++) {
            Arrays.fill(data[i], (byte) val);
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Picture))
            return false;
        Picture other = (Picture) obj;

        if (other.getCroppedWidth() != getCroppedWidth() || other.getCroppedHeight() != getCroppedHeight()
                || other.getColor() != color)
            return false;

        for (int i = 0; i < getData().length; i++)
            if (!planeEquals(other, i))
                return false;
        return true;
    }

    private boolean planeEquals(Picture other, int plane) {
        int cw = color.compWidth[plane];
        int ch = color.compHeight[plane];
        int offA = other.getCrop() == null ? 0 : ((other.getCrop().getX() >> cw) + (other.getCrop().getY() >> ch)
                * (other.getWidth() >> cw));
        int offB = crop == null ? 0 : ((crop.getX() >> cw) + (crop.getY() >> ch) * (width >> cw));

        byte[] planeData = other.getPlaneData(plane);
        for (int i = 0; i < getCroppedHeight() >> ch; i++, offA += (other.getWidth() >> cw), offB += (width >> cw)) {
            for (int j = 0; j < getCroppedWidth() >> cw; j++) {
                if (planeData[offA + j] != data[plane][offB + j])
                    return false;
            }
        }
        return true;
    }

    public int getStartX() {
        return crop == null ? 0 : crop.getX();
    }

    public int getStartY() {
        return crop == null ? 0 : crop.getY();
    }

    public boolean isHiBD() {
        return lowBits != null;
    }

    public Size getSize() {
        return new Size(width, height);
    }
}


package net.sourceforge.jaad.aac.error;

import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.huffman.HCB;
import net.sourceforge.jaad.aac.syntax.SyntaxConstants;
import net.sourceforge.jaad.aac.syntax.IBitStream;
import net.sourceforge.jaad.aac.syntax.ICSInfo;
import net.sourceforge.jaad.aac.syntax.ICStream;

/**
 * This class is part of JAAD ( jaadec.sourceforge.net ) that is distributed
 * under the Public Domain license. Code changes provided by the JCodec project
 * are distributed under FreeBSD license.
 *
 * Huffman Codeword Reordering
 * Decodes spectral data for ICStreams if error resilience is used for
 * section data.
 *
 * @author in-somnia
 */
//TODO: needs decodeSpectralDataER() in BitStream
public class HCR implements SyntaxConstants {

	private static class Codeword {

		int cb, decoded, sp_offset;
		BitsBuffer bits;

		private void fill(int sp, int cb) {
			sp_offset = sp;
			this.cb = cb;
			decoded = 0;
			bits = new BitsBuffer();
		}
	}
	private static final int NUM_CB = 6;
	private static final int NUM_CB_ER = 22;
	private static final int MAX_CB = 32;
	private static final int VCB11_FIRST = 16;
	private static final int VCB11_LAST = 31;
	private static final int[] PRE_SORT_CB_STD = {11, 9, 7, 5, 3, 1};
	private static final int[] PRE_SORT_CB_ER = {11, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 9, 7, 5, 3, 1};
	private static final int[] MAX_CW_LEN = {0, 11, 9, 20, 16, 13, 11, 14, 12, 17, 14, 49,
		0, 0, 0, 0, 14, 17, 21, 21, 25, 25, 29, 29, 29, 29, 33, 33, 33, 37, 37, 41};
	private static boolean isGoodCB(int cb, int sectCB) {
		boolean b = false;
		if((sectCB>HCB.ZERO_HCB&&sectCB<=HCB.ESCAPE_HCB)||(sectCB>=VCB11_FIRST&&sectCB<=VCB11_LAST)) {
			if(cb<HCB.ESCAPE_HCB) b = ((sectCB==cb)||(sectCB==cb+1));
			else b = (sectCB==cb);
		}
		return b;
	}

	//sectionDataResilience = hDecoder->aacSectionDataResilienceFlag
	public static void decodeReorderedSpectralData(ICStream ics, IBitStream _in, short[] spectralData, boolean sectionDataResilience) throws AACException {
		final ICSInfo info = ics.getInfo();
		final int windowGroupCount = info.getWindowGroupCount();
		final int maxSFB = info.getMaxSFB();
		final int[] swbOffsets = info.getSWBOffsets();
		final int swbOffsetMax = info.getSWBOffsetMax();
		//TODO:
		//final SectionData sectData = ics.getSectionData();
		final int[][] sectStart = new int[0][0]; //sectData.getSectStart();
		final int[][] sectEnd = new int[0][0]; //sectData.getSectEnd();
		final int[] numSec = new int[0]; //sectData.getNumSec();
		final int[][] sectCB = new int[0][0]; //sectData.getSectCB();
		final int[][] sectSFBOffsets = new int[0][0]; //info.getSectSFBOffsets();

		//check parameter
		final int spDataLen = ics.getReorderedSpectralDataLength();
		if(spDataLen==0) return;

		final int longestLen = ics.getLongestCodewordLength();
		if(longestLen==0||longestLen>=spDataLen) throw new AACException("length of longest HCR codeword out of range");

		//create spOffsets
		final int[] spOffsets = new int[8];
		final int shortFrameLen = spectralData.length/8;
		spOffsets[0] = 0;
		int g;
		for(g = 1; g<windowGroupCount; g++) {
			spOffsets[g] = spOffsets[g-1]+shortFrameLen*info.getWindowGroupLength(g-1);
		}

		final Codeword[] codeword = new Codeword[512];
		final BitsBuffer[] segment = new BitsBuffer[512];

		int lastCB;
		int[] preSortCB;
		if(sectionDataResilience) {
			preSortCB = PRE_SORT_CB_ER;
			lastCB = NUM_CB_ER;
		}
		else {
			preSortCB = PRE_SORT_CB_STD;
			lastCB = NUM_CB;
		}

		int PCWs_done = 0;
		int segmentsCount = 0;
		int numberOfCodewords = 0;
		int bitsread = 0;

		int sfb, w_idx, i, thisCB, thisSectCB, cws;
		//step 1: decode PCW's (set 0), and stuff data in easier-to-use format
		for(int sortloop = 0; sortloop<lastCB; sortloop++) {
			//select codebook to process this pass
			thisCB = preSortCB[sortloop];

			for(sfb = 0; sfb<maxSFB; sfb++) {
				for(w_idx = 0; 4*w_idx<(Math.min(swbOffsets[sfb+1], swbOffsetMax)-swbOffsets[sfb]); w_idx++) {
					for(g = 0; g<windowGroupCount; g++) {
						for(i = 0; i<numSec[g]; i++) {
							if((sectStart[g][i]<=sfb)&&(sectEnd[g][i]>sfb)) {
								/* check whether codebook used here is the one we want to process */
								thisSectCB = sectCB[g][i];

								if(isGoodCB(thisCB, thisSectCB)) {
									//precalculation
									int sect_sfb_size = sectSFBOffsets[g][sfb+1]-sectSFBOffsets[g][sfb];
									int inc = (thisSectCB<HCB.FIRST_PAIR_HCB) ? 4 : 2;
									int group_cws_count = (4*info.getWindowGroupLength(g))/inc;
									int segwidth = Math.min(MAX_CW_LEN[thisSectCB], longestLen);

									//read codewords until end of sfb or end of window group
									for(cws = 0; (cws<group_cws_count)&&((cws+w_idx*group_cws_count)<sect_sfb_size); cws++) {
										int sp = spOffsets[g]+sectSFBOffsets[g][sfb]+inc*(cws+w_idx*group_cws_count);

										//read and decode PCW
										if(PCWs_done==0) {
											//read in normal segments
											if(bitsread+segwidth<=spDataLen) {
												segment[segmentsCount].readSegment(segwidth, _in);
												bitsread += segwidth;

												//Huffman.decodeSpectralDataER(segment[segmentsCount], thisSectCB, spectralData, sp);

												//keep leftover bits
												segment[segmentsCount].rewindReverse();

												segmentsCount++;
											}
											else {
												//remaining after last segment
												if(bitsread<spDataLen) {
													int additional_bits = spDataLen-bitsread;

													segment[segmentsCount].readSegment(additional_bits, _in);
													segment[segmentsCount].len += segment[segmentsCount-1].len;
													segment[segmentsCount].rewindReverse();

													if(segment[segmentsCount-1].len>32) {
														segment[segmentsCount-1].bufb = segment[segmentsCount].bufb
																+segment[segmentsCount-1].showBits(segment[segmentsCount-1].len-32);
														segment[segmentsCount-1].bufa = segment[segmentsCount].bufa
																+segment[segmentsCount-1].showBits(32);
													}
													else {
														segment[segmentsCount-1].bufa = segment[segmentsCount].bufa
																+segment[segmentsCount-1].showBits(segment[segmentsCount-1].len);
														segment[segmentsCount-1].bufb = segment[segmentsCount].bufb;
													}
													segment[segmentsCount-1].len += additional_bits;
												}
												bitsread = spDataLen;
												PCWs_done = 1;

												codeword[0].fill(sp, thisSectCB);
											}
										}
										else {
											codeword[numberOfCodewords-segmentsCount].fill(sp, thisSectCB);
										}
										numberOfCodewords++;
									}
								}
							}
						}
					}
				}
			}
		}

		if(segmentsCount==0) throw new AACException("no segments _in HCR");

		final int numberOfSets = numberOfCodewords/segmentsCount;

		//step 2: decode nonPCWs
		int trial, codewordBase, segmentID, codewordID;
		for(int set = 1; set<=numberOfSets; set++) {
			for(trial = 0; trial<segmentsCount; trial++) {
				for(codewordBase = 0; codewordBase<segmentsCount; codewordBase++) {
					segmentID = (trial+codewordBase)%segmentsCount;
					codewordID = codewordBase+set*segmentsCount-segmentsCount;

					//data up
					if(codewordID>=numberOfCodewords-segmentsCount) break;

					if((codeword[codewordID].decoded==0)&&(segment[segmentID].len>0)) {
						if(codeword[codewordID].bits.len!=0) segment[segmentID].concatBits(codeword[codewordID].bits);

						int tmplen = segment[segmentID].len;
						/*int ret = Huffman.decodeSpectralDataER(segment[segmentID], codeword[codewordID].cb,
								spectralData, codeword[codewordID].sp_offset);

						if(ret>=0) codeword[codewordID].decoded = 1;
						else {
							codeword[codewordID].bits = segment[segmentID];
							codeword[codewordID].bits.len = tmplen;
						}*/

					}
				}
			}
			for(i = 0; i<segmentsCount; i++) {
				segment[i].rewindReverse();
			}
		}
	}
}


package org.jcodec.common.dct;
import java.util.Arrays;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * Performs IDCT of 8x8 block.
 *
 * See MPEGDecoder for example.
 *
 * @author The JCodec project
 *
 */
public class SparseIDCT {

    public final static int[][] COEFF = new int[64][];
    public final static int PRECISION = 13;
    public final static int DC_SHIFT = PRECISION - 3;

    static {
        COEFF[0] = new int[64];
        Arrays.fill(COEFF[0], 1 << DC_SHIFT);

        int ac = 1 << PRECISION;
        for (int i = 1; i < 64; i++) {
            COEFF[i] = new int[64];
            COEFF[i][i] = ac;
            SimpleIDCT10Bit.idct10(COEFF[i], 0);
        }
    }

    /**
     * Starts DCT reconstruction
     *
     * Faster then call to 'coeff' with ind = 0
     *
     * @param block
     * @param dc
     */
    public static final void start(int[] block, int dc) {
        dc <<= DC_SHIFT;
        for (int i = 0; i < 64; i += 4) {
            block[i + 0] = dc;
            block[i + 1] = dc;
            block[i + 2] = dc;
            block[i + 3] = dc;
        }
    }

    /**
     * Recalculates image based on new DCT coefficient
     *
     * @param block
     * @param ind
     * @param level
     */
    public static final void coeff(int[] block, int ind, int level) {
        for (int i = 0; i < 64; i += 4) {
            block[i] += COEFF[ind][i] * level;
            block[i + 1] += COEFF[ind][i + 1] * level;
            block[i + 2] += COEFF[ind][i + 2] * level;
            block[i + 3] += COEFF[ind][i + 3] * level;
        }
    }

    /**
     * Finalizes DCT calculation
     *
     * @param block
     */
    public static final void finish(int block[]) {
        for (int i = 0; i < 64; i += 4) {
            block[i] = div(block[i]);
            block[i + 1] = div(block[i + 1]);
            block[i + 2] = div(block[i + 2]);
            block[i + 3] = div(block[i + 3]);
        }
    }

    private final static int div(int x) {
        int m = x >> 31;
        int n = x >>> 31;
        return ((((x ^ m) + n) >> PRECISION) ^ m) + n;
    }
}


package org.jcodec.codecs.h264;
import static org.jcodec.codecs.h264.H264Const.PROFILE_BASELINE;
import static org.jcodec.codecs.h264.H264Const.PROFILE_HIGH;
import static org.jcodec.codecs.h264.H264Const.PROFILE_MAIN;
import static org.jcodec.common.tools.MathUtil.wrap;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

import org.jcodec.codecs.h264.H264Utils.MvList2D;
import org.jcodec.codecs.h264.decode.DeblockerInput;
import org.jcodec.codecs.h264.decode.FrameReader;
import org.jcodec.codecs.h264.decode.SliceDecoder;
import org.jcodec.codecs.h264.decode.SliceHeaderReader;
import org.jcodec.codecs.h264.decode.SliceReader;
import org.jcodec.codecs.h264.decode.deblock.DeblockingFilter;
import org.jcodec.codecs.h264.io.model.Frame;
import org.jcodec.codecs.h264.io.model.NALUnit;
import org.jcodec.codecs.h264.io.model.NALUnitType;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.RefPicMarking;
import org.jcodec.codecs.h264.io.model.RefPicMarkingIDR;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.io.model.SliceHeader;
import org.jcodec.codecs.h264.io.model.SliceType;
import org.jcodec.common.IntObjectMap;
import org.jcodec.common.UsedViaReflection;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Rect;
import org.jcodec.common.model.Size;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * MPEG 4 AVC ( H.264 ) Decoder
 *
 * Conforms to H.264 ( ISO/IEC 14496-10 ) specifications
 *
 * @author The JCodec project
 *
 */
public class H264Decoder extends VideoDecoder {

    private Frame[] sRefs;
    private IntObjectMap<Frame> lRefs;
    private List<Frame> pictureBuffer;
    private POCManager poc;
    private FrameReader reader;
    private ExecutorService tp;
    private boolean threaded;

    public H264Decoder() {
        pictureBuffer = new ArrayList<Frame>();
        poc = new POCManager();
        this.threaded = Runtime.getRuntime().availableProcessors() > 1;
        if (threaded) {
            tp = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    Thread t = Executors.defaultThreadFactory().newThread(r);
                    t.setDaemon(true);
                    return t;
                }
            });
        }
        reader = new FrameReader();
    }

    /**
     * Constructs this decoder from a portion of a stream that contains AnnexB
     * delimited (00 00 00 01) SPS/PPS NAL units. SPS/PPS NAL units are 0x67 and
     * 0x68 respectfully.
     *
     * @param codecPrivate
     */
    public static H264Decoder createH264DecoderFromCodecPrivate(ByteBuffer codecPrivate) {
        H264Decoder d = new H264Decoder();
        for (ByteBuffer bb : H264Utils.splitFrame(codecPrivate.duplicate())) {
            NALUnit nu = NALUnit.read(bb);
            if (nu.type == NALUnitType.SPS) {
                d.reader.addSps(bb);
            } else if (nu.type == NALUnitType.PPS) {
                d.reader.addPps(bb);
            }
        }
        return d;
    }

    @Override
    public Frame decodeFrame(ByteBuffer data, byte[][] buffer) {
        return decodeFrameFromNals(H264Utils.splitFrame(data), buffer);
    }

    public Frame decodeFrameFromNals(List<ByteBuffer> nalUnits, byte[][] buffer) {
        return new FrameDecoder(this).decodeFrame(nalUnits, buffer);
    }

    private static final class SliceDecoderRunnable implements Runnable {
        private final SliceReader sliceReader;
        private final Frame result;
        private FrameDecoder fdec;

        private SliceDecoderRunnable(FrameDecoder fdec, SliceReader sliceReader, Frame result) {
            this.fdec = fdec;
            this.sliceReader = sliceReader;
            this.result = result;
        }

        public void run() {
            new SliceDecoder(fdec.activeSps, fdec.dec.sRefs, fdec.dec.lRefs, fdec.di, result)
                    .decodeFromReader(sliceReader);
        }
    }

    static class FrameDecoder {
        private SeqParameterSet activeSps;
        private DeblockingFilter filter;
        private SliceHeader firstSliceHeader;
        private NALUnit firstNu;
        private H264Decoder dec;
        private DeblockerInput di;

        public FrameDecoder(H264Decoder decoder) {
            this.dec = decoder;
        }

        public Frame decodeFrame(List<ByteBuffer> nalUnits, byte[][] buffer) {
            List<SliceReader> sliceReaders = dec.reader.readFrame(nalUnits);
            if (sliceReaders == null || sliceReaders.size() == 0)
                return null;
            final Frame result = init(sliceReaders.get(0), buffer);
            if (dec.threaded && sliceReaders.size() > 1) {
                List<Future<?>> futures = new ArrayList<Future<?>>();
                for (SliceReader sliceReader : sliceReaders) {
                    futures.add(dec.tp.submit(new SliceDecoderRunnable(this, sliceReader, result)));
                }

                for (Future<?> future : futures) {
                    waitForSure(future);
                }

            } else {
                for (SliceReader sliceReader : sliceReaders) {
                    new SliceDecoder(activeSps, dec.sRefs, dec.lRefs, di, result).decodeFromReader(sliceReader);
                }
            }

            filter.deblockFrame(result);

            updateReferences(result);

            return result;
        }

        private void waitForSure(Future<?> future) {
            while (true) {
                try {
                    future.get();
                    break;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

        private void updateReferences(Frame picture) {
            if (firstNu.nal_ref_idc != 0) {
                if (firstNu.type == NALUnitType.IDR_SLICE) {
                    performIDRMarking(firstSliceHeader.refPicMarkingIDR, picture);
                } else {
                    performMarking(firstSliceHeader.refPicMarkingNonIDR, picture);
                }
            }
        }

        private Frame init(SliceReader sliceReader, byte[][] buffer) {
            firstNu = sliceReader.getNALUnit();

            firstSliceHeader = sliceReader.getSliceHeader();
            activeSps = firstSliceHeader.sps;

            validateSupportedFeatures(firstSliceHeader.sps, firstSliceHeader.pps);

            int picWidthInMbs = activeSps.picWidthInMbsMinus1 + 1;

            if (dec.sRefs == null) {
                dec.sRefs = new Frame[1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4)];
                dec.lRefs = new IntObjectMap<Frame>();
            }

            di = new DeblockerInput(activeSps);

            Frame result = createFrame(activeSps, buffer, firstSliceHeader.frameNum, firstSliceHeader.sliceType,
                    di.mvs, di.refsUsed, dec.poc.calcPOC(firstSliceHeader, firstNu));

            filter = new DeblockingFilter(picWidthInMbs, activeSps.bitDepthChromaMinus8 + 8, di);

            return result;
        }

        private void validateSupportedFeatures(SeqParameterSet sps, PictureParameterSet pps) {
            if (sps.mbAdaptiveFrameFieldFlag)
                throw new RuntimeException("Unsupported h264 feature: MBAFF.");
            if (sps.bitDepthLumaMinus8 != 0 || sps.bitDepthChromaMinus8 != 0)
                throw new RuntimeException("Unsupported h264 feature: High bit depth.");
            if (sps.chromaFormatIdc != ColorSpace.YUV420J)
                throw new RuntimeException("Unsupported h264 feature: " + sps.chromaFormatIdc + " color.");
            if (!sps.frameMbsOnlyFlag || sps.fieldPicFlag)
                throw new RuntimeException("Unsupported h264 feature: interlace.");
            if (pps.constrainedIntraPredFlag)
                throw new RuntimeException("Unsupported h264 feature: constrained intra prediction.");
//            if (sps.getScalingMatrix() != null || pps.extended != null && pps.extended.getScalingMatrix() != null)
//                throw new RuntimeException("Unsupported h264 feature: scaling list.");
            if (sps.qpprimeYZeroTransformBypassFlag)
                throw new RuntimeException("Unsupported h264 feature: qprime zero transform bypass.");
            if (sps.profileIdc != PROFILE_BASELINE && sps.profileIdc != PROFILE_MAIN && sps.profileIdc != PROFILE_HIGH)
                throw new RuntimeException("Unsupported h264 feature: " + sps.profileIdc + " profile.");
        }

        public void performIDRMarking(RefPicMarkingIDR refPicMarkingIDR, Frame picture) {
            clearAll();
            dec.pictureBuffer.clear();

            Frame saved = saveRef(picture);
            if (refPicMarkingIDR.isUseForlongTerm()) {
                dec.lRefs.put(0, saved);
                saved.setShortTerm(false);
            } else
                dec.sRefs[firstSliceHeader.frameNum] = saved;
        }

        private Frame saveRef(Frame decoded) {
            Frame frame = dec.pictureBuffer.size() > 0 ? dec.pictureBuffer.remove(0) : Frame.createFrame(decoded);
            frame.copyFromFrame(decoded);
            return frame;
        }

        private void releaseRef(Frame picture) {
            if (picture != null) {
                dec.pictureBuffer.add(picture);
            }
        }

        public void clearAll() {
            for (int i = 0; i < dec.sRefs.length; i++) {
                releaseRef(dec.sRefs[i]);
                dec.sRefs[i] = null;
            }
            int[] keys = dec.lRefs.keys();
            for (int i = 0; i < keys.length; i++) {
                releaseRef(dec.lRefs.get(keys[i]));
            }
            dec.lRefs.clear();
        }

        public void performMarking(RefPicMarking refPicMarking, Frame picture) {
            Frame saved = saveRef(picture);

            if (refPicMarking != null) {
                RefPicMarking.Instruction[] instructions = refPicMarking.getInstructions();
                for (int i = 0; i < instructions.length; i++) {
                    RefPicMarking.Instruction instr = instructions[i];
                    switch (instr.getType()) {
                    case REMOVE_SHORT:
                        unrefShortTerm(instr.getArg1());
                        break;
                    case REMOVE_LONG:
                        unrefLongTerm(instr.getArg1());
                        break;
                    case CONVERT_INTO_LONG:
                        convert(instr.getArg1(), instr.getArg2());
                        break;
                    case TRUNK_LONG:
                        truncateLongTerm(instr.getArg1() - 1);
                        break;
                    case CLEAR:
                        clearAll();
                        break;
                    case MARK_LONG:
                        saveLong(saved, instr.getArg1());
                        saved = null;
                    }
                }
            }
            if (saved != null)
                saveShort(saved);

            int maxFrames = 1 << (activeSps.log2MaxFrameNumMinus4 + 4);
            if (refPicMarking == null) {
                int maxShort = Math.max(1, activeSps.numRefFrames - dec.lRefs.size());
                int min = Integer.MAX_VALUE, num = 0, minFn = 0;
                for (int i = 0; i < dec.sRefs.length; i++) {
                    if (dec.sRefs[i] != null) {
                        int fnWrap = unwrap(firstSliceHeader.frameNum, dec.sRefs[i].getFrameNo(), maxFrames);
                        if (fnWrap < min) {
                            min = fnWrap;
                            minFn = dec.sRefs[i].getFrameNo();
                        }
                        num++;
                    }
                }
                if (num > maxShort) {
                    releaseRef(dec.sRefs[minFn]);
                    dec.sRefs[minFn] = null;
                }
            }
        }

        private int unwrap(int thisFrameNo, int refFrameNo, int maxFrames) {
            return refFrameNo > thisFrameNo ? refFrameNo - maxFrames : refFrameNo;
        }

        private void saveShort(Frame saved) {
            dec.sRefs[firstSliceHeader.frameNum] = saved;
        }

        private void saveLong(Frame saved, int longNo) {
            Frame prev = dec.lRefs.get(longNo);
            if (prev != null)
                releaseRef(prev);
            saved.setShortTerm(false);

            dec.lRefs.put(longNo, saved);
        }

        private void truncateLongTerm(int maxLongNo) {
            int[] keys = dec.lRefs.keys();
            for (int i = 0; i < keys.length; i++) {
                if (keys[i] > maxLongNo) {
                    releaseRef(dec.lRefs.get(keys[i]));
                    dec.lRefs.remove(keys[i]);
                }
            }
        }

        private void convert(int shortNo, int longNo) {
            int ind = wrap(firstSliceHeader.frameNum - shortNo,
                    1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4));
            releaseRef(dec.lRefs.get(longNo));
            dec.lRefs.put(longNo, dec.sRefs[ind]);
            dec.sRefs[ind] = null;
            dec.lRefs.get(longNo).setShortTerm(false);
        }

        private void unrefLongTerm(int longNo) {
            releaseRef(dec.lRefs.get(longNo));
            dec.lRefs.remove(longNo);
        }

        private void unrefShortTerm(int shortNo) {
            int ind = wrap(firstSliceHeader.frameNum - shortNo,
                    1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4));
            releaseRef(dec.sRefs[ind]);
            dec.sRefs[ind] = null;
        }
    }

    public static Frame createFrame(SeqParameterSet sps, byte[][] buffer, int frameNum, SliceType frameType,
            MvList2D mvs, Frame[][][] refsUsed, int POC) {
        int width = sps.picWidthInMbsMinus1 + 1 << 4;
        int height = SeqParameterSet.getPicHeightInMbs(sps) << 4;

        Rect crop = null;
        if (sps.frameCroppingFlag) {
            int sX = sps.frameCropLeftOffset << 1;
            int sY = sps.frameCropTopOffset << 1;
            int w = width - (sps.frameCropRightOffset << 1) - sX;
            int h = height - (sps.frameCropBottomOffset << 1) - sY;
            crop = new Rect(sX, sY, w, h);
        }
        return new Frame(width, height, buffer, ColorSpace.YUV420, crop, frameNum, frameType, mvs, refsUsed, POC);
    }

    public void addSps(List<ByteBuffer> spsList) {
        reader.addSpsList(spsList);
    }

    public void addPps(List<ByteBuffer> ppsList) {
        reader.addPpsList(ppsList);
    }

    @UsedViaReflection
    public static int probe(ByteBuffer data) {
        boolean validSps = false, validPps = false, validSh = false;
        for (ByteBuffer nalUnit : H264Utils.splitFrame(data.duplicate())) {
            NALUnit marker = NALUnit.read(nalUnit);
            if (marker.type == NALUnitType.IDR_SLICE || marker.type == NALUnitType.NON_IDR_SLICE) {
                BitReader reader = BitReader.createBitReader(nalUnit);
                validSh = validSh(SliceHeaderReader.readPart1(reader));
                break;
            } else if (marker.type == NALUnitType.SPS) {
                validSps = validSps(SeqParameterSet.read(nalUnit));
            } else if (marker.type == NALUnitType.PPS) {
                validPps = validPps(PictureParameterSet.read(nalUnit));
            }
        }

        return (validSh ? 60 : 0) + (validSps ? 20 : 0) + (validPps ? 20 : 0);
    }

    private static boolean validSh(SliceHeader sh) {
        return sh.firstMbInSlice == 0 && sh.sliceType != null && sh.picParameterSetId < 2;
    }

    private static boolean validSps(SeqParameterSet sps) {
        return sps.bitDepthChromaMinus8 < 4 && sps.bitDepthLumaMinus8 < 4 && sps.chromaFormatIdc != null
                && sps.seqParameterSetId < 2 && sps.picOrderCntType <= 2;
    }

    private static boolean validPps(PictureParameterSet pps) {
        return pps.picInitQpMinus26 <= 26 && pps.seqParameterSetId <= 2 && pps.picParameterSetId <= 2;
    }

    @Override
    public VideoCodecMeta getCodecMeta(ByteBuffer data) {
        List<ByteBuffer> rawSPS = H264Utils.getRawSPS(data.duplicate());
        List<ByteBuffer> rawPPS = H264Utils.getRawPPS(data.duplicate());
        if (rawSPS.size() == 0) {
            Logger.warn("Can not extract metadata from the packet not containing an SPS.");
            return null;
        }
        SeqParameterSet sps = SeqParameterSet.read(rawSPS.get(0));
        Size size = H264Utils.getPicSize(sps);
//, H264Utils.saveCodecPrivate(rawSPS, rawPPS)
        return org.jcodec.common.VideoCodecMeta.createSimpleVideoCodecMeta(size, ColorSpace.YUV420);
    }
}