blob: 9ad33d79bec89c0752b518600b84a84346b943ce [file] [log] [blame]
#!/usr/bin/python2.7
"""
Copyright (C) 2019 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.
"""
# Run OboeTester with progressive timing changes
# to measure the DSP timing profile.
# Print a CSV table of offsets and glitch counts.
#
# Run Automated Test using an Intent
# https://github.com/google/oboe/blob/master/apps/OboeTester/docs/AutomatedTesting.md
import array
import collections
import os
import os.path
import sys
import subprocess
import time
from datetime import datetime
kPropertyOutputOffset = "aaudio.out_mmap_offset_usec"
kMinPeakAmplitude = 0.04
kOffsetMin = 0000
kOffsetMax = 4000
kOffsetIncrement = 100
kOutputFileBase = "/sdcard/dsp_timing_"
gOutputFile = kOutputFileBase + "now.txt"
def launchLatencyTest():
command = ["adb", "shell", "am", \
"start", "-n", "com.google.sample.oboe.manualtest/.MainActivity", \
"--es", "test", "latency", \
"--es", "file", gOutputFile, \
"--ei", "buffer_bursts", "1"]
return subprocess.check_output(command)
def launchGlitchTest():
command = ["adb", "shell", "am", \
"start", "-n", "com.google.sample.oboe.manualtest/.MainActivity", \
"--es", "test", "glitch", \
"--es", "file", gOutputFile, \
"--es", "in_perf", "lowlat", \
"--es", "out_perf", "lowlat", \
"--es", "in_sharing", "exclusive", \
"--es", "out_sharing", "exclusive", \
"--ei", "buffer_bursts", "1"]
return subprocess.check_output(command)
def setAndroidProperty(property, value):
return subprocess.check_output(["adb", "shell", "setprop", property, value])
def getAndroidProperty(property):
return subprocess.check_output(["adb", "shell", "getprop", property])
def setOutputOffset(offset):
setAndroidProperty(kPropertyOutputOffset, str(offset))
def getOutputOffset():
offsetText = getAndroidProperty(kPropertyOutputOffset).strip()
if len(offsetText) == 0:
return 0;
return int(offsetText)
def loadNameValuePairsFromFile(filename):
myvars = {}
with open(filename) as myfile:
for line in myfile:
name, var = line.partition("=")[::2]
myvars[name.strip()] = var
return myvars
def loadNameValuePairsFromString(text):
myvars = {}
listOutput = text.splitlines()
for line in listOutput:
name, var = line.partition("=")[::2]
myvars[name.strip()] = var
return myvars
def waitForTestResult():
testOutput = ""
for i in range(10):
if subprocess.call(["adb", "shell", "ls", gOutputFile, "2>/dev/null"]) == 0:
testOutput = subprocess.check_output(["adb", "shell", "cat", gOutputFile])
break
else:
print str(i) + ": waiting until test finishes..."
time.sleep(2)
# print testOutput
subprocess.call(["adb", "shell", "rm", gOutputFile])
return loadNameValuePairsFromString(testOutput)
# volume too low?
# return true if bad
def checkPeakAmplitude(pairs):
if not pairs.has_key('peak.amplitude'):
print "ERROR no peak.amplitude"
return True
peakAmplitude = float(pairs['peak.amplitude'])
if peakAmplitude < kMinPeakAmplitude:
print "ERROR peakAmplitude = " + str(peakAmplitude) \
+ " < " + str(kMinPeakAmplitude) \
+ ", turn up volume"
return True
return False
def startOneTest(offset):
print "=========================="
setOutputOffset(offset)
print "try offset = " + getAndroidProperty(kPropertyOutputOffset)
subprocess.call(["adb", "shell", "rm", gOutputFile, "2>/dev/null"])
def runOneGlitchTest(offset):
startOneTest(offset)
print launchGlitchTest()
pairs = waitForTestResult()
if checkPeakAmplitude(pairs):
return -1
if not pairs.has_key('glitch.count'):
print "ERROR no glitch.count"
return -1
return int(pairs['glitch.count'])
def runOneLatencyTest(offset):
startOneTest(offset)
print launchLatencyTest()
pairs = waitForTestResult()
if not pairs.has_key('latency.msec'):
print "ERROR no latency.msec"
return -1
return float(pairs['latency.msec'])
def runGlitchSeries():
offsets = array.array('i')
glitches = array.array('i')
for offset in range(kOffsetMin, kOffsetMax, kOffsetIncrement):
offsets.append(offset)
result = runOneGlitchTest(offset)
glitches.append(result)
if result < 0:
break
print "offset = " + str(offset) + ", glitches = " + str(result)
print "offsetUs, glitches"
for i in range(len(offsets)):
print " " + str(offsets[i]) + ", " + str(glitches[i])
# return true if bad
def checkLatencyValid():
startOneTest(0)
print launchLatencyTest()
pairs = waitForTestResult()
print "burst = " + pairs['out.burst.frames']
capacity = int(pairs['out.buffer.capacity.frames'])
if capacity < 0:
print "ERROR capacity = " + str(capacity)
return True
sampleRate = int(pairs['out.rate'])
capacityMillis = capacity * 1000.0 / sampleRate
print "capacityMillis = " + str(capacityMillis)
if pairs['in.mmap'].strip() != "yes":
print "ERROR Not using input MMAP"
return True
if pairs['out.mmap'].strip() != "yes":
print "ERROR Not using output MMAP"
return True
# Check whether we can change latency a moving the DSP pointer
# past the CPU pointer and wrapping the buffer.
latencyMin = runOneLatencyTest(kOffsetMin)
latencyMax = runOneLatencyTest(kOffsetMax + 1000)
print "latency = " + str(latencyMin) + " => " + str(latencyMax)
if latencyMax < (latencyMin + (capacityMillis / 2)):
print "ERROR Latency not affected by changing offset!"
return True
return False
def isRunningAsRoot():
userName = subprocess.check_output(["adb", "shell", "whoami"]).strip()
if userName != "root":
print "WARNING: changing to 'adb root'"
subprocess.call(["adb", "root"])
userName = subprocess.check_output(["adb", "shell", "whoami"]).strip()
if userName != "root":
print "ERROR: cannot set 'adb root'"
return False
return True
def isMMapSupported():
mmapPolicy = int(getAndroidProperty("aaudio.mmap_policy").strip())
if mmapPolicy < 2:
print "ERROR: AAudio MMAP not enabled, aaudio.mmap_policy = " + str(mmapPolicy)
return False
if checkLatencyValid():
return False;
return True
def isTimingSeriesSupported():
if not isRunningAsRoot():
return False
if not isMMapSupported():
return False
return True
def main():
global gOutputFile
print "gOutputFile = " + gOutputFile
now = datetime.now() # current date and time
gOutputFile = kOutputFileBase \
+ now.strftime("%Y%m%d_%H%M%S") \
+ ".txt"
print "gOutputFile = " + gOutputFile
initialOffset = getOutputOffset()
print "initial offset = " + str(initialOffset)
print "Android version = " + \
getAndroidProperty("ro.build.id").strip()
print " release " + \
getAndroidProperty("ro.build.version.release").strip()
if (isTimingSeriesSupported()):
runGlitchSeries()
setOutputOffset(initialOffset)
if __name__ == '__main__':
main()