Skip to content

Commit 66fa3b7

Browse files
committed
Add AudioPlayoutStats interface to spec
1 parent 7f58ac7 commit 66fa3b7

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

index.bs

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,8 @@ The interfaces defined are:
663663
{{AudioNode}} which applies a non-linear waveshaping
664664
effect for distortion and other more subtle warming effects.
665665

666+
* An {{AudioPlayoutStats}} interface, which provides statistics about the audio played from the {{AudioContext}}.
667+
666668
There are also several features that have been deprecated from the
667669
Web Audio API but not yet removed, pending implementation experience
668670
of their replacements:
@@ -1488,6 +1490,7 @@ interface AudioContext : BaseAudioContext {
14881490
[SecureContext] readonly attribute (DOMString or AudioSinkInfo) sinkId;
14891491
attribute EventHandler onsinkchange;
14901492
attribute EventHandler onerror;
1493+
[SameObject] readonly attribute AudioPlayoutStats playoutStats;
14911494
AudioTimestamp getOutputTimestamp ();
14921495
Promise<undefined> resume ();
14931496
Promise<undefined> suspend ();
@@ -1533,6 +1536,10 @@ and to allow it only when the {{AudioContext}}'s [=relevant global object=] has
15331536
::
15341537
An ordered list to store pending {{Promise}}s created by
15351538
{{AudioContext/resume()}}. It is initially empty.
1539+
1540+
: <dfn>[[playout stats]]</dfn>
1541+
::
1542+
A slot where an instance of {{AudioPlayoutStats}} can be stored. It is initially null.
15361543
</dl>
15371544

15381545
<h4 id="AudioContext-constructors">
@@ -1769,6 +1776,19 @@ Attributes</h4>
17691776
the context is {{AudioContextState/running}}.
17701777
* When the operating system reports an audio device malfunction.
17711778

1779+
: <dfn>playoutStats</dfn>
1780+
::
1781+
An instance of {{AudioPlayoutStats}} for this {{AudioContext}}.
1782+
1783+
<div algorithm="access playoutStats">
1784+
<span class="synchronous">When accessing this attribute, run the following steps:</span>
1785+
1786+
1. If the {{[[playout stats]]}} slot is null, construct a new {{AudioPlayoutStats}} object with [=this=] as the argument, and store it in {{[[playout stats]]}}.
1787+
1788+
1. Return the value of the {{[[playout stats]]}} internal slot.
1789+
</div>
1790+
1791+
17721792
</dl>
17731793

17741794
<h4 id="AudioContext-methods">
@@ -11536,6 +11556,246 @@ context.audioWorklet.addModule('vumeter-processor.js').then(() => {
1153611556
});
1153711557
</xmp>
1153811558

11559+
<h3 interface lt="AudioPlayoutStats" id="AudioPlayoutStats">
11560+
The {{AudioPlayoutStats}} Interface</h3>
11561+
11562+
Provides audio underrun and latency statistics for audio played through the {{AudioContext}}.
11563+
11564+
Audio underruns (also commonly called glitches) are gaps in the audio
11565+
playout which occur when the audio pipeline cannot deliver audio on time.
11566+
Underruns (often manifesting as audible "clicks" in the playout) are bad
11567+
for the user experience, so if any of these occur it
11568+
can be useful for the application to be able to detect this and possibly
11569+
take some action to improve the playout.
11570+
11571+
{{AudioPlayoutStats}} is a dedicated object for audio stats reporting;
11572+
it reports audio underrun and playout latency statistics for the {{AudioContext's}} playout path via
11573+
{{AudioDestinationNode}} and the associated output device. This allows
11574+
applications to measure underruns occurring due to underperforming
11575+
AudioWorklets as well as underruns and latency originating in the playout
11576+
path between the {{AudioDestinationNode}} and the output device.
11577+
11578+
Underruns are defined in terms of [=underrun frames=] and [=underrun events=]:
11579+
- An <dfn>underrun frame</dfn> is an audio frame played by the output device that was not provided by the playout path.
11580+
This happens when the playout path fails to provide audio frames
11581+
to the output device on time, in which case underrun frames will be played (underrun frames are typically silence).
11582+
This typically only happens if the pipeline is underperforming.
11583+
This includes underrun situations that happen for reasons unrelated to WebAudio/{{AudioWorklet}}s.</li>
11584+
- When an [=underrun frame=] is played after a non-underrun frame, we consider this an <dfn>underrun event</dfn>.
11585+
That is, multiple consecutive [=underrun frames=] will count as a single [=underrun event=].</li>
11586+
11587+
<pre class="idl">
11588+
[Exposed=Window, SecureContext]
11589+
interface AudioPlayoutStats {
11590+
constructor (AudioContext context);
11591+
readonly attribute double underrunDuration;
11592+
readonly attribute unsigned long underrunEvents;
11593+
readonly attribute double totalDuration;
11594+
readonly attribute double averageLatency;
11595+
readonly attribute double minimumLatency;
11596+
readonly attribute double maximumLatency;
11597+
undefined resetLatency();
11598+
[Default] object toJSON();
11599+
};
11600+
</pre>
11601+
11602+
{{AudioPlayoutStats}} has the following internal slots:
11603+
11604+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11605+
: <dfn>[[audio context]]</dfn>
11606+
::
11607+
The {{AudioContext}} that this instance of {{AudioPlayoutStats}} is associated with.
11608+
11609+
: <dfn>[[underrun duration]]</dfn>
11610+
::
11611+
The total duration in seconds of [=underrun frames=] that {{[[audio context]]}} has played as of the last stat update, a double. Initialized to 0.
11612+
11613+
: <dfn>[[underrun events]]</dfn>
11614+
::
11615+
The total number of [=underrun events=] that has occurred in playout by {{[[audio context]]}} as of the last stat update, an int. Initialized to 0.
11616+
11617+
: <dfn>[[total duration]]</dfn>
11618+
::
11619+
The total duration in seconds of all frames (including [=underrun frames=]) that {{[[audio context]]}} has played as of the last stat update, a double. Initialized to 0.
11620+
11621+
: <dfn>[[average latency]]</dfn>
11622+
::
11623+
The average playout latency in seconds of frames played by {{[[audio context]]}} over the currently tracked interval, a double. Initialized to 0.
11624+
11625+
: <dfn>[[minimum latency]]</dfn>
11626+
::
11627+
The minimum playout latency in seconds of frames played by {{[[audio context]]}} over the currently tracked interval, a double. Initialized to 0.
11628+
11629+
: <dfn>[[maximum latency]]</dfn>
11630+
::
11631+
The maximum playout latency in seconds of frames played by {{[[audio context]]}} over the currently tracked interval, a double. Initialized to 0.
11632+
11633+
: <dfn>[[latency reset time]]</dfn>
11634+
::
11635+
The time when the latency statistics were last reset, a {{DOMHighResTimeStamp}}.
11636+
</dl>
11637+
11638+
<h4 id="AudioPlayoutStats-constructors">
11639+
Constructors</h4>
11640+
11641+
<dl dfn-type="constructor" dfn-for="AudioPlayoutStats" id="dom-audioplayoutstats-constructor-audioplayoutstats">
11642+
: <dfn>AudioPlayoutStats(context)</dfn>
11643+
::
11644+
Run the following steps:
11645+
1. Set {{[[audio context]]}} to <code>context</code>.
11646+
1. Set {{[[latency reset time]]}} to 0.
11647+
11648+
<pre class=argumentdef for="AudioPlayoutStats/constructor()">
11649+
context: The {{AudioContext}} this new {{AudioPlayoutStats}} will be associated with.
11650+
</pre>
11651+
</dl>
11652+
11653+
<h4 id="AudioPlayoutStats-attributes">
11654+
Attributes</h4>
11655+
11656+
Note: These attributes update only once per second and under specific conditions. See the <a href="#update-audio-stats">update audio stats</a> algorithm and <a href="#AudioPlayoutStats-mitigations">privacy mitigations</a> for details.
11657+
11658+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11659+
: <dfn>underrunDuration</dfn>
11660+
::
11661+
Measures the duration of [=underrun frames=] played by the {{AudioContext}}, in seconds.
11662+
This metric can be used together with {{totalDuration}} to
11663+
calculate the percentage of played out media that was not provided by the {{AudioContext}}.
11664+
11665+
Returns the value of the {{[[underrun duration]]}} internal slot.
11666+
11667+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11668+
: <dfn>underrunEvents</dfn>
11669+
::
11670+
Measures the number of [=underrun events=] that have occurred during playout by the {{AudioContext}}.
11671+
11672+
Returns the value of the {{[[underrun events]]}} internal slot.
11673+
11674+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11675+
: <dfn>totalDuration</dfn>
11676+
::
11677+
Measures the total duration of all audio played by the {{AudioContext}}, in seconds.
11678+
11679+
Returns the value of the {{[[total duration]]}} internal slot.
11680+
11681+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11682+
: <dfn>averageLatency</dfn>
11683+
::
11684+
The average playout latency, in seconds, for audio played since the
11685+
last call to {{resetLatency()}}, or since the creation of the {{AudioContext}} if
11686+
{{resetLatency()}} has not been called.
11687+
11688+
Returns the value of the {{[[average latency]]}} internal slot.
11689+
11690+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11691+
: <dfn>minimumLatency</dfn>
11692+
::
11693+
The minimum playout latency, in seconds, for audio played since the
11694+
last call to {{resetLatency()}}, or since the creation of the {{AudioContext}} if
11695+
{{resetLatency()}} has not been called.
11696+
11697+
Returns the value of the {{[[minimum latency]]}} internal slot.
11698+
11699+
<dl dfn-type=attribute dfn-for="AudioPlayoutStats">
11700+
: <dfn>maximumLatency</dfn>
11701+
::
11702+
The maximum playout latency, in seconds, for audio played since the
11703+
last call to {{resetLatency()}}, or since the creation of the {{AudioContext}} if
11704+
{{resetLatency()}} has not been called.
11705+
11706+
Returns the value of the {{[[maximum latency]]}} internal slot.
11707+
11708+
<h4 id="AudioPlayoutStats-methods">
11709+
Methods</h4>
11710+
11711+
<dl dfn-type=method dfn-for="AudioPlayoutStats">
11712+
: <dfn>resetLatency()</dfn>
11713+
::
11714+
Sets the start of the interval that latency stats are tracked over to the current time.
11715+
When {{resetLatency}} is called, run the following steps:
11716+
11717+
1. Set {{[[latency reset time]]}} to {{BaseAudioContext/currentTime}}.
11718+
1. Let <var>currentLatency</var> be the playout latency of the last frame played by
11719+
{{[[audio context]]}}, or 0 if no frames have been played out yet.
11720+
1. Set {{[[average latency]]}} to <var>currentLatency</var>.
11721+
1. Set {{[[minimum latency]]}} to <var>currentLatency</var>.
11722+
1. Set {{[[maximum latency]]}} to <var>currentLatency</var>.
11723+
11724+
<h4> Updating the stats</h4>
11725+
<div id="update-audio-stats" algorithm="update audio stats">
11726+
Once per second, execute the <a href="#update-audio-stats">update audio stats</a> algorithm:
11727+
1. If {{[[audio context]]}} is not running, abort these steps.
11728+
1. Let <var>canUpdate</var> be false.
11729+
1. Let <var>document</var> be the current [=this=]'s [=relevant global object=]'s [=associated Document=].
11730+
If <var>document</var> is [=Document/fully active=] and <var>document</var>'s [=Document/visibility state=] is `"visible"`, set <var>canUpdate</var> to true.
11731+
1. Let <var>permission</var> be the [=permission state=] for the permission associated with [="microphone"=] access.
11732+
If <var>permission</var> is "granted", set <var>canUpdate</var> to true.
11733+
1. If <var>canUpdate</var> is false, abort these steps.
11734+
1. Set {{[[underrun duration]]}} to the total duration of all [=underrun frames=] (in seconds) that
11735+
{{[[audio context]]}} has played since its construction.
11736+
1. Set {{[[underrun events]]}} to the number of times that {{[[audio context]]}}
11737+
has played an [=underrun frame=] after a non-underrun frame since its construction.
11738+
1. Set {{[[total duration]]}} to the total duration of all frames (in seconds) that
11739+
{{[[audio context]]}} has played since its construction.
11740+
1. Set {{[[average latency]]}} to the average playout latency (in seconds) of frames that
11741+
{{[[audio context]]}} has played since {{[[latency reset time]]}}.
11742+
1. Set {{[[minimum latency]]}} to the minimum playout latency (in seconds) of frames that
11743+
{{[[audio context]]}} has played since {{[[latency reset time]]}}.
11744+
1. Set {{[[maximum latency]]}} to the maximum playout latency (in seconds) of frames that
11745+
{{[[audio context]]}} has played since {{[[latency reset time]]}}.
11746+
</div>
11747+
11748+
<h4>Privacy considerations of glitch stats</h4>
11749+
11750+
<h5>Risk</h5>
11751+
Audio underrun information could be used to form a cross-site
11752+
covert channel between two cooperating sites.
11753+
One site could transmit information by intentionally causing audio glitches
11754+
(by causing very high CPU usage, for example) while the other site
11755+
could detect these glitches.
11756+
<h5 id="AudioPlayoutStats-mitigations">Mitigations</h5>
11757+
To inihibit the usage of such a covert channel, the API implements these mitigations.
11758+
- The values returned by the API should not be updated more than once per second.
11759+
- The api should be restricted to sites fulfill at least one of the following criteria:
11760+
1. The site has obtained <a href="https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia">getUserMedia</a> permission.
11761+
11762+
Note: The reasoning is that if a site has obtained <a href="https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia">getUserMedia</a> permission, it can receive glitch information or communicate efficiently through use of the microphone, making access to the information provided by {{AudioPlayoutStats}} redundant. These options include detecting glitches through gaps in the microphone signal, or communicating using human-inaudible sine waves. If microphone access is ever made safer in this regard, this condition should be reconsidered.
11763+
1. The document is [=Document/fully active=] and its [=Document/visibility state=] is `"visible"`.
11764+
11765+
Note: Assuming that neither cooperating site has microphone permission, this criteria ensures that the site that receives the covert signal must be visible, restricting the conditions under which the covert channel can be used. It makes it impossible for sites to communicate with each other using the covert channel while not visible.
11766+
11767+
<h4>Usage example</h4>
11768+
This example shows how the {{AudioPlayoutStats}} can be used to calculate audio underrun and latency statistics,
11769+
and what the statistics might be used for.
11770+
<pre line-numbers class="example" highlight="js">
11771+
var oldTotalDuration = audioContext.playoutStats.totalDuration;
11772+
var oldUnderrunDuration = audioContext.playoutStats.underrunDuration;
11773+
var oldUnderrunEvents = audioContext.playoutStats.underrunEvents;
11774+
audioContext.playoutStats.resetLatency();
11775+
11776+
// Wait while playing audio
11777+
...
11778+
11779+
// the number of seconds that were covered by the frames played by the output device between the two executions.
11780+
let deltaTotalDuration = audioContext.playoutStats.totalDuration - oldTotalDuration;
11781+
let deltaUnderrunDuration = audioContext.playoutStats.underrunDuration - oldUnderrunDuration;
11782+
let deltaUnderrunEvents = audioContext.playoutStats.underrunEvents - oldUnderrunEvents;
11783+
11784+
// underrun fraction stat over the last deltaTotalDuration seconds
11785+
let underrunFraction = deltaUnderrunDuration / deltaTotalDuration;
11786+
// underrun frequency stat over the last deltaTotalDuration seconds
11787+
let underrunFrequency = deltaUnderrunEvents / deltaTotalDuration;
11788+
// Average playout delay stat during the last deltaTotalDuration seconds
11789+
let playoutDelay = audioContext.playoutStats.averageLatency;
11790+
11791+
if (underrunFrequency > 0) {
11792+
// Do something to prevent audio glitches, like lowering WebAudio graph complexity
11793+
}
11794+
if (playoutDelay > 0.2) {
11795+
// Do something to reduce latency, like suggesting alternative playout methods
11796+
}
11797+
</pre>
11798+
1153911799
<h2 id="processing-model">
1154011800
Processing model</h2>
1154111801

0 commit comments

Comments
 (0)