package org.jcodec.movtool.streaming.tracks;
import java.lang.IllegalStateException;
import java.lang.System;
import java.lang.ThreadLocal;
import static org.jcodec.common.model.Label.Center;
import static org.jcodec.common.model.Label.Discrete;
import static org.jcodec.common.model.Label.LFE2;
import static org.jcodec.common.model.Label.LFEScreen;
import static org.jcodec.common.model.Label.Left;
import static org.jcodec.common.model.Label.LeftCenter;
import static org.jcodec.common.model.Label.LeftSurround;
import static org.jcodec.common.model.Label.LeftTotal;
import static org.jcodec.common.model.Label.Mono;
import static org.jcodec.common.model.Label.RearSurroundLeft;
import static org.jcodec.common.model.Label.RearSurroundRight;
import static org.jcodec.common.model.Label.Right;
import static org.jcodec.common.model.Label.RightCenter;
import static org.jcodec.common.model.Label.RightSurround;
import static org.jcodec.common.model.Label.RightTotal;
import static org.jcodec.common.model.Label.Unused;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.Label;
import org.jcodec.movtool.streaming.AudioCodecMeta;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Downmixes PCM audio data into 16 bit stereo track
*
* @author The JCodec project
*
*/
public class DownmixHelper {
private int nSamples;
private ThreadLocal<float[][]> fltBuf;
private float[][] matrix;
private int[][] counts;
private int[][] channels;
private AudioCodecMeta[] se;
public DownmixHelper(AudioCodecMeta[] se, int nSamples, boolean[][] solo) {
this.fltBuf = new ThreadLocal<float[][]>();
this.nSamples = nSamples;
this.se = se;
List<float[]> matrixBuilder = new ArrayList<float[]>();
List<int[]> countsBuilder = new ArrayList<int[]>();
List<int[]> channelsBuilder = new ArrayList<int[]>();
for (int tr = 0; tr < se.length; tr++) {
Label[] channels = se[tr].getChannelLabels();
IntArrayList tmp = IntArrayList.createIntArrayList();
for (int ch = 0; ch < channels.length; ch++) {
if (solo != null && !solo[tr][ch])
continue;
tmp.add(ch);
Label label = channels[ch];
if (label == Left || label == LeftTotal || label == LeftCenter) {
matrixBuilder.add(new float[] { 1f, 0f });
countsBuilder.add(new int[] { 1, 0 });
} else if (label == LeftSurround || label == RearSurroundLeft ) {
matrixBuilder.add(new float[] { .7f, 0f });
countsBuilder.add(new int[] { 1, 0 });
} else if (label == Right || label == RightTotal || label == RightCenter ) {
matrixBuilder.add(new float[] { 0f, 1f });
countsBuilder.add(new int[] { 0, 1 });
} else if (label == RightSurround || label == RearSurroundRight ) {
matrixBuilder.add(new float[] { 0f, .7f });
countsBuilder.add(new int[] { 0, 1 });
} else if (label == Mono || label == LFEScreen || label == Center || label == LFE2 || label == Discrete ) {
matrixBuilder.add(new float[] { .7f, .7f });
countsBuilder.add(new int[] { 1, 1 });
} else if (label == Unused) {
} else {
if((label.getVal() >>> 16) == 1) {
matrixBuilder.add(new float[] { .7f, .7f });
countsBuilder.add(new int[] { 1, 1 });
Logger.info("Discrete" + (label.getVal() & 0xffff));
}
}
}
channelsBuilder.add(tmp.toArray());
}
matrix = matrixBuilder.toArray(new float[0][]);
counts = countsBuilder.toArray(new int[0][]);
channels = channelsBuilder.toArray(new int[0][]);
}
public void downmix(ByteBuffer[] data, ByteBuffer out) {
out.order(ByteOrder.LITTLE_ENDIAN);
if (matrix.length == 0) {
out.limit(nSamples << 2);
return;
}
float[][] flt = fltBuf.get();
if (flt == null) {
flt = new float[matrix.length][nSamples];
fltBuf.set(flt);
}
for (int tr = 0, i = 0; tr < se.length; tr++) {
for (int ch = 0; ch < channels[tr].length; ch++, i++) {
toFloat(flt[i], se[tr], data[tr], channels[tr][ch], se[tr].getChannelCount());
}
}
for (int s = 0; s < nSamples; s++) {
int lcount = 0, rcount = 0;
float lSum = 0, lMul = 1, rSum = 0, rMul = 1;
for (int inp = 0; inp < matrix.length; inp++) {
float sample = flt[inp][s];
float l = matrix[inp][0] * sample;
float r = matrix[inp][1] * sample;
lSum += l;
lMul *= l;
rSum += r;
rMul *= r;
lcount += counts[inp][0];
rcount += counts[inp][1];
}
float outLeft = lcount > 1 ? clamp1f(lSum - lMul) : lSum;
float outRight = rcount > 1 ? clamp1f(rSum - rMul) : rSum;
short left = (short) (outLeft * 32767f);
short right = (short) (outRight * 32767f);
out.putShort(left);
out.putShort(right);
}
out.flip();
}
private void toFloat(float[] fSamples, AudioCodecMeta se, ByteBuffer bb, int ch, int nCh) {
byte[] ba;
int off, len;
if (bb.hasArray()) {
ba = bb.array();
off = bb.arrayOffset() + bb.position();
len = bb.remaining();
} else {
ba = NIOUtils.toArray(bb);
off = 0;
len = ba.length;
}
int maxSamples;
if (se.getSampleSize() == 3) {
int step = nCh * 3;
maxSamples = Math.min(nSamples, len / step);
if (se.getEndian() == ByteOrder.BIG_ENDIAN) {
for (int s = 0, bi = off + ch * 3; s < maxSamples; s++, bi += step) {
fSamples[s] = nextSample24BE(ba, bi);
}
} else {
for (int s = 0, bi = off + ch * 3; s < maxSamples; s++, bi += step) {
fSamples[s] = nextSample24LE(ba, bi);
}
}
} else {
int step = nCh * 2;
maxSamples = Math.min(nSamples, len / step);
if (se.getEndian() == ByteOrder.BIG_ENDIAN) {
for (int s = 0, bi = off + ch * 2; s < maxSamples; s++, bi += step) {
fSamples[s] = nextSample16BE(ba, bi);
}
} else {
for (int s = 0, bi = off + ch * 2; s < maxSamples; s++, bi += step) {
fSamples[s] = nextSample16LE(ba, bi);
}
}
}
for (int s = maxSamples; s < nSamples; s++)
fSamples[s] = 0;
}
public final static float clamp1f(float f) {
if (f > 1f)
return 1f;
if (f < -1f)
return -1f;
return f;
}
private static float rev = 1f / 2147483647;
private static final float nextSample24BE(byte[] ba, int bi) {
return rev * (((ba[bi] & 0xff) << 24) | ((ba[bi + 1] & 0xff) << 16) | ((ba[bi + 2] & 0xff) << 8));
}
private static final float nextSample24LE(byte[] ba, int bi) {
return rev * (((ba[bi] & 0xff) << 8) | ((ba[bi + 1] & 0xff) << 16) | ((ba[bi + 2] & 0xff) << 24));
}
private static final float nextSample16BE(byte[] ba, int bi) {
return rev * (((ba[bi] & 0xff) << 24) | ((ba[bi + 1] & 0xff) << 16));
}
private static final float nextSample16LE(byte[] ba, int bi) {
return rev * (((ba[bi] & 0xff) << 16) | ((ba[bi + 1] & 0xff) << 24));
}
}