package org.jcodec.movtool.streaming.tracks; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import org.jcodec.common.IntArrayList; import org.jcodec.common.NIOUtils; import org.jcodec.containers.mp4.boxes.AudioSampleEntry; import org.jcodec.containers.mp4.boxes.EndianBox.Endian; import org.jcodec.containers.mp4.boxes.channel.ChannelUtils; import org.jcodec.containers.mp4.boxes.channel.Label; /** * 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 = new ThreadLocal<float[][]>(); private float[][] matrix; private int[][] counts; private int[][] channels; private AudioSampleEntry[] se; public DownmixHelper(AudioSampleEntry[] se, int nSamples, boolean[][] solo) { 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 = ChannelUtils.getLabels(se[tr]); IntArrayList tmp = new IntArrayList(); for (int ch = 0; ch < channels.length; ch++) { if (solo != null && !solo[tr][ch]) continue; tmp.add(ch); switch (channels[ch]) { case Left: case LeftTotal: case LeftCenter: matrixBuilder.add(new float[] { 1f, 0f }); countsBuilder.add(new int[] { 1, 0 }); break; case LeftSurround: case RearSurroundLeft: matrixBuilder.add(new float[] { .7f, 0f }); countsBuilder.add(new int[] { 1, 0 }); break; case Right: case RightTotal: case RightCenter: matrixBuilder.add(new float[] { 0f, 1f }); countsBuilder.add(new int[] { 0, 1 }); break; case RightSurround: case RearSurroundRight: matrixBuilder.add(new float[] { 0f, .7f }); countsBuilder.add(new int[] { 0, 1 }); break; case Mono: case LFEScreen: case Center: case LFE2: case Discrete: matrixBuilder.add(new float[] { .7f, .7f }); countsBuilder.add(new int[] { 1, 1 }); break; case Unused: break; default: if((channels[ch].getVal() >>> 16) == 1) { matrixBuilder.add(new float[] { .7f, .7f }); countsBuilder.add(new int[] { 1, 1 }); System.out.println("Discrete" + (channels[ch].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, AudioSampleEntry 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.calcSampleSize() == 3) { int step = nCh * 3; maxSamples = Math.min(nSamples, len / step); if (se.getEndian() == Endian.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() == Endian.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)); } }