1
votes

I'm attempting to write a program in which audio is read from my computer's microphone, altered in some way(for now it's just to test it), then played back out through the speakers. As it is, it works fine, but there's a very noticeable delay in between when audio is input through the mic and when it can be heard, I'm trying to find a way to reduce this. I am aware that is nearly impossible for the delay to be completely removed, but I'm looking for a way to at least make it nearly inaudible.

The code is as follows:

package com.funguscow;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

public class Listen {

    public static void main(String[] args){
        AudioFormat format = new AudioFormat(44100, 16, 2, true, true); //get the format for audio

        DataLine.Info targetInfo = new DataLine.Info(TargetDataLine.class, format); //input line
        DataLine.Info sourceInfo = new DataLine.Info(SourceDataLine.class, format); //output line

        try {
            TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(targetInfo);
            targetLine.open(format);
            targetLine.start();

            SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(sourceInfo);
            sourceLine.open(format);
            sourceLine.start();

            int numBytesRead;
            byte[] targetData = new byte[sourceLine.getBufferSize()];

            while (true) {
                numBytesRead = targetLine.read(targetData, 0, targetData.length); //read into the buffer

                if (numBytesRead == -1) break;

                for(int i=0; i<numBytesRead/2; i++){ //apply hard distortion/clipping
                    int j = (((targetData[i * 2]) << 8) & 0xff00) | ((targetData[i * 2 + 1]) & 0xff);
                    j *= 2;
                    if(j > 65535) j = 65535;
                    if(j < 0) j = -0;
                    targetData[i * 2] = (byte)((j & 0xff00) >> 8);
                    targetData[i * 2 + 1] = (byte)(j & 0x00ff);
                }

                sourceLine.write(targetData, 0, numBytesRead); //play
            }
        }
        catch (Exception e) {
            System.err.println(e);
        }
    }

}

As it is there is a delay of what seems to be roughly 1 second, is it possible to remedy this?

2
I have the same problem. If someone found the answer, please post here..Ramesh-X
I haven't found the definitive information on this, but changing the buffer size on .open() definitely is the right approach. However, in my experience you don't have to and don't really want to make it as small as what you want to sample. For instance, 4196 let me grab samples in 2048 blocks. 44100 won't let me. To figure this out I put timing statements around the read(). If you see reads of practically 0 millis something is amiss.Joel

2 Answers

1
votes

I would declare a final int with the Buffer size. A delay of 10 milliseconds would be the number of bytes per frame times the number of frames per second divided by 100. If stereo 16-bit encoding (CD quality) at 44100fps, that would be (4 * 44100)/100 = 1764 bytes.

Then, you open both the TargetDataLine and SourceDataLine with that buffer size:

targetLine.open(format, BUFFER_SIZE);
sourceLine.open(format, BUFFER_SIZE);

Check that the lines actually are using the size you specified and use the verified value in the new byte declaration for the buffer array, too.

You would also use the constant in the reads and writes.

You might have to play around with the value to get it to be optimal. Hence it makes sense to define it just once, so you don't have to make multiple edits. The value must correspond to the number of bytes needed for an integral number of frames to be in the read or write. Too high increases latency, too low increases the likelihood of dropouts.

10 millis would be pretty good performance, especially if you aren't dealing with overly percussive sounds.

EDIT, 9/20: I was reminded of this post when someone today gave it an up vote. The issue with the buffers may have indeed been the main thing causing the 1-second latency of the OP, but making the buffers smaller is only one measure. Another measure that one can take that I've learned more recently is to use a buffer in between the input and the editing/output. The reason is that processing for both the miking and playback goes in spurts. If they are directly tied together, the slower process will define the pace. If there is a buffer in between, both can flex somewhat without impeding the other.

0
votes

How large is the buffer returned from sourceLine.getBufferSize()? If you are reading from the mic at 44,100 samples/second at 2 bytes/sample, it takes exactly 1 second to fill a 88,200 byte buffer. I would guess that the system-determined buffer is around that size. Try using a smaller buffer on line byte[] targetData = new byte[sourceLine.getBufferSize()]; I'd recommend keeping it small enough so that audio latency is 10 milliseconds or less (so 882 bytes or smaller) for best user experience.