So I found a great bit of code here that does what I need it to do. However, I did notice via the Jconsole
that it just keeps recording and recording data and not releasing it. Obviously there is no line.close()
call. Here is the issue in order to determine if the data is good to passed on(it has to be loud enough) the line has to be constantly recording, so closing it down would not allow that determination to take place. Is there a way to release all of the memory occupied by the line.read()
without line.close()
? I noticed that I was able to perform a GC from Jconsole
in the memory tab, but it invokes in around about way a System.gc()
. Which I know is something you are not supposed to call, but it appeared to be able to clear up the memory that was being used by the buffer, so line.close()
appears to not be the only way.
Here is the code from the link except that I ripped out the GUI elements, and added where I feel like the GC should be performed.
import javax.swing.SwingUtilities;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Control.Type;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.DataLine.Info;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
public class LevelMeter {
private float amp = 0f;
private float peak = 0f;
public void setAmplitude(float amp) {
this.amp = Math.abs(amp);
}
public void setPeak(float peak) {
this.peak = Math.abs(peak);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
LevelMeter meter = new LevelMeter();
new Thread(new Recorder(meter)).start();
}
});
}
static class Recorder implements Runnable {
final LevelMeter meter;
Recorder(final LevelMeter meter) {
this.meter = meter;
}
@Override
public void run() {
AudioFormat fmt = new AudioFormat(44100f, 16, 1, true, false);
final DataLine.Info info = new DataLine.Info(SourceDataLine.class, fmt, 1);
final SourceDataLine soundLine = (SourceDataLine) AudioSystem.getLine(info);
final int bufferByteSize = 2048;
TargetDataLine line;
try {
line = AudioSystem.getTargetDataLine(fmt);
line.open(fmt, bufferByteSize);
soundLine.open(fmt);
} catch(LineUnavailableException e) {
System.err.println(e);
return;
}
byte[] buf = new byte[bufferByteSize];
float[] samples = new float[bufferByteSize / 2];
float lastPeak = 0f;
line.start();
for(int b; (b = line.read(buf, 0, buf.length)) > -1;) {
// convert bytes to samples here
for(int i = 0, s = 0; i < b;) {
int sample = 0;
sample |= buf[i++] & 0xFF; // (reverse these two lines
sample |= buf[i++] << 8; // if the format is big endian)
// normalize to range of +/-1.0f
samples[s++] = sample / 32768f;
}
float rms = 0f;
float peak = 0f;
for(float sample : samples) {
float abs = Math.abs(sample);
if(abs > peak) {
peak = abs;
}
rms += sample * sample;
}
rms = (float)Math.sqrt(rms / samples.length);
if(lastPeak > peak) {
peak = lastPeak * 0.875f;
}
if(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() > 7000000){
System.gc();
System.gc();
}
if(peak > 0.20){
sourceLine.start();
}
lastPeak = peak;
System.out.println(peak);
setMeterOnEDT(rms, peak);
}
}
void setMeterOnEDT(final float rms, final float peak) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
meter.setAmplitude(rms);
meter.setPeak(peak);
}
});
}
}
}
EDIT: No I realize that it's because I don't close the line is the reason why memory isn't freed up. The problem is that I cannot close the line because, I need to only grab the bytes that indicate that someone is actually speaking into the mic. If I close the line then its effectively muted, or at least that is my understanding. However, I have setup a dirty way to erase the buffer from time to time in my updated code above. I am currently trying to get the bytes and play them back out if they are above a certain threshold. Can I just write out from the buffer that the other line is reading into? Or do I have to setup a second buffer to write out from?