• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.cts.verifier.audioquality;
18 
19 import android.content.Context;
20 import android.media.AudioFormat;
21 import android.media.AudioRecord;
22 import android.media.AudioTrack;
23 import android.os.Environment;
24 import android.util.Log;
25 
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.nio.ByteBuffer;
33 import java.nio.ByteOrder;
34 
35 /**
36  * File and data utilities for the Audio Verifier.
37  */
38 public class Utils {
39     public static final String TAG = "AudioQualityVerifier";
40     public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
41 
42     /**
43      * @param minBufferSize requested
44      * @return the buffer size or a negative {@link AudioTrack} ERROR value
45      */
getAudioTrackBufferSize(int minBufferSize)46     public static int getAudioTrackBufferSize(int minBufferSize) {
47         int minHardwareBufferSize = AudioTrack.getMinBufferSize(
48                 AudioQualityVerifierActivity.SAMPLE_RATE,
49                 AudioFormat.CHANNEL_OUT_MONO,
50                 AudioQualityVerifierActivity.AUDIO_FORMAT);
51         if (minHardwareBufferSize < 0) {
52             return minHardwareBufferSize;
53         } else {
54             return Math.max(minHardwareBufferSize, minBufferSize);
55         }
56     }
57 
58     /**
59      * @param minBufferSize requested
60      * @return the buffer size or a negative {@link AudioRecord} ERROR value
61      */
getAudioRecordBufferSize(int minBufferSize)62     public static int getAudioRecordBufferSize(int minBufferSize) {
63         int minHardwareBufferSize = AudioRecord.getMinBufferSize(
64                 AudioQualityVerifierActivity.SAMPLE_RATE,
65                 AudioFormat.CHANNEL_IN_MONO,
66                 AudioQualityVerifierActivity.AUDIO_FORMAT);
67         if (minHardwareBufferSize < 0) {
68             return minHardwareBufferSize;
69         } else {
70             return Math.max(minHardwareBufferSize, minBufferSize);
71         }
72     }
73 
74     /**
75      *  Time delay.
76      *
77      *  @param ms time in milliseconds to pause for
78      */
delay(int ms)79     public static void delay(int ms) {
80         try {
81             Thread.sleep(ms);
82         } catch (InterruptedException e) {}
83     }
84 
getExternalDir(Context context, Object exp)85     public static String getExternalDir(Context context, Object exp) {
86         checkExternalStorageAvailable();
87         // API level 8:
88         // return context.getExternalFilesDir(null).getAbsolutePath();
89         // API level < 8:
90         String dir = Environment.getExternalStorageDirectory().getAbsolutePath();
91         dir += "/Android/data/" + exp.getClass().getPackage().getName() + "/files";
92         checkMakeDir(dir);
93         return dir;
94     }
95 
checkExternalStorageAvailable()96     private static void checkExternalStorageAvailable() {
97         String state = Environment.getExternalStorageState();
98         if (!Environment.MEDIA_MOUNTED.equals(state)) {
99             // TODO: Raise a Toast and supply internal storage instead
100         }
101     }
102 
checkMakeDir(String dir)103     private static void checkMakeDir(String dir) {
104         File f = new File(dir);
105         if (!f.exists()) {
106             f.mkdirs();
107         }
108     }
109 
110     /**
111      * Convert a string (e.g. the name of an experiment) to something more suitable
112      * for use as a filename.
113      *
114      * @param s the string to be cleaned
115      * @return a string which is similar (not necessarily unique) and safe for filename use
116      */
cleanString(String s)117     public static String cleanString(String s) {
118         StringBuilder sb = new StringBuilder();
119         for (char c : s.toCharArray()) {
120             if (Character.isWhitespace(c)) sb.append('_');
121             else if (Character.isLetterOrDigit(c)) sb.append(c);
122         }
123         return sb.toString();
124     }
125 
126     /**
127      * Convert a sub-array from bytes to shorts.
128      *
129      * @param data array of bytes to be converted
130      * @param start first index to convert (should be even)
131      * @param len number of bytes to convert (should be even)
132      * @return an array of half the length, containing shorts
133      */
byteToShortArray(byte[] data, int start, int len)134     public static short[] byteToShortArray(byte[] data, int start, int len) {
135         short[] samples = new short[len / 2];
136         ByteBuffer bb = ByteBuffer.wrap(data, start, len);
137         bb.order(BYTE_ORDER);
138         for (int i = 0; i < len / 2; i++) {
139             samples[i] = bb.getShort();
140         }
141         return samples;
142     }
143 
144     /**
145      * Convert a byte array to an array of shorts (suitable for the phone test
146      * native library's audio sample data).
147      *
148      * @param data array of bytes to be converted
149      * @return an array of half the length, containing shorts
150      */
byteToShortArray(byte[] data)151     public static short[] byteToShortArray(byte[] data) {
152         int len = data.length / 2;
153         short[] samples = new short[len];
154         ByteBuffer bb = ByteBuffer.wrap(data);
155         bb.order(BYTE_ORDER);
156         for (int i = 0; i < len; i++) {
157             samples[i] = bb.getShort();
158         }
159         return samples;
160     }
161 
162     /**
163      * Convert a short array (as returned by the phone test native library)
164      * to an array of bytes.
165      *
166      * @param samples array of shorts to be converted
167      * @return an array of twice the length, broken out into bytes
168      */
shortToByteArray(short[] samples)169     public static byte[] shortToByteArray(short[] samples) {
170         int len = samples.length;
171         byte[] data = new byte[len * 2];
172         ByteBuffer bb = ByteBuffer.wrap(data);
173         bb.order(BYTE_ORDER);
174         for (int i = 0; i < len; i++) {
175             bb.putShort(samples[i]);
176         }
177         return data;
178     }
179 
180     /**
181      * Scale the amplitude of an array of samples.
182      *
183      * @param samples to be scaled
184      * @param db decibels to scale up by (may be negative)
185      * @return the scaled samples
186      */
scale(short[] samples, float db)187     public static short[] scale(short[] samples, float db) {
188         short[] scaled = new short[samples.length];
189         // Convert decibels to a linear ratio:
190         double ratio = Math.pow(10.0, db / 20.0);
191         for (int i = 0; i < samples.length; i++) {
192             scaled[i] = (short) (samples[i] * ratio);
193         }
194         return scaled;
195     }
196 
197     /**
198      * Read an entire file into memory.
199      *
200      * @param filename to be opened
201      * @return the file data, or null in case of error
202      */
readFile(String filename)203     private static byte[] readFile(String filename) {
204         FileInputStream fis;
205         try {
206             fis = new FileInputStream(filename);
207         } catch (FileNotFoundException e1) {
208             return null;
209         }
210 
211         File file = new File(filename);
212         int len = (int) file.length();
213         byte[] data = new byte[len];
214 
215         int pos = 0;
216         int bytes = 0;
217         int count;
218         while (pos < len) {
219             try {
220                 count = fis.read(data, pos, len - pos);
221             } catch (IOException e) {
222                 return null;
223             }
224             if (count < 1) return null;
225             pos += count;
226         }
227 
228         try {
229             fis.close();
230         } catch (IOException e) {}
231         return data;
232     }
233 
234     /**
235      * Read an entire file from an InputStream.
236      * Useful as AssetManager returns these.
237      *
238      * @param stream to read file contents from
239      * @return file data
240      */
readFile(InputStream stream)241     public static byte[] readFile(InputStream stream) {
242         final int CHUNK_SIZE = 10000;
243         ByteArrayBuilder bab = new ByteArrayBuilder();
244         byte[] buf = new byte[CHUNK_SIZE];
245         int count;
246         while (true) {
247             try {
248                 count = stream.read(buf, 0, CHUNK_SIZE);
249             } catch (IOException e) {
250                 return null;
251             }
252             if (count == -1) break; // EOF
253             bab.append(buf, count);
254         }
255         return bab.toByteArray();
256     }
257 
258     /**
259      * Save binary (audio) data to a file.
260      *
261      * @param filename to be written
262      * @param data contents
263      */
saveFile(String filename, byte[] data)264     public static void saveFile(String filename, byte[] data) {
265         try {
266             FileOutputStream fos = new FileOutputStream(filename);
267             fos.write(data);
268             fos.close();
269         } catch (IOException e) {
270             Log.e(TAG, "Error writing to file " + filename, e);
271         }
272     }
273 
274     /**
275      * Push an entire array of audio data to an AudioTrack.
276      *
277      * @param at destination
278      * @param data to be written
279      * @return true if successful, or false on error
280      */
writeAudio(AudioTrack at, byte[] data)281     public static boolean writeAudio(AudioTrack at, byte[] data) {
282         int pos = 0;
283         int len = data.length;
284         int count;
285 
286         while (pos < len) {
287             count = at.write(data, pos, len - pos);
288             if (count < 0) return false;
289             pos += count;
290         }
291         at.flush();
292         return true;
293     }
294 
295     /**
296      * Determine the number of audio samples in a file
297      *
298      * @param filename file containing audio data
299      * @return number of samples in file, or 0 if file does not exist
300      */
duration(String filename)301     public static int duration(String filename) {
302         File file = new File(filename);
303         int len = (int) file.length();
304         return len / AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
305     }
306 
307     /**
308      * Determine the number of audio samples in a stimulus asset
309      *
310      * @param context to look up stimulus
311      * @param stimNum index number of this stimulus
312      * @return number of samples in stimulus
313      */
duration(Context context, int stimNum)314     public static int duration(Context context, int stimNum) {
315         byte[] data = AudioAssets.getStim(context, stimNum);
316         return data.length / AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
317     }
318 
playRawFile(String filename)319     public static void playRawFile(String filename) {
320         byte[] data = readFile(filename);
321         if (data == null) {
322             Log.e(TAG, "Cannot read " + filename);
323             return;
324         }
325         playRaw(data);
326     }
327 
playStim(Context context, int stimNum)328     public static void playStim(Context context, int stimNum) {
329         Utils.playRaw(getStim(context, stimNum));
330     }
331 
getStim(Context context, int stimNum)332     public static byte[] getStim(Context context, int stimNum) {
333         return AudioAssets.getStim(context, stimNum);
334     }
335 
getPinkNoise(Context context, int ampl, int duration)336     public static byte[] getPinkNoise(Context context, int ampl, int duration) {
337         return AudioAssets.getPinkNoise(context, ampl, duration);
338     }
339 
playRaw(byte[] data)340     public static void playRaw(byte[] data) {
341         Log.i(TAG, "Playing " + data.length + " bytes of pre-recorded audio");
342         AudioTrack at = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM, AudioQualityVerifierActivity.SAMPLE_RATE,
343                 AudioFormat.CHANNEL_OUT_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT,
344                 data.length, AudioTrack.MODE_STREAM);
345         writeAudio(at, data);
346         at.play();
347     }
348 
349     /**
350      * The equivalent of a simplified StringBuilder, but for bytes.
351      */
352     public static class ByteArrayBuilder {
353         private byte[] buf;
354         private int capacity, size;
355 
ByteArrayBuilder()356         public ByteArrayBuilder() {
357             capacity = 100;
358             size = 0;
359             buf = new byte[capacity];
360         }
361 
append(byte[] b, int nBytes)362         public void append(byte[] b, int nBytes) {
363             if (nBytes < 1) return;
364             if (size + nBytes > capacity) expandCapacity(size + nBytes);
365             System.arraycopy(b, 0, buf, size, nBytes);
366             size += nBytes;
367         }
368 
toByteArray()369         public byte[] toByteArray() {
370             byte[] result = new byte[size];
371             System.arraycopy(buf, 0, result, 0, size);
372             return result;
373         }
374 
expandCapacity(int min)375         private void expandCapacity(int min) {
376             capacity *= 2;
377             if (capacity < min) capacity = min;
378             byte[] expanded = new byte[capacity];
379             System.arraycopy(buf, 0, expanded, 0, size);
380             buf = expanded;
381         }
382     }
383 }
384