/*
* Copyright (C) 2011 in-somnia
*
* This file is part of JAAD.
*
* JAAD 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 3 of the
* License, or (at your option) any later version.
*
* JAAD 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, see <http://www.gnu.org/licenses/>.
*/
package net.sourceforge.jaad.aac.sbr2;
import java.util.logging.Level;
import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.SampleFrequency;
import net.sourceforge.jaad.aac.ps2.PS;
import net.sourceforge.jaad.aac.syntax.BitStream;
import net.sourceforge.jaad.aac.syntax.Constants;
public class SBR implements SBRConstants {
//arguments
private boolean stereo;
private int sampleFrequency;
private boolean downSampled;
//data
private final SBRHeader header;
private final ChannelData[] cd;
private final FrequencyTables tables;
private boolean coupling;
//processing buffers
private final float[][][][] W; //analysis QMF output
private final float[][][] Xlow, Xhigh;
private final float[][][][] Y;
private final float[][][][] X;
//filterbanks
private final AnalysisFilterbank qmfA;
private final SynthesisFilterbank qmfS;
//PS extension
private PS ps;
private boolean psUsed;
public SBR(SampleFrequency sf, boolean downSampled) {
sampleFrequency = sf.getFrequency()*2;
this.downSampled = downSampled;
header = new SBRHeader();
cd = new ChannelData[2];
cd[0] = new ChannelData();
cd[1] = new ChannelData();
tables = new FrequencyTables();
W = new float[2][TIME_SLOTS_RATE][TIME_SLOTS_RATE][2]; //for both channels
Xlow = new float[32][40][2];
Xhigh = new float[64][40][2];
Y = new float[2][38+MAX_LTEMP][64][2]; //for both channels
X = new float[2][64][38][2]; //for both channels
qmfA = new AnalysisFilterbank();
qmfS = new SynthesisFilterbank();
psUsed = false;
}
/*========================= decoding =========================*/
public void decode(BitStream in, int count, boolean stereo, boolean crc) throws AACException {
this.stereo = stereo;
final int pos = in.getPosition();
if(crc) {
Constants.LOGGER.info("SBR CRC bits present");
in.skipBits(10); //TODO: implement crc check
}
if(in.readBool()) {
header.decode(in);
if(header.isReset()) tables.calculate(header, sampleFrequency);
}
//if at least one header was present yet: decode, else skip
if(header.isDecoded()) {
decodeData(in, stereo);
//check for remaining bits (byte-align) and skip them
final int len = in.getPosition()-pos;
final int bitsLeft = count-len;
if(bitsLeft>=8) Constants.LOGGER.log(Level.WARNING, "SBR: bits left: {0}", bitsLeft);
else if(bitsLeft<0) throw new AACException("SBR data overread: "+bitsLeft);
in.skipBits(bitsLeft);
}
else {
final int left = count-pos+in.getPosition();
in.skipBits(left);
Constants.LOGGER.log(Level.INFO, "SBR frame without header, skipped {0} bits", left);
}
}
private void decodeData(BitStream in, boolean stereo) throws AACException {
if(stereo) decodeChannelPairElement(in);
else decodeSingleChannelElement(in);
//extended data
if(in.readBool()) {
int count = in.readBits(4);
if(count==15) count += in.readBits(8);
int bitsLeft = 8*count;
int extensionID;
while(bitsLeft>7) {
bitsLeft -= 2;
extensionID = in.readBits(2);
bitsLeft -= decodeExtension(in, extensionID);
}
if(bitsLeft>0) in.skipBits(bitsLeft);
}
}
private void decodeSingleChannelElement(BitStream in) throws AACException {
if(in.readBool()) in.skipBits(4); //reserved
cd[0].decodeGrid(in, header, tables);
cd[0].decodeDTDF(in);
cd[0].decodeInvf(in, header, tables);
cd[0].decodeEnvelope(in, header, tables, false, false);
cd[0].decodeNoise(in, header, tables, false, false);
cd[0].decodeSinusoidal(in, header, tables);
dequantSingle(0);
}
private void decodeChannelPairElement(BitStream in) throws AACException {
if(in.readBool()) in.skipBits(8); //reserved
if(coupling = in.readBool()) {
cd[0].decodeGrid(in, header, tables);
cd[1].copyGrid(cd[0]);
cd[0].decodeDTDF(in);
cd[1].decodeDTDF(in);
cd[0].decodeInvf(in, header, tables);
cd[1].copyInvf(cd[0]);
cd[0].decodeEnvelope(in, header, tables, false, coupling);
cd[0].decodeNoise(in, header, tables, false, coupling);
cd[1].decodeEnvelope(in, header, tables, true, coupling);
cd[1].decodeNoise(in, header, tables, true, coupling);
dequantCoupled();
}
else {
cd[0].decodeGrid(in, header, tables);
cd[1].decodeGrid(in, header, tables);
cd[0].decodeDTDF(in);
cd[1].decodeDTDF(in);
cd[0].decodeInvf(in, header, tables);
cd[1].decodeInvf(in, header, tables);
cd[0].decodeEnvelope(in, header, tables, false, coupling);
cd[1].decodeEnvelope(in, header, tables, true, coupling);
cd[0].decodeNoise(in, header, tables, false, coupling);
cd[1].decodeNoise(in, header, tables, true, coupling);
dequantSingle(0);
dequantSingle(1);
}
cd[0].decodeSinusoidal(in, header, tables);
cd[1].decodeSinusoidal(in, header, tables);
}
private int decodeExtension(BitStream in, int extensionID) throws AACException {
final int start = in.getPosition();
switch(extensionID) {
case EXTENSION_ID_PS:
if(ps==null) ps = new PS();
ps.decode(in);
if(!psUsed&&ps.hasHeader()) psUsed = true;
break;
default:
in.skipBits(6); //extension data
break;
}
return in.getPosition()-start;
}
/*============ dequantization/stereo decoding (4.6.18.3.5) =============*/
private void dequantSingle(int ch) {
//envelopes
final float a = cd[ch].getAmpRes() ? 1f : 0.5f;
final float[][] e = cd[ch].getEnvelopeScalefactors();
final int[] freqRes = cd[ch].getFrequencyResolutions();
final int[] n = tables.getN();
for(int l = 0; l<cd[ch].getEnvCount(); l++) {
for(int k = 0; k<n[freqRes[l]]; k++) {
e[l][k] = (float) Math.pow(2.0f, e[l][k]*a+6.0f);
}
}
//noise
final int nq = tables.getNq();
final int lq = cd[ch].getNoiseCount();
final float[][] q = cd[ch].getNoiseFloorData();
for(int l = 0; l<lq; l++) {
for(int k = 0; k<nq; k++) {
q[l][k] = (float) Math.pow(2.0f, NOISE_FLOOR_OFFSET-q[l][k]);
}
}
}
//dequantization of coupled channel pair
private void dequantCoupled() {
//envelopes
final float a = cd[0].getAmpRes() ? 1f : 0.5f;
final int panOffset = PAN_OFFSETS[cd[0].getAmpRes() ? 1 : 0];
final float[][] e0 = cd[0].getEnvelopeScalefactors();
final float[][] e1 = cd[1].getEnvelopeScalefactors();
final int[] r = cd[0].getFrequencyResolutions();
final int le = cd[0].getEnvCount();
final int[] n = tables.getN();
float f1, f2, f3;
for(int l = 0; l<le; l++) {
for(int k = 0; k<n[r[l]]; k++) {
f1 = (float) Math.pow(2.0f, (e0[l][k]*a)+7.0f);
f2 = (float) Math.pow(2.0f, (panOffset-e1[l][k])*a);
f3 = f1/(1.0f+f2);
e0[l][k] = f3;
e1[l][k] = f3*f2;
}
}
//noise
final float[][] q0 = cd[0].getNoiseFloorData();
final float[][] q1 = cd[1].getNoiseFloorData();
final int lq = cd[0].getNoiseCount();
final int nq = tables.getNq();
for(int l = 0; l<lq; l++) {
for(int k = 0; k<nq; k++) {
f1 = (float) Math.pow(2.0f, NOISE_FLOOR_OFFSET-q0[l][k]+1.0f);
f2 = (float) Math.pow(2.0f, PAN_OFFSETS[1]-q1[l][k]);
f3 = f1/(1.0f+f2);
q0[l][k] = f3;
q0[l][k] = f3*f2;
}
}
}
/*========================= processing =========================*/
public boolean isPSUsed() {
return psUsed;
}
//left/right: 1024 time samples
public void process(float[] left, float[] right, boolean downSampled) throws AACException {
processChannel(0, left);
if(stereo) processChannel(1, right);
else if(psUsed) {
int k;
for(int l = TIME_SLOTS_RATE; l<TIME_SLOTS_RATE+6; l++) {
for(k = 0; k<5; k++) {
X[0][k][l][0] = Xlow[k][l+T_HF_ADJ][0];
X[0][k][l][1] = Xlow[k][l+T_HF_ADJ][1];
}
}
ps.process(X[0], X[1]);
}
qmfS.process(X[0], left, 0);
if(stereo||psUsed) qmfS.process(X[1], right, 0);
}
private void processChannel(int ch, float[] data) throws AACException {
//1. old W -> Xlow (4.6.18.5)
final int kxPrev = tables.getKx(true);
int l, k;
for(l = 0; l<T_HF_GEN; l++) {
for(k = 0; k<kxPrev; k++) {
Xlow[k][l][0] = W[ch][k][l+TIME_SLOTS_RATE-T_HF_GEN][0];
Xlow[k][l][1] = W[ch][k][l+TIME_SLOTS_RATE-T_HF_GEN][1];
}
for(k = kxPrev; k<32; k++) {
Xlow[k][l][0] = 0;
Xlow[k][l][1] = 0;
}
}
//2. analysis QMF (data -> W)
qmfA.process(data, W[ch], 0);
//3. new W -> Xlow (4.6.18.5)
final int kx = tables.getKx(false);
for(l = T_HF_GEN; l<TIME_SLOTS_RATE+T_HF_GEN; l++) {
for(k = 0; k<kx; k++) {
Xlow[k][l][0] = W[ch][l-T_HF_GEN][k][0];
Xlow[k][l][1] = W[ch][l-T_HF_GEN][k][1];
}
for(k = kx; k<32; k++) {
Xlow[k][l][0] = 0;
Xlow[k][l][1] = 0;
}
}
//4. HF generation (Xlow -> Xhigh)
HFGenerator.process(tables, cd[ch], Xlow, Xhigh);
//5. old Y -> X
final int lTemp = cd[ch].getLTemp();
final int mPrev = tables.getM(true);
final int m = tables.getM(false);
for(l = 0; l<lTemp; l++) {
for(k = 0; k<kxPrev; k++) {
X[ch][k][l][0] = Xlow[k][l+T_HF_ADJ][0];
X[ch][k][l][1] = Xlow[k][l+T_HF_ADJ][1];
}
for(k = kxPrev; k<kxPrev+mPrev; k++) {
X[ch][k][l][0] = Y[ch][l+T_HF_ADJ+TIME_SLOTS_RATE][k][0];
X[ch][k][l][1] = Y[ch][l+T_HF_ADJ+TIME_SLOTS_RATE][k][1];
}
for(k = kxPrev+mPrev; k<64; k++) {
X[ch][k][l][0] = 0;
X[ch][k][l][1] = 0;
}
}
//6. HF adjustment (Xhigh -> Y)
HFAdjuster.process(header, tables, cd[ch], Xhigh, Y[ch]);
//7. new Y -> X
for(l = lTemp; l<TIME_SLOTS_RATE; l++) {
//System.out.println("lTemp: "+lTemp);
for(k = 0; k<kx; k++) {
X[ch][k][l][0] = Xlow[k][l+T_HF_ADJ][0];
X[ch][k][l][1] = Xlow[k][l+T_HF_ADJ][1];
}
for(k = kx; k<kx+m; k++) {
X[ch][k][l][0] = Y[ch][l+T_HF_ADJ][k][0];
X[ch][k][l][1] = Y[ch][l+T_HF_ADJ][k][1];
}
for(k = kx+m; k<64; k++) {
X[ch][k][l][0] = 0;
X[ch][k][l][1] = 0;
}
}
//save data for next frame
cd[ch].savePreviousData();
}
}