From afa1b24b077c942cc3866c8a23ceecd81b010dbc Mon Sep 17 00:00:00 2001
From: Lyubomir Marinov <lyubomir.marinov@jitsi.org>
Date: Thu, 17 Apr 2014 23:14:03 +0300
Subject: [PATCH] Mitigates an issue with sample rate conversion in Windows
 Audio Session API (WASAPI) which leads to audio glitches.

---
 .../media/renderer/audio/WASAPIRenderer.java  | 499 ++++++++++--------
 1 file changed, 289 insertions(+), 210 deletions(-)

diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/WASAPIRenderer.java b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/WASAPIRenderer.java
index e7005a63..9625c478 100644
--- a/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/WASAPIRenderer.java
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/WASAPIRenderer.java
@@ -47,6 +47,128 @@ public class WASAPIRenderer
     private static final String PLUGIN_NAME
         = "Windows Audio Session API (WASAPI) Renderer";
 
+    /**
+     * Finds the first non-<tt>null</tt> element in a specific array of
+     * <tt>AudioFormat</tt>s.
+     *
+     * @param formats the array of <tt>AudioFormat</tt>s in which the first
+     * non-<tt>null</tt> element is to be found
+     * @return the first non-<tt>null</tt> element in <tt>format</tt>s if any;
+     * otherwise, <tt>null</tt>
+     */
+    private static AudioFormat findFirst(AudioFormat[] formats)
+    {
+        AudioFormat format = null;
+
+        for (AudioFormat aFormat : formats)
+        {
+            if (aFormat != null)
+            {
+                format = aFormat;
+                break;
+            }
+        }
+        return format;
+    }
+
+    /**
+     * Attempts to initialize and open a new <tt>Codec</tt> to resample media
+     * data from a specific input <tt>AudioFormat</tt> into a specific output
+     * <tt>AudioFormat</tt>. If no suitable resampler is found, returns
+     * <tt>null</tt>. If a suitable resampler is found but its initialization or
+     * opening fails, logs and swallows any <tt>Throwable</tt> and returns
+     * <tt>null</tt>.
+     *
+     * @param inFormat the <tt>AudioFormat</tt> in which the new instance is to
+     * input media data
+     * @param outFormat the <tt>AudioFormat</tt> in which the new instance is to
+     * output media data
+     * @return a new <tt>Codec</tt> which is able to resample media data from
+     * the specified <tt>inFormat</tt> into the specified <tt>outFormat</tt> if
+     * such a resampler could be found, initialized and opened; otherwise,
+     * <tt>null</tt>
+     */
+    public static Codec maybeOpenResampler(
+            AudioFormat inFormat,
+            AudioFormat outFormat)
+    {
+        @SuppressWarnings("unchecked")
+        List<String> classNames
+            = PlugInManager.getPlugInList(
+                    inFormat,
+                    outFormat,
+                    PlugInManager.CODEC);
+        Codec resampler = null;
+
+        if (classNames != null)
+        {
+            for (String className : classNames)
+            {
+                try
+                {
+                    Codec codec
+                        = (Codec) Class.forName(className).newInstance();
+                    Format setInput = codec.setInputFormat(inFormat);
+
+                    if ((setInput != null) && inFormat.matches(setInput))
+                    {
+                        Format setOutput = codec.setOutputFormat(outFormat);
+
+                        if ((setOutput != null) && outFormat.matches(setOutput))
+                        {
+                            codec.open();
+                            resampler = codec;
+                            break;
+                        }
+                    }
+                }
+                catch (Throwable t)
+                {
+                    if (t instanceof ThreadDeath)
+                        throw (ThreadDeath) t;
+                    else
+                    {
+                        logger.warn(
+                                "Failed to open resampler " + className,
+                                t);
+                    }
+                }
+            }
+        }
+        return resampler;
+    }
+
+    /**
+     * Pops a specific number of bytes from (the head of) a specific array of
+     * <tt>byte</tt>s.
+     *
+     * @param array the array of <tt>byte</tt> from which the specified number
+     * of bytes are to be popped
+     * @param arrayLength the number of elements in <tt>array</tt> which contain
+     * valid data
+     * @param length the number of bytes to be popped from <tt>array</tt>
+     * @return the number of elements in <tt>array</tt> which contain valid data
+     * after the specified number of bytes have been popped from it
+     */
+    public static int pop(byte[] array, int arrayLength, int length)
+    {
+        if (length < 0)
+            throw new IllegalArgumentException("length");
+        if (length == 0)
+            return arrayLength;
+
+        int newArrayLength = arrayLength - length;
+
+        if (newArrayLength > 0)
+        {
+            for (int i = 0, j = length; i < newArrayLength; i++, j++)
+                array[i] = array[j];
+        }
+        else
+            newArrayLength = 0;
+        return newArrayLength;
+    }
+
     /**
      * The duration in milliseconds of the endpoint buffer.
      */
