1 /*
2  * Copyright 2019 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 com.android.tv.settings.system.development.audio;
18 
19 import android.util.Log;
20 
21 import java.io.DataOutputStream;
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.nio.ShortBuffer;
26 
27 /** Contains a static method for writing an audio buffer to a WAV file. */
28 public class WavWriter {
29 
30     private static final String TAG = "WavWriter";
31 
32     private static final int HEADER_SIZE = 44;
33 
34     /**
35      * Writes an audio buffer to a WAV file.
36      *
37      * @param directory   The directory to store the file in
38      * @param audioBuffer The buffer to write to the file
39      */
writeToFile(File directory, ShortBuffer audioBuffer)40     public static void writeToFile(File directory, ShortBuffer audioBuffer) {
41         long timestamp = System.currentTimeMillis();
42         File file = new File(directory, String.format("recording_%d.wav", timestamp));
43 
44         byte[] data = toLittleEndianBytes(audioBuffer);
45 
46         try {
47             DataOutputStream stream = new DataOutputStream(
48                     new FileOutputStream(file.getAbsolutePath()));
49             writeFileHeader(stream, data.length);
50             stream.write(data, 0, data.length);
51             stream.close();
52             Log.i(TAG, String.format("WAV file written to %s", file.getAbsolutePath()));
53         } catch (Exception e) {
54             Log.e(TAG, String.format("Error writing to file: ", e.toString()));
55         }
56     }
57 
58     /**
59      * Writes a WAV header to a file.
60      *
61      * @param stream   The output stream for the file.
62      * @param numBytes The number of (non-header) bytes to be written.
63      */
writeFileHeader(DataOutputStream stream, int numBytes)64     private static void writeFileHeader(DataOutputStream stream, int numBytes) throws IOException {
65         // 1 - 4 "RIFF" Marks the file as a riff file. Characters are each 1 byte long.
66         stream.writeBytes("RIFF");
67         // 5 - 8 File size (32-bit integer). Number of bytes in the entire file, minus 8.
68         stream.write(toLittleEndianBytes(HEADER_SIZE + numBytes - 8));
69         // 9 -12 "WAVE" File Type Header. For our purposes, it always equals "WAVE".
70         stream.writeBytes("WAVE");
71         // 13-16 "fmt " Format chunk marker. Includes trailing null.
72         stream.writeBytes("fmt ");
73         // 17-20 Length of format data as listed above (16).
74         stream.write(toLittleEndianBytes(16), 0, 4);
75         // 21-22 Type of format (16-bit integer). 1 is PCM.
76         stream.write(toLittleEndianBytes(1), 0, 2);
77         // 23-24 Number of channels (16-bit integer).
78         stream.write(toLittleEndianBytes(AudioDebug.CHANNELS), 0, 2);
79         // 25-28 Sample Rate (32-bit integer). Common values are 44100 (CD), 48000 (DAT).
80         stream.write(toLittleEndianBytes(AudioDebug.SAMPLE_RATE), 0, 4);
81         // 29-32 (Sample Rate * BitsPerSample * Channels) / 8.
82         stream.write(
83                 toLittleEndianBytes(
84                         AudioDebug.SAMPLE_RATE * AudioDebug.BITRATE * AudioDebug.CHANNELS / 8), 0,
85                 4);
86         // 33-34 (BitsPerSample * Channels) / 8.
87         //        1 - 8 bit mono; 2 - 8 bit stereo/16 bit mono; 4 - 16 bit stereo
88         stream.write(toLittleEndianBytes(AudioDebug.BITRATE * AudioDebug.CHANNELS / 8), 0, 2);
89         // 35-36 Bits per sample
90         stream.write(toLittleEndianBytes(AudioDebug.BITRATE), 0, 2);
91         // 37-40 "data" chunk header. Marks the beginning of the data section.
92         stream.writeBytes("data");
93         // 41-44 Size of the data section (32-bit integer).
94         stream.write(toLittleEndianBytes(numBytes), 0, 4);
95     }
96 
toLittleEndianBytes(ShortBuffer buffer)97     private static byte[] toLittleEndianBytes(ShortBuffer buffer) {
98         int numShorts = buffer.position();
99 
100         byte[] result = new byte[numShorts * 2];
101         for (int shortIdx = 0; shortIdx < numShorts; shortIdx++) {
102             short s = buffer.get(shortIdx);
103             result[shortIdx * 2] = (byte) s;
104             result[shortIdx * 2 + 1] = (byte) (s >> 8);
105         }
106 
107         return result;
108     }
109 
toLittleEndianBytes(int x)110     private static byte[] toLittleEndianBytes(int x) {
111         byte[] result = new byte[4];
112 
113         result[0] = (byte) x;
114         result[1] = (byte) (x >> 8);
115         result[2] = (byte) (x >> 16);
116         result[3] = (byte) (x >> 24);
117 
118         return result;
119     }
120 
121 }
122