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