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