Hello,
I'm trying to stream sound to the PC's speakers using SourceDataLine, but I have trouble getting rid of a clicking noise.
It seems to occur when sourceDataLine.available() equals sourceDataLine.getBufferSize(). Is there a way to avoid this?
I am dealing with an audio of indeterminate length. Here's the test code:
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class PlaybackLoop implements LineListener {
SourceDataLine sourceDataLine;
AudioFormat format;
public PlaybackLoop() throws LineUnavailableException {
AudioFormat.Encoding
encoding = AudioFormat.Encoding.PCM_SIGNED;
int sampleRate = 8000; // samples per sec
int sampleSizeInBits = 16; // bits per sample
int channels = 1;
int frameSize = 2; // bytes per frame
int frameRate = 8000; // frames per sec
// size of 1 sample * # of channels = size of 1 frame
boolean bigEndian = true;
format = new AudioFormat(
encoding,
sampleRate,
sampleSizeInBits,
channels,
frameSize,
frameRate,
bigEndian);
// PCM_SIGNED 8000.0 Hz, 16 bit, mono, 2 bytes/frame, big-endian
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
}
public synchronized void play() throws LineUnavailableException,
InterruptedException {
int bytesPerSec = (int) format.getFrameRate() * format.getFrameSize();
int intervalMs = 200;
int loop = 50;
int bytesSize = bytesPerSec * intervalMs / 1000;
byte[] bytes = new byte[bytesSize];
// creates a high pitched sound
for (int i = 0; i < bytesSize / 2; i++) {
if (i % 2 == 0) {
writeFrame(bytes, i, 0x05dc);
} else {
writeFrame(bytes, i, 0x059c);
}
}
sourceDataLine.open(format, 16000);
sourceDataLine.addLineListener(this);
int bufferSize = sourceDataLine.getBufferSize();
System.out.println(format.toString());
System.out.println(bufferSize + " bytes of line buffer.");
long nextTime = System.currentTimeMillis() + intervalMs;
sourceDataLine.start();
for (int i = 0; i < loop; i ++) {
int available = sourceDataLine.available();
if (available == bufferSize) {
// clicking noise occurs here
System.out.println("*");
}
int w = sourceDataLine.write(bytes, 0, bytesSize);
long currentTime = System.currentTimeMillis();
if (w != bytesSize) {
System.out.println("Not all written.");
// TODO
}
// time adjustment , to prevent accumulated delay.
long delta = (nextTime - currentTime);
long wait = intervalMs + delta;
if (0 < wait) {
this.wait(wait);
nextTime += intervalMs;
} else {
nextTime = currentTime + intervalMs;
}
}
System.out.println();
System.out.println("End play()");
}
public static void main(String[] args) throws LineUnavailableException,
InterruptedException {
new PlaybackLoop().play();
}
public static void writeFrame(byte[] bytes, int halfwordOffset, int value) {
writeFrame(bytes, 0, halfwordOffset, value);
}
public static void writeFrame(byte[] bytes, int byteOffset,
int halfwordOffset, int value) {
byteOffset += 2 * halfwordOffset;
bytes[byteOffset++] = (byte) (value >> 8);
bytes[byteOffset++] = (byte) (value >> 0);
}
public void update(LineEvent event) {
System.out.println();
System.out.print("Update:");
System.out.println(event.getType().toString());
}
}