1 /* 2 * Copyright (C) 2012 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.io.FileDescriptor; 20 import java.io.FileOutputStream; 21 import java.io.IOException; 22 import java.util.Arrays; 23 24 import android.content.Context; 25 import android.net.Uri; 26 import android.os.ParcelFileDescriptor; 27 import android.util.Log; 28 29 30 /** 31 * This class is used to save the results to a .wav file. 32 */ 33 34 public class AudioFileOutput { 35 private static final String TAG = "AudioFileOutput"; 36 37 private Uri mUri; 38 private Context mContext; 39 private FileOutputStream mOutputStream; 40 private final int mSamplingRate; 41 42 AudioFileOutput(Context context, Uri uri, int samplingRate)43 public AudioFileOutput(Context context, Uri uri, int samplingRate) { 44 mContext = context; 45 mUri = uri; 46 mSamplingRate = samplingRate; 47 } 48 49 writeData(double[] data)50 public boolean writeData(double[] data) { 51 return writeRingBufferData(data, 0, data.length); 52 } 53 54 /** 55 * Writes recorded wav data to file 56 * endIndex <= startIndex: Writes [startIndex, data.length) then [0, endIndex) 57 * endIndex > startIndex : Writes [startIndex, endIndex) 58 * Returns true on successful write to file 59 */ writeRingBufferData(double[] data, int startIndex, int endIndex)60 public boolean writeRingBufferData(double[] data, int startIndex, int endIndex) { 61 62 boolean status = false; 63 ParcelFileDescriptor parcelFileDescriptor = null; 64 try { 65 parcelFileDescriptor = 66 mContext.getContentResolver().openFileDescriptor(mUri, "w"); 67 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 68 mOutputStream = new FileOutputStream(fileDescriptor); 69 log("Done creating output stream"); 70 int sampleCount = endIndex - startIndex; 71 if (sampleCount <= 0) { 72 sampleCount += data.length; 73 } 74 writeHeader(sampleCount); 75 76 if (endIndex > startIndex) { 77 writeDataBuffer(data, startIndex, endIndex); 78 } else { 79 writeDataBuffer(data, startIndex, data.length); 80 writeDataBuffer(data, 0, endIndex); 81 } 82 83 mOutputStream.close(); 84 status = true; 85 parcelFileDescriptor.close(); 86 } catch (Exception e) { 87 mOutputStream = null; 88 log("Failed to open wavefile" + e); 89 } finally { 90 try { 91 if (parcelFileDescriptor != null) { 92 parcelFileDescriptor.close(); 93 } 94 } catch (Exception e) { 95 e.printStackTrace(); 96 log("Error closing ParcelFile Descriptor"); 97 } 98 } 99 return status; 100 } 101 writeHeader(int samples)102 private void writeHeader(int samples) { 103 if (mOutputStream != null) { 104 try { 105 int channels = 1; 106 int blockAlignment = 2; 107 int bitsPerSample = 16; 108 byte[] chunkSize = new byte[4]; 109 byte[] dataSize = new byte[4]; 110 int tempChunkSize = (samples * 2) + 36; 111 chunkSize[3] = (byte) (tempChunkSize >> 24); 112 chunkSize[2] = (byte) (tempChunkSize >> 16); 113 chunkSize[1] = (byte) (tempChunkSize >> 8); 114 chunkSize[0] = (byte) tempChunkSize; 115 int tempDataSize = samples * 2; 116 dataSize[3] = (byte) (tempDataSize >> 24); 117 dataSize[2] = (byte) (tempDataSize >> 16); 118 dataSize[1] = (byte) (tempDataSize >> 8); 119 dataSize[0] = (byte) tempDataSize; 120 121 byte[] header = new byte[] { 122 'R', 'I', 'F', 'F', 123 chunkSize[0], chunkSize[1], chunkSize[2], chunkSize[3], 124 'W', 'A', 'V', 'E', 125 'f', 'm', 't', ' ', 126 16, 0, 0, 0, 127 1, 0, // PCM 128 (byte) channels, 0, // number of channels 129 (byte) mSamplingRate, (byte) (mSamplingRate >> 8), 0, 0, // sample rate 130 0, 0, 0, 0, // byte rate 131 (byte) (channels * blockAlignment), 132 0, // block alignment 133 (byte) bitsPerSample, 134 0, // bits per sample 135 'd', 'a', 't', 'a', 136 dataSize[0], dataSize[1], dataSize[2], dataSize[3], 137 }; 138 mOutputStream.write(header); 139 log("Done writing header"); 140 } catch (IOException e) { 141 Log.e(TAG, "Error writing header " + e); 142 } 143 } 144 } 145 146 writeDataBuffer(double [] data, int startIndex, int end)147 private void writeDataBuffer(double [] data, int startIndex, int end) { 148 if (mOutputStream != null) { 149 try { 150 int bufferSize = 1024; //blocks of 1024 samples 151 byte [] buffer = new byte[bufferSize * 2]; 152 153 for (int ii = startIndex; ii < end; ii += bufferSize) { 154 //clear buffer 155 Arrays.fill(buffer, (byte) 0); 156 int bytesUsed = 0; 157 for (int jj = 0; jj < bufferSize; jj++) { 158 int index = ii + jj; 159 if (index >= end) 160 break; 161 int value = (int) Math.round(data[index] * Short.MAX_VALUE); 162 byte ba = (byte) (0xFF & (value >> 8)); //little-endian 163 byte bb = (byte) (0xFF & (value)); 164 buffer[(jj * 2) + 1] = ba; 165 buffer[jj * 2] = bb; 166 bytesUsed += 2; 167 } 168 mOutputStream.write(buffer, 0, bytesUsed); 169 } 170 log("Done writing data"); 171 } catch (IOException e) { 172 Log.e(TAG, "Error writing data " + e); 173 } 174 } 175 } 176 177 log(String msg)178 private static void log(String msg) { 179 Log.v(TAG, msg); 180 } 181 182 } 183