| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifier.audioquality; |
| |
| import android.content.Context; |
| import android.media.AudioFormat; |
| import android.media.AudioTrack; |
| import android.os.Environment; |
| import android.util.Log; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| |
| /** |
| * File and data utilities for the Audio Verifier. |
| */ |
| public class Utils { |
| public static final String TAG = "AudioQualityVerifier"; |
| public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN; |
| |
| /** |
| * Time delay. |
| * |
| * @param ms time in milliseconds to pause for |
| */ |
| public static void delay(int ms) { |
| try { |
| Thread.sleep(ms); |
| } catch (InterruptedException e) {} |
| } |
| |
| public static String getExternalDir(Context context, Object exp) { |
| checkExternalStorageAvailable(); |
| // API level 8: |
| // return context.getExternalFilesDir(null).getAbsolutePath(); |
| // API level < 8: |
| String dir = Environment.getExternalStorageDirectory().getAbsolutePath(); |
| dir += "/Android/data/" + exp.getClass().getPackage().getName() + "/files"; |
| checkMakeDir(dir); |
| return dir; |
| } |
| |
| private static void checkExternalStorageAvailable() { |
| String state = Environment.getExternalStorageState(); |
| if (!Environment.MEDIA_MOUNTED.equals(state)) { |
| // TODO: Raise a Toast and supply internal storage instead |
| } |
| } |
| |
| private static void checkMakeDir(String dir) { |
| File f = new File(dir); |
| if (!f.exists()) { |
| f.mkdirs(); |
| } |
| } |
| |
| /** |
| * Convert a string (e.g. the name of an experiment) to something more suitable |
| * for use as a filename. |
| * |
| * @param s the string to be cleaned |
| * @return a string which is similar (not necessarily unique) and safe for filename use |
| */ |
| public static String cleanString(String s) { |
| StringBuilder sb = new StringBuilder(); |
| for (char c : s.toCharArray()) { |
| if (Character.isWhitespace(c)) sb.append('_'); |
| else if (Character.isLetterOrDigit(c)) sb.append(c); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Convert a sub-array from bytes to shorts. |
| * |
| * @param data array of bytes to be converted |
| * @param start first index to convert (should be even) |
| * @param len number of bytes to convert (should be even) |
| * @return an array of half the length, containing shorts |
| */ |
| public static short[] byteToShortArray(byte[] data, int start, int len) { |
| short[] samples = new short[len / 2]; |
| ByteBuffer bb = ByteBuffer.wrap(data, start, len); |
| bb.order(BYTE_ORDER); |
| for (int i = 0; i < len / 2; i++) { |
| samples[i] = bb.getShort(); |
| } |
| return samples; |
| } |
| |
| /** |
| * Convert a byte array to an array of shorts (suitable for the phone test |
| * native library's audio sample data). |
| * |
| * @param data array of bytes to be converted |
| * @return an array of half the length, containing shorts |
| */ |
| public static short[] byteToShortArray(byte[] data) { |
| int len = data.length / 2; |
| short[] samples = new short[len]; |
| ByteBuffer bb = ByteBuffer.wrap(data); |
| bb.order(BYTE_ORDER); |
| for (int i = 0; i < len; i++) { |
| samples[i] = bb.getShort(); |
| } |
| return samples; |
| } |
| |
| /** |
| * Convert a short array (as returned by the phone test native library) |
| * to an array of bytes. |
| * |
| * @param samples array of shorts to be converted |
| * @return an array of twice the length, broken out into bytes |
| */ |
| public static byte[] shortToByteArray(short[] samples) { |
| int len = samples.length; |
| byte[] data = new byte[len * 2]; |
| ByteBuffer bb = ByteBuffer.wrap(data); |
| bb.order(BYTE_ORDER); |
| for (int i = 0; i < len; i++) { |
| bb.putShort(samples[i]); |
| } |
| return data; |
| } |
| |
| /** |
| * Scale the amplitude of an array of samples. |
| * |
| * @param samples to be scaled |
| * @param db decibels to scale up by (may be negative) |
| * @return the scaled samples |
| */ |
| public static short[] scale(short[] samples, float db) { |
| short[] scaled = new short[samples.length]; |
| // Convert decibels to a linear ratio: |
| double ratio = Math.pow(10.0, db / 20.0); |
| for (int i = 0; i < samples.length; i++) { |
| scaled[i] = (short) (samples[i] * ratio); |
| } |
| return scaled; |
| } |
| |
| /** |
| * Read an entire file into memory. |
| * |
| * @param filename to be opened |
| * @return the file data, or null in case of error |
| */ |
| private static byte[] readFile(String filename) { |
| FileInputStream fis; |
| try { |
| fis = new FileInputStream(filename); |
| } catch (FileNotFoundException e1) { |
| return null; |
| } |
| |
| File file = new File(filename); |
| int len = (int) file.length(); |
| byte[] data = new byte[len]; |
| |
| int pos = 0; |
| int bytes = 0; |
| int count; |
| while (pos < len) { |
| try { |
| count = fis.read(data, pos, len - pos); |
| } catch (IOException e) { |
| return null; |
| } |
| if (count < 1) return null; |
| pos += count; |
| } |
| |
| try { |
| fis.close(); |
| } catch (IOException e) {} |
| return data; |
| } |
| |
| /** |
| * Read an entire file from an InputStream. |
| * Useful as AssetManager returns these. |
| * |
| * @param stream to read file contents from |
| * @return file data |
| */ |
| public static byte[] readFile(InputStream stream) { |
| final int CHUNK_SIZE = 10000; |
| ByteArrayBuilder bab = new ByteArrayBuilder(); |
| byte[] buf = new byte[CHUNK_SIZE]; |
| int count; |
| while (true) { |
| try { |
| count = stream.read(buf, 0, CHUNK_SIZE); |
| } catch (IOException e) { |
| return null; |
| } |
| if (count == -1) break; // EOF |
| bab.append(buf, count); |
| } |
| return bab.toByteArray(); |
| } |
| |
| /** |
| * Save binary (audio) data to a file. |
| * |
| * @param filename to be written |
| * @param data contents |
| */ |
| public static void saveFile(String filename, byte[] data) { |
| try { |
| FileOutputStream fos = new FileOutputStream(filename); |
| fos.write(data); |
| fos.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Error writing to file " + filename); |
| } |
| } |
| |
| /** |
| * Push an entire array of audio data to an AudioTrack. |
| * |
| * @param at destination |
| * @param data to be written |
| * @return true if successful, or false on error |
| */ |
| public static boolean writeAudio(AudioTrack at, byte[] data) { |
| int pos = 0; |
| int len = data.length; |
| int count; |
| |
| while (pos < len) { |
| count = at.write(data, pos, len - pos); |
| if (count < 0) return false; |
| pos += count; |
| } |
| at.flush(); |
| return true; |
| } |
| |
| /** |
| * Determine the number of audio samples in a file |
| * |
| * @param filename file containing audio data |
| * @return number of samples in file, or 0 if file does not exist |
| */ |
| public static int duration(String filename) { |
| File file = new File(filename); |
| int len = (int) file.length(); |
| return len / AudioQualityVerifierActivity.BYTES_PER_SAMPLE; |
| } |
| |
| /** |
| * Determine the number of audio samples in a stimulus asset |
| * |
| * @param context to look up stimulus |
| * @param stimNum index number of this stimulus |
| * @return number of samples in stimulus |
| */ |
| public static int duration(Context context, int stimNum) { |
| byte[] data = AudioAssets.getStim(context, stimNum); |
| return data.length / AudioQualityVerifierActivity.BYTES_PER_SAMPLE; |
| } |
| |
| public static void playRawFile(String filename) { |
| byte[] data = readFile(filename); |
| if (data == null) { |
| Log.e(TAG, "Cannot read " + filename); |
| return; |
| } |
| playRaw(data); |
| } |
| |
| public static void playStim(Context context, int stimNum) { |
| Utils.playRaw(getStim(context, stimNum)); |
| } |
| |
| public static byte[] getStim(Context context, int stimNum) { |
| return AudioAssets.getStim(context, stimNum); |
| } |
| |
| public static byte[] getPinkNoise(Context context, int ampl, int duration) { |
| return AudioAssets.getPinkNoise(context, ampl, duration); |
| } |
| |
| public static void playRaw(byte[] data) { |
| Log.i(TAG, "Playing " + data.length + " bytes of pre-recorded audio"); |
| AudioTrack at = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM, AudioQualityVerifierActivity.SAMPLE_RATE, |
| AudioFormat.CHANNEL_OUT_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT, |
| data.length, AudioTrack.MODE_STREAM); |
| writeAudio(at, data); |
| at.play(); |
| } |
| |
| /** |
| * The equivalent of a simplified StringBuilder, but for bytes. |
| */ |
| public static class ByteArrayBuilder { |
| private byte[] buf; |
| private int capacity, size; |
| |
| public ByteArrayBuilder() { |
| capacity = 100; |
| size = 0; |
| buf = new byte[capacity]; |
| } |
| |
| public void append(byte[] b, int nBytes) { |
| if (nBytes < 1) return; |
| if (size + nBytes > capacity) expandCapacity(size + nBytes); |
| System.arraycopy(b, 0, buf, size, nBytes); |
| size += nBytes; |
| } |
| |
| public byte[] toByteArray() { |
| byte[] result = new byte[size]; |
| System.arraycopy(buf, 0, result, 0, size); |
| return result; |
| } |
| |
| private void expandCapacity(int min) { |
| capacity *= 2; |
| if (capacity < min) capacity = min; |
| byte[] expanded = new byte[capacity]; |
| System.arraycopy(buf, 0, expanded, 0, size); |
| buf = expanded; |
| } |
| } |
| } |