---
title: "Rebuilding MLow: WhatsApp's Voice Codec in Pure Go — WhatsMCP Blog"
description: "How we built a clean-room, pure-Go encoder and decoder for MLow — Meta's low-bitrate codec behind billions of WhatsApp, Messenger and Instagram calls: the hybrid SILK/CELT structure, reverse-engineering it against the live library, and frame-exact validation."
url: "https://whatsmcp.com/blog/mlow-codec-pure-go"
---

Most of the world's phone calls don't run on the codec you think they do. If you build voice software in the West, you probably reach for Opus and never look back. But more than a fifth of WhatsApp calls happen on ARMv7 devices, and tens of millions a day are placed on phones more than a decade old, on networks that would make a video buffer weep. For those calls, Meta built a different codec — **MLow** — and in 2024 it started carrying a very large share of the planet's conversations.

We wrote a clean-room, **pure-Go** implementation of it. This is what MLow is, why it exists, and what it took to rebuild it from the outside — encoder and decoder, no C, validated against the genuine codec frame by frame. The result is open source at **[github.com/whatsmcp/mlow](https://github.com/whatsmcp/mlow)**.

## What MLow actually is

[MLow](https://engineering.fb.com/2024/06/13/web/mlow-metas-low-bitrate-audio-codec/) is Meta's low-bitrate voice codec, now carrying WhatsApp, Messenger, and Instagram calls. Its headline result is blunt: at 6 kbps wideband it scores a **POLQA MOS of 3.9 against Opus's 1.89** — roughly *twice* the perceived quality at the same bitrate — while running at about **10% lower computational complexity**. That second number matters as much as the first. A codec that sounds great but pins a ten-year-old CPU is useless to the people who need it most; MLow's whole design premise is high quality at the lowest bitrates *on the weakest hardware*.

It gets there by saturating quality faster than Opus as bitrate drops. Opus degrades steadily as you starve it; MLow holds a usable, wideband-sounding call together at rates where Opus has already fallen apart. For a user on a congested 3G cell, that is the difference between a conversation and a series of apologies.

![MLow vs Opus quality at 6 kbps wideband: POLQA MOS 3.9 vs 1.89 — about twice the quality — at roughly 10% lower computational complexity.](https://content.whatsmcp.com/uploads/mlow_quality_41a46d193c.png)

The catch, for anyone who wants to interoperate with it, is that **MLow is not in stock libopus**. It's a proprietary fork — internally "mode 1002" — with no published source, no paper, and, crucially, no RTP or SDP mapping. It ships as an ARM-only shared library inside the app. If you want to speak MLow to a real WhatsApp peer, you cannot download a library and link it. You have to rebuild the codec.

## Why pure Go, and why from scratch

Two constraints shaped every decision.

The first is that there is no fast C library to bind. Meta's `libopus_mlow.so` is aarch64/bionic — Android only. There is no x86-64 build and no source, so cgo can't load it on a server; your options are a pure reimplementation or nothing. "Just call the native lib" was never on the table.

The second is a hard project rule: **`CGO_ENABLED=0`**. A pure-Go codec cross-compiles to any target, survives static analysis and binary obfuscation, and lifts cleanly out of a larger program as a zero-dependency library. The moment you introduce cgo you lose all of that, and you inherit a C toolchain on every build. So the codec is written in Go, top to bottom — no cgo, no third-party dependencies, `Go 1.25+`, mono — and it builds and tests clean with the C compiler switched off entirely.

That combination — no bindable library *and* no cgo — means the only path is a from-scratch, pure-Go reimplementation validated against the real thing. Which is exactly what we built.

## The shape of the codec

The first surprise of reverse-engineering MLow is that it isn't one codec. It's a **hybrid**, and like stock Opus it chooses per packet: the leading TOC (table-of-contents) byte selects the mode, with config values 0–11 behaving like SILK and 16–31 like CELT.

- **The primary 1:1-voice path is a SILK/CELP speech codec** — internally "SMPL" — carried as TOC `0x50`: 60 ms frames, wideband, at around 16 kbps. This is what the overwhelming majority of a real call is made of. It's a fork of SILK (RFC 6716) with MLow-specific DSP: a continuous sigmoid-and-power gain dequantizer instead of SILK's log-to-linear one, an MLow-specific long-term-prediction comb postfilter, sub-band recombination, and its own noise and NLSF handling.
- **A CELT/MDCT transform path** handles a minority of frames (TOC `0xb8` wideband, `0x98` narrowband) at 20 ms. This part turns out to be about 85–90% equivalent to the public, BSD-3-licensed Opus CELT: the same range coder, pyramid vector quantization, band-energy coding, bit allocation, inverse MDCT, comb post-filter, and packet-loss concealment. Meta's deviations here are purely configuration — mono only, fixed 20 ms frames, and a TOC-to-band-set selection instead of an on-wire mode parse.

Both modes share the Opus range coder, and both are stateful across frames — inter-frame prediction, overlap-add, comb and PLC history — so you run exactly one encoder and one decoder per call, never concurrently.

![MLow's hybrid structure: the leading TOC byte selects the mode — SILK/CELP 'SMPL' (0x50, 60 ms wideband, the primary voice path) or a CELT/MDCT transform (0xb8/0x98, 20 ms), over a shared Opus range coder.](https://content.whatsmcp.com/uploads/mlow_structure_4dedae95c4.png)

The module mirrors that structure. A top-level package presents the public hybrid API and the receive dispatcher — it unwraps the `0x86` RED (redundancy) and multi-frame containers, reads each inner TOC, and routes SILK inners to the speech path and everything else to the transform path. Underneath sit the `smpl` speech codec, the `celt` transform codec, a clean-room `silk` decoder, and the shared range coder. Zero non-stdlib imports across all of it.

## Clean-room, and checked against the genuine codec

Rebuilding a codec you can't read is only worth doing if you can prove the result is *correct* — that a real WhatsApp peer decodes your packets to the right audio, and that you decode theirs. So the entire effort was built around ground truth.

Two public projects made that possible without guesswork: the Rust library **[whatsapp-rust](https://github.com/oxidezap/whatsapp-rust)** carries the canonical, byte-exact MLow implementation, and **[meowcaller](https://github.com/purpshell/meowcaller)** ports it 1:1 to Go. Our speech path (`smpl`) is a byte-exact vendoring validated against that reference — **295 of 295 frames identical** — so its output is exactly what a genuine peer renders. The transform path was validated a second way, against golden vectors captured from the live library on-device: **CELT decode is bit-exact at roughly 80 dB SNR** (wideband, narrowband, and packet-loss concealment all matching), and **CELT encode produces packets the genuine WhatsApp decoder renders to within ±2 LSB** — the last bit of rounding, not a structural difference.

![Clean-room validation: the reference chain whatsapp-rust to meowcaller to our pure-Go implementation, validated at 295/295 byte-exact speech frames, ~80 dB CELT decode SNR, and encode within +/-2 LSB of the genuine decoder.](https://content.whatsmcp.com/uploads/mlow_re_3518acd838.png)

The single most valuable lesson came from *how* we got there. An early, static-only reading of the library confidently concluded MLow's primary decoder was a SILK CELP codec. A later dynamic oracle — driving the real `libopus_mlow.so` inside the running app and dumping its internal buffers stage by stage — proved that guess wrong: for the transform frames the SILK code merely coexists in the fork and never runs. The same oracle refuted a second confident static guess about a coding constant. The rule we took away, and now apply everywhere: **when you can capture the real thing running, do — a live oracle beats a careful read of the source you don't have.** And for the final say on quality, we validate by ear, because offline correlation metrics over-fit and a number that looks great can still hiss.

## Fast enough that performance stops being the question

The reflexive objection to "a codec in Go" is speed. In practice it evaporated.

The live speech encoder runs at about **4.1 ms per 60 ms frame** — roughly **14× faster than real time** — after a round of optimization that cut it more than fivefold from the first working version. Decode is faster still. A whole call costs a few percent of one core and tens of megabytes of RSS, most of it spent in the *other* codec on the bridge, not MLow.

![Pure-Go performance: the encoder runs about 4.1 ms per 60 ms frame (~14x real time), 5.2x faster after optimization, ~363 allocations per frame, with zero non-stdlib dependencies and CGO_ENABLED=0.](https://content.whatsmcp.com/uploads/mlow_perf_8d21a85fca.png)

The optimizations were the ordinary, satisfying kind. A CPU profile showed the encoder spending nearly half its time in `math.cos` and `math.sin`, because the recursive FFT recomputed its twiddle factors on every butterfly of every frame; precomputing them once per size erased almost all of it. The next profile found the same bug class again — constant tables (a DCT basis, analysis windows) rebuilt every frame despite depending on nothing — and fixing those, plus per-stream scratch buffers to kill per-frame allocations, took the encoder from ~14,000 allocations per frame to a few hundred. Every one of those changes was made under a hard constraint: the scalar Go path is canonical and must stay **bit-exact**, so the range coder stays integer-exact and no optimization is allowed to desync the bitstream. What remains on the profile is pure DSP — the codebook search, the filters, the FFT butterflies — which is exactly the region SIMD would target and exactly the region it *can't* touch without changing float rounding. That's the practical floor for a wire-exact, pure-Go encoder, and it's already an order of magnitude inside the real-time budget.

## Where a codec like this fits

MLow's only real value is talking to WhatsApp, and WhatsApp doesn't speak SIP — MLow has no `a=rtpmap` name, no published RTP payload format, and no other endpoint on earth that decodes it. So it doesn't belong *inside* a PBX. It belongs in the bridge: the SIP leg speaks G.711 or Opus to Asterisk, and the bridge transcodes to and from MLow on the WhatsApp leg only. That keeps the codec a clean, embeddable library and the PBX blissfully unaware that anything unusual is happening.

Machine learning, similarly, goes *around* the codec rather than into it — neural rate control driving the bitrate from network telemetry, a pre-encode denoiser, and, the biggest quality lever of all, post-decode neural enhancement. The DSP core stays deterministic and `CGO_ENABLED=0`; the ML runs out-of-process or behind its own optional boundary. The codec's job is to be small, correct, and fast, and to get out of the way.

## Provenance and license

The transform path is derived from the public **Opus reference implementation** (BSD-3-licensed, © the Xiph.Org Foundation and contributors); the MLow-specific tables and deviations were recovered by clean-room reverse-engineering of behavior, and the speech path is validated against the MIT-licensed whatsapp-rust and meowcaller reference implementations. Full per-component provenance, the wire format, encoder and decoder internals, and the benchmark suite live in the repository.

If you work on WhatsApp interoperability, voice bridging, or just want to see what a modern speech codec looks like with the C stripped away and every stage in readable Go, the code is at **[github.com/whatsmcp/mlow](https://github.com/whatsmcp/mlow)**.

---

### Further reading

- [MLow: Meta's low bitrate audio codec](https://engineering.fb.com/2024/06/13/web/mlow-metas-low-bitrate-audio-codec/) — Engineering at Meta
- [github.com/whatsmcp/mlow](https://github.com/whatsmcp/mlow) — our clean-room, pure-Go encoder + decoder
- [whatsapp-rust](https://github.com/oxidezap/whatsapp-rust) — the canonical MLow reference implementation (Rust)
- [meowcaller](https://github.com/purpshell/meowcaller) — a 1:1 Go port of the reference