@@ -139,20 +261,6 @@ public class WASAPIRenderer
      */
     private int numBufferFrames;
 
-    /**
-     * The data which has remained unwritten during earlier invocations of
-     * {@link #process(Buffer)} because it represents frames which are few
-     * enough to be accepted on their own for writing by
-     * {@link #iAudioRenderClient}.
-     */
-    private byte[] remainder;
-
-    /**
-     * The number of bytes in {@link #remainder} which represent valid audio
-     * data to be written by {@link #iAudioRenderClient}.
-     */
-    private int remainderLength;
-
     /**
      * The <tt>Codec</tt> which resamples the media provided to this
      * <tt>Renderer</tt> via {@link #process(Buffer)} into {@link #dstFormat}
@@ -175,7 +283,7 @@ public class WASAPIRenderer
 
     /**
      * The <tt>Buffer</tt> which provides the input to {@link #resampler}.
-     * Represents a unit of {@link #remainder} to be processed in a single call
+     * Represents a unit of {@link #srcBuffer} to be processed in a single call
      * to <tt>resampler</tt>.
      */
     private Buffer resamplerInBuffer;
@@ -190,12 +298,32 @@ public class WASAPIRenderer
      */
     private int resamplerSampleSize;
 
+    /**
+     * The data which has remained unwritten during earlier invocations of
+     * {@link #process(Buffer)} because it represents frames which are few
+     * enough to be accepted on their own for writing by
+     * {@link #iAudioRenderClient}.
+     */
+    private byte[] srcBuffer;
+
+    /**
+     * The number of bytes in {@link #srcBuffer} which represent valid audio
+     * data to be written by {@link #iAudioRenderClient}.
+     */
+    private int srcBufferLength;
+
     /**
      * The number of channels which which this <tt>Renderer</tt> has been
      * opened.
      */
     private int srcChannels;
 
+    /**
+     * The <tt>AudioFormat</tt> with which this <tt>Renderer</tt> has been
+     * opened.
+     */
+    private AudioFormat srcFormat;
+
     /**
      * The frame size in bytes with which this <tt>Renderer</tt> has been
      * opened. It is the product of {@link #srcSampleSize} and
@@ -203,12 +331,6 @@ public class WASAPIRenderer
      */
     private int srcFrameSize;
 
-    /**
-     * The <tt>AudioFormat</tt> with which this <tt>Renderer</tt> has been
-     * opened.
-     */
-    private AudioFormat srcFormat;
-
     /**
      * The sample size in bytes with which this <tt>Renderer</tt> has been
      * opened.
@@ -224,7 +346,7 @@ public class WASAPIRenderer
 
     /**
      * The time in milliseconds at which the writing to the render endpoint
-     * buffer has started malfunctioning. For example, {@link #remainder} being
+     * buffer has started malfunctioning. For example, {@link #srcBuffer} being
      * full from the point of view of {@link #process(Buffer)} for an extended
      * period of time may indicate abnormal functioning.
      */
@@ -315,8 +437,8 @@ public synchronized void close()
 
             dstFormat = null;
             locatorIsNull = false;
-            remainder = null;
-            remainderLength = 0;
+            srcBuffer = null;
+            srcBufferLength = 0;
             srcFormat = null;
             started = false;
 
@@ -324,30 +446,6 @@ public synchronized void close()
         }
     }
 
