Home | History | Annotate | Download | only in loopback
      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 {
     28         boolean writeToFile(AudioFileOutput audioFile);
     29     }
     30 
     31     private WaveDeck mLoadedDeck;
     32     private WaveDeck mShelvedDeck;
     33 
     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 
     44     public synchronized void writeWaveData(double[] data, int srcPos, int length) {
     45         mLoadedDeck.writeWaveData(data, srcPos, length);
     46     }
     47 
     48     public synchronized double[] getWaveRecord() {
     49         return mLoadedDeck.getWaveRecord();
     50     }
     51 
     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      **/
     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 
     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          */
     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          */
    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 **/
    146         public void reset() {
    147             mIndex = 0;
    148             mArrayFull = false;
    149         }
    150 
    151         public boolean isBeingRead() {
    152             return mIsBeingRead;
    153         }
    154 
    155         private void readyForRead() {
    156             mIsBeingRead = true;
    157         }
    158 
    159         @Override
    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