/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*----------------------------------------------------------------------
*/
package org.geogebra.desktop.sound.mp3transform;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import org.geogebra.common.util.debug.Log;
public class Decoder {
public static final int BUFFER_SIZE = 2 * 1152;
public static final int MAX_CHANNELS = 2;
private static final boolean BENCHMARK = false;
protected final int[] bufferPointer = new int[MAX_CHANNELS];
protected int channels;
private SynthesisFilter filter1;
private SynthesisFilter filter2;
private Layer3Decoder l3decoder;
private boolean initialized;
private SourceDataLine line;
private final byte[] buffer = new byte[BUFFER_SIZE * 2];
private boolean stop;
private volatile boolean pause;
// public static void mainBits(String[] args) throws Exception {
// Decoder.testBits();
// Decoder.testBits();
// }
// public static void testBits() throws Exception {
// Random r = new Random();
// byte[] data = new byte[2000];
// r.nextBytes(data);
// BitReservoir bytes = new BitReservoir2();
// BitReservoir2 bytes2 = new BitReservoir2();
// for(int i=0; i<data.length; i++) {
// bytes.putByte(data[i] & 0xff);
// bytes2.putByte(data[i] & 0xff);
// }
// long time = System.currentTimeMillis();
// for(int a = 0; a<30000; a++) {
// for(int i=0; i<data.length * 8; i++) {
//// int b = bytes.getOneBit();
// int b2 = bytes2.getOneBit();
// }
// }
// // old: 7188, 11625
// // int:
// // byte: 6219,4484
// System.out.println(System.currentTimeMillis() - time);
// }
// public static void main(String[] a) throws Exception {
// Decoder decoder = new Decoder();
// BENCHMARK = false;
// for (int i = 0; i < a.length; i++) {
// decoder.playAll(new File(a[i]));
// }
// BENCHMARK = false;
// decoder.mainTest();
// System.out.println(Layer3Decoder.count);
// mainTest();
// System.exit(0);
// mainTest();
// mainTest();
// }
// private void playAll(File file) throws Exception {
// if (file.isDirectory()) {
// File[] list = file.listFiles();
// for (int i = 0; i < list.length; i++) {
// File temp = list[i];
// int x = (int) (Math.random() * list.length);
// list[i] = list[x];
// list[x] = temp;
// }
// for (int i = 0; i < list.length; i++) {
// playAll(list[i]);
// }
// } else {
// play(new BufferedInputStream(new FileInputStream(file), 4 * 1024));
// }
// }
public void decodeFrame(Header header, Bitstream stream)
throws IOException {
if (!initialized) {
double scaleFactor = 32700.0f;
int mode = header.mode();
int channels1 = mode == Header.MODE_SINGLE_CHANNEL ? 1 : 2;
filter1 = new SynthesisFilter(0, scaleFactor);
if (channels1 == 2) {
filter2 = new SynthesisFilter(1, scaleFactor);
}
initialized = true;
}
if (l3decoder == null) {
l3decoder = new Layer3Decoder(stream, header, filter1, filter2,
this);
}
l3decoder.decodeFrame();
writeBuffer();
}
protected void initOutputBuffer(SourceDataLine line1,
int numberOfChannels) {
this.line = line1;
channels = numberOfChannels;
for (int i = 0; i < channels; i++) {
bufferPointer[i] = i + i;
}
}
public void appendSamples(int channel, double[] f) {
int p = bufferPointer[channel];
for (int i = 0; i < 32; i++) {
double sample = f[i];
int s = (int) ((sample > 32767.0f) ? 32767
: ((sample < -32768.0f) ? -32768 : sample));
buffer[p] = (byte) (s >> 8);
buffer[p + 1] = (byte) (s & 0xff);
p += 4;
}
bufferPointer[channel] = p;
}
protected void writeBuffer() {
if (line != null) {
line.write(buffer, 0, bufferPointer[0]);
}
for (int i = 0; i < channels; i++) {
bufferPointer[i] = i + i;
}
}
public void play(String name, InputStream in) throws IOException {
stop = false;
int frameCount = Integer.MAX_VALUE;
// int testing;
// frameCount = 100;
Decoder decoder = new Decoder();
Bitstream stream = new Bitstream(in);
SourceDataLine line1 = null;
int error = 0;
for (int frame = 0; !stop && frame < frameCount; frame++) {
if (pause) {
line1.stop();
while (pause && !stop) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore
}
}
line1.flush();
line1.start();
}
try {
Header header = stream.readFrame();
if (header == null) {
break;
}
if (decoder.channels == 0) {
int channels1 = (header
.mode() == Header.MODE_SINGLE_CHANNEL)
? 1 : 2;
float sampleRate = header.frequency();
int sampleSize = 16;
AudioFormat format = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, sampleRate,
sampleSize, channels1, channels1 * (sampleSize / 8),
sampleRate, true);
// big endian
SourceDataLine.Info info = new DataLine.Info(
SourceDataLine.class, format);
line1 = (SourceDataLine) AudioSystem.getLine(info);
if (BENCHMARK) {
decoder.initOutputBuffer(null, channels1);
} else {
decoder.initOutputBuffer(line1, channels1);
}
// TODO sometimes the line can not be opened (maybe not
// enough system resources?): display error message
// System.out.println(line.getFormat().toString());
line1.open(format);
line1.start();
}
while (line1.available() < 100) {
Thread.yield();
Thread.sleep(200);
}
decoder.decodeFrame(header, stream);
} catch (Exception e) {
if (error++ > 1000) {
break;
}
// TODO should not write directly
Log.debug("Error at: " + name + " Frame: " + frame + " Error: "
+ e.toString());
// e.printStackTrace();
} finally {
stream.closeFrame();
}
}
if (error > 0) {
Log.debug("errors: " + error);
}
in.close();
if (line1 != null) {
line1.stop();
line1.close();
line1 = null;
}
}
public void stop() {
this.stop = true;
}
public boolean pause() {
this.pause = !pause;
return pause;
}
}