-    /**
-     * Finds the first non-<tt>null</tt> element in a specific array of
-     * <tt>AudioFormat</tt>s.
-     *
-     * @param formats the array of <tt>AudioFormat</tt>s in which the first
-     * non-<tt>null</tt> element is to be found
-     * @return the first non-<tt>null</tt> element in <tt>format</tt>s if any;
-     * otherwise, <tt>null</tt>
-     */
-    private static AudioFormat findFirst(AudioFormat[] formats)
-    {
-        AudioFormat format = null;
-
-        for (AudioFormat aFormat : formats)
-        {
-            if (aFormat != null)
-            {
-                format = aFormat;
-                break;
-            }
-        }
-        return format;
-    }
-
     /**
      * Gets an array of alternative <tt>AudioFormat</tt>s based on
      * <tt>inputFormat</tt> with which an attempt is to be made to initialize a
@@ -390,6 +488,80 @@ private AudioFormat[] getFormatsToInitializeIAudioClient()
                     formats.add((AudioFormat) format);
                 }
             }
+
+            /*
+             * Resampling isn't very cool. Moreover, resampling between sample
+             * rates with a non-integer quotient may result in audio glitches.
+             * Try to minimize the risks of having to use any of these two when
+             * unnecessary.
+             */
+            final int sampleRate = (int) inputFormat.getSampleRate();
+
+            if (sampleRate != Format.NOT_SPECIFIED)
+            {
+                Collections.sort(
+                        formats,
+                        new Comparator<AudioFormat>()
+                        {
+                            @Override
+                            public int compare(AudioFormat af1, AudioFormat af2)
+                            {
+                                int d1 = computeSampleRateDistance(af1);
+                                int d2 = computeSampleRateDistance(af2);
+
+                                return (d1 < d2) ? -1 : (d1 == d2) ? 0 : 1;
+                            }
+
+                            private int computeSampleRateDistance(
+                                    AudioFormat af)
+                            {
+                                int sr = (int) af.getSampleRate();
+
+                                if (sr == Format.NOT_SPECIFIED)
+                                    return Integer.MAX_VALUE;
+                                else if (sr == sampleRate)
+                                    return 0;
+
+                                int min, max;
+                                boolean downsample;
+
+                                if (sr < sampleRate)
+                                {
+                                    min = sr;
+                                    max = sampleRate;
+                                    downsample = true;
+                                }
+                                else
+                                {
+                                    min = sampleRate;
+                                    max = sr;
+                                    downsample = false;
+                                }
+                                if (min == 0)
+                                    return Integer.MAX_VALUE;
+                                else
+                                {
+                                    int h = max % min;
+                                    int l = max / min;
+
+                                    /*
+                                     * Prefer AudioFormats which will cause
+                                     * upsampling to AudioFormats which will
+                                     * cause downsampling.
+                                     */
+                                    if (downsample)
+                                    {
+                                        l = Short.MAX_VALUE - l;
+                                        if (h != 0)
+                                            h = Short.MAX_VALUE - h;
+                                    }
+
+                                    return (h << 16) | l;
+                                }
+                            }
+                        });
+            }
+
             return formats.toArray(new AudioFormat[formats.size()]);
         }
     }
@@ -572,73 +744,6 @@ private void maybeOpenResampler()
         }
     }
 
