The PD controller is replaced by a controller with a similar proportional term, but expressed in expected time to converged to the desired buffering level. The hysteresis decisions based on proximity to the desired buffering level are replaced with decisions based on the speed at which the current resampling ratio will converge. This means that overshoot and consequent oscillations are avoided. The derivative term is dropped because it is not necessary, and can trigger oscillations when the corrected rate is close to steady state and rounds to a different integer values: even small changes in the derivative term can be enough to cause the corrected rate to oscillate back to the previous integer value. The derivative term did provide a more rapid initial response to changes in the desired buffer level, but significant changes in resampling rates for such rapid responses can degrade echo canceller performance. Because some hysteresis is now applied even when average buffering is far from desired, if the existing correction is making sufficient progress, TestDriftController.LargeStepResponse takes a little longer to converge, but converges to a stable rate and buffer level. For similar reasons TestDriftController.VerySmallBufferedFrames takes longer to reach its end point. Hysteresis means the corrected rate is not always changing and the change in rate is capped when it does change. The buffer level is higher before the missing input packet in TestAudioDriftCorrection.DriftStepResponseUnderrunHighLatencyInput so the underrun occurs a little later. TestAudioDriftCorrection.DynamicInputBufferSizeChanges is adjusted for less hysteresis influence in response to the first missing packet and more influence in response to reductions in desired buffering. Differential Revision: https://phabricator.services.mozilla.com/D215488
159 lines
5.6 KiB
Python
Executable File
159 lines
5.6 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# This scripts plots graphs produced by our drift correction code.
|
|
#
|
|
# Install dependencies with:
|
|
# > pip install bokeh pandas
|
|
#
|
|
# Generate the csv data file with the DriftControllerGraphs log module:
|
|
# > MOZ_LOG=raw,sync,DriftControllerGraphs:5 \
|
|
# > MOZ_LOG_FILE=/tmp/driftcontrol.csv \
|
|
# > ./mach gtest '*AudioDrift*StepResponse'
|
|
#
|
|
# Generate the graphs with this script:
|
|
# > ./dom/media/driftcontrol/plot.py /tmp/driftcontrol.csv.moz_log
|
|
#
|
|
# The script should produce a file plot.html in the working directory and
|
|
# open it in the default browser.
|
|
|
|
import argparse
|
|
from collections import OrderedDict
|
|
|
|
import pandas
|
|
from bokeh.io import output_file, show
|
|
from bokeh.layouts import gridplot
|
|
from bokeh.models import TabPanel, Tabs
|
|
from bokeh.plotting import figure
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
prog="plot.py for DriftControllerGraphs",
|
|
description="""Takes a csv file of DriftControllerGraphs data
|
|
(from a single DriftController instance) and plots
|
|
them into plot.html in the current working directory.
|
|
|
|
The easiest way to produce the data is with MOZ_LOG:
|
|
MOZ_LOG=raw,sync,DriftControllerGraphs:5 \
|
|
MOZ_LOG_FILE=/tmp/driftcontrol.csv \
|
|
./mach gtest '*AudioDrift*StepResponse'""",
|
|
)
|
|
parser.add_argument("csv_file", type=str)
|
|
args = parser.parse_args()
|
|
|
|
all_df = pandas.read_csv(args.csv_file)
|
|
|
|
# Filter on distinct ids to support multiple plotting sources
|
|
tabs = []
|
|
for id in list(OrderedDict.fromkeys(all_df["id"])):
|
|
df = all_df[all_df["id"] == id]
|
|
|
|
t = df["t"]
|
|
buffering = df["buffering"]
|
|
avgbuffered = df["avgbuffered"]
|
|
desired = df["desired"]
|
|
buffersize = df["buffersize"]
|
|
inlatency = df["inlatency"]
|
|
outlatency = df["outlatency"]
|
|
inframesavg = df["inframesavg"]
|
|
outframesavg = df["outframesavg"]
|
|
inrate = df["inrate"]
|
|
outrate = df["outrate"]
|
|
steadystaterate = df["steadystaterate"]
|
|
nearthreshold = df["nearthreshold"]
|
|
corrected = df["corrected"]
|
|
hysteresiscorrected = df["hysteresiscorrected"]
|
|
configured = df["configured"]
|
|
|
|
output_file("plot.html")
|
|
|
|
fig1 = figure()
|
|
# Variables with more variation are plotted after smoother variables
|
|
# because latter variables are drawn on top and so visibility of
|
|
# individual values in the variables with more variation is improved
|
|
# (when both variables are shown).
|
|
fig1.line(
|
|
t, inframesavg, color="violet", legend_label="Average input packet size"
|
|
)
|
|
fig1.line(
|
|
t, outframesavg, color="purple", legend_label="Average output packet size"
|
|
)
|
|
fig1.line(t, inlatency, color="hotpink", legend_label="In latency")
|
|
fig1.line(t, outlatency, color="firebrick", legend_label="Out latency")
|
|
fig1.line(t, desired, color="goldenrod", legend_label="Desired buffering")
|
|
fig1.line(
|
|
t, avgbuffered, color="orangered", legend_label="Average buffered estimate"
|
|
)
|
|
fig1.line(t, buffering, color="dodgerblue", legend_label="Actual buffering")
|
|
fig1.line(t, buffersize, color="seagreen", legend_label="Buffer size")
|
|
fig1.varea(
|
|
t,
|
|
[d - h for (d, h) in zip(desired, nearthreshold)],
|
|
[d + h for (d, h) in zip(desired, nearthreshold)],
|
|
alpha=0.2,
|
|
color="goldenrod",
|
|
legend_label='"Near" band (won\'t reduce desired buffering outside)',
|
|
)
|
|
|
|
slowConvergenceSecs = 30
|
|
adjustmentInterval = 1
|
|
slowHysteresis = 1
|
|
avgError = avgbuffered - desired
|
|
absAvgError = [abs(e) for e in avgError]
|
|
slow_offset = [e / slowConvergenceSecs - slowHysteresis for e in absAvgError]
|
|
fast_offset = [e / adjustmentInterval for e in absAvgError]
|
|
low_offset, high_offset = zip(
|
|
*[
|
|
(s, f) if e >= 0 else (-f, -s)
|
|
for (e, s, f) in zip(avgError, slow_offset, fast_offset)
|
|
]
|
|
)
|
|
|
|
fig2 = figure(x_range=fig1.x_range)
|
|
fig2.varea(
|
|
t,
|
|
steadystaterate + low_offset,
|
|
steadystaterate + high_offset,
|
|
alpha=0.2,
|
|
color="goldenrod",
|
|
legend_label="Deadband (won't change in rate within)",
|
|
)
|
|
fig2.line(t, inrate, color="hotpink", legend_label="Nominal in sample rate")
|
|
fig2.line(t, outrate, color="firebrick", legend_label="Nominal out sample rate")
|
|
fig2.line(
|
|
t,
|
|
steadystaterate,
|
|
color="orangered",
|
|
legend_label="Estimated in rate with drift",
|
|
)
|
|
fig2.line(
|
|
t, corrected, color="dodgerblue", legend_label="Corrected in sample rate"
|
|
)
|
|
fig2.line(
|
|
t,
|
|
hysteresiscorrected,
|
|
color="seagreen",
|
|
legend_label="Hysteresis-corrected in sample rate",
|
|
)
|
|
fig2.line(
|
|
t, configured, color="goldenrod", legend_label="Configured in sample rate"
|
|
)
|
|
|
|
fig1.legend.location = "top_left"
|
|
fig2.legend.location = "top_right"
|
|
for fig in (fig1, fig2):
|
|
fig.legend.background_fill_alpha = 0.6
|
|
fig.legend.click_policy = "hide"
|
|
|
|
tabs.append(TabPanel(child=gridplot([[fig1, fig2]]), title=str(id)))
|
|
|
|
show(Tabs(tabs=tabs))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|