---
# System prepended metadata

title: BUILD 15/9

---

# BUILD 15/9

FFTAudioProcessor.java
```java
**
 * Created by Nhien Nguyen on 5/5/2022
 * <p>
 * An audio processor which forwards the input to the output,
 * but also takes the input and executes a Fast-Fourier Transformation (FFT) on it.
 * The results of this transformation is an array of frequencies with their amplitudes,
 * which will be forwarded to the listener<br>
 * The sequence diagram is apply in Exoplayer 2.16.1
 * <p>
 * <p>
 * <center><object style="width: 480px; height: 190px;" type="image/jpeg"
 * <img
 * src="https://i.imgur.com/Bjcr9dy.jpg" style="width: 480px; height: 190px"
 * alt="FFTAudioProcessor Sequence diagram"></object></center>
 * <p>
 */

@SuppressWarnings("FieldCanBeLocal")
public class FFTAudioProcessor implements AudioProcessor {
    public static final String TAG = "FFTAudioProcessor";
    private static final int HEADER_LENGTH = 44;
    public static int SAMPLE_SIZE = 4096; //Default 4096 -> The higher SAMPLE_SIZE value, the slower data return
    public final long EXO_MIN_BUFFER_DURATION_US = 250000L;
    public final long EXO_MAX_BUFFER_DURATION_US = 750000L;
    public final long EXO_BUFFER_MULTIPLICATION_FACTOR = 4;
    private final boolean isDebugging = false;
    private final byte[] scratchBuffer = new byte[1024];
    private final ByteBuffer scratchByteBuffer = ByteBuffer.wrap(scratchBuffer).order(ByteOrder.LITTLE_ENDIAN);
    public int BUFFER_EXTRA_SIZE = SAMPLE_SIZE * 8; //lossless 8 mp3 2
    public FFTAudioProcessor.FFTListener listener;
    private byte[] mTempByteArray = new byte[SAMPLE_SIZE * 2];
    private float[] mSrc = new float[SAMPLE_SIZE];
    private float[] mDst = new float[SAMPLE_SIZE + 2];
    private float[] fft;
    private byte[] tempBufferArray;
    private Noise mNoise;
    private ByteBuffer mProcessBuffer;
    private ByteBuffer mFftBuffer;
    private ByteBuffer mOutputBuffer;
    private ByteBuffer mSrcBuffer;
    private int mSrcBufferPosition = 0;
    private int mAudioTrackBufferSize = 0;
    private int mChannelCount;
    private int mSampleRateHz;
    private int mEncoding = 0;
    private int mBytesToProcess = SAMPLE_SIZE * 2;
    private int position;
    private int limit;
    private int frameCount;
    private int singleChannelOutputSize; //UNUSED YET
    private int outputSize;
    private boolean mIsActive;
    private boolean wasActive;
    private boolean mInputEnded;
    private long mFFTProcessTimeMs;
    // Fields for legacy queueInput
    private int bytesWritten;
    private @Nullable
    RandomAccessFile randomAccessFile;
    private int counter;
    private final TeeAudioProcessor.AudioBufferSink audioBufferSink = new TeeAudioProcessor.WavFileAudioBufferSink("safeBufferSink");


    public FFTAudioProcessor() {
        mProcessBuffer = AudioProcessor.EMPTY_BUFFER;
        mFftBuffer = AudioProcessor.EMPTY_BUFFER;
        mOutputBuffer = AudioProcessor.EMPTY_BUFFER;
        mChannelCount = Format.NO_VALUE;
        mSampleRateHz = Format.NO_VALUE;
    }

    private int getDefaultBufferSizeInBytes() {
        if (isDebugging) Log.i(TAG, "getDefaultBufferSizeInBytes");
        int outputPcmFrameSize = Util.getPcmFrameSize(mEncoding, mChannelCount);
        int minBufferSize = AudioTrack.getMinBufferSize(mSampleRateHz, Util.getAudioTrackChannelConfig(mChannelCount), mEncoding);
        Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
        int multipliedBufferSize = (int) (minBufferSize * EXO_BUFFER_MULTIPLICATION_FACTOR);
        int minAppBufferSize = (int) durationUsToFrames(EXO_MIN_BUFFER_DURATION_US) * outputPcmFrameSize;
        long maxAppBufferSize = Math.max(minBufferSize, (durationUsToFrames(EXO_MAX_BUFFER_DURATION_US) * outputPcmFrameSize));
        int bufferSizeInFrames = (int) (Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize) / outputPcmFrameSize);
        return bufferSizeInFrames * outputPcmFrameSize;
    }

    private long durationUsToFrames(long durationUs) {
        return durationUs * mSampleRateHz / C.MICROS_PER_SECOND;
    }

    @Override
    public boolean configure(int sampleRateHz, int channelCount, int encoding) throws UnhandledFormatException {
        if (isDebugging) Log.i(TAG, "Configure");
        if (encoding != C.ENCODING_PCM_16BIT) {
            throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
        }
        mSampleRateHz = sampleRateHz;
        mChannelCount = channelCount;
        mEncoding = encoding;
        wasActive = mIsActive;
        mIsActive = true;
        mNoise = Noise.real(SAMPLE_SIZE);
        mAudioTrackBufferSize = getDefaultBufferSizeInBytes();
        mSrcBuffer = ByteBuffer.allocate(mAudioTrackBufferSize + BUFFER_EXTRA_SIZE);
        Log.i("FFTProcessorDetail", toString());
        return !wasActive;
    }

    @Override
    public boolean isActive() {
        return mIsActive;
    }

    @Override
    public int getOutputChannelCount() {
        return mChannelCount;
    }

    @Override
    public int getOutputEncoding() {
        return mEncoding;
    }

    @Override
    public int getOutputSampleRateHz() {
        return mSampleRateHz;
    }

    @Override
    public void queueInput(ByteBuffer inputBuffer) {
        if (isDebugging) Log.i(TAG, "queueInput");
        try {
            int remaining = inputBuffer.remaining();
            if (remaining == 0) return;
            position = inputBuffer.position();
            limit = inputBuffer.limit();
            frameCount = (limit - position) / (2 * mChannelCount);
            singleChannelOutputSize = frameCount * 2;
            outputSize = frameCount * mChannelCount * 2;
            if (mProcessBuffer.capacity() < outputSize) {
                mProcessBuffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
            } else {
                mProcessBuffer.clear();
            }

            if (mFftBuffer.capacity() < remaining) {
                mFftBuffer = ByteBuffer.allocateDirect(remaining).order(ByteOrder.nativeOrder());
            } else {
                mFftBuffer.clear();
            }
            while (position < limit) {
                int summedUp = 0;
                for (int channelIndex = 0; channelIndex < mChannelCount; channelIndex++) {
                    short current = inputBuffer.getShort(position + 2 * channelIndex);
                    mProcessBuffer.putShort(current);
                    summedUp += current;
                }
                mFftBuffer.putShort((short) (summedUp / mChannelCount));
                position += mChannelCount * 2;
            }
            inputBuffer.position(limit);
            processFFT(mFftBuffer);
            mProcessBuffer.flip();
            mOutputBuffer = this.mProcessBuffer;
        } catch (Exception e){
            e.printStackTrace();
            safeQueueInput(inputBuffer);
        }
    }

    private void safeQueueInput(ByteBuffer buffer) {
        int remaining = buffer.remaining();
        if (remaining == 0) {
            return;
        }
        audioBufferSink.handleBuffer(buffer.asReadOnlyBuffer());

        if (this.mProcessBuffer.capacity() < remaining) {
            this.mProcessBuffer = ByteBuffer.allocateDirect(remaining).order(ByteOrder.nativeOrder());
        } else {
            this.mProcessBuffer.clear();
        }
        this.mProcessBuffer.put(buffer);
        this.mProcessBuffer.flip();
        mOutputBuffer = this.mProcessBuffer;
    }

    private void processFFT(ByteBuffer buffer) {
        if (isDebugging) Log.i(TAG, "processFFT");
        if (listener == null) {
            return;
        }
        try {
            mSrcBuffer.put(buffer.array());
            mSrcBufferPosition += buffer.array().length;
            mBytesToProcess = SAMPLE_SIZE * 2;
            Byte currentByte = null;
            while (mSrcBufferPosition > mAudioTrackBufferSize) {
                mSrcBuffer.position(0);
                mSrcBuffer.get(mTempByteArray, 0, mBytesToProcess);
                for (int idx = 0; idx < mTempByteArray.length; idx++) {
                    if (currentByte == null) {
                        currentByte = mTempByteArray[idx];
                    } else {
                        mSrc[idx / 2] = ((float) currentByte * Byte.MAX_VALUE + mTempByteArray[idx]) / (Byte.MAX_VALUE * Byte.MAX_VALUE);
                        mDst[idx / 2] = 0f;
                        currentByte = null;
                    }
                }
                tempBufferArray = new byte[mSrcBuffer.remaining()];
                Arrays.fill(tempBufferArray, (byte) 0);
                mSrcBuffer.get(tempBufferArray);
                mSrcBuffer.position(mBytesToProcess);
                mSrcBuffer.compact();
                mSrcBufferPosition -= mBytesToProcess;
                mSrcBuffer.position(mSrcBufferPosition);
                fft = mNoise.fft(mSrc, mDst);
                listener.onFFTReady(mSampleRateHz, mChannelCount, fft);
                if (isDebugging)
                    Log.i("FFTProcessTime", "Process in " + (System.currentTimeMillis() - mFFTProcessTimeMs) + " ms");
                mFFTProcessTimeMs = System.currentTimeMillis();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void queueEndOfStream() {
        mInputEnded = true;
        mProcessBuffer = AudioProcessor.EMPTY_BUFFER;
    }

    @Override
    public ByteBuffer getOutput() {
        ByteBuffer outputBuffer = this.mOutputBuffer;
        this.mOutputBuffer = AudioProcessor.EMPTY_BUFFER;
        return outputBuffer;
    }

    @Override
    public boolean isEnded() {
        return mInputEnded && mProcessBuffer == AudioProcessor.EMPTY_BUFFER;
    }

    @Override
    public void flush() {
        mOutputBuffer = AudioProcessor.EMPTY_BUFFER;
        mInputEnded = false;
        audioBufferSink.flush(mSampleRateHz, mChannelCount, mEncoding);
    }

    @Override
    public void reset() {
        if (isDebugging) Log.i(TAG, "reset");
        flush();
        mProcessBuffer = AudioProcessor.EMPTY_BUFFER;
        mFftBuffer = AudioProcessor.EMPTY_BUFFER;
        mOutputBuffer = AudioProcessor.EMPTY_BUFFER;
        mEncoding = Format.NO_VALUE;
        mChannelCount = Format.NO_VALUE;
        mSampleRateHz = Format.NO_VALUE;
    }

    public void closeInstance() {
        mNoise.close();
    }

    public void showToast(Context context, String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    }

    @NonNull
    @Override
    public String toString() {
        return "FFTAudioProcessor{" +
                "SAMPLE_SIZE=" + SAMPLE_SIZE +
                ", BUFFER_EXTRA_SIZE=" + BUFFER_EXTRA_SIZE +
                ", mAudioTrackBufferSize=" + mAudioTrackBufferSize +
                ", mChannelCount=" + mChannelCount +
                ", mSampleRateHz=" + mSampleRateHz +
                ", mEncoding=" + mEncoding +
                '}';
    }

    public interface FFTListener {
        void onFFTReady(int sampleRateHz, int channelCount, float[] FFTArray);
    }
}

```