# AudioContext sample rate fingerprint: what it reveals

> AudioContext.sampleRate is the rate your output device runs at, usually 44100 or 48000. As a fingerprint it is one bit, and changing it is easy to catch.

Source: https://mosaicbrowser.com/blog/audio-sample-rate-fingerprint

## What AudioContext.sampleRate says about your speakers

AudioContext.sampleRate is the rate your output device runs at, usually 44100 or 48000. As a fingerprint it is one bit, and changing it is easy to catch.

![48000: One line of script reads the rate your speakers run at](https://mosaicbrowser.com/covers/audio-sample-rate-fingerprint.webp)

`AudioContext.sampleRate` tells a page how many samples per second your audio output runs at, and for a live context that number comes from the output device: the built-in speakers, the headphones, the USB interface. Most desktops report 44100 or 48000. As an audio sample rate fingerprint it is worth about one bit, but it costs one line of script, and it has to agree with everything else a page knows about the machine.

## Where the number comes from

The Web Audio specification is short on this point. When a page creates an `AudioContext` and does not ask for a rate, the context uses the preferred sample rate of the output device. The browser asks the operating system what that device is running at and renders at that rate, so it does not have to resample on the way out.

In practice the operating system holds the setting. On a Mac it is the format shown for the device in Audio MIDI Setup. On Windows it is the default format under the device's properties in Sound settings, such as "2 channel, 24 bit, 48000 Hz". A studio interface may be set to 96000. A Bluetooth headset that switches into its hands-free mode for a call can drop far lower, and a context created during that call reports it.

Three other cases do not read the hardware at all:

- `new AudioContext({ sampleRate: 44100 })` asks for a rate, and the browser honours it and resamples.
- `new OfflineAudioContext(1, 5000, 44100)` renders into memory at whatever rate the page passed. It never touches a device, which is why the [OfflineAudioContext fingerprint](https://mosaicbrowser.com/blog/offlineaudiocontext-explained) measures the maths of the audio stack and not the speakers.
- A machine with no audio output gets whatever default the audio layer falls back to, which is itself a little unusual.

You can read your own in a second. Open the DevTools console on any page and type `new AudioContext().sampleRate`. Then change the output device and run it again.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## What the sample rate fingerprint tells a site

On its own, very little. Two values cover nearly all desktops, so the rate splits visitors into two big groups and names nobody. Fingerprinting scripts collect it anyway, because it is free and because it sits next to things that matter more: the audio latencies, the channel count, and the audio rendering hash.

It also changes. Plug in headphones that run at 44100 on a machine whose speakers run at 48000, and the next context reports the headphones. A site that sees the rate flip between visits has seen someone change devices, which is ordinary. A site that sees a value no device in that class reports, or a rate that disagrees with the sound the page just rendered, has seen something less ordinary.

## Why we stopped changing it

We changed it once, and took the change out. An early version of our JavaScript fallback, the layer that runs on stock Chromium rather than on our own engine, defined its own `sampleRate` on `BaseAudioContext.prototype`. The value came from a table. It contradicted itself three ways, and each is one line for a page to test.

The first was the offline context. `sampleRate` really lives on `BaseAudioContext`, and `OfflineAudioContext` inherits it rather than declaring its own. So a page that wrote `new OfflineAudioContext(1, 5000, 48000)` read 44100 back out of the object it had just built at 48000. No browser does that.

The second was the same mistake on a live context. A real Chrome honours `new AudioContext({ sampleRate: 48000 })`, and ours answered the table's number instead of the one the page had asked for.

The third could not be patched around at all. An `AnalyserNode` splits the signal into frequency bins whose width is the real sample rate divided by the FFT size. Feed it an oscillator at a known frequency, find the loudest bin, and the bin gives back the rate the audio stack is actually rendering at, whatever the getter says. Claiming 44100 on a 48000 machine moves that peak by nearly nine percent, because 48000 is 8.8% more than 44100. The getter can say anything; the sound cannot.

The premise was wrong as well. The table assumed macOS defaults to 44.1 kHz. A stock Chrome on the Mac we were writing on reported 48000.

So the rule we kept is that the rate belongs to the device. Tesseract Chrome, our engine, leaves the live `sampleRate` alone, and the machine it runs on answers. The audio values that do separate two profiles are elsewhere: each profile renders its own stable audio hash, changed in C++ inside the engine and keyed to the profile's seed, which is how [our engine changes values without injected JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript). How that rendering hash is read, and the bug we had in it, are in [an audio spoof that changed the sound every time it was read](https://mosaicbrowser.com/blog/getchanneldata-compounding).

## A value with almost no entropy can still give you away

The lesson generalises past audio. The cheapest values to read are often the cheapest to fake, and the easiest to cross-check, because the browser computes something else from them. Here the rendered sound carries the real rate. A canvas carries the real GPU's rasterisation, and a font list carries the real installed fonts. A low-entropy value left true says almost nothing. The same value faked has to stay consistent with everything computed from it, and a page can check that in one line. The full list of values a page reads, and which of them carry real weight, is in [browser fingerprint](https://mosaicbrowser.com/glossary/browser-fingerprint).

## Questions

### What does AudioContext.sampleRate show?

The number of samples per second the context renders at. For a live AudioContext created without options, that is the preferred rate of the output device, so it describes your speakers or headphones rather than the browser.

### Is the audio sample rate a strong fingerprint?

No. Almost every desktop reports 44100 or 48000, so it splits visitors into two large groups. It is worth more as a consistency check than as an identifier.

### Why did my sample rate change between visits?

Because the output device changed. Plugging in headphones, switching to a USB interface or a Bluetooth headset in call mode can move the rate, and a context made afterwards reports the new device's rate.

### Can a browser fake the audio sample rate?

It can return a different number, but the audio still renders at the real rate. A page can recover the real rate from where a known tone lands in an AnalyserNode's frequency bins, so a faked getter contradicts the sound it produced.

## Related

[How the OfflineAudioContext fingerprint is computed](https://mosaicbrowser.com/blog/offlineaudiocontext-explained): An oscillator through a compressor, rendered silently and summed: the OfflineAudioContext fingerprint reads your machine's floating-point audio path.

[An audio spoof that changed the sound every time it was read](https://mosaicbrowser.com/blog/getchanneldata-compounding): getChannelData returns the buffer's own memory, so our audio noise multiplied samples on every read: v·f, then v·f². A seeded byte fixed it.

[Why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript): A JavaScript spoof is one realm deep. An iframe read the real source of 33 of our patched functions; the same values changed in C++ agree everywhere.

[Browser fingerprint: what a site can read about your machine](https://mosaicbrowser.com/glossary/browser-fingerprint): A browser fingerprint is the set of values a site reads about your browser and computer, which together recognize you without cookies. What goes into one.

[Why hCaptcha gives some browsers a puzzle every time](https://mosaicbrowser.com/blog/hcaptcha-image-challenge-every-time): hCaptcha's challenge is its score. A puzzle every time usually means blocked cookies, a crowded VPN exit or a browser whose values disagree with each other.

[Why every Mac's Chrome says Mac OS X 10_15_7](https://mosaicbrowser.com/blog/mac-os-x-10-15-7): Chrome froze the macOS version in its User-Agent, so every Mac claims 10.15.7. The real one is in Sec-CH-UA-Platform-Version, a hint sites must ask for.

[Why the User-Agent says Windows 10 on Windows 11](https://mosaicbrowser.com/blog/windows-11-user-agent-windows-10): Windows 11 and 10 both send Windows NT 10.0 in the User-Agent. Only the platformVersion client hint, 13 or higher, says Windows 11.

[Why Chrome's User-Agent says 0.0.0](https://mosaicbrowser.com/blog/chrome-user-agent-0-0-0): Chrome zeroes everything after the major, so the User-Agent says 0.0.0 and the build moves to Sec-CH-UA-Full-Version-List. A real build in the UA is a tell.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
