Androidに固有の遅延があり、それを考慮する必要がありますが、それ以外は...
循環バッファを作成します。N 0サンプルに対して十分な大きさである限り、どれだけ大きくてもかまいません。ここで、N個の「0」サンプルを使用して記述します。
この場合のNは(秒単位の遅延)*(ヘルツ単位のサンプルレート)です。
例:16kHzステレオで200ms:
0.2s * 16000Hz *(2チャンネル)= 3200*2サンプル=6400サンプル
おそらく16ビットのpcmデータも使用するので、バイトではなくショートを使用します。
バッファに適切な量のゼロを入力した後、マイクからのデータを入力しながら、スピーカーのデータの読み取りを開始します。
PCM Fifo:
public class PcmQueue
{
    private short                mBuf[] = null;
    private int                  mWrIdx = 0;
    private int                  mRdIdx = 0;
    private int                  mCount = 0;
    private int                  mBufSz = 0;
    private Object               mSync  = new Object();
    private PcmQueue(){}
    public PcmQueue( int nBufSz )
    {
        try {
            mBuf = new short[nBufSz];
        } catch (Exception e) {
            Log.e(this.getClass().getName(), "AudioQueue allocation failed.", e);
            mBuf = null;
            mBufSz = 0;
        }
    }
    public int doWrite( final short pWrBuf[], final int nWrBufIdx, final int nLen )
    {
        int sampsWritten   = 0;
        if ( nLen > 0 ) {
            int toWrite;
            synchronized(mSync) {
                // Write nothing if there isn't room in the buffer.
                toWrite = (nLen <= (mBufSz - mCount)) ? nLen : 0;
            }
            // We can definitely read toWrite shorts.
            while (toWrite > 0)
            {
                // Calculate how many contiguous shorts to the end of the buffer
                final int sampsToCopy = Math.min( toWrite, (mBufSz - mWrIdx) );
                // Copy that many shorts.
                System.arraycopy(pWrBuf, sampsWritten + nWrBufIdx, mBuf, mWrIdx, sampsToCopy);
                // Circular buffering.
                mWrIdx += sampsToCopy;
                if (mWrIdx >= mBufSz) {
                    mWrIdx -= mBufSz;
                }
                // Increment the number of shorts sampsWritten.
                sampsWritten += sampsToCopy;
                toWrite -= sampsToCopy;
            }
            synchronized(mSync) {
                // Increment the count.
                mCount = mCount + sampsWritten;
            }
        }
        return sampsWritten;
    }
    public int doRead( short pcmBuffer[], final int nRdBufIdx, final int nRdBufLen )
    {
        int sampsRead   = 0;
        final int nSampsToRead = Math.min( nRdBufLen, pcmBuffer.length - nRdBufIdx );
        if ( nSampsToRead > 0 ) {
            int sampsToRead;
            synchronized(mSync) {
                // Calculate how many shorts can be read from the RdBuffer.
                sampsToRead = Math.min(mCount, nSampsToRead);
            }
            // We can definitely read sampsToRead shorts.
            while (sampsToRead > 0) 
            {
                // Calculate how many contiguous shorts to the end of the buffer
                final int sampsToCopy = Math.min( sampsToRead, (mBufSz - mRdIdx) );
                // Copy that many shorts.
                System.arraycopy( mBuf, mRdIdx, pcmBuffer, sampsRead + nRdBufIdx, sampsToCopy);
                // Circular buffering.
                mRdIdx += sampsToCopy;
                if (mRdIdx >= mBufSz)  {
                    mRdIdx -= mBufSz;
                }
                // Increment the number of shorts read.
                sampsRead += sampsToCopy;
                sampsToRead -= sampsToCopy;
            }
            // Decrement the count.
            synchronized(mSync) {
                mCount = mCount - sampsRead;
            }
        }
        return sampsRead;
    }
}
そして、FIFO用に変更されたコード... TargetDataLine / SourceDataLineの経験がないので、バイト配列のみを処理する場合は、FIFOをshortではなくbyteに変更します。
private int mBufferSize; // 256
private TargetDataLine mLineOutput;
private SourceDataLine mLineInput;
public void run() {
    ... creating the DataLines and getting the lines from AudioSystem ...
    // short buffer for audio
    short[] data = new short[256];
    final int emptySamples = (int)(44100.0 * 0.2); 
    final int bufferSize = emptySamples*2; 
    PcmQueue pcmQueue = new PcmQueue( bufferSize );
    // Create a temporary empty buffer to write to the PCM queue
    {
        short[] emptyBuf = new short[emptySamples];
        Arrays.fill(emptyBuf, (short)emptySamples );
        pcmQueue.doWrite(emptyBuf, 0, emptySamples);
    }
    // start recording and playing back
    while (running) {
        mLineOutput.read(data, 0, mBufferSize);
        pcmQueue.doWrite(data, 0, mBufferSize);        
        pcmQueue.doRead(data, 0, mBufferSize);        
        mLineInput.write(data, 0, mBufferSize);
    }
    ... closing the lines and exiting ...
}