Home | History | Annotate | Download | only in audioquality
      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      */
     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      */
     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      */
     79     public static void delay(int ms) {
     80         try {
     81             Thread.sleep(ms);
     82         } catch (InterruptedException e) {}
     83     }
     84 
     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 
     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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    328     public static void playStim(Context context, int stimNum) {
    329         Utils.playRaw(getStim(context, stimNum));
    330     }
    331 
    332     public static byte[] getStim(Context context, int stimNum) {
    333         return AudioAssets.getStim(context, stimNum);
    334     }
    335 
    336     public static byte[] getPinkNoise(Context context, int ampl, int duration) {
    337         return AudioAssets.getPinkNoise(context, ampl, duration);
    338     }
    339 
    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 
    356         public ByteArrayBuilder() {
    357             capacity = 100;
    358             size = 0;
    359             buf = new byte[capacity];
    360         }
    361 
    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 
    369         public byte[] toByteArray() {
    370             byte[] result = new byte[size];
    371             System.arraycopy(buf, 0, result, 0, size);
    372             return result;
    373         }
    374 
    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