/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.media.codec.atrac3;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static jpcsp.media.codec.atrac3.Atrac3Data.clc_length_tab;
import static jpcsp.media.codec.atrac3.Atrac3Data.inv_max_quant;
import static jpcsp.media.codec.atrac3.Atrac3Data.mantissa_clc_tab;
import static jpcsp.media.codec.atrac3.Atrac3Data.mantissa_vlc_tab;
import static jpcsp.media.codec.atrac3.Atrac3Data.matrix_coeffs;
import static jpcsp.media.codec.atrac3.Atrac3Data.subband_tab;
import static jpcsp.media.codec.atrac3plus.Atrac.ff_atrac_sf_table;
import static jpcsp.media.codec.util.CodecUtils.writeOutput;
import static jpcsp.media.codec.util.FloatDSP.vectorFmul;
import static jpcsp.util.Utilities.signExtend;
import java.util.Arrays;
import org.apache.log4j.Logger;
import jpcsp.media.codec.ICodec;
import jpcsp.media.codec.atrac3plus.Atrac;
import jpcsp.media.codec.util.BitReader;
import jpcsp.media.codec.util.FFT;
import jpcsp.media.codec.util.VLC;
/*
* Based on the FFmpeg version from Maxim Poliakovski and Benjamin Larsson.
* All credits go to them.
*/
public class Atrac3Decoder implements ICodec {
public static Logger log = Logger.getLogger("atrac3");
public static final int AT3_ERROR = -2;
public static final int JOINT_STEREO = 0x12;
public static final int STEREO = 0x2;
public static final int SAMPLES_PER_FRAME = 1024;
private static final int MDCT_SIZE = 512;
private static final float[] mdct_window = new float[MDCT_SIZE];
private static final VLC[] spectral_coeff_tab = new VLC[7];
private Context ctx;
private BitReader br;
private static boolean staticInitDone = false;
private static void initStaticData() {
if (staticInitDone) {
return;
}
initImdctWindow();
Atrac.generateTables();
// Initialize the VLC tables
for (int i = 0; i < 7; i++) {
spectral_coeff_tab[i] = new VLC();
spectral_coeff_tab[i].initVLCSparse(9, Atrac3Data.huff_tab_sizes[i], Atrac3Data.huff_bits[i], Atrac3Data.huff_codes[i], null);
}
staticInitDone = true;
}
@Override
public int init(int blockAlign, int channels, int outputChannels, int codingMode) {
int ret;
initStaticData();
ctx = new Context();
ctx.channels = channels;
ctx.outputChannels = outputChannels;
ctx.codingMode = codingMode != 0 ? JOINT_STEREO : STEREO;
ctx.blockAlign = blockAlign;
// initialize th MDCT transform
ctx.mdctCtx = new FFT();
ret = ctx.mdctCtx.mdctInit(9, true, 1.0 / 32768.0);
if (ret < 0) {
return ret;
}
// init the joint-stereo decoding data
ctx.weightingDelay[0] = 0;
ctx.weightingDelay[1] = 7;
ctx.weightingDelay[2] = 0;
ctx.weightingDelay[3] = 7;
ctx.weightingDelay[4] = 0;
ctx.weightingDelay[5] = 7;
for (int i = 0; i < 4; i++) {
ctx.matrixCoeffIndexPrev[i] = 3;
ctx.matrixCoeffIndexNow[i] = 3;
ctx.matrixCoeffIndexNext[i] = 3;
}
ctx.gaincCtx = new Atrac();
ctx.gaincCtx.initGainCompensation(4, 3);
for (int i = 0; i < ctx.units.length; i++) {
ctx.units[i] = new ChannelUnit();
}
return 0;
}
private void imlt(float[] input, int inputOffset, float[] output, int outputOffset, boolean oddBand) {
if (oddBand) {
/**
* Reverse the odd bands before IMDCT, this is an effect of the QMF
* transform or it gives better compression to do it this way.
* FIXME: It should be possible to handle this in imdct_calc
* for that to happen a modification of the prerotation step of
* all SIMD code and C code is needed.
* Or fix the functions before so they generate a pre reversed spectrum.
*/
for (int i = 0; i < 128; i++) {
float tmp = input[inputOffset + i];
input[inputOffset + i] = input[inputOffset + 255 - i];
input[inputOffset + 255 - i] = tmp;
}
}
ctx.mdctCtx.imdctCalc(output, outputOffset, input, inputOffset);
// Perform windowing on the output
vectorFmul(output, outputOffset, output, outputOffset, mdct_window, 0, MDCT_SIZE);
}
private static void initImdctWindow() {
// generate the mdct window, for details see
// http://wiki.multimedia.cx/index.php?title=RealAudio_atrc#Windows
for (int i = 0, j = 255; i < 128; i++, j--) {
float wi = (float) sin(((i + 0.5) / 256.0 - 0.5) * Math.PI) + 1.0f;
float wj = (float) sin(((j + 0.5) / 256.0 - 0.5) * Math.PI) + 1.0f;
float w = 0.5f * (wi * wi + wj * wj);
mdct_window[i] = wi / w;
mdct_window[j] = wj / w;
mdct_window[511 - i] = wi / w;
mdct_window[511 - j] = wj / w;
}
}
/**
* Decode gain parameters for the coded bands
*
* @param block the gainblock for the current band
* @param numBands amount of coded bands
*/
private int decodeGainControl(GainBlock block, int numBands) {
int b;
for (b = 0; b <= numBands; b++) {
block.gBlock[b].numPoints = br.read(3);
int[] level = block.gBlock[b].levCode;
int[] loc = block.gBlock[b].locCode;
for (int j = 0; j < block.gBlock[b].numPoints; j++) {
level[j] = br.read(4);
loc[j] = br.read(5);
if (j > 0 && loc[j] <= loc[j - 1]) {
return AT3_ERROR;
}
}
}
// Clear the unused blocks
for (; b < 4; b++) {
block.gBlock[b].numPoints = 0;
}
return 0;
}
/**
* Restore the quantized tonal components
*
* @param components tonal components
* @param numBands number of coded bands
*/
private int decodeTonalComponents(TonalComponent[] components, int numBands) {
int bandFlags[] = new int[4];
int mantissa[] = new int[8];
int componentCount = 0;
int nbComponents = br.read(5);
// no tonal components
if (nbComponents == 0) {
return 0;
}
int codingModeSelector = br.read(2);
if (codingModeSelector == 2) {
return AT3_ERROR;
}
int codingMode = codingModeSelector & 1;
for (int i = 0; i < nbComponents; i++) {
for (int b = 0; b <= numBands; b++) {
bandFlags[b] = br.read1();
}
int codedValuesPerComponent = br.read(3);
int quantStepIndex = br.read(3);
if (quantStepIndex <= 1) {
return AT3_ERROR;
}
if (codingModeSelector == 3) {
codingMode = br.read1();
}
for (int b = 0; b < (numBands + 1) * 4; b++) {
if (bandFlags[b >> 2] == 0) {
continue;
}
int codedComponents = br.read(3);
for (int c = 0; c < codedComponents; c++) {
if (componentCount >= 64) {
return AT3_ERROR;
}
TonalComponent cmp = components[componentCount];
int sfIndex = br.read(6);
cmp.pos = b * 64 + br.read(6);
int maxCodedValues = SAMPLES_PER_FRAME - cmp.pos;
int codedValues = codedValuesPerComponent + 1;
codedValues = min(maxCodedValues, codedValues);
float scaleFactor = ff_atrac_sf_table[sfIndex] * inv_max_quant[quantStepIndex];
readQuantSpectralCoeffs(quantStepIndex, codingMode, mantissa, codedValues);
cmp.numCoefs = codedValues;
// inverse quant
for (int m = 0; m < codedValues; m++) {
cmp.coef[m] = mantissa[m] * scaleFactor;
}
componentCount++;
}
}
}
return componentCount;
}
/**
* Mantissa decoding
*
* @param selector which table the output values are coded with
* @param codingFlag constant length coding or variable length coding
* @param mantissas mantissa output table
* @param numCodes number of values to get
*/
private void readQuantSpectralCoeffs(int selector, int codingFlag, int[] mantissas, int numCodes) {
if (selector == 1) {
numCodes /= 2;
}
if (codingFlag != 0) {
// constant length coding (CLC)
int numBits = clc_length_tab[selector];
if (selector > 1) {
for (int i = 0; i < numCodes; i++) {
int code = (numBits != 0 ? signExtend(br.read(numBits), numBits) : 0);
mantissas[i] = code;
}
} else {
for (int i = 0; i < numCodes; i++) {
int code = (numBits != 0 ? br.read(numBits) : 0); // numBits is always 4 in this case
mantissas[i * 2 ] = mantissa_clc_tab[code >> 2];
mantissas[i * 2 + 1] = mantissa_clc_tab[code & 3];
}
}
} else {
// variable length coding (VLC)
if (selector != 1) {
for (int i = 0; i < numCodes; i++) {
int huffSymb = spectral_coeff_tab[selector - 1].getVLC2(br, 3);
huffSymb += 1;
int code = huffSymb >> 1;
if ((huffSymb & 1) != 0) {
code = -code;
}
mantissas[i] = code;
}
} else {
for (int i = 0; i < numCodes; i++) {
int huffSymb = spectral_coeff_tab[selector - 1].getVLC2(br, 3);
mantissas[i * 2 ] = mantissa_vlc_tab[huffSymb * 2 ];
mantissas[i * 2 + 1] = mantissa_vlc_tab[huffSymb * 2 + 1];
}
}
}
}
/**
* Restore the quantized band spectrum coefficients
*
* @return subband count, fix for broken specification/files
*/
private int decodeSpectrum(float[] output) {
int subbandVlcIndex[] = new int[32];
int sfIndex[] = new int[32];
int mantissas[] = new int[128];
int numSubbands = br.read(5); // number of coded subbands;
int codingMode = br.read(1); // coding Mode: 0 - VLC/ 1-CLC
// get the VLC selector table for the subbands, 0 means not coded
for (int i = 0; i <= numSubbands; i++) {
subbandVlcIndex[i] = br.read(3);
}
// read the scale factor indexes from the stream
for (int i = 0; i <= numSubbands; i++) {
if (subbandVlcIndex[i] != 0) {
sfIndex[i] = br.read(6);
}
}
int i;
for (i = 0; i <= numSubbands; i++) {
int first = subband_tab[i ];
int last = subband_tab[i + 1];
int subbandSize = last - first;
if (subbandVlcIndex[i] != 0) {
// decode spectral coefficients for this subband
// TODO: This can be done faster is several blocks share the
// same VLC selector (subband_vlc_index)
readQuantSpectralCoeffs(subbandVlcIndex[i], codingMode, mantissas, subbandSize);
// decode the scale factor for this subband
float scaleFactor = ff_atrac_sf_table[sfIndex[i]] * inv_max_quant[subbandVlcIndex[i]];
// inverse quantize the coefficients
for (int j = 0; first < last; first++, j++) {
output[first] = mantissas[j] * scaleFactor;
}
} else {
// this subband was not coded, so zero the entire subband
Arrays.fill(output, first, first + subbandSize, 0f);
}
}
// clear the subbands that were not coded
Arrays.fill(output, subband_tab[i], SAMPLES_PER_FRAME, 0f);
return numSubbands;
}
/**
* Combine the tonal band spectrum and regular band spectrum
*
* @param spectrum output spectrum buffer
* @param numComponents number of tonal components
* @param components tonal components for this band
* @return position of the last tonal coefficient
*/
private int addTonalComponents(float[] spectrum, int numComponents, TonalComponent[] components) {
int lastPos = -1;
for (int i = 0; i < numComponents; i++) {
lastPos = Math.max(components[i].pos + components[i].numCoefs, lastPos);
for (int j = 0; j < components[i].numCoefs; j++) {
spectrum[components[i].pos + j] += components[i].coef[j];
}
}
return lastPos;
}
private void reverseMatrixing(float[] su1, float[] su2, int[] prevCode, int[] currCode) {
for (int i = 0, band = 0; band < 4 * 256; band += 256, i++) {
int s1 = prevCode[i];
int s2 = currCode[i];
int nsample = band;
if (s1 != s2) {
// Selector value changed, interpolation needed.
float mc1l = matrix_coeffs[s1 * 2 ];
float mc1r = matrix_coeffs[s1 * 2 + 1];
float mc2l = matrix_coeffs[s2 * 2 ];
float mc2r = matrix_coeffs[s2 * 2 + 1];
// Interpolation is done over the first eight samples.
for (; nsample < band + 8; nsample++) {
float c1 = su1[nsample];
float c2 = su2[nsample];
c2 = c1 * INTERPOLATE(mc1l, mc2l, nsample - band) +
c2 * INTERPOLATE(mc1r, mc2r, nsample - band);
su1[nsample] = c2;
su2[nsample] = c1 * 2f - c2;
}
}
// Apply the matrix without interpolation.
switch (s2) {
case 0: // M/S decoding
for (; nsample < band + 256; nsample++) {
float c1 = su1[nsample];
float c2 = su2[nsample];
su1[nsample] = c2 * 2f;
su2[nsample] = (c1 - c2) * 2f;
}
break;
case 1:
for (; nsample < band + 256; nsample++) {
float c1 = su1[nsample];
float c2 = su2[nsample];
su1[nsample] = (c1 + c2) * 2f;
su2[nsample] = c2 * -2f;
}
break;
case 2:
case 3:
for (; nsample < band + 256; nsample++) {
float c1 = su1[nsample];
float c2 = su2[nsample];
su1[nsample] = c1 + c2;
su2[nsample] = c1 - c2;
}
break;
default:
log.fatal(String.format("Invalid s2 code %d", s2));
break;
}
}
}
private void getChannelWeights(int index, int flag, float ch[]) {
if (index == 7) {
ch[0] = 1f;
ch[1] = 1f;
} else {
ch[0] = (index & 7) / 7f;
ch[1] = (float) sqrt(2f - ch[0] * ch[0]);
if (flag != 0) {
float tmp = ch[0];
ch[0] = ch[1];
ch[1] = tmp;
}
}
}
private float INTERPOLATE(float oldValue, float newValue, int nsample) {
return oldValue + nsample * 0.125f * (newValue - oldValue);
}
private void channelWeighting(float[] su1, float[] su2, int[] p3) {
// w[x][y] y=0 is left y=1 is right
float w[][] = new float[2][2];
if (p3[1] != 7 || p3[3] != 7) {
getChannelWeights(p3[1], p3[0], w[0]);
getChannelWeights(p3[3], p3[2], w[1]);
for (int band = 256; band < 4 * 256; band += 256) {
int nsample;
for (nsample = band; nsample < band + 8; nsample++) {
su1[nsample] *= INTERPOLATE(w[0][0], w[0][1], nsample - band);
su2[nsample] *= INTERPOLATE(w[1][0], w[1][1], nsample - band);
}
for (; nsample < band + 256; nsample++) {
su1[nsample] *= w[1][0];
su2[nsample] *= w[1][1];
}
}
}
}
/**
* Decode a Sound Unit
*
* @param snd the channel unit to be used
* @param output the decoded samples before IQMF in float representation
* @param channelNum channel number
* @param codingMode the coding mode (JOINT_STEREO or regular stereo/mono)
*/
private int decodeChannelSoundUnit(ChannelUnit snd, float[] output, int channelNum, int codingMode) {
int ret;
GainBlock gain1 = snd.gainBlock[ snd.gcBlkSwitch];
GainBlock gain2 = snd.gainBlock[1 - snd.gcBlkSwitch];
if (codingMode == JOINT_STEREO && channelNum == 1) {
if (br.read(2) != 3) {
log.error(String.format("JS mono Sound Unit id != 3"));
return AT3_ERROR;
}
} else {
if (br.read(6) != 0x28) {
log.error(String.format("Sound Unit id != 0x28"));
return AT3_ERROR;
}
}
// number of coded QMF bands
snd.bandsCoded = br.read(2);
ret = decodeGainControl(gain2, snd.bandsCoded);
if (ret != 0) {
return ret;
}
snd.numComponents = decodeTonalComponents(snd.components, snd.bandsCoded);
if (snd.numComponents < 0) {
return snd.numComponents;
}
int numSubbands = decodeSpectrum(snd.spectrum);
// Merge the decoded spectrum and tonal components
int lastTonal = addTonalComponents(snd.spectrum, snd.numComponents, snd.components);
// calculate number of used MLT/QMF bands according to the amount of coded
// spectral lines
int numBands = (subband_tab[numSubbands] - 1) >> 8;
if (lastTonal >= 0) {
numBands = max((lastTonal + 256) >> 8, numBands);
}
// Reconstruct time domain samples
for (int band = 0; band < 4; band++) {
// Perform the IMDCT step without overlapping
if (band <= numBands) {
imlt(snd.spectrum, band * 256, snd.imdctBuf, 0, (band & 1) != 0);
} else {
Arrays.fill(snd.imdctBuf, 0, 512, 0f);
}
// gain compensation and overlapping
ctx.gaincCtx.gainCompensation(snd.imdctBuf, 0, snd.prevFrame, band * 256, gain1.gBlock[band], gain2.gBlock[band], 256, output, band * 256);
}
// Swap the gain control buffers for the next frame
snd.gcBlkSwitch ^= 1;
return 0;
}
private int decodeFrame() {
int ret;
if (ctx.codingMode == JOINT_STEREO) {
// channel coupling mode
// decode Sound Unit 1
ret = decodeChannelSoundUnit(ctx.units[0], ctx.samples[0], 0, JOINT_STEREO);
if (ret != 0) {
return ret;
}
// Framedata of the su2 in the joint-stereo mode is encoded in
// reverse byte order so we need read in reverse direction
br.seek(ctx.blockAlign - 1);
br.setDirection(-1);
// Skip the sync codes (0xF8).
while (br.peek(8) == 0xF8) {
br.read(8);
}
// Fill the Weighting coeffs delay buffer
System.arraycopy(ctx.weightingDelay, 2, ctx.weightingDelay, 0, 4);
ctx.weightingDelay[4] = br.read1();
ctx.weightingDelay[5] = br.read(3);
for (int i = 0; i < 4; i++) {
ctx.matrixCoeffIndexPrev[i] = ctx.matrixCoeffIndexNow[i];
ctx.matrixCoeffIndexNow[i] = ctx.matrixCoeffIndexNext[i];
ctx.matrixCoeffIndexNext[i] = br.read(2);
}
// Decode sound Unit 2.
ret = decodeChannelSoundUnit(ctx.units[1], ctx.samples[1], 1, JOINT_STEREO);
br.setDirection(1);
br.seek(ctx.blockAlign);
if (ret != 0) {
return ret;
}
// Reconstruct the channel coefficients
reverseMatrixing(ctx.samples[0], ctx.samples[1], ctx.matrixCoeffIndexPrev, ctx.matrixCoeffIndexNow);
channelWeighting(ctx.samples[0], ctx.samples[1], ctx.weightingDelay);
} else {
// normal stereo mode or mono
// Decode the channel sound units
for (int i = 0; i < ctx.channels; i++) {
// Set the bitstream reader at the start of a channel sound unit
br.seek(i * ctx.blockAlign / ctx.channels);
ret = decodeChannelSoundUnit(ctx.units[i], ctx.samples[i], i, ctx.codingMode);
if (ret != 0) {
return ret;
}
}
}
// Apply the iQMF synthesis filter
for (int i = 0; i < ctx.channels; i++) {
Atrac.iqmf(ctx.samples[i], 0, ctx.samples[i], 256, 256, ctx.samples[i], 0, ctx.units[i].delayBuf1, ctx.tempBuf);
Atrac.iqmf(ctx.samples[i], 768, ctx.samples[i], 512, 256, ctx.samples[i], 512, ctx.units[i].delayBuf2, ctx.tempBuf);
Atrac.iqmf(ctx.samples[i], 0, ctx.samples[i], 512, 512, ctx.samples[i], 0, ctx.units[i].delayBuf3, ctx.tempBuf);
}
return 0;
}
@Override
public int decode(int inputAddr, int inputLength, int outputAddr) {
br = new BitReader(inputAddr, inputLength);
ctx.br = br;
int ret = decodeFrame();
if (ret < 0) {
return ret;
}
writeOutput(ctx.samples, outputAddr, SAMPLES_PER_FRAME, ctx.channels, ctx.outputChannels);
if (log.isDebugEnabled()) {
log.debug(String.format("Bytes read 0x%X", ctx.br.getBytesRead()));
}
return ctx.br.getBytesRead();
}
@Override
public int getNumberOfSamples() {
return SAMPLES_PER_FRAME;
}
}