blob: 885e18cf67c7dbc9c0137eea03b127051a4c7eaa [file] [log] [blame]
/*
* 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;
}
}
}