-    /**
-     * Attempts to initialize and open a new <tt>Codec</tt> to resample media
-     * data from a specific input <tt>AudioFormat</tt> into a specific output
-     * <tt>AudioFormat</tt>. If no suitable resampler is found, returns
-     * <tt>null</tt>. If a suitable resampler is found but its initialization or
-     * opening fails, logs and swallows any <tt>Throwable</tt> and returns
-     * <tt>null</tt>.
-     *
-     * @param inFormat the <tt>AudioFormat</tt> in which the new instance is to
-     * input media data
-     * @param outFormat the <tt>AudioFormat</tt> in which the new instance is to
-     * output media data
-     * @return a new <tt>Codec</tt> which is able to resample media data from
-     * the specified <tt>inFormat</tt> into the specified <tt>outFormat</tt> if
-     * such a resampler could be found, initialized and opened; otherwise,
-     * <tt>null</tt>
-     */
-    public static Codec maybeOpenResampler(
-            AudioFormat inFormat,
-            AudioFormat outFormat)
-    {
-        @SuppressWarnings("unchecked")
-        List<String> classNames
-            = PlugInManager.getPlugInList(
-                    inFormat,
-                    outFormat,
-                    PlugInManager.CODEC);
-        Codec resampler = null;
-
-        if (classNames != null)
-        {
-            for (String className : classNames)
-            {
-                try
-                {
-                    Codec codec
-                        = (Codec) Class.forName(className).newInstance();
-                    Format setInput = codec.setInputFormat(inFormat);
-
-                    if ((setInput != null) && inFormat.matches(setInput))
-                    {
-                        Format setOutput = codec.setOutputFormat(outFormat);
-
-                        if ((setOutput != null) && outFormat.matches(setOutput))
-                        {
-                            codec.open();
-                            resampler = codec;
-                            break;
-                        }
-                    }
-                }
-                catch (Throwable t)
-                {
-                    if (t instanceof ThreadDeath)
-                        throw (ThreadDeath) t;
-                    else
-                    {
-                        logger.warn(
-                                "Failed to open resampler " + className,
-                                t);
-                    }
-                }
-            }
-        }
-        return resampler;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -756,17 +861,22 @@ public synchronized void open()
                          * IAudioRenderClient_Write cannot be more than the
                          * maximum capacity of the endpoint buffer.
                          */
-                        remainder = new byte[numBufferFrames * srcFrameSize];
+                        srcBuffer = new byte[numBufferFrames * srcFrameSize];
                         /*
                          * Introduce latency in order to decrease the likelihood
                          * of underflow.
                          */
-                        remainderLength = remainder.length;
+                        srcBufferLength = srcBuffer.length;
 
-                        if (resampler != null)
+                        if (resampler == null)
+                        {
+                            resamplerInBuffer = null;
+                            resamplerOutBuffer = null;
+                        }
+                        else
                         {
                             resamplerInBuffer = new Buffer();
-                            resamplerInBuffer.setData(remainder);
+                            resamplerInBuffer.setData(srcBuffer);
                             resamplerInBuffer.setFormat(srcFormat);
                             resamplerOutBuffer = new Buffer();
                         }
@@ -871,46 +981,15 @@ protected synchronized void playbackDevicePropertyChange(
     }
 
     /**
-     * Pops a specific number of bytes from {@link #remainder}. For example,
-     * because such a number of bytes have been read from <tt>remainder</tt> and
+     * Pops a specific number of bytes from {@link #srcBuffer}. For example,
+     * because such a number of bytes have been read from <tt>srcBuffer</tt> and
      * written into the rendering endpoint buffer.
      *
-     * @param length the number of bytes to pop from <tt>remainder</tt>
+     * @param length the number of bytes to pop from <tt>srcBuffer</tt>
      */
-    private void popFromRemainder(int length)
+    private void popFromSrcBuffer(int length)
     {
-        remainderLength = pop(remainder, remainderLength, length);
-    }
-
-    /**
-     * Pops a specific number of bytes from (the head of) a specific array of
-     * <tt>byte</tt>s.
-     *
-     * @param array the array of <tt>byte</tt> from which the specified number
-     * of bytes are to be popped
-     * @param arrayLength the number of elements in <tt>array</tt> which contain
-     * valid data
-     * @param length the number of bytes to be popped from <tt>array</tt>
-     * @return the number of elements in <tt>array</tt> which contain valid data
-     * after the specified number of bytes have been popped from it
-     */
-    public static int pop(byte[] array, int arrayLength, int length)
-    {
-        if (length < 0)
-            throw new IllegalArgumentException("length");
-        if (length == 0)
-            return arrayLength;
-
-        int newArrayLength = arrayLength - length;
-
-        if (newArrayLength > 0)
-        {
-            for (int i = 0, j = length; i < newArrayLength; i++, j++)
-                array[i] = array[j];
-        }
-        else
-            newArrayLength = 0;
-        return newArrayLength;
+        srcBufferLength = pop(srcBuffer, srcBufferLength, length);
     }
 
     /**
@@ -1004,11 +1083,11 @@ else if (!started)
                     else
                     {
                         /*
-                         * The process method will write into remainder, the
-                         * runInEventHandleCmd will read from remainder and
+                         * The process method will write into srcBuffer, the
+                         * runInEventHandleCmd will read from srcBuffer and
                          * write into the rendering endpoint buffer.
                          */
-                        int toCopy = remainder.length - remainderLength;
+                        int toCopy = srcBuffer.length - srcBufferLength;
 
                         if (toCopy > 0)
                         {
@@ -1016,9 +1095,9 @@ else if (!started)
                                 toCopy = length;
                             System.arraycopy(
                                     data, offset,
-                                    remainder, remainderLength,
+                                    srcBuffer, srcBufferLength,
                                     toCopy);
-                            remainderLength += toCopy;
+                            srcBufferLength += toCopy;
 
                             if (length > toCopy)
                             {
@@ -1028,7 +1107,7 @@ else if (!started)
                             }
 
                             /*
-                             * Writing from the input Buffer into remainder has
+                             * Writing from the input Buffer into srcBuffer has
                              * occurred so it does not look like the writing to
                              * the render endpoint buffer is malfunctioning.
                              */
@@ -1041,7 +1120,7 @@ else if (!started)
                             ret |= INPUT_BUFFER_NOT_CONSUMED;
                             sleep = devicePeriod;
                             /*
-                             * No writing from the input Buffer into remainder
+                             * No writing from the input Buffer into srcBuffer
                              * has occurred so it is possible that the writing
                              * to the render endpoint buffer is malfunctioning.
                              */
@@ -1057,7 +1136,7 @@ else if (!started)
                      * There is available space in the rendering endpoint
                      * buffer into which this Renderer can write data.
                      */
-                    int effectiveLength = remainderLength + length;
+                    int effectiveLength = srcBufferLength + length;
                     int toWrite
                         = Math.min(
                                 effectiveLength,
@@ -1065,17 +1144,17 @@ else if (!started)
                     byte[] effectiveData;
                     int effectiveOffset;
 
-                    if (remainderLength > 0)
+                    if (srcBufferLength > 0)
                     {
                         /*
                          * There is remainder/residue from earlier invocations
                          * of the method. This Renderer will feed
-                         * iAudioRenderClient from remainder.
+                         * iAudioRenderClient from srcBuffer.
                          */
-                        effectiveData = remainder;
+                        effectiveData = srcBuffer;
                         effectiveOffset = 0;
 
-                        int toCopy = toWrite - remainderLength;
+                        int toCopy = toWrite - srcBufferLength;
 
                         if (toCopy <= 0)
                             ret |= INPUT_BUFFER_NOT_CONSUMED;
@@ -1085,12 +1164,12 @@ else if (!started)
                                 toCopy = length;
                             System.arraycopy(
                                     data, offset,
-                                    remainder, remainderLength,
+                                    srcBuffer, srcBufferLength,
                                     toCopy);
-                            remainderLength += toCopy;
+                            srcBufferLength += toCopy;
 
-                            if (toWrite > remainderLength)
-                                toWrite = remainderLength;
+                            if (toWrite > srcBufferLength)
+                                toWrite = srcBufferLength;
 
                             if (length > toCopy)
                             {
@@ -1161,9 +1240,9 @@ else if (!started)
                                  */
                                 System.arraycopy(
                                         data, offset,
-                                        remainder, remainderLength,
+                                        srcBuffer, srcBufferLength,
                                         toWrite);
-                                remainderLength += toWrite;
+                                srcBufferLength += toWrite;
                                 written = toWrite;
                             }
                             if (length > written)
@@ -1175,8 +1254,8 @@ else if (!started)
                         }
                         else if (written > 0)
                         {
-                            // We have consumed frames from remainder.
-                            popFromRemainder(written);
+                            // We have consumed frames from srcBuffer.
+                            popFromSrcBuffer(written);
                         }
 
                         if (writeIsMalfunctioningSince
@@ -1204,10 +1283,10 @@ else if (written > 0)
                     {
                         /*
                          * The writing to the render endpoint buffer has taken
-                         * too long so whatever is in remainder is surely
+                         * too long so whatever is in srcBuffer is surely
                          * out-of-date.
                          */
-                        remainderLength = 0;
+                        srcBufferLength = 0;
                         ret = BUFFER_PROCESSED_FAILED;
                         logger.warn(
                                 "Audio endpoint device appears to be"
@@ -1260,16 +1339,16 @@ else if (written > 0)
     }
 
     /**
-     * Processes audio samples from {@link #remainder} through
+     * Processes audio samples from {@link #srcBuffer} through
      * {@link #resampler} i.e. resamples them in order to produce media data
      * in {@link #resamplerOutBuffer} to be written into the render endpoint
      * buffer.
      *
-     * @param inOffset the offset in <tt>remainder</tt> at which the audio
+     * @param inOffset the offset in <tt>srcBuffer</tt> at which the audio
      * samples to be resampled begin
-     * @param inLength the number of bytes in <tt>remainder</tt> beginning at
+     * @param inLength the number of bytes in <tt>srcBuffer</tt> beginning at
      * <tt>inOffset</tt> which are to be resampled
-     * @return the number of bytes from <tt>remainder</tt> which have been
+     * @return the number of bytes from <tt>srcBuffer</tt> which have been
      * resampled and written into {@link #resamplerOutBuffer}
      */
     private int resample(int inOffset, int inLength)
@@ -1350,10 +1429,10 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
 
                     int numFramesRequested = numBufferFrames - numPaddingFrames;
 
-                    if (resampler != null)
+                    if ((resampler != null) && (numFramesRequested > 0))
                     {
                         /*
-                         * Since remainder is measured in units based on
+                         * Since srcBuffer is measured in units based on
                          * srcFormat and numFramesRequested is currently
                          * expressed in units based on dstFormat, convert
                          * numFramesRequested in units based on srcFormat. 
@@ -1382,32 +1461,32 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
                     if (numFramesRequested > 0)
                     {
                         /*
-                         * Write as much from remainder as possible while
+                         * Write as much from srcBuffer as possible while
                          * minimizing the risk of audio glitches and the amount
                          * of artificial/induced silence.
                          */
-                        int remainderFrames = remainderLength / srcFrameSize;
+                        int srcBufferFrames = srcBufferLength / srcFrameSize;
 
-                        if ((numFramesRequested > remainderFrames)
-                                && (remainderFrames >= devicePeriodInFrames))
-                            numFramesRequested = remainderFrames;
+                        if ((numFramesRequested > srcBufferFrames)
+                                && (srcBufferFrames >= devicePeriodInFrames))
+                            numFramesRequested = srcBufferFrames;
 
                         // Pad with silence in order to avoid underflows.
                         // TODO why can the toWrite calculation get too big?
                         int toWrite = numFramesRequested * srcFrameSize;
-                        if (toWrite > remainder.length)
-                        {
-                            toWrite = remainder.length;
-                        }
 
-                        int silence = toWrite - remainderLength;
+                        if (toWrite > srcBuffer.length)
+                            toWrite = srcBuffer.length;
+
+                        int silence = toWrite - srcBufferLength;
+
                         if (silence > 0)
                         {
                             Arrays.fill(
-                                    remainder,
-                                    remainderLength, toWrite,
+                                    srcBuffer,
+                                    srcBufferLength, toWrite,
                                     (byte) 0);
-                            remainderLength = toWrite;
+                            srcBufferLength = toWrite;
                         }
 
                         /*
@@ -1420,7 +1499,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
                         {
                             BasicVolumeControl.applyGain(
                                     gainControl,
-                                    remainder, 0, toWrite);
+                                    srcBuffer, 0, toWrite);
                         }
 
                         int written;
@@ -1429,7 +1508,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
                         {
                             written
                                 = maybeIAudioRenderClientWrite(
-                                        remainder, 0, toWrite,
+                                        srcBuffer, 0, toWrite,
                                         srcSampleSize, srcChannels);
                         }
                         else
@@ -1457,7 +1536,7 @@ private void runInEventHandleCmd(Runnable eventHandleCmd)
                         }
                         if (written != 0)
                         {
-                            popFromRemainder(written);
+                            popFromSrcBuffer(written);
 
                             if (writeIsMalfunctioningSince
                                     != DiagnosticsControl.NEVER)
@@ -1575,33 +1654,33 @@ public synchronized void start()
              * Introduce latency in order to decrease the likelihood of
              * underflow.
              */
-            if (remainder != null)
+            if (srcBuffer != null)
             {
-                if (remainderLength > 0)
+                if (srcBufferLength > 0)
                 {
                     /*
-                     * Shift the valid audio data to the end of remainder so
+                     * Shift the valid audio data to the end of srcBuffer so
                      * that silence can be written at the beginning.
                      */
-                    for (int i = remainder.length - 1, j = remainderLength - 1;
+                    for (int i = srcBuffer.length - 1, j = srcBufferLength - 1;
                             j >= 0;
                             i--, j--)
                     {
-                        remainder[i] = remainder[j];
+                        srcBuffer[i] = srcBuffer[j];
                     }
                 }
-                else if (remainderLength < 0)
-                    remainderLength = 0;
+                else if (srcBufferLength < 0)
+                    srcBufferLength = 0;
 
                 /*
-                 * If there is valid audio data in remainder, it has been
+                 * If there is valid audio data in srcBuffer, it has been
                  * shifted to the end to make room for silence at the beginning.
                  */
-                int silence = remainder.length - remainderLength;
+                int silence = srcBuffer.length - srcBufferLength;
 
                 if (silence > 0)
-                    Arrays.fill(remainder, 0, silence, (byte) 0);
-                remainderLength = remainder.length;
+                    Arrays.fill(srcBuffer, 0, silence, (byte) 0);
+                srcBufferLength = srcBuffer.length;
             }
 
             try
-- 
GitLab