--- /dev/null
+/*
+ * 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.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Experiment;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.util.Log;
+
+/**
+ * LoopbackExperiment represents a general class of experiments, all of which
+ * comprise playing an audio stimulus of some kind, whilst simultaneously
+ * recording from the microphone. The recording is then analyzed to determine
+ * the test results (score and report).
+ */
+public class LoopbackExperiment extends Experiment {
+ protected static final int TIMEOUT = 10;
+
+ // Amount of silence in ms before and after playback
+ protected static final int END_DELAY_MS = 500;
+
+ private Recorder mRecorder = null;
+
+ public LoopbackExperiment(boolean enable) {
+ super(enable);
+ }
+
+ protected byte[] getStim(Context context) {
+ int stimNum = 2;
+ byte[] data = Utils.getStim(context, stimNum);
+ return data;
+ }
+
+ @Override
+ public void run() {
+ byte[] playbackData = getStim(mContext);
+ byte[] recordedData = loopback(playbackData);
+
+ compare(playbackData, recordedData);
+ setRecording(recordedData);
+ mTerminator.terminate(false);
+ }
+
+ protected byte[] loopback(byte[] playbackData) {
+ int samples = playbackData.length / 2;
+ int duration = (samples * 1000) / AudioQualityVerifierActivity.SAMPLE_RATE; // In ms
+ int padSamples = (END_DELAY_MS * AudioQualityVerifierActivity.SAMPLE_RATE) / 1000;
+ int totalSamples = samples + 2 * padSamples;
+ byte[] recordedData = new byte[totalSamples * 2];
+
+ mRecorder = new Recorder(recordedData, totalSamples);
+ mRecorder.start();
+ Utils.delay(END_DELAY_MS);
+
+ Utils.playRaw(playbackData);
+
+ int timeout = duration + 2 * END_DELAY_MS;
+ try {
+ mRecorder.join(timeout);
+ } catch (InterruptedException e) {}
+
+ return recordedData;
+ }
+
+ protected void compare(byte[] stim, byte[] record) {
+ setScore(getString(R.string.aq_complete));
+ setReport(getString(R.string.aq_loopback_report));
+ }
+
+ private void halt() {
+ if (mRecorder != null) {
+ mRecorder.halt();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ super.cancel();
+ halt();
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ halt();
+ }
+
+ @Override
+ public int getTimeout() {
+ return TIMEOUT;
+ }
+
+ /* Class which records audio in a background thread, to fill the supplied buffer. */
+ class Recorder extends Thread {
+ private AudioRecord mRecord;
+ private int mSamples;
+ private byte[] mBuffer;
+ private boolean mProceed;
+
+ Recorder(byte[] buffer, int samples) {
+ mBuffer = buffer;
+ mSamples = samples;
+ mProceed = true;
+ }
+
+ public void halt() {
+ mProceed = false;
+ }
+
+ @Override
+ public void run() {
+ final int minBufferSize = AudioQualityVerifierActivity.SAMPLE_RATE
+ * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+ final int minHardwareBufferSize = AudioRecord.getMinBufferSize(
+ AudioQualityVerifierActivity.SAMPLE_RATE,
+ AudioFormat.CHANNEL_IN_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT);
+ final int bufferSize = Math.max(minHardwareBufferSize, minBufferSize);
+
+ mRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION,
+ AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
+ AudioQualityVerifierActivity.AUDIO_FORMAT, bufferSize);
+ if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+ Log.e(TAG, "Couldn't open audio for recording");
+ return;
+ }
+ mRecord.startRecording();
+ if (mRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+ Log.e(TAG, "Couldn't record");
+ return;
+ }
+
+ captureLoop();
+
+ mRecord.stop();
+ mRecord.release();
+ mRecord = null;
+ }
+
+ private void captureLoop() {
+ int totalBytes = mSamples * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+ Log.i(TAG, "Recording " + totalBytes + " bytes");
+ int position = 0;
+ int bytes;
+ while (position < totalBytes && mProceed) {
+ bytes = mRecord.read(mBuffer, position, totalBytes - position);
+ if (bytes < 0) {
+ if (bytes == AudioRecord.ERROR_INVALID_OPERATION) {
+ Log.e(TAG, "Recording object not initalized");
+ } else if (bytes == AudioRecord.ERROR_BAD_VALUE) {
+ Log.e(TAG, "Invalid recording parameters");
+ } else {
+ Log.e(TAG, "Error during recording");
+ }
+ return;
+ }
+ position += bytes;
+ }
+ }
+ }
+}