1#!/usr/bin/python2.7 2""" 3Copyright (C) 2019 The Android Open Source Project 4 5Licensed under the Apache License, Version 2.0 (the "License"); 6you may not use this file except in compliance with the License. 7You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11Unless required by applicable law or agreed to in writing, software 12distributed under the License is distributed on an "AS IS" BASIS, 13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14See the License for the specific language governing permissions and 15limitations under the License. 16""" 17 18# Run OboeTester with progressive timing changes 19# to measure the DSP timing profile. 20# Print a CSV table of offsets and glitch counts. 21# 22# Run Automated Test using an Intent 23# https://github.com/google/oboe/blob/master/apps/OboeTester/docs/AutomatedTesting.md 24 25import array 26import collections 27import os 28import os.path 29import sys 30import subprocess 31import time 32 33from datetime import datetime 34 35kPropertyOutputOffset = "aaudio.out_mmap_offset_usec" 36kMinPeakAmplitude = 0.04 37kOffsetMin = 0000 38kOffsetMax = 4000 39kOffsetIncrement = 100 40kOutputFileBase = "/sdcard/dsp_timing_" 41gOutputFile = kOutputFileBase + "now.txt" 42 43def launchLatencyTest(): 44 command = ["adb", "shell", "am", \ 45 "start", "-n", "com.mobileer.oboetester/.MainActivity", \ 46 "--es", "test", "latency", \ 47 "--es", "file", gOutputFile, \ 48 "--ei", "buffer_bursts", "1"] 49 return subprocess.check_output(command) 50 51def launchGlitchTest(): 52 command = ["adb", "shell", "am", \ 53 "start", "-n", "com.mobileer.oboetester/.MainActivity", \ 54 "--es", "test", "glitch", \ 55 "--es", "file", gOutputFile, \ 56 "--es", "in_perf", "lowlat", \ 57 "--es", "out_perf", "lowlat", \ 58 "--es", "in_sharing", "exclusive", \ 59 "--es", "out_sharing", "exclusive", \ 60 "--ei", "buffer_bursts", "1"] 61 return subprocess.check_output(command) 62 63def setAndroidProperty(property, value): 64 return subprocess.check_output(["adb", "shell", "setprop", property, value]) 65 66def getAndroidProperty(property): 67 return subprocess.check_output(["adb", "shell", "getprop", property]) 68 69def setOutputOffset(offset): 70 setAndroidProperty(kPropertyOutputOffset, str(offset)) 71 72def getOutputOffset(): 73 offsetText = getAndroidProperty(kPropertyOutputOffset).strip() 74 if len(offsetText) == 0: 75 return 0; 76 return int(offsetText) 77 78def loadNameValuePairsFromFile(filename): 79 myvars = {} 80 with open(filename) as myfile: 81 for line in myfile: 82 name, var = line.partition("=")[::2] 83 myvars[name.strip()] = var 84 return myvars 85 86def loadNameValuePairsFromString(text): 87 myvars = {} 88 listOutput = text.splitlines() 89 for line in listOutput: 90 name, var = line.partition("=")[::2] 91 myvars[name.strip()] = var 92 return myvars 93 94def waitForTestResult(): 95 testOutput = "" 96 for i in range(10): 97 if subprocess.call(["adb", "shell", "ls", gOutputFile, "2>/dev/null"]) == 0: 98 testOutput = subprocess.check_output(["adb", "shell", "cat", gOutputFile]) 99 break 100 else: 101 print str(i) + ": waiting until test finishes..." 102 time.sleep(2) 103 # print testOutput 104 subprocess.call(["adb", "shell", "rm", gOutputFile]) 105 return loadNameValuePairsFromString(testOutput) 106 107# volume too low? 108# return true if bad 109def checkPeakAmplitude(pairs): 110 if not pairs.has_key('peak.amplitude'): 111 print "ERROR no peak.amplitude" 112 return True 113 peakAmplitude = float(pairs['peak.amplitude']) 114 if peakAmplitude < kMinPeakAmplitude: 115 print "ERROR peakAmplitude = " + str(peakAmplitude) \ 116 + " < " + str(kMinPeakAmplitude) \ 117 + ", turn up volume" 118 return True 119 return False 120 121def startOneTest(offset): 122 print "==========================" 123 setOutputOffset(offset) 124 print "try offset = " + getAndroidProperty(kPropertyOutputOffset) 125 subprocess.call(["adb", "shell", "rm", gOutputFile, "2>/dev/null"]) 126 127def runOneGlitchTest(offset): 128 startOneTest(offset) 129 print launchGlitchTest() 130 pairs = waitForTestResult() 131 if checkPeakAmplitude(pairs): 132 return -1 133 if not pairs.has_key('glitch.count'): 134 print "ERROR no glitch.count" 135 return -1 136 return int(pairs['glitch.count']) 137 138def runOneLatencyTest(offset): 139 startOneTest(offset) 140 print launchLatencyTest() 141 pairs = waitForTestResult() 142 if not pairs.has_key('latency.msec'): 143 print "ERROR no latency.msec" 144 return -1 145 return float(pairs['latency.msec']) 146 147def runGlitchSeries(): 148 offsets = array.array('i') 149 glitches = array.array('i') 150 for offset in range(kOffsetMin, kOffsetMax, kOffsetIncrement): 151 offsets.append(offset) 152 result = runOneGlitchTest(offset) 153 glitches.append(result) 154 if result < 0: 155 break 156 print "offset = " + str(offset) + ", glitches = " + str(result) 157 print "offsetUs, glitches" 158 for i in range(len(offsets)): 159 print " " + str(offsets[i]) + ", " + str(glitches[i]) 160 161# return true if bad 162def checkLatencyValid(): 163 startOneTest(0) 164 print launchLatencyTest() 165 pairs = waitForTestResult() 166 print "burst = " + pairs['out.burst.frames'] 167 capacity = int(pairs['out.buffer.capacity.frames']) 168 if capacity < 0: 169 print "ERROR capacity = " + str(capacity) 170 return True 171 sampleRate = int(pairs['out.rate']) 172 capacityMillis = capacity * 1000.0 / sampleRate 173 print "capacityMillis = " + str(capacityMillis) 174 if pairs['in.mmap'].strip() != "yes": 175 print "ERROR Not using input MMAP" 176 return True 177 if pairs['out.mmap'].strip() != "yes": 178 print "ERROR Not using output MMAP" 179 return True 180 # Check whether we can change latency a moving the DSP pointer 181 # past the CPU pointer and wrapping the buffer. 182 latencyMin = runOneLatencyTest(kOffsetMin) 183 latencyMax = runOneLatencyTest(kOffsetMax + 1000) 184 print "latency = " + str(latencyMin) + " => " + str(latencyMax) 185 if latencyMax < (latencyMin + (capacityMillis / 2)): 186 print "ERROR Latency not affected by changing offset!" 187 return True 188 return False 189 190def isRunningAsRoot(): 191 userName = subprocess.check_output(["adb", "shell", "whoami"]).strip() 192 if userName != "root": 193 print "WARNING: changing to 'adb root'" 194 subprocess.call(["adb", "root"]) 195 userName = subprocess.check_output(["adb", "shell", "whoami"]).strip() 196 if userName != "root": 197 print "ERROR: cannot set 'adb root'" 198 return False 199 return True 200 201def isMMapSupported(): 202 mmapPolicy = int(getAndroidProperty("aaudio.mmap_policy").strip()) 203 if mmapPolicy < 2: 204 print "ERROR: AAudio MMAP not enabled, aaudio.mmap_policy = " + str(mmapPolicy) 205 return False 206 if checkLatencyValid(): 207 return False; 208 return True 209 210def isTimingSeriesSupported(): 211 if not isRunningAsRoot(): 212 return False 213 if not isMMapSupported(): 214 return False 215 return True 216 217def main(): 218 global gOutputFile 219 print "gOutputFile = " + gOutputFile 220 now = datetime.now() # current date and time 221 gOutputFile = kOutputFileBase \ 222 + now.strftime("%Y%m%d_%H%M%S") \ 223 + ".txt" 224 print "gOutputFile = " + gOutputFile 225 226 initialOffset = getOutputOffset() 227 print "initial offset = " + str(initialOffset) 228 229 print "Android version = " + \ 230 getAndroidProperty("ro.build.id").strip() 231 print " release " + \ 232 getAndroidProperty("ro.build.version.release").strip() 233 if (isTimingSeriesSupported()): 234 runGlitchSeries() 235 setOutputOffset(initialOffset) 236 237if __name__ == '__main__': 238 main() 239 240