1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.drrickorang.loopback; 18 19 import java.util.Arrays; 20 21 /** 22 * Maintains two ring buffers for recording wav data 23 * At any one time one buffer is available for writing to file while one is recording incoming data 24 */ 25 public class WaveDataRingBuffer { 26 27 public interface ReadableWaveDeck { writeToFile(AudioFileOutput audioFile)28 boolean writeToFile(AudioFileOutput audioFile); 29 } 30 31 private WaveDeck mLoadedDeck; 32 private WaveDeck mShelvedDeck; 33 WaveDataRingBuffer(int size)34 public WaveDataRingBuffer(int size) { 35 if (size < Constant.SAMPLING_RATE_MIN * Constant.BUFFER_TEST_DURATION_SECONDS_MIN) { 36 size = Constant.SAMPLING_RATE_MIN * Constant.BUFFER_TEST_DURATION_SECONDS_MIN; 37 } else if (size > Constant.SAMPLING_RATE_MAX * Constant.BUFFER_TEST_DURATION_SECONDS_MAX) { 38 size = Constant.SAMPLING_RATE_MAX * Constant.BUFFER_TEST_DURATION_SECONDS_MAX; 39 } 40 mLoadedDeck = new WaveDeck(size); 41 mShelvedDeck = new WaveDeck(size); 42 } 43 writeWaveData(double[] data, int srcPos, int length)44 public synchronized void writeWaveData(double[] data, int srcPos, int length) { 45 mLoadedDeck.writeWaveData(data, srcPos, length); 46 } 47 getWaveRecord()48 public synchronized double[] getWaveRecord() { 49 return mLoadedDeck.getWaveRecord(); 50 } 51 SwapDecks()52 private void SwapDecks() { 53 WaveDeck temp = mShelvedDeck; 54 mShelvedDeck = mLoadedDeck; 55 mLoadedDeck = temp; 56 } 57 58 /** 59 * Returns currently writing buffer as writeToFile interface, load erased shelved deck for write 60 * If shelved deck is still being read returns null 61 **/ getWaveDeck()62 public synchronized ReadableWaveDeck getWaveDeck() { 63 if (!mShelvedDeck.isBeingRead()) { 64 SwapDecks(); 65 mShelvedDeck.readyForRead(); 66 mLoadedDeck.reset(); 67 return mShelvedDeck; 68 } else { 69 return null; 70 } 71 } 72 73 /** 74 * Maintains a recording of wave data of last n seconds 75 */ 76 public class WaveDeck implements ReadableWaveDeck { 77 78 private double[] mWaveRecord; 79 private volatile int mIndex = 0; // between 0 and mWaveRecord.length - 1 80 private boolean mArrayFull = false; // true after mIndex has wrapped 81 private boolean mIsBeingRead = false; 82 WaveDeck(int size)83 public WaveDeck(int size) { 84 mWaveRecord = new double[size]; 85 } 86 87 /** 88 * Write length number of doubles from data into ring buffer from starting srcPos 89 */ writeWaveData(double[] data, int srcPos, int length)90 public void writeWaveData(double[] data, int srcPos, int length) { 91 if (length > data.length - srcPos) { 92 // requested to write more data than available 93 // bad request leave data un-affected 94 return; 95 } 96 97 if (length >= mWaveRecord.length) { 98 // requested write would fill or exceed ring buffer capacity 99 // fill ring buffer with last segment of requested write 100 System.arraycopy(data, srcPos + (length - mWaveRecord.length), mWaveRecord, 0, 101 mWaveRecord.length); 102 mIndex = 0; 103 } else if (mWaveRecord.length - mIndex > length) { 104 // write requested data from current offset 105 System.arraycopy(data, srcPos, mWaveRecord, mIndex, length); 106 mIndex += length; 107 } else { 108 // write to available buffer then wrap and overwrite previous records 109 if (!mArrayFull) { 110 mArrayFull = true; 111 } 112 113 int availBuff = mWaveRecord.length - mIndex; 114 115 System.arraycopy(data, srcPos, mWaveRecord, mIndex, availBuff); 116 System.arraycopy(data, srcPos + availBuff, mWaveRecord, 0, length - availBuff); 117 118 mIndex = length - availBuff; 119 120 } 121 122 } 123 124 /** 125 * Returns a private copy of recorded wave data 126 * 127 * @return double array of wave recording, rearranged with oldest sample at first index 128 */ getWaveRecord()129 public double[] getWaveRecord() { 130 double outputBuffer[] = new double[mWaveRecord.length]; 131 132 if (!mArrayFull) { 133 //return partially filled sample with trailing zeroes 134 System.arraycopy(mWaveRecord, 0, outputBuffer, 0, mIndex); 135 Arrays.fill(outputBuffer, mIndex+1, outputBuffer.length-1, 0); 136 } else { 137 //copy buffer to contiguous sample and return unwrapped array 138 System.arraycopy(mWaveRecord, mIndex, outputBuffer, 0, mWaveRecord.length - mIndex); 139 System.arraycopy(mWaveRecord, 0, outputBuffer, mWaveRecord.length - mIndex, mIndex); 140 } 141 142 return outputBuffer; 143 } 144 145 /** Make buffer available for new recording **/ reset()146 public void reset() { 147 mIndex = 0; 148 mArrayFull = false; 149 } 150 isBeingRead()151 public boolean isBeingRead() { 152 return mIsBeingRead; 153 } 154 readyForRead()155 private void readyForRead() { 156 mIsBeingRead = true; 157 } 158 159 @Override writeToFile(AudioFileOutput audioFile)160 public boolean writeToFile(AudioFileOutput audioFile) { 161 boolean successfulWrite; 162 if (mArrayFull) { 163 successfulWrite = audioFile.writeRingBufferData(mWaveRecord, mIndex, mIndex); 164 } else { 165 // Write only filled part of array to file 166 successfulWrite = audioFile.writeRingBufferData(mWaveRecord, 0, mIndex); 167 } 168 169 mIsBeingRead = false; 170 return successfulWrite; 171 } 172 } 173 } 174