package org.jcodec.movtool.streaming.tracks;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.AudioFormat;
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
import org.jcodec.containers.mp4.boxes.EndianBox.Endian;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.movtool.streaming.VirtualPacket;
import org.jcodec.movtool.streaming.VirtualTrack;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Downmixes a set of input audio tracks ( possibly with multiple channels ) to
* one stereo track.
*
* Produces frames of exactly equal size regarless of underlying tracks PCM
* chunk sizes
*
* @author The JCodec project
*
*/
public class StereoDownmixTrack implements VirtualTrack {
private final static int FRAMES_IN_OUT_PACKET = 1024 * 20;
private VirtualTrack[] sources;
private AudioSampleEntry[] sampleEntries;
private int rate;
private int frameNo;
private boolean[][] solo;
private DownmixHelper downmix;
public StereoDownmixTrack(VirtualTrack... tracks) {
this.rate = -1;
sources = new VirtualTrack[tracks.length];
sampleEntries = new AudioSampleEntry[sources.length];
solo = new boolean[tracks.length][];
for (int i = 0; i < tracks.length; i++) {
SampleEntry se = tracks[i].getSampleEntry();
if (!(se instanceof AudioSampleEntry))
throw new IllegalArgumentException("Non audio track");
AudioSampleEntry ase = (AudioSampleEntry) se;
if (!ase.isPCM())
throw new IllegalArgumentException("Non PCM audio track.");
AudioFormat format = ase.getFormat();
if (rate != -1 && rate != format.getFrameRate())
throw new IllegalArgumentException("Can not downmix tracks of different rate.");
rate = (int) format.getFrameRate();
sampleEntries[i] = ase;
sources[i] = new PCMFlatternTrack(tracks[i], FRAMES_IN_OUT_PACKET);
solo[i] = new boolean[format.getChannels()];
}
downmix = new DownmixHelper(sampleEntries, FRAMES_IN_OUT_PACKET, null);
}
public void soloTrack(int track, boolean s) {
for (int ch = 0; ch < solo[track].length; ch++) {
solo[track][ch] = s;
}
downmix = new DownmixHelper(sampleEntries, FRAMES_IN_OUT_PACKET, solo);
}
public void soloChannel(int track, int channel, boolean s) {
solo[track][channel] = s;
downmix = new DownmixHelper(sampleEntries, FRAMES_IN_OUT_PACKET, solo);
}
public boolean isChannelMute(int track, int channel) {
return solo[track][channel];
}
public boolean[][] bulkGetSolo() {
return solo;
}
public void soloAll() {
for(int i = 0; i < solo.length; i++)
for(int j = 0; j < solo[i].length; j++)
solo[i][j] = true;
}
public void muteAll() {
for(int i = 0; i < solo.length; i++)
for(int j = 0; j < solo[i].length; j++)
solo[i][j] = false;
}
public void bulkSetSolo(boolean[][] solo) {
this.solo = solo;
downmix = new DownmixHelper(sampleEntries, FRAMES_IN_OUT_PACKET, solo);
}
@Override
public VirtualPacket nextPacket() throws IOException {
VirtualPacket[] packets = new VirtualPacket[sources.length];
boolean allNull = true;
for (int i = 0; i < packets.length; i++) {
packets[i] = sources[i].nextPacket();
allNull &= packets[i] == null;
}
if (allNull)
return null;
VirtualPacket ret = new DownmixVirtualPacket(packets, frameNo);
frameNo += FRAMES_IN_OUT_PACKET;
return ret;
}
@Override
public SampleEntry getSampleEntry() {
return MP4Muxer.audioSampleEntry("sowt", 1, 2, 2, rate, Endian.LITTLE_ENDIAN);
}
@Override
public void close() throws IOException {
for (VirtualTrack virtualTrack : this.sources) {
virtualTrack.close();
}
}
protected class DownmixVirtualPacket implements VirtualPacket {
private VirtualPacket[] packets;
private int frameNo;
public DownmixVirtualPacket(VirtualPacket[] packets, int pktNo) {
this.packets = packets;
this.frameNo = pktNo;
}
@Override
public ByteBuffer getData() throws IOException {
ByteBuffer[] data = new ByteBuffer[packets.length];
for (int i = 0; i < data.length; i++)
data[i] = packets[i] == null ? null : packets[i].getData();
ByteBuffer out = ByteBuffer.allocate(FRAMES_IN_OUT_PACKET << 2);
downmix.downmix(data, out);
return out;
}
@Override
public int getDataLen() {
return FRAMES_IN_OUT_PACKET << 2;
}
@Override
public double getPts() {
return (double) frameNo / rate;
}
@Override
public double getDuration() {
return (double) FRAMES_IN_OUT_PACKET / rate;
}
@Override
public boolean isKeyframe() {
return true;
}
@Override
public int getFrameNo() {
return frameNo;
}
}
@Override
public VirtualEdit[] getEdits() {
return null;
}
@Override
public int getPreferredTimescale() {
return rate;
}
}