I am learning to use the sound API of Java. I've watched a tutorial on YouTube where the instructor simply creates SourceDataLine and TargetDataLine instances and uses them in separate threads. He calls the threads one after another with a Thread.sleep() method in between. Within that sleeping period, the required sound is captured and then the sound is heard.
Now, in the program below, I've tried to extend the idea and tried to achieve a continuous stream of audio. That is, I will speak and the sound will be heard automatically. But it cannot be achieved. I know I am at wrong as I'm still a newbie in this regard. What changes should I make and where? It won't be a problem if there is a satisfying delay between recording and playing the sound.
P.S. I will try to use this with OpenCV video sharing in another program. If you know something about that, please feel free to share it. Thanks!
import javax.sound.sampled.*;
import java.io.ByteArrayOutputStream;
public class Main {
public boolean recording = true;
public int rate = 0;
public static void main(String[] args) throws Exception{
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
info = new DataLine.Info(SourceDataLine.class, format);
final SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
Main m = new Main();
new record(m, format, out, targetLine);
new play(m, format, out, sourceLine);
}
synchronized public void record(TargetDataLine targetLine, ByteArrayOutputStream out){
while(!recording){
try{
wait();
}catch(Exception e){
System.out.println(e);
}
}
byte[] data = new byte[targetLine.getBufferSize()/5];
int readBytes;
readBytes = targetLine.read(data, 0, data.length);
out.write(data, 0, readBytes);
}
synchronized public void play(SourceDataLine sourceLine, ByteArrayOutputStream out){
while(recording){
try{
wait();
}catch(Exception e){
System.out.println(e);
}
}
sourceLine.write(out.toByteArray(), 0, out.size());
}
synchronized public int change(){
rate++;
if(rate > 6000 && recording){
rate = 0;
recording = false;
notifyAll();
return 1;
}
else if(rate > 6000 && !recording){
rate = 0;
recording = true;
notifyAll();
return 1;
}
return 0;
}
}
class record implements Runnable{
private Main m;
private AudioFormat format;
private ByteArrayOutputStream out;
final TargetDataLine targetLine;
public record(Main m, AudioFormat format, ByteArrayOutputStream out, TargetDataLine targetLine) throws Exception{
this.m = m;
this.format = format;
this.out = out;
this.targetLine = targetLine;
targetLine.open();
System.out.println("Started recording...");
new Thread(this).start();
}
@Override
public void run() {
targetLine.start();
while(true){
m.record(targetLine, out);
while(m.change() == 1) targetLine.stop();
targetLine.start();
}
}
}
class play implements Runnable{
private Main m;
private AudioFormat format;
private ByteArrayOutputStream out;
final SourceDataLine sourceLine;
public play(Main m, AudioFormat format, ByteArrayOutputStream out, SourceDataLine sourceLine) throws Exception{
this.m = m;
this.format = format;
this.out = out;
this.sourceLine = sourceLine;
sourceLine.open();
System.out.println("Started playing...");
new Thread(this).start();
}
@Override
public void run() {
sourceLine.start();
while(true){
m.play(sourceLine, out);
while(m.change() == 1) sourceLine.stop();
sourceLine.start();
}
}
}
Edit:
I can get two streams run one after another as follows, but I have to hard-code the threads. I wrote four threads individually. How can I write efficient code, i.e. make use of the two earlier threads and record-play sound continuously? My synchronization doesn't seem to work.
import javax.sound.sampled.*;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
public class Main {
public static void main(String[] args) throws Exception{
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
final SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
sourceLine.open();
info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
targetLine.open();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
Thread record = new Thread(){
@Override
public void run(){
targetLine.start();
byte[] data = new byte[targetLine.getBufferSize()/5];
int readBytes;
while(true){
readBytes = targetLine.read(data, 0, data.length);
out.write(data, 0, readBytes);
}
}
};
Thread play = new Thread(){
@Override
public void run(){
sourceLine.start();
while(true){
sourceLine.write(out.toByteArray(), 0, out.toByteArray().length);
}
}
};
final ByteArrayOutputStream out1 = new ByteArrayOutputStream();
Thread record1 = new Thread(() -> {
targetLine.start();
byte[] data = new byte[targetLine.getBufferSize()/5];
int readBytes;
while(true){
readBytes = targetLine.read(data, 0, data.length);
out1.write(data, 0, readBytes);
}
});
Thread play1 = new Thread(() -> {
sourceLine.start();
while(true){
sourceLine.write(out1.toByteArray(), 0, out1.toByteArray().length);
}
});
record.start();
System.out.println("Recording...");
Thread.sleep(4000);
targetLine.stop();
targetLine.drain();
targetLine.close();
play.start();
Thread.sleep(4000);
System.out.println("Playing...");
sourceLine.stop();
sourceLine.drain();
sourceLine.close();
targetLine.open();
sourceLine.open();
record1.start();
System.out.println("Recording...");
Thread.sleep(4000);
targetLine.stop();
targetLine.close();
play1.start();
Thread.sleep(4000);
System.out.println("Playing...");
sourceLine.stop();
sourceLine.close();
}
}