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
17package org.drrickorang.loopback;
18
19import java.io.FileDescriptor;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.util.Arrays;
23
24import android.content.Context;
25import android.net.Uri;
26import android.os.ParcelFileDescriptor;
27import android.util.Log;
28
29
30/**
31 * This class is used to save the results to a .wav file.
32 */
33
34public 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
43    public AudioFileOutput(Context context, Uri uri, int samplingRate) {
44        mContext = context;
45        mUri = uri;
46        mSamplingRate = samplingRate;
47    }
48
49
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     */
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
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
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
178    private static void log(String msg) {
179        Log.v(TAG, msg);
180    }
181
182}
183