# audio

> A short sound rendered offline through a compressor; the last 500 samples are reported as a digest. Nothing is played.

Source: https://whiskerjs.com/signals/audio/ (last updated 2026-10-26)

**audio at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `audio` |
| In the visitorId | yes |
| structuredId group | [audio](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | always a digest (first 128 bits of SHA-256) |
| Excluded under | Brave (brave_farbling); Safari Advanced Fingerprinting Protection (safari_afp) |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 1](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> WhiskerJS asks the browser to compute a short test tone and pass it through a sound compressor, all in memory, without playing anything. The exact numbers that come out depend on how the browser, the operating system and the processor do floating-point maths, so they differ slightly between devices. WhiskerJS keeps only a short code (a digest) of the end of the sound.

## What it reads

`audio` creates an `OfflineAudioContext` (or the older `webkitOfflineAudioContext`) with 1 channel, 5000 frames at 44100 Hz. Inside it:

- an oscillator with type `triangle` at 10000 Hz;
- a dynamics compressor with threshold -50, knee 40, ratio 12, attack 0 and release 0.25;
- oscillator to compressor to the context’s destination.

It starts the oscillator, renders the buffer with `startRendering()`, takes channel 0 and keeps the last 500 samples as 32-bit floats. Those 2000 bytes are hashed.

## How the technique works

An offline audio context computes sound as fast as it can into memory instead of sending it to speakers. The triangle wave is a simple, known input; the compressor applies non-linear gain changes that exercise the audio engine’s maths. Small differences in how engines implement these nodes, and in the CPU’s floating-point behaviour, show up in the last digits of the samples. If all 500 samples are zero, the render produced silence (a sign of blocking), and the component is reported `blocked` / `silent` instead of hashing zeros.

Before the samples are reported, WhiskerJS waits for the audio [known-answer probe](https://whiskerjs.com/signals/noise-profile/): a second `OfflineAudioContext` (1024 frames at 44100 Hz) renders a `ConstantSourceNode` at 0.5, and its channel data is read twice. Every sample must be exactly 0.5, a value 32-bit floats hold exactly. If the probe comes back `seeded` or `per_read`, the browser adds noise to audio reads and `audio` reports `noisy` with ruleId `seeded_noise`.

## Why it helps

The audio stack differs between browser engines (Chromium, Gecko, WebKit), their versions, operating systems and CPU families, while the same setup usually produces exactly the same samples. It is the only member of the `audio` group of the [structuredId](https://whiskerjs.com/glossary/#structured-id), so a change in that part points straight at the audio stack.

## Stability

It can change with a browser update that touches the Web Audio implementation, an operating system update, or a change of CPU architecture. WhiskerJS renders the test tone once only: unlike `canvas` and `webgl_image`, there is no double render. Noise is caught instead by the audio known-answer probe (since signalsVersion 6): a browser or extension that alters audio reads makes the probe fail and the component `noisy`, which is the same in every session, so the id changes once, when the noise is switched on or off, instead of on every run. Browsers known to add noise on purpose are also handled through the [exclusion table](https://whiskerjs.com/docs/how-it-works/#the-exclusion-table).

## Browser support and what blocks it

- `ok` in all current browsers with Web Audio: [Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Edge](https://whiskerjs.com/browsers/edge/), [Firefox](https://whiskerjs.com/browsers/firefox/), [Safari](https://whiskerjs.com/browsers/safari-macos/) and mobile browsers.
- [Brave](https://whiskerjs.com/browsers/brave/): `excluded` / `brave_farbling`.
- [Safari with Advanced Fingerprinting Protection](https://whiskerjs.com/browsers/safari-macos/): `excluded` / `safari_afp`.
- Not excluded under [Firefox resistFingerprinting, Tor Browser and Mullvad Browser](https://whiskerjs.com/browsers/firefox-rfp-and-tor/) or Firefox Fingerprinting Protection.
- `noisy` / `seeded_noise` where the audio known-answer probe sees noise that no exclusion row covers.
- `blocked` / `silent` when a blocker makes the render return only zeros.

## Privacy

The output is a computed test tone, not a recording: no microphone, no playback, no audio device. It reveals how the device computes audio and leaves only as a 32-character digest. No permission, no prompt, no request.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` | none | rendered; value is a digest |
| `unavailable` | `no_api` | no `OfflineAudioContext` |
| `blocked` | `no_buffer` | the render gave no buffer |
| `blocked` | `short_buffer` | fewer than 5000 samples |
| `blocked` | `silent` | the last 500 samples were all zero |
| `noisy` | `seeded_noise` | the audio known-answer probe did not read back exactly 0.5 |
| `blocked` | `security_error`, `threw` | creating or rendering the context threw or rejected |
| `unavailable` | `no_digest` | no Web Crypto |
| `excluded` | `brave_farbling`, `safari_afp`, `caller_excluded` | exclusion table or your list |
| `timed_out` | `timeout`, `aborted` | 1000 ms inner timeout, budget, abort |

## Example value

From a headless Chromium test browser in a container:

```json
"audio": { "status": "ok", "value": "40febf1c331d3bd4c550bd46ce0e1813", "hashed": true }
```

## Questions and answers

### Does the audio signal play a sound or use the microphone?

No. The sound is rendered offline into a buffer in memory. No audio device is opened, nothing is played and the microphone is never touched.

### Why is audio excluded in Brave and Safari?

Brave adds per-site, per-session noise to audio reads, and so does Safari with Advanced Fingerprinting Protection. Reading it would only make the id change, so the exclusion table skips it and reports brave_farbling or safari_afp.

### Is audio checked for noise like canvas?

Yes, since signalsVersion 6, but not with a double render. The audio known-answer probe renders a constant level of 0.5 offline and reads it back twice; every sample must be exactly 0.5. When it is not, audio reports noisy with ruleId seeded_noise instead of a digest that would change every session.

## Related pages

- [audio_latency](https://whiskerjs.com/signals/audio-latency/): The output latency, outside the ids.
- [Offline audio rendering](https://whiskerjs.com/glossary/#offline-audio-rendering): How a page renders sound without playing it.
- [Farbling](https://whiskerjs.com/glossary/#farbling): Brave's per-site randomisation.
- [noise_profile](https://whiskerjs.com/signals/noise-profile/): The known-answer probes, including the audio one.
- [Brave](https://whiskerjs.com/browsers/brave/): Where audio is excluded.
- [The exclusion table](https://whiskerjs.com/docs/how-it-works/#the-exclusion-table): Which browsers skip which signals.
