blob: 1e4859bae4f895692b183846221cdd1516e8c275 [file] [log] [blame]
/*
* Copyright (C) 2015 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.media.tests;
import com.android.ddmlib.CollectingOutputReceiver;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.SnapshotInputStreamSource;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.RunUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* A harness that launches Audio Loopback tool and reports result.
*/
public class AudioLoopbackTest implements IDeviceTest, IRemoteTest {
private static final long TIMEOUT_MS = 5 * 60 * 1000; // 5 min
private static final long DEVICE_SYNC_MS = 5 * 60 * 1000; // 5 min
private static final long POLLING_INTERVAL_MS = 5 * 1000;
private static final int MAX_ATTEMPTS = 3;
private static final String TESTTYPE_LATENCY = "222";
private static final String TESTTYPE_BUFFER = "223";
private static final Map<String, String> METRICS_KEY_MAP = createMetricsKeyMap();
private ITestDevice mDevice;
@Option(name = "run-key", description = "Run key for the test")
private String mRunKey = "AudioLoopback";
@Option(name = "sampling-freq", description = "Sampling Frequency for Loopback app")
private String mSamplingFreq = "48000";
@Option(name = "mic-source", description = "Mic Source for Loopback app")
private String mMicSource = "3";
@Option(name = "audio-thread", description = "Audio Thread for Loopback app")
private String mAudioThread = "1";
@Option(name = "audio-level", description = "Audio Level for Loopback app. " +
"A device specific param which makes waveform in loopback test hit 60% to 80% range")
private String mAudioLevel = "12";
@Option(name = "test-type", description = "Test type to be executed")
private String mTestType = TESTTYPE_LATENCY;
@Option(name = "buffer-test-duration", description = "Buffer test duration in seconds")
private String mBufferTestDuration = "10";
@Option(name = "key-prefix", description = "Key Prefix for reporting")
private String mKeyPrefix = "48000_Mic3_";
private static final String DEVICE_TEMP_DIR_PATH = "/sdcard/";
private static final String OUTPUT_FILENAME = "output_" + System.currentTimeMillis();
private static final String OUTPUT_RESULT_TXT_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + ".txt";
private static final String OUTPUT_PNG_PATH = DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + ".png";
private static final String OUTPUT_WAV_PATH = DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + ".wav";
private static final String OUTPUT_PLAYER_BUFFER_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + "_playerBufferPeriod.txt";
private static final String OUTPUT_PLAYER_BUFFER_PNG_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + "_playerBufferPeriod.png";
private static final String OUTPUT_RECORDER_BUFFER_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + "_recorderBufferPeriod.txt";
private static final String OUTPUT_RECORDER_BUFFER_PNG_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + "_recorderBufferPeriod.png";
private static final String OUTPUT_GLITCH_PATH =
DEVICE_TEMP_DIR_PATH + OUTPUT_FILENAME + "_glitchMillis.txt";
private static final String AM_CMD =
"am start -n org.drrickorang.loopback/.LoopbackActivity"
+ " --ei SF %s --es FileName %s --ei MicSource %s --ei AudioThread %s"
+ " --ei AudioLevel %s --ei TestType %s --ei BufferTestDuration %s";
private static Map<String, String> createMetricsKeyMap() {
Map<String, String> result = new HashMap<String, String>();
result.put("LatencyMs", "latency_ms");
result.put("LatencyConfidence", "latency_confidence");
result.put("Recorder Benchmark", "recorder_benchmark");
result.put("Recorder Number of Outliers", "recorder_outliers");
result.put("Player Benchmark", "player_benchmark");
result.put("Player Number of Outliers", "player_outliers");
result.put("Total Number of Glitches", "number_of_glitches");
result.put("kth% Late Recorder Buffer Callbacks", "late_recorder_callbacks");
result.put("kth% Late Player Buffer Callbacks", "late_player_callbacks");
result.put("Glitches Per Hour", "glitches_per_hour");
return Collections.unmodifiableMap(result);
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
TestIdentifier testId = new TestIdentifier(getClass().getCanonicalName(), mRunKey);
ITestDevice device = getDevice();
// Wait device to settle
RunUtil.getDefault().sleep(DEVICE_SYNC_MS);
listener.testRunStarted(mRunKey, 0);
listener.testStarted(testId);
long testStartTime = System.currentTimeMillis();
Map<String, String> metrics = new HashMap<String, String>();
// start measurement and wait for result file
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
device.unlockDevice();
String loopbackCmd = String.format(
AM_CMD, mSamplingFreq, OUTPUT_FILENAME, mMicSource, mAudioThread,
mAudioLevel, mTestType, mBufferTestDuration);
CLog.i("Running cmd: " + loopbackCmd);
device.executeShellCommand(loopbackCmd, receiver,
TIMEOUT_MS, TimeUnit.MILLISECONDS, MAX_ATTEMPTS);
long timeout = Long.parseLong(mBufferTestDuration) * 1000 + TIMEOUT_MS;
long loopbackStartTime = System.currentTimeMillis();
boolean isTimedOut = false;
boolean isResultGenerated = false;
File loopbackReport = null;
while (!isResultGenerated && !isTimedOut) {
RunUtil.getDefault().sleep(POLLING_INTERVAL_MS);
isTimedOut = (System.currentTimeMillis() - loopbackStartTime >= timeout);
boolean isResultFileFound = device.doesFileExist(OUTPUT_RESULT_TXT_PATH);
if (isResultFileFound) {
loopbackReport = device.pullFile(OUTPUT_RESULT_TXT_PATH);
if (loopbackReport.length() > 0) {
isResultGenerated = true;
}
}
}
if (isTimedOut) {
reportFailure(listener, testId, "Loopback result not found, timed out.");
return;
}
// TODO: fail the test or rerun if the confidence level is too low
// parse result
CLog.i("== Loopback result ==");
try {
Map<String, String> loopbackResult = parseResult(loopbackReport);
if (loopbackResult == null || loopbackResult.size() == 0) {
reportFailure(listener, testId, "Failed to parse Loopback result.");
return;
}
metrics = loopbackResult;
listener.testLog(mKeyPrefix + "result", LogDataType.TEXT,
new SnapshotInputStreamSource(new FileInputStream(loopbackReport)));
File loopbackGraphFile = device.pullFile(OUTPUT_PNG_PATH);
listener.testLog(mKeyPrefix + "graph", LogDataType.PNG,
new SnapshotInputStreamSource(new FileInputStream(loopbackGraphFile)));
File loopbackWaveFile = device.pullFile(OUTPUT_WAV_PATH);
listener.testLog(mKeyPrefix + "wave", LogDataType.UNKNOWN,
new SnapshotInputStreamSource(new FileInputStream(loopbackWaveFile)));
if (mTestType.equals(TESTTYPE_BUFFER)) {
File loopbackPlayerBuffer = device.pullFile(OUTPUT_PLAYER_BUFFER_PATH);
listener.testLog(mKeyPrefix + "player_buffer", LogDataType.TEXT,
new SnapshotInputStreamSource(
new FileInputStream(loopbackPlayerBuffer)));
File loopbackPlayerBufferPng = device.pullFile(OUTPUT_PLAYER_BUFFER_PNG_PATH);
listener.testLog(mKeyPrefix + "player_buffer_histogram", LogDataType.PNG,
new SnapshotInputStreamSource(new FileInputStream(loopbackPlayerBufferPng)));
File loopbackRecorderBuffer = device.pullFile(OUTPUT_RECORDER_BUFFER_PATH);
listener.testLog(mKeyPrefix + "recorder_buffer", LogDataType.TEXT,
new SnapshotInputStreamSource(new FileInputStream(
loopbackRecorderBuffer)));
File loopbackRecorderBufferPng = device.pullFile(OUTPUT_RECORDER_BUFFER_PNG_PATH);
listener.testLog(mKeyPrefix + "recorder_buffer_histogram", LogDataType.PNG,
new SnapshotInputStreamSource(new FileInputStream(
loopbackRecorderBufferPng)));
File loopbackGlitch = device.pullFile(OUTPUT_GLITCH_PATH);
listener.testLog(mKeyPrefix + "glitches_millis", LogDataType.TEXT,
new SnapshotInputStreamSource(new FileInputStream(
loopbackGlitch)));
}
} catch (IOException ioe) {
CLog.e(ioe.getMessage());
reportFailure(listener, testId, "I/O error while parsing Loopback result.");
return;
}
long durationMs = System.currentTimeMillis() - testStartTime;
listener.testEnded(testId, metrics);
listener.testRunEnded(durationMs, metrics);
}
/**
* Report failure with error message specified and fail the test.
*
* @param listener
* @param testId
* @param errMsg
*/
private void reportFailure(ITestInvocationListener listener, TestIdentifier testId,
String errMsg) {
CLog.e(errMsg);
listener.testFailed(testId, errMsg);
listener.testEnded(testId, new HashMap<String, String>());
listener.testRunFailed(errMsg);
}
/**
* Parse result.
* Format: key = value
*
* @param result Loopback app result file
* @return a {@link HashMap} that contains metrics keys and results
* @throws IOException
*/
private Map<String, String> parseResult(File result) throws IOException {
Map<String, String> resultMap = new HashMap<String, String>();
BufferedReader br = new BufferedReader(new FileReader(result));
try {
String line = br.readLine();
while (line != null) {
line = line.trim().replaceAll(" +", " ");
String[] tokens = line.split("=");
if (tokens.length >= 2) {
String metricName = tokens[0].trim();
String metricValue = tokens[1].trim();
if (METRICS_KEY_MAP.containsKey(metricName)) {
CLog.i(String.format("%s: %s", metricName, metricValue));
resultMap.put(mKeyPrefix + METRICS_KEY_MAP.get(metricName), metricValue);
}
}
line = br.readLine();
}
} finally {
br.close();
}
return resultMap;
}
}