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.experiments; 18 19 import com.android.cts.verifier.R; 20 import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity; 21 import com.android.cts.verifier.audioquality.Experiment; 22 import com.android.cts.verifier.audioquality.Utils; 23 24 import android.content.Context; 25 import android.media.AudioFormat; 26 import android.media.AudioRecord; 27 import android.media.MediaRecorder; 28 import android.util.Log; 29 30 /** 31 * LoopbackExperiment represents a general class of experiments, all of which 32 * comprise playing an audio stimulus of some kind, whilst simultaneously 33 * recording from the microphone. The recording is then analyzed to determine 34 * the test results (score and report). 35 */ 36 public class LoopbackExperiment extends Experiment { 37 protected static final int TIMEOUT = 10; 38 39 // Amount of silence in ms before and after playback 40 protected static final int END_DELAY_MS = 500; 41 42 private Recorder mRecorder = null; 43 44 public LoopbackExperiment(boolean enable) { 45 super(enable); 46 } 47 48 protected byte[] getStim(Context context) { 49 int stimNum = 2; 50 byte[] data = Utils.getStim(context, stimNum); 51 return data; 52 } 53 54 @Override 55 public void run() { 56 byte[] playbackData = getStim(mContext); 57 byte[] recordedData = loopback(playbackData); 58 59 compare(playbackData, recordedData); 60 setRecording(recordedData); 61 mTerminator.terminate(false); 62 } 63 64 protected byte[] loopback(byte[] playbackData) { 65 int samples = playbackData.length / 2; 66 int duration = (samples * 1000) / AudioQualityVerifierActivity.SAMPLE_RATE; // In ms 67 int padSamples = (END_DELAY_MS * AudioQualityVerifierActivity.SAMPLE_RATE) / 1000; 68 int totalSamples = samples + 2 * padSamples; 69 byte[] recordedData = new byte[totalSamples * 2]; 70 71 mRecorder = new Recorder(recordedData, totalSamples); 72 mRecorder.start(); 73 Utils.delay(END_DELAY_MS); 74 75 Utils.playRaw(playbackData); 76 77 int timeout = duration + 2 * END_DELAY_MS; 78 try { 79 mRecorder.join(timeout); 80 } catch (InterruptedException e) {} 81 82 return recordedData; 83 } 84 85 protected void compare(byte[] stim, byte[] record) { 86 setScore(getString(R.string.aq_complete)); 87 setReport(getString(R.string.aq_loopback_report)); 88 } 89 90 private void halt() { 91 if (mRecorder != null) { 92 mRecorder.halt(); 93 } 94 } 95 96 @Override 97 public void cancel() { 98 super.cancel(); 99 halt(); 100 } 101 102 @Override 103 public void stop() { 104 super.stop(); 105 halt(); 106 } 107 108 @Override 109 public int getTimeout() { 110 return TIMEOUT; 111 } 112 113 /* Class which records audio in a background thread, to fill the supplied buffer. */ 114 class Recorder extends Thread { 115 private AudioRecord mRecord; 116 private int mSamples; 117 private byte[] mBuffer; 118 private boolean mProceed; 119 120 Recorder(byte[] buffer, int samples) { 121 mBuffer = buffer; 122 mSamples = samples; 123 mProceed = true; 124 } 125 126 public void halt() { 127 mProceed = false; 128 } 129 130 @Override 131 public void run() { 132 final int minBufferSize = AudioQualityVerifierActivity.SAMPLE_RATE 133 * AudioQualityVerifierActivity.BYTES_PER_SAMPLE; 134 final int bufferSize = Utils.getAudioRecordBufferSize(minBufferSize); 135 136 mRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION, 137 AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, 138 AudioQualityVerifierActivity.AUDIO_FORMAT, bufferSize); 139 if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) { 140 Log.e(TAG, "Couldn't open audio for recording"); 141 return; 142 } 143 mRecord.startRecording(); 144 if (mRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { 145 Log.e(TAG, "Couldn't record"); 146 return; 147 } 148 149 captureLoop(); 150 151 mRecord.stop(); 152 mRecord.release(); 153 mRecord = null; 154 } 155 156 private void captureLoop() { 157 int totalBytes = mSamples * AudioQualityVerifierActivity.BYTES_PER_SAMPLE; 158 Log.i(TAG, "Recording " + totalBytes + " bytes"); 159 int position = 0; 160 int bytes; 161 while (position < totalBytes && mProceed) { 162 bytes = mRecord.read(mBuffer, position, totalBytes - position); 163 if (bytes < 0) { 164 if (bytes == AudioRecord.ERROR_INVALID_OPERATION) { 165 Log.e(TAG, "Recording object not initalized"); 166 } else if (bytes == AudioRecord.ERROR_BAD_VALUE) { 167 Log.e(TAG, "Invalid recording parameters"); 168 } else { 169 Log.e(TAG, "Error during recording"); 170 } 171 return; 172 } 173 position += bytes; 174 } 175 } 176 } 177 } 178