xref: /aosp_15_r20/external/oboe/apps/OboeTester/scripts/dsp_timing.py (revision 05767d913155b055644481607e6fa1e35e2fe72c)
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