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