Files
tubestation/dom/media/driftcontrol/plot.py
Karl Tomlinson fc4d776c8d Bug 1890467 Use knowledge of resampling rate correction effects to determine acceptable correction rates for hysteresis r=pehrsons
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
2024-07-11 20:29:26 +00:00

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()